diff --git a/frontend/src/app/(public)/book/[eventId]/page.tsx b/frontend/src/app/(public)/book/[eventId]/page.tsx index 106bd7a..8675a3b 100644 --- a/frontend/src/app/(public)/book/[eventId]/page.tsx +++ b/frontend/src/app/(public)/book/[eventId]/page.tsx @@ -6,7 +6,7 @@ import Link from 'next/link'; import { useLanguage } from '@/context/LanguageContext'; import { useAuth } from '@/context/AuthContext'; import { eventsApi, ticketsApi, paymentOptionsApi, Event, PaymentOptionsConfig } from '@/lib/api'; -import { formatPrice } from '@/lib/utils'; +import { formatPrice, formatDateLong, formatTime } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; @@ -217,21 +217,8 @@ export default function BookingPage() { } }, [user]); - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateLong(dateStr, locale as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); const validateForm = (): boolean => { const newErrors: Partial> = {}; @@ -879,7 +866,7 @@ export default function BookingPage() {

{t('booking.success.event')}: {event?.title}

{t('booking.success.date')}: {event && formatDate(event.startDatetime)}

-

{t('booking.success.time')}: {event && formatTime(event.startDatetime)}

+

{t('booking.success.time')}: {event && fmtTime(event.startDatetime)}

{t('booking.success.location')}: {event?.location}

@@ -955,7 +942,7 @@ export default function BookingPage() {

{t('booking.success.event')}: {event.title}

{t('booking.success.date')}: {formatDate(event.startDatetime)}

-

{t('booking.success.time')}: {formatTime(event.startDatetime)}

+

{t('booking.success.time')}: {fmtTime(event.startDatetime)}

{t('booking.success.location')}: {event.location}

@@ -1045,7 +1032,7 @@ export default function BookingPage() {
- {formatDate(event.startDatetime)} • {formatTime(event.startDatetime)} + {formatDate(event.startDatetime)} • {fmtTime(event.startDatetime)}
diff --git a/frontend/src/app/(public)/booking/[ticketId]/page.tsx b/frontend/src/app/(public)/booking/[ticketId]/page.tsx index 3e5b291..2efe005 100644 --- a/frontend/src/app/(public)/booking/[ticketId]/page.tsx +++ b/frontend/src/app/(public)/booking/[ticketId]/page.tsx @@ -5,7 +5,7 @@ import { useParams, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { useLanguage } from '@/context/LanguageContext'; import { ticketsApi, paymentOptionsApi, Ticket, PaymentOptionsConfig } from '@/lib/api'; -import { formatPrice } from '@/lib/utils'; +import { formatPrice, formatDateLong, formatTime } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { @@ -152,21 +152,8 @@ export default function BookingPaymentPage() { } }; - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateLong(dateStr, locale as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); // Loading state if (step === 'loading') { @@ -237,7 +224,7 @@ export default function BookingPaymentPage() {

{locale === 'es' ? 'Evento' : 'Event'}: {ticket.event.title}

{locale === 'es' ? 'Fecha' : 'Date'}: {formatDate(ticket.event.startDatetime)}

-

{locale === 'es' ? 'Hora' : 'Time'}: {formatTime(ticket.event.startDatetime)}

+

{locale === 'es' ? 'Hora' : 'Time'}: {fmtTime(ticket.event.startDatetime)}

{locale === 'es' ? 'Ubicación' : 'Location'}: {ticket.event.location}

)} @@ -286,7 +273,7 @@ export default function BookingPaymentPage() {

{locale === 'es' ? 'Evento' : 'Event'}: {ticket.event.title}

{locale === 'es' ? 'Fecha' : 'Date'}: {formatDate(ticket.event.startDatetime)}

-

{locale === 'es' ? 'Hora' : 'Time'}: {formatTime(ticket.event.startDatetime)}

+

{locale === 'es' ? 'Hora' : 'Time'}: {fmtTime(ticket.event.startDatetime)}

{locale === 'es' ? 'Ubicación' : 'Location'}: {ticket.event.location}

)} @@ -333,7 +320,7 @@ export default function BookingPaymentPage() {
- {formatDate(ticket.event.startDatetime)} - {formatTime(ticket.event.startDatetime)} + {formatDate(ticket.event.startDatetime)} - {fmtTime(ticket.event.startDatetime)}
diff --git a/frontend/src/app/(public)/booking/success/[ticketId]/page.tsx b/frontend/src/app/(public)/booking/success/[ticketId]/page.tsx index 43fd1ef..27ad450 100644 --- a/frontend/src/app/(public)/booking/success/[ticketId]/page.tsx +++ b/frontend/src/app/(public)/booking/success/[ticketId]/page.tsx @@ -5,6 +5,7 @@ import { useParams } from 'next/navigation'; import Link from 'next/link'; import { useLanguage } from '@/context/LanguageContext'; import { ticketsApi, Ticket } from '@/lib/api'; +import { formatDateLong, formatTime } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { @@ -69,21 +70,8 @@ export default function BookingSuccessPage() { }; }, [ticketId]); - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateLong(dateStr, locale as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); if (loading) { return ( @@ -191,7 +179,7 @@ export default function BookingSuccessPage() { <>

{locale === 'es' ? 'Evento' : 'Event'}: {ticket.event.title}

{locale === 'es' ? 'Fecha' : 'Date'}: {formatDate(ticket.event.startDatetime)}

-

{locale === 'es' ? 'Hora' : 'Time'}: {formatTime(ticket.event.startDatetime)}

+

{locale === 'es' ? 'Hora' : 'Time'}: {fmtTime(ticket.event.startDatetime)}

{locale === 'es' ? 'Ubicación' : 'Location'}: {ticket.event.location}

)} diff --git a/frontend/src/app/(public)/components/NextEventSection.tsx b/frontend/src/app/(public)/components/NextEventSection.tsx index 2fac674..2353af0 100644 --- a/frontend/src/app/(public)/components/NextEventSection.tsx +++ b/frontend/src/app/(public)/components/NextEventSection.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { useLanguage } from '@/context/LanguageContext'; import { eventsApi, Event } from '@/lib/api'; -import { formatPrice } from '@/lib/utils'; +import { formatPrice, formatDateLong, formatTime } from '@/lib/utils'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import { CalendarIcon, MapPinIcon } from '@heroicons/react/24/outline'; @@ -27,21 +27,8 @@ export default function NextEventSection({ initialEvent }: NextEventSectionProps .finally(() => setLoading(false)); }, [initialEvent]); - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateLong(dateStr, locale as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); if (loading) { return ( @@ -84,7 +71,7 @@ export default function NextEventSection({ initialEvent }: NextEventSectionProps - {formatTime(nextEvent.startDatetime)} + {fmtTime(nextEvent.startDatetime)}
diff --git a/frontend/src/app/(public)/dashboard/components/PaymentsTab.tsx b/frontend/src/app/(public)/dashboard/components/PaymentsTab.tsx index 39d73fb..ea9fd21 100644 --- a/frontend/src/app/(public)/dashboard/components/PaymentsTab.tsx +++ b/frontend/src/app/(public)/dashboard/components/PaymentsTab.tsx @@ -25,6 +25,7 @@ export default function PaymentsTab({ payments, language }: PaymentsTabProps) { year: 'numeric', month: 'short', day: 'numeric', + timeZone: 'America/Asuncion', }); }; diff --git a/frontend/src/app/(public)/dashboard/components/ProfileTab.tsx b/frontend/src/app/(public)/dashboard/components/ProfileTab.tsx index c2828c9..bff2f5f 100644 --- a/frontend/src/app/(public)/dashboard/components/ProfileTab.tsx +++ b/frontend/src/app/(public)/dashboard/components/ProfileTab.tsx @@ -118,7 +118,7 @@ export default function ProfileTab({ onUpdate }: ProfileTabProps) { {profile?.memberSince ? new Date(profile.memberSince).toLocaleDateString( language === 'es' ? 'es-ES' : 'en-US', - { year: 'numeric', month: 'long', day: 'numeric' } + { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'America/Asuncion' } ) : '-'} diff --git a/frontend/src/app/(public)/dashboard/components/SecurityTab.tsx b/frontend/src/app/(public)/dashboard/components/SecurityTab.tsx index 13e0a32..fd43bbe 100644 --- a/frontend/src/app/(public)/dashboard/components/SecurityTab.tsx +++ b/frontend/src/app/(public)/dashboard/components/SecurityTab.tsx @@ -153,6 +153,7 @@ export default function SecurityTab() { day: 'numeric', hour: '2-digit', minute: '2-digit', + timeZone: 'America/Asuncion', }); }; diff --git a/frontend/src/app/(public)/dashboard/components/TicketsTab.tsx b/frontend/src/app/(public)/dashboard/components/TicketsTab.tsx index 647e505..69335f7 100644 --- a/frontend/src/app/(public)/dashboard/components/TicketsTab.tsx +++ b/frontend/src/app/(public)/dashboard/components/TicketsTab.tsx @@ -30,6 +30,7 @@ export default function TicketsTab({ tickets, language }: TicketsTabProps) { year: 'numeric', month: 'short', day: 'numeric', + timeZone: 'America/Asuncion', }); }; diff --git a/frontend/src/app/(public)/dashboard/page.tsx b/frontend/src/app/(public)/dashboard/page.tsx index 6eefb25..3a16e1f 100644 --- a/frontend/src/app/(public)/dashboard/page.tsx +++ b/frontend/src/app/(public)/dashboard/page.tsx @@ -7,6 +7,7 @@ import { useAuth } from '@/context/AuthContext'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { dashboardApi, DashboardSummary, NextEventInfo, UserTicket, UserPayment } from '@/lib/api'; +import { formatDateLong, formatTime } from '@/lib/utils'; import toast from 'react-hot-toast'; import Link from 'next/link'; import { @@ -85,21 +86,8 @@ export default function DashboardPage() { ); } - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(language === 'es' ? 'es-ES' : 'en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(language === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateLong(dateStr, language as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, language as 'en' | 'es'); return (
diff --git a/frontend/src/app/(public)/events/[id]/EventDetailClient.tsx b/frontend/src/app/(public)/events/[id]/EventDetailClient.tsx index 94731bd..0b8a1c6 100644 --- a/frontend/src/app/(public)/events/[id]/EventDetailClient.tsx +++ b/frontend/src/app/(public)/events/[id]/EventDetailClient.tsx @@ -5,7 +5,7 @@ import Link from 'next/link'; import Image from 'next/image'; import { useLanguage } from '@/context/LanguageContext'; import { eventsApi, Event } from '@/lib/api'; -import { formatPrice } from '@/lib/utils'; +import { formatPrice, formatDateLong, formatTime } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import ShareButtons from '@/components/ShareButtons'; @@ -54,21 +54,8 @@ export default function EventDetailClient({ eventId, initialEvent }: EventDetail setTicketQuantity(prev => Math.min(maxTickets, prev + 1)); }; - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateLong(dateStr, locale as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); const isCancelled = event.status === 'cancelled'; // Only calculate isPastEvent after mount to avoid hydration mismatch @@ -228,8 +215,8 @@ export default function EventDetailClient({ eventId, initialEvent }: EventDetail

{t('events.details.time')}

- {formatTime(event.startDatetime)} - {event.endDatetime && ` - ${formatTime(event.endDatetime)}`} + {fmtTime(event.startDatetime)} + {event.endDatetime && ` - ${fmtTime(event.endDatetime)}`}

diff --git a/frontend/src/app/(public)/events/[id]/page.tsx b/frontend/src/app/(public)/events/[id]/page.tsx index 05fb4df..6b6bc9b 100644 --- a/frontend/src/app/(public)/events/[id]/page.tsx +++ b/frontend/src/app/(public)/events/[id]/page.tsx @@ -95,11 +95,9 @@ function generateEventJsonLd(event: Event) { startDate: event.startDatetime, endDate: event.endDatetime || event.startDatetime, eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode', - eventStatus: isCancelled - ? 'https://schema.org/EventCancelled' - : isPastEvent - ? 'https://schema.org/EventPostponed' - : 'https://schema.org/EventScheduled', + eventStatus: isCancelled + ? 'https://schema.org/EventCancelled' + : 'https://schema.org/EventScheduled', location: { '@type': 'Place', name: event.location, diff --git a/frontend/src/app/(public)/events/page.tsx b/frontend/src/app/(public)/events/page.tsx index 8985852..9ecbc7b 100644 --- a/frontend/src/app/(public)/events/page.tsx +++ b/frontend/src/app/(public)/events/page.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { useLanguage } from '@/context/LanguageContext'; import { eventsApi, Event } from '@/lib/api'; -import { formatPrice } from '@/lib/utils'; +import { formatPrice, formatDateShort, formatTime } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { CalendarIcon, MapPinIcon, UserGroupIcon } from '@heroicons/react/24/outline'; @@ -33,20 +33,8 @@ export default function EventsPage() { const displayedEvents = filter === 'upcoming' ? upcomingEvents : pastEvents; - const formatDate = (dateStr: string) => { - return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { - weekday: 'short', - month: 'short', - day: 'numeric', - }); - }; - - const formatTime = (dateStr: string) => { - return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { - hour: '2-digit', - minute: '2-digit', - }); - }; + const formatDate = (dateStr: string) => formatDateShort(dateStr, locale as 'en' | 'es'); + const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); const getStatusBadge = (event: Event) => { if (event.status === 'cancelled') { @@ -130,7 +118,7 @@ export default function EventsPage() {
- {formatDate(event.startDatetime)} - {formatTime(event.startDatetime)} + {formatDate(event.startDatetime)} - {fmtTime(event.startDatetime)}
diff --git a/frontend/src/app/(public)/layout.tsx b/frontend/src/app/(public)/layout.tsx index d7474a9..a3a7b23 100644 --- a/frontend/src/app/(public)/layout.tsx +++ b/frontend/src/app/(public)/layout.tsx @@ -20,7 +20,7 @@ export const metadata: Metadata = { const organizationSchema = { '@context': 'https://schema.org', '@type': 'Organization', - name: 'Spanglish', + name: 'Spanglish Community', url: siteUrl, logo: `${siteUrl}/images/logo.png`, description: 'Language exchange community organizing English and Spanish meetups in Asunción, Paraguay.', @@ -30,7 +30,7 @@ const organizationSchema = { addressCountry: 'PY', }, sameAs: [ - process.env.NEXT_PUBLIC_INSTAGRAM_URL, + 'https://instagram.com/spanglishsocialpy', process.env.NEXT_PUBLIC_WHATSAPP_URL, process.env.NEXT_PUBLIC_TELEGRAM_URL, ].filter(Boolean), diff --git a/frontend/src/app/(public)/page.tsx b/frontend/src/app/(public)/page.tsx index b08b89f..e6a7283 100644 --- a/frontend/src/app/(public)/page.tsx +++ b/frontend/src/app/(public)/page.tsx @@ -66,6 +66,7 @@ export async function generateMetadata(): Promise { year: 'numeric', month: 'long', day: 'numeric', + timeZone: 'America/Asuncion', }); const description = `Next event: ${eventDate} – ${event.title}. Practice English and Spanish at relaxed social events in Asunción. Meet locals and internationals.`; diff --git a/frontend/src/app/admin/bookings/page.tsx b/frontend/src/app/admin/bookings/page.tsx index b346dc6..97a5b22 100644 --- a/frontend/src/app/admin/bookings/page.tsx +++ b/frontend/src/app/admin/bookings/page.tsx @@ -125,6 +125,7 @@ export default function AdminBookingsPage() { year: 'numeric', hour: '2-digit', minute: '2-digit', + timeZone: 'America/Asuncion', }); }; diff --git a/frontend/src/app/admin/contacts/page.tsx b/frontend/src/app/admin/contacts/page.tsx index 6043853..8953ed9 100644 --- a/frontend/src/app/admin/contacts/page.tsx +++ b/frontend/src/app/admin/contacts/page.tsx @@ -49,6 +49,7 @@ export default function AdminContactsPage() { day: 'numeric', hour: '2-digit', minute: '2-digit', + timeZone: 'America/Asuncion', }); }; diff --git a/frontend/src/app/admin/emails/page.tsx b/frontend/src/app/admin/emails/page.tsx index 6d57f5b..7535707 100644 --- a/frontend/src/app/admin/emails/page.tsx +++ b/frontend/src/app/admin/emails/page.tsx @@ -373,6 +373,7 @@ export default function AdminEmailsPage() { year: 'numeric', hour: '2-digit', minute: '2-digit', + timeZone: 'America/Asuncion', }); }; @@ -545,7 +546,7 @@ export default function AdminEmailsPage() {
{hasDraft && ( - Draft saved {composeForm.savedAt ? new Date(composeForm.savedAt).toLocaleString() : ''} + Draft saved {composeForm.savedAt ? new Date(composeForm.savedAt).toLocaleString(locale === 'es' ? 'es-ES' : 'en-US', { timeZone: 'America/Asuncion' }) : ''} )}