diff --git a/android/README.md b/android/README.md index f0fd601..8709348 100644 --- a/android/README.md +++ b/android/README.md @@ -54,7 +54,7 @@ |---|---|---| | اسمِ پکیج | `applicationId` در `app/build.gradle.kts` | `com.tadrisino.math.app`؛ بازار محصول‌های ۱۰۳۴۲–۱۰۳۴۴ را زیرِ همین نام شناخت (API، توکنِ ساختگی) | | کلیدِ RSAِ عمومیِ اپ | `BAZAAR_RSA_KEY` در `app/build.gradle.kts` | گذاشته شد | -| SKUِ هر پلن | پنلِ ادمینِ بک‌اند: `/admin/subscription/plan/` ← «Bazaar sku» | یک‌ماهه `10342`، سه‌ماهه `10343`، شش‌ماهه `10344`؛ بسته‌ها هنوز در پیشخان تعریف نشده‌اند | +| SKUِ هر پلن | پنلِ ادمینِ بک‌اند: `/admin/subscription/plan/` ← «Bazaar sku» | یک‌ماهه `10342`، سه‌ماهه `10343`، شش‌ماهه `10344`؛ بسته‌ی سوال فروخته نمی‌شود | | کلیدِ «تخفیف پویا» | فقط `.env`ِ سرور (`BAZAAR_DYNAMIC_PRICE_KEY`)، **هرگز در اپ** | روی سرور | قیمت: فرانت `TadrisinoAndroid.bazaarPrices(skusJson, productType)` را صدا می‌زند و قیمتِ پیشخان را روی کاشیِ بازار نشان می‌دهد. diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx index 3e4ed05..639f6f0 100644 --- a/src/app/MainApp.tsx +++ b/src/app/MainApp.tsx @@ -283,7 +283,7 @@ function RouteView({ route, onSignedOut }: { route: Route } & Pick; case 'lessons': return ; case 'ask': return ; - case 'subscription': return ; + case 'subscription': return ; case 'profile': return ; case 'unit': return ; case 'lesson': return ; diff --git a/src/components/BottomNav.tsx b/src/components/BottomNav.tsx index fafeb35..047abe7 100644 --- a/src/components/BottomNav.tsx +++ b/src/components/BottomNav.tsx @@ -8,7 +8,7 @@ const ITEMS: { id: TabName; icon: LucideIcon; label: string }[] = [ { id: 'home', icon: Home, label: 'خانه' }, { id: 'lessons', icon: BookOpen, label: 'درس‌ها' }, { id: 'ask', icon: Brain, label: 'دستیار' }, - { id: 'subscription', icon: ShoppingBag, label: 'بسته‌ها' }, + { id: 'subscription', icon: ShoppingBag, label: 'اشتراک' }, { id: 'profile', icon: UserRound, label: 'من' }, ]; diff --git a/src/nav/routes.ts b/src/nav/routes.ts index d81cfd4..c5c8500 100644 --- a/src/nav/routes.ts +++ b/src/nav/routes.ts @@ -4,7 +4,7 @@ export type Route = | { name: 'lessons' } | { name: 'ask'; lessonId?: number; questionId?: string; prompt?: string } /** view: مستقیم به فهرستِ اشتراک‌ها یا بسته‌ها (مثلاً از فصلِ قفل)؛ بی‌آن انتخابِ دوگزینه‌ای. */ - | { name: 'subscription'; view?: 'subscription' | 'pack' } + | { name: 'subscription' } | { name: 'profile' } | { name: 'unit'; unitId: string } | { name: 'lesson'; lessonId: number; nodeId?: string } diff --git a/src/screens/AskScreen.tsx b/src/screens/AskScreen.tsx index 23ded16..76a9c21 100644 --- a/src/screens/AskScreen.tsx +++ b/src/screens/AskScreen.tsx @@ -315,7 +315,7 @@ function Message({
{message.quotaReached ? ( ) : message.failedQuestion ? ( + {credits > 0 ? ( + {toFa(credits)} سوالِ خریده‌شده + ) : free === 0 ? ( + + ) : null} ); } diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 8691244..4b71a1f 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -25,7 +25,7 @@ export function HomeScreen() { const { profile, gradeTitle, summary, home, homeError, companion, unreadCount, refreshHome } = useAppData(); const nav = useNav(); // فصلِ غیرِ اول بی‌اشتراک: زدنِ جزیره یک‌راست فهرستِ اشتراک‌ها را باز می‌کند. - const paywall = () => nav.push({ name: 'subscription', view: 'subscription' }); + const paywall = () => nav.push({ name: 'subscription' }); const [openNode, setOpenNode] = useState<{ node: HomeNode; color: string } | null>(null); const currentRef = useRef(null); const scrolled = useRef(false); diff --git a/src/screens/LessonsScreen.tsx b/src/screens/LessonsScreen.tsx index 35bf48c..60520ae 100644 --- a/src/screens/LessonsScreen.tsx +++ b/src/screens/LessonsScreen.tsx @@ -130,7 +130,7 @@ function UnitCard({ className="overflow-hidden rounded-2xl bg-white" style={{ border: `2px solid ${open ? color : '#e9e6f7'}`, boxShadow: open ? `0 4px 0 ${color}40` : '0 2px 0 #e9e6f7' }} > - - ); -} - /** * وضعیتِ کاربر بالای فروشگاه. مشترک: یک کارتِ واضح با روزهای مانده. بی‌اشتراک: هیچ کارتی (دو گزینه‌ی زیر * خودشان حرف را می‌زنند)، مگر سوالِ خریده‌شده یا قلبِ هدیه داشته باشد. تا وضعیت نرسیده جای خالیِ هم‌اندازه @@ -215,36 +155,19 @@ function Pill({ icon, text }: { icon: ReactNode; text: string }) { ); } -/** مزیت‌های اشتراک، یک بار. */ -function Perks({ items }: { items: string[] }) { - return ( -
    - {items.map((f) => ( -
  • - - {f} -
  • - ))} -
- ); -} - -/** یک ردیفِ انتخابی: نام و قیمت درشت؛ فقط یک نکته‌ی کوچک (تخفیف یا هدیه)، نه پنج خطِ ریز. */ -function PlanRow({ plan, monthlyPrice, selected, onSelect }: { plan: Plan; monthlyPrice?: number; selected: boolean; onSelect: () => void }) { - const isPack = plan.kind === 'pack'; +/** یک کارتِ انتخابی: نام و قیمت درشت، و زیرش آنچه این اشتراک می‌دهد (خواستِ سینیور: روی هر کارت). */ +function PlanRow({ plan, assistant, monthlyPrice, selected, onSelect }: { plan: Plan; assistant: string; monthlyPrice?: number; selected: boolean; onSelect: () => void }) { const months = Math.round(plan.durationDays / 30); - const saving = !isPack && monthlyPrice && months > 1 ? Math.round((1 - plan.price / (monthlyPrice * months)) * 100) : 0; - const tone = isPack ? '#9B7BFF' : '#FF7A3D'; - const note = isPack - ? plan.heartsDays > 0 ? `+ ${toFa(plan.heartsDays)} روز قلبِ نامحدود هدیه` : '' - : saving > 0 ? `${toFa(saving)}٪ ارزان‌تر از ماهانه` : ''; + const saving = monthlyPrice && months > 1 ? Math.round((1 - plan.price / (monthlyPrice * months)) * 100) : 0; + const tone = '#FF7A3D'; + const perks = [...plan.features, `${toFa(plan.aiDailyQuota)} سوال در روز از ${assistant}`]; return ( ); } @@ -411,7 +342,7 @@ function CheckoutSheet({ plan, storePrices, onClose, onPaid }: { plan: Plan | nu : 'پرداختت در بازار تأیید شد و به حسابت اضافه شد.'; return ( - + {done ? ( <>

{doneText}

diff --git a/src/screens/UnitScreen.tsx b/src/screens/UnitScreen.tsx index bbd4bb2..a1d2c62 100644 --- a/src/screens/UnitScreen.tsx +++ b/src/screens/UnitScreen.tsx @@ -14,7 +14,7 @@ import { MathText } from '@/lib/MathText'; export function UnitScreen({ unitId }: { unitId: string }) { const { home, homeError, refreshHome, companion } = useAppData(); const nav = useNav(); - const paywall = () => nav.push({ name: 'subscription', view: 'subscription' }); + const paywall = () => nav.push({ name: 'subscription' }); const [openNode, setOpenNode] = useState(null); const found = findUnit(home, unitId);