diff --git a/src/api/index.ts b/src/api/index.ts index f0f3d72..feb51a2 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -39,17 +39,16 @@ export const api = { http.post<{ success: boolean; devOtp?: string }>('/auth/otp/request', { phone }, { timeoutMs: 45_000 }), /** - * گامِ ۲: تأییدِ کد. اگر الان مهمان باشد، سرور همان کاربر را نگه می‌دارد و - * پیشرفتش از دست نمی‌رود (توکنِ مهمان همراهِ همین درخواست می‌رود). + * گامِ ۲: تأییدِ کد. شماره‌ی تازه حسابِ تازه می‌سازد. */ login: (phone: string, otp: string) => http.post('/auth/login', { phone, otp }).then(saveTokens), /** - * حذفِ همیشگیِ حساب. کاربرِ شماره‌دار کدِ تازه‌ی پیامکی می‌دهد (اول requestOtp با شماره‌ی خودش)، - * مهمان کلمه‌ی «حذف» را. بعد از موفقیت توکن‌ها پاک می‌شوند؛ دیگر کاربری پشتشان نیست. + * حذفِ همیشگیِ حساب. کاربر کدِ تازه‌ی پیامکی می‌دهد (اول requestOtp با شماره‌ی خودش). + * بعد از موفقیت توکن‌ها پاک می‌شوند؛ دیگر کاربری پشتشان نیست. */ - deleteAccount: (body: { otp?: string; confirm?: string }) => + deleteAccount: (body: { otp: string }) => http.post<{ success: boolean }>('/user/delete', body).then((r) => { http.storage.set(null); return r; @@ -208,7 +207,7 @@ export const api = { affiliate: { me: () => http.get('/affiliate/me'), - /** کاربرِ تازه (با شماره، نه مهمان) را به صاحبِ کدِ معرف وصل می‌کند. */ + /** کاربرِ تازه را به صاحبِ کدِ معرف وصل می‌کند. */ track: (referralCode: string) => http.post<{ success: boolean }>('/affiliate/track', { referralCode }), }, }; diff --git a/src/api/types.ts b/src/api/types.ts index 7aad36f..682d4e4 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -14,7 +14,6 @@ export interface AuthResponse { token: string; refreshToken: string; isNewUser?: boolean; - isGuest?: boolean; } export interface Grade { @@ -33,7 +32,6 @@ export interface UserProfile { phone: string | null; name: string; avatar: string; - isGuest: boolean; gradeId: number | null; birthYear: number | null; /** تاریخِ تولدِ شمسی «1385-04-12»؛ null وقتی فقط سال داده شده یا هیچ. */ diff --git a/src/app/App.tsx b/src/app/App.tsx index 9758df0..07ec43f 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -34,7 +34,7 @@ export function App() { /** پروفایل و پایه‌ها را می‌گیرد و تصمیم می‌گیرد کاربر کجا برود. */ const enter = useCallback(async () => { const [profile, grades] = await Promise.all([api.profile.get(), api.grades()]); - await trackReferral(profile); + await trackReferral(); if (profile.name && profile.gradeId) setFlow({ kind: 'main', profile, grades }); else setFlow({ kind: 'setup', profile, grades }); }, []); @@ -124,7 +124,6 @@ export function App() { grades={flow.grades} payment={launchPayment} onSignedOut={() => setFlow({ kind: 'login' })} - onRegistered={trackReferral} /> )} @@ -135,13 +134,10 @@ export function App() { ); } -/** - * اگر کاربر با لینکِ دعوت آمده بود و حالا با شماره وارد شده، معرفی را ثبت کن. - * مهمان را سرور نمی‌پذیرد، پس کد تا ثبتِ شماره نگه داشته می‌شود. - */ -async function trackReferral(profile: UserProfile) { +/** اگر کاربر با لینکِ دعوت آمده بود و حالا وارد شده، معرفی را ثبت کن. */ +async function trackReferral() { const code = store.get(KEYS.referralCode); - if (!code || profile.isGuest) return; + if (!code) return; try { await api.affiliate.track(code); store.remove(KEYS.referralCode); diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index 60ce497..03a5aed 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -7,10 +7,8 @@ import { NavigatorProvider, useNav } from '@/nav/Navigator'; import { isTab, type Route } from '@/nav/routes'; import { BottomNav } from '@/components/BottomNav'; import { Sheet } from '@/components/Sheet'; -import { useToast } from '@/components/Toast'; import { LoadingState, PrimaryButton } from '@/components/ui'; import { HomeScreen } from '@/screens/HomeScreen'; -import { LoginScreen } from '@/screens/LoginScreen'; import type { PaymentReturn } from './launchParams'; /** @@ -54,23 +52,22 @@ interface Props { grades: Grade[]; payment: PaymentReturn | null; onSignedOut: () => void; - onRegistered: (profile: UserProfile) => Promise; } /** اپِ اصلی بعد از ورود: تب‌ها، صفحه‌ها و داده‌ی مشترک. */ -export function MainApp({ profile, grades, payment, onSignedOut, onRegistered }: Props) { +export function MainApp({ profile, grades, payment, onSignedOut }: Props) { useEffect(preloadScreens, []); return ( - + {payment && } ); } -function Screens({ onSignedOut, onRegistered }: Pick) { +function Screens({ onSignedOut }: Pick) { const nav = useNav(); const { route } = nav; const showNav = isTab(route.name) && nav.tab === route.name; @@ -88,7 +85,7 @@ function Screens({ onSignedOut, onRegistered }: Pick }> - + @@ -98,7 +95,7 @@ function Screens({ onSignedOut, onRegistered }: Pick) { +function RouteView({ route, onSignedOut }: { route: Route } & Pick) { switch (route.name) { case 'home': return ; case 'map': return ; @@ -116,34 +113,9 @@ function RouteView({ route, onSignedOut, onRegistered }: { route: Route } & Pick case 'report': return ; case 'invoices': return ; case 'invite': return ; - case 'register': return ; } } -/** مهمانی که شماره ثبت می‌کند؛ همان کاربر می‌ماند و فقط پروفایل تازه می‌شود. */ -function RegisterView({ onRegistered }: Pick) { - const nav = useNav(); - const { setProfile } = useAppData(); - const toast = useToast(); - return ( - { - try { - const profile = await api.profile.get(); - setProfile(profile); - await onRegistered(profile); - } catch { - /* ورود انجام شده؛ پروفایل با بازگشت به اپ تازه می‌شود */ - } - toast('شماره‌ت ثبت شد! پیشرفتت امنه.', 'success'); - nav.back(); - }} - /> - ); -} - /** * نتیجه‌ی برگشت از درگاه. فقط پاسخِ سرور تعیین می‌کند پرداخت موفق بوده یا نه؛ `status` در آدرس را هر * لینکِ ساختگی می‌تواند «paid» بگذارد. diff --git a/src/nav/routes.ts b/src/nav/routes.ts index 58557d7..3a20a43 100644 --- a/src/nav/routes.ts +++ b/src/nav/routes.ts @@ -18,8 +18,7 @@ export type Route = | { name: 'report' } /** پرداخت‌ها و فاکتورهای خودِ کاربر. */ | { name: 'invoices' } - | { name: 'invite' } - | { name: 'register' }; + | { name: 'invite' }; export type TabName = 'home' | 'map' | 'ask' | 'subscription' | 'profile'; diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx index a972a27..9144a24 100644 --- a/src/screens/LoginScreen.tsx +++ b/src/screens/LoginScreen.tsx @@ -11,20 +11,9 @@ const OTP_LENGTH = 6; const RESEND_SECONDS = 120; /** - * ورود با شماره (OTP). ورودِ مهمان نداریم: هر کاربر حداقل با شماره ثبت‌نام می‌کند. - * - * `upgrade` برای حسابِ مهمانِ قدیمی است (دیگر ساخته نمی‌شود) که از صفحه‌ی «من» - * شماره ثبت می‌کند؛ سرور همان کاربر را نگه می‌دارد. + * ورود با شماره (OTP): تنها راهِ ورود است. */ -export function LoginScreen({ - onDone, - onCancel, - upgrade = false, -}: { - onDone: () => void; - onCancel?: () => void; - upgrade?: boolean; -}) { +export function LoginScreen({ onDone, onCancel }: { onDone: () => void; onCancel?: () => void }) { const [step, setStep] = useState<'phone' | 'otp'>('phone'); const [phone, setPhone] = useState(''); const [otp, setOtp] = useState(''); @@ -117,10 +106,10 @@ export function LoginScreen({ )}
-

{upgrade ? 'ثبتِ شماره‌ی موبایل' : 'ورود به تدریسینو'}

+

ورود به تدریسینو

{step === 'phone' - ? upgrade ? 'امتیازها و پیشرفتت سرِ جاش می‌مونه' : 'شماره‌ی موبایلت رو وارد کن' + ? 'شماره‌ی موبایلت رو وارد کن' : `کدِ ${toFa(OTP_LENGTH)} رقمیِ پیامک‌شده رو وارد کن`}

diff --git a/src/screens/ProfileScreen.tsx b/src/screens/ProfileScreen.tsx index 827edd8..f33e7fc 100644 --- a/src/screens/ProfileScreen.tsx +++ b/src/screens/ProfileScreen.tsx @@ -1,7 +1,7 @@ import { useState, type ReactNode } from 'react'; import { BarChart3, Bell, CalendarDays, Check, ChevronLeft, Coins, Flame, Gift, GraduationCap, LifeBuoy, LogOut, - FileText, Pencil, Repeat, Smartphone, Sparkles, Star, Trash2, Trophy, Volume2, VolumeX, + FileText, Pencil, Repeat, Sparkles, Star, Trash2, Trophy, Volume2, VolumeX, } from 'lucide-react'; import { api } from '@/api'; import { useAppData } from '@/state/AppData'; @@ -48,11 +48,7 @@ export function ProfileScreen({ onSignedOut }: { onSignedOut: () => void }) {
{gradeTitle && {gradeTitle}} {age && {toFa(age)} ساله} - {profile.isGuest ? ( - مهمان - ) : ( - profile.phone && {toFa(profile.phone)} - )} + {profile.phone && {toFa(profile.phone)}}
@@ -72,21 +68,6 @@ export function ProfileScreen({ onSignedOut }: { onSignedOut: () => void }) {
- {profile.isGuest && ( - - )} - setEditing('grade')} /> void }) { - {!profile.isGuest && } + void * دیگری می‌شد، و چند refresh tokenِ ماندگار در مرورگر خطرِ دزدیده شدن را چند برابر می‌کرد. */ function LogoutSheet({ open, switching = false, onClose, onSignedOut }: { open: boolean; switching?: boolean; onClose: () => void; onSignedOut: () => void }) { - const { profile } = useAppData(); const [busy, setBusy] = useState(false); async function logout() { @@ -291,9 +271,7 @@ function LogoutSheet({ open, switching = false, onClose, onSignedOut }: { open:

{switching && 'اول از این حساب خارج می‌شی، بعد با شماره‌ی دیگه وارد شو. '} - {profile.isGuest - ? 'تو مهمانی: با همین گوشی و مرورگر، «فعلاً مهمان باشم» دوباره تو رو به همین پیشرفت برمی‌گردونه، ولی از گوشیِ دیگه نه. برای امن‌تر شدن، اول شماره‌ت رو ثبت کن.' - : 'هر وقت خواستی با همین شماره دوباره وارد شو؛ پیشرفتت سرِ جاشه.'} + هر وقت خواستی با همین شماره دوباره وارد شو؛ پیشرفتت سرِ جاشه.

نه، می‌مونم @@ -307,7 +285,7 @@ function LogoutSheet({ open, switching = false, onClose, onSignedOut }: { open: /** * حذفِ همیشگیِ حساب (POST /user/delete). شماره‌دار با کدِ تازه‌ی پیامکی تأیید می‌کند تا گوشیِ - * بازمانده یا توکنِ دزدیده نتواند حساب را پاک کند؛ مهمان با تایپِ «حذف». + * بازمانده یا توکنِ دزدیده نتواند حساب را پاک کند. */ function DeleteAccountSheet({ open, onClose, onSignedOut }: { open: boolean; onClose: () => void; onSignedOut: () => void }) { const { profile } = useAppData(); @@ -315,7 +293,6 @@ function DeleteAccountSheet({ open, onClose, onSignedOut }: { open: boolean; onC const [value, setValue] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); - const needsCode = Boolean(profile.phone); async function sendCode() { if (!profile.phone) return; @@ -335,7 +312,7 @@ function DeleteAccountSheet({ open, onClose, onSignedOut }: { open: boolean; onC setBusy(true); setError(''); try { - await api.auth.deleteAccount(needsCode ? { otp: digitsOnly(value, 6) } : { confirm: value.trim() }); + await api.auth.deleteAccount({ otp: digitsOnly(value, 6) }); askStore.clear(); onSignedOut(); } catch (err) { @@ -351,37 +328,28 @@ function DeleteAccountSheet({ open, onClose, onSignedOut }: { open: boolean; onC امتیاز، زنجیره، پیشرفتِ جزیره‌ها، گفتگوها با دستیار، سوال‌های خریده‌شده و قلبِ نامحدودت پاک می‌شن و برنمی‌گردن. سوابقِ پرداخت فقط برای حسابداری می‌مونه.

- {needsCode ? ( - sent ? ( - setValue(digitsOnly(e.target.value, 6))} - placeholder="کدِ پیامک‌شده" - inputMode="numeric" - dir="ltr" - className="mb-3 w-full rounded-2xl border-2 border-line px-4 py-3 text-center font-bold tracking-widest outline-none focus:border-red-400" - /> - ) : ( -

برای اطمینان، یک کد به شماره‌ی خودت پیامک می‌شه.

- ) - ) : ( + {sent ? ( setValue(e.target.value.slice(0, 10))} - placeholder="برای تأیید بنویس: حذف" - className="mb-3 w-full rounded-2xl border-2 border-line px-4 py-3 text-center font-bold outline-none focus:border-red-400" + onChange={(e) => setValue(digitsOnly(e.target.value, 6))} + placeholder="کدِ پیامک‌شده" + inputMode="numeric" + dir="ltr" + className="mb-3 w-full rounded-2xl border-2 border-line px-4 py-3 text-center font-bold tracking-widest outline-none focus:border-red-400" /> + ) : ( +

برای اطمینان، یک کد به شماره‌ی خودت پیامک می‌شه.

)} {error &&
{error}
}
نه، پشیمون شدم - {needsCode && !sent ? ( + {!sent ? ( void sendCode()}>فرستادنِ کد ) : ( void remove()} > حذفِ همیشگی