// setup.jsx — Page 3: Consultation Setup with sidebar wizard // Sub-steps: 1) Welcome, 2) Video onboarding, 3) Detailed intake form, 4) Booking calendar function SetupPage({ navigate, formData, setFormData, intakeData, setIntakeData, booking, setBooking, tweaks, hasBooked }) { const [step, setStep] = React.useState(1); const [completed, setCompleted] = React.useState({ 1: false, 2: false, 3: false, 4: false }); // Track which steps have ever been visited so we can keep them mounted // (display:none instead of unmount) — prevents re-fetching API on tab switch. const [mounted, setMounted] = React.useState({ 1: true }); const markDone = (n) => setCompleted((c) => ({ ...c, [n]: true })); const go = (n) => { setMounted(m => ({ ...m, [n]: true })); setStep(n); }; const STEPS = [ { n: 1, title: "Введение", sub: "О консультации и процессе" }, { n: 2, title: "Инструкция", sub: "Видео-разбор от Дениса · 17 глав" }, { n: 3, title: "Анкета", sub: "Расскажите о вашей ситуации" }, { n: 4, title: "Бронирование", sub: "Выберите удобное время" }, ]; return (
Шаг {step} из 4
{mounted[1] &&
{ markDone(1); go(2); }} formData={formData}/>
} {mounted[2] &&
{ markDone(2); go(3); }} onBack={() => go(1)}/>
} {mounted[3] &&
{ markDone(3); go(4); }} onBack={() => go(2)}/>
} {mounted[4] &&
{ markDone(4); navigate("confirmation"); }} onBack={() => go(3)} formData={formData} intakeData={intakeData}/>
}
); } /* -------- Step 1: Welcome -------- */ const WELCOME_ICON_MAP = { Clock: Icon.Clock, Calendar: Icon.Calendar, Video: Icon.Video, Doc: Icon.Doc, User: Icon.User, Upload: Icon.Upload, Shield: Icon.Shield, Globe: Icon.Globe, Help: Icon.Help, Lock: Icon.Lock, Check: Icon.Check, Plane: Icon.Plane, }; const WELCOME_FALLBACK_ICONS = [Icon.Clock, Icon.Help, Icon.Doc, Icon.Shield]; function SetupWelcome({ onNext, formData }) { const [content, setContent] = React.useState(null); React.useEffect(() => { fetch("/api/welcome") .then(r => r.ok ? r.json() : null) .then(d => { if (d) setContent(d); }) .catch(() => {}); }, []); const heading = content?.heading ?? "Добро пожаловать"; const lead = content?.lead ?? ""; const features = content?.features ?? []; const whyEnabled = content?.why_enabled ?? true; const whyTitle = content?.why_title ?? "Почему это важно?"; const whyBody = content?.why_body ?? ""; if (!content) return (
); return (

{heading}!

{lead}

{features.map((f, i) => { const Ico = WELCOME_ICON_MAP[f.icon] ?? WELCOME_FALLBACK_ICONS[i % WELCOME_FALLBACK_ICONS.length]; return (
{f.title}

{f.body}

); })}
{whyEnabled && (
{whyTitle}
{whyBody}
)}
); } /* -------- Step 2: Video -------- */ const VIDEO_LIBRARY = [ { t: "Знакомство: как проходит консультация", d: "3:42", x: "Короткое введение в формат: что мы обсудим, как лучше подготовиться, какие материалы будут полезны. Этот ролик стоит посмотреть в первую очередь — он задаёт контекст для всех остальных." }, { t: "Какие вопросы подготовить заранее", d: "4:15", x: "Чек-лист вопросов, которые помогут вам получить максимум от консультации. Чем точнее вы сформулируете свои сомнения, тем конкретнее будут наши рекомендации." }, { t: "Документы и материалы, которые могут понадобиться", d: "5:08", x: "Список документов, которые стоит подготовить: дипломы, сертификаты языка, опыт работы, рекомендации. Расскажем, что обязательно, а что — желательно." }, { t: "Типы виз: обзор основных категорий", d: "8:21", x: "Рабочая, учебная, партнёрская, инвесторская, по таланту, гуманитарная. Сильные и слабые стороны каждой, кому какая подходит." }, { t: "Skilled Migrant Category: как набрать баллы", d: "9:54", x: "Подробный разбор балльной системы: возраст, образование, опыт, английский, предложение о работе. Реальные примеры успешных и неуспешных профилей." }, { t: "Work Visa: как найти работодателя", d: "7:33", x: "Стратегии поиска работы из-за границы. Какие компании готовы спонсировать визу, как составить резюме под местный рынок, на что обращают внимание рекрутёры." }, { t: "Study Pathway: учёба как путь к иммиграции", d: "6:47", x: "Какие специальности и уровни образования открывают дорогу к ПМЖ. Стоимость, сроки, требования к английскому. Когда учёба — это инвестиция, а когда — пустая трата денег." }, { t: "Партнёрская виза: что важно знать", d: "5:29", x: "Документы, доказательства отношений, частые отказы. Разница между супружеской и de facto визой. Что делать, если отношения нестандартные." }, { t: "Бизнес и инвестиции: путь предпринимателя", d: "8:02", x: "Entrepreneur Work Visa, Investor Visa, требования к капиталу и бизнес-плану. Реальные пороги входа и сроки рассмотрения." }, { t: "Английский язык: уровень и сертификаты", d: "5:51", x: "IELTS, PTE, TOEFL — какой сдавать, на какой балл целиться. Где готовиться, сколько это стоит, можно ли обойти этот пункт." }, { t: "Финансовая подготовка к переезду", d: "6:38", x: "Минимальный бюджет, подушка безопасности, перевод денег между странами. Налоговые нюансы: где платить, как избежать двойного налогообложения." }, { t: "Переезд с семьёй: что учесть", d: "7:14", x: "Визы для супруга и детей, школы, страховка, аренда жилья на семью. Разница между переездом одного человека и переездом семьи." }, { t: "Медицина и страхование", d: "4:55", x: "Медосмотр для визы, какие требования, у кого могут быть проблемы. Страховка на период переезда и после." }, { t: "Жизнь после переезда: первые месяцы", d: "9:11", x: "IRD-номер, банк, аренда, водительские права, симка. Что делать в первые две недели — пошаговый план." }, { t: "Поиск работы на месте", d: "8:27", x: "Локальные сайты, рекрутёры, networking. Как переписать резюме и LinkedIn под местный рынок. Чего ждать на интервью и какие зарплаты реалистичны." }, { t: "Частые ошибки кандидатов", d: "6:03", x: "Топ-15 ошибок, которые мы видим у клиентов: от плохо оформленных документов до неправильного выбора визы. Как их избежать." }, { t: "Что делать после консультации", d: "4:22", x: "Как использовать наши рекомендации, в каком порядке начинать действовать, когда возвращаться для повторной консультации." }, ]; function SetupVideo({ onNext, onBack }) { const [openVideos, setOpenVideos] = React.useState({}); // id -> true const [playingId, setPlayingId] = React.useState(null); const [openFaq, setOpenFaq] = React.useState(-1); const toggle = (i) => setOpenVideos((o) => ({ ...o, [i]: !o[i] })); const FAQ = [ { q: "Обязательно ли смотреть все 17 видео?", a: "Нет, но мы рекомендуем посмотреть хотя бы первые 5 — они дают общую картину. Остальные стоит выбирать по вашей ситуации (тип визы, семейное положение, профессия)." }, { q: "Сколько длится консультация?", a: "Стандартная консультация — 60 минут. При необходимости мы можем продлить разговор — предупредим заранее." }, { q: "На каком языке проводится консультация?", a: "На русском или английском — на ваш выбор. Возможен смешанный формат." }, { q: "Получу ли я запись консультации?", a: "Да, после консультации мы отправим запись и резюме с планом действий на email." }, { q: "Что если я не смогу подключиться вовремя?", a: "Можем перенести консультацию один раз бесплатно, если вы предупредите за 24 часа." }, ]; const watchedCount = Object.values(openVideos).filter(Boolean).length; return (

Видео-материалы

17 коротких видео по ключевым темам иммиграции. Посмотрите те, что относятся к вашей ситуации — это сильно ускорит консультацию.

Не обязательно смотреть все. Минимум — первые 5 для общего контекста, затем выбирайте по теме.

Все материалы

{watchedCount} из {VIDEO_LIBRARY.length} открыто
{VIDEO_LIBRARY.map((v, i) => { const isOpen = !!openVideos[i]; return (
{isOpen && (
{ e.stopPropagation(); setPlayingId(playingId===i?null:i); }}> {playingId === i ? (
Здесь будет встроенное видео
) : ( <>
{v.d}
)}
{v.x}
)}
); })}

Частые вопросы

{FAQ.map((f, i) => (
setOpenFaq(openFaq===i?-1:i)}> {f.q}
{openFaq===i &&
{f.a}
}
))}
); } /* -------- Step 3: Intake form (multi-section) -------- */ const ENGLISH_LEVELS = ["A1 — Beginner","A2 — Elementary","B1 — Intermediate","B2 — Upper-Intermediate","C1 — Advanced","C2 — Proficiency / Native"]; const EDU_LEVELS = ["Среднее","Среднее специальное","Незаконченное высшее","Бакалавр","Специалист","Магистр","PhD / Кандидат наук"]; function PersonBlock({ prefix, data, update, optional }) { const v = (k) => data[`${prefix}_${k}`] || ""; const set = (k, val) => update(`${prefix}_${k}`, val); return (
set("name",e.target.value)}/> set("age",e.target.value)}/>
set("gpa",e.target.value)}/>
set("major",e.target.value)}/>