aaa
This commit is contained in:
parent
f42ba94af4
commit
d47294bf9e
9
package-lock.json
generated
9
package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"lucide-svelte": "^0.373.0",
|
"lucide-svelte": "^0.373.0",
|
||||||
"mode-watcher": "^0.3.0",
|
"mode-watcher": "^0.3.0",
|
||||||
"svelte-radix": "^1.1.0",
|
"svelte-radix": "^1.1.0",
|
||||||
|
"svelte-sonner": "^0.3.22",
|
||||||
"sveltekit-superforms": "^2.12.6",
|
"sveltekit-superforms": "^2.12.6",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwind-variants": "^0.2.1",
|
"tailwind-variants": "^0.2.1",
|
||||||
@ -3043,6 +3044,14 @@
|
|||||||
"svelte": "^3.54.0 || ^4.0.0 || ^5.0.0"
|
"svelte": "^3.54.0 || ^4.0.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svelte-sonner": {
|
||||||
|
"version": "0.3.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.22.tgz",
|
||||||
|
"integrity": "sha512-1AEBl7rTP4oeMAmBmkcvoHNOwB8gPzz73RYApcY8pyDwbjBewU8ATnXV8N42omV1sQvtSX/X0o5A1nfkN3T6cg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": ">=3 <5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sveltekit-superforms": {
|
"node_modules/sveltekit-superforms": {
|
||||||
"version": "2.12.6",
|
"version": "2.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.12.6.tgz",
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"lucide-svelte": "^0.373.0",
|
"lucide-svelte": "^0.373.0",
|
||||||
"mode-watcher": "^0.3.0",
|
"mode-watcher": "^0.3.0",
|
||||||
"svelte-radix": "^1.1.0",
|
"svelte-radix": "^1.1.0",
|
||||||
|
"svelte-sonner": "^0.3.22",
|
||||||
"sveltekit-superforms": "^2.12.6",
|
"sveltekit-superforms": "^2.12.6",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwind-variants": "^0.2.1",
|
"tailwind-variants": "^0.2.1",
|
||||||
|
@ -2,7 +2,6 @@ import { config } from '$lib/index.js';
|
|||||||
|
|
||||||
/** @type {import('@sveltejs/kit').HandleFetch} */
|
/** @type {import('@sveltejs/kit').HandleFetch} */
|
||||||
export async function handleFetch({ event, request, fetch }) {
|
export async function handleFetch({ event, request, fetch }) {
|
||||||
console.log(event.request.headers.get("cookie"))
|
|
||||||
if (request.url.startsWith(config.api_url)) {
|
if (request.url.startsWith(config.api_url)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
request.headers.set('cookie', event.request.headers.get('cookie'));
|
request.headers.set('cookie', event.request.headers.get('cookie'));
|
||||||
|
1
src/lib/components/ui/sonner/index.js
Normal file
1
src/lib/components/ui/sonner/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Toaster } from "./sonner.svelte";
|
18
src/lib/components/ui/sonner/sonner.svelte
Normal file
18
src/lib/components/ui/sonner/sonner.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
import { Toaster as Sonner } from "svelte-sonner";
|
||||||
|
import { mode } from "mode-watcher";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sonner
|
||||||
|
theme={$mode}
|
||||||
|
class="toaster group"
|
||||||
|
toastOptions={{
|
||||||
|
classes: {
|
||||||
|
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||||
|
description: "group-[.toast]:text-muted-foreground",
|
||||||
|
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||||
|
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
@ -1,3 +1,3 @@
|
|||||||
export const config = {
|
export const config = {
|
||||||
api_url: "http://api.omersabic.com:3001"
|
api_url: "http://localhost:3000"
|
||||||
}
|
}
|
20
src/routes/(app)/+layout.server.js
Normal file
20
src/routes/(app)/+layout.server.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { config } from "$lib"
|
||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
/** @type {import("./$types").LayoutServerLoad} */
|
||||||
|
export const load = async ({fetch, cookies}) => {
|
||||||
|
if(!cookies.get("token")) return redirect(302, "/auth")
|
||||||
|
const res = await fetch(config.api_url+"/me")
|
||||||
|
if(res.status > 399 && res.status < 499) {
|
||||||
|
cookies.delete("token", {
|
||||||
|
path: "/"
|
||||||
|
});
|
||||||
|
|
||||||
|
redirect(302, "/auth");
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
me: data
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.pcss';
|
import '../../app.pcss';
|
||||||
|
|
||||||
import Menu from 'lucide-svelte/icons/menu';
|
import Menu from 'lucide-svelte/icons/menu';
|
||||||
import Package2 from 'lucide-svelte/icons/package-2';
|
import Package2 from 'lucide-svelte/icons/package-2';
|
||||||
@ -14,11 +14,13 @@
|
|||||||
import { Button } from '$lib/components/ui/button/index.js';
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
|
|
||||||
import { ModeWatcher, toggleMode } from 'mode-watcher';
|
import { ModeWatcher, toggleMode } from 'mode-watcher';
|
||||||
|
import { Toaster } from "$lib/components/ui/sonner";
|
||||||
|
|
||||||
/** @type {import('./$types').LayoutServerData} */
|
/** @type {import('./$types').LayoutServerData} */
|
||||||
export let data;
|
export let data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Toaster />
|
||||||
<ModeWatcher />
|
<ModeWatcher />
|
||||||
|
|
||||||
<div class="flex min-h-screen w-full flex-col">
|
<div class="flex min-h-screen w-full flex-col">
|
50
src/routes/(app)/articles/+page.server.js
Normal file
50
src/routes/(app)/articles/+page.server.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { config } from "$lib"
|
||||||
|
import { superValidate } from "sveltekit-superforms";
|
||||||
|
import { formSchema } from "./schema";
|
||||||
|
import { zod } from "sveltekit-superforms/adapters";
|
||||||
|
import { fail } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
/** @type {import("./$types").PageServerLoad} */
|
||||||
|
export const load = async ({ fetch }) => {
|
||||||
|
const blogRes = await fetch(config.api_url + "/blog?mine=true", {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
const videosRes = await fetch(config.api_url + "/videos", {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataBlog = await blogRes.json();
|
||||||
|
const dataVideos = await videosRes.json();
|
||||||
|
return {
|
||||||
|
articles: dataBlog.articles,
|
||||||
|
videos: dataVideos.videos,
|
||||||
|
form: await superValidate(zod(formSchema)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import("@sveltejs/kit").Actions} */
|
||||||
|
export const actions = {
|
||||||
|
default: async (event) => {
|
||||||
|
console.log("creating article...")
|
||||||
|
const form = await superValidate(event, zod(formSchema));
|
||||||
|
console.log(form.data)
|
||||||
|
console.log(form.valid)
|
||||||
|
if (!form.valid) {
|
||||||
|
return fail(400, {
|
||||||
|
form,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const res = await event.fetch(config.api_url + "/blog/create", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(form.data)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(res.status)
|
||||||
|
console.log(await res.json())
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
@ -10,11 +10,14 @@
|
|||||||
import TooltipButton from '$lib/components/molecules/tooltipbutton.svelte';
|
import TooltipButton from '$lib/components/molecules/tooltipbutton.svelte';
|
||||||
import ProBadge from '$lib/components/molecules/probadge.svelte';
|
import ProBadge from '$lib/components/molecules/probadge.svelte';
|
||||||
|
|
||||||
import { formSchema } from "./schema";
|
import { formSchema } from './schema';
|
||||||
import { superForm } from 'sveltekit-superforms';
|
import { superForm } from 'sveltekit-superforms';
|
||||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
/** @type {import("./$types").PageData} */
|
/** @type {import("./$types").PageData} */
|
||||||
export let data;
|
export let data;
|
||||||
|
let isDialogOpen = false;
|
||||||
|
|
||||||
const form = superForm(data.form, {
|
const form = superForm(data.form, {
|
||||||
validators: zodClient(formSchema)
|
validators: zodClient(formSchema)
|
||||||
@ -22,7 +25,12 @@
|
|||||||
|
|
||||||
const { form: formData, enhance } = form;
|
const { form: formData, enhance } = form;
|
||||||
|
|
||||||
const invoices = [
|
function submitArticle() {
|
||||||
|
isDialogOpen = false;
|
||||||
|
toast('Article is queued for generation.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const articles = [
|
||||||
{
|
{
|
||||||
id: 'AG64NE',
|
id: 'AG64NE',
|
||||||
title: 'Nullam ornare ornare orci a auctor.',
|
title: 'Nullam ornare ornare orci a auctor.',
|
||||||
@ -59,12 +67,13 @@
|
|||||||
source: 'Youtube'
|
source: 'Youtube'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto max-w-[1500px]">
|
<div class="mx-auto w-full max-w-[1000px]">
|
||||||
<Dialog.Root>
|
<Dialog.Root bind:open={isDialogOpen}>
|
||||||
<Dialog.Trigger class={buttonVariants({ variant: 'default' })}>Create Article</Dialog.Trigger>
|
<Dialog.Trigger class={buttonVariants({ variant: 'default' })}>Create Article</Dialog.Trigger>
|
||||||
<Dialog.Content class="sm:max-w-[750px]">
|
<Dialog.Content class="w-full sm:max-w-[750px]">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Create Article</Dialog.Title>
|
<Dialog.Title>Create Article</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
@ -72,19 +81,41 @@
|
|||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<form method="POST" use:enhance name="blog-converter">
|
<form
|
||||||
<Form.Field {form} name="email">
|
method="POST"
|
||||||
|
use:enhance
|
||||||
|
name="blog-converter"
|
||||||
|
id="blog-converter"
|
||||||
|
on:submit={submitArticle}
|
||||||
|
>
|
||||||
|
<Form.Field {form} name="video_id">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
<div class="grid gap-4 py-4">
|
<div class="grid gap-4 py-4">
|
||||||
<div class="grid grid-cols-4 items-center gap-4">
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
<Form.Label for="youtube_url" class="text-right">Youtube video URL*</Form.Label>
|
<Form.Label for="video_id" class="text-right">Youtube video*</Form.Label>
|
||||||
<Input
|
<!-- <Input
|
||||||
id="youtube_url"
|
id="youtube_url"
|
||||||
placeholder="www.youtube.com/watch?v=..."
|
placeholder="www.youtube.com/watch?v=..."
|
||||||
class="col-span-3"
|
class="col-span-3"
|
||||||
/>
|
/> -->
|
||||||
|
<Select.Root>
|
||||||
|
<Select.Trigger class="w-[300px]" {...attrs}>
|
||||||
|
<Select.Value />
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Group>
|
||||||
|
{#each data.videos as video}
|
||||||
|
<Select.Item
|
||||||
|
value={video.snippet.resourceId.videoId}
|
||||||
|
label={video.snippet.title}>{video.snippet.title}</Select.Item
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</Select.Group>
|
||||||
|
</Select.Content>
|
||||||
|
<Select.Input bind:value={$formData.video_id} name={attrs.name} />
|
||||||
|
</Select.Root>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-4 items-center gap-4">
|
<!-- <div class="grid grid-cols-4 items-center gap-4">
|
||||||
<Form.Label for="length" class="text-right">Article length</Form.Label>
|
<Form.Label for="length" class="text-right">Article length</Form.Label>
|
||||||
<Select.Root portal={null} name="length">
|
<Select.Root portal={null} name="length">
|
||||||
<Select.Trigger class="w-[300px]">
|
<Select.Trigger class="w-[300px]">
|
||||||
@ -92,23 +123,23 @@
|
|||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
<Select.Group>
|
<Select.Group>
|
||||||
<Select.Item value="short" label="Short (~700 words)"
|
<Select.Item value="700" label="Short (~700 words)"
|
||||||
>Short (~700 words)</Select.Item
|
>Short (~700 words)</Select.Item
|
||||||
>
|
>
|
||||||
<Select.Item value="medium" label="Medium (~1500 words)"
|
<Select.Item value="1500" label="Medium (~1500 words)"
|
||||||
>Medium (~1500 words)<ProBadge /></Select.Item
|
>Medium (~1500 words)<ProBadge /></Select.Item
|
||||||
>
|
>
|
||||||
<Select.Item value="long" label="Long (~2500 words)"
|
<Select.Item value="2500" label="Long (~2500 words)"
|
||||||
>Long (~2500 words)<ProBadge /></Select.Item
|
>Long (~2500 words)<ProBadge /></Select.Item
|
||||||
>
|
>
|
||||||
</Select.Group>
|
</Select.Group>
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
<Select.Input name="blogLength" />
|
<Select.Input {...attrs} />
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-4 items-center gap-4">
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
<Form.Label for="length" class="text-right">Format</Form.Label>
|
<Form.Label for="format" class="text-right">Format</Form.Label>
|
||||||
<Select.Root portal={null} name="length">
|
<Select.Root portal={null} name="format">
|
||||||
<Select.Trigger class="w-[200px]">
|
<Select.Trigger class="w-[200px]">
|
||||||
<Select.Value />
|
<Select.Value />
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
@ -123,9 +154,9 @@
|
|||||||
<Select.Item value="tutorial" label="Tutorial">Tutorial</Select.Item>
|
<Select.Item value="tutorial" label="Tutorial">Tutorial</Select.Item>
|
||||||
</Select.Group>
|
</Select.Group>
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
<Select.Input name="blogFormat" />
|
<Select.Input {...attrs}/>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.Description />
|
<Form.Description />
|
||||||
@ -134,7 +165,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
<Button type="submit" form="blog-converter">Create</Button>
|
<Form.Button type="submit" form="blog-converter">Create</Form.Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
@ -143,24 +174,20 @@
|
|||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Head class="w-[100px]">ID</Table.Head>
|
<Table.Head class="w-[100px]">ID</Table.Head>
|
||||||
<Table.Head class="w-[300px]">Title</Table.Head>
|
<Table.Head>Title</Table.Head>
|
||||||
<Table.Head class="w-[500px]">Preview</Table.Head>
|
<Table.Head class="text-end">Source</Table.Head>
|
||||||
<Table.Head>Source</Table.Head>
|
<Table.Head class="text-end">Actions</Table.Head>
|
||||||
<Table.Head>Actions</Table.Head>
|
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{#each invoices as invoice, i (i)}
|
{#each articles as invoice, i (i)}
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell class="font-medium">{invoice.id}</Table.Cell>
|
<Table.Cell class="font-medium">{invoice.id}</Table.Cell>
|
||||||
<Table.Cell class="max-w-16 overflow-hidden overflow-ellipsis text-nowrap"
|
<Table.Cell class="w-fill overflow-hidden overflow-ellipsis text-nowrap"
|
||||||
>{invoice.title}</Table.Cell
|
>{invoice.title}</Table.Cell
|
||||||
>
|
>
|
||||||
<Table.Cell class="max-w-10 overflow-hidden overflow-ellipsis text-nowrap"
|
<Table.Cell class="text-end">{invoice.source}</Table.Cell>
|
||||||
>{invoice.preview}</Table.Cell
|
<Table.Cell class="w-fit text-end">
|
||||||
>
|
|
||||||
<Table.Cell>{invoice.source}</Table.Cell>
|
|
||||||
<Table.Cell>
|
|
||||||
<TooltipButton variant="outline" size="icon" tip="Preview">
|
<TooltipButton variant="outline" size="icon" tip="Preview">
|
||||||
<ExternalLink size="1rem" />
|
<ExternalLink size="1rem" />
|
||||||
</TooltipButton>
|
</TooltipButton>
|
9
src/routes/(app)/articles/schema.js
Normal file
9
src/routes/(app)/articles/schema.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const formSchema = z.object({
|
||||||
|
video_id: z.string(),
|
||||||
|
// length: z.number().optional(),
|
||||||
|
// format: z.enum(["summary", "listicle", "product review", "news report", "tutorial"]).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @typedef {typeof formSchema} FormSchema */
|
21
src/routes/(auth)/auth/+page.server.js
Normal file
21
src/routes/(auth)/auth/+page.server.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { config } from "$lib"
|
||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
/** @type {import("./$types").PageServerLoad} */
|
||||||
|
export const load = async ({request, cookies}) => {
|
||||||
|
let url = new URL(request.url);
|
||||||
|
|
||||||
|
if(url.searchParams.has("token")) {
|
||||||
|
// @ts-ignore
|
||||||
|
cookies.set("token", url.searchParams.get("token"), {
|
||||||
|
path: "/",
|
||||||
|
});
|
||||||
|
|
||||||
|
redirect(302, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "missing token"
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
import { config } from "$lib"
|
|
||||||
|
|
||||||
/** @type {import("./$types").LayoutServerLoad} */
|
|
||||||
export const load = async ({fetch}) => {
|
|
||||||
const res = await fetch(config.api_url+"/me")
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
me: data
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import { config } from "$lib"
|
|
||||||
import { superValidate } from "sveltekit-superforms";
|
|
||||||
import { formSchema } from "./schema";
|
|
||||||
import { zod } from "sveltekit-superforms/adapters";
|
|
||||||
|
|
||||||
/** @type {import("./$types").PageServerLoad} */
|
|
||||||
export const load = async ({fetch}) => {
|
|
||||||
const res = await fetch(config.api_url+"/blog?mine=true", {
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
console.log(data);
|
|
||||||
return {
|
|
||||||
articles: data,
|
|
||||||
form: await superValidate(zod(formSchema)),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const formSchema = z.object({
|
|
||||||
youtube_url: z.string().url(),
|
|
||||||
length: z.enum(["short", "medium", "long"]).optional(),
|
|
||||||
format: z.enum(["summary", "listicle", "product review", "news report", "tutorial"]).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @typedef {typeof formSchema} FormSchema */
|
|
Loading…
Reference in New Issue
Block a user