289 lines
10 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|