// Standalone web pages — browse, detail, become-provider, contact, sign-in // All consume the WebContext (set by WebApp). const WebContext = React.createContext(null); const useWeb = () => React.useContext(WebContext); // ─── Page header (used on subpages) ──────────────────────────────────── function WebPageHeader({ theme, lang, eyebrow, title, sub, tone = 'camel' }) { const T = theme; const rtl = isRTL(lang); const tones = { camel: `linear-gradient(135deg, ${T.camel} 0%, ${T.camelDeep} 100%)`, ink: `linear-gradient(135deg, ${T.ink} 0%, ${T.inkSoft} 100%)`, brown: `linear-gradient(135deg, ${T.pastelBrown} 0%, ${T.camelDeep} 100%)`, }; return (
{eyebrow &&
{eyebrow}
}

{title}

{sub &&

{sub}

}
); } // ─── Browse / search results page ────────────────────────────────────── function WebBrowsePage({ theme, lang }) { const T = theme; const l = L[lang]; const rtl = isRTL(lang); const { setPage, setPkg } = useWeb(); const [filtersOpen, setFiltersOpen] = React.useState(false); const packages = [ { id: 'fairmont', hotel: rtl ? 'فيرمونت ساعة مكة' : 'Fairmont Makkah Clock Tower', dist: rtl ? '50م من الحرم' : '50m from Haram', stars: 5, nights: 7, price: 4899, was: 5499, seats: 4, board: rtl ? 'إفطار+عشاء' : 'B&D', tone: 'dark', provider: rtl ? 'الإيلاف الفاخرة' : 'Elaf Premium', dates: rtl ? '16–25 رمضان' : '16–25 Ramadan' }, { id: 'swiss', hotel: rtl ? 'سويس أوتيل برج الزمزم' : 'Swissôtel Al Maqam', dist: rtl ? '120م من الحرم' : '120m from Haram', stars: 5, nights: 7, price: 3899, seats: 9, board: rtl ? 'إفطار' : 'Breakfast', tone: 'camel', provider: rtl ? 'الرحمة للحج' : 'Al-Rahma', dates: rtl ? '1–8 شوال' : '1–8 Shawwal' }, { id: 'hilton', hotel: rtl ? 'هيلتون مكة المكرمة' : 'Hilton Suites Makkah', dist: rtl ? '200م من الحرم' : '200m from Haram', stars: 4, nights: 7, price: 2799, seats: 12, board: rtl ? 'بدون' : 'Room only', tone: 'brown', provider: rtl ? 'إيلاف ضيوف' : 'Elaf Guests', dates: rtl ? '20 رمضان' : '20 Ramadan' }, { id: 'safwah', hotel: rtl ? 'الصفوة رويال أوركيد' : 'Al Safwah Royal Orchid', dist: rtl ? '320م' : '320m', stars: 4, nights: 6, price: 1199, seats: 9, board: rtl ? 'إفطار' : 'Breakfast', tone: 'cream', provider: rtl ? 'حجاج الجزيرة' : 'Hujjaj Aljazeera', dates: rtl ? '8 ذو القعدة' : '8 Dhul Qadah' }, { id: 'fairmont2', hotel: rtl ? 'فيرمونت — شوال' : 'Fairmont — Shawwal', dist: '50m', stars: 5, nights: 10, price: 5499, seats: 2, board: rtl ? 'إفطار+عشاء' : 'B&D', tone: 'dark', provider: rtl ? 'الإيلاف الفاخرة' : 'Elaf Premium', dates: rtl ? '12 شوال' : '12 Shawwal' }, { id: 'movenpick', hotel: rtl ? 'موڤنبيك حجاج' : 'Mövenpick Hajar', dist: '180m', stars: 5, nights: 10, price: 5290, seats: 7, board: rtl ? 'إفطار' : 'Breakfast', tone: 'brown', provider: rtl ? 'بوابة الكعبة' : 'Kaaba Gate', dates: rtl ? '20 شوال' : '20 Shawwal' }, ]; return ( <> {/* Compact search re-bar */}
{rtl ? 'جدة → مكة' : 'Jeddah → Makkah'}
16 Ramadan · 10 days
{rtl ? '3 مسافرين' : '3 travelers'}
{filtersOpen &&
{/* Price range */}
{rtl ? 'السعر' : 'Price (SAR)'}
1,2005,500
{/* Stars */}
{rtl ? 'تقييم الفندق' : 'Hotel rating'}
{[5, 4, 3].map((n, i) => ( ))}
{/* Distance from Haram */}
{rtl ? 'المسافة من الحرم' : 'Distance from Haram'}
{[rtl ? 'أقل من 100م' : 'Within 100m', rtl ? '100–300م' : '100–300m', rtl ? '300–500م' : '300–500m', rtl ? 'أكثر من 500م' : 'Over 500m'].map((d, i) => ( ))}
{/* Board */}
{rtl ? 'الإقامة' : 'Board'}
{[rtl ? 'بدون' : 'Room only', rtl ? 'إفطار' : 'Breakfast', rtl ? 'نصف إقامة' : 'Half board', rtl ? 'كاملة' : 'Full board'].map((b, i) => (
); } // ─── Package detail page ─────────────────────────────────────────────── function WebDetailPage({ theme, lang }) { const T = theme; const l = L[lang]; const rtl = isRTL(lang); const { pkg, setPage } = useWeb(); const p = pkg || { hotel: rtl ? 'فيرمونت ساعة مكة' : 'Fairmont Makkah Clock Royal', provider: rtl ? 'الإيلاف الفاخرة' : 'Elaf Premium', price: 4899, was: 5499, dates: rtl ? '16–25 رمضان' : '16–25 Ramadan', nights: 7, dist: rtl ? '50م من الحرم' : '50m from Haram', stars: 5, tone: 'dark', board: rtl ? 'إفطار+عشاء' : 'B&D', seats: 4 }; return (
{/* Breadcrumb */}
{/* Gallery */}
{rtl ? '+8 صور' : '+8 photos'}
{/* Main column */}

{p.hotel}

4.8 · 312 {rtl ? 'تقييم' : 'reviews'} · {p.dist}
{/* Stats strip */}
{[ { i: 'calendar', t: p.dates, l: rtl ? 'الموعد' : 'Window' }, { i: 'bed', t: `${p.nights} ${rtl ? 'ليالٍ' : 'nights'}`, l: rtl ? 'الإقامة' : 'Stay' }, { i: 'plane', t: rtl ? 'مع طيران' : 'Flight included', l: rtl ? 'السفر' : 'Travel' }, { i: 'coffee', t: p.board, l: rtl ? 'الوجبات' : 'Meals' }, ].map((m, i) => (
{m.t} {m.l}
))}
{/* Description */}

{rtl ? 'عن الباقة' : 'About this package'}

{rtl ? 'باقة عمرة كاملة تشمل الطيران والإقامة في فيرمونت ساعة مكة على بُعد 50 متراً فقط من الحرم. الإقامة شاملة الإفطار والعشاء يومياً، ونقل من المطار، ومرشد ميداني طوال فترة الإقامة. الباقة مجدولة مسبقاً من المزوّد ولا تتطلب اختيار مقعد.' : 'A complete Umrah package including flight and stay at Fairmont Makkah Clock Royal — just 50m from the Haram. Includes daily breakfast & dinner, airport transfers, and an on-site guide throughout your stay. Pre-arranged by the provider; no seat selection required.'}

{/* What's included */}

{rtl ? 'ما يشمله' : "What's included"}

{[ { i: 'plane', t: rtl ? 'تذكرة طيران ذهاب وعودة' : 'Round-trip flight ticket' }, { i: 'bed', t: rtl ? '6 ليالٍ في فيرمونت' : '6 nights at Fairmont' }, { i: 'coffee', t: rtl ? 'إفطار وعشاء يومياً' : 'Daily breakfast & dinner' }, { i: 'car', t: rtl ? 'نقل من المطار' : 'Airport transfers' }, { i: 'compass', t: rtl ? 'مرشد ميداني' : 'On-site guide' }, { i: 'shield', t: rtl ? 'تأمين سفر' : 'Travel insurance' }, ].map((it, i) => (
{it.t}
))}
{/* Provider card */}

{rtl ? 'عن المزوّد' : 'About the provider'}

{rtl ? 'مزوّد موثّق · منذ 2019' : 'Verified provider · since 2019'}
142 {rtl ? 'باقة نشطة · 4.7★' : 'active packages · 4.7★'}
{/* Sticky booking panel */}
); } function WebCheckoutPage({ theme, lang }) { const T = theme; const rtl = isRTL(lang); const { pkg, setPage } = useWeb(); const [form, setForm] = React.useState({ firstName: '', lastName: '', email: '', phone: '' }); const [busy, setBusy] = React.useState(false); const [message, setMessage] = React.useState(''); const update = e => setForm(v => ({ ...v, [e.target.name]: e.target.value })); const submit = async e => { e.preventDefault(); setBusy(true); setMessage(''); try { const response = await fetch(`${window.DUYUF_API_URL}/v1/payments/edfapay/create`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(form), }); const body = await response.json(); if (!response.ok || !body.redirectUrl) throw new Error(body.error || 'gateway_error'); window.location.assign(body.redirectUrl); } catch (_) { setMessage(rtl ? 'تعذر فتح بوابة الدفع. راجع البيانات وحاول مرة أخرى.' : 'Could not open the payment gateway. Check your details and retry.'); setBusy(false); } }; const input = (name, label, type='text', placeholder='') => ; return

{rtl?'تجربة السداد':'Payment test'}

{rtl?`سنفتح صفحة EdfaPay الرسمية لتجربة دفع 1 ر.س فقط. سعر الباقة ${Number(pkg?.price || 0).toLocaleString()} ر.س لن يُخصم في هذه التجربة.`:`We will open the official EdfaPay page for a SAR 1 test only. The package price will not be charged in this test.`}

{rtl?'المبلغ التجريبي: 1.00 ر.س':'Test amount: SAR 1.00'}
{input('firstName',rtl?'الاسم الأول':'First name')}{input('lastName',rtl?'اسم العائلة':'Last name')}{input('email',rtl?'البريد الإلكتروني':'Email','email','name@example.com')}{input('phone',rtl?'رقم الجوال السعودي':'Saudi mobile','tel','05xxxxxxxx')}
{message&&

{message}

}
; } function WebPaymentResultPage({ theme, lang }) { const T=theme, rtl=isRTL(lang), { setPage }=useWeb(); const orderId=new URLSearchParams(window.location.search).get('order')||''; const [status,setStatus]=React.useState(null); React.useEffect(()=>{ if(orderId) fetch(`${window.DUYUF_API_URL}/v1/payments/status?order=${encodeURIComponent(orderId)}`).then(r=>r.json()).then(b=>setStatus(b.order?.status||'pending')).catch(()=>setStatus('pending')); },[orderId]); return

{rtl?'جارٍ التحقق من عملية السداد':'Verifying payment'}

{rtl?'لا نعتبر العملية ناجحة حتى يصل تأكيد آمن من بوابة الدفع. يمكنك العودة للموقع الآن.':'The payment is not considered successful until a secure gateway confirmation arrives.'}

{orderId&&

{orderId} · {status||'...'}

}
; } // ─── Become provider page ────────────────────────────────────────────── function WebProviderSignupPage({ theme, lang }) { const T = theme; const l = L[lang]; const rtl = isRTL(lang); const { setPage } = useWeb(); const [businessName, setBusinessName] = React.useState(''); const [phone, setPhone] = React.useState(''); const [otp, setOtp] = React.useState(''); const [step, setStep] = React.useState('details'); const [busy, setBusy] = React.useState(false); const [message, setMessage] = React.useState(''); const verifyingRef = React.useRef(false); const normalizeSaudiPhone = (value) => { const digits = String(value || '').replace(/\D/g, ''); if (/^05\d{8}$/.test(digits)) return `+966${digits.slice(1)}`; if (/^9665\d{8}$/.test(digits)) return `+${digits}`; if (/^5\d{8}$/.test(digits)) return `+966${digits}`; return ''; }; const sendOtp = async () => { const formattedPhone = normalizeSaudiPhone(phone); if (!businessName.trim() || !formattedPhone) { setMessage(rtl ? 'أدخل الاسم التجاري ورقم جوال سعودي صحيح.' : 'Enter a business name and valid Saudi mobile.'); return; } setBusy(true); setMessage(''); try { if (window.duyufRecaptcha) window.duyufRecaptcha.clear(); window.duyufRecaptcha = new firebase.auth.RecaptchaVerifier('duyuf-recaptcha', { size: 'invisible' }); window.duyufConfirmation = await firebase.auth().signInWithPhoneNumber(formattedPhone, window.duyufRecaptcha); setStep('otp'); setMessage(rtl ? 'أرسلنا رمز التحقق إلى جوالك.' : 'We sent a verification code to your phone.'); } catch (error) { console.error(error); setMessage(rtl ? 'تعذّر إرسال الرمز. تأكد من تفعيل Phone وإضافة الدومين في Firebase.' : 'Could not send code. Check Phone Auth and Firebase authorized domains.'); } finally { setBusy(false); } }; const verifyOtp = async (codeValue = otp) => { if (!/^\d{6}$/.test(codeValue) || !window.duyufConfirmation || verifyingRef.current) return; verifyingRef.current = true; setBusy(true); setMessage(''); let credential; try { credential = await window.duyufConfirmation.confirm(codeValue); } catch (error) { console.error('Firebase OTP confirmation failed', error); setMessage(rtl ? 'الرمز غير صحيح أو انتهت صلاحيته. اطلب رمزاً جديداً.' : 'The code is invalid or expired. Request a new one.'); setBusy(false); verifyingRef.current = false; return; } try { const idToken = await credential.user.getIdToken(true); const response = await fetch(`${window.DUYUF_API_URL}/v1/auth/firebase-provider`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ idToken, businessName: businessName.trim() }), }); const result = await response.json(); if (!response.ok || !result.ok) throw new Error(result.error || 'provider_login_failed'); localStorage.setItem('duyuf_provider_session', result.sessionToken); localStorage.setItem('duyuf_provider', JSON.stringify(result.provider)); setPage('provider-dashboard'); } catch (error) { console.error('Provider account creation failed', error); setMessage(rtl ? 'تم توثيق الجوال، لكن تعذّر إنشاء حساب المزود. حاول مرة أخرى دون طلب رمز جديد.' : 'Phone verified, but provider account creation failed. Try again without requesting a new code.'); } finally { setBusy(false); verifyingRef.current = false; } }; return ( <>
{/* Form */}

{rtl ? 'بيانات الشركة' : 'Company information'}

{step === 'details' ? (rtl ? 'ابدأ بالاسم التجاري والجوال فقط' : 'Start with business name and mobile only') : step === 'otp' ? (rtl ? 'تحقق من رقم الجوال' : 'Verify mobile') : (rtl ? 'تم التسجيل بنجاح' : 'Registration complete')}

{step === 'details' && <> setBusinessName(e.target.value)} placeholder={rtl ? 'مثال: رحلات النور' : 'Example: Al Noor Travel'} style={{ height: 46, padding: '0 14px', borderRadius: 12, background: T.bg, border: `1px solid ${T.hairlineStrong}`, color: T.text, fontFamily: 'Alexandria', fontSize: 14 }}/>
🇸🇦+966
setPhone(e.target.value.replace(/\D/g, '').slice(0, 10))} inputMode="numeric" autoComplete="tel-national" placeholder="5xxxxxxxx" aria-label={rtl ? 'رقم الجوال' : 'Mobile number'} style={{ minWidth: 0, flex: 1, height: '100%', padding: '0 12px', border: 0, outline: 0, background: 'transparent', color: T.text, fontFamily: 'Alexandria', fontSize: 16, textAlign: 'left' }}/>
} {step === 'otp' && <> { const value=e.target.value.replace(/\D/g, '').slice(0, 6); setOtp(value); if(value.length===6) setTimeout(() => verifyOtp(value), 0); }} inputMode="numeric" autoComplete="one-time-code" placeholder="••••••" dir="ltr" style={{ height: 54, padding: '0 14px', borderRadius: 12, textAlign: 'center', letterSpacing: 8, background: T.bg, border: `1px solid ${T.hairlineStrong}`, color: T.text, fontSize: 22 }}/> } {step === 'done' &&
{rtl ? 'حالة الملف: غير مكتمل. ستجد داخل لوحة المزود السجل التجاري والترخيص والبريد والوثائق المطلوبة.' : 'Profile status: incomplete. Complete registration, license, email and documents from your dashboard.'}
}
{message &&
{message}
}
{/* Side info */}

{rtl ? 'ماذا تحصل عليه' : "What you'll get"}

{[ { i: 'trend-up', t: rtl ? 'لوحة إحصائيات لحظية' : 'Live analytics dashboard', d: rtl ? 'تابع الإيرادات والحجوزات بالوقت الفعلي' : 'Track revenue and bookings in real time' }, { i: 'kaaba', t: rtl ? 'معالج تكوين الباقات' : 'Package wizard', d: rtl ? 'انشر باقات جديدة خلال دقائق' : 'Publish new packages in minutes' }, { i: 'scan', t: rtl ? 'تطبيق التشغيل' : 'Operations app', d: rtl ? 'تحقّق من العملاء وتنبيهات أثناء الرحلة' : 'Check in travelers and send in-trip alerts' }, { i: 'card', t: rtl ? 'دفعات مجدولة وآمنة' : 'Scheduled secure payouts', d: rtl ? 'تحويل أسبوعي لحسابك البنكي' : 'Weekly payouts to your bank account' }, ].map((b, i) => (
{b.t}
{b.d}
))}
{rtl ? 'العمولة الشفافة' : 'Transparent commission'}
{rtl ? 'عمولة ضيوف 8% فقط من سعر الباقة الأساسي. لا رسوم مخفية، لا اشتراك شهري.' : 'Just 8% Duyuf commission on base package price. No hidden fees, no monthly subscription.'}
); } // ─── Contact page ────────────────────────────────────────────────────── function WebContactPage({ theme, lang }) { const T = theme; const rtl = isRTL(lang); return ( <>
{[ { i: 'phone', t: rtl ? 'واتساب' : 'WhatsApp', sub: '+966 800 200 0', meta: rtl ? 'الرد خلال دقائق' : 'Reply in minutes', tone: 'green' }, { i: 'mail', t: rtl ? 'بريد إلكتروني' : 'Email', sub: 'support@duyuf.app', meta: rtl ? 'الرد خلال 24 ساعة' : 'Reply in 24h', tone: 'camel' }, { i: 'shield', t: rtl ? 'للمزوّدين' : 'Providers', sub: 'providers@duyuf.app', meta: rtl ? 'فريق مخصّص' : 'Dedicated team', tone: 'ink' }, ].map((c, i) => (
{c.t}
{c.sub}
{c.meta}
))}
{/* Contact form */}

