// ============================================================
// JET LUXE — Quote modal (multi-step) + main app
// ============================================================
const { useState: useState_, useEffect: useEffect_, useMemo: useMemo_, Fragment: Fragment_ } = React;
// ============================================================
// QUOTE MODAL
// ============================================================
function QuoteModal({ t, locale, open, onClose, initialService }) {
const Q = t.quote;
const STEPS = 5;
const [step, setStep] = useState_(1);
const [data, setData] = useState_({
service: initialService || 'charter',
trip: 0,
from: '',
to: '',
depart: '',
ret: '',
pax: '4',
notes: '',
name: '',
email: '',
phone: '',
prefer: 0
});
useEffect_(() => {
if (open) {
setStep(1);
setData(d => ({ ...d, service: initialService || d.service }));
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
}, [open, initialService]);
if (!open) return null;
const update = (k, v) => setData(d => ({ ...d, [k]: v }));
const next = () => setStep(s => Math.min(STEPS, s + 1));
const back = () => setStep(s => Math.max(1, s - 1));
const canAdvance = () => {
if (step === 1) return !!data.service;
if (step === 2) return data.from.trim() && data.to.trim();
if (step === 3) return data.depart.trim() && data.pax;
if (step === 4) return data.name.trim() && data.email.trim();
return true;
};
// build WhatsApp deep-link from current state
const waText = useMemo_(() => {
const lines = [
locale === 'ar' ? 'مرحباً Jet Luxe، أودّ الاستفسار عن:' : 'Hi Jet Luxe, I’d like to enquire about:',
`· ${Q.step1.options.find(o => o.id === data.service)?.name || data.service}`,
`· ${Q.step2.from}: ${data.from || '—'}`,
`· ${Q.step2.to}: ${data.to || '—'}`,
`· ${Q.step3.depart}: ${data.depart || '—'}`,
`· ${Q.step3.pax}: ${data.pax || '—'}`,
data.notes ? `· ${Q.step3.notes}: ${data.notes}` : null
].filter(Boolean);
return encodeURIComponent(lines.join('\n'));
}, [data, locale, Q]);
const waUrl = `https://wa.me/+17724448268?text=${waText}`;
const reference = useMemo_(() => 'JL-' + Math.random().toString(36).slice(2, 8).toUpperCase(), [open]);
// ---- Step contents ----
const Step1 = () => (
{locale === 'ar' ? 'الخطوة ١ من ٥' : 'Step 01 / 05'}
{Q.step1.h}
{Q.step1.sub}
{Q.step1.options.map(o => (
))}
);
const Step2 = () => (
{locale === 'ar' ? 'الخطوة ٢ من ٥' : 'Step 02 / 05'}
{Q.step2.h}
{Q.step2.sub}
{Q.step2.trip.map((tt, i) => (
))}
);
const Step3 = () => (
{locale === 'ar' ? 'الخطوة ٣ من ٥' : 'Step 03 / 05'}
{Q.step3.h}
{Q.step3.sub}
);
const Step4 = () => (
{locale === 'ar' ? 'الخطوة ٤ من ٥' : 'Step 04 / 05'}
{Q.step4.h}
{Q.step4.sub}
update('name', e.target.value)} />
{Q.step4.prefers.map((p, i) => (
))}
);
const Step5 = () => (
✓
{Q.step5.h}
{Q.step5.sub}
{Q.step5.refLbl} · {reference}
);
const stepBody = step === 1 ? : step === 2 ? : step === 3 ? : step === 4 ? : ;
return (
e.stopPropagation()} role="dialog" aria-modal="true">
{Array.from({ length: STEPS }).map((_, i) => {
const n = i + 1;
const cls = n < step ? 'done' : n === step ? 'active' : '';
return ;
})}
{stepBody}
{step < 5 && (
{String(step).padStart(2, '0')} / 0{STEPS}
)}
);
}
// ============================================================
// TWEAKS
// ============================================================
function JLTweaks({ tweaks, setTweak }) {
return (
setTweak('theme', v)}
options={[{ value: 'light', label: 'Light' }, { value: 'dark', label: 'Dark' }]} />
setTweak('accent', v)}
options={[
{ value: 'petrol', label: 'Petrol blue · champagne' },
{ value: 'midnight', label: 'Midnight · champagne' },
{ value: 'champagne', label: 'Champagne only' },
{ value: 'ocean', label: 'Ocean · gold' }
]} />
setTweak('hero', v)}
options={[
{ value: 'photo', label: 'Photo' },
{ value: 'split', label: 'Split' },
{ value: 'type', label: 'Type-led' }
]} />
setTweak('density', v)}
options={[
{ value: 'editorial', label: 'Editorial' },
{ value: 'spacious', label: 'Spacious' },
{ value: 'cinematic', label: 'Cinematic' }
]} />
setTweak('locale', v)}
options={[{ value: 'en', label: 'English' }, { value: 'ar', label: 'العربية' }]} />
);
}
// ============================================================
// APP
// ============================================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"theme": "light",
"accent": "petrol",
"hero": "photo",
"density": "spacious",
"locale": "en"
}/*EDITMODE-END*/;
function App() {
const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
const locale = tweaks.locale;
const setLocale = (v) => setTweak('locale', v);
const t = useMemo_(() => {
const base = window.JL_CONTENT[locale];
return { ...base, locale };
}, [locale]);
const [quoteOpen, setQuoteOpen] = useState_(false);
const [initialService, setInitialService] = useState_('charter');
const openQuote = (svc) => {
setInitialService(svc || 'charter');
setQuoteOpen(true);
};
const closeQuote = () => setQuoteOpen(false);
// apply data attributes on
useEffect_(() => {
document.documentElement.setAttribute('data-theme', tweaks.theme);
document.documentElement.setAttribute('data-accent', tweaks.accent);
document.documentElement.setAttribute('data-density', tweaks.density);
document.documentElement.setAttribute('dir', t.dir);
document.documentElement.setAttribute('lang', locale);
}, [tweaks, locale, t]);
return (
);
}
ReactDOM.createRoot(document.getElementById('app')).render();