Compare commits
18
Commits
0f7573c934
..
1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
defd9685e0 | ||
|
|
1ed62b0d3f | ||
|
|
91de6df04d | ||
|
|
a5d97d65e1 | ||
|
|
f0128f66b0 | ||
|
|
b33c68feb0 | ||
|
|
15655e3987 | ||
|
|
d8b3864411 | ||
|
|
194cbd6ca8 | ||
|
|
d5445c2282 | ||
|
|
dcfefc8371 | ||
|
|
b5f14335c4 | ||
|
|
d44ac949b5 | ||
|
|
a5e939221d | ||
|
|
833e3e5a9c | ||
|
|
ba1975dd6d | ||
|
|
3025ef3d21 | ||
|
|
8564f8af83 |
@@ -1370,7 +1370,7 @@ export default function BookingPage() {
|
|||||||
>
|
>
|
||||||
{t('booking.form.termsAgreePart1')}
|
{t('booking.form.termsAgreePart1')}
|
||||||
<Link
|
<Link
|
||||||
href={`/legal/terms-policy${locale === 'es' ? '?locale=es' : ''}`}
|
href="/legal/terms-policy"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-secondary-blue hover:text-brand-navy underline"
|
className="text-secondary-blue hover:text-brand-navy underline"
|
||||||
@@ -1379,7 +1379,7 @@ export default function BookingPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
{t('booking.form.termsAgreePart2')}
|
{t('booking.form.termsAgreePart2')}
|
||||||
<Link
|
<Link
|
||||||
href={`/legal/privacy-policy${locale === 'es' ? '?locale=es' : ''}`}
|
href="/legal/privacy-policy"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-secondary-blue hover:text-brand-navy underline"
|
className="text-secondary-blue hover:text-brand-navy underline"
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ interface EventDetailClientProps {
|
|||||||
initialEvent: Event;
|
initialEvent: Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_TICKETS_PER_PERSON = 5;
|
|
||||||
|
|
||||||
export default function EventDetailClient({ eventId, initialEvent }: EventDetailClientProps) {
|
export default function EventDetailClient({ eventId, initialEvent }: EventDetailClientProps) {
|
||||||
const { t, locale } = useLanguage();
|
const { t, locale } = useLanguage();
|
||||||
const [event, setEvent] = useState<Event>(initialEvent);
|
const [event, setEvent] = useState<Event>(initialEvent);
|
||||||
@@ -46,13 +44,7 @@ export default function EventDetailClient({ eventId, initialEvent }: EventDetail
|
|||||||
// Spots left: never negative; sold out when confirmed >= capacity
|
// Spots left: never negative; sold out when confirmed >= capacity
|
||||||
const spotsLeft = Math.max(0, event.capacity - (event.bookedCount ?? 0));
|
const spotsLeft = Math.max(0, event.capacity - (event.bookedCount ?? 0));
|
||||||
const isSoldOut = (event.bookedCount ?? 0) >= event.capacity;
|
const isSoldOut = (event.bookedCount ?? 0) >= event.capacity;
|
||||||
const maxTickets = isSoldOut ? 0 : Math.min(MAX_TICKETS_PER_PERSON, Math.max(1, spotsLeft));
|
const maxTickets = isSoldOut ? 0 : Math.max(1, spotsLeft);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (maxTickets > 0) {
|
|
||||||
setTicketQuantity((q) => Math.min(q, maxTickets));
|
|
||||||
}
|
|
||||||
}, [maxTickets]);
|
|
||||||
|
|
||||||
const decreaseQuantity = () => {
|
const decreaseQuantity = () => {
|
||||||
setTicketQuantity(prev => Math.max(1, prev - 1));
|
setTicketQuantity(prev => Math.max(1, prev - 1));
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ export default async function LegalPage({ params, searchParams }: PageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LegalPageLayout
|
<LegalPageLayout
|
||||||
slug={resolvedParams.slug}
|
|
||||||
initialLocale={locale}
|
|
||||||
title={legalPage.title}
|
title={legalPage.title}
|
||||||
content={legalPage.content}
|
content={legalPage.content}
|
||||||
lastUpdated={legalPage.lastUpdated}
|
lastUpdated={legalPage.lastUpdated}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export default function Footer() {
|
|||||||
{legalLinks.map((link) => (
|
{legalLinks.map((link) => (
|
||||||
<Link
|
<Link
|
||||||
key={link.slug}
|
key={link.slug}
|
||||||
href={`/legal/${link.slug}${locale === 'es' ? '?locale=es' : ''}`}
|
href={`/legal/${link.slug}`}
|
||||||
className="hover:opacity-70 transition-colors text-sm"
|
className="hover:opacity-70 transition-colors text-sm"
|
||||||
style={{ color: '#002F44' }}
|
style={{ color: '#002F44' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,74 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ArrowLeftIcon } from '@heroicons/react/24/outline';
|
import { ArrowLeftIcon } from '@heroicons/react/24/outline';
|
||||||
import { useLanguage } from '@/context/LanguageContext';
|
|
||||||
import { legalPagesApi } from '@/lib/api';
|
|
||||||
|
|
||||||
interface LegalPageLayoutProps {
|
interface LegalPageLayoutProps {
|
||||||
slug: string;
|
|
||||||
initialLocale: 'en' | 'es';
|
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
lastUpdated?: string;
|
lastUpdated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractLastUpdated(contentMarkdown: string, updatedAt?: string): string | undefined {
|
export default function LegalPageLayout({ title, content, lastUpdated }: LegalPageLayoutProps) {
|
||||||
const match = contentMarkdown?.match(/Last updated:\s*(.+)/i);
|
|
||||||
return match ? match[1].trim() : updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LegalPageLayout({
|
|
||||||
slug,
|
|
||||||
initialLocale,
|
|
||||||
title: initialTitle,
|
|
||||||
content: initialContent,
|
|
||||||
lastUpdated: initialLastUpdated,
|
|
||||||
}: LegalPageLayoutProps) {
|
|
||||||
const { locale, t } = useLanguage();
|
|
||||||
const [title, setTitle] = useState(initialTitle);
|
|
||||||
const [content, setContent] = useState(initialContent);
|
|
||||||
const [lastUpdated, setLastUpdated] = useState(initialLastUpdated);
|
|
||||||
const [loadedLocale, setLoadedLocale] = useState(initialLocale);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (locale === loadedLocale) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returning to the server-rendered language: restore SSR content without a fetch
|
|
||||||
if (locale === initialLocale) {
|
|
||||||
setTitle(initialTitle);
|
|
||||||
setContent(initialContent);
|
|
||||||
setLastUpdated(initialLastUpdated);
|
|
||||||
setLoadedLocale(initialLocale);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelled = false;
|
|
||||||
legalPagesApi
|
|
||||||
.getBySlug(slug, locale)
|
|
||||||
.then(({ page }) => {
|
|
||||||
if (cancelled || !page) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTitle(page.title);
|
|
||||||
setContent(page.contentMarkdown);
|
|
||||||
setLastUpdated(extractLastUpdated(page.contentMarkdown, page.updatedAt));
|
|
||||||
setLoadedLocale(locale);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// Keep the server-rendered content if the re-fetch fails
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, [locale, loadedLocale, initialLocale, slug, initialTitle, initialContent, initialLastUpdated]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-padding">
|
<div className="section-padding">
|
||||||
<div className="container-page max-w-4xl">
|
<div className="container-page max-w-4xl">
|
||||||
@@ -78,7 +21,7 @@ export default function LegalPageLayout({
|
|||||||
className="inline-flex items-center text-gray-600 hover:text-primary-dark transition-colors mb-8"
|
className="inline-flex items-center text-gray-600 hover:text-primary-dark transition-colors mb-8"
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||||
{t('legalPage.backToHome')}
|
Back to Home
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
@@ -88,7 +31,7 @@ export default function LegalPageLayout({
|
|||||||
</h1>
|
</h1>
|
||||||
{lastUpdated && lastUpdated !== '[Insert Date]' && (
|
{lastUpdated && lastUpdated !== '[Insert Date]' && (
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
{t('legalPage.lastUpdated', { date: lastUpdated })}
|
Last updated: {lastUpdated}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -239,7 +182,7 @@ export default function LegalPageLayout({
|
|||||||
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
|
||||||
className="text-gray-500 hover:text-primary-dark transition-colors text-sm"
|
className="text-gray-500 hover:text-primary-dark transition-colors text-sm"
|
||||||
>
|
>
|
||||||
{t('legalPage.backToTop')}
|
Back to top
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -322,11 +322,6 @@
|
|||||||
"refund": "Refund Policy"
|
"refund": "Refund Policy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"legalPage": {
|
|
||||||
"backToHome": "Back to Home",
|
|
||||||
"lastUpdated": "Last updated: {date}",
|
|
||||||
"backToTop": "Back to top"
|
|
||||||
},
|
|
||||||
"linktree": {
|
"linktree": {
|
||||||
"tagline": "Language Exchange Community",
|
"tagline": "Language Exchange Community",
|
||||||
"nextEvent": "Next Event",
|
"nextEvent": "Next Event",
|
||||||
|
|||||||
@@ -322,11 +322,6 @@
|
|||||||
"refund": "Política de Reembolso"
|
"refund": "Política de Reembolso"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"legalPage": {
|
|
||||||
"backToHome": "Volver al inicio",
|
|
||||||
"lastUpdated": "Última actualización: {date}",
|
|
||||||
"backToTop": "Volver arriba"
|
|
||||||
},
|
|
||||||
"linktree": {
|
"linktree": {
|
||||||
"tagline": "Comunidad de Intercambio de Idiomas",
|
"tagline": "Comunidad de Intercambio de Idiomas",
|
||||||
"nextEvent": "Próximo Evento",
|
"nextEvent": "Próximo Evento",
|
||||||
|
|||||||
Reference in New Issue
Block a user