{rtl ? 'أرسل لنا رسالة' : 'Send us a message'}

{[ { l: rtl ? 'الاسم' : 'Name', v: rtl ? 'محمد الفهد' : 'Mohammed AlFahd' }, { l: rtl ? 'البريد' : 'Email', v: 'm.alfahd@email.com' }, ].map((f, i) => (
{f.v}
))}
{rtl ? 'استفسار عن حجز قائم' : 'Question about an existing booking'}
{rtl ? 'اكتب رسالتك هنا…' : 'Type your message here…'}
{/* Office */}

{rtl ? 'المكتب الرئيسي' : 'Head office'}

{rtl ? 'برج المملكة، الطابق 22\nطريق الملك فهد، الرياض 12241\nالمملكة العربية السعودية' : 'Kingdom Tower, Floor 22\nKing Fahd Road, Riyadh 12241\nSaudi Arabia'}

Duyuf HQ
); } // ─── Sign-in page ────────────────────────────────────────────────────── function WebSignInPage({ theme, lang }) { const T = theme; const rtl = isRTL(lang); const { setPage } = useWeb(); const [phone, setPhone] = React.useState(''); const [otp, setOtp] = React.useState(''); const [step, setStep] = React.useState('phone'); const [busy, setBusy] = React.useState(false); const [message, setMessage] = React.useState(''); const loginVerifyingRef = React.useRef(false); React.useEffect(() => { if (localStorage.getItem('duyuf_provider_session')) setPage('provider-dashboard'); }, []); const normalize = value => { const d = String(value || '').replace(/\D/g, ''); if (/^05\d{8}$/.test(d)) return `+966${d.slice(1)}`; if (/^9665\d{8}$/.test(d)) return `+${d}`; if (/^5\d{8}$/.test(d)) return `+966${d}`; return ''; }; const send = async () => { const mobile = normalize(phone); if (!mobile) return setMessage(rtl ? 'أدخل رقم جوال سعودي صحيح.' : 'Enter a valid Saudi mobile.'); setBusy(true); setMessage(''); try { if (window.duyufLoginRecaptcha) window.duyufLoginRecaptcha.clear(); window.duyufLoginRecaptcha = new firebase.auth.RecaptchaVerifier('duyuf-login-recaptcha', { size: 'invisible' }); window.duyufLoginConfirmation = await firebase.auth().signInWithPhoneNumber(mobile, window.duyufLoginRecaptcha); setStep('otp'); setMessage(rtl ? 'أرسلنا رمز التحقق إلى جوالك.' : 'Verification code sent.'); } catch (e) { console.error(e); setMessage(rtl ? 'تعذر إرسال الرمز. راجع إعدادات Firebase.' : 'Could not send the code.'); } finally { setBusy(false); } }; const verify = async (codeValue = otp) => { if (!/^\d{6}$/.test(codeValue) || loginVerifyingRef.current) return; loginVerifyingRef.current = true; setBusy(true); setMessage(''); let credential; try { credential = await window.duyufLoginConfirmation.confirm(codeValue); } catch (e) { console.error('Firebase OTP confirmation failed', e); setMessage(rtl ? 'الرمز غير صحيح أو انتهت صلاحيته. اطلب رمزاً جديداً.' : 'Invalid or expired code. Request a new one.'); setBusy(false); loginVerifyingRef.current = false; return; } try { const idToken = await credential.user.getIdToken(true); const response = await fetch(`${window.DUYUF_API_URL}/v1/auth/firebase-provider`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ idToken }) }); const result = await response.json(); if (!response.ok || !result.ok) throw new Error(result.error || 'login_failed'); localStorage.setItem('duyuf_provider_session', result.sessionToken); localStorage.setItem('duyuf_provider', JSON.stringify(result.provider)); setPage('provider-dashboard'); } catch (e) { console.error('Provider login failed', e); setMessage(e.message === 'provider_not_registered' ? (rtl ? 'تم توثيق الجوال، لكن الرقم غير مسجل كمزوّد. اختر «سجّل هنا» أولاً.' : 'Phone verified, but it is not registered as a provider.') : (rtl ? 'تم توثيق الجوال، لكن تعذّر فتح حساب المزود. حاول مرة أخرى.' : 'Phone verified, but the provider account could not be opened. Try again.')); } finally { setBusy(false); loginVerifyingRef.current = false; } }; return (

