From d479726fe3521a32b1bb47d79a9a3850a1b4aaa2 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Wed, 25 Mar 2026 22:42:20 +0300 Subject: [PATCH] feat: add app edit page with pre-populated form Add /apps/[id]/edit route that loads existing app data into the form, allowing users to update app properties. Adds edit pencil button to AppCard (visible on hover) and i18n keys for both EN and RU. --- src/lib/components/app/AppCard.svelte | 37 +++++++++++--- src/lib/components/app/AppForm.svelte | 7 +-- src/lib/i18n/en.json | 4 ++ src/lib/i18n/ru.json | 4 ++ src/routes/apps/[id]/edit/+page.server.ts | 60 +++++++++++++++++++++++ src/routes/apps/[id]/edit/+page.svelte | 32 ++++++++++++ 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 src/routes/apps/[id]/edit/+page.server.ts create mode 100644 src/routes/apps/[id]/edit/+page.svelte diff --git a/src/lib/components/app/AppCard.svelte b/src/lib/components/app/AppCard.svelte index 64a8642..44dbd4a 100644 --- a/src/lib/components/app/AppCard.svelte +++ b/src/lib/components/app/AppCard.svelte @@ -68,11 +68,12 @@ }); - window.open(app.url, '_blank', 'noopener,noreferrer')} + onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') window.open(app.url, '_blank', 'noopener,noreferrer'); }} + class="card-hover group flex cursor-pointer flex-col rounded-xl border border-border bg-card p-4 transition-colors hover:border-primary/50" title={app.description ?? app.name} >
@@ -93,7 +94,29 @@ {app.name.charAt(0).toUpperCase()} {/if}
- +
+ e.stopPropagation()} + class="rounded-md p-1 text-muted-foreground opacity-0 transition-all hover:bg-accent hover:text-foreground group-hover:opacity-100" + title={$t('app.edit')} + > + + + + + + +

@@ -123,4 +146,4 @@ {app.category} {/if} - + diff --git a/src/lib/components/app/AppForm.svelte b/src/lib/components/app/AppForm.svelte index dc0d288..275478f 100644 --- a/src/lib/components/app/AppForm.svelte +++ b/src/lib/components/app/AppForm.svelte @@ -14,12 +14,13 @@ interface Props { form: SuperValidated; action?: string; + mode?: 'create' | 'edit'; } - let { form: formData, action = '?/create' }: Props = $props(); + let { form: formData, action = '?/create', mode = 'create' }: Props = $props(); const { form, errors, enhance, submitting } = superForm(formData, { - resetForm: true + resetForm: mode === 'create' }); let showAdvanced = $state(false); @@ -383,7 +384,7 @@ {#if $submitting} {$t('app.saving')} {:else} - {$t('app.save')} + {mode === 'edit' ? $t('app.update') : $t('app.save')} {/if} diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json index 2378a29..5938544 100644 --- a/src/lib/i18n/en.json +++ b/src/lib/i18n/en.json @@ -333,6 +333,10 @@ "settings.bookmarklet_drag_hint": "Drag this to your bookmarks bar", "settings.bookmarklet_show_code": "Show bookmarklet code", + "app.edit": "Edit", + "app.edit_title": "Edit App", + "app.update": "Update App", + "app.quick_add_title": "Quick Add App", "app.quick_add_description": "Review the details below and save to add this app to your launcher.", "app.quick_add_success": "App added successfully!", diff --git a/src/lib/i18n/ru.json b/src/lib/i18n/ru.json index a290776..e993b04 100644 --- a/src/lib/i18n/ru.json +++ b/src/lib/i18n/ru.json @@ -302,6 +302,10 @@ "settings.bookmarklet_drag": "Добавить в Launcher", "settings.bookmarklet_drag_hint": "Перетащите на панель закладок", "settings.bookmarklet_show_code": "Показать код букмарклета", + "app.edit": "Редактировать", + "app.edit_title": "Редактирование приложения", + "app.update": "Обновить приложение", + "app.quick_add_title": "Быстрое добавление приложения", "app.quick_add_description": "Проверьте данные ниже и сохраните, чтобы добавить приложение в лаунчер.", "app.quick_add_success": "Приложение успешно добавлено!", diff --git a/src/routes/apps/[id]/edit/+page.server.ts b/src/routes/apps/[id]/edit/+page.server.ts new file mode 100644 index 0000000..443e093 --- /dev/null +++ b/src/routes/apps/[id]/edit/+page.server.ts @@ -0,0 +1,60 @@ +import type { Actions, PageServerLoad } from './$types.js'; +import { superValidate, setError } from 'sveltekit-superforms'; +import { zod } from '$lib/utils/zod-adapter.js'; +import { error, fail, redirect } from '@sveltejs/kit'; +import { requireAuth } from '$lib/server/middleware/authenticate.js'; +import * as appService from '$lib/server/services/appService.js'; +import { createAppSchema } from '$lib/utils/validators.js'; + +export const load: PageServerLoad = async (event) => { + requireAuth(event); + + let app; + try { + app = await appService.findById(event.params.id); + } catch { + throw error(404, 'App not found'); + } + + const form = await superValidate(zod(createAppSchema)); + + // Pre-fill form with existing app data + form.data.name = app.name; + form.data.url = app.url; + form.data.icon = app.icon ?? undefined; + form.data.iconType = app.iconType as typeof form.data.iconType; + form.data.description = app.description ?? undefined; + form.data.category = app.category ?? undefined; + form.data.tags = app.tags ?? undefined; + form.data.healthcheckEnabled = app.healthcheckEnabled; + form.data.healthcheckInterval = app.healthcheckInterval; + form.data.healthcheckMethod = app.healthcheckMethod as typeof form.data.healthcheckMethod; + form.data.healthcheckExpectedStatus = app.healthcheckExpectedStatus; + form.data.healthcheckTimeout = app.healthcheckTimeout; + form.data.integrationType = app.integrationType ?? undefined; + form.data.integrationConfig = (app.integrationConfig as string) ?? undefined; + form.data.integrationEnabled = app.integrationEnabled; + + return { form, app: { id: app.id, name: app.name } }; +}; + +export const actions: Actions = { + update: async (event) => { + requireAuth(event); + + const form = await superValidate(event.request, zod(createAppSchema)); + + if (!form.valid) { + return fail(400, { form }); + } + + try { + await appService.update(event.params.id, form.data); + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to update app'; + return setError(form, '', message); + } + + throw redirect(303, '/apps'); + } +}; diff --git a/src/routes/apps/[id]/edit/+page.svelte b/src/routes/apps/[id]/edit/+page.svelte new file mode 100644 index 0000000..b205dbc --- /dev/null +++ b/src/routes/apps/[id]/edit/+page.svelte @@ -0,0 +1,32 @@ + + + + {$t('app.edit_title')} — {data.app.name} + + +
+
+
+
+

{$t('app.edit_title')}

+

{data.app.name}

+
+ + {$t('common.cancel')} + +
+ +
+ +
+
+