Add invitation and dashboard pages with UI components

Introduced a dynamic invitation page to display event details based on a slug, including RSVP functionality (placeholder). Added a dashboard page for authenticated users, providing a welcome message and placeholder for event management. Also implemented reusable Card UI components for consistent styling.
This commit is contained in:
2025-03-11 06:57:34 +01:00
parent f245145087
commit 158a8b441a
3 changed files with 310 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
// src/app/(main)/dashboard/page.tsx
"use client";
import { useAuth } from "@/context/auth-context";
import Navbar from "@/components/layout/navbar";
export default function DashboardPage() {
const { user, isLoading } = useAuth();
// Show loading state
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="h-8 w-8 mx-auto border-4 border-t-blue-500 border-blue-200 rounded-full animate-spin"></div>
<p className="mt-2">Loading...</p>
</div>
</div>
);
}
return (
<>
<Navbar />
<div className="container mx-auto px-4 py-12">
<div className="max-w-7xl mx-auto">
<h1 className="text-3xl font-bold mb-8">Dashboard</h1>
<div className="bg-white dark:bg-slate-800 rounded-lg shadow p-6">
<h2 className="text-xl font-semibold mb-4">
Welcome, {user?.first_name || "User"}!
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
You are now logged in to EventSpace.
</p>
<div className="mt-8">
<h3 className="text-lg font-medium mb-4">Your Events</h3>
<div className="bg-gray-100 dark:bg-slate-700 rounded p-8 text-center">
<p className="text-gray-500 dark:text-gray-400">
No events yet
</p>
<button
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
onClick={() =>
alert("Create event functionality coming soon!")
}
>
Create Your First Event
</button>
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,184 @@
"use client";
import React, { useEffect, useState } from "react";
import { motion } from "framer-motion";
import { useParams } from "next/navigation";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Calendar, Gift, MapPin, Clock } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useEvents } from "@/context/event-context";
import { EventResponse } from "@/client/types.gen";
const InvitationPage = () => {
const { slug } = useParams();
const { getEventBySlug } = useEvents();
const [isLoading, setIsLoading] = useState(true);
const [event, setEvent] = useState<EventResponse | null>(null);
const [notFound, setNotFound] = useState(false);
const [showRSVP, setShowRSVP] = useState(false);
useEffect(() => {
const fetchEvent = async () => {
setIsLoading(true);
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) {
fetchEvent();
}
}, [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 (
<div className="h-screen flex items-center justify-center">
<div className="text-center">
<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>
);
}
if (notFound || !event) {
return (
<div className="h-screen flex flex-col items-center justify-center text-center space-y-4">
<Image
src="/api/placeholder/404/404"
alt="Not Found"
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>
<Button asChild>
<Link href="/">Go Back Home</Link>
</Button>
</div>
);
}
return (
<div
className="min-h-screen py-8 px-4 bg-gradient-to-b from-blue-50 to-green-50"
style={{
backgroundImage: "url('/api/placeholder/20/20')",
backgroundRepeat: "repeat",
backgroundSize: "20px 20px",
}}
>
<div className="max-w-4xl mx-auto">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="text-center mb-6"
>
<h1
className="text-4xl sm:text-5xl md:text-6xl font-bold text-emerald-700 mb-2"
style={{ fontFamily: "var(--font-bubblegum)" }}
>
{event.title}
</h1>
<p className="text-xl sm:text-2xl text-amber-600 font-medium">
{event.description}
</p>
</motion.div>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 0.3 }}
className="rounded-2xl overflow-hidden shadow-xl bg-white p-4"
>
<Card className="shadow-none border-none">
<CardContent>
<ul className="space-y-4">
<li className="flex items-center gap-2">
<Calendar className="text-green-600" />
<span>{formatDate(event.event_date)}</span>
</li>
<li className="flex items-center gap-2">
<Clock className="text-green-600" />
<span>
{formatTime(event.event_date)} -{" "}
{formatTime(event.event_end_time || "")}
</span>
</li>
<li className="flex items-center gap-2">
<MapPin className="text-green-600" />
<span>
{event.location_name}, {event.location_address}
</span>
</li>
{event.gift_registry_enabled && (
<li className="flex items-center gap-2">
<Gift className="text-green-600" />
<Link href="/gift-registry">
<span className="underline cursor-pointer">
Gift Registry
</span>
</Link>
</li>
)}
</ul>
<Button className="mt-6" onClick={() => setShowRSVP(true)}>
RSVP
</Button>
{showRSVP && (
<div className="mt-4 text-center">
<p>RSVP functionality coming soon!</p>
</div>
)}
</CardContent>
</Card>
</motion.div>
</div>
</div>
);
};
export default InvitationPage;

View File

@@ -0,0 +1,68 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn("flex flex-col gap-1.5 px-6", className)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6", className)}
{...props}
/>
)
}
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }