262 lines
10 KiB
Svelte
262 lines
10 KiB
Svelte
<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 KeyRound from "@lucide/svelte/icons/key-round";
|
|
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 password
|
|
let profileData = $state({
|
|
name: user.name ?? "",
|
|
username: user.username ?? "",
|
|
});
|
|
|
|
let passwordData = $state({
|
|
password: "",
|
|
confirmPassword: "",
|
|
});
|
|
|
|
// Handle profile form submission (name, username)
|
|
async function handleProfileSubmit(e: SubmitEvent) {
|
|
e.preventDefault();
|
|
await accountVM.updateProfile(profileData);
|
|
}
|
|
|
|
// Handle password form submission
|
|
async function handlePasswordSubmit(e: SubmitEvent) {
|
|
e.preventDefault();
|
|
if (
|
|
passwordData.password.length >= 6 &&
|
|
passwordData.password === passwordData.confirmPassword
|
|
) {
|
|
const didChange = await accountVM.changePassword(
|
|
passwordData.password,
|
|
);
|
|
if (didChange) {
|
|
passwordData.password = "";
|
|
passwordData.confirmPassword = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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>
|
|
|
|
<!-- Password Settings -->
|
|
<Card.Root>
|
|
<Card.Header>
|
|
<Card.Title>Password Settings</Card.Title>
|
|
<Card.Description>
|
|
Update your account password.
|
|
</Card.Description>
|
|
</Card.Header>
|
|
<Card.Content>
|
|
<form onsubmit={handlePasswordSubmit} class="space-y-4">
|
|
<div class="grid grid-cols-1 gap-1.5">
|
|
<Label for="password" class="flex items-center gap-1.5">
|
|
<Icon
|
|
icon={KeyRound}
|
|
cls="h-3.5 w-3.5 text-muted-foreground"
|
|
/>
|
|
New Password
|
|
</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
bind:value={passwordData.password}
|
|
placeholder="Enter new password"
|
|
minlength={6}
|
|
/>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-1.5">
|
|
<Label
|
|
for="confirm-password"
|
|
class="flex items-center gap-1.5"
|
|
>
|
|
<Icon
|
|
icon={KeyRound}
|
|
cls="h-3.5 w-3.5 text-muted-foreground"
|
|
/>
|
|
Confirm Password
|
|
</Label>
|
|
<Input
|
|
id="confirm-password"
|
|
type="password"
|
|
bind:value={passwordData.confirmPassword}
|
|
placeholder="Re-enter new password"
|
|
minlength={6}
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<Button
|
|
type="submit"
|
|
disabled={accountVM.passwordLoading ||
|
|
passwordData.password.length < 6 ||
|
|
passwordData.password !==
|
|
passwordData.confirmPassword}
|
|
variant="outline"
|
|
class="w-full sm:w-auto"
|
|
>
|
|
{#if accountVM.passwordLoading}
|
|
<Icon
|
|
icon={KeyRound}
|
|
cls="h-4 w-4 mr-2 animate-spin"
|
|
/>
|
|
Updating...
|
|
{:else}
|
|
<Icon icon={KeyRound} cls="h-4 w-4 mr-2" />
|
|
Update Password
|
|
{/if}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card.Content>
|
|
<Card.Footer>
|
|
<p class="text-muted-foreground text-xs">
|
|
Choose a strong password with at least 6 characters.
|
|
</p>
|
|
</Card.Footer>
|
|
</Card.Root>
|
|
</div>
|