Files
holiday-property-booking/src/app/page.tsx

289 lines
10 KiB
TypeScript

import Link from 'next/link';
import { Section } from '@/components/Section';
import { bookingCatalog, formatPoundsFromCents, quoteStay } from '@/lib/booking';
import {
featuredProperties,
locationHighlights,
site,
testimonials,
} from '@/lib/site';
const ctaPoints = [
'Featured stays and clear summaries',
'Location-led content for quick orientation',
'A direct contact route for questions before booking',
];
const bookingFields = [
{ label: 'Arrival', value: 'Choose a date' },
{ label: 'Departure', value: 'Choose a date' },
{ label: 'Guests', value: '2 adults' },
{ label: 'Area', value: 'Coastal or rural' },
];
const demoQuote = quoteStay(bookingCatalog[0]!, {
arrivalDate: '2026-07-10',
departureDate: '2026-07-14',
adults: 2,
children: 1,
pets: 0,
});
export default function HomePage() {
return (
<>
<section className="hero" id="top">
<div className="hero-copy">
<p className="brand-kicker">Public website slice</p>
<h2>{site.tagline}</h2>
<p>{site.description}</p>
<div className="hero-actions">
<Link className="btn btn-primary" href="#browse">
Explore featured stays
</Link>
<Link className="btn btn-outline-dark" href="/contact">
Contact the team
</Link>
</div>
<ul className="hero-points">
{ctaPoints.map((point) => (
<li key={point}>{point}</li>
))}
</ul>
</div>
<aside className="hero-panel" aria-label="Quick search preview">
<div className="info-card">
<p className="footer-label">Search preview</p>
<strong>Plan the right stay</strong>
<p className="mb-0 text-body-secondary">
The booking flow will later use live availability and pricing. This slice keeps the public browsing entry point clear.
</p>
</div>
<form className="search-panel" aria-label="Availability search preview">
{bookingFields.map((field) => (
<label key={field.label} className="search-field">
<span>{field.label}</span>
<input aria-label={field.label} defaultValue={field.value} />
</label>
))}
<Link className="btn btn-dark" href="/bookings/new">
Check availability
</Link>
</form>
<div className="quote-panel" aria-label="Booking quote preview">
<div className={`availability-pill ${demoQuote.available ? 'is-available' : 'is-unavailable'}`}>
{demoQuote.available ? 'Available now' : 'Unavailable'}
</div>
<div className="quote-heading">
<div>
<p className="footer-label">Live quote core</p>
<h3>{demoQuote.propertyName}</h3>
</div>
<strong>{formatPoundsFromCents(demoQuote.totalCents)}</strong>
</div>
<p className="mb-0">
{demoQuote.arrivalDate} to {demoQuote.departureDate} {demoQuote.nights} nights
</p>
<p className="mb-0 text-body-secondary">
Booking hold: {demoQuote.holdExpiresAt ? '30 minutes after checkout starts' : 'not available yet'}
</p>
</div>
</aside>
</section>
<Section
eyebrow="Booking core"
title="Availability and pricing are now based on explicit rules"
description="The site can check dates, block conflicts, and preview a booking total before payment starts. Later tickets can reuse the same core on the property pages and checkout path."
>
<div className="data-grid">
<article className="data-card">
<h3>Availability checks</h3>
<p>
Published properties are filtered by search terms, guest count, pet rules, minimum stay, and any blocked or already-booked date ranges.
</p>
</article>
<article className="data-card">
<h3>Pricing rules</h3>
<p>
The quote core applies seasonal pricing, weekend overrides, guest supplements, and a 30-minute hold window for the booking start step.
</p>
</article>
</div>
</Section>
<Section
id="browse"
eyebrow="Featured stays"
title="A few properties guests can imagine themselves in"
description="The homepage gives the browsing experience enough shape to be useful before the dedicated listing and detail pages arrive."
>
<div className="property-grid">
{featuredProperties.map((property) => (
<article key={property.slug} className="property-card">
<div className="property-card-top">
<div>
<p className="footer-label">{property.area}</p>
<h3>{property.name}</h3>
</div>
<span className="property-price">{property.priceFrom}</span>
</div>
<p>{property.summary}</p>
<dl className="property-metrics">
<div>
<dt>Sleeps</dt>
<dd>{property.sleeps}</dd>
</div>
<div>
<dt>Bedrooms</dt>
<dd>{property.bedrooms}</dd>
</div>
<div>
<dt>Bathrooms</dt>
<dd>{property.bathrooms}</dd>
</div>
</dl>
<ul className="tag-list">
{property.tags.map((tag) => (
<li key={tag}>{tag}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<Section
id="story"
eyebrow="About the business"
title="Editorial content keeps the journey understandable"
description="The homepage introduces the business, then lets the page hierarchy handle deeper detail. That keeps the first visit focused and low friction."
>
<div className="content-grid">
<article className="content-card">
<h3>What the site should do</h3>
<p>
Guests should understand the style of stay, the practical details, and the next step without having to hunt through the UI.
</p>
<ul>
<li>Present the booking business clearly</li>
<li>Reduce uncertainty before the enquiry step</li>
<li>Keep public content editable as the site grows</li>
</ul>
</article>
<article className="content-card">
<h3>How this slice is framed</h3>
<p>
The public website now has a real homepage, contact page, and editable content-page route so the next tickets can add richer booking behavior.
</p>
<Link className="inline-link" href="/about">
Read the About page
</Link>
</article>
</div>
</Section>
<Section
id="places"
eyebrow="Location highlights"
title="The site can now speak about place, not just property"
description="Guests often choose by location first, so the homepage includes content that makes the area feel legible before search and pricing are wired up."
>
<div className="card-stack">
{locationHighlights.map((item) => (
<article key={item.title} className="content-card">
<h3>{item.title}</h3>
<p>{item.body}</p>
</article>
))}
</div>
</Section>
<Section
id="stories"
eyebrow="Guest feedback"
title="Testimonials and trust signals belong on the public page"
description="The marketing layer needs a few trust cues so the journey feels deliberate instead of empty scaffold."
>
<div className="testimonial-grid">
{testimonials.map((testimonial) => (
<blockquote key={testimonial.author} className="testimonial-card">
<p>{testimonial.quote}</p>
<footer>
<strong>{testimonial.author}</strong>
<span>{testimonial.location}</span>
</footer>
</blockquote>
))}
</div>
</Section>
<Section
id="content"
eyebrow="Editable pages"
title="About, FAQs, local area, and policy pages are now routable"
description="The content-page route can render the editorial and policy pages the public site needs without building each one as a special case."
>
<div className="content-grid content-grid-tight">
<article className="content-card">
<h3>Available pages</h3>
<ul className="link-list">
<li>
<Link href="/about">About</Link>
</li>
<li>
<Link href="/local-area">Local area guide</Link>
</li>
<li>
<Link href="/faqs">FAQs</Link>
</li>
<li>
<Link href="/terms-and-conditions">Terms and conditions</Link>
</li>
<li>
<Link href="/privacy-policy">Privacy policy</Link>
</li>
</ul>
</article>
<article className="content-card">
<h3>What comes next</h3>
<p>
The next tickets can now focus on the property listing and property detail pages while the public content layer and booking core stay reusable.
</p>
<Link className="inline-link" href="/contact">
Enquire through the contact page
</Link>
</article>
</div>
</Section>
<Section
id="contact"
eyebrow="Next step"
title="A clear contact route is already live"
description="If a guest is not ready to book, the site now gives them a proper contact path rather than forcing a dead end."
>
<div className="cta-band">
<div>
<p className="footer-label">Contact details</p>
<h3>{site.contact.email}</h3>
<p className="mb-0">
{site.contact.phone} {site.contact.area}
</p>
</div>
<Link className="btn btn-dark" href="/contact">
Open contact page
</Link>
</div>
</Section>
</>
);
}