/* 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 (
);
}
/* ───────── 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')}
);
}
/* ───────── 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')}
{t('contactHoursTitle')}
{t('contactHours')}
);
}
Object.assign(window, { AuthFlow, Onboarding, RedeemScreen, PlansScreen, ContactScreen });