AliReza b75127c237 Session, navigation, payment and resilience fixes found in a behaviour review
- http: a request whose 401 arrived after another request's refresh had finished refreshed again;
  it now resends with the new token. A refresh finishing after logout no longer restores tokens.
- logout sends the refresh token current at send time (after a 401 -> refresh it had sent the
  rotated-out one and left the new session alive on the server for 30 days).
- Sign-out/sign-in in another browser tab is followed (storage event).
- Navigator removes its history entries when the main app unmounts (Back did nothing after logout).
- Login: +98 / 0098 / 9xxxxxxxxx phone input (autofill, typing, paste) normalised; incomplete OTP
  is not sent (each wrong guess burns one of five).
- Quiz resumes at the same question after hearts refill instead of restarting; the no-hearts
  screen re-checks while due (phone clock ahead of the server left it stuck at zero).
- Payment return: a pending order is re-checked for about a minute instead of shown as failed;
  the pay button is reset when the page comes back from bfcache.
- Tutor: retry keeps the quiz question id; a network error no longer forgets the conversation;
  daily quota detected by code ai_daily_quota.
- Root error boundary and vite:preloadError reload instead of a white screen; deploy.sh keeps
  hashed assets of earlier builds for 30 days so open apps can still load their chunks.
- Sheet: labelled dialog, focus moves in and back, Escape closes only the top sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:25:40 +03:30
2026-10-07 19:04:45 +03:30
2026-08-16 12:18:41 +03:30

تدریسینو — اپِ ریاضی (فرانت‌اند)

اپِ وبِ موبایل‌محورِ تدریسینو، آموزشِ ریاضی به سبکِ دولینگو برای پایه‌ی سوم تا دوازدهم: نقشه‌ای از جزیره‌ها برای هر کتاب، آزمون‌های کوتاه از مسئله‌های خودِ کتابِ درسی، دستیارِ هوشِ مصنوعی، همراه‌ها، زنجیره‌ی روزانه و اشتراک. همین مخزن پوسته‌ی اندرویدِ اپ را هم دارد (android/).

بک‌اند مخزنِ جداگانه‌ای است (Tadrisino/MathApp) و قراردادِ API اسکیمای OpenAPIِ همان‌جاست. این اپ تنها کلاینتِ آن API است.

سایتِ زنده https://math.tadrisino2.ir · API: https://mathapi.tadrisino2.ir
فناوری React 18 · TypeScript (strict) · Vite 6 · Tailwind 4 · Motion
وابستگی‌ها عمداً کم: بی‌کتابخانه‌ی router و state؛ ناوبری و داده‌ی مشترک در خودِ کد
اندروید پوسته‌ی WebView روی همین سایت، با پرداختِ درون‌برنامه‌ایِ کافه‌بازار (Poolakey)؛ بسته‌ی com.tadrisino.math.app

راه‌اندازی

npm install
npm run dev          # http://localhost:5173

در حالتِ توسعه، Vite درخواست‌های /api را به بک‌اند پاس می‌دهد، پس CORS مطرح نیست. پیش‌فرض بک‌اندِ محلی است (python manage.py runserver روی پورتِ ۸۰۰۰). بدونِ بک‌اندِ محلی، فایلِ .env.development.local بسازید:

VITE_DEV_PROXY_TARGET=https://mathapi.tadrisino2.ir

⚠️ در این حالت هر کاری در اپ بکنید روی دیتابیسِ واقعی نوشته می‌شود.

دستور کار
npm run dev سرورِ توسعه
npm run typecheck بررسیِ نوع‌ها (strict)
npm run build type-check و بعد build در dist/
npm run preview اجرای همان build

ساختار

src/
  config.ts            آدرسِ API (بخشِ پایین)
  api/                 تنها درِ ارتباط با سرور — هیچ جای دیگر fetch نمی‌زند
    http.ts            توکن، timeout، تکرارِ GET، refreshِ تک‌نسخه‌ای، SSE، Idempotency-Key
    index.ts           یک تابع برای هر اندپوینت
    types.ts           شکلِ پاسخ‌ها، از روی پاسخِ واقعیِ سرور
  app/                 App (جریانِ ورود) و MainApp (تب‌ها و صفحه‌ها)
  nav/                 ناوبریِ پشته‌ای، هماهنگ با دکمه‌ی back گوشی
  state/               داده‌ی مشترک (پروفایل، نقشه، کتابِ انتخاب‌شده، همراه) و گفتگوی دستیار
  screens/             یک فایل برای هر صفحه
  components/          اجزای مشترک: مسیرِ جزیره‌ها، نوارِ کتاب‌ها، برگه‌ی پایین، بازخورد، …
  mascots/             همراه‌ها (سیگما، دلتا، پی، امگا): SVG از کد، شش حالتِ چهره
  diagram/             شکلِ درس و جوابِ دستیار: توصیفِ JSON از بک‌اند → SVG؛ همه‌ی مختصات این‌جا حساب می‌شود
  lib/                 اعدادِ فارسی، تاریخِ شمسی، MathText، پیامِ خطا، صدا، پلِ کافه‌بازار، روش‌های پرداخت
android/               پوسته‌ی اندروید (README خودش)
pay.html · src/pay.ts  صفحه‌ی واسطِ پرداختِ زرین‌پال برای اپِ اندرویدِ فعلی

