From b7598350ba617350632151a844b53bf65eb7fbc1 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Wed, 12 Mar 2025 12:44:52 +0100 Subject: [PATCH] Enhance event details UI and add date/time format utilities Refactor event details page with a modernized design using cards, badges, and icons, improving user experience and accessibility. Introduce robust utilities in `/lib/utils.ts` for formatting dates and --- .../(main)/dashboard/events/[slug]/page.tsx | 285 ++++++++++++++++-- frontend/src/lib/utils.ts | 67 +++- 2 files changed, 331 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/(main)/dashboard/events/[slug]/page.tsx b/frontend/src/app/(main)/dashboard/events/[slug]/page.tsx index 350040a..8f467c3 100644 --- a/frontend/src/app/(main)/dashboard/events/[slug]/page.tsx +++ b/frontend/src/app/(main)/dashboard/events/[slug]/page.tsx @@ -2,8 +2,27 @@ import React, { useEffect } from "react"; import { useParams } from "next/navigation"; -import Navbar from "@/components/layout/navbar"; +import Link from "next/link"; import { useEvents } from "@/context/event-context"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + CalendarIcon, + ClockIcon, + LinkIcon, + Loader2Icon, + MailIcon, + MapPinIcon, + PhoneIcon, +} from "lucide-react"; export default function EventDetailPage() { const { slug } = useParams<{ slug: string }>(); @@ -15,37 +34,267 @@ export default function EventDetailPage() { if (isLoadingEvent) { return ( - <> - -
Loading event...
- +
+ + Loading event details... +
); } if (eventError) { return ( - <> - -
Error: {eventError.message}
- + + + Error + + +

There was a problem loading the event: {eventError.message}

+ +
+
); } if (!event) { return ( - <> - -
Event not found!
- + + + Event Not Found + + +

+ The event you're looking for doesn't exist or may have been removed. +

+ +
+
); } return ( - <> -
-

{event.title}

-

{event.description}

+
+
+

{event.title}

+
+ {event.is_public ? ( + Public + ) : ( + Private + )} + {event.is_active && ( + + Active + + )} + {event.rsvp_enabled && ( + + RSVP Enabled + + )} + {event.gift_registry_enabled && ( + + Gift Registry + + )} +
+
+ +
+ + + Event Details + + + {event.description && ( +
+

About

+

+ {event.description} +

+
+ )} + +
+

Date & Time

+
+
+ + {new Date(event.event_date).toLocaleDateString()} +
+ + {(event.event_start_time || event.event_end_time) && ( +
+ + + {event.event_start_time && ( + <> + {new Date(event.event_start_time).toLocaleTimeString( + [], + { + hour: "2-digit", + minute: "2-digit", + }, + )} + + )} + {event.event_start_time && event.event_end_time && " - "} + {event.event_end_time && ( + <> + {new Date(event.event_end_time).toLocaleTimeString( + [], + { + hour: "2-digit", + minute: "2-digit", + }, + )} + + )}{" "} + + ({event.timezone}) + + +
+ )} + + {event.rsvp_deadline && ( +
+ + RSVP by{" "} + {new Date(event.rsvp_deadline).toLocaleDateString()} + +
+ )} +
+
+ + {(event.location_name || event.location_address) && ( +
+

Location

+
+ {event.location_name && ( +

{event.location_name}

+ )} + {event.location_address && ( +
+ + {event.location_address} +
+ )} + {event.location_url && ( + + )} +
+
+ )} +
+
+ +
+ + + Contact Information + + + {event.contact_email || event.contact_phone ? ( +
+ {event.contact_email && ( + + )} + {event.contact_phone && ( + + )} +
+ ) : ( +

+ No contact information provided +

+ )} +
+
+ + {event.rsvp_enabled && ( + + + RSVP + + Let the host know if you'll be attending + + + + + + + )} + + {event.gift_registry_enabled && ( + + + Gift Registry + + View gift suggestions for this event + + + + + + + )} +
- +
); } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index bd0c391..2a65abe 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,6 +1,67 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); +} +// in /lib/utils.ts + +/** + * Formats a date string or Date object into a human-readable date/time format + * + * @param dateString - ISO date string or Date object to format + * @param options - Intl.DateTimeFormatOptions to customize the formatting + * @returns Formatted date/time string + */ +export function formatDateTime( + dateString: string | Date | undefined | null, + options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }, +): string { + if (!dateString) return ""; + + try { + const date = + typeof dateString === "string" ? new Date(dateString) : dateString; + + // Check if the date is valid + if (isNaN(date.getTime())) { + return ""; + } + + return new Intl.DateTimeFormat("en-US", options).format(date); + } catch (error) { + console.error("Error formatting date:", error); + return ""; + } +} + +/** + * Formats a date string or Date object as a date only + */ +export function formatDate( + dateString: string | Date | undefined | null, +): string { + return formatDateTime(dateString, { + year: "numeric", + month: "long", + day: "numeric", + }); +} + +/** + * Formats a date string or Date object as time only + */ +export function formatTime( + dateString: string | Date | undefined | null, +): string { + return formatDateTime(dateString, { + hour: "2-digit", + minute: "2-digit", + }); }