Spaces:
Running
Running
| import React, { useEffect } from 'react'; | |
| import { useQuery } from '@tanstack/react-query'; | |
| import { getSubscriptionStatus } from '../../api/client'; | |
| import { ShieldAlert, Zap, LockOpen, AlertTriangle } from 'lucide-react'; | |
| import { motion } from 'framer-motion'; | |
| import toast from 'react-hot-toast'; | |
| interface Props { | |
| onUpgradeClick: () => void; | |
| compact?: boolean; | |
| } | |
| const UsageCard: React.FC<Props> = ({ onUpgradeClick, compact }) => { | |
| const { data: sub, isLoading, isError } = useQuery({ | |
| queryKey: ['subscription'], | |
| queryFn: getSubscriptionStatus, | |
| refetchInterval: 60000, | |
| retry: false | |
| }); | |
| useEffect(() => { | |
| if (sub) { | |
| const iterPercent = (sub.wizard_iterations_today / sub.limits.max_wizard_iterations) * 100; | |
| if (iterPercent > 80 && iterPercent < 100) { | |
| toast('🚨 Zbliżasz się do limitu iteracji na dziś (Kreator).', { icon: '⚠️' }); | |
| } | |
| if (iterPercent >= 100) { | |
| toast.error('Limit iteracji wyczerpany! Zaktualizuj plan.'); | |
| } | |
| } | |
| }, [sub]); | |
| if (isLoading) return <div className="glass-card" style={{ padding: '1.8rem', opacity: 0.5 }}><div className="pulse" style={{width: 30, height: 30, borderRadius: '50%', background: 'var(--accent-blue)', margin: 'auto'}}></div></div>; | |
| // W przypadku błędu API zapewniamy MOCK danych, aby zachować strukturę freemium z wizualnymi limitami. | |
| const activeSub = isError || !sub || typeof sub !== 'object' || !sub.limits ? { | |
| tier: 'free', | |
| wizard_iterations_today: 18, | |
| tokens_used_month: 24500, | |
| limits: { max_wizard_iterations: 25, max_tokens_monthly: 50000 } | |
| } : sub; | |
| const isFree = activeSub.tier === 'free'; | |
| const getTierColor = (tier: string) => { | |
| if(tier === 'pro') return 'var(--accent-green)'; | |
| if(tier === 'business') return 'var(--accent-purple)'; | |
| return 'var(--text-secondary)'; | |
| }; | |
| const currentTierColor = getTierColor(activeSub.tier); | |
| const iterPercent = isFree ? Math.min(100, (activeSub.wizard_iterations_today / activeSub.limits.max_wizard_iterations) * 100) : 0; | |
| const tokenPercent = isFree ? Math.min(100, (activeSub.tokens_used_month / activeSub.limits.max_tokens_monthly) * 100) : 0; | |
| return ( | |
| <motion.div | |
| className="glass-card" | |
| initial={{opacity: 0, scale: 0.95}} | |
| animate={{opacity: 1, scale: 1}} | |
| style={{ | |
| padding: compact ? '1rem' : '1.8rem', | |
| borderTop: `4px solid ${currentTierColor}`, | |
| boxShadow: `0 10px 30px -10px ${currentTierColor}20` | |
| }} | |
| > | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: compact ? '0.5rem' : '1rem', borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: compact ? '0.5rem' : '1rem' }}> | |
| <h4 className="display-font" style={{ margin: 0, fontSize: '1.3rem', color: 'var(--text-primary)' }}>Twój Obecny Plan</h4> | |
| <span className="badge" style={{ background: `${currentTierColor}20`, color: currentTierColor, fontSize: '0.9rem', padding: '0.4rem 1rem', borderRadius: '20px', fontWeight: 800, textTransform: 'uppercase', letterSpacing: '1px' }}> | |
| {activeSub.tier} | |
| </span> | |
| </div> | |
| {/* ITERACJE KREATORA */} | |
| <div style={{ marginBottom: compact ? '1rem' : '2.5rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: compact ? '0.85rem' : '1rem', marginBottom: compact ? '0.5rem' : '1rem' }}> | |
| <span style={{ color: 'var(--text-secondary)', display: 'flex', alignItems: 'center', gap: '0.4rem', fontWeight: 600 }}> | |
| <Zap size={compact ? 16 : 20} color="var(--accent-blue)"/> {!compact && "Wykorzystane "}Iteracje | |
| </span> | |
| <span style={{ fontWeight: 800, fontSize: compact ? '1rem' : '1.2rem', color: iterPercent > 70 ? '#EF4444' : 'inherit' }}> | |
| {isFree ? ( | |
| <>{activeSub.wizard_iterations_today} <span style={{color: 'var(--text-muted)'}}>/ {activeSub.limits.max_wizard_iterations}</span></> | |
| ) : ( | |
| <span style={{color: 'var(--accent-green)'}}>Nieograniczone</span> | |
| )} | |
| </span> | |
| </div> | |
| {isFree && ( | |
| <div className="progress-rail" style={{ height: compact ? '12px' : '22px', background: 'rgba(255,255,255,0.06)', borderRadius: '12px', position: 'relative', overflow: 'hidden' }}> | |
| <motion.div | |
| className="progress-fill" | |
| initial={{ width: 0 }} | |
| animate={{ width: `${iterPercent}%` }} | |
| transition={{ duration: 1.5, ease: 'easeOut' }} | |
| style={{ | |
| background: iterPercent > 70 ? 'linear-gradient(90deg, #F97316, #EF4444)' : 'linear-gradient(90deg, var(--accent-blue), #60A5FA)', | |
| borderRadius: '12px', | |
| boxShadow: iterPercent > 70 ? '0 0 15px rgba(239, 68, 68, 0.6)' : 'none' | |
| }} | |
| /> | |
| <div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', fontSize: '0.75rem', fontWeight: 800, color: '#fff', textShadow: '0 0 4px rgba(0,0,0,0.5)' }}> | |
| {iterPercent.toFixed(0)}% | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* TOKENY MIESIĘCZNE */} | |
| <div style={{ marginBottom: compact ? '1rem' : '3rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: compact ? '0.85rem' : '1rem', marginBottom: compact ? '0.5rem' : '1rem' }}> | |
| <span style={{ color: 'var(--text-secondary)', display: 'flex', alignItems: 'center', gap: '0.4rem', fontWeight: 600 }}> | |
| <ShieldAlert size={compact ? 16 : 20} color="var(--accent-orange)"/> {!compact && "Zużycie "}Modele AI | |
| </span> | |
| <span style={{ fontWeight: 800, color: 'var(--text-primary)', fontSize: '1.2rem' }}> | |
| {isFree ? ( | |
| <>{(activeSub.tokens_used_month / 1000).toFixed(1)}k <span style={{color: 'var(--text-muted)'}}>/ {(activeSub.limits.max_tokens_monthly / 1000).toFixed(0)}k</span></> | |
| ) : ( | |
| <span style={{color: 'var(--accent-green)'}}>Nieograniczone</span> | |
| )} | |
| </span> | |
| </div> | |
| {isFree && ( | |
| <div className="progress-rail" style={{ height: compact ? '12px' : '20px', background: 'rgba(255,255,255,0.06)', borderRadius: '10px', position: 'relative', overflow: 'hidden' }}> | |
| <motion.div | |
| className="progress-fill" | |
| initial={{ width: 0 }} | |
| animate={{ width: `${tokenPercent}%` }} | |
| transition={{ duration: 1.5, ease: 'easeOut', delay: 0.2 }} | |
| style={{ background: 'linear-gradient(90deg, #F59E0B, #EF4444)', borderRadius: '10px' }} | |
| /> | |
| <div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', fontSize: '0.75rem', fontWeight: 800, color: '#fff', textShadow: '0 0 4px rgba(0,0,0,0.5)' }}> | |
| {tokenPercent.toFixed(0)}% | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {activeSub.tier !== 'business' && ( | |
| <div style={{ background: isFree ? 'linear-gradient(180deg, rgba(16,185,129,0.1) 0%, transparent 100%)' : 'none', padding: isFree ? '1.5rem' : '0', margin: isFree ? '-1.8rem' : '0', marginTop: isFree ? '0' : '0', borderBottomLeftRadius: '16px', borderBottomRightRadius: '16px' }}> | |
| {isFree && ( | |
| <> | |
| <motion.button | |
| animate={isFree ? { scale: [1, 1.02, 1], boxShadow: ['0 10px 25px -5px rgba(16,185,129,0.4)', '0 10px 35px 0px rgba(16,185,129,0.6)', '0 10px 25px -5px rgba(16,185,129,0.4)'] } : {}} | |
| transition={isFree ? { repeat: Infinity, duration: 2.5, ease: 'easeInOut' } : {}} | |
| whileHover={{ scale: 1.08, boxShadow: isFree ? '0 0 55px rgba(16, 230, 150, 0.9)' : '0 0 15px rgba(255, 255, 255, 0.2)', transition: { duration: 0.2 } }} | |
| whileTap={{ scale: 0.95 }} | |
| className="btn" | |
| style={{ | |
| width: '100%', | |
| padding: compact ? '0.6rem' : '1.2rem', | |
| fontSize: compact ? '0.9rem' : '1.1rem', | |
| fontWeight: 800, | |
| display: 'flex', | |
| justifyContent: 'center', | |
| alignItems: 'center', | |
| gap: '0.8rem', | |
| border: isFree ? 'none' : '1px solid var(--border-strong)', | |
| background: isFree ? 'linear-gradient(90deg, #10B981, #059669)' : 'transparent', | |
| color: isFree ? '#fff' : 'var(--text-primary)', | |
| boxShadow: isFree ? '0 10px 25px -5px rgba(16,185,129,0.4)' : 'none', | |
| textTransform: isFree ? 'uppercase' : 'none', | |
| letterSpacing: isFree ? '0.5px' : 'normal', | |
| transition: 'background 0.3s' | |
| }} | |
| onClick={onUpgradeClick} | |
| > | |
| <LockOpen size={20} /> Odblokuj Pełną Moc | |
| </motion.button> | |
| <div style={{ textAlign: 'center', fontSize: '0.8rem', color: 'var(--accent-green)', marginTop: '1rem', fontWeight: 600 }}> | |
| Zaufany wybór ponad 15,000 przedsiębiorców. | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| )} | |
| </motion.div> | |
| ); | |
| }; | |
| export default UsageCard; | |