قاعده‌ها:

  • کلاینت را از حافظه ننویسید. قرارداد را از اسکیمای بک‌اند بخوانید (python manage.py spectacular --file schema.yaml --format openapi) و هر تغییرِ types.ts را با پاسخِ واقعیِ سرور بسنجید.
  • صفحه‌ها هرگز مستقیم سرور را صدا نمی‌زنند: همیشه screens/* → api/index.ts → api/http.ts.
  • اعداد: برای نمایش toFa، برای ورودی digitsOnly (کیبوردِ فارسی ارقامِ ۰-۹ می‌فرستد و \d در JS آن‌ها را نمی‌شناسد).
  • رنگ‌ها در src/styles/index.css (@theme) تعریف شده‌اند: bg-coral، text-ink، …
  • خطا: پیامِ کاربر همیشه از friendlyError می‌آید، نه متنِ خامِ سرور.
  • بازشدنِ فوری: کاربرِ واردشده خانه را از عکسِ روی گوشی می‌بیند (state/homeSnapshot.ts: پروفایل، پایه‌ها، نقشه، آمار) و همان درخواست‌های بوت پشتِ صحنه تازه‌اش می‌کنند. عکس با پاک شدنِ توکن و ورودِ تازه پاک می‌شود، با بیلدِ دیگر یا کهنه‌تر از ۷ روز خوانده نمی‌شود، و درستی (قفل، اشتراک، قلب) همیشه با سرور است. داده‌ی تازه‌ای که در خانه دیده می‌شود باید در AppData بماند تا در عکس هم برود.
  • متنِ فارسی بی‌اعراب و بی «ی»ِ اضافه‌ی نوشته‌شده («پایه‌ی سوم») است (تصمیمِ شرکت)؛ تنوین («قبلاً») می‌ماند. متنِ تازه را همین‌طور بنویسید. متنِ سرور را بک‌اند هنگامِ ساختنِ پاسخ پاک می‌کند و plainPersian در MathText همان کار را برای پاسخِ زنده‌ی دستیار. lib/plainMath.ts هر LaTeX یا markdownی را که برسد ساده می‌کند و باید با content/plain_math.pyِ بک‌اند هم‌رفتار بماند.

آدرسِ بک‌اند و عوض کردنِ دامنه

دامنه هیچ‌جای کد نیامده. آدرسِ API به این ترتیب خوانده می‌شود (src/config.ts):

  1. dist/config.js (از public/config.js) — در زمانِ اجرا؛ بعد از build هم روی سرور ویرایش‌شدنی است.
  2. VITE_API_BASE_URL — در زمانِ build، از .env.production.
  3. /api — هم‌دامنه، وقتی nginxِ دامنه‌ی فرانت مسیرِ /api را به بک‌اند پراکسی کند.

برای جابه‌جاییِ دامنه:

  • سریع، بدونِ build: روی سرور در dist/config.js بنویسید apiBaseUrl: "https://<دامنه‌ی تازه>/api" و کشِ آروان را برای همین فایل پاک کنید.
  • ماندگار: خطِ VITE_API_BASE_URL در .env.production را عوض کنید و build بگیرید.
  • سمتِ بک‌اند دامنه‌ی تازه‌ی فرانت باید مجاز باشد (FRONTEND_URL)، و در CSPِ nginx (connect-src) دامنه‌ی تازه‌ی API بیاید. رویه‌ی کامل: docs/operations.mdِ بک‌اند، بخشِ «عوض کردنِ دامنه».

روش‌های پرداخت

برگه‌ی پرداخت روش‌ها را از paymentMethods در dist/config.js می‌خواند (src/lib/paymentMethods.ts)، جدا برای مرورگر (web) و داخلِ اپِ اندروید (app)؛ مقدارها "zarinpal" و "bazaar". پیش‌فرض: مرورگر فقط زرین‌پال، اپ هر دو. نسخه‌ای که فقط در کافه‌بازار است: paymentMethods: { app: ["bazaar"] }؛ build لازم نیست، فقط کشِ آروانِ این فایل را پاک کنید.


انتشار

روی سرور (~/mathapp_frontend_new، که nginxِ math.tadrisino2.ir از dist/ش سرو می‌کند):

cd ~/mathapp_frontend_new && ./deploy.sh   # fetch، npm ci، type-check، build در dist.next و جابه‌جاییِ بی‌وقفه

buildِ قبلی در dist.prev می‌ماند (برگشت: rm -rf dist && mv dist.prev dist) و dist/config.jsِ ویرایش‌شده روی سرور به buildِ تازه منتقل می‌شود. سرور مشترکِ شرکت است: پیش از هر کار آن‌جا قواعدِ CLAUDE.mdِ بک‌اند (بخشِ «Working on the servers») را بخوانید.

جای دیگر: npm ci && npm run build و سرو کردنِ dist/. اپ مسیرِ جدا (route) ندارد و همه‌چیز از / بار می‌شود؛ فقط index.html و config.js نباید طولانی کش شوند (باقیِ فایل‌ها نامِ hashدار دارند). درگاهِ پرداخت کاربر را به /?orderId=…&status=… برمی‌گرداند و لینکِ دعوت /?ref=CODE است؛ هر دو را خودِ اپ می‌خواند.

CSPِ سایت در nginx است (ops/nginx/zareian-front-headers.conf در مخزنِ بک‌اند). اگر اسکریپتِ درون‌خطیِ index.html عوض شود، هشِ تازه‌اش باید در همان فایل بنشیند، وگرنه مرورگر آن را اجرا نمی‌کند.

Description
No description provided
Readme 2.5 MiB
Languages
TypeScript 94.3%
Kotlin 3.1%
CSS 1.2%
HTML 0.6%
Shell 0.6%
Other 0.2%