Fixing the login page

This commit is contained in:
Bradley Shellnut 2025-01-01 21:51:11 -08:00
parent 2c6a22d686
commit 42d7627783
9 changed files with 63 additions and 50 deletions

View file

@ -22,7 +22,7 @@ const apiClient: Handle = async ({ event, resolve }) => {
/* ----------------------------- Auth functions ----------------------------- */ /* ----------------------------- Auth functions ----------------------------- */
async function getAuthedUser() { async function getAuthedUser() {
const { data } = await api.users.me.$get().then(parseApiResponse); const { data } = await api.users.me.$get().then(parseApiResponse);
return { user: data?.user, session: data?.session }; return data?.user;
} }
async function getAuthedUserOrThrow() { async function getAuthedUserOrThrow() {

View file

@ -0,0 +1,12 @@
import {z} from "zod";
export const signinDto = z.object({
username: z
.string()
.trim()
.min(3, { message: 'Must be at least 3 characters' })
.max(50, { message: 'Must be less than 50 characters' }),
password: z.string({ required_error: 'Password is required' }).trim(),
});
export type signinDto = z.infer<typeof signinDto>;

View file

@ -0,0 +1,12 @@
import {z} from "zod";
export const signinDto = z.object({
username: z
.string()
.trim()
.min(3, { message: 'Must be at least 3 characters' })
.max(50, { message: 'Must be less than 50 characters' }),
password: z.string({ required_error: 'Password is required' }).trim(),
});
export type signinDto = z.infer<typeof signinDto>;

View file

@ -25,6 +25,7 @@ export class UsersController extends Controller {
.get('/me', async (c) => { .get('/me', async (c) => {
const session = c.var.session; const session = c.var.session;
const user = session ? await this.usersRepository.findOneByIdOrThrow(session.userId) : null; const user = session ? await this.usersRepository.findOneByIdOrThrow(session.userId) : null;
c.var.logger.info(`Get user: ${JSON.stringify(user)}`);
return c.json(user); return c.json(user);
}) })
.patch('/me', authState('session'), zValidator('form', updateUserDto), async (c) => { .patch('/me', authState('session'), zValidator('form', updateUserDto), async (c) => {

View file

@ -11,6 +11,7 @@
import HouseIcon from 'lucide-svelte/icons/house'; import HouseIcon from 'lucide-svelte/icons/house';
import { page } from '$app/state'; import { page } from '$app/state';
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import ThemeDropdown from '@/components/theme-dropdown.svelte';
const { children, data } = $props(); const { children, data } = $props();
@ -86,10 +87,11 @@
/> />
</div> </div>
</form> </form>
<ThemeDropdown />
{#if data.authedUser} {#if data.authedUser}
{@render userDropdown()} {@render userDropdown()}
{:else} {:else}
<Button href="/register">Login</Button> <Button href="/login">Login</Button>
{/if} {/if}
</div> </div>
</header> </header>

View file

@ -2,14 +2,12 @@ import { StatusCodes } from "$lib/constants/status-codes";
import { redirect } from "@sveltejs/kit"; import { redirect } from "@sveltejs/kit";
export const load = async ({ locals }) => { export const load = async ({ locals }) => {
const user = await locals.getAuthedUser();
return { user: user };
}; };
export const actions = { export const actions = {
logout: async ({ locals }) => { logout: async ({ locals }) => {
await locals.api.iam.logout.$post() await locals.api.iam.logout.$post()
redirect(StatusCodes.SEE_OTHER, '/register') redirect(StatusCodes.SEE_OTHER, '/')
} }
} }

View file

@ -2,5 +2,5 @@
let { data } = $props(); let { data } = $props();
</script> </script>
<h1>Welcome {data?.user?.email}</h1> <h1>Welcome {data?.authedUser?.username}</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p> <p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

View file

@ -1,23 +1,22 @@
import { signinUsernameDto } from '$lib/dtos/signin-username.dto';
import { StatusCodes } from '$lib/utils/status-codes'; import { StatusCodes } from '$lib/utils/status-codes';
import { type Actions, fail } from '@sveltejs/kit'; import { type Actions, fail } from '@sveltejs/kit';
import { redirect } from 'sveltekit-flash-message/server'; import { redirect } from 'sveltekit-flash-message/server';
import { zod } from 'sveltekit-superforms/adapters'; import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { signinDto } from '@/server/api/dtos/signin.dto';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
const { locals } = event; const { locals, parent } = event;
const { authedUser } = await parent();
const { user } = await locals.getAuthedUser(); if (authedUser) {
if (user) {
console.log('user already signed in'); console.log('user already signed in');
const message = { type: 'success', message: 'You are already signed in' } as const; const message = { type: 'success', message: 'You are already signed in' } as const;
throw redirect('/', message, event); throw redirect('/', message, event);
// redirect(302, '/', message, event) // redirect(302, '/', message, event)
} }
const form = await superValidate(event, zod(signinUsernameDto)); const form = await superValidate(event, zod(signinDto));
return { return {
form, form,
@ -35,7 +34,7 @@ export const actions: Actions = {
throw redirect('/', message, event); throw redirect('/', message, event);
} }
const form = await superValidate(event, zod(signinUsernameDto)); const form = await superValidate(event, zod(signinDto));
const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse); const { error } = await locals.api.login.$post({ json: form.data }).then(locals.parseApiResponse);
console.log('Login error', error); console.log('Login error', error);

View file

@ -1,40 +1,25 @@
<script lang="ts"> <script lang="ts">
import { Button } from "$components/ui/button"; import { Button } from '$lib/components/ui/button';
import * as Card from "$components/ui/card"; import * as Card from '$lib/components/ui/card';
import * as Form from "$components/ui/form"; import { Input } from '$lib/components/ui/input';
import { Input } from "$components/ui/input"; import * as Form from '$lib/components/ui/form';
import { boredState } from "$lib/stores/boredState.js";
import { receive, send } from "$lib/utils/pageCrossfade"; import { receive, send } from "$lib/utils/pageCrossfade";
import * as flashModule from "sveltekit-flash-message/client"; import { zodClient } from "sveltekit-superforms/adapters";
import { superForm } from "sveltekit-superforms/client"; import { superForm } from "sveltekit-superforms/client";
import { signinDto } from '@/dtos/signin.dto.js';
let { data } = $props(); let { data } = $props();
const superLoginForm = superForm(data.form, { const sf_login_password = superForm(data.form, {
onSubmit: () => boredState.update((n) => ({ ...n, loading: true })), validators: zodClient(signinDto),
onResult: () => boredState.update((n) => ({ ...n, loading: false })), resetForm: false
flashMessage: {
module: flashModule,
onError: ({ result, flashMessage }) => {
// Error handling for the flash message:
// - result is the ActionResult
// - message is the flash store (not the status message store)
const errorMessage = result.error.message;
flashMessage.set({ type: "error", message: errorMessage });
},
},
syncFlashMessage: false,
taintedMessage: null,
// validators: zodClient(signInSchema),
// validationMethod: 'oninput',
delayMs: 0,
}); });
const { form: loginForm, enhance } = superLoginForm; const { form: loginForm, enhance: loginEnhance, errors: loginErrors } = sf_login_password;
</script> </script>
<svelte:head> <svelte:head>
<title>Bored Game | Login</title> <title>Acme | Login</title>
</svelte:head> </svelte:head>
<div in:receive={{ key: "auth-card" }} out:send={{ key: "auth-card" }}> <div in:receive={{ key: "auth-card" }} out:send={{ key: "auth-card" }}>
@ -44,8 +29,8 @@
</Card.Header> </Card.Header>
<Card.Content class="grid gap-4"> <Card.Content class="grid gap-4">
{@render usernamePasswordForm()} {@render usernamePasswordForm()}
<span class="text-center text-sm text-muted-foreground">or sign in with</span> <!-- <span class="text-center text-sm text-muted-foreground">or sign in with</span> -->
{@render oAuthButtons()} <!-- {@render oAuthButtons()} -->
<p class="px-8 py-4 text-center text-sm text-muted-foreground"> <p class="px-8 py-4 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our By clicking continue, you agree to our
<a href="/terms" class="underline underline-offset-4 hover:text-primary"> Terms of Use </a> <a href="/terms" class="underline underline-offset-4 hover:text-primary"> Terms of Use </a>
@ -57,18 +42,22 @@
</div> </div>
{#snippet usernamePasswordForm()} {#snippet usernamePasswordForm()}
<form method="POST" use:enhance> <form method="POST" use:loginEnhance>
<Form.Field form={superLoginForm} name="username"> <Form.Field form={sf_login_password} name="username">
<Form.Control let:attrs> <Form.Control>
<Form.Label for="username">Username</Form.Label> {#snippet children({ props })}
<Input {...attrs} autocomplete="username" bind:value={$loginForm.username} /> <Form.Label for="username">Username / Email</Form.Label>
<Input {...props} autocomplete="username" placeholder="john.doe@example.com" bind:value={$loginForm.username} />
{/snippet}
</Form.Control> </Form.Control>
<Form.FieldErrors /> <Form.FieldErrors />
</Form.Field> </Form.Field>
<Form.Field form={superLoginForm} name="password"> <Form.Field form={sf_login_password} name="password">
<Form.Control let:attrs> <Form.Control>
{#snippet children({ props })}
<Form.Label for="password">Password</Form.Label> <Form.Label for="password">Password</Form.Label>
<Input {...attrs} autocomplete="current-password" type="password" bind:value={$loginForm.password} /> <Input {...props} autocomplete="current-password" placeholder={"••••••••"} type="password" bind:value={$loginForm.password} />
{/snippet}
</Form.Control> </Form.Control>
<Form.FieldErrors /> <Form.FieldErrors />
</Form.Field> </Form.Field>