212 lines
5.8 KiB
Svelte
212 lines
5.8 KiB
Svelte
<script lang="ts">
|
|
import { Card, CardContent } from "$lib/components/ui/card";
|
|
import { Input } from "$lib/components/ui/input";
|
|
import { Label } from "$lib/components/ui/label";
|
|
import { Textarea } from "$lib/components/ui/textarea";
|
|
import { Pencil, Check, X } from "lucide-svelte";
|
|
import ColorPicker from "$lib/components/ui/ColorPicker.svelte";
|
|
import ThemePicker from "$lib/components/ui/theme-picker.svelte";
|
|
import type { Wishlist } from "$lib/server/schema";
|
|
import { languageStore } from '$lib/stores/language.svelte';
|
|
import { getCardStyle } from '$lib/utils/colors';
|
|
|
|
let {
|
|
wishlist,
|
|
onTitleUpdate,
|
|
onDescriptionUpdate,
|
|
onColorUpdate,
|
|
onEndDateUpdate,
|
|
onThemeUpdate
|
|
}: {
|
|
wishlist: Wishlist;
|
|
onTitleUpdate: (title: string) => Promise<boolean>;
|
|
onDescriptionUpdate: (description: string | null) => Promise<boolean>;
|
|
onColorUpdate: (color: string | null) => void;
|
|
onEndDateUpdate: (endDate: string | null) => void;
|
|
onThemeUpdate: (theme: string | null) => void;
|
|
} = $props();
|
|
|
|
const t = $derived(languageStore.t);
|
|
|
|
let editingTitle = $state(false);
|
|
let editingDescription = $state(false);
|
|
let wishlistTitle = $state(wishlist.title);
|
|
let wishlistDescription = $state(wishlist.description || "");
|
|
let wishlistColor = $state<string | null>(wishlist.color);
|
|
let wishlistTheme = $state<string>(wishlist.theme || 'none');
|
|
let wishlistEndDate = $state<string | null>(
|
|
wishlist.endDate
|
|
? new Date(wishlist.endDate).toISOString().split("T")[0]
|
|
: null,
|
|
);
|
|
|
|
const cardStyle = $derived(getCardStyle(null, wishlistColor));
|
|
|
|
async function saveTitle() {
|
|
if (!wishlistTitle.trim()) {
|
|
wishlistTitle = wishlist.title;
|
|
editingTitle = false;
|
|
return;
|
|
}
|
|
|
|
const success = await onTitleUpdate(wishlistTitle.trim());
|
|
if (success) {
|
|
editingTitle = false;
|
|
} else {
|
|
wishlistTitle = wishlist.title;
|
|
editingTitle = false;
|
|
}
|
|
}
|
|
|
|
async function saveDescription() {
|
|
const success = await onDescriptionUpdate(wishlistDescription.trim() || null);
|
|
if (success) {
|
|
editingDescription = false;
|
|
} else {
|
|
wishlistDescription = wishlist.description || "";
|
|
editingDescription = false;
|
|
}
|
|
}
|
|
|
|
function handleEndDateChange(e: Event) {
|
|
const input = e.target as HTMLInputElement;
|
|
wishlistEndDate = input.value || null;
|
|
onEndDateUpdate(wishlistEndDate);
|
|
}
|
|
|
|
function clearEndDate() {
|
|
wishlistEndDate = null;
|
|
onEndDateUpdate(null);
|
|
}
|
|
</script>
|
|
|
|
<!-- Title Header -->
|
|
<div class="flex items-center justify-between gap-4 mb-6">
|
|
<div class="flex items-center gap-2 flex-1 min-w-0">
|
|
{#if editingTitle}
|
|
<Input
|
|
bind:value={wishlistTitle}
|
|
class="text-3xl font-bold h-auto py-0 leading-[2.25rem]"
|
|
onkeydown={(e) => {
|
|
if (e.key === "Enter") {
|
|
saveTitle();
|
|
} else if (e.key === "Escape") {
|
|
wishlistTitle = wishlist.title;
|
|
editingTitle = false;
|
|
}
|
|
}}
|
|
autofocus
|
|
/>
|
|
{:else}
|
|
<h1 class="text-3xl font-bold leading-[2.25rem]">{wishlistTitle}</h1>
|
|
{/if}
|
|
<button
|
|
type="button"
|
|
onclick={() => {
|
|
if (editingTitle) {
|
|
saveTitle();
|
|
} else {
|
|
editingTitle = true;
|
|
}
|
|
}}
|
|
class="shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors"
|
|
aria-label={editingTitle ? "Save title" : "Edit title"}
|
|
>
|
|
{#if editingTitle}
|
|
<Check class="w-4 h-4" />
|
|
{:else}
|
|
<Pencil class="w-4 h-4" />
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
<div class="flex items-center gap-2 shrink-0">
|
|
<ThemePicker
|
|
value={wishlistTheme}
|
|
onValueChange={async (theme) => {
|
|
wishlistTheme = theme;
|
|
onThemeUpdate(theme);
|
|
// Force reactivity by updating the wishlist object
|
|
wishlist.theme = theme;
|
|
}}
|
|
/>
|
|
<ColorPicker
|
|
bind:color={wishlistColor}
|
|
onchange={() => onColorUpdate(wishlistColor)}
|
|
size="sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Card -->
|
|
<Card style={cardStyle}>
|
|
<CardContent class="pt-6 space-y-4">
|
|
<!-- Description -->
|
|
<div class="space-y-2">
|
|
<div class="flex items-center justify-between gap-2">
|
|
<Label for="wishlist-description">{t.form.descriptionOptional}</Label>
|
|
<button
|
|
type="button"
|
|
onclick={() => {
|
|
if (editingDescription) {
|
|
saveDescription();
|
|
} else {
|
|
editingDescription = true;
|
|
}
|
|
}}
|
|
class="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors"
|
|
aria-label={editingDescription ? "Save description" : "Edit description"}
|
|
>
|
|
{#if editingDescription}
|
|
<Check class="w-4 h-4" />
|
|
{:else}
|
|
<Pencil class="w-4 h-4" />
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
{#if editingDescription}
|
|
<Textarea
|
|
id="wishlist-description"
|
|
bind:value={wishlistDescription}
|
|
class="w-full"
|
|
rows={3}
|
|
onkeydown={(e) => {
|
|
if (e.key === "Escape") {
|
|
wishlistDescription = wishlist.description || "";
|
|
editingDescription = false;
|
|
}
|
|
}}
|
|
autofocus
|
|
/>
|
|
{:else}
|
|
<div class="w-full py-2 px-3 rounded-md border border-input bg-transparent text-sm min-h-[80px]">
|
|
{wishlistDescription || t.form.noDescription}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- End Date -->
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4">
|
|
<Label for="wishlist-end-date">{t.form.endDateOptional}</Label>
|
|
<div class="flex items-center gap-2">
|
|
{#if wishlistEndDate}
|
|
<button
|
|
type="button"
|
|
onclick={clearEndDate}
|
|
class="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full border border-input hover:bg-accent transition-colors"
|
|
aria-label="Clear end date"
|
|
>
|
|
<X class="w-4 h-4" />
|
|
</button>
|
|
{/if}
|
|
<Input
|
|
id="wishlist-end-date"
|
|
type="date"
|
|
value={wishlistEndDate || ""}
|
|
onchange={handleEndDateChange}
|
|
class="w-full sm:w-auto"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|