/* flows.jsx — 穿山甲 VPN mobile · auth, onboarding, redeem/buy */ const { useState: useStateF } = React; /* shared field */ function Field({ icon, label, children }) { return ( ); } const inputStyle = { border: 'none', background: 'transparent', outline: 'none', flex: 1, fontFamily: 'var(--font-sans)', fontSize: 15, color: 'var(--fg1)', minWidth: 0 }; /* ───────── AUTH (login + email-code register) ───────── */ function AuthFlow({ t, lang, onDone }) { const [mode, setMode] = useStateF('login'); // login | register const [step, setStep] = useStateF(0); // register: 0 email+code, 1 set password const [email, setEmail] = useStateF(''); const [code, setCode] = useStateF(''); const [pw, setPw] = useStateF(''); const [sent, setSent] = useStateF(false); const [totp, setTotp] = useStateF(false); // 2FA step after login (PRO) const [otp, setOtp] = useStateF(''); const valid = /\S+@\S+\.\S+/.test(email); const tab = (id, label) => ( ); const primaryBtn = (label, onClick, enabled) => ( ); return (
{/* brand header */}
{t('brand')}
{t('authTagline')}
{/* tabs */}
{tab('login', t('tabLogin'))} {tab('register', t('tabRegister'))}
{mode === 'login' && !totp && (<> setEmail(e.target.value)} placeholder={t('emailPh')} /> setPw(e.target.value)} placeholder={t('pwPh')} />
{t('forgotPw')}
{primaryBtn(t('doLogin'), () => setTotp(true), valid && pw.length > 0)} )} {mode === 'login' && totp && (<>
{t('twoFATitle')} PRO
{t('twoFAHint')}
setOtp(e.target.value.replace(/\D/g, '').slice(0, 6))} placeholder="······" /> {primaryBtn(t('twoFAConfirm'), onDone, otp.length === 6)}
setTotp(false)} style={{ textAlign: 'center', fontSize: 12.5, color: 'var(--fg3)', cursor: 'pointer' }}>{t('twoFABack')}
)} {mode === 'register' && step === 0 && (<> setEmail(e.target.value)} placeholder={t('emailPh')} /> {sent &&
{t('codeSentTo')} {email}
} setCode(e.target.value.replace(/\D/g, '').slice(0, 6))} placeholder="······" /> {primaryBtn(t('doNext'), () => setStep(1), sent && code.length === 6)} )} {mode === 'register' && step === 1 && (<>
{email}
setPw(e.target.value)} placeholder={t('setPwPh')} /> {primaryBtn(t('doCreate'), onDone, pw.length >= 6)} )}
{t('tos')}
); } /* ───────── ONBOARDING (3 screens) ───────── */ function Onboarding({ t, lang, onDone }) { const [i, setI] = useStateF(0); const slides = [ { icon: 'globe', tint: 'var(--accent)', bg: 'var(--accent-subtle)', title: t('ob1Title'), sub: t('ob1Sub'), cta: t('obNext') }, { icon: 'shield', tint: 'var(--accent)', bg: 'var(--accent-subtle)', title: t('ob2Title'), sub: t('ob2Sub'), cta: t('obAllow') }, { icon: 'shield-check', tint: 'var(--success)', bg: 'var(--success-subtle)', title: t('ob3Title'), sub: t('ob3Sub'), cta: t('obStart') }, ]; const s = slides[i]; const next = () => (i < 2 ? setI(i + 1) : onDone()); return (
{s.title}
{s.sub}
{slides.map((_, k) =>
)}
); } /* ───────── REDEEM & BUY ───────── */ function RedeemScreen({ t, lang, onBack }) { const [code, setCode] = useStateF(''); const [ok, setOk] = useStateF(false); const channels = [ { icon: 'shopping-bag', name: t('chStore'), sub: t('chStoreSub'), accent: true }, { icon: 'credit-card', name: 'USDT (TRC20)', sub: t('chUsdtSub') }, { icon: 'send', name: 'Telegram', sub: t('chTgSub') }, { icon: 'message-circle', name: 'LINE', sub: t('chLineSub') }, { icon: 'mail', name: t('chEmail'), sub: t('chEmailSub') }, ]; return (
{t('redeemTitle')}
{/* redeem code */}
{t('redeemCodeTitle')}
{ok ? (
{t('redeemOk')}
) : (<>
setCode(e.target.value.toUpperCase())} placeholder={t('redeemPh')} style={{ flex: 1, minWidth: 0, border: '1.5px solid var(--border-strong)', borderRadius: 'var(--radius-md)', padding: '12px 14px', fontFamily: 'var(--font-mono)', fontSize: 14, letterSpacing: '0.08em', color: 'var(--fg1)', background: 'var(--bg)', outline: 'none' }} />
)}
{/* buy channels */}
{t('buyTitle')}
{t('buySub')}
{channels.map(c => ( ))}
); } /* ───────── PLANS (shown on upgrade) ───────── */ function PlansScreen({ t, lang, onBack, onChoose }) { const plans = [ { id: 'free', name: t('free'), price: '¥0', feats: t('feFree'), cta: t('current'), variant: 'cur' }, { id: 'pro', name: t('proPlan'), price: '¥25', feats: t('fePro'), cta: t('upgrade'), variant: 'pro' }, { id: 'team', name: t('team'), price: '¥99', feats: t('feTeam'), cta: t('choose'), variant: 'plain' }, ]; return (
{t('choosePlan')}
{plans.map(p => { const isPro = p.variant === 'pro'; return (
{p.name}{isPro && {t('mostPopular')}}
{p.price}{t('perMonth')}
{p.feats.map((f, i) => (
{f}
))}
); })}
); } /* ───────── CONTACT US ───────── */ function ContactScreen({ t, lang, onBack }) { const channels = [ { icon: 'send', name: 'Telegram', sub: '@PangolinVPN_bot', accent: true }, { icon: 'message-circle', name: 'LINE', sub: '@pangolinvpn' }, { icon: 'mail', name: t('contactEmail'), sub: 'support@pangolin.vpn' }, { icon: 'shopping-bag', name: t('contactStore'), sub: 'shop.pangolin.vpn' }, ]; return (
{t('contactTitle')}
{t('contactIntro')}
{channels.map(c => ( ))}
{t('contactHoursTitle')}
{t('contactHours')}
); } Object.assign(window, { AuthFlow, Onboarding, RedeemScreen, PlansScreen, ContactScreen });