Initial commit
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
import Link from 'next/link'
|
||||
import { Clock, MapPin, Users, ChevronDown, Route, Droplets, Award, ShieldCheck } from 'lucide-react'
|
||||
import { Navigation } from '@/components/navigation'
|
||||
import { Footer } from '@/components/footer'
|
||||
import { getPublicEvent } from '@/lib/lukenya-api'
|
||||
import {
|
||||
displayPrice,
|
||||
formatClock,
|
||||
formatCutoff,
|
||||
formatMoney,
|
||||
type PublicCategory,
|
||||
} from '@/lib/event-types'
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
function startTimesAnswer(categories: PublicCategory[]): string {
|
||||
const timed = categories.filter((c) => c.start_time)
|
||||
if (!timed.length) return 'Start times are published with the race categories closer to race day.'
|
||||
return timed.map((c) => `${c.category_name} (${c.distance_label}): ${formatClock(c.start_time)}`).join('. ') + '.'
|
||||
}
|
||||
|
||||
function ageAnswer(categories: PublicCategory[]): string {
|
||||
const parts = categories.map((c) => {
|
||||
if (c.min_age) return `${c.category_name}: ${c.min_age}+`
|
||||
if (c.eligibility) return `${c.category_name}: ${c.eligibility}`
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
if (!parts.length) return 'Age limits are listed with each race category. Runners under 18 need a parent or guardian to consent.'
|
||||
return parts.join('. ') + '. Runners under 18 need a parent or guardian to consent.'
|
||||
}
|
||||
|
||||
const routeHighlights = [
|
||||
{ icon: Route, title: 'Course Type', description: 'Mixed terrain: tarmac and scenic trails' },
|
||||
{ icon: Droplets, title: 'Water Stations', description: 'Every 2.5km with hydration and first aid' },
|
||||
{ icon: Award, title: 'Timing', description: 'Sporthive certified chip timing' },
|
||||
]
|
||||
|
||||
const staticFaqs = [
|
||||
{
|
||||
question: 'Is parking available?',
|
||||
answer: 'Yes, free parking is available at the venue. Follow the signage to the designated parking areas. Arrive early for the best spots.'
|
||||
},
|
||||
{
|
||||
question: 'Where do I collect my race pack?',
|
||||
answer: 'Race packs can be collected at the Expo on the day before the race (10 AM - 6 PM) or on race morning from 4:30 AM at the registration tent.'
|
||||
},
|
||||
{
|
||||
question: 'Is bag drop available?',
|
||||
answer: 'Yes, secure bag drop is available near the start line. Please label your bag with your bib number. Bags can be collected after you finish.'
|
||||
},
|
||||
{
|
||||
question: 'What is included in my entry fee?',
|
||||
answer: 'Race bib with chip timing, finisher medal, event t-shirt, access to water stations and post-race refreshments. Inclusions for each race are listed on the registration page.'
|
||||
},
|
||||
{
|
||||
question: 'Can I transfer my entry to someone else?',
|
||||
answer: 'Entry transfers are allowed up to 7 days before the race for a small admin fee. Contact info@lukenyamarathon.co.ke to arrange a transfer.'
|
||||
},
|
||||
{
|
||||
question: 'What happens if it rains?',
|
||||
answer: 'The race proceeds rain or shine. Kenya\'s climate is generally favorable, but come prepared with appropriate gear.'
|
||||
},
|
||||
]
|
||||
|
||||
export default async function RaceInfoPage() {
|
||||
const event = await getPublicEvent()
|
||||
const categories = event.categories
|
||||
const edition = event.edition
|
||||
const featured = categories.find((c) => c.is_featured) || categories[0]
|
||||
const featuredStart = featured ? formatClock(featured.start_time) : null
|
||||
const routeItems = [
|
||||
{ icon: MapPin, title: 'Start/Finish', description: edition?.venue || 'Venue to be announced' },
|
||||
...routeHighlights,
|
||||
]
|
||||
const faqs = [
|
||||
{ question: 'What time does the race start?', answer: startTimesAnswer(categories) },
|
||||
staticFaqs[0],
|
||||
staticFaqs[1],
|
||||
staticFaqs[2],
|
||||
{ question: 'Are there age requirements?', answer: ageAnswer(categories) },
|
||||
...staticFaqs.slice(3),
|
||||
]
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Navigation />
|
||||
|
||||
{/* Page Hero */}
|
||||
<section className="bg-[#006838] pt-20">
|
||||
<div className="max-w-5xl mx-auto px-4 text-center py-16 md:py-20">
|
||||
<span className="text-[#006838] bg-white/10 backdrop-blur font-semibold text-xs uppercase tracking-[0.12em] px-4 py-2 rounded-full inline-block mb-4">
|
||||
Everything You Need to Know
|
||||
</span>
|
||||
<h1 className="text-3xl md:text-4xl lg:text-5xl font-extrabold text-white mb-4">
|
||||
Race Information
|
||||
</h1>
|
||||
<p className="text-white/80 text-lg max-w-2xl mx-auto">
|
||||
Categories, route details, timing, and everything you need for race day{edition?.venue ? ` at ${edition.venue}` : ''}.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick Info Bar */}
|
||||
<section className="bg-[#005030] py-6">
|
||||
<div className="max-w-5xl mx-auto px-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div className="text-center">
|
||||
<Clock className="w-6 h-6 text-[#ffcd05] mx-auto mb-2" />
|
||||
<p className="text-white font-bold">{featuredStart ? `${featuredStart} Start` : 'Start time TBA'}</p>
|
||||
<p className="text-white/60 text-sm">{featured?.category_name || 'First race'}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<MapPin className="w-6 h-6 text-[#ffcd05] mx-auto mb-2" />
|
||||
<p className="text-white font-bold">{edition?.venue || 'Venue TBA'}</p>
|
||||
<p className="text-white/60 text-sm">{edition?.county || 'Kenya'}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Users className="w-6 h-6 text-[#ffcd05] mx-auto mb-2" />
|
||||
<p className="text-white font-bold">{categories.length || 'All'} Categories</p>
|
||||
<p className="text-white/60 text-sm">All abilities</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<ShieldCheck className="w-6 h-6 text-[#ffcd05] mx-auto mb-2" />
|
||||
<p className="text-white font-bold">Sporthive Timing</p>
|
||||
<p className="text-white/60 text-sm">Certified results</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Race Categories */}
|
||||
<section className="bg-white py-16 md:py-24">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-[#006838] font-semibold text-xs uppercase tracking-[0.12em] mb-2 block">
|
||||
Race Categories
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#006838]">
|
||||
Choose Your Challenge
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[600px]">
|
||||
<thead>
|
||||
<tr className="border-b-2 border-[#e5e7eb]">
|
||||
<th className="text-left py-4 px-4 font-semibold text-[#111518]">Category</th>
|
||||
<th className="text-left py-4 px-4 font-semibold text-[#111518]">Distance</th>
|
||||
<th className="text-left py-4 px-4 font-semibold text-[#111518]">Cut-off Time</th>
|
||||
<th className="text-left py-4 px-4 font-semibold text-[#111518]">Elevation</th>
|
||||
<th className="text-left py-4 px-4 font-semibold text-[#111518]">Entry Fee (Kenyan resident)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{categories.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-6 px-4 text-[#6b7280]">
|
||||
Race categories for the next edition will be published here soon.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{categories.map((category) => (
|
||||
<tr key={category.name} className="border-b border-[#e5e7eb] hover:bg-[#F5F7FC]/50">
|
||||
<td className="py-4 px-4 font-semibold text-[#111518]">
|
||||
{category.category_name}
|
||||
{category.sold_out && <span className="ml-2 text-xs font-bold text-[#c0392b]">Sold out</span>}
|
||||
</td>
|
||||
<td className="py-4 px-4 text-[#006838] font-bold">
|
||||
{category.distance_km ? `${category.distance_km}km` : category.distance_label}
|
||||
</td>
|
||||
<td className="py-4 px-4 text-[#6b7280]">{formatCutoff(category.cutoff_hours) || '—'}</td>
|
||||
<td className="py-4 px-4 text-[#6b7280]">{category.elevation_gain_m ? `+${category.elevation_gain_m}m` : '—'}</td>
|
||||
<td className="py-4 px-4 text-[#3bb34a] font-bold">
|
||||
{formatMoney(displayPrice(category, 'resident'), event.currency) || 'Announced soon'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8">
|
||||
<Link
|
||||
href="/registration"
|
||||
className="inline-block bg-[#3bb34a] text-white font-semibold text-sm px-8 py-3 rounded-lg hover:bg-[#2a9a3a] transition-colors duration-200"
|
||||
>
|
||||
REGISTER NOW
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Route Information */}
|
||||
<section className="bg-[#F5F7FC] py-16 md:py-24">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-[#006838] font-semibold text-xs uppercase tracking-[0.12em] mb-2 block">
|
||||
The Course
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#006838]">
|
||||
Route Information
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Route Map Placeholder */}
|
||||
<div className="bg-white rounded-xl shadow-[0_2px_16px_rgba(0,0,0,0.08)] p-8 mb-12">
|
||||
<div className="aspect-video bg-[#e5e7eb] rounded-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Route className="w-12 h-12 text-[#6b7280] mx-auto mb-2" />
|
||||
<p className="text-[#6b7280]">Course Map Coming Soon</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Route Highlights */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{routeItems.map((item, index) => (
|
||||
<div key={index} className="bg-white rounded-xl p-6 shadow-[0_2px_16px_rgba(0,0,0,0.08)]">
|
||||
<item.icon className="w-8 h-8 text-[#006838] mb-4" />
|
||||
<h3 className="font-bold text-[#111518] mb-2">{item.title}</h3>
|
||||
<p className="text-[#6b7280] text-sm">{item.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="bg-white py-16 md:py-24">
|
||||
<div className="max-w-3xl mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-[#006838] font-semibold text-xs uppercase tracking-[0.12em] mb-2 block">
|
||||
Common Questions
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-[#006838]">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, index) => (
|
||||
<details
|
||||
key={index}
|
||||
className="group bg-[#F5F7FC] rounded-xl overflow-hidden"
|
||||
>
|
||||
<summary className="flex items-center justify-between cursor-pointer p-6 font-semibold text-[#111518]">
|
||||
{faq.question}
|
||||
<ChevronDown className="w-5 h-5 text-[#6b7280] group-open:rotate-180 transition-transform" />
|
||||
</summary>
|
||||
<div className="px-6 pb-6 text-[#6b7280]">
|
||||
{faq.answer}
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="bg-[#006838] py-16">
|
||||
<div className="max-w-3xl mx-auto px-4 text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-extrabold text-white mb-4">
|
||||
Ready to Run?
|
||||
</h2>
|
||||
<p className="text-white/80 text-lg mb-8">
|
||||
Secure your place in Kenya's only environmental marathon.
|
||||
</p>
|
||||
<Link
|
||||
href="/registration"
|
||||
className="inline-block bg-[#ffcd05] text-[#111518] font-bold text-sm px-10 py-4 rounded-lg hover:bg-[#c4930f] transition-colors duration-200"
|
||||
>
|
||||
REGISTER NOW
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user