Translate invite page rsvp components to italian
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user