/** * @page Cashify-Style Progressive Service Scheduler Booking Wizard (`app/services/page.tsx`) * @purpose Unified drill-down wizard (Device Type -> Brand -> Series -> Model -> Repair Category/Variant -> Slot -> 20% Advance Checkout) + Dynamic CMS Sections (Hero, How It Works, Why Choose Us, FAQs). */ 'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useAuthStore } from '@/store/authStore'; import { formatCurrency } from '@/lib/utils'; import { serviceBookingService, AvailableSlot, ServiceBookingPayload, isDemoSlot, liveCatalogId, matchServiceCatalogItem, } from '@/services/api/serviceBooking'; import { isDemoCatalogId } from '@/lib/demoRepairCatalog'; import { storefrontService } from '@/services/api/storefrontService'; import { setAccessToken } from '@/services/api/client'; import GoogleAuthModal from '@/components/auth/GoogleAuthModal'; import { toast } from 'sonner'; import { ShieldCheck, Lock, Loader2, ArrowRight, ArrowLeft, ChevronDown, Clock, HelpCircle, Check, Truck, Store, Video, Phone, MapPin, KeyRound, RotateCcw, Upload, } from 'lucide-react'; import { AnnouncementBar } from '@/layout/AnnouncementBar'; import { Header } from '@/layout/Header'; import { Navbar } from '@/layout/Navbar'; import { Footer } from '@/layout/Footer'; import { StickyHeaderSpacer } from '@/components/StickyHeaderSpacer'; import { FloatingButtons } from '@/components/FloatingButtons'; import { ClientModalsContainer } from '@/components/ClientModalsContainer'; import { PhoneInput } from '@/components/PhoneInput'; import { FormSelect } from '@/components/services/FormSelect'; import { parseMetadata } from '@/lib/layoutConfig'; import { findServiceSection, formatDeviceTypeLabel, getDeviceTypeIcon, getWhyUsIcon, isServiceSectionVisible, normalizeDeviceTypes, normalizeServiceLayout, } from '@/lib/servicePageLayout'; type WizardStep = 'selection' | 'schedule' | 'review'; function PageShell({ children }: { children: React.ReactNode }) { return (
{children}
); } export default function ServiceBookingPage() { const router = useRouter(); const { isAuthenticated, accessToken, bootstrapAuth } = useAuthStore(); const [authModalOpen, setAuthModalOpen] = useState(false); const [activeStep, setActiveStep] = useState('selection'); const [loadingBrands, setLoadingBrands] = useState(false); const [loadingSeries, setLoadingSeries] = useState(false); const [loadingModels, setLoadingModels] = useState(false); const [advancePercent, setAdvancePercent] = useState(20); const [loadingRepair, setLoadingRepair] = useState(false); const [submitting, setSubmitting] = useState(false); const [checkoutOverlay, setCheckoutOverlay] = useState< null | 'preparing' | 'verifying' >(null); const paymentHandledRef = useRef(false); // Dynamic CMS Layout Sections State const [layoutCMS, setLayoutCMS] = useState([]); const [openFaqIdx, setOpenFaqIdx] = useState(0); const [deviceTypes, setDeviceTypes] = useState([ 'mobile', 'laptop', 'tablet', 'smartwatch', ]); const [loadingDeviceTypes, setLoadingDeviceTypes] = useState(true); // Catalog Progressive Selections const [deviceType, setDeviceType] = useState('mobile'); const [brands, setBrands] = useState([]); const [selectedBrand, setSelectedBrand] = useState(null); const [seriesList, setSeriesList] = useState([]); const [selectedSeries, setSelectedSeries] = useState(null); const [models, setModels] = useState([]); const [selectedModel, setSelectedModel] = useState(null); const [repairConfig, setRepairConfig] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null); const [selectedVariant, setSelectedVariant] = useState(null); // Scheduling State const [targetDate, setTargetDate] = useState(new Date().toISOString().split('T')[0]); const [availableSlots, setAvailableSlots] = useState([]); const [loadingSlots, setLoadingSlots] = useState(false); const [selectedSlot, setSelectedSlot] = useState(null); // Fulfillment & Customer Form State const [fulfillmentMode, setFulfillmentMode] = useState<'WALK_IN' | 'DELIVERY'>('WALK_IN'); const [deliverySubOption, setDeliverySubOption] = useState<'COURIER' | 'PICKUP'>('PICKUP'); const [custName, setCustName] = useState(''); const [custEmail, setCustEmail] = useState(''); const [altPhone, setAltPhone] = useState(''); const [isWhatsappAlt, setIsWhatsappAlt] = useState(true); const [deliveryAddress, setDeliveryAddress] = useState(''); // Lock Credentials State const [lockType, setLockType] = useState<'NONE' | 'PIN' | 'PASSWORD' | 'PATTERN'>('NONE'); const [lockPasscode, setLockPasscode] = useState(''); const [patternNodes, setPatternNodes] = useState([]); // Pre-dispatch Video Upload State const [preDispatchVideoFile, setPreDispatchVideoFile] = useState(null); const [preDispatchVideoId, setPreDispatchVideoId] = useState(null); const [uploadingVideo, setUploadingVideo] = useState(false); const handlePatternNodeClick = (node: number) => { if (patternNodes.includes(node)) { toast.warning(`Node ${node} is already part of the pattern sequence.`); return; } if (patternNodes.length >= 9) { toast.warning('Maximum 9 nodes allowed in pattern.'); return; } const newSeq = [...patternNodes, node]; setPatternNodes(newSeq); setLockPasscode(newSeq.join('-')); }; const resetPattern = () => { setPatternNodes([]); setLockPasscode(''); }; const fulfillmentType = fulfillmentMode === 'WALK_IN' ? 'WALK_IN' : (deliverySubOption === 'PICKUP' ? 'DOORSTEP_PICKUP' : 'COURIER'); const pickupFee = fulfillmentType === 'DOORSTEP_PICKUP' ? 250 : 0; const basePrice = selectedVariant?.price || 0; const totalQuote = basePrice + pickupFee; const advancePrice = Math.round(totalQuote * (advancePercent / 100)); const remainingPrice = totalQuote - advancePrice; // Fetch dynamic advance percent from CMS settings useEffect(() => { storefrontService.getSettings().then((s) => { if (s.advance_percent !== undefined && s.advance_percent !== null) { setAdvancePercent(Number(s.advance_percent)); } }).catch(() => {}); // graceful fallback — keeps 20% }, []); useEffect(() => { void bootstrapAuth(); }, [bootstrapAuth]); useEffect(() => { if (accessToken) { setAccessToken(accessToken); } }, [accessToken]); // Load CMS Service Layout Configuration from Backend Admin CMS useEffect(() => { const loadLayout = async () => { try { const cms = await serviceBookingService.fetchServicePageLayout(); setLayoutCMS(normalizeServiceLayout(cms)); } catch { // Graceful fallback to rich defaults if offline or empty } }; void loadLayout(); }, []); // Device categories from catalog API (admin-managed catalog) useEffect(() => { const loadDeviceTypes = async () => { setLoadingDeviceTypes(true); try { const types = normalizeDeviceTypes( await serviceBookingService.fetchDeviceTypes() ); setDeviceTypes(types); setDeviceType((prev) => (types.includes(prev) ? prev : types[0])); } catch { setDeviceTypes(['mobile', 'laptop', 'tablet', 'smartwatch']); } finally { setLoadingDeviceTypes(false); } }; void loadDeviceTypes(); }, []); // Load Brands when Device Type changes useEffect(() => { if (!deviceType) return; loadBrands(deviceType); }, [deviceType]); const loadBrands = async (type: string) => { setLoadingBrands(true); try { const data = await serviceBookingService.fetchBrands(type); setBrands(data); setSelectedBrand(null); setSelectedSeries(null); setSelectedModel(null); setRepairConfig(null); setSelectedCategory(null); setSelectedVariant(null); } catch (err: any) { toast.error(err.message || 'Failed to load brands.'); } finally { setLoadingBrands(false); } }; const handleDeviceTypeChange = (type: string) => { setDeviceType(type); setSelectedBrand(null); setSelectedSeries(null); setSelectedModel(null); setRepairConfig(null); setSelectedCategory(null); setSelectedVariant(null); }; const handleSelectBrand = async (brand: any) => { setSelectedBrand(brand); setSelectedSeries(null); setSelectedModel(null); setRepairConfig(null); setSelectedCategory(null); setSelectedVariant(null); setLoadingSeries(true); try { const series = await serviceBookingService.fetchSeriesByBrand(brand.brand_id, deviceType); setSeriesList(series); } catch (err: any) { toast.error(err.message || 'Failed to load device series.'); } finally { setLoadingSeries(false); } }; const handleSelectSeries = async (series: any) => { setSelectedSeries(series); setSelectedModel(null); setRepairConfig(null); setSelectedCategory(null); setSelectedVariant(null); setLoadingModels(true); try { const mdlList = await serviceBookingService.fetchModelsBySeries(series.series_id); setModels(mdlList); } catch (err: any) { toast.error(err.message || 'Failed to load models.'); } finally { setLoadingModels(false); } }; const handleSelectModel = async (model: any) => { setSelectedModel(model); setSelectedCategory(null); setSelectedVariant(null); setLoadingRepair(true); try { const config = await serviceBookingService.fetchRepairConfig(model.model_id); setRepairConfig(config); if (config.categories && config.categories.length > 0) { setSelectedCategory(config.categories[0]); if (config.categories[0].variants && config.categories[0].variants.length > 0) { setSelectedVariant(config.categories[0].variants[0]); } } } catch (err: any) { toast.error(err.message || 'Failed to load repair options.'); } finally { setLoadingRepair(false); } }; // Slot querying triggered when moving to Schedule step useEffect(() => { if (activeStep === 'schedule' && selectedVariant) { loadSlots(); } }, [activeStep, targetDate, selectedVariant, selectedCategory]); const loadSlots = async () => { setLoadingSlots(true); setSelectedSlot(null); try { let serviceId = 'OTHER_SERVICE'; try { const catalog = await serviceBookingService.fetchServiceCatalog(); serviceId = matchServiceCatalogItem(catalog, selectedCategory?.category_name)?.service_id || catalog[0]?.service_id || 'OTHER_SERVICE'; } catch { /* keep OTHER_SERVICE */ } const slots = await serviceBookingService.fetchAvailableSlots(serviceId, targetDate); setAvailableSlots(slots); } catch (err: any) { toast.error(err.message || 'Failed to load scheduling slots.'); } finally { setLoadingSlots(false); } }; const loadRazorpayScript = (): Promise => { return new Promise((resolve) => { if ((window as any).Razorpay) { resolve(true); return; } const script = document.createElement('script'); script.src = 'https://checkout.razorpay.com/v1/checkout.js'; script.onload = () => resolve(true); script.onerror = () => resolve(false); document.body.appendChild(script); }); }; const handleCreateBooking = async () => { if (!isAuthenticated) { toast.info('Please sign in to confirm your booking.'); setAuthModalOpen(true); return; } if (!selectedVariant || !selectedModel) { toast.warning('Please select your device and repair option.'); return; } if (fulfillmentMode === 'WALK_IN' && !selectedSlot) { toast.warning('Please pick an appointment date and slot for Walk-in fulfillment.'); return; } if (fulfillmentMode === 'DELIVERY' && (!deliveryAddress || !deliveryAddress.trim())) { toast.warning('Please enter your delivery/pickup address.'); return; } if (lockType === 'PIN') { if (!/^\d{4,8}$/.test(lockPasscode.trim())) { toast.error('PIN must be strictly 4 to 8 numeric digits (0-9).'); return; } } else if (lockType === 'PATTERN') { if (patternNodes.length < 4) { toast.error('Pattern lock must consist of at least 4 nodes.'); return; } } paymentHandledRef.current = false; setSubmitting(true); setCheckoutOverlay('preparing'); try { let videoId = preDispatchVideoId; if (preDispatchVideoFile && !videoId) { setUploadingVideo(true); try { const uploaded = await serviceBookingService.uploadMediaFile(preDispatchVideoFile); videoId = uploaded.file_id; } catch (err: any) { toast.error(err.message || 'Pre-courier video upload failed.'); setSubmitting(false); setCheckoutOverlay(null); return; } finally { setUploadingVideo(false); } } const catalog = await serviceBookingService.fetchServiceCatalog().catch(() => []); const catalogService = matchServiceCatalogItem( catalog, selectedCategory?.category_name ); const usingDemoRepair = isDemoCatalogId(selectedVariant.variant_id) || isDemoCatalogId(selectedCategory?.service_type_id) || isDemoCatalogId(selectedCategory?.repair_service_id); const serviceId = usingDemoRepair ? catalogService?.service_id || 'OTHER_SERVICE' : catalogService?.service_id; const customServiceName = [ selectedCategory?.category_name, selectedVariant.name, ] .filter(Boolean) .join(' — '); const canLockSlot = fulfillmentMode === 'WALK_IN' && selectedSlot && !isDemoSlot(selectedSlot); const preferredSlotNote = fulfillmentMode === 'WALK_IN' && selectedSlot && !canLockSlot ? `Preferred walk-in slot: ${selectedSlot.start_time} – ${selectedSlot.end_time}` : undefined; const payload: ServiceBookingPayload = { new_device: { brand: selectedBrand?.name || 'Generic', model: selectedModel.name, device_type: deviceType, notes: preferredSlotNote, }, service_id: serviceId || 'OTHER_SERVICE', custom_service_name: customServiceName || undefined, customer_name: custName.trim() || undefined, customer_email: custEmail.trim() || undefined, device_type: deviceType, brand_id: liveCatalogId(selectedBrand?.brand_id), series_id: liveCatalogId(selectedSeries?.series_id), model_id: liveCatalogId(selectedModel.model_id), service_type_id: liveCatalogId(selectedCategory?.service_type_id), repair_service_id: liveCatalogId(selectedCategory?.repair_service_id), repair_variant_id: liveCatalogId(selectedVariant.variant_id), source: 'ONLINE', fulfillment_type: fulfillmentType, alt_phone: altPhone || undefined, is_whatsapp_alt: isWhatsappAlt, delivery_address: fulfillmentMode === 'DELIVERY' ? deliveryAddress.trim() : undefined, pre_dispatch_video_id: videoId || undefined, lock_type: lockType, lock_passcode: lockType !== 'NONE' ? lockPasscode : undefined, appointment: canLockSlot ? { scheduled_start: selectedSlot.start_time, scheduled_end: selectedSlot.end_time, } : null, }; const bookingRes = await serviceBookingService.createServiceBooking(payload); serviceBookingService.rememberLocalJob({ job_id: bookingRes.job_id, job_no: bookingRes.job_no, }); const rzpLoaded = await loadRazorpayScript(); if (!rzpLoaded) { throw new Error('Razorpay SDK failed to load. Please check your network connection.'); } const pmtAmount = bookingRes.advance_deposit || advancePrice; const pmtOrder = await serviceBookingService.initiateServicePayment(bookingRes.job_id, { payment_type: 'ADVANCE', amount: Math.max(1, Math.round(Number(pmtAmount) || 0)), provider: 'RAZORPAY', }); const rzpKey = pmtOrder.rzp_key_id || process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID || 'rzp_test_TTUzPFYF0hRV89'; const goToSuccess = ( extra: Record, overlay: 'preparing' | 'verifying' | null = 'verifying' ) => { paymentHandledRef.current = true; if (overlay) setCheckoutOverlay(overlay); const qs = new URLSearchParams({ job_no: bookingRes.job_no, job_id: bookingRes.job_id, payment_id: String(pmtOrder.payment_id || ''), amount: String(pmtOrder.amount || Math.round(selectedVariant.price * 0.2)), method: 'Razorpay', ...extra, }); router.replace(`/services/success?${qs.toString()}`); }; const options = { key: rzpKey, amount: pmtOrder.amount, currency: pmtOrder.currency || 'INR', name: 'iFixKart Commerce', description: `${advancePercent}% Advance Booking Deposit (${bookingRes.job_no})`, order_id: pmtOrder.rzp_order_id, redirect: false, handler: (response: any) => { goToSuccess({ rzp_payment_id: String(response.razorpay_payment_id || ''), rzp_order_id: String(response.razorpay_order_id || ''), rzp_signature: String(response.razorpay_signature || ''), verify: '1', }); }, modal: { escape: true, handleback: true, confirm_close: true, ondismiss: () => { if (paymentHandledRef.current) return; setCheckoutOverlay(null); setSubmitting(false); toast.info('Payment window closed. Booking saved as pending.'); router.push( `/services/success?job_no=${bookingRes.job_no}&job_id=${bookingRes.job_id}&status=PENDING` ); } }, theme: { color: '#2563eb' } }; try { const rzp = new (window as any).Razorpay(options); rzp.on('payment.failed', function (response: any) { if (paymentHandledRef.current) return; setCheckoutOverlay(null); setSubmitting(false); toast.error(response.error?.description || 'Payment transaction failed.'); }); rzp.open(); } catch (sdkError: any) { setCheckoutOverlay(null); setSubmitting(false); toast.error(sdkError.message || 'Error opening payment checkout.'); } } catch (err: any) { setCheckoutOverlay(null); setSubmitting(false); toast.error(err.message || 'Booking process failed. Please try again.'); } }; const howItWorksCMS = findServiceSection(layoutCMS, 'service_how_it_works'); const whyUsCMS = findServiceSection(layoutCMS, 'service_why_us'); const faqCMS = findServiceSection(layoutCMS, 'service_faq'); const howMeta = parseMetadata(howItWorksCMS); const whyMeta = parseMetadata(whyUsCMS); const faqMeta = parseMetadata(faqCMS); const showHow = isServiceSectionVisible(layoutCMS, 'service_how_it_works'); const showWhy = isServiceSectionVisible(layoutCMS, 'service_why_us'); const showFaq = isServiceSectionVisible(layoutCMS, 'service_faq'); const howTitle = howItWorksCMS?.title || 'How it works'; const howSubtitle = howItWorksCMS?.subtitle || 'Get your device restored in 3 simple steps'; const howSteps = howMeta.steps || [ { number: '1', title: 'Check Price', desc: 'Select your phone model and choice of repairs to see your custom instant pricing quote.', }, { number: '2', title: 'Schedule Service', desc: 'Choose a convenient date and location - your home, office, or our local service workshop.', }, { number: '3', title: 'Get Device Repaired', desc: 'Our certified technician handles the repair on-site. Test it, pay, and receive your 6-month warranty.', }, ]; const whyTitle = whyUsCMS?.title || 'Why Choose iFixKart?'; const whySubtitle = whyUsCMS?.subtitle || 'Top tier hardware repair guarantees'; const whyCards = whyMeta.cards || [ { title: 'Premium Quality Parts', desc: 'We source only certified top-grade components to guarantee perfect compatibility and longevity.', icon: 'ShieldCheck', }, { title: 'Instant On-Site Repair', desc: 'No need to leave your phone at a shop for days. Most repairs completed in under 45 minutes.', icon: 'Clock', }, { title: '6 Months Warranty', desc: 'Every screen and spare part replacement includes our worry-free protection warranty cover.', icon: 'ThumbsUp', }, ]; const faqTitle = faqCMS?.title || 'Frequently Asked Questions'; const faqSubtitle = faqCMS?.subtitle || 'Got questions? We have answers.'; const faqList = faqMeta.faqs || [ { question: 'What happens when I place my booking?', answer: 'Our support rep confirms the time slot. A technician is assigned and visits your address to handle the repair on-site.', }, { question: 'How do I pay for the service?', answer: 'You pay only after the repair is complete and verified. We accept UPI, Cards, Cash, and online payments.', }, { question: 'Is there a warranty on parts replaced?', answer: 'Yes! All screens and battery replacements include a worry-free 6-month replacement warranty cover.', }, { question: 'Will my data remain secure?', answer: 'We strictly ensure data security. Repair is conducted in front of you, ensuring zero access to storage components.', }, ]; const cmsBlocks = useMemo(() => { const blocks: Array<{ key: string; order: number }> = []; if (showHow) { blocks.push({ key: 'how', order: Number(howItWorksCMS?.display_order) || 13, }); } if (showWhy) { blocks.push({ key: 'why', order: Number(whyUsCMS?.display_order) || 14, }); } if (showFaq) { blocks.push({ key: 'faq', order: Number(faqCMS?.display_order) || 15, }); } return blocks.sort((a, b) => a.order - b.order); }, [ showHow, showWhy, showFaq, howItWorksCMS?.display_order, whyUsCMS?.display_order, faqCMS?.display_order, ]); const stepIndex = ['selection', 'schedule', 'review'].indexOf(activeStep); const wizardSteps: { id: WizardStep; label: string; num: number }[] = [ { id: 'selection', label: 'Repair', num: 1 }, { id: 'schedule', label: 'Schedule', num: 2 }, { id: 'review', label: 'Checkout', num: 3 }, ]; const tileClass = (selected: boolean) => `p-3.5 border rounded-lg text-center transition-all cursor-pointer ${ selected ? 'border-primary bg-primary/5 text-primary shadow-sm' : 'border-gray-200 bg-white text-[#1a1a1a] hover:border-primary/40 hover:bg-gray-50' }`; const fieldLabel = 'block font-semibold text-[#1a1a1a] text-[11px] mb-1.5'; const fieldHint = 'text-gray-500 text-[12px] mt-0.5 mb-3'; const selectClass = 'w-full min-h-[46px] bg-white border border-gray-200 rounded-lg px-3.5 py-2.5 text-[13px] text-[#1a1a1a] outline-none focus:border-primary focus:ring-2 focus:ring-primary/15'; const primaryBtn = 'inline-flex items-center justify-center gap-1.5 bg-primary hover:brightness-95 text-white font-bold text-[13px] py-3 px-5 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer'; const ghostBtn = 'inline-flex items-center justify-center gap-1.5 border border-gray-200 text-[#555] font-semibold text-[13px] py-2.5 px-4 rounded-lg hover:bg-gray-50 transition cursor-pointer'; return (

Book a repair

Choose your device, service, and appointment in a few steps.

{wizardSteps.map((s, idx) => { const done = idx < stepIndex; const active = s.id === activeStep; const locked = idx > stepIndex; return (
{idx > 0 && (
)}
); })}
{activeStep === 'selection' && (
{ e.preventDefault(); if (!selectedVariant) { toast.warning('Please complete device and repair details.'); return; } setActiveStep('schedule'); }} className="lg:col-span-2 space-y-5" >
{loadingDeviceTypes ? (
) : (
{deviceTypes.map((id) => { const Icon = getDeviceTypeIcon(id); const isSelected = deviceType === id; return ( ); })}
)}
({ value: b.brand_id, label: b.name, imageUrl: b.logo_url, }))} onChange={(value) => { const brand = brands.find((b) => b.brand_id === value); if (brand) void handleSelectBrand(brand); }} /> ({ value: s.series_id, label: s.name, }))} onChange={(value) => { const series = seriesList.find((s) => s.series_id === value); if (series) void handleSelectSeries(series); }} /> ({ value: m.model_id, label: m.name, hint: m.release_year ? `Released ${m.release_year}` : undefined, }))} onChange={(value) => { const model = models.find((m) => m.model_id === value); if (model) void handleSelectModel(model); }} /> ({ value: String(cat.service_type_id), label: cat.category_name, }))} onChange={(value) => { const cat = repairConfig?.categories?.find( (c: any) => String(c.service_type_id) === value ); if (!cat) return; setSelectedCategory(cat); setSelectedVariant(cat.variants?.[0] || null); }} />
{selectedCategory && (

Choose the part quality for your {selectedModel?.name}

{selectedCategory.variants.map((v: any) => { const isSel = selectedVariant?.variant_id === v.variant_id; return ( ); })}
)}
)} {activeStep === 'schedule' && (
{ e.preventDefault(); if (fulfillmentMode === 'WALK_IN' && !selectedSlot) { toast.warning('Please select an appointment slot for Walk-in fulfillment.'); return; } setActiveStep('review'); }} className="space-y-5" >

Fulfillment & Schedule Allocation

Choose how you would like to submit your device for repair.

{/* Fulfillment Mode Choice Cards */}
{/* WALK_IN: Render Schedule Picker */} {fulfillmentMode === 'WALK_IN' && (
setTargetDate(e.target.value)} className={selectClass} />

Select your preferred service window

{loadingSlots ? (
) : availableSlots.length === 0 ? (
No slots available on this date. Please pick another date.
) : (
{availableSlots.map((slot, idx) => { const startStr = new Date(slot.start_time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', }); const endStr = new Date(slot.end_time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', }); const isSelected = selectedSlot?.start_time === slot.start_time; return ( ); })}
)}
)} {/* DELIVERY: Render Courier vs Doorstep Pickup Sub-Options */} {fulfillmentMode === 'DELIVERY' && (

Select Delivery Method

)}
)} {activeStep === 'review' && selectedVariant && (

Customer Details & Review

Provide contact details, delivery address, and device lock info before payment.

{/* Customer Contact & Delivery Form */}

Customer Contact Information

{fulfillmentMode === 'DELIVERY' && (