Commit Graph

97 Commits

Author SHA1 Message Date
5327fed1dd Android: reproducible signed build (keystore and Bazaar RSA key read from outside the repo), Gradle wrapper, build-apk.sh
First time the project has been built: debug and release both produce an APK.
Release refuses to build without keystore or RSA key.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:05:48 +03:30
2fbcf6e6e0 fixes 2026-10-07 19:04:45 +03:30
5b98c054ea Merge branch 'main' of https://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend 2026-10-07 18:30:25 +03:30
c64b3939ba changes 2026-10-07 18:29:03 +03:30
b11016c6ea LoadingState appears only after 300ms: fast API answers no longer flash the loader (packs screen and every other data screen)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 17:05:52 +03:30
ec1bfc721d Page switch: show the loading box only after 300ms, not for the 1-2 frames lazy() always takes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 17:02:18 +03:30
5b211c2786 Lesson screen: render fixed lesson figures under intro, example and steps
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 16:26:13 +03:30
0db6526933 Remove every guest remnant: profile badge, register flow, delete-by-word
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 18:20:10 +03:30
af2e1f57dd Login: no guest option; every user signs in with a phone number
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 17:38:18 +03:30
1025321b5f Android WebView: bridge only on our own https page, external schemes blocked, file access off; payment URL must be http(s)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 09:44:47 +03:30
e850f6dfbd Payment return decided by the server only; encoded path ids; no sign-out on a two-tab refresh race
- ?orderId must be numeric; every id in an API path is encodeURIComponent'd.
- The payment sheet shows success only from GET /payment/orders/{id}; a failed lookup says the
  status is unknown instead of "paid".
- A 401 from /auth/refresh is not a sign-out when another tab already stored a rotated token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:52:25 +03:30
647ccf2c88 copy: no companion count in splash/onboarding (said three, there are four)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 03:32:57 +03:30
f8cf86e4e8 UI readability pass and Bazaar in-app purchase bridge
- muted text #9e9ec0 -> #727299 (contrast 2.6 -> 4.5 on white); tab labels 11px
- map: chapter titles wrap to two lines instead of being cut off
- reduced-motion respected (MotionConfig + CSS), focus-visible ring,
  no pull-to-refresh, short vibration on correct/wrong answers
- src/lib/bazaar.ts: bridge to the Android WebView wrapper; checkout uses
  Bazaar IAP only inside that app, Zarinpal everywhere else (unchanged)
- android/: WebView wrapper with Poolakey; written but never built

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 19:13:12 +03:30
adecf25486 http: only a 401 from /auth/refresh signs the user out
A 429, 5xx or network failure during token refresh used to sign the user
out. Behind one school Wi-Fi or mobile CGNAT the backend's per-IP refresh
limit was hit, and phone users were sent back to SMS login. Now those cases
keep the session and surface as a retryable error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:05:59 +03:30
33f5f8d009 no-hearts screen: live countdown, filling heart, auto-continue; refunded/canceled invoices
- The wait screen counts down to the next heart second by second, fills
  the first heart as time passes, shows when all five are back, and asks
  the server when the time comes; 'continue' reloads the quiz. Packs and
  'ask Pi meanwhile' (asking costs no hearts) are offered as ways on.
- The server now also refuses to finish a lesson/island without hearts
  and answers (403 no_hearts); the quiz shows the same screen then.
- Invoices know the two admin-set statuses, canceled and refunded.
- Account deletion waits for all six digits of the SMS code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:42:14 +03:30
befaaeb7bb packs: no per-question price on the card
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:27:38 +03:30
a2782da3bd tutor: free and purchased question counters in the header, larger
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:51:33 +03:30
b860c77b91 question packs page, free/purchased counters in the tutor, packs from the no-hearts page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:41:43 +03:30
44871894e0 User-style public ID, tutor quota on plan cards, switch and delete account
- profile shows "User482917" under the name, no copy button
- plan cards start with "N questions a day from Pi (free: M)" taken from the plan itself
- "ورود با حسابِ دیگر" signs out cleanly instead of keeping several accounts on one device
- "حذفِ حساب": SMS code for phone users, typing "حذف" for guests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:10:17 +03:30
de7cd0bfa8 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>
2026-10-02 02:38:43 +03:30
0e56d9c8aa math in Persian text: lists read right-to-left, every content text goes through MathText, chat input picks its direction per line
Measured by rendering every question, option and several tutor answers in
Chrome and comparing the on-screen x of each number (2026-09-29):
- A list inside a sentence ('از −۳ به −۲، −۱، ۰ می‌رسیم', 'الگویِ ۲، ۵، ۸')
  was locked as one left-to-right run, so all 482 read backwards. '،' and
  '؛' outside brackets now split the run; sets and coordinates in brackets
  stay one run. Locked runs are isolates (isolate-override), otherwise the
  comma between two of them was still pulled left-to-right.
