vereinshaus/app/src/routes/(auth)/register/+page.svelte
rene 77c6f513b5 Feature: SEPA-Export, Push-Notifications, Onboarding + vollständige UI
- Phosphor Icons (Icon.svelte, svg-Registry)
- Schema-Abgleich: alle Felder zwischen PB-Migrations und types.ts konsistent
- Stripe entfernt, SEPA pain.008 XML-Export implementiert (sepa.ts)
- Beiträge: vollständiges CRUD + SEPA-Einzug-Sheet mit Vorschau
- Termine: vollständiges CRUD (upcoming/vergangen, datetime-local)
- Mitglieder: Formulare um alle Felder erweitert (Adresse, SEPA-Mandat, Notizen)
- Nachrichten: Brevo E-Mail via PocketBase-Hook, UI mit Gruppen-Filter
- Push-Notifications: VAPID, Custom Service Worker (injectManifest),
  Subscribe/Send API-Routen, automatische Subscription nach Login
- Onboarding: 3-Schritt-Flow für neue Vereine, Guard im App-Layout
- Makefile: .env wird vollständig zur DS übertragen
2026-05-20 13:01:11 +02:00

177 lines
3.2 KiB
Svelte

<script lang="ts">
import { goto } from '$app/navigation';
import { pb } from '$lib/pb';
let vereinsname = $state('');
let email = $state('');
let password = $state('');
let passwordConfirm = $state('');
let error = $state('');
let loading = $state(false);
async function register() {
error = '';
if (password !== passwordConfirm) {
error = 'Passwörter stimmen nicht überein.';
return;
}
loading = true;
try {
await pb.collection('users').create({ email, password, passwordConfirm, name: vereinsname });
await pb.collection('users').authWithPassword(email, password);
goto('/onboarding');
} catch (e: unknown) {
error = e instanceof Error ? e.message : 'Registrierung fehlgeschlagen.';
} finally {
loading = false;
}
}
</script>
<svelte:head>
<title>Verein registrieren — vereins.haus</title>
</svelte:head>
<h1>Verein registrieren</h1>
<p class="subtitle">Kostenlos starten — kein Kreditkarte nötig</p>
<form onsubmit={(e) => { e.preventDefault(); register(); }}>
<div class="field">
<label for="vereinsname">Name des Vereins</label>
<input
id="vereinsname"
type="text"
bind:value={vereinsname}
placeholder="z. B. TSV Musterstadt 1923"
required
/>
</div>
<div class="field">
<label for="email">E-Mail (Admin)</label>
<input
id="email"
type="email"
bind:value={email}
autocomplete="email"
required
/>
</div>
<div class="field">
<label for="password">Passwort</label>
<input
id="password"
type="password"
bind:value={password}
autocomplete="new-password"
minlength="8"
required
/>
</div>
<div class="field">
<label for="passwordConfirm">Passwort bestätigen</label>
<input
id="passwordConfirm"
type="password"
bind:value={passwordConfirm}
autocomplete="new-password"
required
/>
</div>
{#if error}
<p class="error">{error}</p>
{/if}
<button type="submit" disabled={loading}>
{loading ? 'Registrieren…' : 'Kostenlos registrieren'}
</button>
</form>
<p class="switch">
Bereits registriert? <a href="/login">Anmelden</a>
</p>
<style>
h1 {
font-size: 1.4rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 0.25rem;
}
.subtitle {
font-size: 0.9rem;
color: #64748b;
margin-bottom: 1.5rem;
}
.field {
display: flex;
flex-direction: column;
gap: 0.35rem;
margin-bottom: 1rem;
}
label {
font-size: 0.9rem;
font-weight: 500;
color: #475569;
}
input {
padding: 0.65rem 0.85rem;
border: 1.5px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
transition: border-color .15s;
background: #fff;
}
input:focus {
outline: none;
border-color: #1e40af;
}
.error {
color: #dc2626;
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
button {
width: 100%;
padding: 0.75rem;
background: #1e40af;
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
margin-top: 0.5rem;
transition: background .15s;
}
button:hover:not(:disabled) {
background: #1d3a9e;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.switch {
margin-top: 1.25rem;
font-size: 0.9rem;
color: #64748b;
text-align: center;
}
.switch a {
color: #1e40af;
font-weight: 500;
}
</style>