diff --git a/apps/web/package.json b/apps/web/package.json
index 6f45161..b8c8223 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -47,6 +47,7 @@
 		"pocketbase": "^0.21.0",
 		"radix-icons-svelte": "^1.2.1",
 		"svelte-headless-table": "^0.18.1",
+		"svelte-sonner": "^0.3.17",
 		"tailwind-merge": "^2.2.1",
 		"tailwind-variants": "^0.1.20"
 	}
diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml
index 6ea71e9..03132f0 100644
--- a/apps/web/pnpm-lock.yaml
+++ b/apps/web/pnpm-lock.yaml
@@ -32,6 +32,9 @@ dependencies:
   svelte-headless-table:
     specifier: ^0.18.1
     version: 0.18.1(svelte@4.2.9)
+  svelte-sonner:
+    specifier: ^0.3.17
+    version: 0.3.17(svelte@4.2.9)
   tailwind-merge:
     specifier: ^2.2.1
     version: 2.2.1
@@ -2427,6 +2430,14 @@ packages:
       svelte-subscribe: 2.0.1(svelte@4.2.9)
     dev: false
 
+  /svelte-sonner@0.3.17(svelte@4.2.9):
+    resolution: {integrity: sha512-jociRGESILpHi6fIIcqGYE1bWAfK4ZeTLHuXxNgGMhG1FwhNRIK1bsCbXEl8/AjRV8aiNXtkS/+IlQtvb2jR9g==}
+    peerDependencies:
+      svelte: '>=3 <5'
+    dependencies:
+      svelte: 4.2.9
+    dev: false
+
   /svelte-subscribe@2.0.1(svelte@4.2.9):
     resolution: {integrity: sha512-eKXIjLxB4C7eQWPqKEdxcGfNXm2g/qJ67zmEZK/GigCZMfrTR3m7DPY93R6MX+5uoqM1FRYxl8LZ1oy4URWi2A==}
     peerDependencies:
diff --git a/apps/web/src/app.html b/apps/web/src/app.html
index 8e74737..b8c3939 100644
--- a/apps/web/src/app.html
+++ b/apps/web/src/app.html
@@ -8,9 +8,9 @@
 		<link rel="icon" type="image/png" sizes="32x32" href="%sveltekit.assets%/favicon-32x32.png" />
 		<link rel="icon" type="image/png" sizes="16x16" href="%sveltekit.assets%/favicon-16x16.png" />
 		<link rel="manifest" href="%sveltekit.assets%/site.webmanifest" />
-		<link rel="mask-icon" href="%sveltekit.assets%/safari-pinned-tab.svg" color="#000000" />
-		<meta name="msapplication-TileColor" content="#da532c" />
-		<meta name="theme-color" content="#ffffff" />
+		<link rel="mask-icon" href="%sveltekit.assets%/safari-pinned-tab.svg" color="#222222" />
+		<meta name="msapplication-TileColor" content="#222222" />
+		<meta name="theme-color" content="#222222" />
 		%sveltekit.head%
 	</head>
 	<body
diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts
index 6107f63..69fa803 100644
--- a/apps/web/src/hooks.server.ts
+++ b/apps/web/src/hooks.server.ts
@@ -22,8 +22,8 @@ export const handle: Handle = async ({ event, resolve }) => {
 			.authRefresh<{ id: string; email: string }>();
 		event.locals.id = auth.record.id;
 		event.locals.email = auth.record.email;
-	} catch (err) {
-		console.log('Error: ', err);
+	} catch (_) {
+		event.locals.pocketBase.authStore.clear();
 	}
 
 	const response = await resolve(event);
diff --git a/apps/web/src/lib/components/site/icons/logo.svelte b/apps/web/src/lib/components/site/icons/logo.svelte
index 5167ff6..199d086 100644
--- a/apps/web/src/lib/components/site/icons/logo.svelte
+++ b/apps/web/src/lib/components/site/icons/logo.svelte
@@ -3,7 +3,7 @@
 		topLeft: '#FF6C22',
 		topRight: '#FF9209',
 		bottomLeft: '#FFD099',
-		bottomRight: '#2B3499'
+		bottomRight: '#3B48D3'
 	};
 </script>
 
diff --git a/apps/web/src/lib/components/site/nav/mobile-nav.svelte b/apps/web/src/lib/components/site/nav/mobile-nav.svelte
index e213c81..5712101 100644
--- a/apps/web/src/lib/components/site/nav/mobile-nav.svelte
+++ b/apps/web/src/lib/components/site/nav/mobile-nav.svelte
@@ -25,7 +25,9 @@
 	<Sheet.Content side="right" class="pr-0">
 		<MobileLink href="/" class="flex items-center" bind:open>
 			<span class="sr-only">Logo icon (return home)</span>
-			<Icons.logo />
+			<div class="mr-4 rounded-sm bg-gray-950 p-0.5 dark:bg-transparent">
+				<Icons.logo />
+			</div>
 			<span class="font-mono font-bold tracking-tighter">{siteConfig.name}</span>
 		</MobileLink>
 		<div class="my-4 h-[calc(100vh-8rem)] overflow-auto pl-1 pt-10">