- Without MathText 704 of 931 minus signs and 602 of 983 expressions were
  misplaced; lesson and island titles, the lesson intro, unit blurb,
  notification bodies and ticket messages now go through it.
- The chat box takes its direction from each line's first letter, so a
  line of pure math stays left-to-right while typing. A line that starts
  in Persian can still show its math reordered inside the box; a textarea
  cannot isolate part of its text. The sent message renders correctly.

Four expressions with Persian words inside them ('f(x) = (ریشه دوم x) + ۵۰')
remain ambiguous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:46:18 +03:30
db6d694ab0 tutor wait: the thinking bubble moves through stages instead of one fixed line
The model provider holds the whole answer and sends it at once; a full
answer with a diagram took about 30 s on 2026-09-29. A fixed 'thinking'
line for that long looked stuck. The stage times are approximate and not
tied to what the server is actually doing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:05:33 +03:30
0957ef476b lighter first load, math reads left-to-right in quizzes, tickets from the server, readable separators
- Every screen but home is its own chunk and preloads when the browser is
  idle; React and motion get their own long-cached chunks. The app entry
  goes from 505 kB (160 kB gzip) to 85 kB (27 kB gzip).
- Quiz prompts, options and lesson examples go through MathText, so
  '10 + 3 - 7 - 2' reads from the left; an unpaired bracket at the edge of
  a math run stays with the Persian sentence.
- Support lists tickets from GET /support/tickets instead of this
  browser's localStorage; a ticket's messages load when it is opened.
- '·' next to a Persian zero read as another digit ('فصلِ ۱ · ۰' looked
  like ۱۰۰); a thin bar separates them now. Notification bodies show
  Persian digits.
- A root or vertex the model already marked with coordinates is no longer
  labelled a second time on top of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:18:49 +03:30
63f4ea8da0 README: the diagram module and the fourth companion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 06:57:58 +03:30
2f8947ba94 diagram kit: triangle and quad from problem data (law of sines/cosines), anchoring and side-by-side layout, shape patterns, translate points
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 06:13:47 +03:30
dffa940f8a diagram kit: lines from their written equation, regular polygons, exterior angles; hide unused points
Found in a real chat with gpt-4.1-mini: the model writes a line's coefficients
as ax + by + c = 0, so eq now means that and its own earlier outputs render
right; it can also just copy the equation text (3x + 5y = 4, y = 12x - 16).
regularPolygon builds the vertices (the model's own pentagon was not regular),
exteriorAngle draws the extension of the side (the model always drew the
interior angle). Named points no item refers to are no longer drawn, and a
computed root or vertex skips its label when the model already put a point there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 02:37:50 +03:30
ec4c844531 draw every kind of textbook figure, lock math text left-to-right, student-first family screen
- src/diagram: plane (constructed points, graphs with computed roots, vertex,
  extrema, tangent, asymptotes, piecewise limits, regions, rotations and
  reflections, line from ax + by = c), number line with intervals and stacked
  fractions, solids with hidden edges, bar/line/pie charts, sign tables, Venn,
  arrow mappings, probability trees, fraction bars and mixed numbers, grids,
  groups, clock. Pure-math text is bidi-overridden: Persian digits used to turn
  3 × 4 = 12 into 12 = 4 × 3 even with dir=ltr.
- MathText locks math runs inside Persian sentences in chat bubbles and figure
  captions, so -1 no longer shows as 1-.
- Family screen: a student sees only the invite-code form; the parent form is
  behind a link.
- Dev-only diagram gallery at ?gallery (not in the production build).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:24:50 +03:30
355bae7035 play whole islands, make home the path itself, draw tutor diagrams, add tedi
- Home is now the island path of every unit with one clear Start button on the
  current island; the six-card dashboard is gone.
- Start opens /nodes/{id}/quiz (4 questions) instead of one-question lessons, so
  an island takes one round instead of ~11. Lessons stay one tap away in the sheet.
- Tutor answers render the backend's validated diagram (plane, number line,
  fraction) as React SVG; function expressions go through a small parser, no eval.
- Tedi, the mobile-preview mascot, joins as the fourth companion with all six moods
  and an onboarding slide.
