From 9c358a340be8e36a2325e3d6f320edba48385095 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Tue, 30 Apr 2024 11:21:45 +0800 Subject: [PATCH] update links page to use correct qr dialog, create and edit form --- frontend/src/app.d.ts | 52 ++++- .../src/lib/components/ShortenerCard.svelte | 134 +++++++---- .../(app)/links/(components)/form.svelte | 217 ++++++++++++++++++ .../src/routes/(app)/links/+page.server.ts | 79 +++++++ frontend/src/routes/(app)/links/+page.svelte | 99 +++++++- .../links/[id]/edit/(components)/form.svelte | 191 +++++++++++++++ .../(app)/links/[id]/edit/+page.server.ts | 139 +++++++++++ .../routes/(app)/links/[id]/edit/+page.svelte | 24 ++ .../routes/(app)/links/[id]/edit/schema.ts | 13 ++ .../links/[id]/qr/(components)/qr.svelte | 85 +++++++ .../(app)/links/[id]/qr/+page.server.ts | 26 +++ .../routes/(app)/links/[id]/qr/+page.svelte | 26 +++ frontend/src/routes/(app)/links/schema.ts | 13 ++ 13 files changed, 1047 insertions(+), 51 deletions(-) create mode 100644 frontend/src/routes/(app)/links/(components)/form.svelte create mode 100644 frontend/src/routes/(app)/links/[id]/edit/(components)/form.svelte create mode 100644 frontend/src/routes/(app)/links/[id]/edit/+page.server.ts create mode 100644 frontend/src/routes/(app)/links/[id]/edit/+page.svelte create mode 100644 frontend/src/routes/(app)/links/[id]/edit/schema.ts create mode 100644 frontend/src/routes/(app)/links/[id]/qr/(components)/qr.svelte create mode 100644 frontend/src/routes/(app)/links/[id]/qr/+page.server.ts create mode 100644 frontend/src/routes/(app)/links/[id]/qr/+page.svelte create mode 100644 frontend/src/routes/(app)/links/schema.ts diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts index a76c5da..a144086 100644 --- a/frontend/src/app.d.ts +++ b/frontend/src/app.d.ts @@ -1,6 +1,6 @@ // See https://kit.svelte.dev/docs/types#app -import type { Project } from '$lib/db/types' +import type { Project, Setting } from '$lib/db/types' // for information about these interfaces declare global { @@ -13,6 +13,56 @@ declare global { // interface PageData {} // interface Platform {} interface PageState { + linkQR: { + user: User + breadcrumbs: { + name: string + path: string + }[] + page_title: string + shortener_url: string + shortener: { + code: string + } + settings: Setting + } + editLink: { + user: User + breadcrumbs: { + name: string + path: string + }[] + page_title: string + shortener_url: string + projects: Project[] + selectedCategory: + | { + value: string | null + label: string + } + | undefined + form: SuperValidated< + { + link: string + ios: boolean + ios_link: string + android: boolean + android_link: string + active: boolean + project?: string | undefined + }, + any, + { + link: string + ios: boolean + ios_link: string + android: boolean + android_link: string + active: boolean + project?: string | undefined + } + > + } projectLinkQR: { user: User breadcrumbs: { diff --git a/frontend/src/lib/components/ShortenerCard.svelte b/frontend/src/lib/components/ShortenerCard.svelte index 2f76074..a7e96e5 100644 --- a/frontend/src/lib/components/ShortenerCard.svelte +++ b/frontend/src/lib/components/ShortenerCard.svelte @@ -1,10 +1,10 @@ @@ -96,7 +129,34 @@ - {shortener.link} + +
+
+ {shortener.link} +
+ {#if shortener.ios} + + + iOS + + +

{shortener.ios_link}

+
+
+ {/if} + {#if shortener.android} + + + Android + + +

{shortener.android_link}

+
+
+ {/if} +
+
@@ -109,11 +169,15 @@ {shortener.visitorCount} visits
- + @@ -121,18 +185,13 @@ - - openEditDialog( - shortener.code, - shortener.link, - shortener.projectId, - shortener.projectName, - shortener.active, - )} - class="flex gap-2 items-center"> - Edit - + + + Edit + + openDeleteDialog(shortener.code)} class="flex gap-2 items-center text-destructive data-[highlighted]:bg-destructive"> @@ -155,24 +214,3 @@ {editShortenerCategory} /> - - - - - Shortener QR - - Use this QR code to share the shortener. - - -
- - {shortener_url + '/' + qrCode} - - -
-
-
diff --git a/frontend/src/routes/(app)/links/(components)/form.svelte b/frontend/src/routes/(app)/links/(components)/form.svelte new file mode 100644 index 0000000..f00bb8f --- /dev/null +++ b/frontend/src/routes/(app)/links/(components)/form.svelte @@ -0,0 +1,217 @@ + + + + + + Add Shortner + + + + Add Shortener + + Create A New Shortener Here. Click Add To Save. + + + +
+
Preview
+
+
+ {#if isPreviewLoading} +
+ +
+ {:else if previewData} + +
+ {previewData.title} +
+ {/if} +
+
+
+
+ + + Link + + + Shortener link + + + + + Project + { + v && ($formData.project = v.value) + }} + multiple={false}> + + + + + None + + + {#each projects as project} + + {project.name} + + {/each} + + + + + + Shortener Project + + + + + iOS Link + + + + {#if $formData.ios} + + + + + Shortener link for iOS + + + {/if} + + + Android Link + + + + {#if $formData.android} + + + + + Shortener link for Android + + + {/if} + + + + Active + + + + + {#if $submitting} + + {/if} + Add + +
+
+
+
diff --git a/frontend/src/routes/(app)/links/+page.server.ts b/frontend/src/routes/(app)/links/+page.server.ts index 23d54f8..3239398 100644 --- a/frontend/src/routes/(app)/links/+page.server.ts +++ b/frontend/src/routes/(app)/links/+page.server.ts @@ -10,6 +10,11 @@ import { } from 'drizzle-orm' import type { PageServerLoad } from './$types' import { project, shortener, visitor } from '$lib/db/schema' +import { fail, setError, superValidate } from 'sveltekit-superforms' +import { zod } from 'sveltekit-superforms/adapters' +import { formSchema } from './schema' +import type { Actions } from './$types' +import { nanoid } from 'nanoid' export const load = (async (event) => { const user = event.locals.user @@ -64,6 +69,7 @@ export const load = (async (event) => { .select({ ...shortenerColumns, projectName: project.name, + projectUuid: project.uuid, visitorCount: sql`count(${visitor.id})`, }) .from(shortener) @@ -123,5 +129,78 @@ export const load = (async (event) => { search, sortBy, pagination, + form: await superValidate({ active: true }, zod(formSchema)), } }) satisfies PageServerLoad + +export const actions: Actions = { + create: async (event) => { + const form = await superValidate(event, zod(formSchema)) + if (!form.valid) { + return fail(400, { + form, + }) + } + + if (form.data.link.startsWith('http://')) { + return setError(form, 'link', 'Link must be HTTPS') + } + + if (form.data.ios_link.startsWith('http://')) { + return setError(form, 'ios_link', 'Link must be HTTPS') + } + + if (form.data.android_link.startsWith('http://')) { + return setError(form, 'android_link', 'Link must be HTTPS') + } + + const user = event.locals.user + let project = undefined + const selected_project = form.data.project + if (selected_project) { + project = await db.query.project.findFirst({ + where: (project, { eq, and }) => + and( + eq(project.userId, user.id), + eq(project.uuid, selected_project), + ), + }) + } + + let ios_link = '' + + if (form.data.ios_link) { + if (form.data.ios_link.startsWith('https://')) { + ios_link = form.data.ios_link + } else { + ios_link = `https://${form.data.ios_link}` + } + } + + let android_link = '' + + if (form.data.android_link) { + if (form.data.android_link.startsWith('https://')) { + android_link = form.data.android_link + } else { + android_link = `https://${form.data.android_link}` + } + } + + const code = nanoid(8) + await db.insert(shortener).values({ + link: form.data.link.startsWith('https://') + ? form.data.link + : `https://${form.data.link}`, + projectId: project ? project.id : undefined, + userId: user.id, + code: code, + ios: form.data.ios, + ios_link: ios_link, + android: form.data.android, + android_link: android_link, + }) + + return { form } + }, +} diff --git a/frontend/src/routes/(app)/links/+page.svelte b/frontend/src/routes/(app)/links/+page.svelte index 6d0ebe0..34ee069 100644 --- a/frontend/src/routes/(app)/links/+page.svelte +++ b/frontend/src/routes/(app)/links/+page.svelte @@ -3,20 +3,26 @@ import { cn } from '$lib/utils' import { goto } from '$app/navigation' import { browser } from '$app/environment' + import { page } from '$app/stores' import { Button } from '$lib/components/ui/button' import * as Select from '$lib/components/ui/select' import * as Command from '$lib/components/ui/command' import * as Popover from '$lib/components/ui/popover' import * as Pagination from '$lib/components/ui/pagination' + import * as Dialog from '$lib/components/ui/dialog' import { Input } from '$lib/components/ui/input' import ScrollArea from '$lib/components/ui/scroll-area/scroll-area.svelte' import { Skeleton } from '$lib/components/ui/skeleton' import { Check, ChevronsUpDown, SortDescIcon } from 'lucide-svelte' - import AddShortenerDialog from '$lib/components/AddShortenerDialog.svelte' import ShortenerCard from '$lib/components/ShortenerCard.svelte' + import Form from './(components)/form.svelte' + import EditProjectLinkPage from '../projects/[id]/links/[linkid]/edit/+page.svelte' + import ProjectLinkQRPage from '../projects/[id]/links/[linkid]/qr/+page.svelte' + import EditLinkQRPage from './[id]/edit/+page.svelte' + import LinkQRPage from './[id]/qr/+page.svelte' export let data: PageData @@ -51,6 +57,11 @@ return '/links' } } + + $: editProjectLinkOpen = !!$page.state.editProjectLink + $: projectLinkQROpen = !!$page.state.projectLinkQR + $: editLinkOpen = !!$page.state.editLink + $: linkQROpen = !!$page.state.linkQR
- +
@@ -312,3 +323,87 @@ {/await} + + { + if (!open) { + history.back() + } + }}> + + + Edit Shortener + + Edit Shortener Here. Click Save To Save. + + + + + + + + + { + if (!open) { + history.back() + } + }}> + + + Edit Shortener + + Edit Shortener Here. Click Save To Save. + + + + + + + + + { + if (!open) { + history.back() + } + }}> + + + Shortener QR bla + + Use this QR code to share the shortener. + + + + + + + + + { + if (!open) { + history.back() + } + }}> + + + Shortener QR + + Use this QR code to share the shortener. + + + + + + + diff --git a/frontend/src/routes/(app)/links/[id]/edit/(components)/form.svelte b/frontend/src/routes/(app)/links/[id]/edit/(components)/form.svelte new file mode 100644 index 0000000..3f14272 --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/edit/(components)/form.svelte @@ -0,0 +1,191 @@ + + +
+
Preview
+
+
+ {#if isPreviewLoading} +
+ +
+ {:else if previewData} + +
+ {previewData.title} +
+ {/if} +
+
+
+ + + + Link + + + Shortener link + + + + + Project + { + v && ($formData.project = v.value) + }} + multiple={false}> + + + + + None + + + {#each projects as project} + + {project.name} + + {/each} + + + + + + Shortener Project + + + + + iOS Link + + + + {#if $formData.ios} + + + + + Shortener link for iOS + + + {/if} + + + Android Link + + + + {#if $formData.android} + + + + + Shortener link for Android + + + {/if} + + + + Active + + + + + {#if $submitting} + + {/if} + Save + + diff --git a/frontend/src/routes/(app)/links/[id]/edit/+page.server.ts b/frontend/src/routes/(app)/links/[id]/edit/+page.server.ts new file mode 100644 index 0000000..b8a558f --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/edit/+page.server.ts @@ -0,0 +1,139 @@ +import type { PageServerLoad } from './$types' +import { fail, setError, superValidate } from 'sveltekit-superforms' +import { zod } from 'sveltekit-superforms/adapters' +import { formSchema } from './schema' +import type { Actions } from './$types' +import { db } from '$lib/db' +import { redirect } from '@sveltejs/kit' +import { shortener } from '$lib/db/schema' +import { eq } from 'drizzle-orm' + +export const load = (async (event) => { + const user = event.locals.user + const { id } = event.params + + const shortener = await db.query.shortener.findFirst({ + columns: { + code: true, + projectId: true, + ios: true, + ios_link: true, + android: true, + android_link: true, + link: true, + active: true, + }, + where: (shortener, { eq, and }) => + and(eq(shortener.code, id), eq(shortener.userId, user.id)), + }) + + if (!shortener) { + redirect(300, `/links`) + } + + const projects = await db.query.project.findMany({ + where: (project, { eq }) => eq(project.userId, user.id), + }) + + let selectedCategory = undefined + if (shortener.projectId) { + const project = projects.find( + (project) => project.id === shortener.projectId, + ) + if (project) { + selectedCategory = { value: project.uuid, label: project.name } + } + } + + return { + projects, + selectedCategory, + form: await superValidate( + { + ...shortener, + project: selectedCategory?.value || undefined, + }, + zod(formSchema), + ), + } +}) satisfies PageServerLoad + +export const actions: Actions = { + default: async (event) => { + const form = await superValidate(event, zod(formSchema)) + if (!form.valid) { + return fail(400, { + form, + }) + } + + if (form.data.link.startsWith('http://')) { + return setError(form, 'link', 'Link must be HTTPS') + } + + if ( + form.data.ios_link && + form.data.ios_link.startsWith('http://') + ) { + return setError(form, 'ios_link', 'Link must be HTTPS') + } + + if ( + form.data.android_link && + form.data.android_link.startsWith('http://') + ) { + return setError(form, 'android_link', 'Link must be HTTPS') + } + + const user = event.locals.user + + let project = undefined + const selected_project = form.data.project + if (selected_project) { + project = await db.query.project.findFirst({ + where: (project, { eq, and }) => + and( + eq(project.userId, user.id), + eq(project.uuid, selected_project), + ), + }) + } + + let ios_link = '' + + if (form.data.ios_link) { + if (form.data.ios_link.startsWith('https://')) { + ios_link = form.data.ios_link + } else { + ios_link = `https://${form.data.ios_link}` + } + } + + let android_link = '' + + if (form.data.android_link) { + if (form.data.android_link.startsWith('https://')) { + android_link = form.data.android_link + } else { + android_link = `https://${form.data.android_link}` + } + } + + await db + .update(shortener) + .set({ + link: form.data.link.startsWith('https://') + ? form.data.link + : `https://${form.data.link}`, + projectId: project ? project.id : null, + userId: user.id, + ios: form.data.ios, + ios_link: ios_link, + android: form.data.android, + android_link: android_link, + }) + .where(eq(shortener.code, event.params.id)) + + return { form } + }, +} diff --git a/frontend/src/routes/(app)/links/[id]/edit/+page.svelte b/frontend/src/routes/(app)/links/[id]/edit/+page.svelte new file mode 100644 index 0000000..8e35ec6 --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/edit/+page.svelte @@ -0,0 +1,24 @@ + + +{#if !shallowRouting} + +
+
+
+
+{:else} + +{/if} diff --git a/frontend/src/routes/(app)/links/[id]/edit/schema.ts b/frontend/src/routes/(app)/links/[id]/edit/schema.ts new file mode 100644 index 0000000..6916e90 --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/edit/schema.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const formSchema = z.object({ + link: z.string(), + project: z.string().optional(), + active: z.boolean(), + ios: z.boolean(), + ios_link: z.string().nullable(), + android: z.boolean(), + android_link: z.string().nullable(), +}) + +export type FormSchema = typeof formSchema diff --git a/frontend/src/routes/(app)/links/[id]/qr/(components)/qr.svelte b/frontend/src/routes/(app)/links/[id]/qr/(components)/qr.svelte new file mode 100644 index 0000000..93f4885 --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/qr/(components)/qr.svelte @@ -0,0 +1,85 @@ + + +
+ + {value} + + {value} +
+ + + + + + + Standard + With Color + + +
+
diff --git a/frontend/src/routes/(app)/links/[id]/qr/+page.server.ts b/frontend/src/routes/(app)/links/[id]/qr/+page.server.ts new file mode 100644 index 0000000..2a757db --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/qr/+page.server.ts @@ -0,0 +1,26 @@ +import { db } from '$lib/db' +import { redirect } from '@sveltejs/kit' +import type { PageServerLoad } from './$types' + +export const load = (async (event) => { + const user = event.locals.user + const { id } = event.params + + const shortener = await db.query.shortener.findFirst({ + columns: { + code: true, + }, + where: (shortener, { eq, and, isNull }) => + and(eq(shortener.code, id), isNull(shortener.projectId)), + }) + + if (!shortener) { + redirect(300, `/links`) + } + + const settings = await db.query.setting.findFirst({ + where: (settings, { eq }) => eq(settings.userId, user.id), + }) + + return { shortener, settings } +}) satisfies PageServerLoad diff --git a/frontend/src/routes/(app)/links/[id]/qr/+page.svelte b/frontend/src/routes/(app)/links/[id]/qr/+page.svelte new file mode 100644 index 0000000..0edf1d9 --- /dev/null +++ b/frontend/src/routes/(app)/links/[id]/qr/+page.svelte @@ -0,0 +1,26 @@ + + +{#if !shallowRouting} + +
+ +
+
+{:else} + +{/if} diff --git a/frontend/src/routes/(app)/links/schema.ts b/frontend/src/routes/(app)/links/schema.ts new file mode 100644 index 0000000..1577273 --- /dev/null +++ b/frontend/src/routes/(app)/links/schema.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const formSchema = z.object({ + link: z.string(), + project: z.string().optional(), + active: z.boolean(), + ios: z.boolean(), + ios_link: z.string(), + android: z.boolean(), + android_link: z.string(), +}) + +export type FormSchema = typeof formSchema