fix some signup ux issues

This commit is contained in:
JCEEE
2026-08-25 07:57:50 +01:00
parent f7d4ed45b3
commit f63e2918ff
5 changed files with 114 additions and 2 deletions
@@ -911,6 +911,9 @@
<p class="empty">Loading...</p>
{:else if error}
<p class="error">{error}</p>
<p class="empty">
<Button href={`/${famSlug}/join/${username}`} variant="primary" size="md">Join</Button>
</p>
{:else}
{#if simulateEow}
<div class="preview-notice">
@@ -82,7 +82,7 @@
let copied = $state(false);
let parentInviteEmail = $state('');
let members = $state(famStore.initialized ? famStore.members : data.members || []);
let members = $derived(famStore.initialized ? famStore.members : (data.members || []));
let deletingSeason = $state<any>(null);
let issued = $state<{ otp: string; joinUrl: string; name: string } | null>(null);
@@ -0,0 +1,33 @@
import { fail, redirect } from '@sveltejs/kit';
import { redeemOtp } from '$lib/server/member-otp';
import { handle } from '@shared/slugify';
import { setSessionCookie, clearLegacyCookies } from '$lib/server/session';
export const actions = {
default: async (event) => {
const fam = event.params.fam;
const fd = await event.request.formData();
const name = (fd.get('name') || '').toString().trim();
const otp = (fd.get('otp') || '').toString().trim();
if (!name) return fail(400, { error: 'Enter your name.', name, otp });
if (!otp) return fail(400, { error: 'Enter the code shown by your parent.', name, otp });
try {
// redeemOtp derives the username from the handle internally; pass the
// raw name so it resolves the same {famSlug}:{handle} identity.
const token = await redeemOtp({ famSlug: fam, username: name, otp });
clearLegacyCookies(event.cookies);
setSessionCookie(event.cookies, token);
} catch (e) {
return fail(400, {
error: e instanceof Error ? e.message : 'Join failed',
name,
otp
});
}
const handleName = handle(name);
throw redirect(303, `/${fam}/${encodeURIComponent(handleName)}`);
}
};
@@ -0,0 +1,65 @@
<script lang="ts">
import { page } from '$app/state';
import { enhance } from '$app/forms';
import { Button } from '$lib/components';
const famSlug = page.params.fam;
let name = $state('');
let otp = $state(page.url.searchParams.get('code') || '');
let { form } = $props();
</script>
<svelte:head><title>Join {famSlug}</title></svelte:head>
<main class="mx-auto flex min-h-screen max-w-md flex-col items-center justify-center px-6">
<section class="w-full rounded-2xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100 text-2xl">
🏠
</div>
<h1 class="text-xl font-bold text-slate-900">Join {famSlug}</h1>
<p class="mt-1 text-sm text-slate-500">
Enter your name and the code your parent gave you to get started.
</p>
<form
class="mt-6 flex flex-col gap-3"
method="POST"
use:enhance={() => {
return async ({ result, update }) => {
if (result.type === 'failure') {
name = (result.data as any)?.name || '';
otp = (result.data as any)?.otp || '';
}
await update();
};
}}
>
<input
type="text"
name="name"
bind:value={name}
placeholder="Your name"
autocomplete="name"
class="w-full rounded-lg border border-slate-300 px-4 py-3 text-center text-lg text-slate-900 outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200"
/>
<input
type="text"
name="otp"
bind:value={otp}
inputmode="numeric"
maxlength="6"
placeholder="6-digit code"
autocomplete="one-time-code"
class="w-full rounded-lg border border-slate-300 px-4 py-3 text-center text-2xl tracking-[0.5em] text-slate-900 outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200"
/>
{#if form?.error}
<p class="text-sm font-medium text-rose-600">{form.error}</p>
{/if}
<Button type="submit" variant="primary" size="lg">Join</Button>
</form>
<p class="mt-6 text-xs text-slate-400">
Code is valid for 20 minutes. Ask your parent for a new one if it expires.
</p>
</section>
</main>
@@ -22,7 +22,18 @@
gave you to get started.
</p>
<form class="mt-6 flex flex-col gap-3" method="POST" use:enhance={() => ({})}>
<form
class="mt-6 flex flex-col gap-3"
method="POST"
use:enhance={() => {
return async ({ result, update }) => {
if (result.type === 'failure') {
otp = (result.data as any)?.otp || '';
}
await update();
};
}}
>
<input
type="text"
name="otp"