parent report page with two charts, Jalali birth date picker, user ID and invoices in profile

- the parents section is gone: no parent login, invite codes or "I'm a parent" button;
  "گزارشِ والدین" in the profile shows the week against the previous one (praise line,
  six tiles, minutes-per-day and right/wrong-per-day charts, a table of the numbers)
- birth date as day / month / year selects (Jalali, leap Esfand handled) instead of age;
  adults (19+) are asked which grade's math they want to learn
- user ID under the name, copyable
- "پرداخت‌ها و فاکتورها": every order with invoice number, discount, amount and reference code
- tutor: when the free daily quota runs out, a button to the subscription page instead of "retry"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 02:38:43 +03:30
parent 0e56d9c8aa
commit de7cd0bfa8
15 changed files with 738 additions and 373 deletions

View File

@@ -26,13 +26,14 @@ function screen<M extends Record<K, ComponentType<any>>, K extends keyof M>(load
}
const AskScreen = screen(() => import('@/screens/AskScreen'), 'AskScreen');
const CharactersScreen = screen(() => import('@/screens/CharactersScreen'), 'CharactersScreen');
const FamilyScreen = screen(() => import('@/screens/FamilyScreen'), 'FamilyScreen');
const InviteScreen = screen(() => import('@/screens/InviteScreen'), 'InviteScreen');
const InvoicesScreen = screen(() => import('@/screens/InvoicesScreen'), 'InvoicesScreen');
const LeaderboardScreen = screen(() => import('@/screens/LeaderboardScreen'), 'LeaderboardScreen');
const LessonScreen = screen(() => import('@/screens/LessonScreen'), 'LessonScreen');
const MapScreen = screen(() => import('@/screens/MapScreen'), 'MapScreen');
const NotificationsScreen = screen(() => import('@/screens/NotificationsScreen'), 'NotificationsScreen');
const ProfileScreen = screen(() => import('@/screens/ProfileScreen'), 'ProfileScreen');
const ReportScreen = screen(() => import('@/screens/ReportScreen'), 'ReportScreen');
const QuizScreen = screen(() => import('@/screens/QuizScreen'), 'QuizScreen');
const StreakScreen = screen(() => import('@/screens/StreakScreen'), 'StreakScreen');
const SubscriptionScreen = screen(() => import('@/screens/SubscriptionScreen'), 'SubscriptionScreen');
@@ -52,18 +53,16 @@ interface Props {
profile: UserProfile;
grades: Grade[];
payment: PaymentReturn | null;
/** صفحه‌ی شروع؛ برای والد بخشِ والدین است. */
start?: Route;
onSignedOut: () => void;
onRegistered: (profile: UserProfile) => Promise<void>;
}
/** اپِ اصلی بعد از ورود: تب‌ها، صفحه‌ها و داده‌ی مشترک. */
export function MainApp({ profile, grades, payment, start, onSignedOut, onRegistered }: Props) {
export function MainApp({ profile, grades, payment, onSignedOut, onRegistered }: Props) {
useEffect(preloadScreens, []);
return (
<AppDataProvider initialProfile={profile} grades={grades}>
<NavigatorProvider initial={start ?? { name: payment ? 'subscription' : 'home' }}>
<NavigatorProvider initial={{ name: payment ? 'subscription' : 'home' }}>
<Screens onSignedOut={onSignedOut} onRegistered={onRegistered} />
{payment && <PaymentResult payment={payment} />}
</NavigatorProvider>
@@ -114,7 +113,8 @@ function RouteView({ route, onSignedOut, onRegistered }: { route: Route } & Pick
case 'characters': return <CharactersScreen />;
case 'streak': return <StreakScreen />;
case 'support': return <SupportScreen />;
case 'family': return <FamilyScreen onSignedOut={onSignedOut} />;
case 'report': return <ReportScreen />;
case 'invoices': return <InvoicesScreen />;
case 'invite': return <InviteScreen />;
case 'register': return <RegisterView onRegistered={onRegistered} />;
}