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",
|
||||
"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",
|
||||
|
@ -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",
|
||||
|
@ -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'));
|
||||
|
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 = {
|
||||
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>
|
||||
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">
|
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 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>
|
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