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')} + +
+ +
+ +
+
+