{rtl ? 'مرحباً بعودتك' : 'Welcome back'}

{rtl ? 'سجّل الدخول برقم جوالك' : 'Sign in with your phone number'}

{step === 'phone' ?
🇸🇦+966
setPhone(e.target.value.replace(/\D/g, '').slice(0, 10))} inputMode="numeric" autoComplete="tel-national" dir="ltr" placeholder="5xxxxxxxx" aria-label={rtl ? 'رقم الجوال' : 'Mobile number'} style={{ minWidth: 0, flex: 1, height: '100%', padding: '0 12px', border: 0, outline: 0, background: 'transparent', color: T.text, fontFamily: 'Alexandria', fontSize: 16, textAlign: 'left' }}/>
: { const value=e.target.value.replace(/\D/g, '').slice(0, 6); setOtp(value); if(value.length===6) setTimeout(() => verify(value), 0); }} inputMode="numeric" autoComplete="one-time-code" dir="ltr" placeholder="••••••" style={{ boxSizing: 'border-box', width: '100%', height: 52, padding: '0 14px', textAlign: 'center', letterSpacing: 8, borderRadius: 14, background: T.bg, color: T.text, border: `1.5px solid ${T.primary}`, fontSize: 22 }}/>}
{message &&
{message}
}
{rtl ? 'أو' : 'or'}

