& so it begins

This commit is contained in:
user
2026-02-28 14:50:04 +02:00
commit f00381f2b6
536 changed files with 26294 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
<script lang="ts">
import Icon from "$lib/components/atoms/icon.svelte";
import * as Avatar from "$lib/components/ui/avatar";
import { Button } from "$lib/components/ui/button";
import * as Card from "$lib/components/ui/card";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
import { Separator } from "$lib/components/ui/separator";
import { secondaryNavTree } from "$lib/core/constants";
import { accountVM } from "$lib/domains/account/account.vm.svelte";
import { breadcrumbs } from "$lib/global.stores";
import AtSign from "@lucide/svelte/icons/at-sign";
import Mail from "@lucide/svelte/icons/mail";
import Save from "@lucide/svelte/icons/save";
import Upload from "@lucide/svelte/icons/upload";
import User from "@lucide/svelte/icons/user";
import type { PageData } from "./$types";
breadcrumbs.set([secondaryNavTree[0]]);
let { data }: { data: PageData } = $props();
const user = $state(data.user!);
// Separate form state for profile and email
let profileData = $state({
name: user.name ?? "",
username: user.username ?? "",
});
let emailData = $state({
email: user.email,
});
// Handle profile form submission (name, username)
async function handleProfileSubmit(e: SubmitEvent) {
e.preventDefault();
await accountVM.updateProfile(profileData);
}
// Handle email form submission
async function handleEmailSubmit(e: SubmitEvent) {
e.preventDefault();
if (user.email !== emailData.email) {
await accountVM.changeEmail(emailData.email);
}
}
// Handle image upload - would connect to your storage service
function handleImageUpload() {
// In a real implementation, this would trigger a file picker
console.log("Image upload triggered");
}
</script>
<div class="space-y-8">
<!-- Profile Information -->
<Card.Root>
<Card.Header>
<div class="text-center">
<div class="flex flex-col items-center justify-center gap-4">
<div class="relative">
<Avatar.Root class="border-muted h-24 w-24 border-2">
{#if user.image}
<Avatar.Image
src={user.image}
alt={user.name || "User"}
/>
{:else}
<Avatar.Fallback class="bg-primary/10 text-xl">
{(user.name || "User")
.substring(0, 2)
.toUpperCase()}
</Avatar.Fallback>
{/if}
</Avatar.Root>
<button
class="bg-primary text-primary-foreground hover:bg-primary/90 focus:ring-ring absolute right-0 bottom-0 rounded-full p-1.5 shadow focus:ring-2 focus:ring-offset-2 focus:outline-none"
onclick={handleImageUpload}
aria-label="Upload new profile image"
type="button"
>
<Icon icon={Upload} cls="h-3 w-3" />
</button>
</div>
<div>
<h1 class="text-foreground text-xl font-semibold">
{user.name}
</h1>
<p class="text-muted-foreground text-sm">
Member since {new Date(
user.createdAt.toString(),
).toLocaleDateString()}
</p>
</div>
</div>
</div>
<Separator class="mt-4 mb-8" />
<Card.Title>Personal Information</Card.Title>
<Card.Description>
Update your personal information and how others see you on the
platform.
</Card.Description>
</Card.Header>
<Card.Content class="space-y-6">
<!-- Profile Form (Name, Username) -->
<form onsubmit={handleProfileSubmit} class="space-y-6">
<!-- Full Name -->
<div class="grid grid-cols-1 gap-1.5">
<Label for="name" class="flex items-center gap-1.5">
<Icon
icon={User}
cls="h-3.5 w-3.5 text-muted-foreground"
/>
Full Name
</Label>
<Input
id="name"
bind:value={profileData.name}
placeholder="Your name"
minlength={3}
/>
</div>
<!-- Username -->
<div class="grid grid-cols-1 gap-1.5">
<Label for="username" class="flex items-center gap-1.5">
<Icon
icon={AtSign}
cls="h-3.5 w-3.5 text-muted-foreground"
/>
Username
</Label>
<Input
id="username"
bind:value={profileData.username}
placeholder="username"
minlength={6}
maxlength={32}
/>
<p class="text-muted-foreground text-xs">
This is your public username visible to other users.
</p>
</div>
<div class="flex justify-end">
<Button
type="submit"
disabled={accountVM.loading}
class="w-full sm:w-auto"
>
{#if accountVM.loading}
<Icon icon={Save} cls="h-4 w-4 mr-2 animate-spin" />
Saving...
{:else}
<Icon icon={Save} cls="h-4 w-4 mr-2" />
Save Profile
{/if}
</Button>
</div>
</form>
</Card.Content>
<Card.Footer>
<p class="text-muted-foreground text-xs">
Last updated: {new Date(
user.updatedAt.toString(),
).toLocaleString()}
</p>
</Card.Footer>
</Card.Root>
<!-- Email Settings -->
<Card.Root>
<Card.Header>
<Card.Title>Email Settings</Card.Title>
<Card.Description>
Manage your email address and verification status.
</Card.Description>
</Card.Header>
<Card.Content>
<form onsubmit={handleEmailSubmit} class="space-y-4">
<!-- Email -->
<div class="grid grid-cols-1 gap-1.5">
<Label for="email" class="flex items-center gap-1.5">
<Icon
icon={Mail}
cls="h-3.5 w-3.5 text-muted-foreground"
/>
Email Address
</Label>
<Input
id="email"
type="email"
bind:value={emailData.email}
placeholder="your.email@example.com"
minlength={6}
maxlength={128}
/>
<div class="flex items-center gap-1.5">
<span
class={user.emailVerified
? "text-xs text-emerald-600 dark:text-emerald-400"
: "text-xs text-amber-600"}
>
{user.emailVerified
? "Email verified"
: "Email not verified"}
</span>
{#if !user.emailVerified}
<Button
variant="link"
size="sm"
class="h-auto p-0 text-xs">Verify now</Button
>
{/if}
</div>
</div>
<div class="flex justify-end">
<Button
type="submit"
disabled={accountVM.emailLoading ||
user.email === emailData.email}
variant="outline"
class="w-full sm:w-auto"
>
{#if accountVM.emailLoading}
<Icon icon={Mail} cls="h-4 w-4 mr-2 animate-spin" />
Sending...
{:else}
<Icon icon={Mail} cls="h-4 w-4 mr-2" />
Update Email
{/if}
</Button>
</div>
</form>
</Card.Content>
<Card.Footer>
<p class="text-muted-foreground text-xs">
Changing your email will require verification of the new
address.
</p>
</Card.Footer>
</Card.Root>
</div>