diff --git a/apps/web/src/lib/components/site/particles.svelte b/apps/web/src/lib/components/site/particles.svelte
index 6fc5880..675f3ab 100644
--- a/apps/web/src/lib/components/site/particles.svelte
+++ b/apps/web/src/lib/components/site/particles.svelte
@@ -58,10 +58,7 @@
 	}
 
 	mode.subscribe((value) => {
-		console.log('value', value);
 		color = value === 'dark' ? '#ffffff' : '#000000';
-
-		// Move the `rgb` calculation inside the `mode.subscribe` callback
 		rgb = hexToRgb(color);
 	});
 
diff --git a/apps/web/src/lib/components/site/site-navbar.svelte b/apps/web/src/lib/components/site/site-navbar.svelte
index 0f5ec7b..bdfc3cb 100644
--- a/apps/web/src/lib/components/site/site-navbar.svelte
+++ b/apps/web/src/lib/components/site/site-navbar.svelte
@@ -13,7 +13,9 @@
 	<div class="container flex h-14 items-center">
 		<a href="/" class="mr-6 flex items-center space-x-2">
 			<span class="sr-only">Logo (return home)</span>
-			<Icons.logo />
+			<div class="rounded-sm bg-gray-950 p-0.5 dark:bg-transparent">
+				<Icons.logo />
+			</div>
 			<span class="text-xl font-bold tracking-tight">{siteConfig.name}</span>
 		</a>
 		<MainNav {authenticated} />
diff --git a/apps/web/src/lib/components/ui/card/card-content.svelte b/apps/web/src/lib/components/ui/card/card-content.svelte
new file mode 100644
index 0000000..c87d58a
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/card-content.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	import { cn } from '$lib/utils';
+	import type { HTMLAttributes } from 'svelte/elements';
+
+	type $$Props = HTMLAttributes<HTMLDivElement>;
+
+	let className: $$Props['class'] = undefined;
+	export { className as class };
+</script>
+
+<div class={cn('p-6 pt-0', className)} {...$$restProps}>
+	<slot />
+</div>
diff --git a/apps/web/src/lib/components/ui/card/card-description.svelte b/apps/web/src/lib/components/ui/card/card-description.svelte
new file mode 100644
index 0000000..ffc81f9
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/card-description.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	import type { HTMLAttributes } from 'svelte/elements';
+	import { cn } from '$lib/utils';
+
+	type $$Props = HTMLAttributes<HTMLParagraphElement>;
+
+	let className: $$Props['class'] = undefined;
+	export { className as class };
+</script>
+
+<p class={cn('text-sm text-muted-foreground', className)} {...$$restProps}>
+	<slot />
+</p>
diff --git a/apps/web/src/lib/components/ui/card/card-footer.svelte b/apps/web/src/lib/components/ui/card/card-footer.svelte
new file mode 100644
index 0000000..414ded9
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/card-footer.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	import type { HTMLAttributes } from 'svelte/elements';
+	import { cn } from '$lib/utils';
+
+	type $$Props = HTMLAttributes<HTMLDivElement>;
+
+	let className: $$Props['class'] = undefined;
+	export { className as class };
+</script>
+
+<div class={cn('flex items-center p-6 pt-0', className)} {...$$restProps}>
+	<slot />
+</div>
diff --git a/apps/web/src/lib/components/ui/card/card-header.svelte b/apps/web/src/lib/components/ui/card/card-header.svelte
new file mode 100644
index 0000000..8079df3
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/card-header.svelte
@@ -0,0 +1,13 @@
+<script lang="ts">
+	import type { HTMLAttributes } from 'svelte/elements';
+	import { cn } from '$lib/utils';
+
+	type $$Props = HTMLAttributes<HTMLDivElement>;
+
+	let className: $$Props['class'] = undefined;
+	export { className as class };
+</script>
+
+<div class={cn('flex flex-col space-y-1.5 p-6', className)} {...$$restProps}>
+	<slot />
+</div>
diff --git a/apps/web/src/lib/components/ui/card/card-title.svelte b/apps/web/src/lib/components/ui/card/card-title.svelte
new file mode 100644
index 0000000..d0d98c0
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/card-title.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+	import type { HTMLAttributes } from 'svelte/elements';
+	import { cn } from '$lib/utils';
+	import type { HeadingLevel } from '.';
+
+	type $$Props = HTMLAttributes<HTMLHeadingElement> & {
+		tag?: HeadingLevel;
+	};
+
+	let className: $$Props['class'] = undefined;
+	export let tag: $$Props['tag'] = 'h3';
+	export { className as class };
+</script>
+
+<svelte:element
+	this={tag}
+	class={cn('font-semibold leading-none tracking-tight', className)}
+	{...$$restProps}
+>
+	<slot />
+</svelte:element>
diff --git a/apps/web/src/lib/components/ui/card/card.svelte b/apps/web/src/lib/components/ui/card/card.svelte
new file mode 100644
index 0000000..8bc551c
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/card.svelte
@@ -0,0 +1,22 @@
+<script lang="ts">
+	import type { HTMLAttributes } from 'svelte/elements';
+	import { cn } from '$lib/utils';
+
+	type $$Props = HTMLAttributes<HTMLDivElement>;
+
+	let className: $$Props['class'] = undefined;
+	export { className as class };
+</script>
+
+<!-- svelte-ignore a11y-no-static-element-interactions -->
+<div
+	class={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
+	{...$$restProps}
+	on:click
+	on:focusin
+	on:focusout
+	on:mouseenter
+	on:mouseleave
+>
+	<slot />
+</div>
diff --git a/apps/web/src/lib/components/ui/card/index.ts b/apps/web/src/lib/components/ui/card/index.ts
new file mode 100644
index 0000000..86c5408
--- /dev/null
+++ b/apps/web/src/lib/components/ui/card/index.ts
@@ -0,0 +1,24 @@
+import Root from './card.svelte';
+import Content from './card-content.svelte';
+import Description from './card-description.svelte';
+import Footer from './card-footer.svelte';
+import Header from './card-header.svelte';
+import Title from './card-title.svelte';
+
+export {
+	Root,
+	Content,
+	Description,
+	Footer,
+	Header,
+	Title,
+	//
+	Root as Card,
+	Content as CardContent,
+	Description as CardDescription,
+	Footer as CardFooter,
+	Header as CardHeader,
+	Title as CardTitle
+};
+
+export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
diff --git a/apps/web/src/lib/components/ui/sonner/index.ts b/apps/web/src/lib/components/ui/sonner/index.ts
new file mode 100644
index 0000000..fcaf06b
--- /dev/null
+++ b/apps/web/src/lib/components/ui/sonner/index.ts
@@ -0,0 +1 @@
+export { default as Toaster } from './sonner.svelte';
diff --git a/apps/web/src/lib/components/ui/sonner/sonner.svelte b/apps/web/src/lib/components/ui/sonner/sonner.svelte
new file mode 100644
index 0000000..f1d41c5
--- /dev/null
+++ b/apps/web/src/lib/components/ui/sonner/sonner.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+	import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
+	import { mode } from 'mode-watcher';
+
+	type $$Props = SonnerProps;
+</script>
+
+<Sonner
+	theme={$mode}
+	class="toaster group"
+	toastOptions={{
+		classes: {
+			toast:
+				'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
+			description: 'group-[.toast]:text-muted-foreground',
+			actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
+			cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground'
+		}
+	}}
+	{...$$restProps}
+/>
diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts
index eba19d8..a02cbcf 100644
--- a/apps/web/src/lib/utils.ts
+++ b/apps/web/src/lib/utils.ts
@@ -54,3 +54,9 @@ export const flyAndScale = (
 		easing: cubicOut
 	};
 };
