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

@@ -61,7 +61,7 @@ export const api = {
profile: {
get: () => http.get<T.UserProfile>('/user/profile'),
update: (patch: Partial<Pick<T.UserProfile, 'name' | 'avatar' | 'birthYear' | 'parentPhone' | 'bio'>>) =>
update: (patch: Partial<Pick<T.UserProfile, 'name' | 'avatar' | 'birthYear' | 'birthDate' | 'bio'>>) =>
http.patch<T.UserProfile>('/user/profile', patch),
setGrade: (gradeId: number) =>
http.post<{ success: boolean; gradeId: number }>('/user/grade', { gradeId }),
@@ -70,6 +70,8 @@ export const api = {
summary: () => http.get<T.UserSummary>('/user/summary'),
hearts: () => http.get<T.Hearts>('/user/hearts'),
streakCalendar: () => http.get<T.StreakCalendar>('/user/streak/calendar'),
/** «گزارشِ والدین»: ۷ روزِ اخیرِ همین حساب در کنارِ ۷ روزِ قبلش. */
weeklyReport: () => http.get<T.WeeklyReport>('/user/weekly-report'),
/** بدونِ grade، پایه‌ی ذخیره‌شده‌ی خودِ کاربر ملاک است. */
home: () => http.get<T.HomeResponse>('/home'),
@@ -167,6 +169,8 @@ export const api = {
redeem: (code: string, orderId: string) =>
http.post<T.RedeemResponse>('/discount/redeem', { code, orderId }),
order: (orderId: string) => http.get<T.OrderStatus>(`/payment/orders/${orderId}`),
/** پرداخت‌ها و فاکتورهای همین کاربر، تازه‌ترین اول. */
invoices: () => http.get<{ invoices: T.Invoice[] }>('/payment/invoices').then((r) => r.invoices),
},
support: {
@@ -181,17 +185,6 @@ export const api = {
http.create<{ bugReportId: string; status: string }>('/support/bug-reports', { title, description }),
},
family: {
link: () => http.get<T.FamilyLink>('/family/link'),
invite: (childPhone: string) =>
http.post<{ familyLinkId: string; inviteCode: string; status: string }>('/family/link/invite', { childPhone }),
accept: (inviteCode: string) =>
http.post<{ familyLinkId: string; status: string }>('/family/link/accept', { inviteCode }),
weeklyReport: () => http.get<T.FamilyWeeklyReport>('/family/weekly-report'),
settings: () => http.get<T.FamilySettings>('/family/settings'),
updateSettings: (patch: Partial<T.FamilySettings>) => http.patch('/family/settings', patch),
},
affiliate: {
me: () => http.get<T.AffiliateMe>('/affiliate/me'),
/** کاربرِ تازه (با شماره، نه مهمان) را به صاحبِ کدِ معرف وصل می‌کند. */