diff --git a/.env.example b/.env.example index fa7c8c6..5fbef6d 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,8 @@ PUBLIC_SITE_URL=https://fdbck.msbls.de # Optional cookie scope (leave unset for host-only) # COOKIE_DOMAIN=.msbls.de + +# Shlink short-URL service (admin "Share" feature) +# SHLINK_URL defaults to https://msbls.de when unset. +SHLINK_URL=https://msbls.de +SHLINK_API_KEY= diff --git a/src/routes/admin/feedback/[id]/+page.svelte b/src/routes/admin/feedback/[id]/+page.svelte index 7a7c9b4..87ac39d 100644 --- a/src/routes/admin/feedback/[id]/+page.svelte +++ b/src/routes/admin/feedback/[id]/+page.svelte @@ -24,6 +24,11 @@ let editChatEnabled = $state(inst.chat_enabled); let editLiveResults = $state(inst.live_results_enabled); + let shareSlugInput = $state(''); + let shareInFlight = $state(false); + let shareError = $state(null); + let shareCopied = $state(false); + let editMode = $state<'visual' | 'json'>('visual'); let editForm = $state( inst.form_definition ? (inst.form_definition as FeedbackFormDefinition) : null, @@ -213,6 +218,46 @@ } } + async function copyShortUrl(): Promise { + if (!inst.short_url) return; + try { + await navigator.clipboard.writeText(inst.short_url); + shareCopied = true; + setTimeout(() => (shareCopied = false), 1500); + } catch { + // silent + } + } + + async function createShareLink(): Promise { + shareError = null; + shareInFlight = true; + try { + const slug = shareSlugInput.trim(); + const body: { customSlug?: string } = {}; + if (slug) body.customSlug = slug; + + const res = await fetch(`/api/admin/feedback/${inst.id}/share`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + if (!res.ok) { + const j = (await res.json().catch(() => ({}))) as { error?: string; details?: unknown }; + shareError = j.error ?? `Error ${res.status}`; + if (j.details) shareError += ' — ' + JSON.stringify(j.details); + return; + } + const j = (await res.json()) as { instance: typeof inst }; + inst = j.instance; + shareSlugInput = ''; + } catch (e) { + shareError = e instanceof Error ? e.message : 'Network error'; + } finally { + shareInFlight = false; + } + } + function fmtDateTime(iso: string): string { return new Date(iso).toLocaleString(); } @@ -288,6 +333,91 @@
{actionError}
{/if} +
+

Share

+ {#if inst.short_url} +

+ Memorable short link — resolves to /f/{inst.slug}. +

+
+ + {inst.short_url} + + + + Open ↗ + +
+
+ + Replace with a different short link + +
+ +
+ + +
+

+ Leave blank for a random short code, or pick a memorable slug like vote or session-feedback. +

+
+
+ {:else} +

+ Create a memorable short link (e.g. https://msbls.de/vote) that redirects to this form. +

+
+ +
+ + +
+

+ Leave blank for a random short code, or pick a memorable slug like vote or session-feedback. +

+
+ {/if} + {#if shareError} +
{shareError}
+ {/if} +
+
{#each [ { key: 'chat', label: `Chat (${posts.length})`, show: inst.chat_enabled },