/* ── MEMBERSHIP GATE — full accounts ── Free preview → account signup (name + email + password) or sign-in. Online (thefluentengineer.com): accounts live in the MySQL database via api/index.php — sign in from any device, progress syncs. Offline / preview: falls back to this browser's localStorage. window.MemberGate — wrap a mode; locks after the free allowance Storage: tfe_member_v1 (current member), tfe_leads_v1 (local fallback), tfe_token (API session) */ const MEMBER_KEY = 'tfe_member_v1'; const LEADS_KEY = 'tfe_leads_v1'; const FREE_PRACTICE_ANSWERS = 12; // free scored answers (practice + lessons) /* Speaking is ALWAYS free — never gated. */ function loadMember() { try { const m = JSON.parse(localStorage.getItem(MEMBER_KEY)); if (m && m.email) return m; } catch {} return null; } function saveMember(m) { localStorage.setItem(MEMBER_KEY, JSON.stringify(m)); try { const leads = JSON.parse(localStorage.getItem(LEADS_KEY)) || []; if (!leads.some(l => l.email === m.email)) { leads.push(m); localStorage.setItem(LEADS_KEY, JSON.stringify(leads)); } } catch {} } function memberSignout() { try { localStorage.removeItem(MEMBER_KEY); } catch {} if (window.TFE_API) window.TFE_API.setToken(null); } function SignupCard({ t, onDone, context }) { const [mode, setMode] = React.useState('join'); // 'join' | 'signin' const [name, setName] = React.useState(''); const [email, setEmail] = React.useState(''); const [password, setPassword] = React.useState(''); const [whatsapp, setWhatsapp] = React.useState(''); const [offers, setOffers] = React.useState(true); const [err, setErr] = React.useState(null); const [busy, setBusy] = React.useState(false); const [joined, setJoined] = React.useState(null); const apiOn = window.TFE_API && window.TFE_API.isOn(); const finish = (m, showGift) => { saveMember(m); if (showGift) setJoined(m); else onDone(m); }; const submit = async () => { setErr(null); const em = email.trim().toLowerCase(); if (mode === 'join' && !name.trim()) { setErr(t('Please enter your name.','يرجى إدخال اسمك.','Lütfen adınızı girin.')); return; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(em)) { setErr(t('Please enter a valid email.','يرجى إدخال بريد إلكتروني صحيح.','Lütfen geçerli bir e-posta girin.')); return; } if (password.length < 4) { setErr(t('Password must be at least 4 characters.','كلمة المرور 4 أحرف على الأقل.','Şifre en az 4 karakter olmalı.')); return; } if (apiOn) { setBusy(true); try { if (mode === 'join') { const j = await window.TFE_API.call('signup', { name: name.trim(), email: em, password, whatsapp: whatsapp.trim(), offers }); window.TFE_API.setToken(j.token); finish({ name: j.student.name, email: j.student.email, offers, at: Date.now(), id: j.student.id, synced: true }, true); } else { const j = await window.TFE_API.call('signin', { email: em, password }); window.TFE_API.setToken(j.token); if (j.progress && j.progress.data && window.TFE_mergeServerProgress) window.TFE_mergeServerProgress(j.progress.data); finish({ name: j.student.name, email: j.student.email, offers: !!j.student.offers, at: Date.now(), id: j.student.id, synced: true }, false); } } catch (e) { setErr(e.message === 'offline' ? t('Could not reach the server — please try again.','تعذر الوصول للخادم — حاول مجدداً.','Sunucuya ulaşılamadı — tekrar dene.') : e.message); } setBusy(false); return; } /* offline / preview fallback — saved in this browser only */ if (mode === 'signin') { let found = null; try { found = (JSON.parse(localStorage.getItem(LEADS_KEY)) || []).find(l => l.email === em); } catch {} if (found) { finish({ ...found, at: Date.now() }, false); return; } setErr(t('Sign-in works on the live site. In this preview, join instead.','تسجيل الدخول يعمل على الموقع المباشر. في هذه المعاينة انضم من جديد.','Giriş canlı sitede çalışır. Bu önizlemede yeniden katıl.')); return; } finish({ name: name.trim(), email: em, offers, whatsapp: whatsapp.trim(), at: Date.now() }, true); }; if (joined) return (
🎁
{t('Welcome, ','أهلاً بك، ','Hoş geldin, ')}{joined.name}!
{t( 'Your welcome gift is ready: our complete guide to the English tenses — all 8 tenses explained with examples and a cheat sheet. We\u2019ll also send it to your email.', 'هدية ترحيبية جاهزة لك: دليلنا الكامل لأزمنة الإنجليزية — الأزمنة الثمانية مشروحة بأمثلة وورقة مرجعية. وسنرسله أيضاً إلى بريدك.', 'Hoş geldin hediyen hazır: İngilizce zamanların tam rehberi — 8 zaman, örnekler ve kopya kâğıdıyla. E-postana da göndereceğiz.' )}
📘 {t('Open my welcome gift','افتح هديتي','Hediyemi aç')}
); return (
{(context && context.emoji) || '🔓'}
{mode === 'signin' ? t('Welcome back','مرحباً بعودتك','Tekrar hoş geldin') : (context && context.title) || t('You\u2019ve finished your free preview!','لقد أنهيت الجولة المجانية!','Ücretsiz denemeyi tamamladın!')}
{mode === 'signin' ? t('Sign in to continue where you left off — your progress follows you on any device.','سجّل دخولك لتكمل من حيث توقفت — تقدمك يتبعك على أي جهاز.','Kaldığın yerden devam etmek için giriş yap — ilerlemen her cihazda seninle.') : (context && context.sub) || t( 'Create your free account to keep practicing — unlimited exercises, your progress saved on any device, and a welcome gift: our complete English Tenses Guide.', 'أنشئ حسابك المجاني لمواصلة التمرين — تمارين بلا حدود، تقدمك محفوظ على أي جهاز، وهدية ترحيبية: دليل الأزمنة الإنجليزية الكامل.', 'Ücretsiz hesabını oluştur — sınırsız alıştırma, her cihazda kayıtlı ilerleme ve hoş geldin hediyesi: İngilizce Zamanlar Rehberi.' )}
{mode === 'join' && (
setName(e.target.value)} placeholder="Ahmed / Ayşe / Sara" autoComplete="name" />
)}
setEmail(e.target.value)} placeholder="you@example.com" autoComplete="email" />
setPassword(e.target.value)} placeholder={t('At least 4 characters','4 أحرف على الأقل','En az 4 karakter')} autoComplete={mode === 'join' ? 'new-password' : 'current-password'} onKeyDown={e => { if (e.key === 'Enter') submit(); }} />
{mode === 'join' && (
setWhatsapp(e.target.value)} placeholder="+90 5xx xxx xx xx" autoComplete="tel" />
)} {mode === 'join' && ( )} {err &&
⚠ {err}
}
{t('Free forever. No spam — unsubscribe anytime.','مجاني للأبد. لا رسائل مزعجة — يمكنك إلغاء الاشتراك متى شئت.','Sonsuza dek ücretsiz. Spam yok — istediğin zaman ayrılabilirsin.')}
); } /* Wraps a practice-type mode. Pass member/onMember from the app to keep sign-in state in sync across all sections. */ function MemberGate({ state, t, kind, member: memberProp, onMember, children }) { const [memberLocal, setMemberLocal] = React.useState(loadMember); const member = memberProp !== undefined ? memberProp : memberLocal; const setMember = onMember || setMemberLocal; const answers = (state.practiceScore && state.practiceScore.total) || 0; const over = answers >= FREE_PRACTICE_ANSWERS; // Lock is decided when the mode opens — never mid-exercise const [locked] = React.useState(over); if (!member && locked) return ; const leftTxt = `${Math.max(0, FREE_PRACTICE_ANSWERS - answers)} ${t('free answers left','إجابات مجانية متبقية','ücretsiz cevap kaldı')}`; return ( {!member && (
🎁 {t('Free preview','جولة مجانية','Ücretsiz deneme')} — {leftTxt} {t('Join free for unlimited practice','انضم مجاناً لتمرين بلا حدود','Sınırsız alıştırma için ücretsiz katıl')}
)} {member &&
👋 {t('Welcome back,','مرحباً بعودتك،','Tekrar hoş geldin,')} {member.name}
} {children}
); } Object.assign(window, { MemberGate, SignupCard, loadMember, saveMember, memberSignout });