Enhance invitation page with theme-based design and features

This update integrates theme-based styling and assets for the invitation page, including dynamic colors, fonts, and images. Added features include improved loading states, detailed event information, RSVP handling, gift registry, and interactive map functionality. This refactor enhances the user experience and supports event-specific personalization.
This commit is contained in:
2025-03-14 05:03:15 +01:00
parent 3862fdb1ad
commit 382df4f83f

View File

@@ -1,181 +1,425 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { motion } from "framer-motion"; import { useEvents } from "@/context/event-context";
import { useParams } from "next/navigation"; import { useEventThemes } from "@/context/event-theme-context";
import { Card, CardContent } from "@/components/ui/card"; import { format, parseISO } from "date-fns";
import { Button } from "@/components/ui/button";
import { Calendar, Gift, MapPin, Clock } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useEvents } from "@/context/event-context"; import { Button } from "@/components/ui/button";
import { EventResponse } from "@/client/types.gen"; import { Card } from "@/components/ui/card";
import { Loader2 } from "lucide-react";
import { useParams } from "next/navigation";
import { EventResponse } from "@/client";
import { getServerFileUrl } from "@/lib/utils";
interface InvitationParams {
slug: string;
}
const InvitationPage = () => { const InvitationPage = () => {
const { slug } = useParams(); const { slug } = useParams<{ slug: string }>();
const { getEventBySlug } = useEvents(); const { event, fetchEventBySlug, isLoadingEvent, eventError } = useEvents();
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [event, setEvent] = useState<EventResponse | null>(null);
const [notFound, setNotFound] = useState(false); const [notFound, setNotFound] = useState(false);
const [showRSVP, setShowRSVP] = useState(false); const [showRSVP, setShowRSVP] = useState(false);
const { themes, isLoadingThemes } = useEventThemes();
// Fetch event data when slug is available
useEffect(() => { useEffect(() => {
const fetchEvent = async () => { fetchEventBySlug(slug);
setIsLoading(true); }, [slug, fetchEventBySlug]);
try {
const eventData = await getEventBySlug(slug as string);
if (eventData) {
setEvent(eventData);
} else {
setNotFound(true);
}
} catch (error) {
console.error("Failed to fetch event data:", error);
setNotFound(true);
} finally {
setIsLoading(false);
}
};
if (slug) { // If loading, show a spinner
fetchEvent(); if (isLoadingEvent || isLoadingThemes) {
}
}, [slug, getEventBySlug]);
const formatDate = (date: string | Date) => {
return new Date(date).toLocaleDateString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
};
const formatTime = (date: string | Date) => {
return new Date(date).toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
hour12: true,
});
};
if (isLoading) {
return ( return (
<div className="h-screen flex items-center justify-center"> <div className="flex h-screen items-center justify-center">
<div className="text-center"> <Loader2 className="h-12 w-12 animate-spin text-primary" />
<div className="animate-bounce mb-4">
<Image
src="/api/placeholder/150/150"
alt="Loading"
width={150}
height={150}
className="rounded-full"
/>
</div>
<p className="text-xl font-medium">Loading your invitation...</p>
</div>
</div> </div>
); );
} }
if (notFound || !event) { // If no event found, show not found message
if (!event) {
return ( return (
<div className="h-screen flex flex-col items-center justify-center text-center space-y-4"> <div className="flex h-screen flex-col items-center justify-center gap-4 p-4 text-center">
<Image <h1 className="text-3xl font-bold">Invitation Not Found</h1>
src="/api/placeholder/404/404" <p>
alt="Not Found" The invitation you're looking for doesn't exist or has been removed.
width={200}
height={200}
className="rounded-xl"
/>
<h2 className="text-3xl font-semibold text-gray-800">
Event Not Found
</h2>
<p className="text-gray-500">
We couldn't find an event matching your invitation. Please check your
invitation link or contact the event organizer.
</p> </p>
<Button asChild>
<Link href="/">Go Back Home</Link>
</Button>
</div> </div>
); );
} }
// Find the theme for this event
const eventTheme = themes?.find((theme) => theme.id === event.theme_id);
// Default colors if theme is not available
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
text: "#5B4B49", // Warm dark brown
textLight: "#7D6D6B", // Lighter text variant
};
// Format date and time for display
const eventDate = event.event_date
? format(parseISO(event.event_date), "EEEE, MMMM d, yyyy")
: null;
const startTime = event.event_start_time
? format(parseISO(`2000-01-01T${event.event_start_time}`), "HH:mm")
: null;
const endTime = event.event_end_time
? format(parseISO(`2000-01-01T${event.event_end_time}`), "HH:mm")
: null;
const timeRange = startTime && endTime ? `${startTime} - ${endTime}` : null;
// Format RSVP deadline
const rsvpDeadline = event.rsvp_deadline
? format(parseISO(event.rsvp_deadline), "MMMM d, yyyy")
: null;
// Get asset URLs
const getAssetUrl = (key: string) => {
if (eventTheme?.asset_image_urls && eventTheme.asset_image_urls[key]) {
return getServerFileUrl(eventTheme.asset_image_urls[key]);
}
return null;
};
// Background style
const pageStyle = {
backgroundColor: colors.background,
color: colors.text,
fontFamily: eventTheme?.fonts?.body || "sans-serif",
};
const headingStyle = {
fontFamily: eventTheme?.fonts?.heading || "sans-serif",
color: colors.text,
};
return ( return (
<div <div className="min-h-screen pb-10" style={pageStyle}>
className="min-h-screen py-8 px-4 bg-gradient-to-b from-blue-50 to-green-50" <div className="mx-auto max-w-4xl px-4 py-6">
style={{ {/* Wooden Sign Title */}
backgroundImage: "url('/api/placeholder/20/20')", <div
backgroundRepeat: "repeat", className="mx-auto mb-10 mt-6 rounded-lg p-6 text-center"
backgroundSize: "20px 20px", style={{
}} backgroundColor: colors.secondary,
> color: colors.text,
<div className="max-w-4xl mx-auto"> boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
<motion.div }}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="text-center mb-6"
> >
<h1 <h1 className="text-4xl font-bold md:text-5xl" style={headingStyle}>
className="text-4xl sm:text-5xl md:text-6xl font-bold text-emerald-700 mb-2"
style={{ fontFamily: "var(--font-bubblegum)" }}
>
{event.title} {event.title}
</h1> </h1>
<p className="text-xl sm:text-2xl text-amber-600 font-medium"> <h2 className="mt-2 text-xl md:text-2xl" style={headingStyle}>
{event.description} Safari Adventure
</p> </h2>
</motion.div> </div>
<motion.div {/* Safari Animals Banner */}
initial={{ opacity: 0, scale: 0.9 }} <div className="mb-10 flex justify-center">
animate={{ opacity: 1, scale: 1 }} {eventTheme?.foreground_image_url ? (
transition={{ duration: 0.5, delay: 0.3 }} <div className="relative h-48 w-full max-w-2xl md:h-64">
className="rounded-2xl overflow-hidden shadow-xl bg-white p-4" <Image
> src={getServerFileUrl(eventTheme.foreground_image_url) || ""}
<Card className="shadow-none border-none"> alt="Safari Animals"
<CardContent> fill
<ul className="space-y-4"> className="object-contain"
<li className="flex items-center gap-2"> priority
<Calendar className="text-green-600" /> />
<span>{formatDate(event.event_date)}</span> </div>
</li> ) : (
<li className="flex items-center gap-2"> <div
<Clock className="text-green-600" /> className="flex h-48 w-full max-w-2xl items-center justify-center rounded-lg border-2 md:h-64"
<span> style={{
{formatTime(event.event_date)} -{" "} borderColor: colors.primary,
{formatTime(event.event_end_time || "")} backgroundColor: "rgba(255, 255, 255, 0.5)",
</span> }}
</li> >
<li className="flex items-center gap-2"> <p className="text-center text-lg">Safari Animals Illustration</p>
<MapPin className="text-green-600" /> </div>
<span> )}
{event.location_name}, {event.location_address} </div>
</span>
</li> {/* Date/Time with Elephant */}
{event.gift_registry_enabled && ( <div className="mb-10 flex flex-col items-center md:flex-row md:items-start md:space-x-6">
<li className="flex items-center gap-2"> <div className="mb-4 md:mb-0">
<Gift className="text-green-600" /> <div
<Link href="/gift-registry"> className="flex h-28 w-28 items-center justify-center rounded-full border-2 md:h-32 md:w-32"
<span className="underline cursor-pointer"> style={{ borderColor: colors.accent2 }}
Gift Registry >
</span> {getAssetUrl("elephant") ? (
</Link> <div className="relative h-24 w-24 md:h-28 md:w-28">
</li> <Image
)} src={getAssetUrl("elephant") || ""}
</ul> alt="Elephant"
<Button className="mt-6" onClick={() => setShowRSVP(true)}> fill
RSVP className="object-contain"
</Button> />
{showRSVP && (
<div className="mt-4 text-center">
<p>RSVP functionality coming soon!</p>
</div> </div>
) : (
<p className="text-center text-sm">Elephant</p>
)} )}
</CardContent> </div>
</div>
<Card
className="flex-1 p-6"
style={{
backgroundColor: "rgba(240, 233, 214, 0.7)",
borderColor: colors.accent2,
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
}}
>
<h3
className="mb-2 text-center text-2xl font-bold md:text-left"
style={headingStyle}
>
{eventDate}
</h3>
{timeRange && (
<p
className="text-center text-lg md:text-left"
style={{ color: colors.text }}
>
{timeRange}
</p>
)}
</Card> </Card>
</motion.div> </div>
{/* Location with Giraffe */}
<div className="mb-10 flex flex-col-reverse items-center md:flex-row md:items-start md:space-x-6">
<Card
className="flex-1 p-6"
style={{
backgroundColor: "rgba(240, 233, 214, 0.7)",
borderColor: colors.secondary,
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
}}
>
<h3
className="mb-2 text-center text-2xl font-bold md:text-left"
style={headingStyle}
>
{event.location_name}
</h3>
<p
style={{ color: colors.text }}
className="text-center md:text-left"
>
{event.location_address}
</p>
{event.location_url && (
<div className="mt-4 text-center md:text-left">
<Link
href={event.location_url}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="rounded-full"
style={{
backgroundColor: colors.secondary,
color: "white",
}}
>
View Map
</Button>
</Link>
</div>
)}
</Card>
<div className="mb-4 md:mb-0">
<div
className="flex h-28 w-28 items-center justify-center rounded-full border-2 md:h-32 md:w-32"
style={{ borderColor: colors.secondary }}
>
{getAssetUrl("giraffe") ? (
<div className="relative h-24 w-24 md:h-28 md:w-28">
<Image
src={getAssetUrl("giraffe") || ""}
alt="Giraffe"
fill
className="object-contain"
/>
</div>
) : (
<p className="text-center text-sm">Giraffe</p>
)}
</div>
</div>
</div>
{/* Description */}
<Card
className="mb-10 border-2 p-6"
style={{
backgroundColor: "white",
borderColor: colors.primary,
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.05)",
}}
>
<h3
className="mb-4 text-center text-2xl font-bold"
style={headingStyle}
>
La nostra piccola safari adventure!
</h3>
<div
className="prose mx-auto max-w-none"
style={{ color: colors.text }}
>
{event.description ? (
<div className="whitespace-pre-line text-center">
{event.description}
</div>
) : (
<p className="text-center">
Join us for a wild safari celebration with games, food, and
jungle fun! Safari attire encouraged but optional. Children
welcome to dress as their favorite animals for a truly wild
experience!
</p>
)}
</div>
</Card>
{/* RSVP Section */}
{event.rsvp_enabled && (
<div
className="mb-10 rounded-xl p-6"
style={{
backgroundColor: colors.accent,
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
}}
>
<h3
className="mb-2 text-center text-2xl font-bold text-white"
style={headingStyle}
>
RSVP
</h3>
<p className="mb-4 text-center text-white">
{rsvpDeadline
? `Please respond by ${rsvpDeadline}`
: "Please respond as soon as possible"}
</p>
<div className="flex justify-center">
<Link href={`/rsvp/${event.slug}`}>
<Button
className="rounded-full px-8 py-6 text-lg"
style={{
backgroundColor: "white",
color: colors.text,
}}
>
Respond Now
</Button>
</Link>
</div>
</div>
)}
{/* Gift Registry with Lion */}
{event.gift_registry_enabled && (
<div className="mb-10 flex flex-col items-center md:flex-row md:items-center md:space-x-6">
<div>
<div
className="flex h-24 w-24 items-center justify-center rounded-full border-2 md:h-28 md:w-28"
style={{ borderColor: colors.accent3 }}
>
{getAssetUrl("lion") ? (
<div className="relative h-20 w-20 md:h-24 md:w-24">
<Image
src={getAssetUrl("lion") || ""}
alt="Lion"
fill
className="object-contain"
/>
</div>
) : (
<p className="text-center text-sm">Lion</p>
)}
</div>
</div>
<Card
className="mt-4 flex-1 p-6 md:mt-0"
style={{
backgroundColor: "rgba(240, 233, 214, 0.7)",
borderColor: colors.accent3,
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
}}
>
<h3
className="mb-2 text-center text-2xl font-bold md:text-left"
style={headingStyle}
>
Gift Registry
</h3>
<p
style={{ color: colors.text }}
className="text-center md:text-left"
>
View Emma's Wishes
</p>
<div className="mt-4 text-center md:text-left">
<Link href={`/gifts/${event.slug}`}>
<Button
className="rounded-full"
style={{
backgroundColor: colors.accent3,
color: "white",
}}
>
View Gifts
</Button>
</Link>
</div>
</Card>
</div>
)}
{/* View Map Button */}
{event.location_url && (
<div className="mb-10 flex justify-center">
<Link
href={event.location_url}
target="_blank"
rel="noopener noreferrer"
>
<Button
className="rounded-full px-8 py-6 text-lg"
style={{
backgroundColor: colors.primary,
color: "white",
}}
>
View Location Map
</Button>
</Link>
</div>
)}
{/* Footer with Contact Info */}
<div
className="mx-auto max-w-2xl rounded-lg p-6 text-center"
style={{
backgroundColor: "rgba(240, 233, 214, 0.5)",
borderColor: colors.secondary,
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
}}
>
<p className="mb-2 font-semibold">Contact:</p>
{event.contact_email && <p className="mb-1">{event.contact_email}</p>}
{event.contact_phone && <p>{event.contact_phone}</p>}
</div>
</div> </div>
</div> </div>
); );