+
+import { z } from 'zod';
+const emptyStringToUndefined = z.literal('').transform(() => undefined);
+export function asOptionalStringWithoutEmpty<T extends z.ZodString>(schema: T) {
+	return schema.optional().or(emptyStringToUndefined);
+}
diff --git a/apps/web/src/routes/(dashboard)/settings/(components)/avatar-form.svelte b/apps/web/src/routes/(dashboard)/settings/(components)/avatar-form.svelte
new file mode 100644
index 0000000..e3df797
--- /dev/null
+++ b/apps/web/src/routes/(dashboard)/settings/(components)/avatar-form.svelte
@@ -0,0 +1,85 @@
+<script lang="ts" context="module">
+	const MAX_IMAGE_SIZE = 5;
+	const ACCEPTED_IMAGE_TYPES = [
+		'image/jpeg',
+		'image/png',
+		'image/svg+xml',
+		'image/gif',
+		'image/webp'
+	];
+	import { z } from 'zod';
+	const sizeInMB = (sizeInBytes: number, decimalsNum = 2) => {
+		const result = sizeInBytes / (1024 * 1024);
+		return +result.toFixed(decimalsNum);
+	};
+	export const avatarFormSchema = z.object({
+		avatar: z.any()
+		// .custom<FileList>()
+		// .refine((files) => {
+		// 	return Array.from(files ?? []).length !== 0;
+		// }, 'Image is required')
+		// .refine((files) => {
+		// 	return Array.from(files ?? []).every((file) => sizeInMB(file.size) <= MAX_IMAGE_SIZE);
+		// }, `The maximum image size is ${MAX_IMAGE_SIZE}MB`)
+		// .refine((files) => {
+		// 	return Array.from(files ?? []).every((file) => ACCEPTED_IMAGE_TYPES.includes(file.type));
+		// }, 'File type is not supported')
+	});
+	export type AvatarFormSchema = typeof avatarFormSchema;
+</script>
+
+<script lang="ts">
+	import * as Card from '$lib/components/ui/card';
+	import * as Form from '$lib/components/ui/form';
+	import type { SuperValidated } from 'sveltekit-superforms';
+	import type { LayoutData } from '../../$types';
+	import * as Avatar from '$lib/components/ui/avatar';
+	import { toast } from 'svelte-sonner';
+	import type { FormOptions } from 'formsnap';
+	export let avatarFormSchemaData: SuperValidated<AvatarFormSchema>;
+	export let user: LayoutData['user'];
+	export let debug: boolean;
+	const options: FormOptions<AvatarFormSchema> = {
+		onSubmit() {
+			toast.info('Uploading avatar...');
+		},
+		onResult({ result }) {
+			if (result.status === 200) toast.success('Your avatar has been updated!');
+			if (result.status === 400) toast.error('There was an error updating your avatar.');
+		},
+		dataType: 'form'
+	};
+</script>
+
+<Card.Root>
+	<Card.Header>
+		<Card.Title>Avatar image</Card.Title>
+		<Card.Description>
+			This is the image that will be displayed on your profile, in dashboards and in emails.
+		</Card.Description>
+	</Card.Header>
+	<Card.Content>
+		<Form.Root
+			{options}
+			form={avatarFormSchemaData}
+			schema={avatarFormSchema}
+			let:config
+			action="?/avatar"
+			method="POST"
+			class="space-y-2"
+			{debug}
+			><Form.Item>
+				<Form.Field {config} name="avatar">
+					<Form.Label class="sr-only">Avatar</Form.Label>
+					<Avatar.Root class="aspect-square h-auto w-full">
+						<Avatar.Image src={user?.avatarUrl} alt={user?.name} />
+						<Avatar.Fallback class="text-8xl">{user?.initials}</Avatar.Fallback>
+					</Avatar.Root>
+					<Form.Input type="file" />
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+			<Form.Button>Update avatar</Form.Button>
+		</Form.Root>
+	</Card.Content>
+</Card.Root>
diff --git a/apps/web/src/routes/(dashboard)/settings/(components)/email-form.svelte b/apps/web/src/routes/(dashboard)/settings/(components)/email-form.svelte
new file mode 100644
index 0000000..acd356b
--- /dev/null
+++ b/apps/web/src/routes/(dashboard)/settings/(components)/email-form.svelte
@@ -0,0 +1,104 @@
+<script lang="ts" context="module">
+	import { z } from 'zod';
+	export const emailRequestFormSchema = z.object({
+		newEmail: z.string().email()
+	});
+	export const emailConfirmFormSchema = z.object({
+		token: z.string(),
+		password: z.string()
+	});
+	export type EmailRequestFormSchema = typeof emailRequestFormSchema;
+	export type EmailConfirmFormSchema = typeof emailConfirmFormSchema;
+</script>
+
+<script lang="ts">
+	import * as Card from '$lib/components/ui/card';
+	import * as Form from '$lib/components/ui/form';
+	import { toast } from 'svelte-sonner';
+	import type { SuperValidated } from 'sveltekit-superforms';
+	import type { LayoutData } from '../$types';
+	import type { FormOptions } from 'formsnap';
+	export let emailRequestFormSchemaData: SuperValidated<EmailRequestFormSchema>;
+	export let emailConfirmFormSchemaData: SuperValidated<EmailConfirmFormSchema>;
+	const requestOptions: FormOptions<EmailRequestFormSchema> = {
+		onSubmit() {
+			toast.info('Requesting token...');
+		},
+		onResult({ result }) {
+			if (result.status === 200) toast.success('Token sent! Check your email.');
+			if (result.status === 400)
+				toast.error('There was an error sending the token. Is this email already in use?');
+		}
+	};
+	const confirmOptions: FormOptions<EmailConfirmFormSchema> = {
+		onSubmit() {
+			toast.info('Changing email...');
+		},
+		onResult({ result }) {
+			console.log(result);
+			if (result.status === 200) toast.success('Your email has been changed!');
+			if (result.status === 400)
+				toast.error('There was an error changing your email. Is the token correct?');
+		}
+	};
+	export let user: LayoutData['user'];
+	export let debug: boolean;
+</script>
+
+<Card.Root>
+	<Card.Header>
+		<Card.Title>Modify your email</Card.Title>
+		<Card.Description
+			>Use a different email by requesting a token and entering it for verification below.</Card.Description
+		>
+	</Card.Header>
+	<Card.Content>
+		<Form.Root
+			schema={emailRequestFormSchema}
+			form={emailRequestFormSchemaData}
+			options={requestOptions}
+			let:config
+			method="POST"
+			action="?/emailRequest"
+			class="space-y-8"
+			{debug}
+		>
+			<Form.Item>
+				<Form.Field {config} name="newEmail">
+					<Form.Label>New email</Form.Label>
+					<div class="flex space-x-2">
+						<Form.Input placeholder={user?.email} />
+						<Form.Button variant="secondary">Request token</Form.Button>
+					</div>
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+		</Form.Root>
+		<Form.Root
+			options={confirmOptions}
+			form={emailConfirmFormSchemaData}
+			schema={emailConfirmFormSchema}
+			let:config
+			action="?/emailConfirm"
+			method="POST"
+			class="space-y-2"
+			{debug}
+		>
+			<Form.Item>
+				<Form.Field {config} name="token">
+					<Form.Label>Email verification token</Form.Label>
+					<Form.Input />
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+			<Form.Item>
+				<Form.Field {config} name="password">
+					<Form.Label>Password</Form.Label>
+					<Form.Input />
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+			<Form.Button>Confirm email change</Form.Button>
+		</Form.Root>
+	</Card.Content>
+</Card.Root>
diff --git a/apps/web/src/routes/(dashboard)/settings/(components)/name-form.svelte b/apps/web/src/routes/(dashboard)/settings/(components)/name-form.svelte
new file mode 100644
index 0000000..c084ca6
--- /dev/null
+++ b/apps/web/src/routes/(dashboard)/settings/(components)/name-form.svelte
@@ -0,0 +1,79 @@
+<script lang="ts" context="module">
+	import { z } from 'zod';
+	const emptyStringToUndefined = z.literal('').transform(() => undefined);
+	export const nameFormSchema = z.object({
+		name: z
+			.string()
+			.min(2, 'Name must be at least 2 characters.')
+			.max(30, 'Name must not be longer than 30 characters')
+			.optional()
+			.or(emptyStringToUndefined),
+		username: z
+			.string()
+			.min(2, 'Username must be at least 2 characters.')
+			.max(30, 'Username must not be longer than 30 characters')
+			.optional()
+			.or(emptyStringToUndefined)
+	});
+	export type NameFormSchema = typeof nameFormSchema;
+</script>
+
+<script lang="ts">
+	import * as Card from '$lib/components/ui/card';
+	import * as Form from '$lib/components/ui/form';
+	import type { SuperValidated } from 'sveltekit-superforms';
+	import type { LayoutData } from '../$types';
+	import type { FormOptions } from 'formsnap';
+	import { toast } from 'svelte-sonner';
+	export let nameFormSchemaData: SuperValidated<NameFormSchema>;
+	export let user: LayoutData['user'];
+	export let debug: boolean;
+	const options: FormOptions<NameFormSchema> = {
+		onSubmit() {
+			toast.info('Updating name...');
+		},
+		onResult({ result }) {
+			if (result.status === 200) toast.success('Your name has been updated!');
+			if (result.status === 400) toast.error('There was an error updating your name.');
+		},
+		dataType: 'form'
+	};
+</script>
+
+<Card.Root>
+	<Card.Header>
+		<Card.Title>Change your name</Card.Title>
+		<Card.Description>
+			You can change the name that will be displayed on your profile and in emails as well as your
+			username.
+		</Card.Description>
+	</Card.Header>
+	<Card.Content>
+		<Form.Root
+			{options}
+			form={nameFormSchemaData}
+			schema={nameFormSchema}
+			let:config
+			action="?/name"
+			method="POST"
+			class="space-y-2"
+			{debug}
+		>
+			<Form.Item>
+				<Form.Field name="name" {config}>
+					<Form.Label>Display name</Form.Label>
+					<Form.Input placeholder={user?.name} />
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+			<Form.Item>
+				<Form.Field {config} name="username">
+					<Form.Label>Username</Form.Label>
+					<Form.Input placeholder={user?.username} />
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+			<Form.Button>Update name</Form.Button>
+		</Form.Root>
+	</Card.Content>
+</Card.Root>
diff --git a/apps/web/src/routes/(dashboard)/settings/(components)/password-form.svelte b/apps/web/src/routes/(dashboard)/settings/(components)/password-form.svelte
new file mode 100644
index 0000000..8b07ec1
--- /dev/null
+++ b/apps/web/src/routes/(dashboard)/settings/(components)/password-form.svelte
@@ -0,0 +1,78 @@
+<script lang="ts" context="module">
+	import { z } from 'zod';
+	export const passwordFormSchema = z
+		.object({
+			oldPassword: z.string(),
+			password: z.string().min(4),
+			passwordConfirm: z.string().min(4)
+		})
+		.refine(
+			(values) => {
+				return values.password === values.passwordConfirm;
+			},
+			{
+				message: 'Passwords must match.',
+				path: ['passwordConfirm']
+			}
+		);
+	export type PasswordFormSchema = typeof passwordFormSchema;
+</script>
+
+<script lang="ts">
+	import * as Card from '$lib/components/ui/card';
+	import * as Form from '$lib/components/ui/form';
+	import type { SuperValidated } from 'sveltekit-superforms';
+	import type { FormOptions } from 'formsnap';
+	import { toast } from 'svelte-sonner';
+	export let passwordFormSchemaData: SuperValidated<PasswordFormSchema>;
+	export let debug: boolean;
+	const options: FormOptions<PasswordFormSchema> = {
+		onSubmit() {
+			toast.info('Changing password...');
+		},
+		onResult({ result }) {
+			if (result.status === 200)
+				toast.success('Your password has been changed! Please log in again.');
+			if (result.status === 400)
+				toast.error('There was an error changing your password. Is the old password correct?');
+		}
+	};
+</script>
+
+<Card.Root>
+	<Card.Header>
+		<Card.Title>Password change</Card.Title>
+		<Card.Description>You can change your account password here.</Card.Description>
+	</Card.Header>
+	<Card.Content>
+		<Form.Root
+			{options}
+			form={passwordFormSchemaData}
+			schema={passwordFormSchema}
+			let:config
+			action="?/password"
+			method="POST"
+			class="space-y-2"
+			{debug}
+		>
+			<Form.Item>
+				<Form.Field {config} name="oldPassword">
+					<Form.Label>Current Password</Form.Label>
+					<Form.Input type="password" />
+					<Form.Validation />
+				</Form.Field>
+				<Form.Field {config} name="password">
+					<Form.Label>New Password</Form.Label>
+					<Form.Input type="password" />
+					<Form.Validation />
+				</Form.Field>
+				<Form.Field {config} name="passwordConfirm">
+					<Form.Label>Confirm new password</Form.Label>
+					<Form.Input type="password" />
+					<Form.Validation />
+				</Form.Field>
+			</Form.Item>
+			<Form.Button>Update password</Form.Button>
+		</Form.Root>
+	</Card.Content>
+</Card.Root>
diff --git a/apps/web/src/routes/(dashboard)/settings/+page.server.ts b/apps/web/src/routes/(dashboard)/settings/+page.server.ts
index 7307867..8f1e189 100644
--- a/apps/web/src/routes/(dashboard)/settings/+page.server.ts
+++ b/apps/web/src/routes/(dashboard)/settings/+page.server.ts
@@ -1,24 +1,79 @@
 import type { PageServerLoad } from './$types';
 import { superValidate } from 'sveltekit-superforms/server';
