import React, { useState, useEffect, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { CheckIcon, XMarkIcon, SparklesIcon, UserIcon, CreditCardIcon, ClockIcon, ArrowPathIcon, GiftIcon, } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import type { SubscriptionPlan, MySubscriptionData, SubscriptionPeriod } from '../types'; import { usePaymentConfig } from '../hooks/usePaymentConfig'; import { formatRial, formatNumber } from '../lib/utils'; import Modal from '../components/ui/Modal'; import SettingsLayout from '../components/layout/SettingsLayout'; // ── Constants ───────────────────────────────────────────────────────────── export const PLAN_FEATURE_LABELS: Record = { patient_records: 'پرونده بیمار', services: 'مدیریت سرویس‌ها', sms_panel: 'پنل پیامک', }; /** The plan highlighted as "most popular" in the UI (no backend flag exists). */ const POPULAR_PLAN_NAME = 'basic'; const PLAN_META: Record = { free: { label: 'رایگان', desc: 'مناسب برای شروع کار', tint: 'var(--surface-2)' }, basic: { label: 'پایه', desc: 'مناسب برای مطب‌های کوچک و متوسط', tint: 'var(--primary-soft)' }, professional: { label: 'حرفه‌ای', desc: 'مناسب برای کلینیک‌های بزرگ', tint: 'var(--violet-bg)' }, }; export const planMetaOf = (name: string) => PLAN_META[name] ?? PLAN_META.free; // ── Main Page ───────────────────────────────────────────────────────────── export default function SubscriptionPage() { const qc = useQueryClient(); const [purchaseTarget, setPurchaseTarget] = useState<{ period: SubscriptionPeriod; planName: string } | null>(null); const [selectedGateway, setSelectedGateway] = useState(''); const { data: plansData, isLoading: plansLoading } = useQuery>({ queryKey: ['subscription-plans'], queryFn: () => api.get('/api/v1/subscription/plans'), }); const { data: myData, isLoading: myLoading } = useQuery>({ queryKey: ['subscription-my'], queryFn: () => api.get('/api/v1/subscription/my'), }); const { isTestMode, gateways } = usePaymentConfig(); useEffect(() => { if (gateways.length > 0 && !gateways.some((g) => g.name === selectedGateway)) { setSelectedGateway(gateways[0].name); } }, [gateways, selectedGateway]); // Highest plan first so, under RTL, the top-tier plan sits on the right (Figma order). const plans = [...(plansData?.data ?? [])].sort((a, b) => b.level - a.level); const myRaw = myData?.data; const my = myRaw?.subscription ?? null; const usedTrial = myRaw?.used_trial ?? false; const trialMutation = useMutation({ mutationFn: () => api.post('/api/v1/subscription/trial', {}), onSuccess: () => { qc.invalidateQueries({ queryKey: ['subscription-my'] }); toast.success('تریال رایگان با موفقیت فعال شد'); }, onError: (err: any) => toast.error(err.message), }); const purchaseMutation = useMutation({ mutationFn: ({ period_uuid, gateway, amount_rials }: { period_uuid: string; gateway: string; amount_rials: number }) => api.post<{ data: { pay_url?: string; redirect_url?: string; payment_url?: string } }>('/api/v1/subscription-payment', { period_uuid, gateway, amount_rials, // Gateway returns here (with ?payment_uuid&status); the success page reads them. frontend_address: `${window.location.origin}/admin/subscription/success`, }), onSuccess: (res: any) => { const url = res?.data?.pay_url ?? res?.data?.redirect_url ?? res?.data?.payment_url; if (url) window.location.href = url; else toast.error('خطا در دریافت لینک پرداخت'); }, onError: (err: any) => toast.error(err.message), }); return ( {/* ── Header ── */}

انتخاب پلن اشتراک

پلن اشتراک مناسب خود را انتخاب نمایید:

{/* ── وضعیت پلن فعلی ── */} {!myLoading && } {/* ── کارت‌های پلن ── */} {plansLoading ? (
{[1, 2, 3].map((i) => (
))}
) : (
{plans.map((plan) => ( 0} usedTrial={usedTrial} onPurchase={(period) => setPurchaseTarget({ period, planName: plan.name })} onTrial={() => trialMutation.mutate()} trialPending={trialMutation.isPending} /> ))}
)} {/* ── Modal پرداخت (انتخاب درگاه) ── */} { setPurchaseTarget(null); setSelectedGateway(gateways[0]?.name ?? ''); }} title="پرداخت اشتراک" size="sm" > {purchaseTarget && (
دوره انتخابی
{planMetaOf(purchaseTarget.planName).label} — {purchaseTarget.period.label}
{purchaseTarget.period.duration_months} ماه
مبلغ قابل پرداخت
{formatRial(purchaseTarget.period.price_rials)}
{isTestMode ? (
⚠️
درگاه آزمایشی فعال است
پول واقعی کسر نخواهد شد — این تراکنش آزمایشی است
) : gateways.length === 0 ? (
در حال حاضر هیچ درگاه پرداخت فعالی وجود ندارد. لطفاً با پشتیبانی تماس بگیرید.
) : (
انتخاب درگاه پرداخت
{gateways.map((gw) => ( ))}
)}
)}
); } // ── Current plan banner ───────────────────────────────────────────────────── function CurrentPlanBanner({ my }: { my: MySubscriptionData['subscription'] }) { if (!my) return null; const meta = planMetaOf(my.plan.name); const daysLeft = my.days_remaining ?? 0; const expiring = daysLeft > 0 && daysLeft <= 7; return (
پلن فعلی شما: {meta.label} {my.is_trial && تریال}
{daysLeft > 0 ? (
{formatNumber(daysLeft)} روز تا پایان اشتراک
) : (
اشتراک فعالی ندارید
)}
مشاهده فاکتور
); } // ── PlanCard ────────────────────────────────────────────────────────────── function PlanCard({ plan, currentPlanName, currentPlanLevel, currentPlanActive, usedTrial, onPurchase, onTrial, trialPending, }: { plan: SubscriptionPlan; currentPlanName: string; currentPlanLevel: number; currentPlanActive: boolean; usedTrial: boolean; onPurchase: (period: SubscriptionPeriod) => void; onTrial: () => void; trialPending: boolean; }) { const meta = planMetaOf(plan.name); const isCurrent = plan.name === currentPlanName; const isPopular = plan.name === POPULAR_PLAN_NAME; const isDowngrade = currentPlanActive && (plan.level ?? 0) < currentPlanLevel; const paidPeriods = useMemo( () => [...plan.periods].filter((p) => !p.is_trial).sort((a, b) => a.duration_months - b.duration_months), [plan.periods], ); const trialPeriod = plan.periods.find((p) => p.is_trial); // Default to the longest (best-value) period, matching the Figma default. const [selectedUuid, setSelectedUuid] = useState(''); useEffect(() => { if (paidPeriods.length > 0 && !paidPeriods.some((p) => p.uuid === selectedUuid)) { setSelectedUuid(paidPeriods[paidPeriods.length - 1].uuid); } }, [paidPeriods, selectedUuid]); const selectedPeriod = paidPeriods.find((p) => p.uuid === selectedUuid) ?? paidPeriods[0]; const accent = isPopular ? 'var(--primary)' : 'var(--border-2)'; return (
{/* بج محبوب‌ترین */} {isPopular && ( محبوب‌ترین )} {/* سربرگ */}
پلن {meta.label}
{meta.desc}
{formatNumber(plan.max_secretaries)} منشی
{/* توگل دوره */} {paidPeriods.length > 1 && (
{paidPeriods.map((p) => { const on = p.uuid === selectedPeriod?.uuid; return ( ); })}
)} {/* قیمت */}
{selectedPeriod ? (
قیمت: {formatRial(selectedPeriod.price_rials)}
) : (
رایگان
)}
{/* امکانات */}
    {Object.entries(plan.features).map(([key, enabled]) => (
  • {enabled ? : } {PLAN_FEATURE_LABELS[key] ?? key}
  • ))}
{/* تریال */} {trialPeriod && !usedTrial && plan.level > 0 && !isDowngrade && (
تریال {formatNumber(trialPeriod.duration_months)} ماهه رایگان
)}
{/* دکمه خرید */}
{isDowngrade ? ( ) : selectedPeriod ? ( ) : ( )}
); }