Files
famdone/frontend/src/routes/login/+page.svelte
T
2026-09-01 11:58:43 +01:00

182 lines
3.7 KiB
Svelte

<script lang="ts">
import AuthShell from '$lib/components/AuthShell.svelte';
let { form } = $props();
let email = $state('');
let password = $state('');
let showForgot = $state(false);
let forgotEmail = $state('');
let forgotLoading = $state(false);
let forgotSuccess = $state(false);
async function handleForgot() {
if (!forgotEmail || forgotLoading) return;
forgotLoading = true;
try {
const fd = new FormData();
fd.set('email', forgotEmail);
const res = await fetch('/api/email', { method: 'POST', body: fd });
if (res.ok) {
forgotSuccess = true;
}
} catch {
// Silent — generic response
} finally {
forgotLoading = false;
}
}
</script>
<AuthShell title="Log in" subtitle="Welcome back to FamChore.">
{#if showForgot}
{#if forgotSuccess}
<p class="form-ok">
If an account exists for <strong>{forgotEmail}</strong>, a reset link is on its way.
</p>
<button
class="row-btn centered"
type="button"
onclick={() => {
showForgot = false;
forgotSuccess = false;
forgotEmail = '';
}}
>
Back to log in
</button>
{:else}
<form
onsubmit={(e) => {
e.preventDefault();
handleForgot();
}}
>
<label>
Email
<input type="email" bind:value={forgotEmail} placeholder="you@email.com" required />
</label>
<button type="submit" disabled={forgotLoading}>
{forgotLoading ? 'Sending...' : 'Send reset link'}
</button>
</form>
<button class="row-btn centered" type="button" onclick={() => (showForgot = false)}>
Back to log in
</button>
{/if}
{:else}
{#if form?.error}
<p class="form-error">{form.error}</p>
{/if}
<form method="POST">
<label>
Email
<input type="email" name="email" bind:value={email} placeholder="you@email.com" required />
</label>
<label>
Password
<input type="password" name="password" bind:value={password} required />
</label>
<button class="row-btn" type="button" onclick={() => (showForgot = true)}>
Forgot password?
</button>
<button type="submit">Log in</button>
</form>
<p class="alt">
Don't have a family yet? <a href="/signup">Create one</a>
</p>
<p>Trying to join as a child? Ask your parent to issue your QR code.</p>
{/if}
</AuthShell>
<style>
form {
display: grid;
gap: 0.9rem;
}
label {
display: flex;
flex-direction: column;
gap: 0.3rem;
font-size: 0.85rem;
font-weight: 500;
color: #374151;
}
input {
padding: 0.6rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 0.95rem;
}
input:focus {
outline: none;
border-color: #4338ca;
box-shadow: 0 0 0 3px rgba(67, 56, 202, 0.15);
}
button {
margin-top: 0.25rem;
background: #4338ca;
color: #fff;
border: none;
border-radius: 8px;
padding: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
}
button:hover {
background: #3730a3;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.row-btn {
background: none;
color: #4338ca;
font-size: 0.85rem;
font-weight: 500;
padding: 0;
text-decoration: underline;
cursor: pointer;
}
.row-btn:hover {
background: none;
}
.row-btn.centered {
display: block;
margin: 1.25rem auto 0;
width: fit-content;
}
.form-ok {
background: #f0fdf4;
color: #166534;
border: 1px solid #bbf7d0;
border-radius: 8px;
padding: 0.6rem 0.75rem;
font-size: 0.85rem;
margin: 0 0 1rem;
}
.form-error {
background: #fef2f2;
color: #b91c1c;
border: 1px solid #fecaca;
border-radius: 8px;
padding: 0.6rem 0.75rem;
font-size: 0.85rem;
margin: 0 0 1rem;
}
.alt {
margin: 1.25rem 0 0;
font-size: 0.85rem;
color: #6b7280;
text-align: center;
}
.alt a {
color: #4338ca;
text-decoration: none;
font-weight: 500;
}
</style>