Translate invite page to italian

This commit is contained in:
2025-03-17 08:07:17 +01:00
parent 514ca7742e
commit 8dc74c743f

View File

@@ -5,6 +5,8 @@ import React, { useEffect, useState } from "react";
import { useParams, useSearchParams } from "next/navigation";
import { useGifts } from "@/context/gift-context";
import { useGuests } from "@/context/guest-context";
import { useEventThemes } from "@/context/event-theme-context";
import { useEvents } from "@/context/event-context";
import { GiftItem, GiftPurchase } from "@/client/types.gen";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -34,10 +36,11 @@ import {
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { AlertCircle, ExternalLink, Gift, Loader2 } from "lucide-react";
import Link from "next/link";
import { motion } from "framer-motion";
export default function GiftRegistryPage() {
const { slug } = useParams<{ slug: string }>();
const searchParams = useSearchParams();
const invitationCode = searchParams.get("code");
@@ -64,6 +67,15 @@ export default function GiftRegistryPage() {
} = useGifts();
const { findGuestByInvitationCode, guests, isLoadingGuests } = useGuests();
const { themes, isLoadingThemes } = useEventThemes();
const { event, fetchEventBySlug, isLoadingEvent } = useEvents();
// Load event data based on slug
useEffect(() => {
if (slug) {
fetchEventBySlug(slug);
}
}, [slug, fetchEventBySlug]);
// Find current guest based on invitation code
const currentGuest = invitationCode
@@ -76,7 +88,7 @@ export default function GiftRegistryPage() {
if (!currentGuest) {
setErrorMessage(
"Invalid invitation code. Please check your invitation link.",
"Codice invito non valido. Controlla il link dell'invito.",
);
setIsLoading(false);
return;
@@ -90,7 +102,7 @@ export default function GiftRegistryPage() {
.catch((error) => {
console.error("Error fetching gifts:", error);
setErrorMessage(
"Unable to load gift registry. Please try again later.",
"Impossibile caricare la lista regali. Riprova più tardi.",
);
})
.finally(() => {
@@ -108,7 +120,7 @@ export default function GiftRegistryPage() {
console.error("Error fetching guest purchases:", error);
});
} else {
setErrorMessage("No event found for this invitation.");
setErrorMessage("Nessun evento trovato per questo invito.");
setIsLoading(false);
}
}, [
@@ -148,33 +160,58 @@ export default function GiftRegistryPage() {
setReservedGifts(reserved);
}
}, [items, guestPurchases]);
// Find the theme for this event
const eventTheme =
event && themes?.find((theme) => theme.id === event.theme_id);
// Enhanced color palette that works well with safari animals
const colors = eventTheme?.color_palette || {
primary: "#90B77D", // Soft jungle green
secondary: "#D2AB67", // Warm giraffe yellow
accent: "#B5A9EA", // Soft hippo purple
accent2: "#8FBDD3", // Elephant blue
accent3: "#E8B87D", // Lion tan
background: "#F9F5F0", // Cream paper texture
backgroundDark: "#F0E9D6", // Slightly darker cream for panels
text: "#5B4B49", // Warm dark brown
textLight: "#7D6D6B", // Lighter text variant
gold: "#D4AF37", // Gold accent for special elements
};
// Font selections
const fonts = eventTheme?.fonts || {
heading: "Georgia, serif",
body: "Arial, sans-serif",
};
// Format priority for display
const formatPriority = (priority: string) => {
switch (priority) {
case "must_have":
return {
label: "Must Have",
color: "bg-red-100 text-red-800 border-red-200",
label: "Obbligatorio",
color: `bg-opacity-20 text-opacity-90 bg-red-100 text-red-800 border-red-200`,
};
case "high":
return {
label: "High",
color: "bg-orange-100 text-orange-800 border-orange-200",
label: "Alta",
color: `bg-opacity-20 text-opacity-90 bg-orange-100 text-orange-800 border-orange-200`,
};
case "medium":
return {
label: "Medium",
color: "bg-blue-100 text-blue-800 border-blue-200",
label: "Media",
color: `bg-opacity-20 text-opacity-90 bg-blue-100 text-blue-800 border-blue-200`,
};
case "low":
return {
label: "Low",
color: "bg-green-100 text-green-800 border-green-200",
label: "Bassa",
color: `bg-opacity-20 text-opacity-90 bg-green-100 text-green-800 border-green-200`,
};
default:
return {
label: priority,
color: "bg-gray-100 text-gray-800 border-gray-200",
color: `bg-opacity-20 text-opacity-90 bg-gray-100 text-gray-800 border-gray-200`,
};
}
};
@@ -189,7 +226,7 @@ export default function GiftRegistryPage() {
// Remove reservation handler
const handleRemoveReservation = async (gift: GiftItem) => {
if (!currentGuest) {
setErrorMessage("Guest information not found.");
setErrorMessage("Informazioni ospite non trovate.");
return;
}
@@ -206,14 +243,14 @@ export default function GiftRegistryPage() {
}
} catch (error) {
console.error("Error removing reservation:", error);
setErrorMessage("Unable to remove reservation. Please try again.");
setErrorMessage("Impossibile rimuovere la prenotazione. Riprova.");
}
};
// Confirm reservation handler
const handleConfirmReservation = async () => {
if (!selectedGift || !currentGuest) {
setErrorMessage("Required information missing.");
setErrorMessage("Informazioni richieste mancanti.");
return;
}
@@ -235,7 +272,7 @@ export default function GiftRegistryPage() {
}
} catch (error) {
console.error("Error reserving gift:", error);
setErrorMessage("Unable to reserve gift. Please try again.");
setErrorMessage("Impossibile prenotare il regalo. Riprova.");
}
};
@@ -256,12 +293,46 @@ export default function GiftRegistryPage() {
);
};
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
duration: 0.5,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5 },
},
};
// Loading state
if (isLoading || isLoadingGuests || isLoadingItems) {
if (
isLoading ||
isLoadingGuests ||
isLoadingItems ||
isLoadingEvent ||
isLoadingThemes
) {
return (
<div className="flex flex-col items-center justify-center min-h-[300px] p-8">
<Loader2 className="h-8 w-8 animate-spin text-primary mb-4" />
<p className="text-muted-foreground">Loading gift registry...</p>
<div
className="flex flex-col items-center justify-center min-h-[300px] p-8"
style={{ backgroundColor: colors.background }}
>
<Loader2
className="h-8 w-8 animate-spin mb-4"
style={{ color: colors.primary }}
/>
<p style={{ color: colors.textLight }}>Caricamento lista regali...</p>
</div>
);
}
@@ -269,10 +340,10 @@ export default function GiftRegistryPage() {
// Error state
if (errorMessage) {
return (
<div className="p-8">
<div className="p-8" style={{ backgroundColor: colors.background }}>
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertTitle>Errore</AlertTitle>
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
</div>
@@ -280,199 +351,382 @@ export default function GiftRegistryPage() {
}
return (
<div className="container max-w-4xl mx-auto px-4 py-8">
<div className="flex flex-col items-center mb-8">
<h1 className="text-3xl font-bold mb-2 text-center">
🎁 Gift Registry 🎁
</h1>
<p className="text-muted-foreground text-center">
Choose a gift from the wishlist to help celebrate Emma's 1st birthday
</p>
</div>
<Tabs
defaultValue="available"
value={activeTab}
onValueChange={setActiveTab}
className="w-full"
<div
className="min-h-screen pb-16 pt-8"
style={{
backgroundColor: colors.background,
color: colors.text,
fontFamily: fonts.body,
}}
>
<motion.div
className="container max-w-4xl mx-auto px-4"
variants={containerVariants}
initial="hidden"
animate="visible"
>
<TabsList className="grid w-full grid-cols-2 mb-6">
<TabsTrigger value="available" className="text-base">
Available Gifts
</TabsTrigger>
<TabsTrigger value="reserved" className="text-base">
Your Reservations{" "}
{reservedGifts.length > 0 && `(${reservedGifts.length})`}
</TabsTrigger>
</TabsList>
{/* Back to Invitation Link */}
<motion.div variants={itemVariants} className="mb-6">
<Link
href={`/invite/${slug}?code=${invitationCode}`}
className="inline-flex items-center text-sm hover:underline"
style={{ color: colors.primary }}
>
Torna all'invito
</Link>
</motion.div>
<TabsContent value="available">
{availableGifts.length === 0 ? (
<Alert>
<AlertTitle>No Available Gifts</AlertTitle>
<AlertDescription>
All gifts have been reserved or there are no gifts available at
this time.
</AlertDescription>
</Alert>
) : (
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Priority</TableHead>
<TableHead>Link</TableHead>
<TableHead>Description</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{availableGifts.map((gift) => {
const priority = formatPriority(gift.priority || "medium");
return (
<TableRow key={gift.id}>
<TableCell className="font-medium">
{gift.name}
</TableCell>
<TableCell>
<Badge variant="outline" className={priority.color}>
{priority.label}
</Badge>
</TableCell>
<TableCell>
{gift.purchase_url ? (
<a
href={gift.purchase_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-primary hover:underline"
>
<ExternalLink size={14} className="mr-1" />
View Store
</a>
) : (
<span className="text-muted-foreground text-sm">
None
</span>
)}
</TableCell>
<TableCell>{gift.description || ""}</TableCell>
<TableCell>
<Button
size="sm"
onClick={() => handleReserveClick(gift)}
>
<Gift className="h-4 w-4 mr-1" />
Reserve
</Button>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
)}
</TabsContent>
<motion.div
variants={itemVariants}
className="flex flex-col items-center mb-8"
>
<h1
className="text-4xl font-bold mb-2 text-center"
style={{
fontFamily: fonts.heading,
color: colors.secondary,
}}
>
Lista Regali
</h1>
<p className="text-center mb-2" style={{ color: colors.textLight }}>
Scegli un regalo dalla lista per festeggiare il primo compleanno di
Emma
</p>
<div
className="w-24 h-1 rounded-full mt-2"
style={{ backgroundColor: colors.accent }}
></div>
</motion.div>
<TabsContent value="reserved">
{reservedGifts.length === 0 ? (
<Alert>
<AlertTitle>No Reservations</AlertTitle>
<AlertDescription>
You haven't reserved any gifts yet. Switch to the "Available
Gifts" tab to make a selection.
</AlertDescription>
</Alert>
) : (
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Priority</TableHead>
<TableHead>Link</TableHead>
{/*<TableHead>Quantity</TableHead>*/}
<TableHead>Description</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{reservedGifts.map((gift) => {
const priority = formatPriority(gift.priority || "medium");
const quantity = getReservationQuantity(gift);
return (
<TableRow key={gift.id}>
<TableCell className="font-medium">
{gift.name}
</TableCell>
<TableCell>
<Badge variant="outline" className={priority.color}>
{priority.label}
</Badge>
</TableCell>
<TableCell>
{gift.purchase_url ? (
<a
href={gift.purchase_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-primary hover:underline"
>
<ExternalLink size={14} className="mr-1" />
View Store
</a>
) : (
<span className="text-muted-foreground text-sm">
None
</span>
)}
</TableCell>
{/*<TableCell>{quantity}</TableCell>*/}
<TableCell>{gift.description || ""}</TableCell>
<TableCell>
<Button
variant="outline"
size="sm"
onClick={() => handleRemoveReservation(gift)}
>
Remove
</Button>
</TableCell>
<motion.div variants={itemVariants}>
<Tabs
defaultValue="available"
value={activeTab}
onValueChange={setActiveTab}
className="w-full"
>
<TabsList
className="grid w-full grid-cols-2 mb-6 rounded-lg p-1"
style={{
backgroundColor: colors.backgroundDark,
borderColor: colors.primary,
}}
>
<TabsTrigger
value="available"
className="text-base rounded-md data-[state=active]:shadow-sm"
style={{
color: colors.text,
backgroundColor: "transparent",
// "&[data-state=active]": {
// backgroundColor: "white",
// color: colors.primary,
// },
}}
>
Regali Disponibili
</TabsTrigger>
<TabsTrigger
value="reserved"
className="text-base rounded-md data-[state=active]:shadow-sm"
style={{
color: colors.text,
backgroundColor: "transparent",
// "&[data-state=active]": {
// backgroundColor: "white",
// color: colors.primary,
// },
}}
>
Le Tue Prenotazioni{" "}
{reservedGifts.length > 0 && `(${reservedGifts.length})`}
</TabsTrigger>
</TabsList>
<TabsContent value="available">
{availableGifts.length === 0 ? (
<Alert
style={{
backgroundColor: colors.backgroundDark,
color: colors.text,
border: `1px solid ${colors.primary}`,
}}
>
<AlertTitle style={{ color: colors.text }}>
Nessun Regalo Disponibile
</AlertTitle>
<AlertDescription style={{ color: colors.textLight }}>
Tutti i regali sono stati prenotati o non ci sono regali
disponibili al momento.
</AlertDescription>
</Alert>
) : (
<div
className="overflow-x-auto rounded-lg border"
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
}}
>
<Table>
<TableHeader
style={{ backgroundColor: colors.backgroundDark }}
>
<TableRow>
<TableHead style={{ color: colors.text }}>
Nome
</TableHead>
<TableHead style={{ color: colors.text }}>
Priorità
</TableHead>
<TableHead style={{ color: colors.text }}>
Link
</TableHead>
<TableHead style={{ color: colors.text }}>
Descrizione
</TableHead>
<TableHead></TableHead>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
)}
</TabsContent>
</Tabs>
</TableHeader>
<TableBody>
{availableGifts.map((gift) => {
const priority = formatPriority(
gift.priority || "medium",
);
return (
<TableRow
key={gift.id}
style={{ borderBottomColor: colors.backgroundDark }}
>
<TableCell
className="font-medium"
style={{ color: colors.text }}
>
{gift.name}
</TableCell>
<TableCell>
<Badge
variant="outline"
className={priority.color}
>
{priority.label}
</Badge>
</TableCell>
<TableCell>
{gift.purchase_url ? (
<a
href={gift.purchase_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center hover:underline"
style={{ color: colors.primary }}
>
<ExternalLink size={14} className="mr-1" />
Vedi Negozio
</a>
) : (
<span style={{ color: colors.textLight }}>
Nessuno
</span>
)}
</TableCell>
<TableCell style={{ color: colors.textLight }}>
{gift.description || ""}
</TableCell>
<TableCell>
<Button
size="sm"
onClick={() => handleReserveClick(gift)}
style={{
backgroundColor: colors.secondary,
color: "white",
}}
className="hover:opacity-90"
>
<Gift className="h-4 w-4 mr-1" />
Prenota
</Button>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
)}
</TabsContent>
<TabsContent value="reserved">
{reservedGifts.length === 0 ? (
<Alert
style={{
backgroundColor: colors.backgroundDark,
color: colors.text,
border: `1px solid ${colors.primary}`,
}}
>
<AlertTitle style={{ color: colors.text }}>
Nessuna Prenotazione
</AlertTitle>
<AlertDescription style={{ color: colors.textLight }}>
Non hai ancora prenotato alcun regalo. Passa alla scheda
"Regali Disponibili" per fare una selezione.
</AlertDescription>
</Alert>
) : (
<div
className="overflow-x-auto rounded-lg border"
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
}}
>
<Table>
<TableHeader
style={{ backgroundColor: colors.backgroundDark }}
>
<TableRow>
<TableHead style={{ color: colors.text }}>
Name
</TableHead>
<TableHead style={{ color: colors.text }}>
Priority
</TableHead>
<TableHead style={{ color: colors.text }}>
Link
</TableHead>
<TableHead style={{ color: colors.text }}>
Description
</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{reservedGifts.map((gift) => {
const priority = formatPriority(
gift.priority || "medium",
);
const quantity = getReservationQuantity(gift);
return (
<TableRow
key={gift.id}
style={{ borderBottomColor: colors.backgroundDark }}
>
<TableCell
className="font-medium"
style={{ color: colors.text }}
>
{gift.name}
</TableCell>
<TableCell>
<Badge
variant="outline"
className={priority.color}
>
{priority.label}
</Badge>
</TableCell>
<TableCell>
{gift.purchase_url ? (
<a
href={gift.purchase_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center hover:underline"
style={{ color: colors.primary }}
>
<ExternalLink size={14} className="mr-1" />
View Store
</a>
) : (
<span style={{ color: colors.textLight }}>
None
</span>
)}
</TableCell>
<TableCell style={{ color: colors.textLight }}>
{gift.description || ""}
</TableCell>
<TableCell>
<Button
variant="outline"
size="sm"
onClick={() => handleRemoveReservation(gift)}
style={{
borderColor: colors.primary,
color: colors.primary,
backgroundColor: "white",
}}
className="hover:opacity-90"
>
Rimuovi
</Button>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
)}
</TabsContent>
</Tabs>
</motion.div>
</motion.div>
{/* Reservation Dialog */}
<Dialog open={isReserving} onOpenChange={setIsReserving}>
<DialogContent>
<DialogContent
style={{
backgroundColor: colors.background,
borderColor: colors.primary,
}}
>
<DialogHeader>
<DialogTitle>Reserve Gift</DialogTitle>
<DialogDescription>
<DialogTitle
style={{
color: colors.secondary,
fontFamily: fonts.heading,
}}
>
Prenota Regalo
</DialogTitle>
<DialogDescription style={{ color: colors.textLight }}>
{selectedGift?.name} - {selectedGift?.description}
</DialogDescription>
</DialogHeader>
<div className="py-4">
<div className="mb-4">
<label className="block text-sm font-medium mb-1">
How many would you like to reserve?
<label
className="block text-sm font-medium mb-1"
style={{ color: colors.text }}
>
Quanti ne vuoi prenotare?
</label>
<Select value={quantity} onValueChange={setQuantity}>
<SelectTrigger>
<SelectValue placeholder="Select quantity" />
<SelectTrigger
style={{
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
<SelectValue placeholder="Seleziona quantità" />
</SelectTrigger>
<SelectContent>
<SelectContent
style={{
backgroundColor: colors.background,
borderColor: colors.backgroundDark,
}}
>
{selectedGift &&
getQuantityOptions(selectedGift).map((num) => (
<SelectItem key={num} value={num.toString()}>
<SelectItem
key={num}
value={num.toString()}
style={{ color: colors.text }}
>
{num}
</SelectItem>
))}
@@ -481,27 +735,46 @@ export default function GiftRegistryPage() {
</div>
{selectedGift?.purchase_url && (
<div className="mt-4 p-3 bg-muted rounded-md text-sm">
<p className="font-medium mb-1">Where to buy:</p>
<div
className="mt-4 p-3 rounded-md text-sm"
style={{ backgroundColor: colors.backgroundDark }}
>
<p className="font-medium mb-1" style={{ color: colors.text }}>
Dove acquistare:
</p>
<a
href={selectedGift.purchase_url}
target="_blank"
rel="noopener noreferrer"
className="text-primary inline-flex items-center hover:underline"
className="inline-flex items-center hover:underline"
style={{ color: colors.primary }}
>
<ExternalLink size={14} className="mr-1" />
Visit Store
Visita Negozio
</a>
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsReserving(false)}>
Cancel
<Button
variant="outline"
onClick={() => setIsReserving(false)}
style={{
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
Annulla
</Button>
<Button onClick={handleConfirmReservation}>
Confirm Reservation
<Button
onClick={handleConfirmReservation}
style={{
backgroundColor: colors.secondary,
color: "white",
}}
>
Conferma Prenotazione
</Button>
</DialogFooter>
</DialogContent>