- Light polish: 3D buttons, header gradients, warmer page background.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:45:57 +03:30
b5d97c0cda open the code step at once and let the SMS request finish in the background
Kavenegar sends the SMS early but answers our API up to 16 s later
(2026-09-27), and the login screen waited for that answer. The server
stores the code before calling Kavenegar, so the code works immediately
(checked against a local server with an 8 s send: login 200 at 1.5 s).
Real failures still show on the code step, and resend unlocks at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:31:06 +03:30
25127d471e give the login-code request 45 s, since the backend now waits up to 40 s for Kavenegar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:14:23 +03:30
2b4db90fe4 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 <noreply@anthropic.com>
2026-09-27 01:33:39 +03:30
33e539e562 parents screen: tell the child and the parent apart
A linked child (demo user Ali) was told "you are linked to Ali" and the
screen asked for the parent-only weekly report, which 404s. GET /family/link
does not say which side the caller is on, so the role now comes from the
weekly report: it answers only for the parent. The child sees a line saying
their parent follows their progress and no invite/accept forms. Found on the
live site with the demo accounts. deploy.sh now prints the full API URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:26:22 +03:30
0ca7b39e49 point production at mathapi.tadrisino2.ir and add a one-command deploy
The API moved to mathapi.tadrisino2.ir on 2026-09-26; api.math.tadrisino2.ir
no longer resolves. .env.production is the only place that names it.

deploy.sh fetches, runs npm ci and the type-check, builds into dist.next and
swaps it in only when everything succeeded, so the site is never served half
a build. The previous build stays in dist.prev for rollback, and a
hand-edited dist/config.js on the server survives the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:12:39 +03:30
348bfecea6 rebuild the app on a typed, modular structure wired to the real API
The whole app lived in one 3,071-line App.tsx on a Figma scaffold (60 unused
packages, 45 unused UI components, no type-check). The live site could not
reach the backend, Persian-keyboard digits were erased from the phone field,
guests were signed out after an hour, "mark read" hit a method the server
does not accept (405), and iOS Safari blocked the payment window.

What changed:
- src/ split into api/ (one typed client: timeouts, GET retry, single-flight
  refresh, SSE), screens/ (one file each), components/, state/, nav/, lib/.
- The API address is configuration only: public/config.js at runtime, then
  VITE_API_BASE_URL at build time, then same-origin /api. No domain in code.
- Responsive shell instead of a fixed 390x844 phone frame; self-hosted
  Vazirmatn instead of Google Fonts; strict TypeScript, `npm run build`
  type-checks first.
- Everything on the handoff list: tutor answers keep their line breaks, have
  thumbs up/down, follow-ups, "from the book" badge, retry, history that
  survives tab switches and reloads, "why was I wrong" from a wrong answer
  with questionId; question reports; hearts with maxHearts/unlimited and a
  no-hearts screen with the refill time; result screen and a visible error
  when lesson completion fails; subscription status, badge, discount code
  before the gateway and the payment-return message; guest refresh token;
  6-digit OTP with resend timer; age saved as a Jalali birthYear; PATCH
  read-all; leaderboard refetched on open with "you" highlighted; selected
  companion shown across the app; logout that revokes the refresh token.
- New screens for endpoints that had none: profile, streak calendar,
  support tickets and bug reports, parents, invite-a-friend (with ?ref=
  tracking), guest-to-phone upgrade.
- Browser/phone back button walks the screen stack.

