Files
MathApp_frontend/src/state/curriculum.ts
AliReza a8a5351862 Subscription store, paywall on locked units, profile subscription bar, Tadrisino branding
- Store opens with two choices (subscription / question packs); 3- and 6-month cards
  show per-month price and saving.
- Units that need a subscription show a crown badge and open the store; a 402 from
  the quiz goes there too. «ادامه بده» skips locked units.
- Profile: subscription card with an animated days-left bar (planDurationDays).
- BrandMark (SVG) on splash, home header and footer; copyright footer; floating math
  glyphs on splash. CSS transform/opacity only; honours reduced motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:19:09 +03:30

59 lines
2.4 KiB
TypeScript

import type { HomeNode, HomeResponse, HomeUnit, NodeStatus } from '@/api';
/** رنگِ فصل‌ها؛ سرور برای فصل رنگ نمی‌دهد، پس چرخشی انتخاب می‌شود. */
const UNIT_PALETTE = ['#ED6956', '#91ADC6', '#97C0C0', '#9B7BFF', '#FF7A3D', '#F3A9B0', '#4C5FD5', '#5FBF8F', '#E06BAE'];
export const unitColor = (index: number) => UNIT_PALETTE[((index % UNIT_PALETTE.length) + UNIT_PALETTE.length) % UNIT_PALETTE.length];
export function unitProgress(unit: HomeUnit) {
const total = unit.nodes.length;
const done = unit.nodes.filter((n) => n.status === 'completed').length;
return { done, total, percent: total ? Math.round((done / total) * 100) : 0 };
}
export const isPlayable = (status: NodeStatus) => status !== 'locked';
export interface NodeRef {
unit: HomeUnit;
unitIndex: number;
node: HomeNode;
}
/** اولین جزیره‌ی بازِ ناتمام در کلِ نقشه؛ برای «ادامه بده». */
export function findContinue(home: HomeResponse | null): NodeRef | null {
if (!home) return null;
for (let i = 0; i < home.units.length; i++) {
if (home.units[i].requiresSubscription) continue;
const node = home.units[i].nodes.find((n) => n.status !== 'completed' && n.status !== 'locked');
if (node) return { unit: home.units[i], unitIndex: i, node };
}
return null;
}
export function findNode(home: HomeResponse | null, nodeId: string | undefined): NodeRef | null {
if (!home || !nodeId) return null;
for (let i = 0; i < home.units.length; i++) {
const node = home.units[i].nodes.find((n) => n.id === nodeId);
if (node) return { unit: home.units[i], unitIndex: i, node };
}
return null;
}
export function findUnit(home: HomeResponse | null, unitId: string) {
const index = home?.units.findIndex((u) => u.id === unitId) ?? -1;
return index >= 0 ? { unit: home!.units[index], unitIndex: index } : null;
}
/** جزیره‌ای که این درس در آن است. */
export function findNodeOfLesson(home: HomeResponse | null, lessonId: number): NodeRef | null {
if (!home) return null;
for (let i = 0; i < home.units.length; i++) {
const node = home.units[i].nodes.find((n) => n.lessonIds.includes(lessonId));
if (node) return { unit: home.units[i], unitIndex: i, node };
}
return null;
}
/** درسی که با زدنِ جزیره باز می‌شود. */
export const nextLessonOf = (node: HomeNode): number | null => node.nextLessonId ?? node.lessonIds[0] ?? null;