// ═══════════════════════════════════════════════════════════════
// Aurelle Bride — app.jsx
// Public-facing site. Depends on components.jsx loading first.
// ═══════════════════════════════════════════════════════════════
// ── reCAPTCHA v3 helper ───────────────────────────────────────
const RECAPTCHA_SITE_KEY = '6LeWlucsAAAAAOMkcMc9P5IlDD6YqHUM7XZThNIL';
function getRecaptchaToken(action) {
return new Promise((resolve) => {
if (typeof grecaptcha === 'undefined') { resolve(''); return; }
grecaptcha.ready(() => {
grecaptcha.execute(RECAPTCHA_SITE_KEY, { action })
.then(resolve)
.catch(() => resolve(''));
});
});
}
// ── Cursor follower ───────────────────────────────────────────
(function () {
const dot = document.getElementById('cursor');
if (!dot) return;
document.addEventListener('mousemove', e => {
dot.style.transform = `translate(${e.clientX}px, ${e.clientY}px) translate(-50%,-50%)`;
});
document.querySelectorAll('a,button,[role=button]').forEach(el => {
el.addEventListener('mouseenter', () => dot.classList.add('hover'));
el.addEventListener('mouseleave', () => dot.classList.remove('hover'));
});
})();
// ──────────────────────────────────────────────────────────────
// SITE NAV
// ──────────────────────────────────────────────────────────────
// ── Phone-number input with auto-detected country dial code ──
const DIAL_CODES = [
// ── Popular / most likely Aurelle customers first ──
{ code: 'PK', dial: '+92', flag: '🇵🇰', name: 'Pakistan' },
{ code: 'AE', dial: '+971', flag: '🇦🇪', name: 'United Arab Emirates' },
{ code: 'SA', dial: '+966', flag: '🇸🇦', name: 'Saudi Arabia' },
{ code: 'GB', dial: '+44', flag: '🇬🇧', name: 'United Kingdom' },
{ code: 'US', dial: '+1', flag: '🇺🇸', name: 'United States' },
{ code: 'CA', dial: '+1', flag: '🇨🇦', name: 'Canada' },
{ code: 'AU', dial: '+61', flag: '🇦🇺', name: 'Australia' },
{ code: 'IN', dial: '+91', flag: '🇮🇳', name: 'India' },
{ code: 'QA', dial: '+974', flag: '🇶🇦', name: 'Qatar' },
{ code: 'KW', dial: '+965', flag: '🇰🇼', name: 'Kuwait' },
{ code: 'BH', dial: '+973', flag: '🇧🇭', name: 'Bahrain' },
{ code: 'OM', dial: '+968', flag: '🇴🇲', name: 'Oman' },
{ code: 'MY', dial: '+60', flag: '🇲🇾', name: 'Malaysia' },
{ code: 'SG', dial: '+65', flag: '🇸🇬', name: 'Singapore' },
{ code: 'BD', dial: '+880', flag: '🇧🇩', name: 'Bangladesh' },
// ── A ──
{ code: 'AF', dial: '+93', flag: '🇦🇫', name: 'Afghanistan' },
{ code: 'AL', dial: '+355', flag: '🇦🇱', name: 'Albania' },
{ code: 'DZ', dial: '+213', flag: '🇩🇿', name: 'Algeria' },
{ code: 'AD', dial: '+376', flag: '🇦🇩', name: 'Andorra' },
{ code: 'AO', dial: '+244', flag: '🇦🇴', name: 'Angola' },
{ code: 'AG', dial: '+1268',flag: '🇦🇬', name: 'Antigua & Barbuda' },
{ code: 'AR', dial: '+54', flag: '🇦🇷', name: 'Argentina' },
{ code: 'AM', dial: '+374', flag: '🇦🇲', name: 'Armenia' },
{ code: 'AT', dial: '+43', flag: '🇦🇹', name: 'Austria' },
{ code: 'AZ', dial: '+994', flag: '🇦🇿', name: 'Azerbaijan' },
// ── B ──
{ code: 'BS', dial: '+1242',flag: '🇧🇸', name: 'Bahamas' },
{ code: 'BB', dial: '+1246',flag: '🇧🇧', name: 'Barbados' },
{ code: 'BY', dial: '+375', flag: '🇧🇾', name: 'Belarus' },
{ code: 'BE', dial: '+32', flag: '🇧🇪', name: 'Belgium' },
{ code: 'BZ', dial: '+501', flag: '🇧🇿', name: 'Belize' },
{ code: 'BJ', dial: '+229', flag: '🇧🇯', name: 'Benin' },
{ code: 'BT', dial: '+975', flag: '🇧🇹', name: 'Bhutan' },
{ code: 'BO', dial: '+591', flag: '🇧🇴', name: 'Bolivia' },
{ code: 'BA', dial: '+387', flag: '🇧🇦', name: 'Bosnia & Herzegovina' },
{ code: 'BW', dial: '+267', flag: '🇧🇼', name: 'Botswana' },
{ code: 'BR', dial: '+55', flag: '🇧🇷', name: 'Brazil' },
{ code: 'BN', dial: '+673', flag: '🇧🇳', name: 'Brunei' },
{ code: 'BG', dial: '+359', flag: '🇧🇬', name: 'Bulgaria' },
{ code: 'BF', dial: '+226', flag: '🇧🇫', name: 'Burkina Faso' },
{ code: 'BI', dial: '+257', flag: '🇧🇮', name: 'Burundi' },
// ── C ──
{ code: 'CV', dial: '+238', flag: '🇨🇻', name: 'Cabo Verde' },
{ code: 'KH', dial: '+855', flag: '🇰🇭', name: 'Cambodia' },
{ code: 'CM', dial: '+237', flag: '🇨🇲', name: 'Cameroon' },
{ code: 'CF', dial: '+236', flag: '🇨🇫', name: 'Central African Rep.' },
{ code: 'TD', dial: '+235', flag: '🇹🇩', name: 'Chad' },
{ code: 'CL', dial: '+56', flag: '🇨🇱', name: 'Chile' },
{ code: 'CN', dial: '+86', flag: '🇨🇳', name: 'China' },
{ code: 'CO', dial: '+57', flag: '🇨🇴', name: 'Colombia' },
{ code: 'KM', dial: '+269', flag: '🇰🇲', name: 'Comoros' },
{ code: 'CD', dial: '+243', flag: '🇨🇩', name: 'Congo (DRC)' },
{ code: 'CG', dial: '+242', flag: '🇨🇬', name: 'Congo (Republic)' },
{ code: 'CR', dial: '+506', flag: '🇨🇷', name: 'Costa Rica' },
{ code: 'HR', dial: '+385', flag: '🇭🇷', name: 'Croatia' },
{ code: 'CU', dial: '+53', flag: '🇨🇺', name: 'Cuba' },
{ code: 'CY', dial: '+357', flag: '🇨🇾', name: 'Cyprus' },
{ code: 'CZ', dial: '+420', flag: '🇨🇿', name: 'Czech Republic' },
// ── D ──
{ code: 'DK', dial: '+45', flag: '🇩🇰', name: 'Denmark' },
{ code: 'DJ', dial: '+253', flag: '🇩🇯', name: 'Djibouti' },
{ code: 'DM', dial: '+1767',flag: '🇩🇲', name: 'Dominica' },
{ code: 'DO', dial: '+1809',flag: '🇩🇴', name: 'Dominican Republic' },
// ── E ──
{ code: 'EC', dial: '+593', flag: '🇪🇨', name: 'Ecuador' },
{ code: 'EG', dial: '+20', flag: '🇪🇬', name: 'Egypt' },
{ code: 'SV', dial: '+503', flag: '🇸🇻', name: 'El Salvador' },
{ code: 'GQ', dial: '+240', flag: '🇬🇶', name: 'Equatorial Guinea' },
{ code: 'ER', dial: '+291', flag: '🇪🇷', name: 'Eritrea' },
{ code: 'EE', dial: '+372', flag: '🇪🇪', name: 'Estonia' },
{ code: 'SZ', dial: '+268', flag: '🇸🇿', name: 'Eswatini' },
{ code: 'ET', dial: '+251', flag: '🇪🇹', name: 'Ethiopia' },
// ── F ──
{ code: 'FJ', dial: '+679', flag: '🇫🇯', name: 'Fiji' },
{ code: 'FI', dial: '+358', flag: '🇫🇮', name: 'Finland' },
{ code: 'FR', dial: '+33', flag: '🇫🇷', name: 'France' },
{ code: 'GA', dial: '+241', flag: '🇬🇦', name: 'Gabon' },
{ code: 'GM', dial: '+220', flag: '🇬🇲', name: 'Gambia' },
// ── G ──
{ code: 'GE', dial: '+995', flag: '🇬🇪', name: 'Georgia' },
{ code: 'DE', dial: '+49', flag: '🇩🇪', name: 'Germany' },
{ code: 'GH', dial: '+233', flag: '🇬🇭', name: 'Ghana' },
{ code: 'GR', dial: '+30', flag: '🇬🇷', name: 'Greece' },
{ code: 'GD', dial: '+1473',flag: '🇬🇩', name: 'Grenada' },
{ code: 'GT', dial: '+502', flag: '🇬🇹', name: 'Guatemala' },
{ code: 'GN', dial: '+224', flag: '🇬🇳', name: 'Guinea' },
{ code: 'GW', dial: '+245', flag: '🇬🇼', name: 'Guinea-Bissau' },
{ code: 'GY', dial: '+592', flag: '🇬🇾', name: 'Guyana' },
// ── H ──
{ code: 'HT', dial: '+509', flag: '🇭🇹', name: 'Haiti' },
{ code: 'HN', dial: '+504', flag: '🇭🇳', name: 'Honduras' },
{ code: 'HK', dial: '+852', flag: '🇭🇰', name: 'Hong Kong' },
{ code: 'HU', dial: '+36', flag: '🇭🇺', name: 'Hungary' },
// ── I ──
{ code: 'IS', dial: '+354', flag: '🇮🇸', name: 'Iceland' },
{ code: 'ID', dial: '+62', flag: '🇮🇩', name: 'Indonesia' },
{ code: 'IR', dial: '+98', flag: '🇮🇷', name: 'Iran' },
{ code: 'IQ', dial: '+964', flag: '🇮🇶', name: 'Iraq' },
{ code: 'IE', dial: '+353', flag: '🇮🇪', name: 'Ireland' },
{ code: 'IL', dial: '+972', flag: '🇮🇱', name: 'Israel' },
{ code: 'IT', dial: '+39', flag: '🇮🇹', name: 'Italy' },
// ── J ──
{ code: 'JM', dial: '+1876',flag: '🇯🇲', name: 'Jamaica' },
{ code: 'JP', dial: '+81', flag: '🇯🇵', name: 'Japan' },
{ code: 'JO', dial: '+962', flag: '🇯🇴', name: 'Jordan' },
// ── K ──
{ code: 'KZ', dial: '+7', flag: '🇰🇿', name: 'Kazakhstan' },
{ code: 'KE', dial: '+254', flag: '🇰🇪', name: 'Kenya' },
{ code: 'KI', dial: '+686', flag: '🇰🇮', name: 'Kiribati' },
{ code: 'KP', dial: '+850', flag: '🇰🇵', name: 'North Korea' },
{ code: 'KR', dial: '+82', flag: '🇰🇷', name: 'South Korea' },
{ code: 'XK', dial: '+383', flag: '🇽🇰', name: 'Kosovo' },
{ code: 'KG', dial: '+996', flag: '🇰🇬', name: 'Kyrgyzstan' },
// ── L ──
{ code: 'LA', dial: '+856', flag: '🇱🇦', name: 'Laos' },
{ code: 'LV', dial: '+371', flag: '🇱🇻', name: 'Latvia' },
{ code: 'LB', dial: '+961', flag: '🇱🇧', name: 'Lebanon' },
{ code: 'LS', dial: '+266', flag: '🇱🇸', name: 'Lesotho' },
{ code: 'LR', dial: '+231', flag: '🇱🇷', name: 'Liberia' },
{ code: 'LY', dial: '+218', flag: '🇱🇾', name: 'Libya' },
{ code: 'LI', dial: '+423', flag: '🇱🇮', name: 'Liechtenstein' },
{ code: 'LT', dial: '+370', flag: '🇱🇹', name: 'Lithuania' },
{ code: 'LU', dial: '+352', flag: '🇱🇺', name: 'Luxembourg' },
// ── M ──
{ code: 'MO', dial: '+853', flag: '🇲🇴', name: 'Macau' },
{ code: 'MG', dial: '+261', flag: '🇲🇬', name: 'Madagascar' },
{ code: 'MW', dial: '+265', flag: '🇲🇼', name: 'Malawi' },
{ code: 'MV', dial: '+960', flag: '🇲🇻', name: 'Maldives' },
{ code: 'ML', dial: '+223', flag: '🇲🇱', name: 'Mali' },
{ code: 'MT', dial: '+356', flag: '🇲🇹', name: 'Malta' },
{ code: 'MH', dial: '+692', flag: '🇲🇭', name: 'Marshall Islands' },
{ code: 'MR', dial: '+222', flag: '🇲🇷', name: 'Mauritania' },
{ code: 'MU', dial: '+230', flag: '🇲🇺', name: 'Mauritius' },
{ code: 'MX', dial: '+52', flag: '🇲🇽', name: 'Mexico' },
{ code: 'FM', dial: '+691', flag: '🇫🇲', name: 'Micronesia' },
{ code: 'MD', dial: '+373', flag: '🇲🇩', name: 'Moldova' },
{ code: 'MC', dial: '+377', flag: '🇲🇨', name: 'Monaco' },
{ code: 'MN', dial: '+976', flag: '🇲🇳', name: 'Mongolia' },
{ code: 'ME', dial: '+382', flag: '🇲🇪', name: 'Montenegro' },
{ code: 'MA', dial: '+212', flag: '🇲🇦', name: 'Morocco' },
{ code: 'MZ', dial: '+258', flag: '🇲🇿', name: 'Mozambique' },
{ code: 'MM', dial: '+95', flag: '🇲🇲', name: 'Myanmar' },
// ── N ──
{ code: 'NA', dial: '+264', flag: '🇳🇦', name: 'Namibia' },
{ code: 'NR', dial: '+674', flag: '🇳🇷', name: 'Nauru' },
{ code: 'NP', dial: '+977', flag: '🇳🇵', name: 'Nepal' },
{ code: 'NL', dial: '+31', flag: '🇳🇱', name: 'Netherlands' },
{ code: 'NZ', dial: '+64', flag: '🇳🇿', name: 'New Zealand' },
{ code: 'NI', dial: '+505', flag: '🇳🇮', name: 'Nicaragua' },
{ code: 'NE', dial: '+227', flag: '🇳🇪', name: 'Niger' },
{ code: 'NG', dial: '+234', flag: '🇳🇬', name: 'Nigeria' },
{ code: 'MK', dial: '+389', flag: '🇲🇰', name: 'North Macedonia' },
{ code: 'NO', dial: '+47', flag: '🇳🇴', name: 'Norway' },
// ── O–P ──
{ code: 'PW', dial: '+680', flag: '🇵🇼', name: 'Palau' },
{ code: 'PS', dial: '+970', flag: '🇵🇸', name: 'Palestine' },
{ code: 'PA', dial: '+507', flag: '🇵🇦', name: 'Panama' },
{ code: 'PG', dial: '+675', flag: '🇵🇬', name: 'Papua New Guinea' },
{ code: 'PY', dial: '+595', flag: '🇵🇾', name: 'Paraguay' },
{ code: 'PE', dial: '+51', flag: '🇵🇪', name: 'Peru' },
{ code: 'PH', dial: '+63', flag: '🇵🇭', name: 'Philippines' },
{ code: 'PL', dial: '+48', flag: '🇵🇱', name: 'Poland' },
{ code: 'PT', dial: '+351', flag: '🇵🇹', name: 'Portugal' },
// ── R ──
{ code: 'RO', dial: '+40', flag: '🇷🇴', name: 'Romania' },
{ code: 'RU', dial: '+7', flag: '🇷🇺', name: 'Russia' },
{ code: 'RW', dial: '+250', flag: '🇷🇼', name: 'Rwanda' },
// ── S ──
{ code: 'KN', dial: '+1869',flag: '🇰🇳', name: 'Saint Kitts & Nevis' },
{ code: 'LC', dial: '+1758',flag: '🇱🇨', name: 'Saint Lucia' },
{ code: 'VC', dial: '+1784',flag: '🇻🇨', name: 'Saint Vincent' },
{ code: 'WS', dial: '+685', flag: '🇼🇸', name: 'Samoa' },
{ code: 'SM', dial: '+378', flag: '🇸🇲', name: 'San Marino' },
{ code: 'ST', dial: '+239', flag: '🇸🇹', name: 'São Tomé & Príncipe' },
{ code: 'SN', dial: '+221', flag: '🇸🇳', name: 'Senegal' },
{ code: 'RS', dial: '+381', flag: '🇷🇸', name: 'Serbia' },
{ code: 'SC', dial: '+248', flag: '🇸🇨', name: 'Seychelles' },
{ code: 'SL', dial: '+232', flag: '🇸🇱', name: 'Sierra Leone' },
{ code: 'SK', dial: '+421', flag: '🇸🇰', name: 'Slovakia' },
{ code: 'SI', dial: '+386', flag: '🇸🇮', name: 'Slovenia' },
{ code: 'SB', dial: '+677', flag: '🇸🇧', name: 'Solomon Islands' },
{ code: 'SO', dial: '+252', flag: '🇸🇴', name: 'Somalia' },
{ code: 'ZA', dial: '+27', flag: '🇿🇦', name: 'South Africa' },
{ code: 'SS', dial: '+211', flag: '🇸🇸', name: 'South Sudan' },
{ code: 'ES', dial: '+34', flag: '🇪🇸', name: 'Spain' },
{ code: 'LK', dial: '+94', flag: '🇱🇰', name: 'Sri Lanka' },
{ code: 'SD', dial: '+249', flag: '🇸🇩', name: 'Sudan' },
{ code: 'SR', dial: '+597', flag: '🇸🇷', name: 'Suriname' },
{ code: 'SE', dial: '+46', flag: '🇸🇪', name: 'Sweden' },
{ code: 'CH', dial: '+41', flag: '🇨🇭', name: 'Switzerland' },
{ code: 'SY', dial: '+963', flag: '🇸🇾', name: 'Syria' },
// ── T ──
{ code: 'TW', dial: '+886', flag: '🇹🇼', name: 'Taiwan' },
{ code: 'TJ', dial: '+992', flag: '🇹🇯', name: 'Tajikistan' },
{ code: 'TZ', dial: '+255', flag: '🇹🇿', name: 'Tanzania' },
{ code: 'TH', dial: '+66', flag: '🇹🇭', name: 'Thailand' },
{ code: 'TL', dial: '+670', flag: '🇹🇱', name: 'Timor-Leste' },
{ code: 'TG', dial: '+228', flag: '🇹🇬', name: 'Togo' },
{ code: 'TO', dial: '+676', flag: '🇹🇴', name: 'Tonga' },
{ code: 'TT', dial: '+1868',flag: '🇹🇹', name: 'Trinidad & Tobago' },
{ code: 'TN', dial: '+216', flag: '🇹🇳', name: 'Tunisia' },
{ code: 'TR', dial: '+90', flag: '🇹🇷', name: 'Turkey' },
{ code: 'TM', dial: '+993', flag: '🇹🇲', name: 'Turkmenistan' },
{ code: 'TV', dial: '+688', flag: '🇹🇻', name: 'Tuvalu' },
// ── U ──
{ code: 'UG', dial: '+256', flag: '🇺🇬', name: 'Uganda' },
{ code: 'UA', dial: '+380', flag: '🇺🇦', name: 'Ukraine' },
{ code: 'UY', dial: '+598', flag: '🇺🇾', name: 'Uruguay' },
{ code: 'UZ', dial: '+998', flag: '🇺🇿', name: 'Uzbekistan' },
// ── V ──
{ code: 'VU', dial: '+678', flag: '🇻🇺', name: 'Vanuatu' },
{ code: 'VE', dial: '+58', flag: '🇻🇪', name: 'Venezuela' },
{ code: 'VN', dial: '+84', flag: '🇻🇳', name: 'Vietnam' },
// ── Y–Z ──
{ code: 'YE', dial: '+967', flag: '🇾🇪', name: 'Yemen' },
{ code: 'ZM', dial: '+260', flag: '🇿🇲', name: 'Zambia' },
{ code: 'ZW', dial: '+263', flag: '🇿🇼', name: 'Zimbabwe' },
];
// Map currency.jsx country_code → dial entry
const COUNTRY_TO_DIAL = {};
DIAL_CODES.forEach(d => { COUNTRY_TO_DIAL[d.code] = d; });
function useAutoDialCode() {
const [dialEntry, setDialEntry] = React.useState(DIAL_CODES[0]); // default PK
React.useEffect(() => {
try {
const cached = JSON.parse(localStorage.getItem('ab_geo_v2') || '{}');
const cc = cached.country_code;
if (cc && COUNTRY_TO_DIAL[cc]) setDialEntry(COUNTRY_TO_DIAL[cc]);
} catch {}
}, []);
return [dialEntry, setDialEntry];
}
function PhoneInput({ onChange, required = false, label = 'Phone Number' }) {
const [dialEntry, setDialEntry] = useAutoDialCode();
const [open, setOpen] = React.useState(false);
const [local, setLocal] = React.useState('');
const [search, setSearch] = React.useState('');
const filtered = React.useMemo(() => {
const q = search.toLowerCase().trim();
if (!q) return DIAL_CODES;
return DIAL_CODES.filter(d =>
d.name.toLowerCase().includes(q) || d.dial.includes(q) || d.code.toLowerCase().includes(q)
);
}, [search]);
const pickCountry = (d) => {
setDialEntry(d);
setOpen(false);
setSearch('');
if (onChange) onChange(d.dial + local);
};
const handleType = (e) => {
const raw = e.target.value.replace(/[^\d\s\-().]/g, '');
setLocal(raw);
if (onChange) onChange(dialEntry.dial + raw);
};
// Close dropdown on outside click
const wrapRef = React.useRef(null);
React.useEffect(() => {
if (!open) return;
const handler = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) { setOpen(false); setSearch(''); } };
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [open]);
return (
{/* Country code selector */}
{ setOpen(o => !o); setSearch(''); }}
style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '0 10px', height: '100%', minHeight: 42,
background: 'var(--surface-2, #1a1a1a)',
border: '1px solid var(--line)', borderRight: 'none',
borderRadius: '6px 0 0 6px', cursor: 'pointer',
color: 'var(--ink-1)', fontSize: 14, whiteSpace: 'nowrap',
}}
>
{dialEntry.flag}
{dialEntry.dial}
▼
{open && (
{/* Search box */}
setSearch(e.target.value)}
style={{
width: '100%', padding: '6px 10px', fontSize: 13,
background: 'var(--surface-2, #1a1a1a)',
border: '1px solid var(--line)', borderRadius: 6,
color: 'var(--ink-1)', outline: 'none',
}}
/>
{/* Country list */}
{filtered.length === 0 && (
No results
)}
{filtered.map(d => (
pickCountry(d)}
style={{
display: 'flex', alignItems: 'center', gap: 10,
width: '100%', padding: '8px 12px',
border: 'none', borderBottom: '1px solid var(--line)',
cursor: 'pointer', color: 'var(--ink-1)',
fontSize: 13, textAlign: 'left',
background: d.code === dialEntry.code ? 'rgba(191,155,96,0.12)' : 'transparent',
}}
>
{d.flag}
{d.name}
{d.dial}
))}
)}
{/* Number input */}
);
}
function SiteNav({ page, setPage, theme, setTheme }) {
const [menuOpen, setMenuOpen] = useState(false);
const links = [
{ key: 'home', label: 'Home', path: '/' },
{ key: 'collection', label: 'Collection', path: '/collection' },
{ key: 'custom', label: 'Custom Order', path: '/custom' },
{ key: 'contact', label: 'Contact', path: '/contact' },
];
return (
{/* Logo */}
setPage('home')}
style={{
background: 'none',
border: 'none',
padding: 0,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '14px'
}}
aria-label="Go to homepage"
>
Aurelle Bride
{/* Desktop nav */}
{/* Actions */}
setTheme(t => t === 'dark' ? 'light' : 'dark')
}
>
{theme === 'dark'
? (
)
: (
)}
{/* Mobile menu */}
setMenuOpen(o => !o)}
>
☰
{/* Mobile nav */}
{menuOpen && (
)}
);
}
// ──────────────────────────────────────────────────────────────
// WHATSAPP FAB
// ──────────────────────────────────────────────────────────────
function WhatsAppFAB() {
const wa = (brand.whatsapp || '').replace(/\D/g, '');
const msg = encodeURIComponent('Hi Aurelle Bride! I\'d love to learn more about your collection.');
return (
);
}
// ──────────────────────────────────────────────────────────────
// HERO
// ──────────────────────────────────────────────────────────────
function HeroSection({ setPage }) {
return (
Bridal Couture · Karachi
Couture,
made for you.
{brand.heroSub || 'A future-forward bridal atelier.'} Every piece is constructed for one bride — your measurements, your vision, your moment.
setPage('collection')}>
Browse Collection
setPage('custom')}>
Custom Order
{[['6+', 'Silhouettes'], ['100%', 'Made to measure'], ['Worldwide', 'Shipping']].map(([n, l]) => (
))}
Latest drop
Saffron Veil Lehenga ·
Scroll
);
}
// ──────────────────────────────────────────────────────────────
// MARQUEE
// ──────────────────────────────────────────────────────────────
function MarqueeStrip() {
const items = ['Lehenga', 'Sharara', 'Gharara', 'Bridal Gown', 'Anarkali', 'Bridal Mexi', 'Custom Couture'];
const doubled = [...items, ...items];
return (
{doubled.map((t, i) => {t} )}
);
}
// ──────────────────────────────────────────────────────────────
// COLLECTION
// ──────────────────────────────────────────────────────────────
function CollectionSection({ onSelectProduct, limit }) {
const { products, loading } = useProducts();
const [filter, setFilter] = useState('All');
const sectionRef = useRef(null);
useReveal(sectionRef);
// Listen for filter events from footer navigation links
useEffect(() => {
const handler = (e) => setFilter(e.detail || 'All');
window.addEventListener('set-collection-filter', handler);
return () => window.removeEventListener('set-collection-filter', handler);
}, []);
const categories = useMemo(() => ['All', ...new Set(products.map(p => p.col))], [products]);
const filtered = filter === 'All' ? products : products.filter(p => p.col === filter);
const display = limit ? filtered.slice(0, limit) : filtered;
return (
The Collection
Each piece, singular.
One-of-one silhouettes and bespoke couture — no two brides wear the same dress.
{/* Filter tabs */}
{categories.map(cat => (
setFilter(cat)}>
{cat}
))}
{loading
?
: (
{display.map((p, i) => (
))}
)}
);
}
// ──────────────────────────────────────────────────────────────
// PRODUCT DETAIL MODAL
// ──────────────────────────────────────────────────────────────
function ProductModal({ product, onClose, onOrder }) {
const [imgIdx, setImgIdx] = useState(0);
const [lightbox, setLightbox] = useState(false);
const gallery = useMemo(() => {
if (!product) return [];
const g = typeof product.gallery === 'string' ? JSON.parse(product.gallery || '[]') : (product.gallery || []);
return g.length ? g : [{ url: product.image_url, label: 'Main' }];
}, [product]);
useEffect(() => { setImgIdx(0); setLightbox(false); }, [product]);
// Escape key closes lightbox
useEffect(() => {
if (!lightbox) return;
const handler = (e) => { if (e.key === 'Escape') setLightbox(false); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [lightbox]);
// ── Dynamic Product Schema injection for Google rich results ──
useEffect(() => {
if (!product) return;
const slug = product.name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
const productUrl = 'https://aurellebride.com/collection#' + slug;
const images = gallery.map(function(g) { return g.url; }).filter(Boolean);
const schema = {
"@context": "https://schema.org",
"@type": "Product",
"name": product.name,
"description": product.desc || (product.name + ' — luxury bridal couture by Aurelle Bride. Made to your measurements, shipped worldwide.'),
"image": images.length ? images : [product.image_url],
"brand": { "@type": "Brand", "name": "Aurelle Bride" },
"sku": 'AB-' + product.id,
"category": product.col || "Bridal Couture",
"url": productUrl,
"offers": {
"@type": "Offer",
"priceCurrency": "PKR",
"price": String(product.priceValue || 0),
"availability": product.status === 'AVAILABLE'
? "https://schema.org/InStock"
: "https://schema.org/LimitedAvailability",
"itemCondition": "https://schema.org/NewCondition",
"seller": { "@type": "Organization", "name": "Aurelle Bride" },
"url": productUrl
}
};
var existing = document.getElementById('product-schema-dynamic');
if (existing) existing.remove();
var el = document.createElement('script');
el.type = 'application/ld+json';
el.id = 'product-schema-dynamic';
el.text = JSON.stringify(schema);
document.head.appendChild(el);
var prevTitle = document.title;
document.title = product.name + ' — Aurelle Bride';
return function() {
var s = document.getElementById('product-schema-dynamic');
if (s) s.remove();
document.title = prevTitle;
};
}, [product]);
if (!product) return null;
const wa = (brand.whatsapp || '').replace(/\D/g, '');
const waMsg = encodeURIComponent(`Hi! I'm interested in: ${product.name} (₨ ${Number(product.priceValue).toLocaleString()} PKR)`);
return (
<>
{/* ── Lightbox ── */}
{lightbox && (
setLightbox(false)}
style={{
position: 'fixed', inset: 0, zIndex: 99999,
background: 'rgba(0,0,0,0.92)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'zoom-out',
}}
>
{/* Close button */}
setLightbox(false)}
style={{
position: 'absolute', top: 18, right: 22, background: 'none',
border: 'none', color: '#fff', fontSize: 28, cursor: 'pointer',
lineHeight: 1, zIndex: 2, opacity: 0.8,
}}
aria-label="Close zoom"
>✕
{/* Prev / Next arrows */}
{gallery.length > 1 && (
<>
{ e.stopPropagation(); setImgIdx(i => (i - 1 + gallery.length) % gallery.length); }}
style={{
position: 'absolute', left: 16, top: '50%', transform: 'translateY(-50%)',
background: 'rgba(255,255,255,0.12)', border: 'none', borderRadius: '50%',
width: 44, height: 44, fontSize: 20, color: '#fff', cursor: 'pointer', zIndex: 2,
}}>‹
{ e.stopPropagation(); setImgIdx(i => (i + 1) % gallery.length); }}
style={{
position: 'absolute', right: 16, top: '50%', transform: 'translateY(-50%)',
background: 'rgba(255,255,255,0.12)', border: 'none', borderRadius: '50%',
width: 44, height: 44, fontSize: 20, color: '#fff', cursor: 'pointer', zIndex: 2,
}}>›
>
)}
{/* Full image */}
e.stopPropagation()}
style={{
maxWidth: '92vw', maxHeight: '92vh',
objectFit: 'contain', borderRadius: 4,
boxShadow: '0 8px 48px rgba(0,0,0,0.6)',
cursor: 'default',
}}
/>
{/* Caption */}
{gallery[imgIdx]?.label && (
{gallery[imgIdx].label}
)}
)}
e.target === e.currentTarget && onClose()}>
{/* Gallery */}
setLightbox(true)}
style={{ width: '100%', height: '100%', objectFit: 'cover', cursor: 'zoom-in' }} />
{/* Zoom hint */}
setLightbox(true)} style={{
position: 'absolute', bottom: 10, right: 10, cursor: 'zoom-in',
background: 'rgba(0,0,0,0.55)', borderRadius: 6, padding: '4px 8px',
fontSize: 11, color: '#fff', letterSpacing: '.06em', userSelect: 'none',
}}>🔍 tap to zoom
{product.status}
{gallery.length > 1 && (
{gallery.map((img, i) => (
setImgIdx(i)}
style={{ width: 56, height: 72, border: `2px solid ${i === imgIdx ? 'var(--gold)' : 'var(--line)'}`, borderRadius: 6, overflow: 'hidden', cursor: 'pointer', flexShrink: 0, padding: 0 }}>
))}
)}
{/* Info */}
{product.col}
{product.name}
✕
{product.desc &&
{product.desc}
}
{product.fabric_detail && (
Fabric & Craft
{product.fabric_detail}
)}
{/* Details grid */}
{product.delivery_weeks && (
Delivery
~{product.delivery_weeks} weeks
)}
{/* Shipping — always inquire, never fixed fee */}
{product.care && (
Care:
{product.care}
)}
Made to your measurements
Every garment is stitched fresh. We collect your measurements before production begins.
{ onClose(); onOrder(product); }}>
Order / Enquire
WhatsApp
>
);
}
// ──────────────────────────────────────────────────────────────
// CUSTOM ORDER — multi-step form
// ──────────────────────────────────────────────────────────────
// ── Helper sub-components for Custom Order ────────────────────
function OrderStep({ n, t, children }) {
return (
STEP {n} · {t}
{children}
);
}
function OrderTile({ active, onClick, children, sub, img }) {
return (
{img && (
{ e.target.style.display = 'none'; }}
style={{ width: '100%', height: '100%', objectFit: 'contain', display: 'block', padding: 8 }} />
)}
{children}
{sub && {sub}
}
);
}
// ──────────────────────────────────────────────────────────────
// CUSTOM ORDER — original visual builder with sticky summary
// ──────────────────────────────────────────────────────────────
function CustomOrderSection({ preProduct, onSuccess }) {
const { toast, show } = useToast();
const { convert, currency, rate } = useCurrency();
const [type, setType] = useState('lehenga');
const [palette, setPalette] = useState('classic');
const [fabric, setFabric] = useState('silk');
const [embs, setEmbs] = useState([]);
const [neckline, setNeckline] = useState('sweetheart');
const [sleeves, setSleeves] = useState('full');
const [files, setFiles] = useState([]);
const [contact, setContact] = useState({ name: '', email: '', phone: '', phone_whatsapp: true, notes: '', occasion: 'walima' });
const [submitting, setSubmitting] = useState(false);
// Measurements
const [measurements, setMeasurements] = useState({ unit: 'cm', bust:'', waist:'', hips:'', height:'', shoulder:'', sleeve_length:'', shirt_length:'', trouser_length:'', measurement_note:'regular', skip: false });
const types = [
{ id: 'lehenga', label: 'Lehenga', img: '/uploads/silhouettes/lehenga.jpg' },
{ id: 'sharara', label: 'Sharara', img: '/uploads/silhouettes/sharara.jpg' },
{ id: 'gharara', label: 'Gharara', img: '/uploads/silhouettes/gharara.jpg' },
{ id: 'anarkali', label: 'Anarkali', img: '/uploads/silhouettes/anarkali.jpg' },
{ id: 'gown', label: 'Bridal Gown', img: '/uploads/silhouettes/gown.jpg' },
{ id: 'mexi', label: 'Bridal Mexi', img: '/uploads/silhouettes/mexi.jpg' },
];
// Map product col → garment type id when coming from product modal
const COL_TO_TYPE = {
'lehenga': 'lehenga', 'sharara': 'sharara', 'gharara': 'gharara',
'anarkali': 'anarkali', 'bridal gown': 'gown', 'gown': 'gown',
'bridal mexi': 'mexi', 'mexi': 'mexi',
};
useEffect(() => {
if (!preProduct) return;
const col = (preProduct.col || preProduct.garment_type || '').toLowerCase().trim();
const matched = COL_TO_TYPE[col];
if (matched) setType(matched);
// Pre-fill name/email if product has a customer field (unlikely but safe)
}, [preProduct]);
const palettes = [
{ id: 'classic', name: 'Classic Bridal', c: ['#8B0000','#FFD700','#FFFFFF','#C9A961'] },
{ id: 'royal', name: 'Royal Sapphire', c: ['#0a2240','#3556a8','#C0C0C0','#E6E6FA'] },
{ id: 'sunset', name: 'Golden Hour', c: ['#a9772a','#d3a35b','#f3d8a0','#FFF8DC'] },
{ id: 'emerald', name: 'Emerald Garden', c: ['#0f4d3a','#1f8060','#9bc7a3','#ecf3e0'] },
{ id: 'rose', name: 'Rose Atelier', c: ['#7a2030','#c47083','#e9c3c8','#fff4f1'] },
{ id: 'onyx', name: 'Onyx Luxe', c: ['#0E0E10','#3A3A3F','#777','#C9A961'] },
];
const fabricList = [
{ id: 'silk', label: 'Silk', price: 25000 },
{ id: 'velvet', label: 'Velvet', price: 35000 },
{ id: 'georgette', label: 'Georgette', price: 18000 },
{ id: 'organza', label: 'Organza', price: 22000 },
{ id: 'net', label: 'Net', price: 15000 },
{ id: 'brocade', label: 'Brocade', price: 42000 },
];
const embOpts = [
{ id: 'zari', label: 'Zari Work', price: 15000 },
{ id: 'mirror', label: 'Mirror Work', price: 12000 },
{ id: 'thread', label: 'Thread Embroidery', price: 18000 },
{ id: 'stone', label: 'Stone Work', price: 22000 },
{ id: 'pearl', label: 'Pearl Beading', price: 14000 },
{ id: 'gota', label: 'Gota Patti', price: 11000 },
];
const necklines = [['sweetheart','Sweetheart'],['vneck','V-Neck'],['boat','Boat'],['scoop','Scoop'],['halter','Halter']];
const sleeveOpts = [['full','Full'],['threequarter','¾'],['cap','Cap'],['sleeveless','Sleeveless']];
const basePrices = { lehenga:150000, sharara:120000, gharara:110000, anarkali:95000, gown:135000, mexi:115000 };
const breakdown = useMemo(() => {
const b = basePrices[type] || 0;
const f = fabricList.find(x => x.id === fabric)?.price || 0;
const e = embs.reduce((s, id) => s + (embOpts.find(o => o.id === id)?.price || 0), 0);
return { base: b, fabric: f, embs: e, total: b + f + e };
}, [type, fabric, embs]);
// Show price in detected currency
const showPrice = (pkr) => {
if (!rate || currency === 'PKR') return '₨ ' + Number(pkr).toLocaleString();
return convert(pkr) || ('₨ ' + Number(pkr).toLocaleString());
};
const onUpload = (e) => {
const list = Array.from(e.target.files || []);
if (files.length + list.length > 8) { show('Max 8 files', 'error'); return; }
setFiles(prev => [...prev, ...list.map(f => ({
name: f.name,
size: (f.size / 1024 / 1024).toFixed(2) + ' MB',
url: f.type.startsWith('image/') ? URL.createObjectURL(f) : null,
_raw: f,
}))]);
};
const submit = async () => {
if (!contact.name || !contact.email) { show('Please share your name and email', 'error'); return; }
if (!contact.phone) { show('Please add your phone number so we can reach you', 'error'); return; }
setSubmitting(true);
const payload = {
name: contact.name, email: contact.email, phone: contact.phone, phone_whatsapp: contact.phone_whatsapp, occasion: contact.occasion, notes: contact.notes,
garment_type: type, palette, fabric, embellishments: embs.join(','), neckline, sleeves,
total_estimate: breakdown.total,
summary: `${type} · ${palette} · ${fabric} · ${embs.length} emb.`,
// measurements
unit: measurements.unit, bust: measurements.bust, waist: measurements.waist,
hips: measurements.hips, height: measurements.height, shoulder: measurements.shoulder,
sleeve_length: measurements.sleeve_length, shirt_length: measurements.shirt_length,
trouser_length: measurements.trouser_length, measurement_note: measurements.measurement_note,
};
try {
const recaptchaToken = await getRecaptchaToken('custom_order');
const r = await fetch('/api/customs.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...payload, recaptcha_token: recaptchaToken }) });
if (!r.ok) throw new Error();
const { id } = await r.json();
if (files.length) {
const fd = new FormData();
files.forEach(f => f._raw && fd.append('files[]', f._raw));
fd.append('parent_table', 'customs');
fd.append('parent_id', id);
await fetch('/api/upload.php', { method: 'POST', body: fd });
}
show("Brief received — we'll email you within 24 hours.", 'success');
setContact({ name: '', email: '', notes: '', occasion: 'walima' });
setFiles([]); setEmbs([]);
} catch { show('Could not submit. Please try again.', 'error'); }
finally { setSubmitting(false); }
};
return (
THE BUILDER
Design your dream.
Build a brief in steps. Attach inspiration. We respond within 24 hours.
{/* Step 01 — Silhouette */}
{types.map(o => (
setType(o.id)} img={o.img} sub={`base ${showPrice(basePrices[o.id])}`}>
{o.label}
))}
{/* Step 02 — Palette */}
{palettes.map(p => (
setPalette(p.id)} className="card"
style={{ padding: 14, textAlign: 'left', borderColor: palette === p.id ? 'var(--gold)' : 'var(--line)', cursor: 'pointer' }}>
{p.c.map(c => )}
{p.name}
))}
{/* Step 03 — Fabric */}
{fabricList.map(f => (
setFabric(f.id)} sub={`+${showPrice(f.price)}`}>
{f.label}
))}
{/* Step 04 — Details */}
NECKLINE
{necklines.map(([id, l]) => setNeckline(id)}>{l} )}
SLEEVES
{sleeveOpts.map(([id, l]) => setSleeves(id)}>{l} )}
{/* Step 05 — Embellishments */}
{embOpts.map(o => (
setEmbs(prev => prev.includes(o.id) ? prev.filter(x => x !== o.id) : [...prev, o.id])} />
{o.label}
+{showPrice(o.price)}
))}
{/* Step 06 — Measurements */}
{/* Step 07 — Inspiration upload */}
Drop or click to upload references
JPG · PNG · PDF · MAX 8 FILES
{files.length > 0 && (
{files.map((f, i) => (
{f.url ?
:
{f.name}
}
setFiles(prev => prev.filter((_, j) => j !== i))}
style={{ position: 'absolute', top: 6, right: 6, width: 28, height: 28, background: 'rgba(0,0,0,.6)', color: '#fff', border: 'none' }}>✕
))}
)}
{/* Step 08 — Your details */}
setContact({ ...contact, name: e.target.value })} />
setContact({ ...contact, email: e.target.value })} />
setContact({ ...contact, occasion: e.target.value })}>
{['barat','walima','mehndi','nikkah','engagement','other'].map(o => (
{o.charAt(0).toUpperCase() + o.slice(1)}
))}
{/* Sticky summary sidebar */}
);
}
// ──────────────────────────────────────────────────────────────
// JOURNAL / BLOG
// ──────────────────────────────────────────────────────────────
function JournalSection({ limit = 3 }) {
const [posts, setPosts] = useState([]);
const sectionRef = useRef(null);
useReveal(sectionRef);
useEffect(() => {
fetch(`/api/posts.php?status=published&limit=${limit}`)
.then(r => r.json())
.then(d => { if (Array.isArray(d) && d.length) setPosts(d); })
.catch(() => {});
}, [limit]);
if (!posts.length) return null;
return (
{posts.map((post, i) => (
{post.cover_url && (
)}
{post.tag}
{post.reading_time && (
{post.reading_time} MIN READ
)}
{post.title}
{post.excerpt &&
{post.excerpt}
}
{formatDate(post.date)}
))}
);
}
// ──────────────────────────────────────────────────────────────
// REVIEWS
// ──────────────────────────────────────────────────────────────
function ReviewsSection() {
const [reviews, setReviews] = useState(SEED_REVIEWS);
const [showForm, setShowForm] = useState(false);
const sectionRef = useRef(null);
useReveal(sectionRef);
useEffect(() => {
fetch('/api/reviews.php?featured=1&status=approved')
.then(r => r.json())
.then(d => { if (Array.isArray(d) && d.length) setReviews(d); })
.catch(() => {});
}, []);
return (
Brides
Real brides, real stories
setShowForm(true)}>Share your experience →
{reviews.map((r, i) => (
))}
{showForm && setShowForm(false)} />}
);
}
function ReviewFormModal({ onClose }) {
const [form, setForm] = useState({ name: '', email: '', phone: '', location: '', occasion: '', rating: 5, title: '', body: '' });
const [submitting, setSubmitting] = useState(false);
const [done, setDone] = useState(false);
const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
const handleSubmit = async () => {
if (!form.name || !form.email || !form.body) return;
setSubmitting(true);
try {
const recaptchaToken = await getRecaptchaToken('review');
await apiFetch('/reviews', { method: 'POST', body: JSON.stringify({ ...form, recaptcha_token: recaptchaToken }) });
setDone(true);
} catch {
alert('Something went wrong. Please try again.');
} finally {
setSubmitting(false);
}
};
return (
{done ? (
✨
Thank you!
Your review has been submitted and will appear once approved by our team.
Close
) : (
)}
);
}
// ──────────────────────────────────────────────────────────────
// FOOTER
// ──────────────────────────────────────────────────────────────
function SiteFooter({ setPage }) {
const [email, setEmail] = useState('');
const [subbed, setSubbed] = useState(false);
const handleSub = async () => {
if (!email.match(/^\S+@\S+\.\S+$/)) return;
try {
const recaptchaToken = await getRecaptchaToken('newsletter');
await fetch('/api/newsletter.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, recaptcha_token: recaptchaToken })
});
} catch {}
setSubbed(true);
};
const navLinks = {
collection: [
{ label: 'Lehenga', path: '/collection', col: 'Lehenga' },
{ label: 'Sharara', path: '/collection', col: 'Sharara' },
{ label: 'Gharara', path: '/collection', col: 'Gharara' },
{ label: 'Anarkali', path: '/collection', col: 'Anarkali' },
{ label: 'Bridal Mexi', path: '/collection', col: 'Bridal Mexi' },
{ label: 'Gown', path: '/collection', col: 'Gown' }
],
atelier: [
{ label: 'Custom Order', key: 'custom', path: '/custom' },
{ label: 'Journal', key: 'journal', path: '/journal' },
{ label: 'Contact', key: 'contact', path: '/contact' }
]
};
const handleNav = (e, pageKey) => {
e.preventDefault();
if (pageKey) setPage(pageKey);
};
return (
);
}
// ──────────────────────────────────────────────────────────────
// CONTACT PAGE
// ──────────────────────────────────────────────────────────────
function ContactPage() {
const [form, setForm] = useState({ name: '', email: '', phone: '', phone_whatsapp: true, subject: '', detail: '', call_back: false, call_time: '' });
const [sent, setSent] = useState(false);
const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
const handleSubmit = async () => {
if (!form.name || !form.email || !form.phone) return;
try {
const recaptchaToken = await getRecaptchaToken('contact');
await fetch('/api/inquiries.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...form, recaptcha_token: recaptchaToken }) });
setSent(true);
} catch { alert('Please try again or WhatsApp us directly.'); }
};
return (
Get in Touch
We'd love to hear from you
Inquiries, collaborations, press — we're here.
{sent ? (
✉️
Message received
We'll get back to you within 24 hours. For urgent matters, WhatsApp us.
) : (
)}
);
}
// ──────────────────────────────────────────────────────────────
// HOMEPAGE
// ──────────────────────────────────────────────────────────────
function HomePage({ setPage, onSelectProduct, onOrder }) {
return (
<>
Design yours
Nothing like yours exists yet
Tell us your vision — we'll sketch it, source the fabrics, and deliver a one-of-one garment to your door, anywhere in the world.
setPage('custom')}>Start Custom Order →
>
);
}
function trackPageView() {
if (typeof gtag === 'function') {
gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href,
page_path: window.location.pathname
});
}
}
// ──────────────────────────────────────────────────────────────
// ROOT APP
// ──────────────────────────────────────────────────────────────
function App() {
const [page, setPageState] = useState('home');
const [theme, setTheme] = useState('dark');
const [selectedProduct, setSelectedProduct] = useState(null);
const [orderProduct, setOrderProduct] = useState(null);
// Route mapping
const routeToPage = (pathname) => {
const clean = pathname.replace(/\/+$/, '') || '/';
if (clean === '/') return 'home';
if (clean === '/collection') return 'collection';
if (clean === '/custom') return 'custom';
if (clean === '/journal') return 'journal';
if (clean === '/contact') return 'contact';
// admin
if (clean.startsWith('/kr/pk/admin/moon')) {
return clean.replace(/^\//, '');
}
return 'home';
};
const pageToRoute = (pageName) => {
if (pageName === 'home') return '/';
if (pageName === 'collection') return '/collection';
if (pageName === 'custom') return '/custom';
if (pageName === 'journal') return '/journal';
if (pageName === 'contact') return '/contact';
if (pageName.startsWith('kr/pk/admin/moon')) {
return '/' + pageName;
}
return '/';
};
const setPage = (nextPage) => {
const target = pageToRoute(nextPage);
if (window.location.pathname !== target) {
window.history.pushState({}, '', target);
}
setPageState(nextPage);
trackPageView();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
// Initial route detection
useEffect(() => {
setPageState(routeToPage(window.location.pathname));
trackPageView();
}, []);
// Browser back/forward support
useEffect(() => {
const handler = () => {
setPageState(routeToPage(window.location.pathname));
trackPageView();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
window.addEventListener('popstate', handler);
return () => {
window.removeEventListener('popstate', handler);
};
}, []);
const handleSelectProduct = (p) => setSelectedProduct(p);
const handleOrder = (p) => {
setOrderProduct(p);
setPage('custom');
};
// Admin route
if (page.startsWith('kr/pk/admin/moon')) {
const subroute = page
.replace('kr/pk/admin/moon', '')
.replace(/^\//, '');
return ;
}
return (
<>
{page === 'home' && (
)}
{page === 'collection' && (
)}
{page === 'custom' && (
setOrderProduct(null)}
/>
)}
{page === 'journal' && }
{page === 'contact' && }
{selectedProduct && (
setSelectedProduct(null)}
onOrder={handleOrder}
/>
)}
>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(
);