{rtl ? 'بالمتابعة، أنت توافق على الشروط وسياسة الخصوصية' : 'By continuing you agree to the Terms & Privacy Policy'}

); } function WebProviderDashboardPage({ theme, lang }) { const T = theme, rtl = isRTL(lang), { setPage } = useWeb(); const [data, setData] = React.useState({ provider: null, packages: [] }); const [loading, setLoading] = React.useState(true); React.useEffect(() => { const token = localStorage.getItem('duyuf_provider_session'); if (!token) return setPage('signin'); fetch(`${window.DUYUF_API_URL}/v1/provider/packages`, { headers: { authorization: `Bearer ${token}` } }) .then(r => r.json().then(body => ({ ok: r.ok, body }))).then(({ ok, body }) => { if (!ok) throw new Error(); setData(body); }) .catch(() => { localStorage.removeItem('duyuf_provider_session'); setPage('signin'); }).finally(() => setLoading(false)); }, []); const provider = data.provider || JSON.parse(localStorage.getItem('duyuf_provider') || 'null'); return
{rtl ? 'لوحة المزود' : 'Provider dashboard'}

{rtl ? 'أهلاً بك، ' : 'Welcome, '}{provider?.businessName || (rtl ? 'مزود ضيوف' : 'Duyuf provider')}

{provider && !provider.profileComplete &&
{rtl ? 'ملفك غير مكتمل' : 'Your profile is incomplete'}
{rtl ? 'أكمل السجل التجاري والترخيص والبريد والوثائق. يمكنك إضافة باقاتك الآن.' : 'Complete registration, license, email and documents. You can add packages now.'}
}

{rtl ? 'باقاتي' : 'My packages'}

{loading ?
{rtl ? 'جارٍ التحميل…' : 'Loading…'}
: data.packages.length === 0 ?

{rtl ? 'لا توجد باقات بعد' : 'No packages yet'}

{rtl ? 'أضف أول باقة عمرة خلال أقل من دقيقة.' : 'Add your first Umrah package in under a minute.'}

:
{data.packages.map(p =>
{p.title}{p.status === 'active' ? (rtl ? 'نشطة' : 'Active') : (rtl ? 'بانتظار الاعتماد' : 'Pending approval')}
{p.hotel_name}
{p.duration_days} {rtl ? 'أيام' : 'days'} · {p.available_from} — {p.available_to}
{rtl ? 'السعة' : 'Capacity'}: {p.seats} {rtl ? 'شخص' : 'people'}
{Number(p.price_per_person).toLocaleString()} {rtl ? 'ر.س / فرد' : 'SAR / person'}
)}
}
; } function WebProviderAddPackagePage({ theme, lang }) { const T = theme, rtl = isRTL(lang), { setPage } = useWeb(); const iso = date => date.toISOString().slice(0, 10), today = new Date(), after30 = new Date(Date.now() + 30*86400000); const [form, setForm] = React.useState({ title: '', hotelName: '', durationDays: 7, pricePerPerson: '', availableFrom: iso(today), availableTo: iso(after30), seats: 1 }); const [busy, setBusy] = React.useState(false), [message, setMessage] = React.useState(''); const field = (key, label, type='text') => ; const stepper = (key, label, min, max) => { const value = Number(form[key]) || min; const update = next => setForm({ ...form, [key]: Math.min(max, Math.max(min, next)) }); return
{label}
update(Number(e.target.value))} style={{ width:'100%',minWidth:0,border:0,outline:0,background:'transparent',color:T.text,textAlign:'center',fontFamily:'Alexandria',fontSize:17,fontWeight:700,MozAppearance:'textfield' }}/>
; }; const save = async e => { e.preventDefault(); setBusy(true); setMessage(''); try { const token=localStorage.getItem('duyuf_provider_session'); const payload={...form,title:form.title.trim()||`باقة ${form.hotelName.trim()}`,durationDays:Number(form.durationDays),pricePerPerson:Number(form.pricePerPerson),seats:Number(form.seats)}; const r=await fetch(`${window.DUYUF_API_URL}/v1/provider/packages`,{method:'POST',headers:{'content-type':'application/json',authorization:`Bearer ${token}`},body:JSON.stringify(payload)}); const b=await r.json(); if(r.status===401){localStorage.removeItem('duyuf_provider_session');setPage('signin');return;} if(!r.ok||!b.ok){const labels={hotelName:'اسم الفندق',durationDays:'عدد الأيام',pricePerPerson:'سعر الفرد',capacity:'السعة بالأشخاص',availableFrom:'تاريخ البداية',availableTo:'تاريخ النهاية'}; const fields=(b.fields||[]).map(x=>labels[x]||x).join('، '); throw new Error(fields||b.error||'save_failed');} setPage('provider-dashboard'); } catch(error) { setMessage(rtl?`تعذر الحفظ. راجع: ${error.message}`:`Could not save. Check: ${error.message}`); } finally { setBusy(false); } }; return

{rtl?'إضافة باقة سريعة':'Quick package setup'}

{rtl?'أدخل الأساسيات الآن، ويمكنك إضافة التفاصيل والصور لاحقاً. اسم الباقة اختياري ويُنشأ تلقائياً من الفندق.':'Enter the essentials now; add details and photos later. Package title is generated from the hotel if left blank.'}

{field('title',rtl?'اسم الباقة (اختياري)':'Package title (optional)')}{field('hotelName',rtl?'اسم الفندق':'Hotel name')}{stepper('durationDays',rtl?'عدد الأيام':'Duration (days)',1,60)}{field('pricePerPerson',rtl?'سعر الفرد بالريال':'Price per person','number')}{field('availableFrom',rtl?'متاحة من':'Available from','date')}{field('availableTo',rtl?'متاحة إلى':'Available to','date')}{stepper('seats',rtl?'السعة بالأشخاص':'Capacity (people)',1,999)}
{message&&

{message}

}
; } Object.assign(window, { WebContext, useWeb, WebPageHeader, WebBrowsePage, WebDetailPage, WebCheckoutPage, WebPaymentResultPage, WebProviderSignupPage, WebContactPage, WebSignInPage, WebProviderDashboardPage, WebProviderAddPackagePage });