Add PostgreSQL support with SQLite/Postgres database compatibility layer
- Add dbGet/dbAll helper functions for database-agnostic queries - Add toDbBool/convertBooleansForDb for boolean type conversion - Add toDbDate/getNow for timestamp type handling - Add generateId that returns UUID for Postgres, nanoid for SQLite - Update all routes to use compatibility helpers - Add normalizeEvent to return clean number types from Postgres decimal - Add formatPrice utility for consistent price display - Add legal pages admin interface with RichTextEditor - Update carousel images - Add drizzle migration files for PostgreSQL
This commit is contained in:
@@ -6,6 +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 Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
@@ -620,7 +621,7 @@ export default function BookingPage() {
|
||||
{locale === 'es' ? 'Monto a pagar' : 'Amount to pay'}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-primary-dark">
|
||||
{event?.price?.toLocaleString()} {event?.currency}
|
||||
{event?.price !== undefined ? formatPrice(event.price, event.currency) : ''}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -924,7 +925,7 @@ export default function BookingPage() {
|
||||
<span className="font-bold text-lg">
|
||||
{event.price === 0
|
||||
? t('events.details.free')
|
||||
: `${event.price.toLocaleString()} ${event.currency}`}
|
||||
: formatPrice(event.price, event.currency)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +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 Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import {
|
||||
@@ -341,7 +342,7 @@ export default function BookingPaymentPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
<CurrencyDollarIcon className="w-5 h-5 text-primary-yellow" />
|
||||
<span className="font-bold text-lg">
|
||||
{ticket.event.price?.toLocaleString()} {ticket.event.currency}
|
||||
{ticket.event.price !== undefined ? formatPrice(ticket.event.price, ticket.event.currency) : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -374,7 +375,7 @@ export default function BookingPaymentPage() {
|
||||
{locale === 'es' ? 'Monto a pagar' : 'Amount to pay'}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-primary-dark">
|
||||
{ticket.event?.price?.toLocaleString()} {ticket.event?.currency}
|
||||
{ticket.event?.price !== undefined ? formatPrice(ticket.event.price, ticket.event.currency) : ''}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,6 +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 Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { CalendarIcon, MapPinIcon } from '@heroicons/react/24/outline';
|
||||
@@ -91,7 +92,7 @@ export default function NextEventSection() {
|
||||
<span className="text-3xl font-bold text-primary-dark">
|
||||
{nextEvent.price === 0
|
||||
? t('events.details.free')
|
||||
: `${nextEvent.price.toLocaleString()} ${nextEvent.currency}`}
|
||||
: formatPrice(nextEvent.price, nextEvent.currency)}
|
||||
</span>
|
||||
{!nextEvent.externalBookingEnabled && (
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
|
||||
@@ -5,6 +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 Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import ShareButtons from '@/components/ShareButtons';
|
||||
@@ -186,7 +187,7 @@ export default function EventDetailClient({ eventId, initialEvent }: EventDetail
|
||||
<p className="text-4xl font-bold text-primary-dark">
|
||||
{event.price === 0
|
||||
? t('events.details.free')
|
||||
: `${event.price.toLocaleString()} ${event.currency}`}
|
||||
: formatPrice(event.price, event.currency)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,6 +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 Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { CalendarIcon, MapPinIcon, UserGroupIcon } from '@heroicons/react/24/outline';
|
||||
@@ -149,7 +150,7 @@ export default function EventsPage() {
|
||||
<span className="font-bold text-xl text-primary-dark">
|
||||
{event.price === 0
|
||||
? t('events.details.free')
|
||||
: `${event.price.toLocaleString()} ${event.currency}`}
|
||||
: formatPrice(event.price, event.currency)}
|
||||
</span>
|
||||
<Button size="sm">
|
||||
{t('common.moreInfo')}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Metadata } from 'next';
|
||||
import { getLegalPage, getAllLegalSlugs } from '@/lib/legal';
|
||||
import { getLegalPageAsync, getAllLegalSlugs } from '@/lib/legal';
|
||||
import LegalPageLayout from '@/components/layout/LegalPageLayout';
|
||||
|
||||
interface PageProps {
|
||||
params: { slug: string };
|
||||
params: Promise<{ slug: string }>;
|
||||
searchParams: Promise<{ locale?: string }>;
|
||||
}
|
||||
|
||||
// Generate static params for all legal pages
|
||||
@@ -13,11 +14,24 @@ export async function generateStaticParams() {
|
||||
return slugs.map((slug) => ({ slug }));
|
||||
}
|
||||
|
||||
// Enable dynamic rendering to always fetch fresh content from DB
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const revalidate = 60; // Revalidate every 60 seconds
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://spanglish.com.py';
|
||||
|
||||
// Validate and normalize locale
|
||||
function getValidLocale(locale?: string): 'en' | 'es' {
|
||||
if (locale === 'es') return 'es';
|
||||
return 'en'; // Default to English
|
||||
}
|
||||
|
||||
// Generate metadata for SEO
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const legalPage = getLegalPage(params.slug);
|
||||
export async function generateMetadata({ params, searchParams }: PageProps): Promise<Metadata> {
|
||||
const resolvedParams = await params;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const locale = getValidLocale(resolvedSearchParams.locale);
|
||||
const legalPage = await getLegalPageAsync(resolvedParams.slug, locale);
|
||||
|
||||
if (!legalPage) {
|
||||
return {
|
||||
@@ -33,13 +47,20 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
follow: true,
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${siteUrl}/legal/${params.slug}`,
|
||||
canonical: `${siteUrl}/legal/${resolvedParams.slug}`,
|
||||
languages: {
|
||||
'en': `${siteUrl}/legal/${resolvedParams.slug}`,
|
||||
'es': `${siteUrl}/legal/${resolvedParams.slug}?locale=es`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function LegalPage({ params }: PageProps) {
|
||||
const legalPage = getLegalPage(params.slug);
|
||||
export default async function LegalPage({ params, searchParams }: PageProps) {
|
||||
const resolvedParams = await params;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const locale = getValidLocale(resolvedSearchParams.locale);
|
||||
const legalPage = await getLegalPageAsync(resolvedParams.slug, locale);
|
||||
|
||||
if (!legalPage) {
|
||||
notFound();
|
||||
|
||||
Reference in New Issue
Block a user