From 2b4db90fe4e743a9a3813141af765e706695dc7b Mon Sep 17 00:00:00 2001 From: AliReza Date: Sun, 27 Sep 2026 01:33:39 +0330 Subject: [PATCH] give parents their own way in instead of the student setup form A parent account has no grade, so the app sent the demo mother to the "which grade are you in?" form and she never reached the parents screen. Now an account with a name, no grade and a parent-side family link opens straight on the parents screen (with a logout button, since it has no tab bar), and the setup form offers "I'm a parent, not a student" for new ones. The header's back button only shows when there is somewhere to go back to. Found on the live site with the demo accounts. Co-Authored-By: Claude Opus 5.5 --- src/app/App.tsx | 25 +++++++++++++++++++++++-- src/app/MainApp.tsx | 8 +++++--- src/components/ui.tsx | 2 +- src/screens/FamilyScreen.tsx | 16 ++++++++++++++-- src/screens/SetupScreen.tsx | 20 ++++++++++++++++++++ 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 04b2676..6073195 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { AnimatePresence, motion } from 'motion/react'; import { api, ApiError, http, type Grade, type UserProfile } from '@/api'; +import type { Route } from '@/nav/routes'; import { AppShell } from '@/components/AppShell'; import { ToastProvider } from '@/components/Toast'; import { ErrorState } from '@/components/ui'; @@ -19,7 +20,7 @@ type Flow = | { kind: 'onboarding' } | { kind: 'login' } | { kind: 'setup'; profile: UserProfile; grades: Grade[] } - | { kind: 'main'; profile: UserProfile; grades: Grade[] }; + | { kind: 'main'; profile: UserProfile; grades: Grade[]; start?: Route }; /** کمترین زمانِ نمایشِ اسپلش، تا صفحه چشمک نزند. */ const SPLASH_MIN_MS = 700; @@ -35,7 +36,10 @@ export function App() { const enter = useCallback(async () => { const [profile, grades] = await Promise.all([api.profile.get(), api.grades()]); await trackReferral(profile); - setFlow(profile.name && profile.gradeId ? { kind: 'main', profile, grades } : { kind: 'setup', profile, grades }); + if (profile.name && profile.gradeId) setFlow({ kind: 'main', profile, grades }); + // والد پایه ندارد؛ به‌جای فرمِ دانش‌آموز، مستقیم به بخشِ والدین. + else if (profile.name && (await isParent())) setFlow({ kind: 'main', profile, grades, start: { name: 'family' } }); + else setFlow({ kind: 'setup', profile, grades }); }, []); const boot = useCallback(async () => { @@ -113,6 +117,7 @@ export function App() { profile={flow.profile} grades={flow.grades} onDone={(profile) => setFlow({ kind: 'main', profile, grades: flow.grades })} + onParent={(profile) => setFlow({ kind: 'main', profile, grades: flow.grades, start: { name: 'family' } })} /> )} {flow.kind === 'main' && ( @@ -120,6 +125,7 @@ export function App() { profile={flow.profile} grades={flow.grades} payment={launchPayment} + start={flow.start} onSignedOut={() => setFlow({ kind: 'login' })} onRegistered={trackReferral} /> @@ -131,6 +137,21 @@ export function App() { ); } +/** + * آیا این حساب والد است؟ سرور نقش را صریح نمی‌دهد: پیوندِ «در انتظار» فقط برای + * والدی که دعوت ساخته دیده می‌شود، و گزارشِ هفتگی فقط به والد جواب می‌دهد. + */ +async function isParent(): Promise { + try { + const link = await api.family.link(); + if (link.status === 'pending') return true; + await api.family.weeklyReport(); + return true; + } catch { + return false; + } +} + /** * اگر کاربر با لینکِ دعوت آمده بود و حالا با شماره وارد شده، معرفی را ثبت کن. * مهمان را سرور نمی‌پذیرد، پس کد تا ثبتِ شماره نگه داشته می‌شود. diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index 5d4a071..da25127 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -31,15 +31,17 @@ interface Props { profile: UserProfile; grades: Grade[]; payment: PaymentReturn | null; + /** صفحه‌ی شروع؛ برای والد بخشِ والدین است. */ + start?: Route; onSignedOut: () => void; onRegistered: (profile: UserProfile) => Promise; } /** اپِ اصلی بعد از ورود: تب‌ها، صفحه‌ها و داده‌ی مشترک. */ -export function MainApp({ profile, grades, payment, onSignedOut, onRegistered }: Props) { +export function MainApp({ profile, grades, payment, start, onSignedOut, onRegistered }: Props) { return ( - + {payment && } @@ -88,7 +90,7 @@ function RouteView({ route, onSignedOut, onRegistered }: { route: Route } & Pick case 'characters': return ; case 'streak': return ; case 'support': return ; - case 'family': return ; + case 'family': return ; case 'invite': return ; case 'register': return ; } diff --git a/src/components/ui.tsx b/src/components/ui.tsx index 3570077..10b1edf 100644 --- a/src/components/ui.tsx +++ b/src/components/ui.tsx @@ -33,7 +33,7 @@ export function ScreenHeader({
- {back && ( + {back && (typeof back === 'function' || nav.canGoBack) && ( diff --git a/src/screens/FamilyScreen.tsx b/src/screens/FamilyScreen.tsx index 4ca31ad..9fce3a3 100644 --- a/src/screens/FamilyScreen.tsx +++ b/src/screens/FamilyScreen.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { BookOpen, Flame, Link2, Star, Trophy, UserPlus } from 'lucide-react'; +import { BookOpen, Flame, Link2, LogOut, Star, Trophy, UserPlus } from 'lucide-react'; +import { useNav } from '@/nav/Navigator'; import { api, ApiError, type FamilyLink, type FamilyWeeklyReport } from '@/api'; import { useAsync } from '@/lib/useAsync'; import { useToast } from '@/components/Toast'; @@ -12,7 +13,8 @@ import { digitsOnly, formatJalaliDate, toFa } from '@/lib/format'; * (با همان شماره وارد شده) کد را اینجا وارد می‌کند. بعد از اتصال، والد * گزارشِ هفتگی را می‌بیند. */ -export function FamilyScreen() { +export function FamilyScreen({ onSignedOut }: { onSignedOut: () => void }) { + const nav = useNav(); // GET /family/link نمی‌گوید کاربر در این اتصال والد است یا فرزند؛ گزارشِ // هفتگی فقط برای والد جواب می‌دهد و برای فرزند ۴۰۴ است، پس نقش از همان درمی‌آید. const state = useAsync(async () => { @@ -46,6 +48,16 @@ export function FamilyScreen() { {!data?.role && } )} + {/* والد بدونِ پایه نوارِ پایین را ندارد؛ خروج باید همین‌جا باشد. */} + {!nav.canGoBack && ( + + )} ); diff --git a/src/screens/SetupScreen.tsx b/src/screens/SetupScreen.tsx index 30c3469..4f583a0 100644 --- a/src/screens/SetupScreen.tsx +++ b/src/screens/SetupScreen.tsx @@ -12,10 +12,13 @@ export function SetupScreen({ profile, grades, onDone, + onParent, }: { profile: UserProfile; grades: Grade[]; onDone: (profile: UserProfile) => void; + /** «من والدم»: بدونِ پایه، مستقیم به بخشِ والدین. */ + onParent: (profile: UserProfile) => void; }) { const [name, setName] = useState(profile.name); const [age, setAge] = useState(ageFromBirthYear(profile.birthYear)); @@ -46,6 +49,20 @@ export function SetupScreen({ } } + async function submitAsParent() { + setTouched(true); + if (!name.trim() || saving) return; + setSaving(true); + setError(''); + try { + onParent(await api.profile.update({ name: name.trim() })); + } catch (err) { + setError(friendlyError(err, 'ذخیره نشد، دوباره امتحان کن')); + } finally { + setSaving(false); + } + } + return (
@@ -122,6 +139,9 @@ export function SetupScreen({ void submit()} loading={saving} className="mt-auto"> شروعِ یادگیری +
);