2 Commits

Author SHA1 Message Date
21cce5ea2b make the relative /api paths in App.tsx actually reach the backend
کدِ auth داخلِ App.tsx مسیرهای نسبی صدا می‌زند (`/api/auth/login` و مانندش).
این مسیرها به دامنه‌ی خودِ فرانت می‌روند، nginx چون آن مسیر را نمی‌شناسد
index.html برمی‌گرداند، و نتیجه پاسخِ ۲۰۰ با بدنه‌ی HTML است — یعنی res.ok
درست به نظر می‌رسد و بعد res.json() با «Unexpected token '<'» می‌شکند.
همین الان روی math.tadrisino2.ir قابلِ بازتولید است.

در حالتِ توسعه پروکسیِ vite حلش می‌کند: هر /api به بک‌اند فرستاده می‌شود و
کدِ موجود بدونِ هیچ تغییری کار می‌کند. مقصد با VITE_API_PROXY_TARGET قابلِ
عوض کردن است، پیش‌فرض بک‌اندِ مشترک.

برای بیلدِ نهایی apiUrl() اضافه شد: fetch(apiUrl('/auth/login')) به‌جای
fetch('/api/auth/login'). یا اگر روی سرور مسیرِ /api به بک‌اند پروکسی شود،
مسیرهای نسبی همان‌طور که هستند درست کار می‌کنند — آن راه حتی بهتر است چون
same-origin می‌شود و CORS از میان برمی‌دارد.

App.tsx دست نخورده؛ تصمیمِ اینکه کدام راه را بروند با خودشان است.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 02:25:55 +03:30
007c450c0a add the subscription card fields
plans() حالا features و badge می‌دهد؛ بندهای کارتِ اشتراک و برچسبِ بالایش
دیگر لازم نیست در کد بمانند و عوض کردنشان به دیپلویِ تازه نیاز ندارد.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 02:03:19 +03:30
4 changed files with 39 additions and 0 deletions

View File

@@ -53,6 +53,8 @@ const answer = await api.ai.ask('این رو نفهمیدم', { lessonId });
استفاده کنید — یک درخواست برای کلِ مبحث، نه یکی به‌ازای هر کارت. استفاده کنید — یک درخواست برای کلِ مبحث، نه یکی به‌ازای هر کارت.
- `lessons.intro()` متن را سه‌تکه می‌دهد: `introText` (توضیح)، `example`، و - `lessons.intro()` متن را سه‌تکه می‌دهد: `introText` (توضیح)، `example`، و
`steps` (حلِ گام‌به‌گام). `kind` می‌گوید آزمون است یا ویدیو. `steps` (حلِ گام‌به‌گام). `kind` می‌گوید آزمون است یا ویدیو.
- `plans()` حالا `features` و `badge` می‌دهد — بندهای کارتِ اشتراک و برچسبِ
بالایش دیگر لازم نیست در کد بمانند؛ عوض کردنشان به دیپلویِ تازه نیاز ندارد.
- `profile.update({ age })` هم کار می‌کند؛ سرور خودش به سالِ تولدِ شمسی تبدیل - `profile.update({ age })` هم کار می‌کند؛ سرور خودش به سالِ تولدِ شمسی تبدیل
می‌کند. `age` و `birthYear` را با هم نفرستید. می‌کند. `age` و `birthYear` را با هم نفرستید.

View File

@@ -22,6 +22,21 @@ export { ApiError, localTokenStorage, createHttpClient };
export type { Tokens, HttpClient, ClientOptions, RequestOptions }; export type { Tokens, HttpClient, ClientOptions, RequestOptions };
export * from './types'; export * from './types';
/**
* آدرسِ کاملِ یک مسیرِ API.
*
* fetch(apiUrl('/auth/login'), …)
*
* برای کدی که هنوز مستقیم fetch می‌زند. مسیرِ نسبی (`/api/auth/login`) در
* بیلدِ نهایی به دامنه‌ی خودِ فرانت می‌رود، nginx چون مسیر را نمی‌شناسد
* index.html برمی‌گرداند، و نتیجه پاسخِ ۲۰۰ با بدنه‌ی HTML است که موقعِ
* res.json() با «Unexpected token '<'» می‌شکند.
*/
export function apiUrl(path: string): string {
const base = createHttpClient().baseUrl;
return `${base}${path.startsWith('/') ? path : `/${path}`}`;
}
/** پرسش از دستیار کند است (تولیدِ متن)؛ ۱۵ ثانیه‌ی پیش‌فرض کم است. */ /** پرسش از دستیار کند است (تولیدِ متن)؛ ۱۵ ثانیه‌ی پیش‌فرض کم است. */
const ASK_TIMEOUT_MS = 45_000; const ASK_TIMEOUT_MS = 45_000;

View File

@@ -142,6 +142,10 @@ export interface Plan {
popular: boolean; popular: boolean;
discountPercent: number; discountPercent: number;
durationDays: number; durationDays: number;
/** بندهای زیرِ کارتِ پلن — از سرور می‌آید، دیگر لازم نیست در کد بماند. */
features: string[];
/** برچسبِ بالای کارت: «محبوب‌ترین»، «بهترین ارزش». خالی یعنی بدونِ برچسب. */
badge: string;
} }
export type ReportReason = export type ReportReason =

View File

@@ -31,6 +31,24 @@ export default defineConfig({
}, },
}, },
// در حالتِ توسعه، هر درخواستِ /api به بک‌اند فرستاده می‌شود.
//
// چرا لازم است: کدِ اپ مسیرهای نسبی صدا می‌زند (مثلاً /api/auth/login). بدونِ
// این پروکسی، آن درخواست به خودِ سرورِ vite می‌رود و چون مسیرِ ناشناخته است
// index.html برمی‌گردد — یعنی پاسخِ ۲۰۰ ولی HTML، و کد موقعِ res.json() با
// «Unexpected token '<'» می‌شکند. خطایی که علتش از پیامش معلوم نیست.
//
// مقصد را با VITE_API_PROXY_TARGET عوض کنید (پیش‌فرض: بک‌اندِ مشترک).
server: {
proxy: {
'/api': {
target: process.env.VITE_API_PROXY_TARGET || 'https://api.math.tadrisino2.ir',
changeOrigin: true,
secure: true,
},
},
},
// File types to support raw imports. Never add .css, .tsx, or .ts files to this. // File types to support raw imports. Never add .css, .tsx, or .ts files to this.
assetsInclude: ['**/*.svg', '**/*.csv'], assetsInclude: ['**/*.svg', '**/*.csv'],
}) })