/* dflows.jsx — 穿山甲 VPN desktop · auth + account management (integrated dashboard) + redeem */ const { useState: useStateD } = React; const dCard = { background:'var(--surface)', border:'1px solid var(--border)', borderRadius:'var(--radius-xl)', boxShadow:'var(--shadow-sm)' }; const dInput = { border:'1.5px solid var(--border-strong)', borderRadius:'var(--radius-md)', padding:'11px 13px', fontFamily:'var(--font-sans)', fontSize:14, color:'var(--fg1)', background:'var(--bg)', outline:'none', width:'100%', boxSizing:'border-box' }; function DField({ label, children }) { return ; } /* ───────── AUTH — split panel, brand left / form right ───────── */ function DAuthFlow({ t, lang, dark, onDone }) { const [mode,setMode]=useStateD('login'); const [step,setStep]=useStateD(0); const [email,setEmail]=useStateD(''); const [code,setCode]=useStateD(''); const [pw,setPw]=useStateD(''); const [sent,setSent]=useStateD(false); const [totp,setTotp]=useStateD(false); // 2FA step after login (PRO) const [otp,setOtp]=useStateD(''); const valid=/\S+@\S+\.\S+/.test(email); const tab=(id,label)=>( ); const primary=(label,onClick,enabled)=>( ); return (
{/* brand panel */}
{lang==='zh'?'穿山甲':'Pangolin'}
{t('authSidePitch')}
{t('authTagline')}
{/* form panel */}
{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')}
{primary(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="······" style={{ ...dInput, fontFamily:'var(--font-mono)', fontSize:20, letterSpacing:'0.5em', textAlign:'center' }} /> {primary(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="······" /> {primary(t('doNext'), ()=>setStep(1), sent && code.length===6)} )} {mode==='register' && step===1 && (<>
{email}
setPw(e.target.value)} placeholder={t('setPwPh')} /> {primary(t('doCreate'), onDone, pw.length>=6)} )}
{t('tos')}
); } /* ───────── ACCOUNT (integrated: plan + account info + devices) ───────── */ function DAccountView({ t, lang, free, onUpgrade, onSignOut }) { const [twoFA,setTwoFA]=useStateD(true); const [devices,setDevices]=useStateD([ { id:1, icon:'laptop', name:'MacBook Pro', os:'macOS 26', active:t('thisDevice'), me:true }, { id:2, icon:'smartphone', name:'iPhone 17', os:'iOS 26', active:'2 min', me:false }, { id:3, icon:'monitor-smartphone', name:'iPad Air', os:'iPadOS 26', active:lang==='zh'?'3 小时前':'3h ago', me:false }, ]); const infoRow=(label,value,action,last)=>(
{label}
{value}
{action}
); const smallBtn={ border:'1.5px solid var(--border-strong)', background:'transparent', color:'var(--fg2)', fontSize:13, fontWeight:600, padding:'7px 14px', borderRadius:'var(--radius-full)', cursor:'pointer' }; return (
{/* plan banner */}
{t('curPlan')}
{free?t('freePlanName'):t('proMember')}
{free?t('quotaFree'):`${t('expires')} 2026-12-31 · ${t('accUsage')} 42.6 GB`}
{/* account info */}
{t('accInfoTitle')}
{infoRow(t('accEmail'), 'me@pangolin.vpn', )} {infoRow(t('accPassword'), '••••••••••', )} {infoRow(t('twoFA'), t('twoFASub'), setTwoFA(!twoFA)} />)} {infoRow(t('ucEntry'), t('ucEntrySub'), )}
{t('accSignOut')}
{/* devices */}
{t('myDevices')}
{t('devicesSub')}
{devices.map((d,i)=>(
{d.name}{d.me && {d.active}}
{d.os}{!d.me && ` · ${t('lastActive')} ${d.active}`}
{!d.me && }
))}
); } /* ───────── PLANS (shown on renew / upgrade) ───────── */ function DPlansView({ t, lang, 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 (
{plans.map(p=>{ const isPro=p.variant==='pro'; return (
{isPro &&
{t('mostPopular')}
}
{p.name}
{p.price}{t('perMonth')}
{p.feats.map((f,i)=>(
{f}
))}
); })}
); } /* ───────── REDEEM & BUY ───────── */ function DRedeemView({ t, lang }) { const [code,setCode]=useStateD(''); const [ok,setOk]=useStateD(false); const channels=[ { icon:'shopping-bag', name:t('chStore'), sub:t('chStoreSub'), accent:true }, { icon:'credit-card', name:'USDT (TRC20)', sub:t('chUsdtSub'), mono:false }, { icon:'send', name:'Telegram', sub:t('chTgSub'), mono:true }, { icon:'message-circle', name:'LINE', sub:t('chLineSub'), mono:true }, { icon:'mail', name:t('chEmail'), sub:t('chEmailSub'), mono:true }, ]; return (
{t('redeemCodeTitle')}
{ok ? (
{t('redeemOk')}
) : (
setCode(e.target.value.toUpperCase())} placeholder={t('redeemPh')} style={{ ...dInput, fontFamily:'var(--font-mono)', letterSpacing:'0.08em' }} />
)}
{t('buyTitle')}
{t('buySub')}
{channels.map(c=>( ))}
); } /* ───────── ONBOARDING — window card, 3 screens ───────── */ function DOnboarding({ t, lang, dark, onDone }) { const [i,setI]=useStateD(0); const steps=[ { icon:'globe', title:t('ob1Title'), sub:t('ob1Sub'), cta:t('obNext') }, { icon:'lock', title:t('ob2Title'), sub:t('ob2Sub'), cta:t('obAllow') }, { icon:'shield-check', title:t('ob3Title'), sub:t('ob3Sub'), cta:t('obStart') }, ]; const s=steps[i]; const last=i===steps.length-1; return (
{lang==='zh'?'穿山甲':'Pangolin'}
{!last && }
{s.title}
{s.sub}
{steps.map((_,k)=>(
))}
); } /* ───────── CONTACT US ───────── */ function DContactView({ t, lang }) { const channels=[ { icon:'send', name:t('contactTg'), sub:'@PangolinVPN_bot', accent:true }, { icon:'message-circle', name:t('contactLine'), sub:'@pangolinvpn' }, { icon:'mail', name:t('contactEmail'), sub:'support@pangolin.vpn' }, { icon:'shopping-bag', name:t('contactStore'), sub:'shop.pangolin.vpn' }, ]; return (
{t('contactIntro')}
{channels.map(c=>( ))}
{t('contactHoursTitle')}
{t('contactHours')}
{t('contactFaqTitle')}
{t('contactFaq')}
); } Object.assign(window, { DAuthFlow, DOnboarding, DAccountView, DPlansView, DRedeemView, DContactView });