GrantForge Bot
Deploy to Hugging Face
afd56bc
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;