feat: modernize sign-in and responsive refactor
This commit is contained in:
198
src/components/features/BudgetManager.tsx
Normal file
198
src/components/features/BudgetManager.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import {
|
||||
Wallet,
|
||||
TrendingUp,
|
||||
Home,
|
||||
Car,
|
||||
Utensils,
|
||||
ShoppingCart,
|
||||
Target,
|
||||
ArrowRight,
|
||||
AlertCircle
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Mock Data
|
||||
const budgetData = {
|
||||
limit: 15000,
|
||||
spent: 12500,
|
||||
currency: 'AED',
|
||||
categories: [
|
||||
{ name: 'Housing', icon: Home, spent: 6500, limit: 7000, color: 'bg-blue-500' },
|
||||
{ name: 'Food & Dining', icon: Utensils, spent: 2800, limit: 3000, color: 'bg-green-500' },
|
||||
{ name: 'Transportation', icon: Car, spent: 1200, limit: 1500, color: 'bg-orange-500' },
|
||||
{ name: 'Shopping', icon: ShoppingCart, spent: 2000, limit: 1500, color: 'bg-pink-500' }, // Over budget
|
||||
],
|
||||
goals: [
|
||||
{ name: 'Buy a House', target: 2000000, current: 450000, color: 'bg-purple-500' },
|
||||
{ name: 'Retirement', target: 5000000, current: 120000, color: 'bg-indigo-500' },
|
||||
]
|
||||
};
|
||||
|
||||
export default function BudgetManager() {
|
||||
const percentage = Math.min(100, (budgetData.spent / budgetData.limit) * 100);
|
||||
const circumference = 2 * Math.PI * 120; // Radius 120
|
||||
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-8 pb-20 animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-slate-900">Monthly Budget & Goals</h1>
|
||||
<p className="text-sm sm:text-base text-slate-500">Track your spending and save for the future.</p>
|
||||
</div>
|
||||
<Button className="w-full sm:w-auto gap-2 bg-slate-900 text-white hover:bg-slate-800 rounded-full">
|
||||
<Wallet className="w-4 h-4" /> Edit Budget
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 sm:gap-8">
|
||||
{/* Main Budget Ring */}
|
||||
<GlassCard className="col-span-1 lg:col-span-1 flex flex-col items-center justify-center p-6 sm:p-8 relative overflow-hidden">
|
||||
<div className="relative w-64 h-64 sm:w-72 sm:h-72 flex items-center justify-center">
|
||||
{/* Background Circle */}
|
||||
<svg className="w-full h-full transform -rotate-90">
|
||||
<circle
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r="45%"
|
||||
stroke="currentColor"
|
||||
strokeWidth="24"
|
||||
fill="transparent"
|
||||
className="text-slate-100"
|
||||
/>
|
||||
{/* Progress Circle */}
|
||||
<motion.circle
|
||||
initial={{ strokeDashoffset: circumference }}
|
||||
animate={{ strokeDashoffset }}
|
||||
transition={{ duration: 1.5, ease: "easeOut" }}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r="45%"
|
||||
stroke="currentColor"
|
||||
strokeWidth="24"
|
||||
fill="transparent"
|
||||
strokeDasharray={circumference}
|
||||
strokeLinecap="round"
|
||||
className={cn(
|
||||
"text-blue-500 drop-shadow-lg",
|
||||
percentage > 90 ? "text-red-500" : percentage > 75 ? "text-orange-500" : "text-blue-500"
|
||||
)}
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center">
|
||||
<span className="text-xs sm:text-sm text-slate-500 font-medium uppercase tracking-wider">Total Spent</span>
|
||||
<span className="text-3xl sm:text-4xl font-bold text-slate-900 mt-1">
|
||||
{budgetData.currency} {budgetData.spent.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-xs sm:text-sm text-slate-400 mt-1">
|
||||
of {budgetData.currency} {budgetData.limit.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{percentage > 90 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mt-6 flex items-center gap-2 text-red-600 bg-red-50 px-4 py-2 rounded-full border border-red-100"
|
||||
>
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">You've used {percentage.toFixed(0)}% of your budget!</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</GlassCard>
|
||||
|
||||
{/* Categories & Goals */}
|
||||
<div className="col-span-1 lg:col-span-2 space-y-8">
|
||||
{/* Categories Breakdown */}
|
||||
<GlassCard className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-6 flex items-center gap-2">
|
||||
<Utensils className="w-5 h-5 text-slate-500" /> Category Breakdown
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
{budgetData.categories.map((cat, index) => (
|
||||
<motion.div
|
||||
key={cat.name}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="space-y-2"
|
||||
>
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={cn("p-1.5 rounded-lg text-white", cat.color)}>
|
||||
<cat.icon className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<span className="font-medium text-slate-700">{cat.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-semibold">{budgetData.currency} {cat.spent.toLocaleString()}</span>
|
||||
<span className="text-slate-400">/ {cat.limit.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-2.5 bg-slate-100 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${Math.min(100, (cat.spent / cat.limit) * 100)}%` }}
|
||||
transition={{ duration: 1, delay: 0.5 + (index * 0.1) }}
|
||||
className={cn("h-full rounded-full", cat.color)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Long-term Goals */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Target className="w-5 h-5 text-slate-500" /> Long-term Planning
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{budgetData.goals.map((goal, index) => (
|
||||
<GlassCard key={goal.name} className="p-5 relative overflow-hidden group">
|
||||
<div className="relative z-10">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-900">{goal.name}</h4>
|
||||
<div className="bg-green-100 text-green-700 text-xs px-2 py-0.5 rounded-full inline-block mt-1 font-medium">
|
||||
On Track
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("p-2 rounded-xl text-white opacity-80", goal.color)}>
|
||||
<Target className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-slate-500">Progress</span>
|
||||
<span className="font-bold text-slate-900">{Math.round((goal.current / goal.target) * 100)}%</span>
|
||||
</div>
|
||||
<Progress value={(goal.current / goal.target) * 100} className="h-2" indicatorClassName={goal.color} />
|
||||
<div className="flex justify-between text-xs text-slate-400 pt-1">
|
||||
<span>{budgetData.currency} {(goal.current / 1000).toFixed(0)}k</span>
|
||||
<span>{budgetData.currency} {(goal.target / 1000000).toFixed(1)}M</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover Effect Background */}
|
||||
<div className={cn(
|
||||
"absolute inset-0 opacity-0 group-hover:opacity-10 transition-opacity duration-500",
|
||||
goal.color
|
||||
)} />
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user