diff --git a/apps/web/app/(dashboard)/servers/new/page.tsx b/apps/web/app/(dashboard)/servers/new/page.tsx index c7fbf18..3479ed1 100644 --- a/apps/web/app/(dashboard)/servers/new/page.tsx +++ b/apps/web/app/(dashboard)/servers/new/page.tsx @@ -130,8 +130,27 @@ export default function NewServerPage() { }; }>(`/v1/templates/${templateSlug}/fork`, { method: 'POST', body: '{}' }); if (cancelled) return; + + // Auto-unique the slug against the user's existing servers so the + // default fork doesn't 409 (e.g. forking the same template twice). + let uniqueSlug = trySlug(res.template.title); + try { + const own = await apiFetch<{ servers: { slug: string }[] }>('/v1/servers'); + const taken = new Set(own.servers.map((s) => s.slug)); + if (taken.has(uniqueSlug)) { + const base = uniqueSlug; + let n = 2; + while (taken.has(`${base}-${n}`)) n++; + uniqueSlug = `${base}-${n}`; + } + } catch { + // If the lookup fails we still proceed; the slug field is editable + // and the build surfaces a clear slug_taken error. + } + if (cancelled) return; + setName(res.template.title); - setSlug(trySlug(res.template.title)); + setSlug(uniqueSlug); setPrompt(`Fork of "${res.template.title}" template.`); setForkedTemplateId(res.templateId); setForkedTemplateTitle(res.template.title); @@ -325,7 +344,12 @@ export default function NewServerPage() { setStep('building'); } catch (e) { const detail = (e as { detail?: { error?: string; detail?: unknown } }).detail; - setError(detail?.error ?? (e as Error).message); + const code = detail?.error; + setError( + code === 'slug_taken' + ? `The slug "${slug}" is already used by one of your servers — change the Slug field above.` + : (code ?? (e as Error).message), + ); } } @@ -424,19 +448,44 @@ export default function NewServerPage() { {step === 'confirm' && preview && editable && (
{forkedTemplateTitle && ( -
-
- Forking {forkedTemplateTitle} — fill in - your own credentials below. The template author never sees them. +
+
+
+ Forking {forkedTemplateTitle} — name + your copy and fill in your own credentials. The template author never sees them. +
+ + Template ↗ + +
+
+
+ + { + setName(e.target.value); + if (!slug || slug === trySlug(name)) setSlug(trySlug(e.target.value)); + }} + /> +
+
+ + setSlug(trySlug(e.target.value))} + /> +
- - Template ↗ -
)}