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,199 +351,382 @@ 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"
<Tabs variants={containerVariants}
defaultValue="available" initial="hidden"
value={activeTab} animate="visible"
onValueChange={setActiveTab}
className="w-full"
> >
<TabsList className="grid w-full grid-cols-2 mb-6"> {/* Back to Invitation Link */}
<TabsTrigger value="available" className="text-base"> <motion.div variants={itemVariants} className="mb-6">
Available Gifts <Link
</TabsTrigger> href={`/invite/${slug}?code=${invitationCode}`}
<TabsTrigger value="reserved" className="text-base"> className="inline-flex items-center text-sm hover:underline"
Your Reservations{" "} style={{ color: colors.primary }}
{reservedGifts.length > 0 && `(${reservedGifts.length})`} >
</TabsTrigger> Torna all'invito
</TabsList> </Link>
</motion.div>
<TabsContent value="available"> <motion.div
{availableGifts.length === 0 ? ( variants={itemVariants}
<Alert> className="flex flex-col items-center mb-8"
<AlertTitle>No Available Gifts</AlertTitle> >
<AlertDescription> <h1
All gifts have been reserved or there are no gifts available at className="text-4xl font-bold mb-2 text-center"
this time. style={{
</AlertDescription> fontFamily: fonts.heading,
</Alert> color: colors.secondary,
) : ( }}
<div className="overflow-x-auto"> >
<Table> Lista Regali
<TableHeader> </h1>
<TableRow> <p className="text-center mb-2" style={{ color: colors.textLight }}>
<TableHead>Name</TableHead> Scegli un regalo dalla lista per festeggiare il primo compleanno di
<TableHead>Priority</TableHead> Emma
<TableHead>Link</TableHead> </p>
<TableHead>Description</TableHead> <div
<TableHead></TableHead> className="w-24 h-1 rounded-full mt-2"
</TableRow> style={{ backgroundColor: colors.accent }}
</TableHeader> ></div>
<TableBody> </motion.div>
{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>
<TabsContent value="reserved"> <motion.div variants={itemVariants}>
{reservedGifts.length === 0 ? ( <Tabs
<Alert> defaultValue="available"
<AlertTitle>No Reservations</AlertTitle> value={activeTab}
<AlertDescription> onValueChange={setActiveTab}
You haven't reserved any gifts yet. Switch to the "Available className="w-full"
Gifts" tab to make a selection. >
</AlertDescription> <TabsList
</Alert> className="grid w-full grid-cols-2 mb-6 rounded-lg p-1"
) : ( style={{
<div className="overflow-x-auto"> backgroundColor: colors.backgroundDark,
<Table> borderColor: colors.primary,
<TableHeader> }}
<TableRow> >
<TableHead>Name</TableHead> <TabsTrigger
<TableHead>Priority</TableHead> value="available"
<TableHead>Link</TableHead> className="text-base rounded-md data-[state=active]:shadow-sm"
{/*<TableHead>Quantity</TableHead>*/} style={{
<TableHead>Description</TableHead> color: colors.text,
<TableHead></TableHead> backgroundColor: "transparent",
</TableRow> // "&[data-state=active]": {
</TableHeader> // backgroundColor: "white",
<TableBody> // color: colors.primary,
{reservedGifts.map((gift) => { // },
const priority = formatPriority(gift.priority || "medium"); }}
const quantity = getReservationQuantity(gift); >
return ( Regali Disponibili
<TableRow key={gift.id}> </TabsTrigger>
<TableCell className="font-medium"> <TabsTrigger
{gift.name} value="reserved"
</TableCell> className="text-base rounded-md data-[state=active]:shadow-sm"
<TableCell> style={{
<Badge variant="outline" className={priority.color}> color: colors.text,
{priority.label} backgroundColor: "transparent",
</Badge> // "&[data-state=active]": {
</TableCell> // backgroundColor: "white",
<TableCell> // color: colors.primary,
{gift.purchase_url ? ( // },
<a }}
href={gift.purchase_url} >
target="_blank" Le Tue Prenotazioni{" "}
rel="noopener noreferrer" {reservedGifts.length > 0 && `(${reservedGifts.length})`}
className="inline-flex items-center text-primary hover:underline" </TabsTrigger>
> </TabsList>
<ExternalLink size={14} className="mr-1" />
View Store <TabsContent value="available">
</a> {availableGifts.length === 0 ? (
) : ( <Alert
<span className="text-muted-foreground text-sm"> style={{
None backgroundColor: colors.backgroundDark,
</span> color: colors.text,
)} border: `1px solid ${colors.primary}`,
</TableCell> }}
{/*<TableCell>{quantity}</TableCell>*/} >
<TableCell>{gift.description || ""}</TableCell> <AlertTitle style={{ color: colors.text }}>
<TableCell> Nessun Regalo Disponibile
<Button </AlertTitle>
variant="outline" <AlertDescription style={{ color: colors.textLight }}>
size="sm" Tutti i regali sono stati prenotati o non ci sono regali
onClick={() => handleRemoveReservation(gift)} disponibili al momento.
> </AlertDescription>
Remove </Alert>
</Button> ) : (
</TableCell> <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> </TableRow>
); </TableHeader>
})} <TableBody>
</TableBody> {availableGifts.map((gift) => {
</Table> const priority = formatPriority(
</div> gift.priority || "medium",
)} );
</TabsContent> return (
</Tabs> <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 */} {/* 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>