create login page with login and signup forms

This commit is contained in:
hiperman
2026-02-22 01:44:53 -05:00
parent 0dbfd618f3
commit 163c4a7fca
4 changed files with 188 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
<script lang="ts">
import '../layout.css';
import favicon from '$lib/assets/favicon.svg';
import { Toaster } from 'svelte-sonner';
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>
<Toaster />
<div class="flex h-screen min-h-screen flex-col">
<!-- Header -->
<div class="flex items-center gap-4 pt-4 pl-6">
<p class="text-3xl">🎯</p>
<a href="/" class="text-2xl font-semibold">bullseye</a>
</div>
<!-- Main Content Area -->
<main class="flex-1">
{@render children?.()}
</main>
</div>
+31
View File
@@ -0,0 +1,31 @@
<script lang="ts">
import LoginForm from './login-form.svelte';
import SignupForm from './signup-form.svelte';
import * as Tabs from '$lib/components/ui/tabs/index.js';
let tabValue = $state('login');
</script>
<div class="flex h-full items-center justify-center pt-4 md:pb-42">
<div class="mx-4 w-full max-w-sm flex-col gap-6">
<Tabs.Root bind:value={tabValue}>
<Tabs.List class="grid w-full grid-cols-2">
<Tabs.Trigger value="login">Login</Tabs.Trigger>
<Tabs.Trigger value="signup">Sign Up</Tabs.Trigger>
</Tabs.List>
<div class="min-h-120">
<Tabs.Content value="login">
{#if tabValue === 'login'}
<LoginForm />
{/if}
</Tabs.Content>
<Tabs.Content value="signup">
{#if tabValue === 'signup'}
<SignupForm bind:tabValue />
{/if}
</Tabs.Content>
</div>
</Tabs.Root>
</div>
</div>
+52
View File
@@ -0,0 +1,52 @@
<script lang="ts">
import { login } from '$lib/api/auth.remote';
import * as Card from '$lib/components/ui/card/index.js';
import * as Field from '$lib/components/ui/field/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Button } from '$lib/components/ui/button/index.js';
</script>
<Card.Root class="w-full max-w-sm">
<Card.Header>
<Card.Title>Login to your account</Card.Title>
<Card.Description>Enter your email below to login to your account</Card.Description>
</Card.Header>
<form {...login}>
<Card.Content>
<Field.Set>
<Field.Group>
<!-- Email field -->
<Field.Field>
<Field.Label for="email">Email</Field.Label>
<Input {...login.fields.email.as('email')} />
{#each login.fields.email.issues() ?? [] as issue}
<Field.Error>{issue.message}</Field.Error>
{/each}
</Field.Field>
<!-- Password field -->
<Field.Field>
<Field.Label for="password">Password</Field.Label>
<Input {...login.fields.password.as('password')} />
{#each login.fields.password.issues() ?? [] as issue}
<Field.Error>{issue.message}</Field.Error>
{/each}
</Field.Field>
{#each login.fields.allIssues() ?? [] as issue}
{#if !issue.path || issue.path.length === 0}
<Field.Error>{issue.message}</Field.Error>
{/if}
{/each}
</Field.Group>
</Field.Set>
</Card.Content>
<!-- Submit button -->
<Card.Footer class="flex-col gap-2 pt-6">
<Button type="submit" class="w-full">Login</Button>
</Card.Footer>
</form>
</Card.Root>
+79
View File
@@ -0,0 +1,79 @@
<script lang="ts">
import { signup } from '$lib/api/auth.remote';
import * as Card from '$lib/components/ui/card/index.js';
import * as Field from '$lib/components/ui/field/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { toast } from 'svelte-sonner';
let { tabValue = $bindable() }: { tabValue: string } = $props();
</script>
<Card.Root class="w-full max-w-sm">
<Card.Header>
<Card.Title>Sign up for an account</Card.Title>
<Card.Description>Enter your email below to register for an account</Card.Description>
</Card.Header>
<form
{...signup.enhance(async ({ submit, form }) => {
try {
await submit();
// Check if there are any validation issues
const issues = signup.fields.allIssues();
if (issues && issues.length > 0) {
return;
}
// Move to login tab on success
// TODO: Fix previous errors showing on login form
form.reset();
toast.success('Successfully registered!');
tabValue = 'login';
} catch (error) {
console.error('Unknown error occurred: ', error);
toast.error('Registration failed.');
}
})}
>
<Card.Content>
<Field.Set>
<Field.Group>
<!-- Email field -->
<Field.Field>
<Field.Label for="email">Email</Field.Label>
<Input {...signup.fields.email.as('email')} />
{#each signup.fields.email.issues() ?? [] as issue}
<Field.Error>{issue.message}</Field.Error>
{/each}
</Field.Field>
<!-- Name field -->
<Field.Field>
<Field.Label for="name">Name</Field.Label>
<Input {...signup.fields.name.as('text')} />
{#each signup.fields.name.issues() ?? [] as issue}
<Field.Error>{issue.message}</Field.Error>
{/each}
</Field.Field>
<!-- Password field -->
<Field.Field>
<Field.Label for="password">Password</Field.Label>
<Input {...signup.fields.password.as('password')} />
{#each signup.fields.password.issues() ?? [] as issue}
<Field.Error>{issue.message}</Field.Error>
{/each}
</Field.Field>
</Field.Group>
</Field.Set>
</Card.Content>
<!-- Submit button -->
<Card.Footer class="flex-col gap-2 pt-6">
<Button type="submit" class="w-full">Sign Up</Button>
</Card.Footer>
</form>
</Card.Root>