Skip to content

Commit c386472

Browse files
Remove the placeholder fleet, and split the site into separate pages (#9)
Two changes shipped together. The six invented vehicles are gone. cars.ts is now an empty array with a commented template; everything reading it handles being empty, so no page renders a hole. Adding the real fleet is filling the array back in. The single scrolling page becomes six routes matching the old site's menu -- Home, About Us, Our Cars, Tariff, Blog, Contact -- plus a 404 page. The nav marks the current page with a gold dot and collapses to a toggle on phones. Fleet and Enquiry were written for one page; a car chosen on the fleet page now travels as /contact?car=<name> so the choice survives the navigation. scripts/spa-fallback.mjs copies dist/index.html to dist/404.html after every build. GitHub Pages knows nothing about client-side routes, so without it a direct visit to /about or a refresh on /cars returns Pages' own 404 and the app never boots. About and Blog carry TODO comments: their copy states only what the booking flow actually does, rather than inventing a founding year or posts.
2 parents 0eae79f + 50358e8 commit c386472

18 files changed

Lines changed: 646 additions & 166 deletions

my-app/package-lock.json

Lines changed: 59 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

my-app/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "tsc -b && vite build",
8+
"build": "tsc -b && vite build && node scripts/spa-fallback.mjs",
99
"lint": "oxlint",
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
1313
"react": "^19.2.8",
14-
"react-dom": "^19.2.8"
14+
"react-dom": "^19.2.8",
15+
"react-router-dom": "^7.18.3"
1516
},
1617
"devDependencies": {
1718
"@tailwindcss/vite": "^4.3.3",

my-app/scripts/spa-fallback.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* GitHub Pages serves static files and knows nothing about client-side routes,
3+
* so a direct visit to /about or a refresh on /cars returns its 404 page and
4+
* the app never boots. Pages does, however, serve 404.html for any path it
5+
* cannot match -- so an exact copy of index.html there loads the app, and the
6+
* router resolves the URL as usual.
7+
*
8+
* Copied rather than symlinked: the Pages artifact upload does not follow
9+
* symlinks. Written in Node rather than `cp` so the build works on Windows.
10+
*/
11+
import { copyFileSync, existsSync } from 'node:fs';
12+
import { join } from 'node:path';
13+
14+
const dist = join(import.meta.dirname, '..', 'dist');
15+
const index = join(dist, 'index.html');
16+
const fallback = join(dist, '404.html');
17+
18+
if (!existsSync(index)) {
19+
console.error('spa-fallback: dist/index.html is missing; run the build first.');
20+
process.exit(1);
21+
}
22+
23+
copyFileSync(index, fallback);
24+
console.log('spa-fallback: wrote dist/404.html');

my-app/src/App.tsx

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,43 @@
1-
import { useState } from 'react';
1+
import { useEffect } from 'react';
2+
import { Routes, Route, useLocation } from 'react-router-dom';
23
import { Header } from './components/Header';
3-
import { Hero } from './components/Hero';
4-
import { Fleet } from './components/Fleet';
5-
import { HowItWorks } from './components/HowItWorks';
6-
import { Enquiry } from './components/Enquiry';
74
import { Footer } from './components/Footer';
5+
import { Home } from './pages/Home';
6+
import { About } from './pages/About';
7+
import { Cars } from './pages/Cars';
8+
import { Tariff } from './pages/Tariff';
9+
import { Blog } from './pages/Blog';
10+
import { Contact } from './pages/Contact';
11+
import { NotFound } from './pages/NotFound';
812

9-
function App() {
10-
// Clicking "Enquire" on a car preselects it in the form below, so the
11-
// choice is not lost on the way down the page.
12-
const [selectedCar, setSelectedCar] = useState('');
13-
14-
function enquireAbout(car: string) {
15-
setSelectedCar(car);
16-
document.getElementById('enquire')?.scrollIntoView({ behavior: 'smooth' });
17-
}
13+
/**
14+
* A browser restores scroll position on navigation, which on a client-side
15+
* router means a new page can open halfway down. Reset on every path change,
16+
* but leave hash links alone so #anchors still work.
17+
*/
18+
function ScrollToTop() {
19+
const { pathname, hash } = useLocation();
20+
useEffect(() => {
21+
if (!hash) window.scrollTo(0, 0);
22+
}, [pathname, hash]);
23+
return null;
24+
}
1825

26+
function App() {
1927
return (
2028
<>
29+
<ScrollToTop />
2130
<Header />
2231
<main>
23-
<Hero />
24-
<Fleet onEnquire={enquireAbout} />
25-
<HowItWorks />
26-
<Enquiry selectedCar={selectedCar} onCarChange={setSelectedCar} />
32+
<Routes>
33+
<Route path="/" element={<Home />} />
34+
<Route path="/about" element={<About />} />
35+
<Route path="/cars" element={<Cars />} />
36+
<Route path="/tariff" element={<Tariff />} />
37+
<Route path="/blog" element={<Blog />} />
38+
<Route path="/contact" element={<Contact />} />
39+
<Route path="*" element={<NotFound />} />
40+
</Routes>
2741
</main>
2842
<Footer />
2943
</>

my-app/src/components/Enquiry.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, type FormEvent } from 'react';
2+
import { useSearchParams } from 'react-router-dom';
23
import { submitEnquiry } from '../lib/enquiry';
34
import { cars } from '../data/cars';
45

@@ -8,18 +9,18 @@ type Status =
89
| { kind: 'sent'; enquiryNumber: string | null }
910
| { kind: 'error'; message: string; fields?: Record<string, string> };
1011

11-
interface EnquiryProps {
12-
selectedCar: string;
13-
onCarChange: (car: string) => void;
14-
}
15-
1612
const field =
1713
'w-full rounded-xl border border-line bg-white px-3.5 py-2.5 text-ink outline-none transition placeholder:text-ink-faint focus:border-navy focus:ring-2 focus:ring-navy/15';
1814
const label = 'block text-sm font-medium text-ink-dim';
1915

20-
export function Enquiry({ selectedCar, onCarChange }: EnquiryProps) {
16+
export function Enquiry() {
2117
const [status, setStatus] = useState<Status>({ kind: 'idle' });
2218

19+
// A car chosen on the fleet page arrives as ?car=..., so the choice
20+
// survives the navigation here.
21+
const [searchParams] = useSearchParams();
22+
const [selectedCar, setSelectedCar] = useState(searchParams.get('car') ?? '');
23+
2324
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
2425
event.preventDefault();
2526
setStatus({ kind: 'sending' });
@@ -136,6 +137,7 @@ export function Enquiry({ selectedCar, onCarChange }: EnquiryProps) {
136137
) : null}
137138
</div>
138139

140+
{cars.length === 0 ? null : (
139141
<div>
140142
<label className={label} htmlFor="car">
141143
Car you're interested in
@@ -144,7 +146,7 @@ export function Enquiry({ selectedCar, onCarChange }: EnquiryProps) {
144146
id="car"
145147
name="car"
146148
value={selectedCar}
147-
onChange={(e) => onCarChange(e.target.value)}
149+
onChange={(e) => setSelectedCar(e.target.value)}
148150
className={`mt-1.5 ${field}`}
149151
>
150152
<option value="">No preference</option>
@@ -155,6 +157,7 @@ export function Enquiry({ selectedCar, onCarChange }: EnquiryProps) {
155157
))}
156158
</select>
157159
</div>
160+
)}
158161

159162
<div className="grid gap-5 sm:grid-cols-2">
160163
<div>
@@ -183,7 +186,9 @@ export function Enquiry({ selectedCar, onCarChange }: EnquiryProps) {
183186

184187
<div>
185188
<label className={label} htmlFor="message">
186-
Anything else?
189+
{cars.length === 0
190+
? 'What kind of car do you need?'
191+
: 'Anything else?'}
187192
</label>
188193
<textarea id="message" name="message" rows={3} className={`mt-1.5 ${field}`} />
189194
</div>

my-app/src/components/Fleet.tsx

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { useState } from 'react';
2+
import { Link } from 'react-router-dom';
23
import { cars, inr, type BodyType } from '../data/cars';
34

45
type Filter = 'All' | BodyType;
56
const filters: Filter[] = ['All', 'Hatchback', 'Sedan', 'SUV', 'MUV'];
67

7-
interface FleetProps {
8-
onEnquire: (carLabel: string) => void;
9-
}
10-
11-
export function Fleet({ onEnquire }: FleetProps) {
8+
export function Fleet() {
129
const [filter, setFilter] = useState<Filter>('All');
10+
const empty = cars.length === 0;
1311
const shown = filter === 'All' ? cars : cars.filter((c) => c.bodyType === filter);
1412

1513
return (
@@ -20,10 +18,13 @@ export function Fleet({ onEnquire }: FleetProps) {
2018
Our fleet
2119
</h2>
2220
<p className="mt-2 max-w-lg text-ink-dim">
23-
Rates shown are per day. Longer hires bring the daily rate down.
21+
{empty
22+
? 'We are updating our vehicle listing. Call us or send an enquiry and we will tell you what is free for your dates.'
23+
: 'Rates shown are per day. Longer hires bring the daily rate down.'}
2424
</p>
2525
</div>
2626

27+
{empty ? null : (
2728
<div className="flex flex-wrap gap-2" role="group" aria-label="Filter by body type">
2829
{filters.map((f) => {
2930
const active = f === filter;
@@ -44,8 +45,34 @@ export function Fleet({ onEnquire }: FleetProps) {
4445
);
4546
})}
4647
</div>
48+
)}
4749
</div>
4850

51+
{empty ? (
52+
<div className="mt-10 rounded-[14px] border border-line bg-white p-10 text-center shadow-[0_10px_30px_rgba(16,24,40,0.08)]">
53+
<p className="text-lg font-semibold text-navy">
54+
Ask us what's available
55+
</p>
56+
<p className="mx-auto mt-2 max-w-md text-ink-dim">
57+
Our current vehicles are not listed here yet. Tell us your dates and
58+
what you need, and we will come back with the options and the rate.
59+
</p>
60+
<div className="mt-6 flex flex-wrap justify-center gap-3">
61+
<a
62+
href="/contact"
63+
className="rounded-xl bg-navy px-5 py-2.5 font-semibold text-white transition hover:bg-navy/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gold"
64+
>
65+
Send an enquiry
66+
</a>
67+
<a
68+
href="tel:+916374942976"
69+
className="rounded-xl border border-line px-5 py-2.5 font-semibold text-ink-dim transition hover:border-navy/40 hover:text-navy"
70+
>
71+
Call +91 63749 42976
72+
</a>
73+
</div>
74+
</div>
75+
) : (
4976
<div className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
5077
{shown.map((car) => {
5178
const label = `${car.brand} ${car.name}`;
@@ -124,18 +151,18 @@ export function Fleet({ onEnquire }: FleetProps) {
124151
</p>
125152
</div>
126153

127-
<button
128-
type="button"
129-
onClick={() => onEnquire(label)}
130-
className="mt-5 w-full rounded-xl bg-navy px-4 py-2.5 font-semibold text-white transition hover:bg-navy/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gold"
154+
<Link
155+
to={`/contact?car=${encodeURIComponent(label)}`}
156+
className="mt-5 block w-full rounded-xl bg-navy px-4 py-2.5 text-center font-semibold text-white transition hover:bg-navy/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gold"
131157
>
132158
Enquire about this car
133-
</button>
159+
</Link>
134160
</div>
135161
</article>
136162
);
137163
})}
138164
</div>
165+
)}
139166
</section>
140167
);
141168
}

my-app/src/components/Footer.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Link } from 'react-router-dom';
2+
13
export function Footer() {
24
return (
35
<footer className="bg-navy text-white/70">
@@ -42,19 +44,19 @@ export function Footer() {
4244
</h2>
4345
<ul className="mt-3 space-y-2 text-sm">
4446
<li>
45-
<a href="#fleet" className="transition hover:text-gold">
47+
<Link to="/cars" className="transition hover:text-gold">
4648
Our fleet
47-
</a>
49+
</Link>
4850
</li>
4951
<li>
5052
<a href="#how" className="transition hover:text-gold">
5153
How it works
5254
</a>
5355
</li>
5456
<li>
55-
<a href="#enquire" className="transition hover:text-gold">
57+
<Link to="/contact" className="transition hover:text-gold">
5658
Enquire
57-
</a>
59+
</Link>
5860
</li>
5961
</ul>
6062
</div>

0 commit comments

Comments
 (0)