-import { profileFormSchema } from './profile-form.svelte';
 import { fail, type Actions } from '@sveltejs/kit';
+import { nameFormSchema } from './(components)/name-form.svelte';
+import { emailConfirmFormSchema, emailRequestFormSchema } from './(components)/email-form.svelte';
+import { passwordFormSchema } from './(components)/password-form.svelte';
+import { avatarFormSchema } from './(components)/avatar-form.svelte';
 
 export const load: PageServerLoad = async () => {
 	return {
-		form: await superValidate(profileFormSchema)
+		forms: {
+			name: await superValidate(nameFormSchema),
+			emailRequest: await superValidate(emailRequestFormSchema),
+			emailConfirm: await superValidate(emailConfirmFormSchema),
+			password: await superValidate(passwordFormSchema),
+			avatar: await superValidate(avatarFormSchema),
+			debug: false
+		}
 	};
 };
 
 export const actions: Actions = {
-	default: async (event) => {
-		const form = await superValidate(event, profileFormSchema);
+	name: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+		const form = await superValidate(request, nameFormSchema);
 		if (!form.valid) {
 			return fail(400, {
 				form
 			});
 		}
-		return {
-			form
-		};
+		await locals.pocketBase.collection('users').update(locals.id, form.data);
+		return { form };
+	},
+	emailRequest: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+		console.log(request);
+		const form = await superValidate(request, emailRequestFormSchema);
+		if (!form.valid) {
+			return fail(400, {
+				form
+			});
+		}
+		await locals.pocketBase.collection('users').requestEmailChange(form.data.newEmail);
+		return { form };
+	},
+	emailConfirm: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+		const form = await superValidate(request, emailConfirmFormSchema);
+		if (!form.valid) {
+			return fail(400, {
+				form
+			});
+		}
+		await locals.pocketBase
+			.collection('users')
+			.confirmEmailChange(form.data.token, form.data.password);
+		return { form };
+	},
+	password: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+		const form = await superValidate(request, passwordFormSchema);
+		console.log(form);
+		if (!form.valid) {
+			return fail(400, {
+				form
+			});
+		}
+		await locals.pocketBase.collection('users').update(locals.id, form.data);
+		return { form };
+	},
+	avatar: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
+		const formData = await request.formData();
+		const form = await superValidate(request, avatarFormSchema);
+
+		if (!form.valid) return fail(400, { form });
+
+		const file = formData.get('file');
+		if (file instanceof File) {
+			await locals.pocketBase.collection('users').update(locals.id, { avatar: file });
+		}
+		return { form };
 	}
 };
