forked from Tadrisino/MathApp_frontend
- Text: kasra/fatha/damma/shadda/sukun/hamza-above and «هی» ezafe removed from every user-visible string (comments untouched); tanwin kept. Stale copy fixed (question packs are no longer sold), awkward onboarding sentences rewritten, سؤال → سوال. plainPersian() in MathText cleans the live tutor stream. - Home: the mascot + speech bubble header is gone; the selected companion hops beside the current island and sits at every third bend (CSS transform animations only). - Checkout: order summary, payment-method rows with Zarinpal/Bazaar icons and radio, amount on the pay button, lock trust line, darker overlay. paymentMethods in config.js per channel (web: zarinpal, app: zarinpal+bazaar by default). - Profile: «ورود با حساب دیگر» removed (it was logout); new «حساب کاربری» sheet shows phone/ID and holds «حذف حساب کاربری»; logout is a red row. Delete sheet tells Bazaar subscribers to cancel renewal in Bazaar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
111 lines
5.2 KiB
TypeScript
111 lines
5.2 KiB
TypeScript
import { useState } from 'react';
|
||
import { BookOpen, CheckCircle2, ChevronDown, Circle, Lock, Play, RotateCw } from 'lucide-react';
|
||
import { api, type HomeNode } from '@/api';
|
||
import { useNav } from '@/nav/Navigator';
|
||
import { useAsync } from '@/lib/useAsync';
|
||
import { Sheet } from '@/components/Sheet';
|
||
import { ErrorState, PrimaryButton } from '@/components/ui';
|
||
import { toFa } from '@/lib/format';
|
||
import { MathText } from '@/lib/MathText';
|
||
|
||
/**
|
||
* برگهی جزیره. دو دکمه: «اول درسها رو بخون» (رنگی و بالا، چون تستکنندهها
|
||
* دکمهی کمرنگِ قبلی را پیدا نمیکردند) و «شروعِ آزمونِ جزیره». فهرستِ درسها زیرِ دکمهی اول باز میشود.
|
||
*/
|
||
export function IslandSheet({ node, color, onClose }: { node: HomeNode | null; color: string; onClose: () => void }) {
|
||
const nav = useNav();
|
||
const [showLessons, setShowLessons] = useState(false);
|
||
const lessons = useAsync(
|
||
() => (node && showLessons ? api.nodeLessons(node.id) : Promise.resolve(null)),
|
||
[node?.id, showLessons],
|
||
);
|
||
const locked = node?.status === 'locked';
|
||
const done = node?.status === 'completed';
|
||
|
||
const close = () => {
|
||
setShowLessons(false);
|
||
onClose();
|
||
};
|
||
const start = () => {
|
||
if (!node) return;
|
||
close();
|
||
nav.push({ name: 'quiz', nodeId: node.id });
|
||
};
|
||
const openLesson = (lessonId: number) => {
|
||
if (!node) return;
|
||
close();
|
||
nav.push({ name: 'lesson', lessonId, nodeId: node.id });
|
||
};
|
||
|
||
// فقط درسهایی که سوال دارند در lessonIds هستند.
|
||
const playable = new Set(node?.lessonIds ?? []);
|
||
const list = lessons.data?.lessons.filter((l) => playable.has(Number(l.id))) ?? [];
|
||
|
||
return (
|
||
<Sheet open={node !== null} onClose={close} title={node ? <MathText text={node.title} /> : undefined}>
|
||
{locked ? (
|
||
<div className="flex flex-col items-center gap-3 py-4 text-center">
|
||
<span className="flex h-14 w-14 items-center justify-center rounded-full bg-soft">
|
||
<Lock size={24} className="text-muted" />
|
||
</span>
|
||
<p className="text-sm leading-7 text-muted">این جزیره هنوز قفله. جزیره قبلی رو تموم کن تا باز بشه.</p>
|
||
</div>
|
||
) : (
|
||
<>
|
||
<p className="mb-4 text-sm leading-7 text-body">
|
||
{done
|
||
? 'این جزیره رو تموم کردی! میتونی دوباره تمرینش کنی (امتیاز تازه نداره).'
|
||
: 'اگه مطمئن نیستی، اول درسها رو بخون؛ بعد آزمون کوتاه جزیره رو بده. هر جوابی بدی، آخرش جزیره بعدی باز میشه.'}
|
||
</p>
|
||
|
||
<PrimaryButton color={color} onClick={start}>
|
||
{done ? <><RotateCw size={18} /> دوباره تمرین کن</> : <><Play size={18} fill="white" /> شروع آزمون جزیره</>}
|
||
</PrimaryButton>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowLessons((s) => !s)}
|
||
className="mt-3 mb-1 flex w-full items-center justify-center gap-2 rounded-2xl border-2 py-3 text-sm font-black transition-colors active:translate-y-[2px]"
|
||
style={{ borderColor: '#39FF14', color: '#15803d', backgroundColor: showLessons ? '#39FF1422' : 'white' }}
|
||
aria-expanded={showLessons}
|
||
>
|
||
<BookOpen size={18} /> {done ? 'مرور درسها' : 'اول درسها رو بخون'}
|
||
<ChevronDown size={16} className={`transition-transform ${showLessons ? 'rotate-180' : ''}`} />
|
||
</button>
|
||
|
||
{showLessons && (
|
||
lessons.loading ? (
|
||
<div className="py-4 text-center text-sm text-muted">دارم درسها رو میارم…</div>
|
||
) : lessons.error ? (
|
||
<ErrorState error={lessons.error} onRetry={lessons.reload} />
|
||
) : (
|
||
<>
|
||
<p className="mb-2 mt-1 text-xs text-muted">
|
||
{toFa(list.filter((l) => l.done).length)} از {toFa(list.length)} درس خونده شده
|
||
</p>
|
||
<ul className="flex max-h-60 flex-col gap-1.5 overflow-y-auto">
|
||
{list.map((lesson) => (
|
||
<li key={lesson.id}>
|
||
<button
|
||
type="button"
|
||
onClick={() => openLesson(Number(lesson.id))}
|
||
className="flex w-full items-center gap-3 rounded-2xl border border-line bg-white px-3 py-2.5 text-right text-sm"
|
||
>
|
||
{lesson.done ? (
|
||
<CheckCircle2 size={18} className="flex-shrink-0 text-mint" />
|
||
) : (
|
||
<Circle size={18} className="flex-shrink-0 text-faint" />
|
||
)}
|
||
<span className={`flex-1 ${lesson.done ? 'text-muted' : 'text-ink'}`}><MathText text={lesson.title} /></span>
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</>
|
||
)
|
||
)}
|
||
</>
|
||
)}
|
||
</Sheet>
|
||
);
|
||
}
|