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 { useParams, useSearchParams } from "next/navigation";
import { useGifts } from "@/context/gift-context"; import { useGifts } from "@/context/gift-context";
import { useGuests } from "@/context/guest-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 { GiftItem, GiftPurchase } from "@/client/types.gen";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -34,10 +36,11 @@ import {
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { AlertCircle, ExternalLink, Gift, Loader2 } from "lucide-react"; import { AlertCircle, ExternalLink, Gift, Loader2 } from "lucide-react";
import Link from "next/link";
import { motion } from "framer-motion";
export default function GiftRegistryPage() { export default function GiftRegistryPage() {
const { slug } = useParams<{ slug: string }>(); const { slug } = useParams<{ slug: string }>();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const invitationCode = searchParams.get("code"); const invitationCode = searchParams.get("code");
@@ -64,6 +67,15 @@ export default function GiftRegistryPage() {
} = useGifts(); } = useGifts();
const { findGuestByInvitationCode, guests, isLoadingGuests } = useGuests(); 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 // Find current guest based on invitation code
const currentGuest = invitationCode const currentGuest = invitationCode
@@ -76,7 +88,7 @@ export default function GiftRegistryPage() {
if (!currentGuest) { if (!currentGuest) {
setErrorMessage( setErrorMessage(
"Invalid invitation code. Please check your invitation link.", "Codice invito non valido. Controlla il link dell'invito.",
); );
setIsLoading(false); setIsLoading(false);
return; return;
@@ -90,7 +102,7 @@ export default function GiftRegistryPage() {
.catch((error) => { .catch((error) => {
console.error("Error fetching gifts:", error); console.error("Error fetching gifts:", error);
setErrorMessage( setErrorMessage(
"Unable to load gift registry. Please try again later.", "Impossibile caricare la lista regali. Riprova più tardi.",
); );
}) })
.finally(() => { .finally(() => {
@@ -108,7 +120,7 @@ export default function GiftRegistryPage() {
console.error("Error fetching guest purchases:", error); console.error("Error fetching guest purchases:", error);
}); });
} else { } else {
setErrorMessage("No event found for this invitation."); setErrorMessage("Nessun evento trovato per questo invito.");
setIsLoading(false); setIsLoading(false);
} }
}, [ }, [
@@ -148,33 +160,58 @@ export default function GiftRegistryPage() {
setReservedGifts(reserved); setReservedGifts(reserved);
} }
}, [items, guestPurchases]); }, [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 // Format priority for display
const formatPriority = (priority: string) => { const formatPriority = (priority: string) => {
switch (priority) { switch (priority) {
case "must_have": case "must_have":
return { return {
label: "Must Have", label: "Obbligatorio",
color: "bg-red-100 text-red-800 border-red-200", color: `bg-opacity-20 text-opacity-90 bg-red-100 text-red-800 border-red-200`,
}; };
case "high": case "high":
return { return {
label: "High", label: "Alta",
color: "bg-orange-100 text-orange-800 border-orange-200", color: `bg-opacity-20 text-opacity-90 bg-orange-100 text-orange-800 border-orange-200`,
}; };
case "medium": case "medium":
return { return {
label: "Medium", label: "Media",
color: "bg-blue-100 text-blue-800 border-blue-200", color: `bg-opacity-20 text-opacity-90 bg-blue-100 text-blue-800 border-blue-200`,
}; };
case "low": case "low":
return { return {
label: "Low", label: "Bassa",
color: "bg-green-100 text-green-800 border-green-200", color: `bg-opacity-20 text-opacity-90 bg-green-100 text-green-800 border-green-200`,
}; };
default: default:
return { return {
label: priority, 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 // Remove reservation handler
const handleRemoveReservation = async (gift: GiftItem) => { const handleRemoveReservation = async (gift: GiftItem) => {
if (!currentGuest) { if (!currentGuest) {
setErrorMessage("Guest information not found."); setErrorMessage("Informazioni ospite non trovate.");
return; return;
} }
@@ -206,14 +243,14 @@ export default function GiftRegistryPage() {
} }
} catch (error) { } catch (error) {
console.error("Error removing reservation:", error); console.error("Error removing reservation:", error);
setErrorMessage("Unable to remove reservation. Please try again."); setErrorMessage("Impossibile rimuovere la prenotazione. Riprova.");
} }
}; };
// Confirm reservation handler // Confirm reservation handler
const handleConfirmReservation = async () => { const handleConfirmReservation = async () => {
if (!selectedGift || !currentGuest) { if (!selectedGift || !currentGuest) {
setErrorMessage("Required information missing."); setErrorMessage("Informazioni richieste mancanti.");
return; return;
} }
@@ -235,7 +272,7 @@ export default function GiftRegistryPage() {
} }
} catch (error) { } catch (error) {
console.error("Error reserving gift:", 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 // Loading state
if (isLoading || isLoadingGuests || isLoadingItems) { if (
isLoading ||
isLoadingGuests ||
isLoadingItems ||
isLoadingEvent ||
isLoadingThemes
) {
return ( return (
<div className="flex flex-col items-center justify-center min-h-[300px] p-8"> <div
<Loader2 className="h-8 w-8 animate-spin text-primary mb-4" /> className="flex flex-col items-center justify-center min-h-[300px] p-8"
<p className="text-muted-foreground">Loading gift registry...</p> 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> </div>
); );
} }
@@ -269,10 +340,10 @@ export default function GiftRegistryPage() {
// Error state // Error state
if (errorMessage) { if (errorMessage) {
return ( return (
<div className="p-8"> <div className="p-8" style={{ backgroundColor: colors.background }}>
<Alert variant="destructive"> <Alert variant="destructive">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Errore</AlertTitle>
<AlertDescription>{errorMessage}</AlertDescription> <AlertDescription>{errorMessage}</AlertDescription>
</Alert> </Alert>
</div> </div>
@@ -280,63 +351,165 @@ export default function GiftRegistryPage() {
} }
return ( return (
<div className="container max-w-4xl mx-auto px-4 py-8"> <div
<div className="flex flex-col items-center mb-8"> className="min-h-screen pb-16 pt-8"
<h1 className="text-3xl font-bold mb-2 text-center"> style={{
🎁 Gift Registry 🎁 backgroundColor: colors.background,
</h1> color: colors.text,
<p className="text-muted-foreground text-center"> fontFamily: fonts.body,
Choose a gift from the wishlist to help celebrate Emma's 1st birthday }}
</p> >
</div> <motion.div
className="container max-w-4xl mx-auto px-4"
variants={containerVariants}
initial="hidden"
animate="visible"
>
{/* 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>
<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>
<motion.div variants={itemVariants}>
<Tabs <Tabs
defaultValue="available" defaultValue="available"
value={activeTab} value={activeTab}
onValueChange={setActiveTab} onValueChange={setActiveTab}
className="w-full" className="w-full"
> >
<TabsList className="grid w-full grid-cols-2 mb-6"> <TabsList
<TabsTrigger value="available" className="text-base"> className="grid w-full grid-cols-2 mb-6 rounded-lg p-1"
Available Gifts 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>
<TabsTrigger value="reserved" className="text-base"> <TabsTrigger
Your Reservations{" "} 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})`} {reservedGifts.length > 0 && `(${reservedGifts.length})`}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="available"> <TabsContent value="available">
{availableGifts.length === 0 ? ( {availableGifts.length === 0 ? (
<Alert> <Alert
<AlertTitle>No Available Gifts</AlertTitle> style={{
<AlertDescription> backgroundColor: colors.backgroundDark,
All gifts have been reserved or there are no gifts available at color: colors.text,
this time. 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> </AlertDescription>
</Alert> </Alert>
) : ( ) : (
<div className="overflow-x-auto"> <div
className="overflow-x-auto rounded-lg border"
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
}}
>
<Table> <Table>
<TableHeader> <TableHeader
style={{ backgroundColor: colors.backgroundDark }}
>
<TableRow> <TableRow>
<TableHead>Name</TableHead> <TableHead style={{ color: colors.text }}>
<TableHead>Priority</TableHead> Nome
<TableHead>Link</TableHead> </TableHead>
<TableHead>Description</TableHead> <TableHead style={{ color: colors.text }}>
Priorità
</TableHead>
<TableHead style={{ color: colors.text }}>
Link
</TableHead>
<TableHead style={{ color: colors.text }}>
Descrizione
</TableHead>
<TableHead></TableHead> <TableHead></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{availableGifts.map((gift) => { {availableGifts.map((gift) => {
const priority = formatPriority(gift.priority || "medium"); const priority = formatPriority(
gift.priority || "medium",
);
return ( return (
<TableRow key={gift.id}> <TableRow
<TableCell className="font-medium"> key={gift.id}
style={{ borderBottomColor: colors.backgroundDark }}
>
<TableCell
className="font-medium"
style={{ color: colors.text }}
>
{gift.name} {gift.name}
</TableCell> </TableCell>
<TableCell> <TableCell>
<Badge variant="outline" className={priority.color}> <Badge
variant="outline"
className={priority.color}
>
{priority.label} {priority.label}
</Badge> </Badge>
</TableCell> </TableCell>
@@ -346,25 +519,33 @@ export default function GiftRegistryPage() {
href={gift.purchase_url} href={gift.purchase_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center text-primary hover:underline" className="inline-flex items-center hover:underline"
style={{ color: colors.primary }}
> >
<ExternalLink size={14} className="mr-1" /> <ExternalLink size={14} className="mr-1" />
View Store Vedi Negozio
</a> </a>
) : ( ) : (
<span className="text-muted-foreground text-sm"> <span style={{ color: colors.textLight }}>
None Nessuno
</span> </span>
)} )}
</TableCell> </TableCell>
<TableCell>{gift.description || ""}</TableCell> <TableCell style={{ color: colors.textLight }}>
{gift.description || ""}
</TableCell>
<TableCell> <TableCell>
<Button <Button
size="sm" size="sm"
onClick={() => handleReserveClick(gift)} onClick={() => handleReserveClick(gift)}
style={{
backgroundColor: colors.secondary,
color: "white",
}}
className="hover:opacity-90"
> >
<Gift className="h-4 w-4 mr-1" /> <Gift className="h-4 w-4 mr-1" />
Reserve Prenota
</Button> </Button>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -378,37 +559,71 @@ export default function GiftRegistryPage() {
<TabsContent value="reserved"> <TabsContent value="reserved">
{reservedGifts.length === 0 ? ( {reservedGifts.length === 0 ? (
<Alert> <Alert
<AlertTitle>No Reservations</AlertTitle> style={{
<AlertDescription> backgroundColor: colors.backgroundDark,
You haven't reserved any gifts yet. Switch to the "Available color: colors.text,
Gifts" tab to make a selection. 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> </AlertDescription>
</Alert> </Alert>
) : ( ) : (
<div className="overflow-x-auto"> <div
className="overflow-x-auto rounded-lg border"
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
}}
>
<Table> <Table>
<TableHeader> <TableHeader
style={{ backgroundColor: colors.backgroundDark }}
>
<TableRow> <TableRow>
<TableHead>Name</TableHead> <TableHead style={{ color: colors.text }}>
<TableHead>Priority</TableHead> Name
<TableHead>Link</TableHead> </TableHead>
{/*<TableHead>Quantity</TableHead>*/} <TableHead style={{ color: colors.text }}>
<TableHead>Description</TableHead> Priority
</TableHead>
<TableHead style={{ color: colors.text }}>
Link
</TableHead>
<TableHead style={{ color: colors.text }}>
Description
</TableHead>
<TableHead></TableHead> <TableHead></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{reservedGifts.map((gift) => { {reservedGifts.map((gift) => {
const priority = formatPriority(gift.priority || "medium"); const priority = formatPriority(
gift.priority || "medium",
);
const quantity = getReservationQuantity(gift); const quantity = getReservationQuantity(gift);
return ( return (
<TableRow key={gift.id}> <TableRow
<TableCell className="font-medium"> key={gift.id}
style={{ borderBottomColor: colors.backgroundDark }}
>
<TableCell
className="font-medium"
style={{ color: colors.text }}
>
{gift.name} {gift.name}
</TableCell> </TableCell>
<TableCell> <TableCell>
<Badge variant="outline" className={priority.color}> <Badge
variant="outline"
className={priority.color}
>
{priority.label} {priority.label}
</Badge> </Badge>
</TableCell> </TableCell>
@@ -418,26 +633,34 @@ export default function GiftRegistryPage() {
href={gift.purchase_url} href={gift.purchase_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center text-primary hover:underline" className="inline-flex items-center hover:underline"
style={{ color: colors.primary }}
> >
<ExternalLink size={14} className="mr-1" /> <ExternalLink size={14} className="mr-1" />
View Store View Store
</a> </a>
) : ( ) : (
<span className="text-muted-foreground text-sm"> <span style={{ color: colors.textLight }}>
None None
</span> </span>
)} )}
</TableCell> </TableCell>
{/*<TableCell>{quantity}</TableCell>*/} <TableCell style={{ color: colors.textLight }}>
<TableCell>{gift.description || ""}</TableCell> {gift.description || ""}
</TableCell>
<TableCell> <TableCell>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => handleRemoveReservation(gift)} onClick={() => handleRemoveReservation(gift)}
style={{
borderColor: colors.primary,
color: colors.primary,
backgroundColor: "white",
}}
className="hover:opacity-90"
> >
Remove Rimuovi
</Button> </Button>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -449,30 +672,61 @@ export default function GiftRegistryPage() {
)} )}
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</motion.div>
</motion.div>
{/* Reservation Dialog */} {/* Reservation Dialog */}
<Dialog open={isReserving} onOpenChange={setIsReserving}> <Dialog open={isReserving} onOpenChange={setIsReserving}>
<DialogContent> <DialogContent
style={{
backgroundColor: colors.background,
borderColor: colors.primary,
}}
>
<DialogHeader> <DialogHeader>
<DialogTitle>Reserve Gift</DialogTitle> <DialogTitle
<DialogDescription> style={{
color: colors.secondary,
fontFamily: fonts.heading,
}}
>
Prenota Regalo
</DialogTitle>
<DialogDescription style={{ color: colors.textLight }}>
{selectedGift?.name} - {selectedGift?.description} {selectedGift?.name} - {selectedGift?.description}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="py-4"> <div className="py-4">
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium mb-1"> <label
How many would you like to reserve? className="block text-sm font-medium mb-1"
style={{ color: colors.text }}
>
Quanti ne vuoi prenotare?
</label> </label>
<Select value={quantity} onValueChange={setQuantity}> <Select value={quantity} onValueChange={setQuantity}>
<SelectTrigger> <SelectTrigger
<SelectValue placeholder="Select quantity" /> style={{
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
<SelectValue placeholder="Seleziona quantità" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent
style={{
backgroundColor: colors.background,
borderColor: colors.backgroundDark,
}}
>
{selectedGift && {selectedGift &&
getQuantityOptions(selectedGift).map((num) => ( getQuantityOptions(selectedGift).map((num) => (
<SelectItem key={num} value={num.toString()}> <SelectItem
key={num}
value={num.toString()}
style={{ color: colors.text }}
>
{num} {num}
</SelectItem> </SelectItem>
))} ))}
@@ -481,27 +735,46 @@ export default function GiftRegistryPage() {
</div> </div>
{selectedGift?.purchase_url && ( {selectedGift?.purchase_url && (
<div className="mt-4 p-3 bg-muted rounded-md text-sm"> <div
<p className="font-medium mb-1">Where to buy:</p> 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 <a
href={selectedGift.purchase_url} href={selectedGift.purchase_url}
target="_blank" target="_blank"
rel="noopener noreferrer" 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" /> <ExternalLink size={14} className="mr-1" />
Visit Store Visita Negozio
</a> </a>
</div> </div>
)} )}
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setIsReserving(false)}> <Button
Cancel variant="outline"
onClick={() => setIsReserving(false)}
style={{
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
Annulla
</Button> </Button>
<Button onClick={handleConfirmReservation}> <Button
Confirm Reservation onClick={handleConfirmReservation}
style={{
backgroundColor: colors.secondary,
color: "white",
}}
>
Conferma Prenotazione
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>