form feedback

This commit is contained in:
Benjamin Palko 2024-12-20 15:53:26 -05:00 committed by Baobeld
parent fa9a0dc9fa
commit e83de63d34
3 changed files with 72 additions and 26 deletions

View file

@ -1,4 +1,11 @@
{ {
"$schema": "https://inlang.com/schema/inlang-message-format", "$schema": "https://inlang.com/schema/inlang-message-format",
"greeting": "Hello {name}!" "greeting": "Hello {name}!",
"email_required": "Email is required",
"password_required": "Password is required",
"name_required": "Name is required",
"email_incorrect": "Email is incorrect",
"password_incorrect": "Password is incorrect",
"email_inuse": "Email is already in use",
"user_not_found": "The user could not be found"
} }

View file

@ -1,31 +1,40 @@
import { messages } from '$lib/i18n';
import { email_inuse } from '$lib/paraglide/messages';
import { logger } from '$lib/server/logger'; import { logger } from '$lib/server/logger';
import { prisma } from '$lib/server/prisma';
import { error, redirect, type Actions } from '@sveltejs/kit';
import { Argon2id } from 'oslo/password';
import { auth } from '$lib/server/lucia.js'; import { auth } from '$lib/server/lucia.js';
import { prisma } from '$lib/server/prisma';
import { fail, redirect, type Actions } from '@sveltejs/kit';
import { Argon2id } from 'oslo/password';
import { string } from 'zod';
export const actions = { export const actions = {
login: async (event) => { login: async (event) => {
const form = await event.request.formData(); const form = await event.request.formData();
if (!form.has('email')) { const email = form.get('email');
return error(400, 'Email is a required form field!'); if (typeof email !== 'string') {
return fail(400, { email: { error: messages.email_required() } });
} }
const password = form.get('password') as string;
if (!password) {
return fail(400, { password: { error: messages.password_required() } });
}
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
email: form.get('email') as string, email: email,
}, },
}); });
if (!user) { if (!user) {
logger.error('User not found! ${user}'); logger.error(`User not found! ${email}`);
return error(401); return fail(404, { email: { value: email, error: messages.user_not_found() } });
}
const password = form.get('password') as string;
if (!password) {
return error(401, 'Password is required');
} }
const validPassword = await new Argon2id().verify(user.password, password); const validPassword = await new Argon2id().verify(user.password, password);
if (!validPassword) { if (!validPassword) {
return error(400, 'Password is incorrect!'); return fail(400, {
password: { error: messages.password_incorrect() },
});
} }
const session = await auth.createSession(user.id, []); const session = await auth.createSession(user.id, []);
const sessionCookie = auth.createSessionCookie(session.id); const sessionCookie = auth.createSessionCookie(session.id);
@ -38,10 +47,29 @@ export const actions = {
register: async (event) => { register: async (event) => {
const form = await event.request.formData(); const form = await event.request.formData();
if (!form.has('email') || !form.has('name') || !form.has('password')) { if (!form.has('email')) {
return error(400); return fail(400, { email: { error: messages.email_required() } });
} }
const password = form.get('password') as string; const { success, data: email, error } = string().email().safeParse(form.get('email'));
if (!success) {
logger.error(error);
return fail(400, { email: { value: email, error: messages.email_incorrect() } });
}
const password = form.get('password');
if (typeof password !== 'string') {
return fail(400, { password: { error: messages.password_required() } });
}
const name = form.get('name');
if (typeof name !== 'string') {
return fail(400, { name: { error: messages.name_required() } });
}
const usersWithEmail = await prisma.user.count({ where: { email: email } });
if (usersWithEmail !== 0) {
return fail(409, { email: { value: email, error: email_inuse() } });
}
const hashedPassword = await new Argon2id().hash(password); const hashedPassword = await new Argon2id().hash(password);
const user = await prisma.user.create({ const user = await prisma.user.create({
data: { data: {
@ -50,15 +78,14 @@ export const actions = {
password: hashedPassword, password: hashedPassword,
}, },
}); });
const session = await auth.createSession(user.id.toString(), {}); const session = await auth.createSession(user.id.toString(), {});
const sessionCookie = auth.createSessionCookie(session.id); const sessionCookie = auth.createSessionCookie(session.id);
if (!user) {
return error(500);
}
event.cookies.set(sessionCookie.name, sessionCookie.value, { event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '/', path: '/',
maxAge: 120, maxAge: 120,
}); });
redirect(302, '/');
redirect(303, '/');
}, },
} satisfies Actions; } satisfies Actions;

View file

@ -4,6 +4,8 @@
import Tabs from '$lib/components/Navigation/Tabs'; import Tabs from '$lib/components/Navigation/Tabs';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
let { form } = $props();
let tab: 0 | 1 = $state(0); let tab: 0 | 1 = $state(0);
</script> </script>
@ -19,7 +21,14 @@
<i class="fi fi-rr-user"></i> <i class="fi fi-rr-user"></i>
{/snippet} {/snippet}
{#snippet form(variant: 'login' | 'register')} {#snippet alert(message: string)}
<div role="alert" class="alert alert-error absolute -top-20" transition:fade>
<i class="fi fi-bs-octagon-xmark h-6 w-6 shrink-0"></i>
<span>{message}</span>
</div>
{/snippet}
{#snippet Form(variant: 'login' | 'register')}
<form method="POST" action={`?/${variant}`}> <form method="POST" action={`?/${variant}`}>
<div class="card-body gap-4"> <div class="card-body gap-4">
<TextInput start={userIcon} placeholder="Email" name="email" type="email" /> <TextInput start={userIcon} placeholder="Email" name="email" type="email" />
@ -42,9 +51,12 @@
<div class="page" transition:fade> <div class="page" transition:fade>
<div class="card bg-base-200 py-4 shadow-xl"> <div class="card bg-base-200 py-4 shadow-xl">
<div class="card-title"> <div class="card-title">
{#if form}
{@render alert(Object.values(form)[0].error)}
{/if}
<Tabs variant="bordered" bind:selected={tab} tabs={['Login', 'Register']} /> <Tabs variant="bordered" bind:selected={tab} tabs={['Login', 'Register']} />
</div> </div>
{@render form(tab === 0 ? 'login' : 'register')} {@render Form(tab === 0 ? 'login' : 'register')}
</div> </div>
</div> </div>