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";
import React, { useState, useEffect } from "react";
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 { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -13,8 +15,9 @@ import {
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { RsvpStatus } from "@/client/types.gen";
import { useSearchParams } from "next/navigation";
import { useSearchParams, useParams } from "next/navigation";
import { Loader2 } from "lucide-react";
import { motion } from "framer-motion";
interface RSVPProps {
eventId: string;
@@ -29,6 +32,9 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
submitGuestRsvp,
} = useGuests();
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 [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 [isProcessing, setIsProcessing] = useState(false);
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
useEffect(() => {
const invitationCode = searchParams.get("code");
if (!invitationCode) {
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;
}
@@ -57,7 +114,7 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
setGuestId(matchingGuest.id);
setError(null);
} else {
setError("Invalid invitation code. Please check your invitation link.");
setError("Codice invito non valido. Controlla il link dell'invito.");
}
}
}, [searchParams, guests, findGuestByInvitationCode]);
@@ -67,7 +124,7 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
if (!guestId) {
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;
}
@@ -86,13 +143,17 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
dietary_requirements,
});
setSuccess(true);
if (onRSVPSuccess) {
onRSVPSuccess();
setTimeout(() => {
onRSVPSuccess();
}, 2000);
}
} catch (err) {
console.error("Error submitting RSVP:", err);
setError(
"Failed to submit your RSVP. Please try again or contact the host.",
"Impossibile inviare la tua risposta. Riprova o contatta l'organizzatore.",
);
} finally {
setIsProcessing(false);
@@ -100,10 +161,20 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
};
// Show loading state while checking invitation code
if (isLoadingGuests) {
if (isLoadingGuests || isLoadingEvent || isLoadingThemes) {
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]">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<div
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>
);
}
@@ -111,57 +182,172 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
// Show error if no valid invitation code
if (error) {
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>
<h2 className="text-lg font-medium text-destructive">
Invitation Error
<h2
className="text-lg font-medium"
style={{
color: "#EF4444",
fontFamily: fonts.heading,
}}
>
Errore Invito
</h2>
<p className="mt-2 text-muted-foreground">{error}</p>
<p className="mt-2" style={{ color: colors.textLight }}>
{error}
</p>
</div>
<Button
onClick={() => (window.location.href = "/")}
className="w-full md:w-auto self-end"
style={{
backgroundColor: colors.primary,
color: "white",
}}
>
Return Home
Torna alla Home
</Button>
</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 (
<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>
<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>
<form onSubmit={submitRsvp} className="space-y-4">
<div>
<Label htmlFor="status" className="mb-2 block">
Your Attendance
<form onSubmit={submitRsvp} className="space-y-5">
<motion.div variants={itemVariants}>
<Label
htmlFor="status"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
La tua Partecipazione
</Label>
<Select
defaultValue={status}
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>
<span className="capitalize">{status.replace("_", " ")}</span>
{status === "attending" && "Parteciperò"}
{status === "not_attending" && "Non parteciperò"}
{status === "maybe" && "Forse"}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="attending">Attending</SelectItem>
<SelectItem value="not_attending">Not Attending</SelectItem>
<SelectItem value="maybe">Maybe</SelectItem>
<SelectContent
style={{
backgroundColor: colors.background,
borderColor: colors.backgroundDark,
}}
>
<SelectItem value="attending">Parteciperò</SelectItem>
<SelectItem value="not_attending">Non parteciperò</SelectItem>
<SelectItem value="maybe">Forse</SelectItem>
</SelectContent>
</Select>
</div>
</motion.div>
<div>
<Label htmlFor="number_of_guests" className="mb-2 block">
Number of Guests
<motion.div variants={itemVariants}>
<Label
htmlFor="number_of_guests"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
Numero di Ospiti
</Label>
<Input
id="number_of_guests"
@@ -171,57 +357,84 @@ export const RSVP: React.FC<RSVPProps> = ({ eventId, onRSVPSuccess }) => {
onChange={(e) =>
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">
How many people (including yourself) will be attending?
<p className="text-sm mt-1" style={{ color: colors.textLight }}>
Quante persone (incluso te) parteciperanno?
</p>
</div>
</motion.div>
<div>
<Label htmlFor="response_message" className="mb-2 block">
Message to Hosts (optional)
<motion.div variants={itemVariants}>
<Label
htmlFor="response_message"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
Messaggio agli Organizzatori (opzionale)
</Label>
<Textarea
id="response_message"
value={response_message}
onChange={(e) => setResponseMessage(e.target.value)}
placeholder="Write a short message to the hosts"
placeholder="Scrivi un breve messaggio per gli organizzatori"
rows={3}
style={{
borderColor: colors.backgroundDark,
backgroundColor: "white",
color: colors.text,
}}
/>
</div>
</motion.div>
<div>
<Label htmlFor="dietary_requirements" className="mb-2 block">
Dietary Requirements
<motion.div variants={itemVariants}>
<Label
htmlFor="dietary_requirements"
className="mb-2 block font-medium"
style={{ color: colors.text }}
>
Esigenze Alimentari
</Label>
<Textarea
id="dietary_requirements"
value={dietary_requirements}
onChange={(e) => setDietaryRequirements(e.target.value)}
placeholder="Any dietary restrictions or allergies?"
placeholder="Eventuali restrizioni alimentari o allergie?"
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
type="submit"
className="w-full md:w-auto"
disabled={isProcessing}
style={{
backgroundColor: colors.accent,
color: "white",
}}
>
{isProcessing ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Submitting...
Invio in corso...
</>
) : (
"Submit RSVP"
"Invia Risposta"
)}
</Button>
</div>
</motion.div>
</form>
</div>
</div>
</motion.div>
);
};

View File

@@ -4,9 +4,11 @@ import {
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
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 {
eventId: string;
@@ -16,13 +18,66 @@ interface 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 (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader className="">
<DialogTitle></DialogTitle>
<DialogContent
style={{
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>
<RSVP eventId={eventId} onRSVPSuccess={() => setOpen(false)} />
<div className="p-1">
<RSVP eventId={eventId} onRSVPSuccess={() => setOpen(false)} />
</div>
</DialogContent>
</Dialog>
);