diff --git a/apps/web/src/routes/(dashboard)/settings/+page.svelte b/apps/web/src/routes/(dashboard)/settings/+page.svelte
index 121af54..21cadb4 100644
--- a/apps/web/src/routes/(dashboard)/settings/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/settings/+page.svelte
@@ -1,10 +1,13 @@
 <script lang="ts">
 	import type { PageData } from './$types';
-	import ProfileForm from './profile-form.svelte';
 	import { Separator } from '$lib/components/ui/separator';
+	import NameForm from './(components)/name-form.svelte';
+	import EmailForm from './(components)/email-form.svelte';
+	import PasswordForm from './(components)/password-form.svelte';
+	import AvatarForm from './(components)/avatar-form.svelte';
 
 	export let data: PageData;
-	export let { form, user } = data;
+	let { forms, user } = data;
 </script>
 
 <div class="space-y-6">
@@ -13,5 +16,19 @@
 		<p class="text-sm text-muted-foreground">Update your account and profile settings.</p>
 	</div>
 	<Separator />
-	<ProfileForm data={form} {user} />
+	<div class="grid grid-cols-1 gap-4 sm:grid-cols-[4fr,3fr]">
+		<div class="space-y-8">
+			<NameForm nameFormSchemaData={forms.name} {user} debug={forms.debug} />
+			<EmailForm
+				emailRequestFormSchemaData={forms.emailRequest}
+				emailConfirmFormSchemaData={forms.emailConfirm}
+				{user}
+				debug={forms.debug}
+			/>
+			<PasswordForm passwordFormSchemaData={forms.password} debug={forms.debug} />
+		</div>
+		<div>
+			<AvatarForm avatarFormSchemaData={forms.avatar} {user} debug={forms.debug} />
+		</div>
+	</div>
 </div>
