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

@@ -1,7 +1,6 @@
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';
@@ -20,7 +19,7 @@ type Flow =
| { kind: 'onboarding' }
| { kind: 'login' }
| { kind: 'setup'; profile: UserProfile; grades: Grade[] }
| { kind: 'main'; profile: UserProfile; grades: Grade[]; start?: Route };
| { kind: 'main'; profile: UserProfile; grades: Grade[] };
/** کمترین زمانِ نمایشِ اسپلش، تا صفحه چشمک نزند. */
const SPLASH_MIN_MS = 700;
@@ -37,8 +36,6 @@ export function App() {
const [profile, grades] = await Promise.all([api.profile.get(), api.grades()]);
await trackReferral(profile);
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 });
}, []);
@@ -117,7 +114,6 @@ 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' && (
@@ -125,7 +121,6 @@ export function App() {
profile={flow.profile}
grades={flow.grades}
payment={launchPayment}
start={flow.start}
onSignedOut={() => setFlow({ kind: 'login' })}
onRegistered={trackReferral}
/>
@@ -137,21 +132,6 @@ export function App() {
);
}
/**
* آیا این حساب والد است؟ سرور نقش را صریح نمی‌دهد: پیوندِ «در انتظار» فقط برای
* والدی که دعوت ساخته دیده می‌شود، و گزارشِ هفتگی فقط به والد جواب می‌دهد.
*/
async function isParent(): Promise<boolean> {
try {
const link = await api.family.link();
if (link.status === 'pending') return true;
await api.family.weeklyReport();
return true;
} catch {
return false;
}
}
/**
* اگر کاربر با لینکِ دعوت آمده بود و حالا با شماره وارد شده، معرفی را ثبت کن.
* مهمان را سرور نمی‌پذیرد، پس کد تا ثبتِ شماره نگه داشته می‌شود.