From 8a3cdfb2b1a11bd31461e7cdd69c922454fcb8f3 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 8 Jul 2026 13:55:35 +0000 Subject: [PATCH] 2 --- frontend/src/components/NavBarItem.tsx | 3 +- frontend/src/layouts/Authenticated.tsx | 3 +- frontend/src/pages/index.tsx | 1124 +++++++++++++++++++++--- 3 files changed, 984 insertions(+), 146 deletions(-) diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 72935e6..fcbd9b9 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, { useEffect, useRef, useState } from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..73d8391 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 93b183b..2d2e5f2 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,1006 @@ - -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; +import React, { ReactElement, useEffect, useMemo, useState } from 'react'; import Head from 'next/head'; import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +type ServiceOption = { + id: string; + name: string; + price: number; + icon: string; +}; -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); +type BookingForm = { + date: string; + time: string; + make: string; + model: string; + year: string; + registration: string; + vehicleType: string; + services: string[]; + contactMethod: string; + fullName: string; + email: string; + phone: string; + instructions: string; + serviceLocationType: string; + streetAddress: string; + city: string; + postcode: string; + paymentMethod: string; +}; - const title = 'Bumper Buddies Booking' +type BookingRecord = BookingForm & { + bookingNumber: string; + estimate: number; + createdAt: string; + status: string; +}; - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); +const phoneNumber = '027 904 0796'; +const bookingStorageKey = 'bumper-buddies-bookings'; - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); +const vehicleTypes = [ + { label: 'Small Car', multiplier: 1 }, + { label: 'Medium SUV', multiplier: 1.15 }, + { label: 'Large SUV', multiplier: 1.25 }, + { label: '4WD', multiplier: 1.3 }, + { label: 'Van', multiplier: 1.35 }, + { label: 'Ute', multiplier: 1.25 }, + { label: 'Truck', multiplier: 1.55 }, +]; - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } +const serviceOptions: ServiceOption[] = [ + { id: 'regular-service', name: 'Regular Service', price: 189, icon: 'πŸ”§' }, + { id: 'repairs', name: 'Repairs', price: 145, icon: 'πŸ› οΈ' }, + { id: 'wof-inspection', name: 'WOF Inspection', price: 95, icon: 'βœ…' }, + { id: 'wof-repairs', name: 'WOF Repairs', price: 155, icon: '🚘' }, + { id: 'interior-detail', name: 'Interior Detail', price: 169, icon: '✨' }, + { id: 'exterior-detail', name: 'Exterior Detail', price: 159, icon: '🧼' }, + { id: 'car-wash', name: 'Car Wash', price: 65, icon: 'πŸ’¦' }, + { id: 'machine-polish', name: 'Machine Polish', price: 249, icon: '🌟' }, + { id: 'headlight-polish', name: 'Headlight Polish', price: 89, icon: 'πŸ’‘' }, + { id: 'other', name: 'Other', price: 75, icon: 'βž•' }, +]; + +const marketingServices = [ + 'Regular Vehicle Servicing', + 'Oil & Filter Change', + 'Mechanical Repairs', + 'WOF Repairs', + 'WOF Inspection Preparation', + 'Brake Repairs', + 'Battery Replacement', + 'Car Diagnostics', + 'Interior Detailing', + 'Exterior Detailing', + 'Car Wash', + 'Machine Polish', + 'Paint Protection', + 'Headlight Restoration & Polishing', + 'Fleet Vehicle Servicing', + 'Pre-Purchase Inspection', + 'Emergency Repairs', +]; + +const whyChooseUs = [ + { title: 'Mobile Service', copy: 'We come to your location.', icon: 'πŸ“' }, + { title: 'Qualified Technicians', copy: 'Experienced automotive professionals.', icon: 'πŸŽ“' }, + { title: 'Affordable Pricing', copy: 'Competitive and transparent pricing.', icon: 'πŸ’³' }, + { title: 'Fast Booking', copy: 'Book online in minutes.', icon: '⚑' }, + { title: 'Quality Parts', copy: 'Only trusted products and parts.', icon: '🏁' }, + { title: 'Customer Satisfaction', copy: 'Professional service every time.', icon: '⭐' }, +]; + +const faqItems = [ + { + question: 'How do mobile repairs work?', + answer: + 'Choose a date, time, vehicle and service. A Bumper Buddies technician arrives at your home or workplace with the tools needed for the job.', + }, + { + question: 'What areas do you service?', + answer: + 'This first booking slice is set up for New Zealand customers and captures the city and postcode so the team can confirm coverage.', + }, + { + question: 'Do you offer WOF repairs?', + answer: + 'Yes. You can book WOF inspection preparation or WOF repairs from the booking form.', + }, + { + question: 'How do I pay?', + answer: + 'Select card, Apple Pay, Google Pay or Afterpay in the secure checkout step. Payment integration can be connected next.', + }, + { + question: 'Can I reschedule?', + answer: + 'Your local dashboard shows appointment summaries. Full reschedule and cancellation actions are the next workflow layer.', + }, +]; + +const initialForm: BookingForm = { + date: '', + time: '', + make: '', + model: '', + year: '', + registration: '', + vehicleType: 'Small Car', + services: ['regular-service'], + contactMethod: 'Phone', + fullName: '', + email: '', + phone: '', + instructions: '', + serviceLocationType: 'Mobile Service (Customer Address)', + streetAddress: '', + city: '', + postcode: '', + paymentMethod: 'Credit Card', +}; + +const formatCurrency = (amount: number) => + new Intl.NumberFormat('en-NZ', { + style: 'currency', + currency: 'NZD', + maximumFractionDigits: 0, + }).format(amount); + +const getVehicleMultiplier = (vehicleType: string) => + vehicleTypes.find((vehicle) => vehicle.label === vehicleType)?.multiplier || 1; + +const getServiceNames = (ids: string[]) => + ids + .map((id) => serviceOptions.find((service) => service.id === id)?.name) + .filter(Boolean) + .join(', '); + +export default function BumperBuddiesHome() { + const [activeStep, setActiveStep] = useState(1); + const [form, setForm] = useState(initialForm); + const [bookings, setBookings] = useState([]); + const [confirmedBooking, setConfirmedBooking] = useState(null); + const [validationMessage, setValidationMessage] = useState(''); + const [selectedBookingNumber, setSelectedBookingNumber] = useState(null); + + useEffect(() => { + try { + const savedBookings = window.localStorage.getItem(bookingStorageKey); + if (savedBookings) { + setBookings(JSON.parse(savedBookings)); + } + } catch (error) { + console.error('Failed to load Bumper Buddies bookings from local storage:', error); + } + }, []); + + const estimate = useMemo(() => { + const selectedServices = serviceOptions.filter((service) => form.services.includes(service.id)); + const serviceTotal = selectedServices.reduce((total, service) => total + service.price, 0); + const mobileCallout = form.serviceLocationType.includes('Mobile') ? 35 : 0; + + return Math.round(serviceTotal * getVehicleMultiplier(form.vehicleType) + mobileCallout); + }, [form.serviceLocationType, form.services, form.vehicleType]); + + const selectedBooking = + bookings.find((booking) => booking.bookingNumber === selectedBookingNumber) || bookings[0] || null; + + const updateForm = (field: keyof BookingForm, value: string) => { + setValidationMessage(''); + setForm((current) => ({ ...current, [field]: value })); + }; + + const toggleService = (serviceId: string) => { + setValidationMessage(''); + setForm((current) => { + const nextServices = current.services.includes(serviceId) + ? current.services.filter((id) => id !== serviceId) + : [...current.services, serviceId]; + + return { ...current, services: nextServices }; + }); + }; + + const validateStep = () => { + if (activeStep === 1 && (!form.date || !form.time)) { + return 'Choose an available date and time to continue.'; + } + + if ( + activeStep === 2 && + (!form.make || !form.model || !form.year || !form.registration || !form.vehicleType) + ) { + return 'Add your vehicle make, model, year, registration and type.'; + } + + if (activeStep === 3 && form.services.length === 0) { + return 'Select at least one service.'; + } + + if (activeStep === 5 && (!form.fullName || !form.email || !form.phone)) { + return 'Add your name, email and phone number.'; + } + + if ( + activeStep === 6 && + form.serviceLocationType.includes('Mobile') && + (!form.streetAddress || !form.city || !form.postcode) + ) { + return 'Add your service address, city and postcode.'; + } + + return ''; + }; + + const goToNextStep = () => { + const error = validateStep(); + if (error) { + setValidationMessage(error); + return; + } + + setValidationMessage(''); + setActiveStep((step) => Math.min(step + 1, 8)); + }; + + const confirmBooking = () => { + const bookingNumber = `BB-${new Date().getFullYear()}-${Math.floor(1000 + Math.random() * 9000)}`; + const booking: BookingRecord = { + ...form, + bookingNumber, + estimate, + createdAt: new Date().toISOString(), + status: 'Pending confirmation', }; + const nextBookings = [booking, ...bookings]; + + setBookings(nextBookings); + setConfirmedBooking(booking); + setSelectedBookingNumber(booking.bookingNumber); + setForm(initialForm); + setActiveStep(1); + + try { + window.localStorage.setItem(bookingStorageKey, JSON.stringify(nextBookings)); + } catch (error) { + console.error('Failed to save Bumper Buddies booking to local storage:', error); + } + }; + + const inputClass = + 'w-full rounded-2xl border border-zinc-200 bg-white px-4 py-3 text-sm text-zinc-950 shadow-sm outline-none transition focus:border-red-600 focus:ring-4 focus:ring-red-100'; + const labelClass = 'mb-2 block text-sm font-semibold text-zinc-800'; + + const renderBookingStep = () => { + if (activeStep === 1) { + return ( +
+ + +
+ ); + } + + if (activeStep === 2) { + return ( +
+ {[ + ['make', 'Vehicle Make', 'Toyota'], + ['model', 'Vehicle Model', 'Hilux'], + ['year', 'Year', '2021'], + ['registration', 'Registration Number', 'ABC123'], + ].map(([field, label, placeholder]) => ( + + ))} + +
+ ); + } + + if (activeStep === 3) { + return ( +
+ {serviceOptions.map((service) => { + const isSelected = form.services.includes(service.id); + return ( + + ); + })} +
+ ); + } + + if (activeStep === 4) { + return ( +
+

Estimated Total

+

{formatCurrency(estimate)}

+

GST included. Final price confirmed after inspection if required.

+
+ Vehicle: {form.vehicleType} + Services: {form.services.length} + + {form.serviceLocationType.includes('Mobile') ? 'Mobile call-out included' : 'Workshop visit'} + +
+
+ ); + } + + if (activeStep === 5) { + return ( +
+ + + + +