// Cost Estimator — guided multi-step inputs → indicative range
// CRITICAL: ranges are illustrative for prototype. The brief mandates "indicative only".
const SETUP_BASE = {
mainland: { low: 25000, high: 60000, name: 'Mainland' },
freezone: { low: 12000, high: 45000, name: 'Free Zone' },
offshore: { low: 12000, high: 22000, name: 'Offshore' }
};
const ZONE_PRESETS = {
mainland: ['DED Dubai', 'Abu Dhabi DED', 'Sharjah Economic Dept', 'RAK DED'],
freezone: ['IFZA', 'DMCC', 'Meydan', 'RAKEZ', 'SHAMS', 'SPC', 'DAFZA', 'JAFZA'],
offshore: ['JAFZA Offshore', 'RAK ICC', 'Ajman Offshore']
};
const ACTIVITIES = ['Trading', 'Consulting / Professional', 'E-commerce', 'Media & Marketing', 'Tech / SaaS', 'Holding company', 'Real estate', 'Other'];
const OFFICES = [
{ id: 'flexi', name: 'Flexi-desk', sub: 'Shared address, visa-eligible', addLow: 0, addHigh: 4000 },
{ id: 'serviced', name: 'Serviced office', sub: 'Private room at our HQ', addLow: 12000, addHigh: 28000 },
{ id: 'physical', name: 'Physical office', sub: 'Standalone leased space', addLow: 35000, addHigh: 120000 }
];
const VISA_COST_PER = 3500; // indicative add-on per visa
const fmt = (n) => 'AED ' + (n / 1000).toFixed(0) + 'k';
const Estimator = ({ lang }) => {
const [step, setStep] = useState(0);
const [setup, setSetup] = useState('freezone');
const [zone, setZone] = useState(null);
const [visas, setVisas] = useState(2);
const [office, setOffice] = useState('flexi');
const [activity, setActivity] = useState('Consulting / Professional');
const [submitted, setSubmitted] = useState(false);
const [form, setForm] = useState({ name: '', email: '', phone: '' });
const STEPS = ['Setup type', 'Jurisdiction', 'Visas & office', 'Activity', 'Your estimate'];
// compute indicative range
const computed = useMemo(() => {
const base = SETUP_BASE[setup];
const ofc = OFFICES.find(o => o.id === office);
const visaAdd = (setup === 'offshore' ? 0 : visas * VISA_COST_PER);
const low = base.low + (ofc.addLow) + visaAdd * 0.85;
const high = base.high + (ofc.addHigh) + visaAdd * 1.15;
return { low: Math.round(low / 1000) * 1000, high: Math.round(high / 1000) * 1000 };
}, [setup, visas, office]);
const reset = () => { setStep(0); setSetup('freezone'); setZone(null); setVisas(2); setOffice('flexi'); setActivity('Consulting / Professional'); setSubmitted(false); };
return (
Indicative cost estimator
How much does it cost to open a business in the UAE?
Answer four questions. Get an honest indicative range in seconds — and a precise quote at consultation, where we can factor in your activity, ownership and government fees on the day.
Government fees change — this is a range, not a quote.
{/* Progress */}
{STEPS.map((s, i) => (
i < step && setStep(i)} disabled={i > step}
style={{
flex: 1, padding: '20px 18px', background: 'transparent',
border: 'none', borderInlineEnd: i < STEPS.length - 1 ? '1px solid var(--line)' : 'none',
textAlign: 'start', cursor: i <= step ? 'pointer' : 'default',
opacity: i <= step ? 1 : 0.5
}}>
STEP 0{i + 1}
{step > i && }
{s}
))}
{/* Body */}
{/* Left: inputs */}
{step === 0 && (
{Object.entries(SETUP_BASE).map(([key, v]) => (
setSetup(key)}
title={v.name}
meta={`From ${fmt(v.low)}`}
desc={key === 'mainland' ? 'Trade anywhere in the UAE, including the local market.' : key === 'freezone' ? '100% foreign ownership, tax-friendly, 15+ specialised zones.' : 'International holding & asset structure. No UAE visas.'}
/>
))}
)}
{step === 1 && (
{ZONE_PRESETS[setup].map(z => (
setZone(z)} style={{
padding: '12px 18px', borderRadius: 10,
background: zone === z ? 'var(--ink)' : 'var(--paper)',
border: '1px solid ' + (zone === z ? 'var(--ink)' : 'var(--line)'),
color: zone === z ? 'var(--paper)' : 'var(--ink)',
fontFamily: 'DM Sans', fontSize: 14, fontWeight: 500, cursor: 'pointer'
}}>{z}
))}
setZone(zone || 'Not sure — recommend one')} style={{
marginTop: 18, background: 'transparent', border: 'none',
color: 'var(--accent-2)', fontWeight: 500, cursor: 'pointer', padding: 0
}}>I'm not sure — recommend the best one →
)}
{step === 2 && (
UAE residence visas needed
setVisas(Math.max(0, visas - 1))} style={iconBtn} disabled={setup === 'offshore'}>
{setup === 'offshore' ? '—' : visas}
setVisas(Math.min(12, visas + 1))} style={iconBtn} disabled={setup === 'offshore'}>
{setup === 'offshore' &&
Offshore doesn't issue UAE visas. }
Office requirement
{OFFICES.map(o => (
setOffice(o.id)}
title={o.name} meta={`+${fmt(o.addLow)}–${fmt(o.addHigh)}`} desc={o.sub} />
))}
)}
{step === 3 && (
{ACTIVITIES.map(a => (
setActivity(a)} style={{
padding: '12px 18px', borderRadius: 10,
background: activity === a ? 'var(--ink)' : 'var(--paper)',
border: '1px solid ' + (activity === a ? 'var(--ink)' : 'var(--line)'),
color: activity === a ? 'var(--paper)' : 'var(--ink)',
fontFamily: 'DM Sans', fontSize: 14, fontWeight: 500, cursor: 'pointer'
}}>{a}
))}
)}
{step === 4 && !submitted && (
)}
{step === 4 && submitted && (
)}
{/* nav */}
{!submitted && (
{step > 0 && setStep(step - 1)} className="btn btn-ghost btn-sm">← Back }
{step < STEPS.length - 1 && (
setStep(step + 1)} className="btn btn-primary btn-sm" style={{ marginInlineStart: 'auto' }}>
{step === 3 ? 'See my estimate' : 'Continue'}
)}
)}
{/* Right: live summary */}
Your indicative estimate
{fmt(computed.low)} – {fmt(computed.high)}
All-in, first-year indicative range
o.id === office).name} />
Indicative only. UAE government fees are
set by the relevant authority and change. Your exact quote — including activity-specific
approvals, share capital and PRO costs — comes from a free consultation.
);
};
const Step = ({ title, sub, children }) => (
);
const OptionCard = ({ active, onClick, title, meta, desc, compact }) => (
{active && }
{title}
{meta}
{desc && !compact &&
{desc}
}
{desc && compact &&
{desc}
}
);
const Field = ({ label, type = 'text', value, onChange }) => (
{label}
onChange(e.target.value)} required
style={{
width: '100%', padding: '12px 14px',
background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 8,
fontFamily: 'DM Sans', fontSize: 15, color: 'var(--ink)', outline: 'none'
}}
onFocus={e => e.target.style.borderColor = 'var(--accent)'}
onBlur={e => e.target.style.borderColor = 'var(--line)'}
/>
);
const SumRow = ({ label, value }) => (
{label}
{value}
);
const iconBtn = {
width: 40, height: 40, borderRadius: 50,
background: 'var(--paper)', border: '1px solid var(--line)',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', color: 'var(--ink)'
};
Object.assign(window, { Estimator });