Spaces:
Sleeping
Sleeping
| // import { useState, useEffect } from 'react'; | |
| // import { useNavigate } from 'react-router-dom'; | |
| // import { useLanguage } from '../../lib/languageContext'; | |
| // import { LanguageSwitcher } from '../LanguageSwitcher'; | |
| // // ─── Types ──────────────────────────────────────────────────── | |
| // type InterviewStatus = | |
| // | 'loading' // جاري جلب البيانات | |
| // | 'no_interview' // لا يوجد موعد بعد | |
| // | 'scheduled' // موعد محدد — الزر معطل لحد ما يحين الوقت | |
| // | 'joinable' // حان وقت المقابلة — الزر مفعّل | |
| // | 'completed'; // انتهت المقابلة | |
| // interface InterviewData { | |
| // scheduledAt: string; // ISO datetime e.g. "2026-03-25T14:00:00" | |
| // meetingLink: string; // رابط الاجتماع | |
| // status: 'PENDING' | 'COMPLETED' | 'CANCELLED'; | |
| // } | |
| // // ─── Date helpers ───────────────────────────────────────────── | |
| // const AR_DAYS = ['الأحد','الاثنين','الثلاثاء','الأربعاء','الخميس','الجمعة','السبت']; | |
| // const AR_MONTHS = ['يناير','فبراير','مارس','أبريل','مايو','يونيو','يوليو','أغسطس','سبتمبر','أكتوبر','نوفمبر','ديسمبر']; | |
| // function fmtDateAr(d: Date) { | |
| // return `${AR_DAYS[d.getDay()]}، ${d.getDate()} ${AR_MONTHS[d.getMonth()]} ${d.getFullYear()}`; | |
| // } | |
| // function fmtDateEn(d: Date) { | |
| // return d.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); | |
| // } | |
| // function fmtTimeAr(d: Date) { | |
| // const h = d.getHours(); | |
| // const m = d.getMinutes().toString().padStart(2, '0'); | |
| // const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h; | |
| // return `${h12}:${m} ${h < 12 ? 'صباحاً' : 'مساءً'}`; | |
| // } | |
| // function fmtTimeEn(d: Date) { | |
| // return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); | |
| // } | |
| // function timeUntil(d: Date, isAr: boolean): string { | |
| // const diff = d.getTime() - Date.now(); | |
| // if (diff <= 0) return ''; | |
| // const days = Math.floor(diff / 86400000); | |
| // const hours = Math.floor((diff % 86400000) / 3600000); | |
| // const mins = Math.floor((diff % 3600000) / 60000); | |
| // if (isAr) { | |
| // if (days > 0) return `بعد ${days} يوم`; | |
| // if (hours > 0) return `بعد ${hours} ساعة`; | |
| // return `بعد ${mins} دقيقة`; | |
| // } | |
| // if (days > 0) return `in ${days} day${days > 1 ? 's' : ''}`; | |
| // if (hours > 0) return `in ${hours} hour${hours > 1 ? 's' : ''}`; | |
| // return `in ${mins} minute${mins > 1 ? 's' : ''}`; | |
| // } | |
| // // ───────────────────────────────────────────────────────────── | |
| // // ⚠️ API_PLACEHOLDER — استبدل الداتا الاستاتيك دي بـ API حقيقي | |
| // // | |
| // // Endpoint المطلوب: | |
| // // GET /api/sheikh/interview | |
| // // Headers: Authorization: Bearer <token> | |
| // // | |
| // // Response لو في موعد: | |
| // // { | |
| // // "scheduledAt": "2026-03-25T14:00:00", | |
| // // "meetingLink": "https://meet.google.com/xxx", | |
| // // "status": "PENDING" | "COMPLETED" | "CANCELLED" | |
| // // } | |
| // // | |
| // // Response لو مفيش موعد: | |
| // // 404 Not Found | |
| // // | |
| // // ───────────────────────────────────────────────────────────── | |
| // // ⬇️ الداتا الاستاتيك — احذفها لما تربط الـ API | |
| // const STATIC_INTERVIEW: InterviewData | null = { | |
| // // غيّر 'scheduled' لـ null لو عايز تشوف شاشة "لا يوجد موعد" | |
| // // غيّر status لـ 'COMPLETED' لو عايز تشوف شاشة "انتهت المقابلة" | |
| // scheduledAt: (() => { | |
| // // موعد بعد 2 يوم من دلوقتي للاختبار | |
| // const d = new Date(); | |
| // d.setDate(d.getDate() + 2); | |
| // d.setHours(14, 0, 0, 0); | |
| // return d.toISOString(); | |
| // })(), | |
| // meetingLink: 'https://meet.google.com/abc-defg-hij', | |
| // status: 'PENDING', | |
| // }; | |
| // // ─── Main Component ─────────────────────────────────────────── | |
| // export function InterviewSchedule() { | |
| // const { language } = useLanguage(); | |
| // const navigate = useNavigate(); | |
| // const isAr = language === 'ar'; | |
| // const [status, setStatus] = useState<InterviewStatus>('loading'); | |
| // const [interview, setInterview] = useState<InterviewData | null>(null); | |
| // const [error, setError] = useState(''); | |
| // // ───────────────────────────────────────────────────────── | |
| // // ⚠️ API_PLACEHOLDER — جلب بيانات المقابلة | |
| // // استبدل الكود ده بـ: | |
| // // | |
| // // const token = localStorage.getItem('authToken') ?? ''; | |
| // // const res = await fetch('/api/sheikh/interview', { | |
| // // headers: { Authorization: `Bearer ${token}` } | |
| // // }); | |
| // // if (res.status === 404) { setStatus('no_interview'); return; } | |
| // // if (!res.ok) throw new Error(`${res.status}`); | |
| // // const data: InterviewData = await res.json(); | |
| // // processInterviewData(data); | |
| // // ───────────────────────────────────────────────────────── | |
| // useEffect(() => { | |
| // // محاكاة loading لمدة ثانية ونص للاختبار | |
| // const timer = setTimeout(() => { | |
| // try { | |
| // // ⬇️ ابدأ من هنا لما تربط الـ API — شيل الـ STATIC_INTERVIEW واستخدم response الـ API | |
| // const data = STATIC_INTERVIEW; | |
| // if (!data) { | |
| // setStatus('no_interview'); | |
| // return; | |
| // } | |
| // processInterviewData(data); | |
| // } catch (e: any) { | |
| // setError(e.message || 'Unknown error'); | |
| // setStatus('no_interview'); | |
| // } | |
| // }, 1200); | |
| // return () => clearTimeout(timer); | |
| // }, []); | |
| // function processInterviewData(data: InterviewData) { | |
| // setInterview(data); | |
| // if (data.status === 'COMPLETED') { setStatus('completed'); return; } | |
| // if (data.status === 'CANCELLED') { setStatus('no_interview'); return; } | |
| // const scheduled = new Date(data.scheduledAt); | |
| // // يصبح joinable لو الموعد جه أو باقي 5 دقائق أو أقل | |
| // if (scheduled.getTime() - Date.now() <= 5 * 60 * 1000) { | |
| // setStatus('joinable'); | |
| // } else { | |
| // setStatus('scheduled'); | |
| // } | |
| // } | |
| // // Tick كل 30 ثانية عشان يتحول لـ joinable تلقائياً لما يحين الوقت | |
| // useEffect(() => { | |
| // if (!interview || status !== 'scheduled') return; | |
| // const timer = setInterval(() => { | |
| // const scheduled = new Date(interview.scheduledAt); | |
| // if (scheduled.getTime() - Date.now() <= 5 * 60 * 1000) { | |
| // setStatus('joinable'); | |
| // clearInterval(timer); | |
| // } | |
| // }, 30_000); | |
| // return () => clearInterval(timer); | |
| // }, [interview, status]); | |
| // const scheduledDate = interview ? new Date(interview.scheduledAt) : null; | |
| // const remaining = scheduledDate && status === 'scheduled' | |
| // ? timeUntil(scheduledDate, isAr) : ''; | |
| // // ─── Render ───────────────────────────────────────────────── | |
| // return ( | |
| // <> | |
| // <style>{CSS}</style> | |
| // <div className="iv-root" dir={isAr ? 'rtl' : 'ltr'}> | |
| // <div className="iv-bg-circles" aria-hidden="true"> | |
| // <div className="iv-circle iv-c1" /> | |
| // <div className="iv-circle iv-c2" /> | |
| // <div className="iv-circle iv-c3" /> | |
| // </div> | |
| // <div className="iv-grid-pattern" aria-hidden="true" /> | |
| // <div className="iv-lang"><LanguageSwitcher /></div> | |
| // <div className="iv-card"> | |
| // {/* ══════════════ LOADING ══════════════ */} | |
| // {status === 'loading' && ( | |
| // <div className="iv-center"> | |
| // <div className="iv-spinner" /> | |
| // <p className="iv-muted-sm"> | |
| // {isAr ? 'جاري تحميل بيانات المقابلة...' : 'Loading interview details...'} | |
| // </p> | |
| // </div> | |
| // )} | |
| // {/* ══════════════ NO INTERVIEW ══════════════ */} | |
| // {status === 'no_interview' && ( | |
| // <div className="iv-center"> | |
| // <div className="iv-badge"> | |
| // {isAr ? 'المرحلة الأخيرة' : 'Final Step'} | |
| // </div> | |
| // <div className="iv-ornament"><span /><div className="iv-diamond" /><span /></div> | |
| // <h1 className="iv-h1"> | |
| // {isAr ? 'مقابلة التحقق' : 'Verification Interview'} | |
| // </h1> | |
| // <p className="iv-sub"> | |
| // {isAr | |
| // ? 'أنت على وشك إتمام انضمامك للمنصة. فريقنا سيحدد لك موعد المقابلة وسيصلك إشعار فور تحديده.' | |
| // : "You're almost there. Our team will schedule your verification interview and notify you as soon as it's confirmed."} | |
| // </p> | |
| // <div className="iv-pills"> | |
| // {INFO_PILLS.map((p, i) => ( | |
| // <div className="iv-pill" key={i}> | |
| // <span className="iv-pill-icon">{p.icon}</span> | |
| // <span className="iv-pill-val">{isAr ? p.ar : p.en}</span> | |
| // <span className="iv-pill-lbl">{isAr ? p.lAr : p.lEn}</span> | |
| // </div> | |
| // ))} | |
| // </div> | |
| // {/* Waiting indicator */} | |
| // <div className="iv-waiting-box"> | |
| // <div className="iv-dots"> | |
| // <span className="iv-dot iv-dot-1" /> | |
| // <span className="iv-dot iv-dot-2" /> | |
| // <span className="iv-dot iv-dot-3" /> | |
| // </div> | |
| // <p className="iv-waiting-txt"> | |
| // {isAr | |
| // ? 'في انتظار تحديد الموعد من قِبل الفريق...' | |
| // : 'Waiting for the team to schedule your slot...'} | |
| // </p> | |
| // </div> | |
| // <p className="iv-footnote"> | |
| // {isAr | |
| // ? 'ستصلك رسالة بريد إلكتروني فور تحديد الموعد' | |
| // : "You'll receive an email once your slot is confirmed"} | |
| // </p> | |
| // </div> | |
| // )} | |
| // {/* ══════════════ SCHEDULED / JOINABLE ══════════════ */} | |
| // {(status === 'scheduled' || status === 'joinable') && scheduledDate && ( | |
| // <div className="iv-center"> | |
| // <div className="iv-badge iv-badge-green"> | |
| // {isAr ? 'تم تحديد موعدك' : 'Interview Scheduled'} | |
| // </div> | |
| // <div className="iv-ornament"><span /><div className="iv-diamond" /><span /></div> | |
| // <h1 className="iv-h1"> | |
| // {isAr ? 'مقابلة التحقق' : 'Verification Interview'} | |
| // </h1> | |
| // <p className="iv-sub"> | |
| // {isAr | |
| // ? 'تم تحديد موعد مقابلتك. يُرجى الحضور في الوقت المحدد واستعداد كاميرتك وميكروفونك.' | |
| // : 'Your interview has been scheduled. Please be ready on time with your camera and microphone.'} | |
| // </p> | |
| // {/* ─────────────────────────────────────────────── | |
| // Slot card | |
| // البيانات دي جايه من: | |
| // scheduledDate ← interview.scheduledAt (من الـ API) | |
| // remaining ← محسوب تلقائياً من الوقت الحالي | |
| // ─────────────────────────────────────────────── */} | |
| // <div className="iv-slot-card"> | |
| // <div className="iv-slot-icon"> | |
| // <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"> | |
| // <rect x="3" y="4" width="18" height="18" rx="3"/> | |
| // <path d="M16 2v4M8 2v4M3 10h18"/> | |
| // </svg> | |
| // </div> | |
| // <div className="iv-slot-info"> | |
| // <div className="iv-slot-date"> | |
| // {isAr ? fmtDateAr(scheduledDate) : fmtDateEn(scheduledDate)} | |
| // </div> | |
| // <div className="iv-slot-time"> | |
| // {isAr ? fmtTimeAr(scheduledDate) : fmtTimeEn(scheduledDate)} | |
| // </div> | |
| // {remaining && ( | |
| // <div className="iv-slot-badge">{remaining}</div> | |
| // )} | |
| // </div> | |
| // </div> | |
| // <div className="iv-pills"> | |
| // {INFO_PILLS.map((p, i) => ( | |
| // <div className="iv-pill" key={i}> | |
| // <span className="iv-pill-icon">{p.icon}</span> | |
| // <span className="iv-pill-val">{isAr ? p.ar : p.en}</span> | |
| // <span className="iv-pill-lbl">{isAr ? p.lAr : p.lEn}</span> | |
| // </div> | |
| // ))} | |
| // </div> | |
| // {/* ─────────────────────────────────────────────── | |
| // Join button | |
| // - لو joinable=true → زر مفعّل بيفتح interview.meetingLink (من الـ API) | |
| // - لو joinable=false → شكل مقفول | |
| // ─────────────────────────────────────────────── */} | |
| // {status === 'joinable' ? ( | |
| // <a | |
| // href={interview?.meetingLink ?? '#'} | |
| // target="_blank" | |
| // rel="noopener noreferrer" | |
| // className="iv-join-btn" | |
| // > | |
| // <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="18" height="18"> | |
| // <path d="M15 10l4.553-2.069A1 1 0 0121 8.847v6.306a1 1 0 01-1.447.894L15 14M3 8a2 2 0 012-2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V8z"/> | |
| // </svg> | |
| // {isAr ? 'انضم إلى المقابلة الآن' : 'Join Interview Now'} | |
| // </a> | |
| // ) : ( | |
| // <div className="iv-locked-btn"> | |
| // <div className="iv-lock-wrap"> | |
| // <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="18" height="18"> | |
| // <rect x="3" y="11" width="18" height="11" rx="2"/> | |
| // <path d="M7 11V7a5 5 0 0110 0v4"/> | |
| // </svg> | |
| // </div> | |
| // <div> | |
| // <div className="iv-locked-title"> | |
| // {isAr ? 'سيُفتح الرابط قبل المقابلة بـ 5 دقائق' : 'Link opens 5 minutes before interview'} | |
| // </div> | |
| // <div className="iv-locked-sub"> | |
| // {isAr ? 'يُرجى الانتظار حتى يحين الموعد' : 'Please wait until the scheduled time'} | |
| // </div> | |
| // </div> | |
| // </div> | |
| // )} | |
| // <div className="iv-notice"> | |
| // <div className="iv-notice-icon"> | |
| // <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="15" height="15"> | |
| // <circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/> | |
| // </svg> | |
| // </div> | |
| // <p> | |
| // {isAr | |
| // ? 'إذا واجهت أي مشكلة في الدخول تواصل معنا عبر support@platform.com' | |
| // : 'If you have any issues joining, contact us at support@platform.com'} | |
| // </p> | |
| // </div> | |
| // </div> | |
| // )} | |
| // {/* ══════════════ COMPLETED ══════════════ */} | |
| // {status === 'completed' && ( | |
| // <div className="iv-center"> | |
| // <div className="iv-check-wrap"> | |
| // <svg viewBox="0 0 64 64" fill="none"> | |
| // <circle cx="32" cy="32" r="30" stroke="#16a34a" strokeWidth="2" | |
| // strokeDasharray="190" className="iv-ring" /> | |
| // <path d="M20 33l8 8 16-18" stroke="#16a34a" strokeWidth="3" | |
| // strokeLinecap="round" strokeLinejoin="round" | |
| // strokeDasharray="40" className="iv-check" /> | |
| // </svg> | |
| // </div> | |
| // <h1 className="iv-h1"> | |
| // {isAr ? 'تمت المقابلة بنجاح' : 'Interview Completed'} | |
| // </h1> | |
| // <p className="iv-sub"> | |
| // {isAr | |
| // ? 'شكراً لك على وقتك. سيتم مراجعة نتيجة المقابلة وإخطارك قريباً.' | |
| // : "Thank you for your time. We'll review the interview results and notify you soon."} | |
| // </p> | |
| // <button className="iv-primary-btn" onClick={() => navigate('/sheikh/dashboard')}> | |
| // {isAr ? 'الذهاب إلى لوحة التحكم' : 'Go to Dashboard'} | |
| // </button> | |
| // </div> | |
| // )} | |
| // </div> | |
| // </div> | |
| // </> | |
| // ); | |
| // } | |
| // // ─── Static data ────────────────────────────────────────────── | |
| // const INFO_PILLS = [ | |
| // { icon: '⏱', en: '15–20 min', ar: '١٥–٢٠ دقيقة', lEn: 'Duration', lAr: 'المدة' }, | |
| // { icon: '🎥', en: 'Video Call', ar: 'مكالمة فيديو', lEn: 'Format', lAr: 'الشكل' }, | |
| // { icon: '🌐', en: 'Ar / En', ar: 'عربي / إنجليزي', lEn: 'Language', lAr: 'اللغة' }, | |
| // ]; | |
| // // ─── CSS ────────────────────────────────────────────────────── | |
| // const CSS = ` | |
| // @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;700&family=Noto+Naskh+Arabic:wght@400;500;600;700&family=DM+Sans:wght@300;400;500;600&display=swap'); | |
| // :root { | |
| // --green: #16a34a; | |
| // --green-d: #15803d; | |
| // --green-l: #bbf7d0; | |
| // --green-bg: #f0fdf4; | |
| // --green-md: #dcfce7; | |
| // --gborder: #86efac; | |
| // --border: #e2e8f0; | |
| // --text: #1e293b; | |
| // --muted: #64748b; | |
| // --muted-l: #94a3b8; | |
| // --bg: #f8fafc; | |
| // --white: #ffffff; | |
| // } | |
| // * { box-sizing: border-box; margin: 0; padding: 0; } | |
| // .iv-root { | |
| // min-height: 100vh; | |
| // background: var(--bg); | |
| // display: flex; | |
| // align-items: center; | |
| // justify-content: center; | |
| // padding: 48px 16px; | |
| // position: relative; | |
| // overflow: hidden; | |
| // font-family: 'DM Sans', sans-serif; | |
| // color: var(--text); | |
| // } | |
| // [dir="rtl"] .iv-root { font-family: 'Noto Naskh Arabic', 'DM Sans', serif; } | |
| // /* ── Background ── */ | |
| // .iv-bg-circles { position: absolute; inset: 0; pointer-events: none; overflow: hidden; } | |
| // .iv-circle { position: absolute; border-radius: 50%; filter: blur(80px); } | |
| // .iv-c1 { width:500px; height:500px; background:rgba(22,163,74,.06); top:-150px; left:-150px; } | |
| // .iv-c2 { width:400px; height:400px; background:rgba(22,163,74,.04); bottom:-100px; right:-100px; } | |
| // .iv-c3 { width:300px; height:300px; background:rgba(186,230,253,.12); top:40%; left:60%; } | |
| // .iv-grid-pattern { | |
| // position: absolute; inset: 0; | |
| // background-image: | |
| // linear-gradient(var(--border) 1px, transparent 1px), | |
| // linear-gradient(90deg, var(--border) 1px, transparent 1px); | |
| // background-size: 40px 40px; | |
| // opacity: .45; | |
| // pointer-events: none; | |
| // } | |
| // /* ── Language ── */ | |
| // .iv-lang { position: fixed; top: 20px; right: 20px; z-index: 100; } | |
| // [dir="rtl"] .iv-lang { right: auto; left: 20px; } | |
| // /* ── Card ── */ | |
| // .iv-card { | |
| // position: relative; | |
| // z-index: 2; | |
| // width: 100%; | |
| // max-width: 580px; | |
| // background: var(--white); | |
| // border: 1px solid var(--border); | |
| // border-radius: 24px; | |
| // padding: 48px 44px; | |
| // box-shadow: 0 8px 48px rgba(0,0,0,.09); | |
| // animation: iv-rise .6s cubic-bezier(.22,1,.36,1) both; | |
| // } | |
| // @keyframes iv-rise { | |
| // from { opacity:0; transform: translateY(28px) scale(.98); } | |
| // to { opacity:1; transform: none; } | |
| // } | |
| // /* ── Center block ── */ | |
| // .iv-center { | |
| // display: flex; | |
| // flex-direction: column; | |
| // align-items: center; | |
| // text-align: center; | |
| // gap: 20px; | |
| // } | |
| // /* ── Badge ── */ | |
| // .iv-badge { | |
| // display: inline-block; | |
| // padding: 5px 16px; | |
| // border-radius: 100px; | |
| // font-size: 11px; | |
| // font-weight: 600; | |
| // letter-spacing: .08em; | |
| // text-transform: uppercase; | |
| // background: #f1f5f9; | |
| // color: var(--muted); | |
| // border: 1px solid var(--border); | |
| // } | |
| // [dir="rtl"] .iv-badge { letter-spacing: 0; text-transform: none; font-size: 12px; } | |
| // .iv-badge-green { | |
| // background: var(--green-bg); | |
| // color: var(--green-d); | |
| // border-color: var(--gborder); | |
| // } | |
| // /* ── Ornament ── */ | |
| // .iv-ornament { | |
| // display: flex; | |
| // align-items: center; | |
| // gap: 12px; | |
| // width: 100%; | |
| // max-width: 220px; | |
| // } | |
| // .iv-ornament span { | |
| // flex: 1; | |
| // height: 1px; | |
| // background: linear-gradient(90deg, transparent, var(--gborder), transparent); | |
| // } | |
| // .iv-diamond { | |
| // width: 8px; height: 8px; | |
| // background: var(--green); | |
| // border-radius: 2px; | |
| // transform: rotate(45deg); | |
| // flex-shrink: 0; | |
| // opacity: .55; | |
| // } | |
| // /* ── Typography ── */ | |
| // .iv-h1 { | |
| // font-family: 'Playfair Display', serif; | |
| // font-size: clamp(24px, 4vw, 32px); | |
| // font-weight: 700; | |
| // color: var(--text); | |
| // line-height: 1.2; | |
| // } | |
| // [dir="rtl"] .iv-h1 { | |
| // font-family: 'Noto Naskh Arabic', serif; | |
| // font-size: clamp(22px, 4vw, 28px); | |
| // } | |
| // .iv-sub { | |
| // font-size: 14px; | |
| // color: var(--muted); | |
| // line-height: 1.75; | |
| // max-width: 400px; | |
| // } | |
| // .iv-muted-sm { font-size: 13px; color: var(--muted-l); margin-top: 8px; } | |
| // .iv-footnote { font-size: 12px; color: var(--muted-l); line-height: 1.6; } | |
| // /* ── Info Pills ── */ | |
| // .iv-pills { display: flex; gap: 10px; width: 100%; } | |
| // .iv-pill { | |
| // flex: 1; | |
| // border: 1px solid var(--border); | |
| // border-radius: 14px; | |
| // padding: 14px 8px; | |
| // display: flex; | |
| // flex-direction: column; | |
| // align-items: center; | |
| // gap: 4px; | |
| // background: var(--bg); | |
| // transition: border-color .2s, background .2s; | |
| // } | |
| // .iv-pill:hover { border-color: var(--gborder); background: var(--green-bg); } | |
| // .iv-pill-icon { font-size: 18px; } | |
| // .iv-pill-val { font-size: 12px; font-weight: 600; color: var(--text); text-align: center; } | |
| // .iv-pill-lbl { font-size: 10px; color: var(--muted-l); } | |
| // /* ── Slot card ── */ | |
| // .iv-slot-card { | |
| // display: flex; | |
| // align-items: center; | |
| // gap: 16px; | |
| // width: 100%; | |
| // background: var(--green-bg); | |
| // border: 1.5px solid var(--gborder); | |
| // border-radius: 16px; | |
| // padding: 20px 22px; | |
| // text-align: start; | |
| // animation: iv-pop .3s cubic-bezier(.22,1,.36,1) both; | |
| // } | |
| // @keyframes iv-pop { | |
| // from { opacity:0; transform:scale(.96); } | |
| // to { opacity:1; transform:none; } | |
| // } | |
| // .iv-slot-icon { | |
| // width: 44px; height: 44px; | |
| // border-radius: 12px; | |
| // background: var(--green-md); | |
| // border: 1px solid var(--gborder); | |
| // display: flex; align-items: center; justify-content: center; | |
| // color: var(--green-d); | |
| // flex-shrink: 0; | |
| // } | |
| // .iv-slot-icon svg { width: 20px; height: 20px; } | |
| // .iv-slot-info { flex: 1; min-width: 0; } | |
| // .iv-slot-date { font-size: 15px; font-weight: 600; color: var(--text); } | |
| // .iv-slot-time { font-size: 14px; color: var(--green-d); font-weight: 500; margin-top: 2px; } | |
| // .iv-slot-badge { | |
| // display: inline-block; | |
| // margin-top: 6px; | |
| // padding: 2px 10px; | |
| // border-radius: 100px; | |
| // font-size: 11px; | |
| // color: var(--muted); | |
| // background: var(--white); | |
| // border: 1px solid var(--border); | |
| // } | |
| // /* ── Join button (active) ── */ | |
| // .iv-join-btn { | |
| // display: flex; | |
| // align-items: center; | |
| // justify-content: center; | |
| // gap: 10px; | |
| // width: 100%; | |
| // padding: 15px 24px; | |
| // border-radius: 14px; | |
| // background: linear-gradient(135deg, var(--green-d), var(--green)); | |
| // color: #fff; | |
| // font-size: 15px; | |
| // font-weight: 600; | |
| // text-decoration: none; | |
| // font-family: inherit; | |
| // border: none; | |
| // cursor: pointer; | |
| // transition: all .22s; | |
| // box-shadow: 0 4px 20px rgba(22,163,74,.28); | |
| // animation: iv-glow 2s ease-in-out infinite; | |
| // } | |
| // .iv-join-btn:hover { | |
| // transform: translateY(-2px); | |
| // box-shadow: 0 8px 30px rgba(22,163,74,.4); | |
| // } | |
| // @keyframes iv-glow { | |
| // 0%,100% { box-shadow: 0 4px 20px rgba(22,163,74,.28), 0 0 0 0 rgba(22,163,74,.15); } | |
| // 50% { box-shadow: 0 4px 20px rgba(22,163,74,.28), 0 0 0 8px rgba(22,163,74,0); } | |
| // } | |
| // /* ── Locked button ── */ | |
| // .iv-locked-btn { | |
| // display: flex; | |
| // align-items: center; | |
| // gap: 14px; | |
| // width: 100%; | |
| // padding: 16px 20px; | |
| // border-radius: 14px; | |
| // background: var(--bg); | |
| // border: 1.5px dashed var(--border); | |
| // color: var(--muted); | |
| // text-align: start; | |
| // cursor: not-allowed; | |
| // } | |
| // .iv-lock-wrap { | |
| // width: 40px; height: 40px; | |
| // border-radius: 10px; | |
| // background: var(--white); | |
| // border: 1px solid var(--border); | |
| // display: flex; align-items: center; justify-content: center; | |
| // color: var(--muted-l); | |
| // flex-shrink: 0; | |
| // } | |
| // .iv-locked-title { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 2px; } | |
| // .iv-locked-sub { font-size: 12px; color: var(--muted-l); } | |
| // /* ── Primary button ── */ | |
| // .iv-primary-btn { | |
| // display: inline-flex; | |
| // align-items: center; | |
| // justify-content: center; | |
| // width: 100%; | |
| // padding: 14px 24px; | |
| // border-radius: 14px; | |
| // background: linear-gradient(135deg, var(--green-d), var(--green)); | |
| // color: #fff; | |
| // font-size: 15px; | |
| // font-weight: 600; | |
| // border: none; | |
| // cursor: pointer; | |
| // font-family: inherit; | |
| // transition: all .22s; | |
| // box-shadow: 0 4px 20px rgba(22,163,74,.25); | |
| // } | |
| // .iv-primary-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 28px rgba(22,163,74,.35); } | |
| // /* ── Notice ── */ | |
| // .iv-notice { | |
| // display: flex; | |
| // gap: 10px; | |
| // align-items: flex-start; | |
| // width: 100%; | |
| // padding: 14px 16px; | |
| // border-radius: 12px; | |
| // background: var(--bg); | |
| // border: 1px solid var(--border); | |
| // text-align: start; | |
| // } | |
| // .iv-notice-icon { color: var(--muted-l); flex-shrink: 0; margin-top: 2px; } | |
| // .iv-notice p { font-size: 13px; color: var(--muted); line-height: 1.7; } | |
| // /* ── Waiting box ── */ | |
| // .iv-waiting-box { | |
| // display: flex; | |
| // flex-direction: column; | |
| // align-items: center; | |
| // gap: 12px; | |
| // padding: 24px 32px; | |
| // width: 100%; | |
| // background: var(--green-bg); | |
| // border: 1px dashed var(--gborder); | |
| // border-radius: 16px; | |
| // } | |
| // .iv-dots { display: flex; gap: 6px; align-items: center; } | |
| // .iv-dot { | |
| // width: 8px; height: 8px; | |
| // border-radius: 50%; | |
| // background: var(--green); | |
| // opacity: .35; | |
| // animation: iv-bounce 1.3s ease-in-out infinite; | |
| // } | |
| // .iv-dot-1 { animation-delay: 0s; } | |
| // .iv-dot-2 { animation-delay: .2s; } | |
| // .iv-dot-3 { animation-delay: .4s; } | |
| // @keyframes iv-bounce { | |
| // 0%,80%,100% { transform: scale(.75); opacity: .35; } | |
| // 40% { transform: scale(1.25); opacity: 1; } | |
| // } | |
| // .iv-waiting-txt { font-size: 13px; color: var(--green-d); font-weight: 500; } | |
| // /* ── Spinner ── */ | |
| // .iv-spinner { | |
| // width: 36px; height: 36px; | |
| // border: 3px solid var(--border); | |
| // border-top-color: var(--green); | |
| // border-radius: 50%; | |
| // animation: iv-spin .7s linear infinite; | |
| // } | |
| // @keyframes iv-spin { to { transform: rotate(360deg); } } | |
| // /* ── Check animation ── */ | |
| // .iv-check-wrap { | |
| // width: 80px; height: 80px; | |
| // animation: iv-scale-in .5s cubic-bezier(.22,1,.36,1) .1s both; | |
| // } | |
| // @keyframes iv-scale-in { | |
| // from { opacity:0; transform: scale(.4) rotate(-20deg); } | |
| // to { opacity:1; transform: none; } | |
| // } | |
| // .iv-ring { | |
| // animation: iv-draw-ring .6s linear .2s both; | |
| // } | |
| // @keyframes iv-draw-ring { | |
| // from { stroke-dashoffset: 190; } | |
| // to { stroke-dashoffset: 0; } | |
| // } | |
| // .iv-check { | |
| // animation: iv-draw-check .4s ease .7s both; | |
| // } | |
| // @keyframes iv-draw-check { | |
| // from { stroke-dashoffset: 40; } | |
| // to { stroke-dashoffset: 0; } | |
| // } | |
| // @media (max-width: 480px) { | |
| // .iv-card { padding: 28px 20px; } | |
| // .iv-pills { gap: 6px; } | |
| // .iv-pill { padding: 10px 4px; } | |
| // .iv-pill-val { font-size: 11px; } | |
| // .iv-slot-card { padding: 14px 16px; gap: 12px; } | |
| // } | |
| // `; | |
| import { useState, useEffect } from 'react'; | |
| import { useNavigate, useLocation } from 'react-router-dom'; | |
| import { useLanguage } from '../../lib/languageContext'; | |
| import { LanguageSwitcher } from '../LanguageSwitcher'; | |
| import { useAuth } from '../../lib/authContext'; | |
| import { getApiBaseUrl } from '../../lib/api/config'; | |
| // ─── Types ──────────────────────────────────────────────────── | |
| type InterviewStatus = | |
| | 'loading' // جاري جلب البيانات | |
| | 'no_interview' // لا يوجد موعد بعد | |
| | 'scheduled' // موعد محدد – الزر معطل | |
| | 'joinable' // حان وقت المقابلة – الزر مفعّل | |
| | 'completed' // انتهت المقابلة | |
| | 'rejected' // تم الرفض | |
| | 'error'; // خطأ | |
| interface InterviewData { | |
| scheduledAt: string; // مثلاً "2026-03-25T14:00:00" | |
| meetingLink: string; // رابط الاجتماع | |
| status: 'PENDING' | 'COMPLETED' | 'CANCELLED'; | |
| } | |
| // ─── Date helpers (نفس الدوال السابقة) ──────────────────────── | |
| const AR_DAYS = ['الأحد','الاثنين','الثلاثاء','الأربعاء','الخميس','الجمعة','السبت']; | |
| const AR_MONTHS = ['يناير','فبراير','مارس','أبريل','مايو','يونيو','يوليو','أغسطس','سبتمبر','أكتوبر','نوفمبر','ديسمبر']; | |
| function fmtDateAr(d: Date) { | |
| return `${AR_DAYS[d.getDay()]}، ${d.getDate()} ${AR_MONTHS[d.getMonth()]} ${d.getFullYear()}`; | |
| } | |
| function fmtDateEn(d: Date) { | |
| return d.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); | |
| } | |
| function fmtTimeAr(d: Date) { | |
| const h = d.getHours(); | |
| const m = d.getMinutes().toString().padStart(2, '0'); | |
| const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h; | |
| return `${h12}:${m} ${h < 12 ? 'صباحاً' : 'مساءً'}`; | |
| } | |
| function fmtTimeEn(d: Date) { | |
| return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); | |
| } | |
| // حساب المدة المتبقية | |
| function timeUntil(d: Date, isAr: boolean): string { | |
| const diff = d.getTime() - Date.now(); | |
| if (diff <= 0) return ''; | |
| const days = Math.floor(diff / 86400000); | |
| const hours = Math.floor((diff % 86400000) / 3600000); | |
| const mins = Math.floor((diff % 3600000) / 60000); | |
| if (isAr) { | |
| if (days > 0) return `بعد ${days} يوم${days === 1 ? '' : 'اً'}`; | |
| if (hours > 0) return `بعد ${hours} ساعة`; | |
| return `بعد ${mins} دقيقة`; | |
| } | |
| if (days > 0) return `in ${days} day${days > 1 ? 's' : ''}`; | |
| if (hours > 0) return `in ${hours} hour${hours > 1 ? 's' : ''}`; | |
| return `in ${mins} minute${mins > 1 ? 's' : ''}`; | |
| } | |
| // ─── API calls (مكانها المناسب – يمكن استبدالها بطلبات حقيقية) ─ | |
| const BASE_URL = getApiBaseUrl(); | |
| /** | |
| * 🔽 هنا يتم جلب بيانات المقابلة من الخادم. | |
| * إذا لم يوجد موعد (404) نعيد null. | |
| * في حالة الخطأ نرمي استثناء. | |
| */ | |
| async function fetchInterview(): Promise<InterviewData | null> { | |
| const token = localStorage.getItem('authToken') ?? ''; | |
| const headers = { | |
| 'Content-Type': 'application/json', | |
| ...(token ? { Authorization: `Bearer ${token}` } : {}), | |
| }; | |
| // Try the UNDER_REVIEW endpoint first (sheikh/interview), then fall back to pending | |
| for (const url of [`${BASE_URL}/api/sheikh/interview`, `${BASE_URL}/api/sheikh/pending`]) { | |
| const res = await fetch(url, { headers }); | |
| if (res.status === 404 || res.status === 403) continue; | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const text = await res.text(); | |
| if (!text.trim()) continue; | |
| const raw = JSON.parse(text); | |
| // /api/sheikh/interview returns { scheduledAt, meetingLink, status } | |
| if (raw.scheduledAt) { | |
| return { | |
| scheduledAt: raw.scheduledAt, | |
| meetingLink: raw.meetingLink ?? '', | |
| status: raw.status ?? 'PENDING', | |
| }; | |
| } | |
| // /api/sheikh/pending returns { date, time, message } | |
| if (raw.date && raw.time) { | |
| return { | |
| scheduledAt: `${raw.date}T${raw.time}`, | |
| meetingLink: '', | |
| status: 'PENDING', | |
| }; | |
| } | |
| } | |
| return null; | |
| } | |
| // ─── Main Component ─────────────────────────────────────────── | |
| export function InterviewSchedule() { | |
| const { language } = useLanguage(); | |
| const navigate = useNavigate(); | |
| const location = useLocation(); | |
| const { user } = useAuth(); | |
| const isAr = language === 'ar'; | |
| const [status, setStatus] = useState<InterviewStatus>('loading'); | |
| const [interview, setInterview] = useState<InterviewData | null>(null); | |
| const [now, setNow] = useState(new Date()); | |
| const [error, setError] = useState(''); | |
| const [rejectionReason, setRejectionReason] = useState(''); | |
| // ── Check if navigated here from session with a decision state ─ | |
| useEffect(() => { | |
| const state = location.state as { rejected?: boolean; reason?: string } | null; | |
| if (state?.rejected) { | |
| setRejectionReason(state.reason ?? ''); | |
| setStatus('rejected'); | |
| } | |
| }, [location.state]); | |
| // ── On mount: check approval status first, then load interview ─ | |
| useEffect(() => { | |
| const token = localStorage.getItem('authToken') ?? ''; | |
| const checkDecision = async (): Promise<'APPROVED' | 'REJECTED' | 'OTHER'> => { | |
| if (!token) return 'OTHER'; | |
| try { | |
| const res = await fetch(`${BASE_URL}/api/auth/verify-profile`, { | |
| headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, | |
| }); | |
| if (!res.ok) return 'OTHER'; | |
| const data = await res.json(); | |
| const approvalStatus: string = data.sheikhApprovalStatus ?? ''; | |
| if (approvalStatus === 'APPROVED') return 'APPROVED'; | |
| if (approvalStatus === 'REJECTED') { | |
| setRejectionReason(data.rejectionReason ?? ''); | |
| return 'REJECTED'; | |
| } | |
| } catch { /* silent */ } | |
| return 'OTHER'; | |
| }; | |
| const loadInterview = () => { | |
| fetchInterview() | |
| .then(data => { | |
| if (!data) { | |
| setStatus(prev => prev === 'rejected' ? prev : 'no_interview'); | |
| return; | |
| } | |
| setInterview(data); | |
| const scheduled = new Date(data.scheduledAt); | |
| if (data.status === 'COMPLETED') { setStatus('completed'); return; } | |
| if (data.status === 'CANCELLED') { setStatus('no_interview'); return; } | |
| if (scheduled.getTime() <= Date.now()) { setStatus('joinable'); return; } | |
| setStatus('scheduled'); | |
| }) | |
| .catch(e => { setError(e.message); setStatus('error'); }); | |
| }; | |
| // Run approval check first — only show interview UI if not decided | |
| checkDecision().then(decision => { | |
| if (decision === 'APPROVED') { | |
| navigate('/sheikh/dashboard', { replace: true }); | |
| return; | |
| } | |
| if (decision === 'REJECTED') { | |
| setStatus('rejected'); | |
| return; | |
| } | |
| // Not decided yet — load the interview schedule | |
| loadInterview(); | |
| const timer = setInterval(loadInterview, 30_000); | |
| return () => clearInterval(timer); | |
| }); | |
| }, []); // eslint-disable-line react-hooks/exhaustive-deps | |
| // ── Poll verify-profile every 15s for admin decision ─────── | |
| useEffect(() => { | |
| if (status === 'rejected') return; | |
| const token = localStorage.getItem('authToken') ?? ''; | |
| if (!token) return; | |
| const checkDecision = async () => { | |
| try { | |
| const res = await fetch(`${BASE_URL}/api/auth/verify-profile`, { | |
| headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, | |
| }); | |
| if (!res.ok) return; | |
| const data = await res.json(); | |
| const approvalStatus: string = data.sheikhApprovalStatus ?? ''; | |
| if (approvalStatus === 'APPROVED') { | |
| navigate('/sheikh/dashboard', { replace: true }); | |
| } else if (approvalStatus === 'REJECTED') { | |
| setRejectionReason(data.rejectionReason ?? ''); | |
| setStatus('rejected'); | |
| } | |
| } catch { /* silent */ } | |
| }; | |
| const timer = setInterval(checkDecision, 15_000); | |
| return () => clearInterval(timer); | |
| }, [navigate, status]); | |
| const scheduledDate = interview ? new Date(interview.scheduledAt) : null; | |
| const isJoinable = status === 'joinable'; | |
| const remaining = scheduledDate && status === 'scheduled' | |
| ? timeUntil(scheduledDate, isAr) : ''; | |
| // ── Tick every 30s to update countdown and flip to joinable ─ | |
| useEffect(() => { | |
| const timer = setInterval(() => { | |
| setNow(new Date()); | |
| if (interview && status === 'scheduled') { | |
| const scheduled = new Date(interview.scheduledAt); | |
| if (scheduled.getTime() - Date.now() <= 5 * 60 * 1000) { | |
| setStatus('joinable'); | |
| } | |
| } | |
| }, 30_000); | |
| return () => clearInterval(timer); | |
| }, [interview, status]); | |
| // ─── العرض ───────────────────────────────────────────────── | |
| return ( | |
| <> | |
| <style>{CSS}</style> | |
| <div className="iv-root" dir={isAr ? 'rtl' : 'ltr'}> | |
| {/* خلفية */} | |
| <div className="iv-bg-circles" aria-hidden="true"> | |
| <div className="iv-circle iv-c1" /> | |
| <div className="iv-circle iv-c2" /> | |
| <div className="iv-circle iv-c3" /> | |
| </div> | |
| <div className="iv-grid-pattern" aria-hidden="true" /> | |
| {/* مفتاح اللغة */} | |
| <div className="iv-lang"><LanguageSwitcher /></div> | |
| {/* البطاقة الرئيسية */} | |
| <div className="iv-card"> | |
| {/* ── حالة التحميل ── */} | |
| {status === 'loading' && ( | |
| <div className="iv-center-block"> | |
| <div className="iv-spinner" /> | |
| <p className="iv-muted">{isAr ? 'جاري تحميل بيانات المقابلة...' : 'Loading interview details...'}</p> | |
| </div> | |
| )} | |
| {/* ── خطأ ── */} | |
| {status === 'error' && ( | |
| <div className="iv-center-block"> | |
| <div className="iv-icon-wrap iv-icon-warn"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> | |
| <circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/> | |
| </svg> | |
| </div> | |
| <h2 className="iv-h2">{isAr ? 'حدث خطأ' : 'Something went wrong'}</h2> | |
| <p className="iv-muted">{error}</p> | |
| <button className="iv-btn-sec" onClick={() => window.location.reload()}> | |
| {isAr ? 'إعادة المحاولة' : 'Try again'} | |
| </button> | |
| </div> | |
| )} | |
| {/* ── لا يوجد موعد بعد ── */} | |
| {status === 'no_interview' && ( | |
| <div className="iv-center-block"> | |
| <div className="iv-badge">{isAr ? 'المرحلة الأخيرة' : 'Final Step'}</div> | |
| <div className="iv-ornament"><span/><div className="iv-diamond"/><span/></div> | |
| <h1 className="iv-h1">{isAr ? 'مقابلة التحقق' : 'Verification Interview'}</h1> | |
| <p className="iv-sub"> | |
| {isAr | |
| ? 'أنت على وشك إتمام انضمامك للمنصة. فريقنا سيحدد لك موعد المقابلة وسيصلك إشعار فور تحديده.' | |
| : "You're almost there. Our team will schedule your verification interview and notify you as soon as it's confirmed."} | |
| </p> | |
| {/* نقاط معلومات */} | |
| <div className="iv-pills"> | |
| {[ | |
| { icon: '⏱', en: '15–20 min', ar: '١٥–٢٠ دقيقة', lEn: 'Duration', lAr: 'المدة' }, | |
| { icon: '🎥', en: 'Video Call', ar: 'مكالمة فيديو', lEn: 'Format', lAr: 'الشكل' }, | |
| { icon: '🌐', en: 'Ar / En', ar: 'عربي / إنجليزي', lEn: 'Language', lAr: 'اللغة' }, | |
| ].map((p, i) => ( | |
| <div className="iv-pill" key={i}> | |
| <span className="iv-pill-icon">{p.icon}</span> | |
| <span className="iv-pill-val">{isAr ? p.ar : p.en}</span> | |
| <span className="iv-pill-lbl">{isAr ? p.lAr : p.lEn}</span> | |
| </div> | |
| ))} | |
| </div> | |
| {/* حالة الانتظار */} | |
| <div className="iv-waiting-box"> | |
| <div className="iv-waiting-dot-wrap"> | |
| <span className="iv-dot iv-dot-1"/><span className="iv-dot iv-dot-2"/><span className="iv-dot iv-dot-3"/> | |
| </div> | |
| <p className="iv-waiting-txt"> | |
| {isAr | |
| ? 'في انتظار تحديد الموعد من قِبل الفريق...' | |
| : 'Waiting for the team to schedule your slot...'} | |
| </p> | |
| </div> | |
| <p className="iv-footnote"> | |
| {isAr | |
| ? 'ستصلك رسالة بريد إلكتروني فور تحديد الموعد' | |
| : "You'll receive an email once your slot is confirmed"} | |
| </p> | |
| </div> | |
| )} | |
| {/* ── موعد محدد (scheduled أو joinable) ── */} | |
| {(status === 'scheduled' || status === 'joinable') && scheduledDate && ( | |
| <div className="iv-center-block"> | |
| <div className="iv-badge iv-badge-green"> | |
| {isAr ? 'تم تحديد موعدك' : 'Interview Scheduled'} | |
| </div> | |
| <div className="iv-ornament"><span/><div className="iv-diamond"/><span/></div> | |
| <h1 className="iv-h1">{isAr ? 'مقابلة التحقق' : 'Verification Interview'}</h1> | |
| <p className="iv-sub"> | |
| {isAr | |
| ? 'تم تحديد موعد مقابلتك. يُرجى الحضور في الوقت المحدد واستعداد كاميرتك وميكروفونك.' | |
| : 'Your interview has been scheduled. Please be ready on time with your camera and microphone.'} | |
| </p> | |
| {/* بطاقة الموعد */} | |
| <div className="iv-slot-card"> | |
| <div className="iv-slot-icon"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"> | |
| <rect x="3" y="4" width="18" height="18" rx="3"/> | |
| <path d="M16 2v4M8 2v4M3 10h18"/> | |
| </svg> | |
| </div> | |
| <div className="iv-slot-info"> | |
| <div className="iv-slot-date"> | |
| {isAr ? fmtDateAr(scheduledDate) : fmtDateEn(scheduledDate)} | |
| </div> | |
| <div className="iv-slot-time"> | |
| {isAr ? fmtTimeAr(scheduledDate) : fmtTimeEn(scheduledDate)} | |
| </div> | |
| {remaining && ( | |
| <div className="iv-slot-remaining">{remaining}</div> | |
| )} | |
| </div> | |
| </div> | |
| {/* نقاط المعلومات */} | |
| <div className="iv-pills"> | |
| {[ | |
| { icon: '⏱', en: '15–20 min', ar: '١٥–٢٠ دقيقة', lEn: 'Duration', lAr: 'المدة' }, | |
| { icon: '🎥', en: 'Video Call', ar: 'مكالمة فيديو', lEn: 'Format', lAr: 'الشكل' }, | |
| { icon: '🌐', en: 'Ar / En', ar: 'عربي / إنجليزي', lEn: 'Language', lAr: 'اللغة' }, | |
| ].map((p, i) => ( | |
| <div className="iv-pill" key={i}> | |
| <span className="iv-pill-icon">{p.icon}</span> | |
| <span className="iv-pill-val">{isAr ? p.ar : p.en}</span> | |
| <span className="iv-pill-lbl">{isAr ? p.lAr : p.lEn}</span> | |
| </div> | |
| ))} | |
| </div> | |
| {/* زر المقابلة – مفعّل فقط عندما يكون joinable */} | |
| {isJoinable ? ( | |
| <button | |
| onClick={() => navigate(`/sheikh/interview-session/${user?.id}`)} | |
| className="iv-btn iv-btn-join" | |
| > | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="iv-btn-icon"> | |
| <path d="M15 10l4.553-2.069A1 1 0 0121 8.847v6.306a1 1 0 01-1.447.894L15 14M3 8a2 2 0 012-2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V8z"/> | |
| </svg> | |
| {isAr ? 'انضم إلى المقابلة الآن' : 'Join Interview Now'} | |
| </button> | |
| ) : ( | |
| <div className="iv-btn-disabled"> | |
| <div className="iv-lock-icon"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> | |
| <rect x="3" y="11" width="18" height="11" rx="2"/> | |
| <path d="M7 11V7a5 5 0 0110 0v4"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <div className="iv-btn-disabled-title"> | |
| {isAr ? 'سيُفتح الرابط قبل المقابلة بـ 5 دقائق' : 'Link opens 5 minutes before interview'} | |
| </div> | |
| <div className="iv-btn-disabled-sub"> | |
| {isAr ? 'يُرجى الانتظار حتى يحين الموعد' : 'Please wait until the scheduled time'} | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* ملاحظة */} | |
| <div className="iv-notice"> | |
| <span className="iv-notice-icon"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> | |
| <circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/> | |
| </svg> | |
| </span> | |
| <p> | |
| {isAr | |
| ? 'إذا واجهت أي مشكلة في الدخول تواصل معنا عبر support@platform.com' | |
| : 'If you have any issues joining, contact us at support@platform.com'} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| {/* ── انتهت المقابلة ── */} | |
| {status === 'completed' && ( | |
| <div className="iv-center-block"> | |
| <div className="iv-check-anim"> | |
| <svg viewBox="0 0 64 64" fill="none"> | |
| <circle cx="32" cy="32" r="30" stroke="#16a34a" strokeWidth="2" strokeDasharray="190" className="iv-ring"/> | |
| <path d="M20 33l8 8 16-18" stroke="#16a34a" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" strokeDasharray="40" className="iv-check"/> | |
| </svg> | |
| </div> | |
| <h1 className="iv-h1">{isAr ? 'تمت المقابلة بنجاح' : 'Interview Completed'}</h1> | |
| <p className="iv-sub"> | |
| {isAr | |
| ? 'شكراً لك على وقتك. سيتم مراجعة نتيجة المقابلة وإخطارك قريباً.' | |
| : "Thank you for your time. We'll review the interview results and notify you soon."} | |
| </p> | |
| <button className="iv-btn" onClick={() => navigate('/sheikh/dashboard')}> | |
| {isAr ? 'الذهاب إلى لوحة التحكم' : 'Go to Dashboard'} | |
| </button> | |
| </div> | |
| )} | |
| {/* ── تم الرفض ── */} | |
| {status === 'rejected' && ( | |
| <div className="iv-center-block"> | |
| <div className="iv-icon-wrap iv-icon-reject"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> | |
| <circle cx="12" cy="12" r="10"/> | |
| <path d="M15 9l-6 6M9 9l6 6"/> | |
| </svg> | |
| </div> | |
| <h1 className="iv-h1"> | |
| {isAr ? 'لم يتم قبول طلبك' : 'Application Not Approved'} | |
| </h1> | |
| <p className="iv-sub"> | |
| {isAr | |
| ? 'نأسف لإبلاغك بأن طلبك لم يتم قبوله في الوقت الحالي.' | |
| : "We're sorry to inform you that your application was not approved at this time."} | |
| </p> | |
| {rejectionReason && ( | |
| <div className="iv-rejection-box"> | |
| <p className="iv-rejection-label"> | |
| {isAr ? 'سبب الرفض:' : 'Reason:'} | |
| </p> | |
| <p className="iv-rejection-reason">{rejectionReason}</p> | |
| </div> | |
| )} | |
| <div className="iv-notice"> | |
| <span className="iv-notice-icon"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> | |
| <circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/> | |
| </svg> | |
| </span> | |
| <p> | |
| {isAr | |
| ? 'إذا كنت تعتقد أن هذا خطأ أو تريد إعادة التقديم، تواصل معنا عبر support@platform.com' | |
| : 'If you believe this is a mistake or wish to reapply, contact us at support@platform.com'} | |
| </p> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| } | |
| // ─── التصميم (نفس السابق بألوان فاتحة) ──────────────────────── | |
| const CSS = ` | |
| @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;700&family=Noto+Naskh+Arabic:wght@400;500;600;700&family=DM+Sans:wght@300;400;500;600&display=swap'); | |
| :root { | |
| --green: #16a34a; | |
| --green-d: #15803d; | |
| --green-l: #bbf7d0; | |
| --green-bg: #f0fdf4; | |
| --green-mid: #dcfce7; | |
| --border: #e2e8f0; | |
| --border-g: #86efac; | |
| --text: #1e293b; | |
| --muted: #64748b; | |
| --muted-l: #94a3b8; | |
| --bg: #f8fafc; | |
| --white: #ffffff; | |
| --shadow: 0 4px 24px rgba(0,0,0,.06); | |
| --shadow-lg: 0 8px 48px rgba(0,0,0,.1); | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| .iv-root { | |
| min-height: 100vh; | |
| background: var(--bg); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 48px 16px; | |
| position: relative; | |
| overflow: hidden; | |
| font-family: 'DM Sans', sans-serif; | |
| color: var(--text); | |
| } | |
| [dir="rtl"] .iv-root { font-family: 'Noto Naskh Arabic', 'DM Sans', serif; } | |
| /* Background decorations */ | |
| .iv-bg-circles { position: absolute; inset: 0; pointer-events: none; overflow: hidden; } | |
| .iv-circle { | |
| position: absolute; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| } | |
| .iv-c1 { width: 500px; height: 500px; background: rgba(22,163,74,.06); top: -150px; left: -150px; } | |
| .iv-c2 { width: 400px; height: 400px; background: rgba(22,163,74,.04); bottom: -100px; right: -100px; } | |
| .iv-c3 { width: 300px; height: 300px; background: rgba(186,230,253,.15); top: 40%; left: 60%; } | |
| .iv-grid-pattern { | |
| position: absolute; | |
| inset: 0; | |
| background-image: | |
| linear-gradient(var(--border) 1px, transparent 1px), | |
| linear-gradient(90deg, var(--border) 1px, transparent 1px); | |
| background-size: 40px 40px; | |
| opacity: .4; | |
| pointer-events: none; | |
| } | |
| /* Language */ | |
| .iv-lang { position: fixed; top: 20px; right: 20px; z-index: 100; } | |
| [dir="rtl"] .iv-lang { right: auto; left: 20px; } | |
| /* Card */ | |
| .iv-card { | |
| position: relative; | |
| z-index: 2; | |
| width: 100%; | |
| max-width: 600px; | |
| background: var(--white); | |
| border: 1px solid var(--border); | |
| border-radius: 24px; | |
| padding: 48px 44px; | |
| box-shadow: var(--shadow-lg); | |
| animation: iv-rise .6s cubic-bezier(.22,1,.36,1) both; | |
| } | |
| @keyframes iv-rise { | |
| from { opacity:0; transform: translateY(28px) scale(.98); } | |
| to { opacity:1; transform: none; } | |
| } | |
| /* Center block */ | |
| .iv-center-block { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| text-align: center; | |
| gap: 18px; | |
| } | |
| /* Badge */ | |
| .iv-badge { | |
| display: inline-block; | |
| padding: 5px 16px; | |
| border-radius: 100px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: .08em; | |
| text-transform: uppercase; | |
| background: var(--green-bg); | |
| color: var(--green-d); | |
| border: 1px solid var(--border-g); | |
| } | |
| [dir="rtl"] .iv-badge { letter-spacing: 0; text-transform: none; font-size: 12px; } | |
| .iv-badge-green { background: var(--green-bg); } | |
| /* Ornament */ | |
| .iv-ornament { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| width: 100%; | |
| max-width: 240px; | |
| } | |
| .iv-ornament span { | |
| flex: 1; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--border-g), transparent); | |
| } | |
| .iv-diamond { | |
| width: 8px; height: 8px; | |
| background: var(--green); | |
| border-radius: 2px; | |
| transform: rotate(45deg); | |
| flex-shrink: 0; | |
| opacity: .6; | |
| } | |
| /* Headings */ | |
| .iv-h1 { | |
| font-family: 'Playfair Display', serif; | |
| font-size: clamp(24px, 4vw, 34px); | |
| font-weight: 700; | |
| color: var(--text); | |
| line-height: 1.2; | |
| } | |
| [dir="rtl"] .iv-h1 { | |
| font-family: 'Noto Naskh Arabic', serif; | |
| font-size: clamp(22px, 4vw, 30px); | |
| } | |
| .iv-h2 { | |
| font-size: 20px; | |
| font-weight: 600; | |
| color: var(--text); | |
| } | |
| .iv-sub { | |
| font-size: 14px; | |
| color: var(--muted); | |
| line-height: 1.75; | |
| max-width: 420px; | |
| } | |
| .iv-muted { | |
| font-size: 14px; | |
| color: var(--muted-l); | |
| text-align: center; | |
| } | |
| /* Pills */ | |
| .iv-pills { | |
| display: flex; | |
| gap: 10px; | |
| width: 100%; | |
| } | |
| .iv-pill { | |
| flex: 1; | |
| border: 1px solid var(--border); | |
| border-radius: 14px; | |
| padding: 14px 8px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 4px; | |
| background: var(--bg); | |
| transition: border-color .2s, background .2s; | |
| } | |
| .iv-pill:hover { border-color: var(--border-g); background: var(--green-bg); } | |
| .iv-pill-icon { font-size: 18px; } | |
| .iv-pill-val { font-size: 12px; font-weight: 600; color: var(--text); text-align: center; } | |
| .iv-pill-lbl { font-size: 10px; color: var(--muted-l); } | |
| /* Slot card */ | |
| .iv-slot-card { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| width: 100%; | |
| background: var(--green-bg); | |
| border: 1.5px solid var(--border-g); | |
| border-radius: 16px; | |
| padding: 20px 24px; | |
| text-align: start; | |
| animation: iv-pop .3s cubic-bezier(.22,1,.36,1) both; | |
| } | |
| @keyframes iv-pop { | |
| from { opacity:0; transform: scale(.96); } | |
| to { opacity:1; transform: none; } | |
| } | |
| .iv-slot-icon { | |
| width: 44px; height: 44px; | |
| border-radius: 12px; | |
| background: var(--green-mid); | |
| border: 1px solid var(--border-g); | |
| display: flex; align-items: center; justify-content: center; | |
| flex-shrink: 0; | |
| color: var(--green-d); | |
| } | |
| .iv-slot-icon svg { width: 20px; height: 20px; } | |
| .iv-slot-info { flex: 1; min-width: 0; } | |
| .iv-slot-date { | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: var(--text); | |
| } | |
| .iv-slot-time { | |
| font-size: 14px; | |
| color: var(--green-d); | |
| margin-top: 2px; | |
| font-weight: 500; | |
| } | |
| .iv-slot-remaining { | |
| font-size: 12px; | |
| color: var(--muted); | |
| margin-top: 4px; | |
| padding: 2px 8px; | |
| background: var(--white); | |
| border-radius: 100px; | |
| display: inline-block; | |
| border: 1px solid var(--border); | |
| } | |
| /* Join button — active */ | |
| .iv-btn-join { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| width: 100%; | |
| padding: 15px 24px; | |
| border-radius: 14px; | |
| background: linear-gradient(135deg, var(--green-d), var(--green)); | |
| color: #fff; | |
| font-size: 15px; | |
| font-weight: 600; | |
| text-decoration: none; | |
| font-family: inherit; | |
| cursor: pointer; | |
| border: none; | |
| transition: all .22s; | |
| box-shadow: 0 4px 20px rgba(22,163,74,.3); | |
| animation: iv-pulse-border 2s ease-in-out infinite; | |
| } | |
| .iv-btn-join:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 30px rgba(22,163,74,.4); | |
| } | |
| @keyframes iv-pulse-border { | |
| 0%, 100% { box-shadow: 0 4px 20px rgba(22,163,74,.3), 0 0 0 0 rgba(22,163,74,.2); } | |
| 50% { box-shadow: 0 4px 20px rgba(22,163,74,.3), 0 0 0 8px rgba(22,163,74,0); } | |
| } | |
| .iv-btn-icon { width: 18px; height: 18px; flex-shrink: 0; } | |
| /* Join button — disabled */ | |
| .iv-btn-disabled { | |
| display: flex; | |
| align-items: center; | |
| gap: 14px; | |
| width: 100%; | |
| padding: 16px 20px; | |
| border-radius: 14px; | |
| background: var(--bg); | |
| border: 1.5px dashed var(--border); | |
| color: var(--muted); | |
| text-align: start; | |
| cursor: not-allowed; | |
| } | |
| .iv-lock-icon { | |
| width: 40px; height: 40px; | |
| border-radius: 10px; | |
| background: var(--white); | |
| border: 1px solid var(--border); | |
| display: flex; align-items: center; justify-content: center; | |
| flex-shrink: 0; | |
| color: var(--muted-l); | |
| } | |
| .iv-lock-icon svg { width: 18px; height: 18px; } | |
| .iv-btn-disabled-title { | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--muted); | |
| margin-bottom: 2px; | |
| } | |
| .iv-btn-disabled-sub { | |
| font-size: 12px; | |
| color: var(--muted-l); | |
| } | |
| /* General button */ | |
| .iv-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 100%; | |
| padding: 14px 24px; | |
| border-radius: 14px; | |
| background: linear-gradient(135deg, var(--green-d), var(--green)); | |
| color: #fff; | |
| font-size: 15px; | |
| font-weight: 600; | |
| border: none; | |
| cursor: pointer; | |
| font-family: inherit; | |
| transition: all .22s; | |
| box-shadow: 0 4px 20px rgba(22,163,74,.25); | |
| } | |
| .iv-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 28px rgba(22,163,74,.35); } | |
| .iv-btn-sec { | |
| padding: 10px 24px; | |
| border-radius: 10px; | |
| border: 1.5px solid var(--border); | |
| background: var(--white); | |
| color: var(--text); | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| font-family: inherit; | |
| transition: all .2s; | |
| } | |
| .iv-btn-sec:hover { border-color: var(--border-g); color: var(--green-d); } | |
| /* Notice */ | |
| .iv-notice { | |
| display: flex; | |
| gap: 10px; | |
| align-items: flex-start; | |
| width: 100%; | |
| padding: 14px 16px; | |
| border-radius: 12px; | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| text-align: start; | |
| } | |
| .iv-notice-icon { color: var(--muted-l); flex-shrink: 0; margin-top: 2px; } | |
| .iv-notice-icon svg { width: 16px; height: 16px; } | |
| .iv-notice p { font-size: 13px; color: var(--muted); line-height: 1.7; } | |
| /* Rejection state */ | |
| .iv-icon-wrap { | |
| width: 72px; height: 72px; | |
| border-radius: 50%; | |
| display: flex; align-items: center; justify-content: center; | |
| flex-shrink: 0; | |
| } | |
| .iv-icon-wrap svg { width: 32px; height: 32px; } | |
| .iv-icon-reject { | |
| background: #fef2f2; | |
| border: 2px solid #fca5a5; | |
| color: #dc2626; | |
| } | |
| .iv-icon-warn { | |
| background: #fffbeb; | |
| border: 2px solid #fcd34d; | |
| color: #d97706; | |
| } | |
| .iv-rejection-box { | |
| width: 100%; | |
| padding: 16px 20px; | |
| border-radius: 14px; | |
| background: #fef2f2; | |
| border: 1.5px solid #fca5a5; | |
| text-align: start; | |
| } | |
| .iv-rejection-label { | |
| font-size: 11px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: .06em; | |
| color: #dc2626; | |
| margin-bottom: 6px; | |
| } | |
| [dir="rtl"] .iv-rejection-label { letter-spacing: 0; text-transform: none; font-size: 12px; } | |
| .iv-rejection-reason { | |
| font-size: 14px; | |
| color: #7f1d1d; | |
| line-height: 1.65; | |
| } | |
| /* Waiting state */ | |
| .iv-waiting-box { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 24px 32px; | |
| background: var(--green-bg); | |
| border: 1px dashed var(--border-g); | |
| border-radius: 16px; | |
| width: 100%; | |
| } | |
| .iv-waiting-dot-wrap { display: flex; gap: 6px; align-items: center; } | |
| .iv-dot { | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| background: var(--green); | |
| opacity: .4; | |
| animation: iv-bounce 1.2s ease-in-out infinite; | |
| } | |
| .iv-dot-1 { animation-delay: 0s; } | |
| .iv-dot-2 { animation-delay: .2s; } | |
| .iv-dot-3 { animation-delay: .4s; } | |
| @keyframes iv-bounce { | |
| 0%, 80%, 100% { transform: scale(.8); opacity: .4; } | |
| 40% { transform: scale(1.2); opacity: 1; } | |
| } | |
| .iv-waiting-txt { | |
| font-size: 13px; | |
| color: var(--green-d); | |
| font-weight: 500; | |
| } | |
| /* Footnote */ | |
| .iv-footnote { | |
| font-size: 12px; | |
| color: var(--muted-l); | |
| text-align: center; | |
| line-height: 1.6; | |
| } | |
| /* Spinner */ | |
| .iv-spinner { | |
| width: 36px; height: 36px; | |
| border: 3px solid var(--border); | |
| border-top-color: var(--green); | |
| border-radius: 50%; | |
| animation: iv-spin .7s linear infinite; | |
| } | |
| @keyframes iv-spin { to { transform: rotate(360deg); } } | |
| /* Icon wrap */ | |
| .iv-icon-wrap { | |
| width: 64px; height: 64px; | |
| border-radius: 50%; | |
| display: flex; align-items: center; justify-content: center; | |
| } | |
| .iv-icon-warn { | |
| background: #fef3c7; | |
| color: #d97706; | |
| } | |
| .iv-icon-warn svg { width: 28px; height: 28px; } | |
| /* Check animation */ | |
| .iv-check-anim { | |
| width: 80px; height: 80px; | |
| animation: iv-scale-in .5s cubic-bezier(.22,1,.36,1) .1s both; | |
| } | |
| @keyframes iv-scale-in { | |
| from { opacity:0; transform: scale(.4) rotate(-20deg); } | |
| to { opacity:1; transform: none; } | |
| } | |
| .iv-ring { | |
| animation: iv-draw-ring .6s linear .2s both; | |
| } | |
| @keyframes iv-draw-ring { | |
| from { stroke-dashoffset: 190; } | |
| to { stroke-dashoffset: 0; } | |
| } | |
| .iv-check { | |
| animation: iv-draw-check .4s ease .7s both; | |
| } | |
| @keyframes iv-draw-check { | |
| from { stroke-dashoffset: 40; } | |
| to { stroke-dashoffset: 0; } | |
| } | |
| @media (max-width: 480px) { | |
| .iv-card { padding: 28px 20px; } | |
| .iv-pills { gap: 6px; } | |
| .iv-pill { padding: 10px 4px; } | |
| .iv-pill-val { font-size: 11px; } | |
| .iv-slot-card { padding: 14px 16px; } | |
| } | |
| `; | |