Checked with a headless-Chrome walkthrough against a local backend at
390x844 and 360x740 (fresh guest: onboarding, setup, a full lesson, map,
island sheet, every tab and sub-screen, one real tutor question, reload,
logout): no console errors, no failed API calls. Real SMS login and a real
Zarinpal payment were not exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:35:48 +03:30
b411b636f4 Merge pull request 'fixed' (#6) from api-branch into main
Reviewed-on: http://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend/pulls/6
2026-09-24 12:33:08 +03:30
267cf5fca0 fixed 2026-09-24 12:30:58 +03:30
d223336638 Merge pull request 'Leaderboard added' (#5) from api-branch into main
Reviewed-on: http://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend/pulls/5
2026-09-21 14:57:22 +03:30
15d110d824 Leaderboard added 2026-09-21 14:55:28 +03:30
a341ffd314 Merge pull request 'api' (#4) from api-branch into main
Reviewed-on: http://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend/pulls/4
2026-09-21 14:20:20 +03:30
74e048cc2a api 2026-09-21 14:14:38 +03:30
4705925022 Merge pull request 'mascots changed' (#2) from updateApp into main
Reviewed-on: http://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend/pulls/2
2026-09-13 22:11:51 +03:30
82c9e870b3 Merge pull request 'افزودنِ لایه‌ی اتصال به API بک‌اند (بدونِ تغییر در App.tsx)' (#1) from AliReza_Zareian/MathApp_frontend:zareian/api-client into main
Reviewed-on: http://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend/pulls/1
2026-09-13 22:07:55 +03:30
f61c58eb76 match the real API contract, verified against a running backend
اولین نسخه از روی حافظه و حدس نوشته شده بود و سه صدای مهم اصلاً کار نمی‌کردند.
این بار هر مسیر و هر نامِ فیلد از OpenAPI واقعیِ سرویس گرفته شد و بعد کلِ
کلاینت در برابرِ یک بک‌اندِ در حالِ اجرا تست شد.

چیزهایی که غلط بودند:
  • ثبتِ پاسخ به /lessons/{id}/answer می‌رفت — چنین مسیری وجود ندارد.
    مسیرِ درست /questions/{id}/answer است و فیلدش selectedOptionId، نه
    questionId/choiceId.
  • /ai/ask فیلدِ userQuestion می‌خواهد، نه question. با نامِ غلط، پرسش
    اصلاً به مدل نمی‌رسید.
  • فیدبک isHelpful بولین می‌خواهد، نه رشته‌ی up/down.
  • UserProfile فیلدهای xp/coins/streak را نداشت (آن‌ها در /user/summary
    هستند) و isGuest/phone را نداشت.
  • شناسه‌ها در پاسخ‌ها رشته‌اند، نه عدد.

پاسخِ دستیار هم usedChunks دارد نه sources؛ خالی بودنش یعنی از کتاب چیزی
پیدا نشده، که همان چیزی‌ست که برچسبِ «بر اساسِ کتاب» به آن نیاز دارد.

questions.report تازه اضافه شد: بک‌اند حالا POST /questions/{id}/report
دارد تا کاربر بتواند ایرادِ خودِ سوال را گزارش کند.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 21:41:24 +03:30
be9f99d52a add a typed API layer for the Tadrisino backend
اپ تا الان هیچ درخواستی به سرور نمی‌زد و همه‌ی داده‌ها داخلِ خودِ کامپوننت‌ها
هاردکد بود. این کامیت لایه‌ی ارتباط را اضافه می‌کند، بدونِ اینکه هیچ صفحه‌ای
را تغییر دهد.

عمداً فقط فایلِ جدید اضافه شده و App.tsx دست نخورده است، چون کارِ در جریانِ
برنچِ updateApp روی همان فایل است و نباید با هم تصادف کنند. وصل کردنِ
صفحه‌ها به این لایه کارِ بعدی‌ست.

هیچ پکیجِ جدیدی اضافه نشد — فقط fetch استاندارد.

چیزهایی که در client.ts هست برای حالتِ خراب‌اند، نه حالتِ خوب:
  • timeout روی هر درخواست — وگرنه بک‌اندِ کند سوکت‌ها را باز نگه می‌دارد تا
    مرورگر به سقفِ ۶ درخواستِ همزمان بخورد و اپ قفل شود.
  • backoff با jitter — بدونِ jitter همه‌ی کلاینت‌ها بعد از یک خطا همزمان
    دوباره می‌زنند و موجِ دوم از موجِ اول بدتر است.
  • تکرارِ خودکار فقط روی GET؛ POST فقط وقتی Idempotency-Key دارد (ثبتِ پاسخ،
    سفارش) تا دابل‌کلیکِ کاربر دو بار XP یا دو سفارش نسازد.
  • refresh تک‌نسخه‌ای — ۲۰ درخواستِ همزمانِ ۴۰۱ نباید ۲۰ بار refresh بزنند.
  • ادغامِ GETهای یکسانِ در جریان.
  • احترام به هدرِ Retry-After که بک‌اند موقعِ throttle می‌فرستد.

credentials: 'omit' عمدی‌ست: احرازِ هویت توکنِ Bearer در هدر است و سمتِ سرور
CORS_ALLOW_CREDENTIALS=False تنظیم شده.

.env هم به .gitignore اضافه شد؛ قبلاً فقط node_modules داخلش بود و آدرس/
تنظیماتِ محلی می‌توانست ناخواسته کامیت شود.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:06:28 +03:30
8ac87e40de mascots changed 2026-09-13 11:11:01 +03:30
3cca227337 m 2026-08-16 12:22:16 +03:30
fb6b47a1a5 app updated 2026-08-16 12:18:41 +03:30