- 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
177 lines
3.2 KiB
Svelte
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>
|