diff --git a/apps/web/src/routes/(dashboard)/settings/appearance/+page.server.ts b/apps/web/src/routes/(dashboard)/settings/appearance/+page.server.ts
index 3401cd6..e435a79 100644
--- a/apps/web/src/routes/(dashboard)/settings/appearance/+page.server.ts
+++ b/apps/web/src/routes/(dashboard)/settings/appearance/+page.server.ts
@@ -12,7 +12,6 @@ export const load: PageServerLoad = async () => {
 export const actions: Actions = {
 	default: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
 		const form = await superValidate(request, appearanceFormSchema);
-		console.log('form: ', form);
 		if (!form.valid) {
 			return fail(400, {
 				form
@@ -21,5 +20,6 @@ export const actions: Actions = {
 		await locals.pocketBase
 			.collection('users')
 			.update(locals.id, { appearanceMode: form.data.theme });
+		return { form };
 	}
 };
diff --git a/apps/web/src/routes/(dashboard)/settings/appearance/+page.svelte b/apps/web/src/routes/(dashboard)/settings/appearance/+page.svelte
index bf51e55..6dea5a0 100644
--- a/apps/web/src/routes/(dashboard)/settings/appearance/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/settings/appearance/+page.svelte
@@ -15,5 +15,5 @@
 		</p>
 	</div>
 	<Separator />
-	<AppearanceForm data={form} {user}  />
+	<AppearanceForm data={form} {user} />
 </div>
diff --git a/apps/web/src/routes/(dashboard)/settings/appearance/appearance-form.svelte b/apps/web/src/routes/(dashboard)/settings/appearance/appearance-form.svelte
index e7d8c5b..09d8e33 100644
--- a/apps/web/src/routes/(dashboard)/settings/appearance/appearance-form.svelte
+++ b/apps/web/src/routes/(dashboard)/settings/appearance/appearance-form.svelte
@@ -16,12 +16,28 @@
 	import Label from '$lib/components/ui/label/label.svelte';
 	import { dev } from '$app/environment';
 	import type { PageData } from '../$types';
+	import type { FormOptions } from 'formsnap';
+	import { toast } from 'svelte-sonner';
+	export let isLoading = false;
 	export let data: SuperValidated<AppearanceFormSchema>;
+	const options: FormOptions<AppearanceFormSchema> = {
+		onSubmit() {
+			isLoading = true;
+			toast.info('Updating appearance...');
+		},
+		onResult({ result }) {
+			isLoading = false;
+			if (result.status === 200) toast.success('Your appearance has been updated!');
+			if (result.status === 400) toast.error('There was an error updating your appearance.');
+		},
+		dataType: 'form'
+	};
 	export let user: PageData['user'];
 </script>
 
 <Form.Root
 	schema={appearanceFormSchema}
+	{options}
 	form={data}
 	class="space-y-8"
 	method="POST"
@@ -33,7 +49,11 @@
 			<Form.Label>Theme</Form.Label>
 			<Form.Description>Select the theme for the dashboard.</Form.Description>
 			<Form.Validation />
-			<Form.RadioGroup class="grid max-w-xl grid-cols-3 gap-8 pt-2" orientation="horizontal" value={user?.appearanceMode}>
+			<Form.RadioGroup
+				class="grid max-w-xl grid-cols-3 gap-8 pt-2"
+				orientation="horizontal"
+				value={user?.appearanceMode}
+			>
 				<Label for="light" class="[&:has([data-state=checked])>div]:border-primary">
 					<Form.RadioItem id="light" value="light" class="sr-only" />
 					<div class="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
@@ -101,5 +121,5 @@
 			</Form.RadioGroup>
 		</Form.Field>
 	</Form.Item>
-	<Form.Button>Update preferences</Form.Button>
+	<Form.Button disabled={isLoading}>Update preferences</Form.Button>
 </Form.Root>
diff --git a/apps/web/src/routes/(dashboard)/settings/profile-form.svelte b/apps/web/src/routes/(dashboard)/settings/profile-form.svelte
deleted file mode 100644
index 247c5a3..0000000
--- a/apps/web/src/routes/(dashboard)/settings/profile-form.svelte
+++ /dev/null
@@ -1,90 +0,0 @@
-<script lang="ts" context="module">
-	import { z } from 'zod';
-	export const profileFormSchema = z.object({
-		name: z
-			.string({
-				required_error: 'Required.'
-			})
-			.min(2, 'Name must be at least 2 characters.')
-			.max(30, 'Name must not be longer than 30 characters'),
-		username: z
-			.string()
-			.min(2, 'Username must be at least 2 characters.')
-			.max(30, 'Username must not be longer than 30 characters'),
-		email: z.string({ required_error: 'Please enter a valid email' }).email(),
-		avatar: z.any().refine((val) => val.length > 0, 'File is required')
-	});
-	export type ProfileFormSchema = typeof profileFormSchema;
-</script>
-
-<script lang="ts">
-	import * as Form from '$lib/components/ui/form';
-	import type { SuperValidated } from 'sveltekit-superforms';
-	import { dev } from '$app/environment';
-	import type { LayoutData } from '../$types';
-	import * as Avatar from '$lib/components/ui/avatar';
-
-	export let data: SuperValidated<ProfileFormSchema>;
-	export let user: LayoutData['user'];
-</script>
-
-<Form.Root
-	form={data}
-	schema={profileFormSchema}
-	let:config
-	method="POST"
-	class="space-y-8"
-	debug={dev ? true : false}
->
-	<div class="grid grid-cols-[1fr,16rem] gap-4">
-		<div>
-			<Form.Item>
-				<Form.Field name="name" {config}>
-					<Form.Label>Name</Form.Label>
-					<Form.Input placeholder={user?.name} />
-					<Form.Description>
-						This is the name that will be displayed on your profile and in emails.
-					</Form.Description>
-					<Form.Validation />
-				</Form.Field>
-			</Form.Item>
-			<Form.Item>
-				<Form.Field {config} name="username">
-					<Form.Label>Username</Form.Label>
-					<Form.Input placeholder={user?.username} />
-					<Form.Description>
-						This is your public display name. It can be your real name or a pseudonym.
-					</Form.Description>
-					<Form.Validation />
-				</Form.Field>
-			</Form.Item>
-			<Form.Item>
-				<Form.Field {config} name="email">
-					<Form.Label>Email</Form.Label>
-					<Form.Input placeholder={user?.email} />
-					<Form.Description>
-						<Form.Description>
-							This is the email address associated with your account.
-						</Form.Description>
-					</Form.Description>
-					<Form.Validation />
-				</Form.Field>
-			</Form.Item>
-		</div>
-		<Form.Item>
-			<Form.Field {config} name="avatar">
-				<Form.Label>Profile Picture</Form.Label>
-				<Avatar.Root class="aspect-square h-auto w-full">
-					<Avatar.Image src={user?.avatarUrl} alt={user?.name} />
-					<Avatar.Fallback class="text-8xl">{user?.initials}</Avatar.Fallback>
-				</Avatar.Root>
-				<Form.Input type="file" placeholder={user?.email} />
-				<Form.Description>
-					<Form.Description>Your avatar image displayed.</Form.Description>
-				</Form.Description>
-				<Form.Validation />
-			</Form.Field>
-		</Form.Item>
-	</div>
-	<Form.Button>Update profile</Form.Button>
-</Form.Root>
diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte
index 9d562f9..9ed5cec 100644
--- a/apps/web/src/routes/+layout.svelte
+++ b/apps/web/src/routes/+layout.svelte
@@ -6,12 +6,13 @@
 	import type { LayoutData } from './$types';
 	import DataIndicator from '$lib/components/site/data-indicator.svelte';
 	import { fly } from 'svelte/transition';
+	import { Toaster } from 'svelte-sonner';
 
 	export let data: LayoutData;
 </script>
 
 <ModeWatcher />
-
+<Toaster />
 <Metadata />
 
 <div class="relative flex min-h-screen flex-col" id="page">
diff --git a/apps/web/static/android-chrome-192x192.png b/apps/web/static/android-chrome-192x192.png
new file mode 100644
index 0000000..e9f34af
Binary files /dev/null and b/apps/web/static/android-chrome-192x192.png differ
diff --git a/apps/web/static/android-chrome-512x512.png b/apps/web/static/android-chrome-512x512.png
new file mode 100644
index 0000000..208e5a7
Binary files /dev/null and b/apps/web/static/android-chrome-512x512.png differ
diff --git a/apps/web/static/apple-touch-icon.png b/apps/web/static/apple-touch-icon.png
index 89fbaf5..a77caaa 100644
Binary files a/apps/web/static/apple-touch-icon.png and b/apps/web/static/apple-touch-icon.png differ
diff --git a/apps/web/static/favicon-16x16.png b/apps/web/static/favicon-16x16.png
index 8936240..5d06772 100644
Binary files a/apps/web/static/favicon-16x16.png and b/apps/web/static/favicon-16x16.png differ
diff --git a/apps/web/static/favicon-32x32.png b/apps/web/static/favicon-32x32.png
index 4c4bcca..ba5a747 100644
Binary files a/apps/web/static/favicon-32x32.png and b/apps/web/static/favicon-32x32.png differ
diff --git a/apps/web/static/favicon.ico b/apps/web/static/favicon.ico
index eabb654..a54848e 100644
Binary files a/apps/web/static/favicon.ico and b/apps/web/static/favicon.ico differ
diff --git a/apps/web/static/omnidash.svg b/apps/web/static/omnidash.svg
index 4e83575..424fa5a 100644
--- a/apps/web/static/omnidash.svg
+++ b/apps/web/static/omnidash.svg
@@ -1,7 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
-   stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
-  <rect width="7" height="9" x="3" y="3" rx="1" stroke="#FF6C22" />
-  <rect width="7" height="5" x="14" y="3" rx="1" stroke="#FF9209" />
-  <rect width="7" height="9" x="14" y="12" rx="1" stroke="#2B3499" />
-  <rect width="7" height="5" x="3" y="16" rx="1" stroke="#FFD099" />
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1" stroke="#FF6C22"></rect><rect width="7" height="5" x="14" y="3" rx="1" stroke="#FF9209"></rect><rect width="7" height="5" x="3" y="16" rx="1" stroke="#FFD099"></rect><rect width="7" height="9" x="14" y="12" rx="1" stroke="#3B48D3"></rect></svg>
\ No newline at end of file
diff --git a/apps/web/static/site.webmanifest b/apps/web/static/site.webmanifest
index 9591150..cda3fd3 100644
--- a/apps/web/static/site.webmanifest
+++ b/apps/web/static/site.webmanifest
@@ -1,6 +1,6 @@
 {
-	"name": "",
-	"short_name": "",
+	"name": "Omnidash",
+	"short_name": "Omnidash",
 	"icons": [
 		{
 			"src": "/android-chrome-192x192.png",