Translate invite page rsvp components to italian

This commit is contained in:
2025-03-17 08:16:15 +01:00
parent 8e0c5355de
commit 7ef605b468
2 changed files with 321 additions and 53 deletions

View File

@@ -1,6 +1,8 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
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 { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -13,8 +15,9 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { RsvpStatus } from "@/client/types.gen"; import { RsvpStatus } from "@/client/types.gen";
import { useSearchParams } from "next/navigation"; import { useSearchParams, useParams } from "next/navigation";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { motion } from "framer-motion";
interface RSVPProps { interface RSVPProps {
eventId: string; eventId: string;
@@ -29,6 +32,9 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
submitGuestRsvp, submitGuestRsvp,
} = useGuests(); } = useGuests();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { slug } = useParams<{ slug: string }>();
const { event, fetchEventBySlug, isLoadingEvent } = useEvents();
const { themes, isLoadingThemes } = useEventThemes();
const [status, setStatus] = useState<RsvpStatus>(RsvpStatus.ATTENDING); const [status, setStatus] = useState<RsvpStatus>(RsvpStatus.ATTENDING);
const [number_of_guests, setNumberOfGuests] = useState<number>(1); const [number_of_guests, setNumberOfGuests] = useState<number>(1);
@@ -37,13 +43,64 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
const [guestId, setGuestId] = useState<string | null>(null); const [guestId, setGuestId] = useState<string | null>(null);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<boolean>(false);
// Load event data based on slug or event ID
useEffect(() => {
if (slug) {
fetchEventBySlug(slug);
}
}, [slug, fetchEventBySlug]);
// 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",
};
// Animation variants
const formVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4 },
},
};
const itemVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.3 },
},
};
// Extract invitation code from URL parameters // Extract invitation code from URL parameters
useEffect(() => { useEffect(() => {
const invitationCode = searchParams.get("code"); const invitationCode = searchParams.get("code");
if (!invitationCode) { if (!invitationCode) {
setError( setError(
"Missing invitation code. Please use the link provided in your invitation.", "Codice invito mancante. Si prega di utilizzare il link fornito nell'invito.",
); );
return; return;
} }
@@ -57,7 +114,7 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
setGuestId(matchingGuest.id); setGuestId(matchingGuest.id);
setError(null); setError(null);
} else { } else {
setError("Invalid invitation code. Please check your invitation link."); setError("Codice invito non valido. Controlla il link dell'invito.");
} }
} }
}, [searchParams, guests, findGuestByInvitationCode]); }, [searchParams, guests, findGuestByInvitationCode]);
@@ -67,7 +124,7 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
if (!guestId) { if (!guestId) {
setError( setError(
"Cannot identify your invitation. Please check your link or contact the host.", "Impossibile identificare il tuo invito. Controlla il link o contatta l'organizzatore.",
); );
return; return;
} }
@@ -86,13 +143,17 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
dietary_requirements, dietary_requirements,
}); });
setSuccess(true);
if (onRSVPSuccess) { if (onRSVPSuccess) {
onRSVPSuccess(); setTimeout(() => {
onRSVPSuccess();
}, 2000);
} }
} catch (err) { } catch (err) {
console.error("Error submitting RSVP:", err); console.error("Error submitting RSVP:", err);
setError( setError(
"Failed to submit your RSVP. Please try again or contact the host.", "Impossibile inviare la tua risposta. Riprova o contatta l'organizzatore.",
); );
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
@@ -100,10 +161,20 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
}; };
// Show loading state while checking invitation code // Show loading state while checking invitation code
if (isLoadingGuests) { if (isLoadingGuests || isLoadingEvent || isLoadingThemes) {
return ( return (
<div className="w-full bg-card text-card-foreground rounded-lg border shadow-sm p-6 flex justify-center items-center min-h-[300px]"> <div
<Loader2 className="h-8 w-8 animate-spin text-primary" /> className="w-full rounded-lg p-6 flex justify-center items-center min-h-[300px]"
style={{
backgroundColor: colors.background,
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
<Loader2
className="h-8 w-8 animate-spin"
style={{ color: colors.primary }}
/>
</div> </div>
); );
} }
@@ -111,57 +182,172 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
// Show error if no valid invitation code // Show error if no valid invitation code
if (error) { if (error) {
return ( return (
<div className="w-full bg-card text-card-foreground rounded-lg border shadow-sm"> <div
className="w-full rounded-lg border shadow-sm"
style={{
backgroundColor: colors.background,
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
<div className="p-6 flex flex-col gap-6"> <div className="p-6 flex flex-col gap-6">
<div> <div>
<h2 className="text-lg font-medium text-destructive"> <h2
Invitation Error className="text-lg font-medium"
style={{
color: "#EF4444",
fontFamily: fonts.heading,
}}
>
Errore Invito
</h2> </h2>
<p className="mt-2 text-muted-foreground">{error}</p> <p className="mt-2" style={{ color: colors.textLight }}>
{error}
</p>
</div> </div>
<Button <Button
onClick={() => (window.location.href = "/")} onClick={() => (window.location.href = "/")}
className="w-full md:w-auto self-end" className="w-full md:w-auto self-end"
style={{
backgroundColor: colors.primary,
color: "white",
}}
> >
Return Home Torna alla Home
</Button> </Button>
</div> </div>
</div> </div>
); );
} }
// Show success message
if (success) {
return (
<motion.div
className="w-full rounded-lg border shadow-sm p-8 text-center"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
style={{
backgroundColor: colors.background,
borderColor: colors.primary,
color: colors.text,
}}
>
<div className="flex flex-col items-center gap-4">
<div
className="w-16 h-16 rounded-full flex items-center justify-center mb-2"
style={{ backgroundColor: colors.primary + "20" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-8 w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
style={{ color: colors.primary }}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
<h2
className="text-xl font-bold"
style={{
fontFamily: fonts.heading,
color: colors.secondary,
}}
>
Grazie per la tua risposta!
</h2>
<p style={{ color: colors.textLight }}>
La tua presenza è stata registrata con successo.
</p>
</div>
</motion.div>
);
}
return ( return (
<div className="w-full bg-card text-card-foreground rounded-lg border shadow-sm"> <motion.div
className="w-full rounded-lg border shadow-sm"
initial="hidden"
animate="visible"
variants={formVariants}
style={{
backgroundColor: colors.background,
borderColor: colors.backgroundDark,
color: colors.text,
}}
>
<div className="p-6 flex flex-col gap-6"> <div className="p-6 flex flex-col gap-6">
<div> <div>
<h2 className="text-lg font-medium">RSVP to this Event</h2> <h2
className="text-xl font-bold mb-1"
style={{
fontFamily: fonts.heading,
color: colors.secondary,
}}
>
Conferma la tua presenza
</h2>
<p style={{ color: colors.textLight }}>
Ti preghiamo di farci sapere se potrai partecipare
</p>
</div> </div>
<form onSubmit={submitRsvp} className="space-y-4"> <form onSubmit={submitRsvp} className="space-y-5">
<div> <motion.div variants={itemVariants}>
<Label htmlFor="status" className="mb-2 block"> <Label
Your Attendance htmlFor="status"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
La tua Partecipazione
</Label> </Label>
<Select <Select
defaultValue={status} defaultValue={status}
onValueChange={(val) => setStatus(val as any)} onValueChange={(val) => setStatus(val as any)}
> >
<SelectTrigger id="status" className="w-full"> <SelectTrigger
id="status"
className="w-full"
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
color: colors.text,
}}
>
<SelectValue> <SelectValue>
<span className="capitalize">{status.replace("_", " ")}</span> {status === "attending" && "Parteciperò"}
{status === "not_attending" && "Non parteciperò"}
{status === "maybe" && "Forse"}
</SelectValue> </SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent
<SelectItem value="attending">Attending</SelectItem> style={{
<SelectItem value="not_attending">Not Attending</SelectItem> backgroundColor: colors.background,
<SelectItem value="maybe">Maybe</SelectItem> borderColor: colors.backgroundDark,
}}
>
<SelectItem value="attending">Parteciperò</SelectItem>
<SelectItem value="not_attending">Non parteciperò</SelectItem>
<SelectItem value="maybe">Forse</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </motion.div>
<div> <motion.div variants={itemVariants}>
<Label htmlFor="number_of_guests" className="mb-2 block"> <Label
Number of Guests htmlFor="number_of_guests"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
Numero di Ospiti
</Label> </Label>
<Input <Input
id="number_of_guests" id="number_of_guests"
@@ -171,57 +357,84 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
onChange={(e) => onChange={(e) =>
setNumberOfGuests(Math.max(1, Number(e.target.value))) setNumberOfGuests(Math.max(1, Number(e.target.value)))
} }
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
color: colors.text,
}}
/> />
<p className="text-sm text-muted-foreground mt-1"> <p className="text-sm mt-1" style={{ color: colors.textLight }}>
How many people (including yourself) will be attending? Quante persone (incluso te) parteciperanno?
</p> </p>
</div> </motion.div>
<div> <motion.div variants={itemVariants}>
<Label htmlFor="response_message" className="mb-2 block"> <Label
Message to Hosts (optional) htmlFor="response_message"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
Messaggio agli Organizzatori (opzionale)
</Label> </Label>
<Textarea <Textarea
id="response_message" id="response_message"
value={response_message} value={response_message}
onChange={(e) => setResponseMessage(e.target.value)} onChange={(e) => setResponseMessage(e.target.value)}
placeholder="Write a short message to the hosts" placeholder="Scrivi un breve messaggio per gli organizzatori"
rows={3} rows={3}
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
color: colors.text,
}}
/> />
</div> </motion.div>
<div> <motion.div variants={itemVariants}>
<Label htmlFor="dietary_requirements" className="mb-2 block"> <Label
Dietary Requirements htmlFor="dietary_requirements"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
Esigenze Alimentari
</Label> </Label>
<Textarea <Textarea
id="dietary_requirements" id="dietary_requirements"
value={dietary_requirements} value={dietary_requirements}
onChange={(e) => setDietaryRequirements(e.target.value)} onChange={(e) => setDietaryRequirements(e.target.value)}
placeholder="Any dietary restrictions or allergies?" placeholder="Eventuali restrizioni alimentari o allergie?"
rows={2} rows={2}
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
color: colors.text,
}}
/> />
</div> </motion.div>
<div className="flex justify-end mt-6"> <motion.div className="flex justify-end mt-6" variants={itemVariants}>
<Button <Button
type="submit" type="submit"
className="w-full md:w-auto" className="w-full md:w-auto"
disabled={isProcessing} disabled={isProcessing}
style={{
backgroundColor: colors.accent,
color: "white",
}}
> >
{isProcessing ? ( {isProcessing ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-2 h-4 w-4 animate-spin" />
Submitting... Invio in corso...
</> </>
) : ( ) : (
"Submit RSVP" "Invia Risposta"
)} )}
</Button> </Button>
</div> </motion.div>
</form> </form>
</div> </div>
</div> </motion.div>
); );
}; };

