AliReza 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
2026-08-16 12:18:41 +03:30

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

اپِ وبِ موبایل‌محورِ تدریسینو (React + TypeScript + Vite + Tailwind). بک‌اندش ریپوی جداست (Tadrisino/MathApp)؛ قراردادِ API در /api/docs/ همان بک‌اند است.

راه‌اندازی

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

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

VITE_DEV_PROXY_TARGET=https://api.math.tadrisino2.ir

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

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

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

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

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

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

  • سریع، بدونِ build: روی سرور در dist/config.js بنویسید apiBaseUrl: "https://api.new-domain.ir/api" و کشِ CDN را برای همین فایل پاک کنید.
  • ماندگار: خطِ VITE_API_BASE_URL در .env.production را عوض کنید و build بگیرید.
  • سمتِ بک‌اند هم دامنه‌ی تازه‌ی فرانت باید مجاز باشد (FRONTEND_URL در .env بک‌اند). جزئیات در docs/operations.md همان ریپو.

دیپلوی

npm ci && npm run build

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

ساختار

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

قاعده‌ها:

  • اعداد: برای نمایش toFa، برای ورودی digitsOnly (کیبوردِ فارسی ارقامِ ۰-۹ می‌فرستد و \d در JS آن‌ها را نمی‌شناسد).
  • رنگ‌ها در src/styles/index.css (@theme) تعریف شده‌اند: bg-coral، text-ink، …
  • خطا: پیامِ کاربر همیشه از friendlyError می‌آید، نه متنِ خامِ سرور.
  • کلاینت را از حافظه ننویسید. قرارداد را از /api/docs/ بک‌اند بخوانید و تغییرِ types.ts را با پاسخِ واقعیِ سرور بسنجید.
Description
No description provided
Readme 2.4 MiB
Languages
TypeScript 94.4%
Kotlin 3.2%
CSS 1.2%
HTML 0.6%
Shell 0.4%
Other 0.2%