import React, { useState, useEffect } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../context/AuthContext'; import { useSite } from '../context/SiteContext'; import { getSitePackages, subscribePackage, getActiveSubscriptions, Q } from '../api'; import SpotlightCard from '../components/bits/SpotlightCard'; import toast from 'react-hot-toast'; const resetLabelKeys = { never: 'packages.resetNever', daily: 'packages.resetDaily', weekly: 'packages.resetWeekly', monthly: 'packages.resetMonthly', }; function formatDate(unix) { if (!unix) return ''; return new Date(unix * 1000).toLocaleDateString(); } export default function Packages() { const { t } = useTranslation(); const { user, refreshUser } = useAuth(); const { site } = useSite(); const cs = site?.currency_symbol || '¥'; const navigate = useNavigate(); const [packages, setPackages] = useState([]); const [loading, setLoading] = useState(true); const [subscribing, setSubscribing] = useState(null); const [activeSubs, setActiveSubs] = useState([]); const [confirmPkg, setConfirmPkg] = useState(null); const getResetLabel = (period) => t(resetLabelKeys[period] || resetLabelKeys.never); useEffect(() => { getSitePackages() .then((r) => { if (r.data.success) setPackages(r.data.data || []); }) .catch(() => {}) .finally(() => setLoading(false)); }, []); // Load active subscriptions useEffect(() => { if (user) { getActiveSubscriptions() .then((r) => { if (r.data.success) setActiveSubs(r.data.data || []); }) .catch(() => {}); } }, [user]); const handleSubscribe = async (pkg) => { if (!user) { navigate('/register'); return; } setConfirmPkg(pkg); }; const confirmSubscribe = async () => { if (!confirmPkg) return; const pkgId = confirmPkg.id; setSubscribing(pkgId); try { const res = await subscribePackage(pkgId); if (res.data.success) { toast.success(t('packages.subscribedSuccess')); setConfirmPkg(null); await refreshUser(); // Reload subscriptions getActiveSubscriptions() .then((r) => { if (r.data.success) setActiveSubs(r.data.data || []); }) .catch(() => {}); } else { toast.error(res.data.message || t('common.requestFailed')); } } catch (e) { // Error already shown by axios interceptor } setSubscribing(null); }; if (loading) { return (
); } const enabled = packages.filter((p) => p.enabled); const spotlightColors = [ 'rgba(129,140,248,0.15)', 'rgba(192,132,252,0.15)', 'rgba(244,114,182,0.15)', 'rgba(34,197,94,0.15)', ]; return (

{t('packages.title')}

{t('packages.subtitle')}

{/* Active Subscriptions */} {activeSubs.length > 0 && (

{t('packages.mySubscriptions')}

{activeSubs.map((sub) => { const total = sub.amount_total || 0; const used = sub.amount_used || 0; const remain = Math.max(0, total - used); const pct = total > 0 ? Math.min(100, (used / total) * 100) : 0; return (
{t('packages.subscriptionId', { id: sub.id })} {t('packages.active')}
{t('packages.expires')}: {formatDate(sub.end_time)} {sub.next_reset_time > 0 && ( {t('packages.nextReset')}: {formatDate(sub.next_reset_time)} )}
{cs}{(remain / Q).toFixed(2)} / {cs}{(total / Q).toFixed(2)}
); })}
)} {enabled.length === 0 ? (

{t('packages.noPackages')}

{t('packages.checkPricing')} →
) : (
{enabled.map((pkg, i) => { const resetPeriod = pkg.quota_reset_period || 'never'; const isSubscription = resetPeriod !== 'never'; return (
{/* Header */}

{pkg.name}

{isSubscription && ( {getResetLabel(resetPeriod)} )}
{pkg.description && (

{pkg.description}

)}
{/* Price */}
{cs}{Number(pkg.price).toFixed(2)} {pkg.original_price > 0 && pkg.original_price > pkg.price && ( {cs}{Number(pkg.original_price).toFixed(2)} )}
{pkg.duration > 0 && (

{t('packages.daysAccess', { count: pkg.duration })}

)}
{/* Features */}
    {pkg.quota_amount > 0 && (
  • {isSubscription ? t('packages.periodicQuota', { amount: (pkg.quota_amount / Q).toFixed(2), period: getResetLabel(resetPeriod) }) : t('packages.creditIncluded', { amount: (pkg.quota_amount / Q).toFixed(2) }) }
  • )} {isSubscription && (
  • {t('packages.unusedQuotaExpires')}
  • )}
  • {t('packages.allModels')}
  • {t('packages.openaiApi')}
{/* CTA */}
)})}
)} {/* Confirmation Modal */} {confirmPkg && (() => { const userBalance = (user?.quota || 0) / Q; const pkgPrice = Number(confirmPkg.price); const insufficient = userBalance < pkgPrice; const resetPeriod = confirmPkg.quota_reset_period || 'never'; const isSubscription = resetPeriod !== 'never'; return (
!subscribing && setConfirmPkg(null)}>
e.stopPropagation()}>

{t('packages.confirmTitle')}

{t('packages.confirmDesc', { name: confirmPkg.name, price: pkgPrice.toFixed(2) })}

{isSubscription && (

{t('packages.subscriptionInfo', { period: getResetLabel(resetPeriod), days: confirmPkg.duration || 30, amount: (confirmPkg.quota_amount / Q).toFixed(2), })}

)}

{t('packages.yourBalance')} {cs}{userBalance.toFixed(2)}

{insufficient && (

{t('packages.insufficientBalance')}

)}
); })()}
); }