This commit is contained in:
Omer Sabic 2024-04-28 20:58:22 +02:00
parent f42ba94af4
commit d47294bf9e
17 changed files with 191 additions and 73 deletions

9
package-lock.json generated
View File

@ -14,6 +14,7 @@
"lucide-svelte": "^0.373.0",
"mode-watcher": "^0.3.0",
"svelte-radix": "^1.1.0",
"svelte-sonner": "^0.3.22",
"sveltekit-superforms": "^2.12.6",
"tailwind-merge": "^2.3.0",
"tailwind-variants": "^0.2.1",
@ -3043,6 +3044,14 @@
"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": {
"version": "2.12.6",
"resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.12.6.tgz",

View File

@ -35,6 +35,7 @@
"lucide-svelte": "^0.373.0",
"mode-watcher": "^0.3.0",
"svelte-radix": "^1.1.0",
"svelte-sonner": "^0.3.22",
"sveltekit-superforms": "^2.12.6",
"tailwind-merge": "^2.3.0",
"tailwind-variants": "^0.2.1",

View File

@ -2,7 +2,6 @@ import { config } from '$lib/index.js';
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
console.log(event.request.headers.get("cookie"))
if (request.url.startsWith(config.api_url)) {
// @ts-ignore
request.headers.set('cookie', event.request.headers.get('cookie'));

View File

@ -0,0 +1 @@
export { default as Toaster } from "./sonner.svelte";

View 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}
/>

View File

@ -1,3 +1,3 @@
export const config = {
api_url: "http://api.omersabic.com:3001"
api_url: "http://localhost:3000"
}

View 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
}
}

View File

@ -1,5 +1,5 @@
<script>
import '../app.pcss';
import '../../app.pcss';
import Menu from 'lucide-svelte/icons/menu';
import Package2 from 'lucide-svelte/icons/package-2';
@ -14,11 +14,13 @@
import { Button } from '$lib/components/ui/button/index.js';
import { ModeWatcher, toggleMode } from 'mode-watcher';
import { Toaster } from "$lib/components/ui/sonner";
/** @type {import('./$types').LayoutServerData} */
export let data;
</script>
<Toaster />
<ModeWatcher />
<div class="flex min-h-screen w-full flex-col">

View 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,
};
},
};

View File

@ -10,11 +10,14 @@
import TooltipButton from '$lib/components/molecules/tooltipbutton.svelte';
import ProBadge from '$lib/components/molecules/probadge.svelte';
import { formSchema } from "./schema";
import { formSchema } from './schema';
import { superForm } from 'sveltekit-superforms';
import { zodClient } from 'sveltekit-superforms/adapters';
import { toast } from 'svelte-sonner';
/** @type {import("./$types").PageData} */
export let data;
let isDialogOpen = false;
const form = superForm(data.form, {
validators: zodClient(formSchema)
@ -22,7 +25,12 @@
const { form: formData, enhance } = form;
const invoices = [
function submitArticle() {
isDialogOpen = false;
toast('Article is queued for generation.');
}
const articles = [
{
id: 'AG64NE',
title: 'Nullam ornare ornare orci a auctor.',
@ -59,12 +67,13 @@
source: 'Youtube'
}
];
</script>
<div class="mx-auto max-w-[1500px]">
<Dialog.Root>
<div class="mx-auto w-full max-w-[1000px]">
<Dialog.Root bind:open={isDialogOpen}>
<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.Title>Create Article</Dialog.Title>
<Dialog.Description>
@ -72,19 +81,41 @@
</Dialog.Description>
</Dialog.Header>
<form method="POST" use:enhance name="blog-converter">
<Form.Field {form} name="email">
<form
method="POST"
use:enhance
name="blog-converter"
id="blog-converter"
on:submit={submitArticle}
>
<Form.Field {form} name="video_id">
<Form.Control let:attrs>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Form.Label for="youtube_url" class="text-right">Youtube video URL*</Form.Label>
<Input
<Form.Label for="video_id" class="text-right">Youtube video*</Form.Label>
<!-- <Input
id="youtube_url"
placeholder="www.youtube.com/watch?v=..."
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 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>
<Select.Root portal={null} name="length">
<Select.Trigger class="w-[300px]">
@ -92,23 +123,23 @@
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Item value="short" label="Short (~700 words)"
<Select.Item value="700" label="Short (~700 words)"
>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
>
<Select.Item value="long" label="Long (~2500 words)"
<Select.Item value="2500" label="Long (~2500 words)"
>Long (~2500 words)<ProBadge /></Select.Item
>
</Select.Group>
</Select.Content>
<Select.Input name="blogLength" />
<Select.Input {...attrs} />
</Select.Root>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Form.Label for="length" class="text-right">Format</Form.Label>
<Select.Root portal={null} name="length">
<Form.Label for="format" class="text-right">Format</Form.Label>
<Select.Root portal={null} name="format">
<Select.Trigger class="w-[200px]">
<Select.Value />
</Select.Trigger>
@ -123,9 +154,9 @@
<Select.Item value="tutorial" label="Tutorial">Tutorial</Select.Item>
</Select.Group>
</Select.Content>
<Select.Input name="blogFormat" />
<Select.Input {...attrs}/>
</Select.Root>
</div>
</div> -->
</div>
</Form.Control>
<Form.Description />
@ -134,7 +165,7 @@
</form>
<Dialog.Footer>
<Button type="submit" form="blog-converter">Create</Button>
<Form.Button type="submit" form="blog-converter">Create</Form.Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
@ -143,24 +174,20 @@
<Table.Header>
<Table.Row>
<Table.Head class="w-[100px]">ID</Table.Head>
<Table.Head class="w-[300px]">Title</Table.Head>
<Table.Head class="w-[500px]">Preview</Table.Head>
<Table.Head>Source</Table.Head>
<Table.Head>Actions</Table.Head>
<Table.Head>Title</Table.Head>
<Table.Head class="text-end">Source</Table.Head>
<Table.Head class="text-end">Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each invoices as invoice, i (i)}
{#each articles as invoice, i (i)}
<Table.Row>
<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
>
<Table.Cell class="max-w-10 overflow-hidden overflow-ellipsis text-nowrap"
>{invoice.preview}</Table.Cell
>
<Table.Cell>{invoice.source}</Table.Cell>
<Table.Cell>
<Table.Cell class="text-end">{invoice.source}</Table.Cell>
<Table.Cell class="w-fit text-end">
<TooltipButton variant="outline" size="icon" tip="Preview">
<ExternalLink size="1rem" />
</TooltipButton>

View 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 */

View 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"
}
}

View File

@ -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
}
}

View File

@ -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)),
}
}

View File

@ -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 */