feat: Setup schemas, componentize forms

This commit is contained in:
April Hall 2025-02-15 19:01:17 -05:00
parent 3b696209e6
commit 266a329e7e
Signed by: arithefirst
GPG Key ID: 4508A15C4DB91C5B
6 changed files with 169 additions and 43 deletions

View File

@ -0,0 +1,12 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index';
import { Input } from '$lib/components/ui/input/index';
</script>
<form class="grid w-full items-start gap-3">
<fieldset class="grid w-full gap-3 rounded-lg border p-4">
<legend class="-ml-1 px-1 text-sm font-medium"> Upload Profile Image </legend>
<Input type="file" accept="image/jpeg, image/png" />
<Button type="submit">Update Profile Photo</Button>
</fieldset>
</form>

View File

@ -0,0 +1,44 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index';
import { Input } from '$lib/components/ui/input/index';
import { Label } from '$lib/components/ui/label/index';
import type { ChangePasswordSchema } from '$lib/types/schema';
import type { Infer, SuperValidated } from 'sveltekit-superforms';
import { superForm } from 'sveltekit-superforms';
let { data }: { data: SuperValidated<Infer<ChangePasswordSchema>> } = $props();
const { form, errors, message, enhance } = superForm(data);
</script>
<!-- Update Password -->
<form class="grid w-full items-start gap-3" use:enhance method="POST" action="?/updatePassword">
<fieldset class="grid w-full gap-3 rounded-lg border p-4">
<legend class="-ml-1 px-1 text-sm font-medium"> Update Password </legend>
<div class="grid gap-3">
<Label for="currentPassword">Current Password</Label>
<Input
id="currentPassword"
name="currentPassword"
type="password"
placeholder="Current Password"
bind:value={$form.currentPassword}
aria-invalid={$errors.currentPassword ? 'true' : undefined}
/>
{#if $errors.currentPassword}<span class="text-sm text-red-500">{$errors.currentPassword[0]}</span>{/if}
</div>
<div class="grid gap-3">
<Label for="newPassword">New Password</Label>
<Input
id="newPassword"
name="newPassword"
type="password"
placeholder="New Password"
bind:value={$form.newPassword}
aria-invalid={$errors.newPassword ? 'true' : undefined}
/>
{#if $errors.newPassword}<span class="text-sm text-red-500">{$errors.newPassword[0]}</span>{/if}
</div>
<Button type="submit">Update Password</Button>
{#if $message}{$message}{/if}
</fieldset>
</form>

View File

@ -0,0 +1,43 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index';
import { Input } from '$lib/components/ui/input/index';
import { Label } from '$lib/components/ui/label/index';
import type { ChangeUsernameSchema } from '$lib/types/schema';
import type { Infer, SuperValidated } from 'sveltekit-superforms';
import { superForm } from 'sveltekit-superforms';
let { data }: { data: SuperValidated<Infer<ChangeUsernameSchema>> } = $props();
const { form, errors, message, enhance } = superForm(data);
</script>
<form class="grid w-full items-start gap-3" method="POST" action="?/updateUsername" use:enhance>
<fieldset class="grid w-full gap-3 rounded-lg border p-4">
<legend class="-ml-1 px-1 text-sm font-medium"> Update Username </legend>
<div class="grid gap-3">
<Label for="username">New Username</Label>
<Input
id="username"
name="username"
type="password"
placeholder="New Username"
bind:value={$form.username}
aria-invalid={$errors.username ? 'true' : undefined}
/>
{#if $errors.username}<span class="text-sm text-red-500">{$errors.username[0]}</span>{/if}
</div>
<div class="grid gap-3">
<Label for="password">Password</Label>
<Input
id="password"
name="password"
type="password"
placeholder="Password"
bind:value={$form.password}
aria-invalid={$errors.password ? 'true' : undefined}
/>
{#if $errors.password}<span class="text-sm text-red-500">{$errors.password[0]}</span>{/if}
</div>
<Button type="submit">Update Username</Button>
{#if $message}{$message}{/if}
</fieldset>
</form>

View File

@ -32,6 +32,29 @@ export const loginSchema = z.object({
password: z.string().nonempty('Password must not be empty.'), password: z.string().nonempty('Password must not be empty.'),
}); });
export const changePasswordSchema = z.object({
currentPassword: z.string().nonempty('Password must not be empty.'),
newPassword: z
.string()
.min(8, 'New password must be at least 8 characters.')
.regex(/(?=.*[A-Z])/gm, 'New password must contain at uppercase letter.')
.regex(/(?=.*[a-z])/gm, 'New password must contain at lowercase letter.')
.regex(/(?=.*\d)/gm, 'New password must contain at least one number.')
.regex(/(?=.*\W)/gm, 'New password must contain at least one special character'),
});
export const changeUsernameSchema = z.object({
username: z
.string()
.min(3, 'Username must be at least 3 characters.')
.max(15, 'Username must be no more than 15 characters.')
.regex(/^(?![A-Z])/gm, 'Username cannot contain uppercase letters')
.regex(/^(?=[a-z0-9-_]+$)/gm, 'Username cannot contain special characters'),
password: z.string().nonempty('Password must not be empty.'),
});
export type ChangePasswordSchema = typeof changePasswordSchema;
export type ChangeUsernameSchema = typeof changeUsernameSchema;
export type NewChannelSchema = typeof newChannelSchema; export type NewChannelSchema = typeof newChannelSchema;
export type SignUpSchema = typeof signupSchema; export type SignUpSchema = typeof signupSchema;
export type LogInSchema = typeof loginSchema; export type LogInSchema = typeof loginSchema;

View File

@ -1,7 +1,11 @@
import { auth } from '$lib/server/db/auth';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { Actions } from '@sveltejs/kit';
import { fail, message, setError, superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { auth } from '$lib/server/db/auth';
import { changeUsernameSchema, changePasswordSchema } from '$lib/types/schema.js';
export async function load({ request }): Promise<void> { export async function load({ request }) {
const session = await auth.api.getSession({ const session = await auth.api.getSession({
headers: request.headers, headers: request.headers,
}); });
@ -9,4 +13,33 @@ export async function load({ request }): Promise<void> {
if (!session) { if (!session) {
redirect(307, '/signup'); redirect(307, '/signup');
} }
return {
newuserForm: await superValidate(zod(changeUsernameSchema)),
newpassForm: await superValidate(zod(changePasswordSchema)),
};
} }
export const actions = {
updatePassword: async ({ request }) => {
const newpassForm = await superValidate(request, zod(changePasswordSchema));
if (!newpassForm.valid) {
return fail(400, { newpassForm });
}
return message(newpassForm, 'Password updated.');
},
updateUsername: async ({ request }) => {
const newuserForm = await superValidate(request, zod(changeUsernameSchema));
if (!newuserForm.valid) {
return fail(400, { newuserForm });
}
return message(newuserForm, 'Password updated.');
},
updateProfilePhoto: async () => {},
deleteAccount: async () => {},
signOut: async () => {},
} satisfies Actions;

View File

@ -1,52 +1,23 @@
<script lang="ts"> <script lang="ts">
import { Input } from '$lib/components/ui/input/index'; import { Input } from '$lib/components/ui/input/index';
import { Label } from '$lib/components/ui/label/index';
import { Button } from '$lib/components/ui/button/index'; import { Button } from '$lib/components/ui/button/index';
import * as Dialog from '$lib/components/ui/dialog'; import * as Dialog from '$lib/components/ui/dialog';
let { data } = $props();
import UpdatePassword from '$lib/components/forms/updatePassword.svelte';
import UpdateUsername from '$lib/components/forms/updateUsername.svelte';
import UpdatePfp from '$lib/components/forms/updatePFP.svelte';
let open: boolean = $state(false); let open: boolean = $state(false);
</script> </script>
<main class="abs-center w-2/3"> <main class="abs-center w-2/3">
<div class="relative grid w-full grid-cols-1 gap-3 md:grid-cols-2"> <div class="relative grid w-full grid-cols-1 gap-3 md:grid-cols-2">
<!-- Update Password --> <UpdatePassword data={data.newpassForm} />
<form class="grid w-full items-start gap-3"> <UpdateUsername data={data.newuserForm} />
<fieldset class="grid w-full gap-3 rounded-lg border p-4"> <UpdatePfp />
<legend class="-ml-1 px-1 text-sm font-medium"> Update Password </legend>
<div class="grid gap-3">
<Label for="temperature">Current Password</Label>
<Input id="current" type="password" placeholder="Current Password" />
</div>
<div class="grid gap-3">
<Label for="temperature">New Password</Label>
<Input id="new" type="password" placeholder="New Password" />
</div>
<Button type="submit">Update Password</Button>
</fieldset>
</form>
<!-- Change Username -->
<form class="grid w-full items-start gap-3">
<fieldset class="grid w-full gap-3 rounded-lg border p-4">
<legend class="-ml-1 px-1 text-sm font-medium"> Update Username </legend>
<div class="grid gap-3">
<Label for="temperature">New Username</Label>
<Input id="newUsername" type="password" placeholder="New Username" />
</div>
<div class="grid gap-3">
<Label for="temperature">Password</Label>
<Input id="password" type="password" placeholder="Password" />
</div>
<Button type="submit">Update Username</Button>
</fieldset>
</form>
<!-- Upload Profile Photo -->
<form class="grid w-full items-start gap-3">
<fieldset class="grid w-full gap-3 rounded-lg border p-4">
<legend class="-ml-1 px-1 text-sm font-medium"> Upload Profile Image </legend>
<Input type="file" accept="image/jpeg, image/png" />
<Button type="submit">Update Profile Photo</Button>
</fieldset>
</form>
<!-- Account Actions --> <!-- Account Actions -->
<div class="grid w-full items-start gap-3"> <div class="grid w-full items-start gap-3">
<fieldset class="grid w-full gap-3 rounded-lg border p-4"> <fieldset class="grid w-full gap-3 rounded-lg border p-4">
@ -66,10 +37,10 @@
<Dialog.Title>Are you sure absolutely sure?</Dialog.Title> <Dialog.Title>Are you sure absolutely sure?</Dialog.Title>
<Dialog.Description> <Dialog.Description>
This action cannot be undone. This will permanently delete your account and remove your data from our database. This action cannot be undone. This will permanently delete your account and remove your data from our database.
<div class="mt-2 flex gap-2"> <form class="mt-2 flex gap-2">
<Button class="w-1/2" onclick={() => (open = !open)}>I changed my mind!</Button> <Button class="w-1/2" onclick={() => (open = !open)}>I changed my mind!</Button>
<Button variant="destructive" class="w-1/2" type="submit">Delete Account</Button> <Button variant="destructive" class="w-1/2" type="submit">Delete Account</Button>
</div> </form>
</Dialog.Description> </Dialog.Description>
</Dialog.Header> </Dialog.Header>
</Dialog.Content> </Dialog.Content>