View File

@@ -4,9 +4,11 @@ import {
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import RSVP from "@/components/rsvp/rsvp-form"; import RSVP from "@/components/rsvp/rsvp-form";
import { useEventThemes } from "@/context/event-theme-context";
import { useEvents } from "@/context/event-context";
import { motion } from "framer-motion";
interface RSVPModalProps { interface RSVPModalProps {
eventId: string; eventId: string;
@@ -16,13 +18,66 @@ interface RSVPModalProps {
} }
export function RSVPModal({ eventId, guestId, open, setOpen }: RSVPModalProps) { export function RSVPModal({ eventId, guestId, open, setOpen }: RSVPModalProps) {
const { event } = useEvents();
const { themes } = useEventThemes();
// 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",
};
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogContent> <DialogContent
<DialogHeader className=""> style={{
<DialogTitle></DialogTitle> backgroundColor: colors.background,
borderColor: colors.backgroundDark,
fontFamily: fonts.body,
maxWidth: "500px",
width: "100%",
padding: 0,
border: `1px solid ${colors.backgroundDark}`,
borderRadius: "0.75rem",
boxShadow:
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
}}
>
<DialogHeader
className="p-0"
style={{ borderColor: colors.backgroundDark }}
>
<DialogTitle
style={{
fontFamily: fonts.heading,
color: colors.secondary,
fontSize: "1.25rem",
fontWeight: "bold",
textAlign: "center",
}}
></DialogTitle>
</DialogHeader> </DialogHeader>
<RSVP eventId={eventId} onRSVPSuccess={() => setOpen(false)} /> <div className="p-1">
<RSVP eventId={eventId} onRSVPSuccess={() => setOpen(false)} />
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );