Refactor: Global Theme System and Finance Modules Redesign
This commit is contained in:
19
src/App.tsx
19
src/App.tsx
@@ -3,6 +3,7 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { AuthProvider } from "@/hooks/use-auth";
|
||||
import { ThemeProvider } from "@/context/ThemeContext";
|
||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
import Landing from "./pages/Landing";
|
||||
import Index from "./pages/Index";
|
||||
@@ -16,14 +17,16 @@ const App = () => (
|
||||
<Toaster />
|
||||
<Sonner />
|
||||
<AuthProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/app" element={<Index />} />
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
<ThemeProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/app" element={<Index />} />
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import React from 'react';
|
||||
|
||||
interface BudgetRingProps {
|
||||
@@ -53,7 +54,17 @@ export const BudgetRing = ({ spent, total, label, color = "text-primary", size =
|
||||
</div>
|
||||
<div className="mt-2 text-center">
|
||||
<p className="text-sm font-semibold text-slate-700">{label}</p>
|
||||
<p className="text-xs text-slate-400">₹{spent / 1000}k / ₹{total / 1000}k</p>
|
||||
<div className="flex items-center justify-center gap-1 text-xs text-slate-400">
|
||||
<div className="flex items-center gap-0.5">
|
||||
<DirhamIcon className="w-3 h-3" />
|
||||
{formatAmount(spent)}
|
||||
</div>
|
||||
<span>/</span>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<DirhamIcon className="w-3 h-3" />
|
||||
{formatAmount(total)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,69 +1,41 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { BudgetRing } from '@/components/BudgetRing';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Wallet,
|
||||
Target,
|
||||
CreditCard,
|
||||
PiggyBank,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
PieChart,
|
||||
Search,
|
||||
Sparkles,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Search, Sparkles } from 'lucide-react';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
// Import New Modular Components
|
||||
import HeroStats from './dashboard/HeroStats';
|
||||
import TransactionsList from './dashboard/TransactionsList';
|
||||
import GoalsList from './dashboard/GoalsList';
|
||||
import InvestmentPerformance from './dashboard/InvestmentPerformance';
|
||||
import AIInsights from './dashboard/AIInsights';
|
||||
import QuickActions from './dashboard/QuickActions';
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const portfolioData = {
|
||||
totalValue: 350000,
|
||||
change: 3.2,
|
||||
isPositive: true
|
||||
};
|
||||
|
||||
const budgetData = {
|
||||
spent: 4500,
|
||||
budget: 6500,
|
||||
categories: [
|
||||
{ name: 'Food', spent: 1250, budget: 1800, color: 'text-orange-500' },
|
||||
{ name: 'Transport', spent: 850, budget: 1200, color: 'text-blue-500' },
|
||||
{ name: 'Entertainment', spent: 600, budget: 800, color: 'text-purple-500' },
|
||||
{ name: 'Shopping', spent: 1800, budget: 2700, color: 'text-pink-500' }
|
||||
]
|
||||
};
|
||||
|
||||
const savingsGoals = [
|
||||
{ name: 'Emergency Fund', current: 45000, target: 80000, color: 'from-green-400 to-green-600' },
|
||||
{ name: 'Dubai Trip', current: 8500, target: 15000, color: 'from-blue-400 to-blue-600' },
|
||||
{ name: 'New Car', current: 62000, target: 120000, color: 'from-purple-400 to-purple-600' }
|
||||
];
|
||||
|
||||
const creditScore = 768;
|
||||
|
||||
const currentHour = new Date().getHours();
|
||||
const greeting = currentHour < 12 ? 'Good morning' : currentHour < 18 ? 'Good afternoon' : 'Good evening';
|
||||
// Hardcoded Header Data as per Target Design Reference
|
||||
const currentDate = "Sunday, February 15, 2026";
|
||||
const monthlyWealthGrowth = "425,600"; // AED value
|
||||
|
||||
return (
|
||||
<div className="space-y-8 animate-fade-in text-slate-800">
|
||||
{/* Hero Section */}
|
||||
<div className="space-y-8 animate-fade-in text-slate-800 pb-10">
|
||||
{/* Header Section */}
|
||||
<section className="space-y-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||||
<div>
|
||||
<div className="text-sm text-slate-500 font-medium mb-1">{currentDate}</div>
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-light tracking-tight text-slate-900">
|
||||
{greeting}, <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">Anjali</span>
|
||||
Good evening, <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">Anjali!</span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1">Here's your financial snapshot for today.</p>
|
||||
<div className="flex items-center gap-2 mt-2 text-slate-600 bg-white/50 backdrop-blur-sm px-3 py-1.5 rounded-full w-fit shadow-sm border border-purple-100">
|
||||
<span className="text-sm">Monthly Wealth Growth:</span>
|
||||
<div className="flex items-center font-bold text-purple-700">
|
||||
<DirhamIcon className="w-4 h-4 mr-1" /> {monthlyWealthGrowth}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="relative w-full md:w-96 group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<Search className="h-5 w-5 text-slate-400 group-focus-within:text-purple-500 transition-colors" />
|
||||
@@ -82,137 +54,45 @@ const Dashboard: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Key Metrics - Budget Rings */}
|
||||
{/* Hero Stats (Top Row) */}
|
||||
<section>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{budgetData.categories.map((cat, idx) => (
|
||||
<GlassCard key={idx} className="flex flex-col items-center justify-center py-6" hoverEffect>
|
||||
<BudgetRing
|
||||
spent={cat.spent}
|
||||
total={cat.budget}
|
||||
label={cat.name}
|
||||
color={cat.color}
|
||||
size="md"
|
||||
/>
|
||||
</GlassCard>
|
||||
))}
|
||||
<HeroStats />
|
||||
</section>
|
||||
|
||||
{/* Main Content Area (2 Columns) */}
|
||||
<section className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[450px]">
|
||||
{/* Left Column: Recent Transactions */}
|
||||
<div className="h-full">
|
||||
<TransactionsList />
|
||||
</div>
|
||||
|
||||
{/* Right Column: Financial Goals */}
|
||||
<div className="h-full">
|
||||
<GoalsList />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Portfolio & Net Worth - Sliding Cards */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-xl font-medium text-slate-700">Portfolio Highlights</h2>
|
||||
{/* Horizontal Scroll / Slider */}
|
||||
<div className="flex gap-4 overflow-x-auto pb-4 snap-x snap-mandatory scrollbar-hide">
|
||||
{/* Total Net Worth Card */}
|
||||
<GlassCard className="min-w-[280px] md:min-w-[320px] snap-center flex-shrink-0 p-6 bg-gradient-to-br from-white/60 to-purple-50/30">
|
||||
<div className="flex justify-between items-start mb-8">
|
||||
<div className="p-2 rounded-xl bg-purple-100/50 text-purple-600">
|
||||
<Wallet className="h-6 w-6" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold px-2 py-1 rounded-full bg-green-100 text-green-700 flex items-center">
|
||||
<TrendingUp className="w-3 h-3 mr-1" /> +3.2%
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 font-medium">Total Net Worth</p>
|
||||
<h3 className="text-3xl font-bold text-slate-800 mt-1">{formatCurrency(portfolioData.totalValue)}</h3>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* Bottom Section (Full Width) - Performance & AI Insights */}
|
||||
<section className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Investment Performance (Takes 2 columns) */}
|
||||
<div className="lg:col-span-2 h-[350px]">
|
||||
<InvestmentPerformance />
|
||||
</div>
|
||||
|
||||
{/* Savings Card */}
|
||||
<GlassCard className="min-w-[280px] md:min-w-[320px] snap-center flex-shrink-0 p-6">
|
||||
<div className="flex justify-between items-start mb-8">
|
||||
<div className="p-2 rounded-xl bg-blue-100/50 text-blue-600">
|
||||
<PiggyBank className="h-6 w-6" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold px-2 py-1 rounded-full bg-blue-50 text-blue-600">
|
||||
28% Rate
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 font-medium">Total Savings</p>
|
||||
<h3 className="text-3xl font-bold text-slate-800 mt-1">{formatCurrency(portfolioData.totalValue * 0.15)}</h3>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Credit Score Card */}
|
||||
<GlassCard className="min-w-[280px] md:min-w-[320px] snap-center flex-shrink-0 p-6">
|
||||
<div className="flex justify-between items-start mb-8">
|
||||
<div className="p-2 rounded-xl bg-green-100/50 text-green-600">
|
||||
<Shield className="h-6 w-6" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold px-2 py-1 rounded-full bg-green-50 text-green-600">
|
||||
Excellent
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 font-medium">Credit Score</p>
|
||||
<h3 className="text-3xl font-bold text-slate-800 mt-1">{creditScore}</h3>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* AI Insights (Takes 1 column) */}
|
||||
<div className="h-[350px]">
|
||||
<AIInsights />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* AI Insights & Goals */}
|
||||
<section className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Sparkles className="w-5 h-5 text-purple-500" />
|
||||
<h3 className="text-lg font-semibold text-slate-800">AI Recommendations</h3>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 rounded-xl bg-purple-50/50 border border-purple-100 flex gap-4 items-start">
|
||||
<div className="p-2 bg-purple-100 rounded-lg text-purple-600 mt-1">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-800">Optimize Savings</h4>
|
||||
<p className="text-sm text-slate-500 mt-1 leading-relaxed">Cancel unused streaming subscription to save {formatCurrency(150)}/year. You haven't used it in 3 months.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-xl bg-pink-50/50 border border-pink-100 flex gap-4 items-start">
|
||||
<div className="p-2 bg-pink-100 rounded-lg text-pink-600 mt-1">
|
||||
<CreditCard className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-slate-800">Credit utilization</h4>
|
||||
<p className="text-sm text-slate-500 mt-1 leading-relaxed">Pay down {formatCurrency(1200)} on ADCB card to keep utilization under 30%.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Target className="w-5 h-5 text-blue-500" />
|
||||
<h3 className="text-lg font-semibold text-slate-800">Goal Progress</h3>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{savingsGoals.map((goal, idx) => (
|
||||
<div key={idx}>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span className="font-medium text-slate-700">{goal.name}</span>
|
||||
<span className="text-slate-500">{formatCurrency(goal.current)} / {formatCurrency(goal.target)}</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-slate-100 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
className={`h-full bg-gradient-to-r ${goal.color} rounded-full`}
|
||||
initial={{ width: "0%" }}
|
||||
animate={{ width: `${(goal.current / goal.target) * 100}%` }}
|
||||
transition={{ duration: 1, ease: "easeOut" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Button className="w-full mt-2 bg-gradient-to-r from-blue-500 to-cyan-500 text-white rounded-xl shadow-lg border-0 hover:shadow-xl transition-all">
|
||||
View All Goals
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* Footer Actions */}
|
||||
<section>
|
||||
<h3 className="text-lg font-medium text-slate-700 mb-4">Quick Actions</h3>
|
||||
<QuickActions />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { ExpandableTabs } from '@/components/ui/expandable-tabs';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTheme } from '@/context/ThemeContext';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
@@ -43,6 +44,8 @@ export const Layout = ({ children, activeSection, onSectionChange, onLogout }: L
|
||||
const currentNavIndex = navItems.findIndex(item => item.id === activeSection);
|
||||
const selectedIndex = currentNavIndex >= 0 ? currentNavIndex : null;
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const handleTabChange = (index: number | null) => {
|
||||
if (index === null) return;
|
||||
|
||||
@@ -54,13 +57,15 @@ export const Layout = ({ children, activeSection, onSectionChange, onLogout }: L
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-full relative overflow-x-hidden font-sans text-slate-800">
|
||||
{/* Ambient Background Mesh */}
|
||||
<div className="fixed inset-0 z-0 pointer-events-none">
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[50%] h-[50%] rounded-full bg-purple-200/30 blur-[100px]" />
|
||||
<div className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] rounded-full bg-blue-200/30 blur-[100px]" />
|
||||
<div className="absolute top-[40%] left-[40%] w-[30%] h-[30%] rounded-full bg-pink-200/20 blur-[100px]" />
|
||||
</div>
|
||||
<div className={cn("min-h-screen w-full relative overflow-x-hidden font-sans transition-colors duration-500", theme === 'dubai' ? 'text-white' : 'text-slate-800')}>
|
||||
{/* Ambient Background Mesh - Only show for default theme */}
|
||||
{theme === 'default' && (
|
||||
<div className="fixed inset-0 z-0 pointer-events-none">
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[50%] h-[50%] rounded-full bg-purple-200/30 blur-[100px]" />
|
||||
<div className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] rounded-full bg-blue-200/30 blur-[100px]" />
|
||||
<div className="absolute top-[40%] left-[40%] w-[30%] h-[30%] rounded-full bg-pink-200/20 blur-[100px]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content Area */}
|
||||
<main className="relative z-10 pb-28 min-h-screen">
|
||||
|
||||
57
src/components/budget/AddExpenseForm.tsx
Normal file
57
src/components/budget/AddExpenseForm.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
const AddExpenseForm: React.FC = () => {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[400px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
<h3 className="text-xl font-bold text-slate-800 mb-6 text-center">Add New Expense</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="amount" className="text-slate-600">Amount (AED)</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
||||
<DirhamIcon className="w-4 h-4 text-slate-400" />
|
||||
</div>
|
||||
<Input id="amount" placeholder="0.00" className="pl-9 bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="category" className="text-slate-600">Category</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="category" className="bg-white/50 border-slate-200">
|
||||
<SelectValue placeholder="Select Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="food">Food & Dining</SelectItem>
|
||||
<SelectItem value="transport">Transportation</SelectItem>
|
||||
<SelectItem value="shopping">Shopping</SelectItem>
|
||||
<SelectItem value="entertainment">Entertainment</SelectItem>
|
||||
<SelectItem value="utilities">Utilities</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="desc" className="text-slate-600">Description</Label>
|
||||
<Input id="desc" placeholder="e.g. Grocery shopping" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md">
|
||||
Add Expense
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddExpenseForm;
|
||||
68
src/components/budget/BudgetCategoryGrid.tsx
Normal file
68
src/components/budget/BudgetCategoryGrid.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Utensils, Car, ShoppingBag, Clapperboard, HeartPulse, Home } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const BudgetCategoryGrid: React.FC = () => {
|
||||
|
||||
const categories = [
|
||||
{ name: 'Food & Dining', spent: 1250, limit: 3000, icon: Utensils, color: 'bg-green-500', iconBg: 'bg-green-100 text-green-600' },
|
||||
{ name: 'Transportation', spent: 950, limit: 1000, icon: Car, color: 'bg-orange-500', iconBg: 'bg-orange-100 text-orange-600' },
|
||||
{ name: 'Shopping', spent: 2200, limit: 2000, icon: ShoppingBag, color: 'bg-red-500', iconBg: 'bg-red-100 text-red-600' },
|
||||
{ name: 'Entertainment', spent: 800, limit: 1500, icon: Clapperboard, color: 'bg-purple-500', iconBg: 'bg-purple-100 text-purple-600' },
|
||||
{ name: 'Healthcare', spent: 300, limit: 1000, icon: HeartPulse, color: 'bg-blue-500', iconBg: 'bg-blue-100 text-blue-600' },
|
||||
{ name: 'Housing', spent: 6500, limit: 7000, icon: Home, color: 'bg-indigo-500', iconBg: 'bg-indigo-100 text-indigo-600' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{categories.map((cat, index) => {
|
||||
const percentage = Math.min((cat.spent / cat.limit) * 100, 100);
|
||||
const remaining = cat.limit - cat.spent;
|
||||
const isOverGroups = cat.spent > cat.limit;
|
||||
|
||||
return (
|
||||
<GlassCard key={index} className="p-6 relative overflow-hidden group" hoverEffect>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className={`p-3 rounded-xl ${cat.iconBg}`}>
|
||||
<cat.icon className="w-6 h-6" />
|
||||
</div>
|
||||
<Badge variant="outline" className={isOverGroups ? "bg-red-50 text-red-600 border-red-200" : "bg-green-50 text-green-600 border-green-200"}>
|
||||
{isOverGroups ? "Over Budget" : "Active"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-bold text-slate-800 mb-1">{cat.name}</h3>
|
||||
<div className={`text-sm font-medium mb-4 ${remaining < 0 ? 'text-red-500' : 'text-slate-500'}`}>
|
||||
{remaining < 0 ? (
|
||||
<span className="flex items-center gap-1">Over by <DirhamIcon className="w-3 h-3" /> {formatAmount(Math.abs(remaining))}</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1"><DirhamIcon className="w-3 h-3" /> {formatAmount(remaining)} left</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden mb-2">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${percentage}%` }}
|
||||
transition={{ duration: 1 }}
|
||||
className={`h-full ${cat.color}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs text-slate-400">
|
||||
<span className="flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(cat.spent)}</span>
|
||||
<span className="flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(cat.limit)}</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetCategoryGrid;
|
||||
68
src/components/budget/BudgetOverviewList.tsx
Normal file
68
src/components/budget/BudgetOverviewList.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const BudgetOverviewList: React.FC = () => {
|
||||
|
||||
const categories = [
|
||||
{ name: 'Food & Dining', spent: 1250, limit: 3000, status: 'On Track', color: 'bg-green-500' },
|
||||
{ name: 'Transportation', spent: 950, limit: 1000, status: 'Near Limit', color: 'bg-orange-500' },
|
||||
{ name: 'Shopping', spent: 2200, limit: 2000, status: 'Over Budget', color: 'bg-red-500' },
|
||||
{ name: 'Entertainment', spent: 800, limit: 1500, status: 'On Track', color: 'bg-purple-500' },
|
||||
{ name: 'Healthcare', spent: 300, limit: 1000, status: 'On Track', color: 'bg-blue-500' },
|
||||
];
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'On Track': return 'bg-green-100 text-green-700 border-green-200';
|
||||
case 'Over Budget': return 'bg-red-100 text-red-700 border-red-200';
|
||||
case 'Near Limit': return 'bg-orange-100 text-orange-700 border-orange-200';
|
||||
default: return 'bg-slate-100 text-slate-700';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Budget Overview</h3>
|
||||
<div className="space-y-6">
|
||||
{categories.map((cat, index) => {
|
||||
const percentage = Math.min((cat.spent / cat.limit) * 100, 100);
|
||||
return (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="font-medium text-slate-700">{cat.name}</span>
|
||||
<Badge variant="outline" className={`${getStatusColor(cat.status)} text-[10px] px-2 py-0 border`}>
|
||||
{cat.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs text-slate-500 mb-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-semibold text-slate-900"><DirhamIcon className="w-3 h-3 inline" /> {formatAmount(cat.spent)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
of <DirhamIcon className="w-3 h-3 inline" /> {formatAmount(cat.limit)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${percentage}%` }}
|
||||
transition={{ duration: 1, delay: index * 0.1 }}
|
||||
className={`h-full ${cat.color} transition-all duration-1000`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetOverviewList;
|
||||
92
src/components/budget/BudgetSummaryCards.tsx
Normal file
92
src/components/budget/BudgetSummaryCards.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
Wallet,
|
||||
ShoppingBag,
|
||||
TrendingDown,
|
||||
PiggyBank
|
||||
} from 'lucide-react';
|
||||
|
||||
const BudgetSummaryCards: React.FC = () => {
|
||||
// Hardcoded design values
|
||||
const data = {
|
||||
income: 17500,
|
||||
spent: 9600,
|
||||
budget: 15000,
|
||||
savingsRate: 24
|
||||
};
|
||||
|
||||
const remaining = data.budget - data.spent;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{/* Total Income */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Income</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-green-400 to-green-600 flex items-center justify-center shadow-lg shadow-green-200">
|
||||
<Wallet className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(data.income)}</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-2">Monthly inflow</p>
|
||||
</GlassCard>
|
||||
|
||||
{/* Total Spent */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Spent</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-red-400 to-red-600 flex items-center justify-center shadow-lg shadow-red-200">
|
||||
<ShoppingBag className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(data.spent)}</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-2">
|
||||
{(data.spent / data.income * 100).toFixed(1)}% of income
|
||||
</p>
|
||||
</GlassCard>
|
||||
|
||||
{/* Budget Remaining */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Budget Remaining</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg shadow-blue-200">
|
||||
<TrendingDown className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(remaining)}</p>
|
||||
</div>
|
||||
<div className="w-full bg-slate-100 rounded-full h-1.5 mt-3 overflow-hidden">
|
||||
<div className="h-full bg-blue-500" style={{ width: `${(remaining / data.budget) * 100}%` }}></div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Savings Rate */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Savings Rate</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center shadow-lg shadow-purple-200">
|
||||
<PiggyBank className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{data.savingsRate}%</p>
|
||||
<p className="text-xs text-green-600 font-medium mt-2 bg-green-50 inline-block px-2 py-0.5 rounded-full">
|
||||
+2% from last month
|
||||
</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetSummaryCards;
|
||||
69
src/components/budget/FullExpensesTable.tsx
Normal file
69
src/components/budget/FullExpensesTable.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
const FullExpensesTable: React.FC = () => {
|
||||
|
||||
// Mock Data using requested transaction details
|
||||
const transactions = [
|
||||
{ date: 'Feb 15, 2026', desc: 'Carrefour Market', category: 'Food & Dining', amount: -450, type: 'expense' },
|
||||
{ date: 'Feb 14, 2026', desc: 'Uber Ride', category: 'Transportation', amount: -65, type: 'expense' },
|
||||
{ date: 'Feb 12, 2026', desc: 'Freelance Payout', category: 'Income', amount: 3500, type: 'income' },
|
||||
{ date: 'Feb 10, 2026', desc: 'Zara Dubail Mall', category: 'Shopping', amount: -320, type: 'expense' },
|
||||
{ date: 'Feb 08, 2026', desc: 'DEWA Monthly Bill', category: 'Utilities', amount: -650, type: 'expense' },
|
||||
{ date: 'Feb 05, 2026', desc: 'Vox Cinemas', category: 'Entertainment', amount: -120, type: 'expense' },
|
||||
{ date: 'Feb 01, 2026', desc: 'Salary Credit', category: 'Income', amount: 14000, type: 'income' },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-slate-50/50">
|
||||
<TableRow>
|
||||
<TableHead className="w-[180px]">Date</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Category</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead className="text-right">Amount</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{transactions.map((tx, idx) => (
|
||||
<TableRow key={idx} className="hover:bg-slate-50/50">
|
||||
<TableCell className="text-slate-500 font-medium">{tx.date}</TableCell>
|
||||
<TableCell className="font-semibold text-slate-800">{tx.desc}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="bg-white/50 text-slate-600 font-normal">
|
||||
{tx.category}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className={`text-xs font-bold uppercase ${tx.type === 'income' ? 'text-green-600' : 'text-slate-500'}`}>
|
||||
{tx.type}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={`text-right font-bold ${tx.type === 'income' ? 'text-green-600' : 'text-slate-800'}`}>
|
||||
<span className="flex items-center justify-end gap-1">
|
||||
{tx.type === 'income' ? '+' : '-'} <DirhamIcon className="w-3 h-3" /> {formatAmount(Math.abs(tx.amount))}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default FullExpensesTable;
|
||||
45
src/components/budget/RecentTransactionsCard.tsx
Normal file
45
src/components/budget/RecentTransactionsCard.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { ArrowUpRight, ArrowDownLeft } from 'lucide-react';
|
||||
|
||||
const RecentTransactionsCard: React.FC = () => {
|
||||
|
||||
const transactions = [
|
||||
{ date: 'Today, 2:30 PM', name: 'Carrefour', category: 'Food & Dining', amount: -450, type: 'expense' },
|
||||
{ date: 'Yesterday, 8:15 PM', name: 'Uber', category: 'Transportation', amount: -65, type: 'expense' },
|
||||
{ date: 'Feb 12, 10:00 AM', name: 'Freelance Project', category: 'Income', amount: 3500, type: 'income' },
|
||||
{ date: 'Feb 10, 6:45 PM', name: 'Zara', category: 'Shopping', amount: -320, type: 'expense' },
|
||||
{ date: 'Feb 08, 1:20 PM', name: 'DEWA Bill', category: 'Utilities', amount: -650, type: 'expense' },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Recent Transactions</h3>
|
||||
<div className="space-y-4 flex-1">
|
||||
{transactions.map((tx, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 rounded-xl hover:bg-slate-50/80 transition-colors border border-transparent hover:border-slate-100">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${tx.type === 'income' ? 'bg-green-100 text-green-600' : 'bg-slate-100 text-slate-600'}`}>
|
||||
{tx.type === 'income' ? <ArrowDownLeft className="w-5 h-5" /> : <ArrowUpRight className="w-5 h-5" />}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-slate-900">{tx.name}</p>
|
||||
<p className="text-xs text-slate-500">{tx.date} • {tx.category}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`text-right font-bold text-sm ${tx.type === 'income' ? 'text-green-600' : 'text-slate-900'}`}>
|
||||
<span className="flex items-center gap-0.5">
|
||||
{tx.type === 'income' ? '+' : '-'} <DirhamIcon className="w-3 h-3" /> {formatAmount(Math.abs(tx.amount))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecentTransactionsCard;
|
||||
52
src/components/budget/SetBudgetForm.tsx
Normal file
52
src/components/budget/SetBudgetForm.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
const SetBudgetForm: React.FC = () => {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[400px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
<h3 className="text-xl font-bold text-slate-800 mb-6 text-center">Set Monthly Budget</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="category-budget" className="text-slate-600">Category</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="category-budget" className="bg-white/50 border-slate-200">
|
||||
<SelectValue placeholder="Select Category" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="food">Food & Dining</SelectItem>
|
||||
<SelectItem value="transport">Transportation</SelectItem>
|
||||
<SelectItem value="shopping">Shopping</SelectItem>
|
||||
<SelectItem value="entertainment">Entertainment</SelectItem>
|
||||
<SelectItem value="utilities">Utilities</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="budget-amount" className="text-slate-600">Budget Limit (AED)</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
||||
<DirhamIcon className="w-4 h-4 text-slate-400" />
|
||||
</div>
|
||||
<Input id="budget-amount" placeholder="0.00" className="pl-9 bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-700 hover:to-indigo-700 shadow-md">
|
||||
Set Budget
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetBudgetForm;
|
||||
45
src/components/commitment/AddCommitmentForm.tsx
Normal file
45
src/components/commitment/AddCommitmentForm.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
|
||||
const AddCommitmentForm: React.FC = () => {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[500px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
<h3 className="text-xl font-bold text-slate-800 mb-6 text-center">Add New Commitment</h3>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="comm-name" className="text-slate-600">Commitment Name</Label>
|
||||
<Input id="comm-name" placeholder="e.g. Health Insurance" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="comm-amount" className="text-slate-600">Amount (AED)</Label>
|
||||
<Input id="comm-amount" type="number" placeholder="0.00" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="comm-date" className="text-slate-600">Due Date</Label>
|
||||
<Input id="comm-date" type="date" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="comm-type" className="text-slate-600">Type</Label>
|
||||
<Input id="comm-type" placeholder="e.g. Insurance, Utility" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md flex items-center gap-2 justify-center">
|
||||
<Sparkles className="w-4 h-4 text-yellow-400" /> Add Commitment
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCommitmentForm;
|
||||
63
src/components/commitment/CommitmentAITips.tsx
Normal file
63
src/components/commitment/CommitmentAITips.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Zap, ArrowRight, Lightbulb } from 'lucide-react';
|
||||
|
||||
const CommitmentAITips: React.FC = () => {
|
||||
const tips = [
|
||||
{
|
||||
title: 'Automate Recurring Payments',
|
||||
desc: 'Set up auto-pay for Rent to avoid late fees. Potential saving: AED 200/yr.',
|
||||
color: 'border-l-4 border-green-500 bg-green-50/30'
|
||||
},
|
||||
{
|
||||
title: 'Track Due Dates',
|
||||
desc: 'Your Car Loan is due in 3 days. Ensure sufficient balance in your primary account.',
|
||||
color: 'border-l-4 border-orange-500 bg-orange-50/30'
|
||||
},
|
||||
{
|
||||
title: 'Review Subscriptions',
|
||||
desc: 'You have 2 unused subscriptions (Gym, Netflix). Cancel to save AED 450/mo.',
|
||||
color: 'border-l-4 border-blue-500 bg-blue-50/30'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<GlassCard className="p-6 bg-gradient-to-br from-indigo-50 to-purple-50 border-indigo-100">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Zap className="w-5 h-5 text-indigo-600 fill-indigo-600" />
|
||||
<h3 className="font-bold text-indigo-900">AI Financial Insights</h3>
|
||||
</div>
|
||||
<p className="text-sm text-indigo-700/80 mb-4">Smart recommendations to optimize your cash flow.</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{tips.map((tip, idx) => (
|
||||
<div key={idx} className={`p-4 rounded-r-xl ${tip.color} shadow-sm`}>
|
||||
<h4 className="font-semibold text-slate-800 text-sm mb-1">{tip.title}</h4>
|
||||
<p className="text-xs text-slate-600 leading-relaxed">{tip.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-yellow-100 text-yellow-600 rounded-lg">
|
||||
<Lightbulb className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-slate-800">Did you know?</h4>
|
||||
<p className="text-xs text-slate-500">Paying bills 2 days early improves credit score.</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full text-xs border-slate-200">
|
||||
View Credit Impact
|
||||
</Button>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommitmentAITips;
|
||||
85
src/components/commitment/CommitmentCalendarTable.tsx
Normal file
85
src/components/commitment/CommitmentCalendarTable.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Check, Trash2 } from 'lucide-react';
|
||||
|
||||
const CommitmentCalendarTable: React.FC = () => {
|
||||
|
||||
const commitments = [
|
||||
{ name: 'Rent Payment', type: 'Housing', amount: 6500, date: 'Oct 01, 2024', status: 'Due' },
|
||||
{ name: 'Netflix Subscription', type: 'Entertainment', amount: 45, date: 'Oct 03, 2024', status: 'Paid' },
|
||||
{ name: 'Car Loan EMI', type: 'Loan', amount: 2100, date: 'Oct 05, 2024', status: 'Upcoming' },
|
||||
{ name: 'Electricity Bill', type: 'Utility', amount: 450, date: 'Oct 10, 2024', status: 'Upcoming' },
|
||||
{ name: 'Gym Membership', type: 'Health', amount: 350, date: 'Oct 15, 2024', status: 'Upcoming' },
|
||||
];
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
case 'Due': return <Badge variant="destructive" className="bg-red-100 text-red-700 hover:bg-red-100 border-red-200">Due</Badge>;
|
||||
case 'Paid': return <Badge variant="outline" className="bg-green-100 text-green-700 border-green-200">Paid</Badge>;
|
||||
default: return <Badge variant="secondary" className="bg-blue-100 text-blue-700 hover:bg-blue-100 border-blue-200">Upcoming</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GlassCard className="overflow-hidden">
|
||||
<div className="p-6 border-b border-slate-100">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Commitment Calendar</h3>
|
||||
<p className="text-sm text-slate-500">View and manage all your scheduled payments.</p>
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader className="bg-slate-50/50">
|
||||
<TableRow>
|
||||
<TableHead>Commitment</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead className="text-right">Amount (AED)</TableHead>
|
||||
<TableHead>Due Date</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{commitments.map((item, idx) => (
|
||||
<TableRow key={idx} className="hover:bg-slate-50/50">
|
||||
<TableCell className="font-semibold text-slate-800">{item.name}</TableCell>
|
||||
<TableCell className="text-slate-500">{item.type}</TableCell>
|
||||
<TableCell className="text-right font-bold text-slate-900">
|
||||
<span className="flex items-center justify-end gap-1">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(item.amount)}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-slate-600">{item.date}</TableCell>
|
||||
<TableCell>{getStatusBadge(item.status)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{item.status !== 'Paid' && (
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-green-600 hover:text-green-700 hover:bg-green-50">
|
||||
<Check className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400 hover:text-red-600 hover:bg-red-50">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommitmentCalendarTable;
|
||||
90
src/components/commitment/UpcomingCommitmentsList.tsx
Normal file
90
src/components/commitment/UpcomingCommitmentsList.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Home, CreditCard, Zap, Trash2, Check } from 'lucide-react';
|
||||
|
||||
const UpcomingCommitmentsList: React.FC = () => {
|
||||
const commitments = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Rent Payment',
|
||||
type: 'Housing',
|
||||
amount: 6500,
|
||||
dueDate: 'Oct 01, 2024',
|
||||
icon: Home,
|
||||
iconColor: 'bg-blue-100 text-blue-600',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Car Loan EMI',
|
||||
type: 'Loan',
|
||||
amount: 2100,
|
||||
dueDate: 'Oct 05, 2024',
|
||||
icon: CreditCard,
|
||||
iconColor: 'bg-purple-100 text-purple-600',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Electricity Bill',
|
||||
type: 'Utility',
|
||||
amount: 450,
|
||||
dueDate: 'Oct 10, 2024',
|
||||
icon: Zap,
|
||||
iconColor: 'bg-yellow-100 text-yellow-600',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Upcoming Commitments</h3>
|
||||
<div className="space-y-4">
|
||||
{commitments.map((item) => (
|
||||
<div key={item.id} className="flex items-center justify-between p-4 rounded-xl hover:bg-slate-50 border border-transparent hover:border-slate-100 transition-all group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`p-3 rounded-xl ${item.iconColor}`}>
|
||||
<item.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-900">{item.title}</h4>
|
||||
<p className="text-sm text-slate-500">{item.type}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="text-right">
|
||||
<p className="font-bold text-slate-900 flex items-center justify-end gap-1">
|
||||
<DirhamIcon className="w-4 h-4" /> {formatAmount(item.amount)}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">Due {item.dueDate}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button variant="outline" size="sm" className="h-8 border-slate-900 text-slate-900 bg-white hover:bg-slate-50">
|
||||
<Check className="w-3 h-3 mr-1" /> Mark Paid
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-red-500 hover:text-red-600 hover:bg-red-50">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 pt-6 border-t border-slate-100 flex justify-between items-center px-2">
|
||||
<div>
|
||||
<p className="text-sm text-slate-500">Total Due This Month</p>
|
||||
<p className="text-2xl font-bold text-slate-900 mt-1 flex items-center gap-1">
|
||||
<DirhamIcon className="w-5 h-5" /> {formatAmount(9050)}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="ghost" className="text-blue-600 hover:text-blue-700 hover:bg-blue-50">View All History</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpcomingCommitmentsList;
|
||||
49
src/components/credit/AECBInfoCard.tsx
Normal file
49
src/components/credit/AECBInfoCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
const AECBInfoCard: React.FC = () => {
|
||||
|
||||
// AECB score ranges
|
||||
const ranges = [
|
||||
{ label: 'Excellent', range: '781 - 900', color: 'text-green-600 font-bold' },
|
||||
{ label: 'Very Good', range: '721 - 780', color: 'text-emerald-600 font-medium' },
|
||||
{ label: 'Good', range: '661 - 720', color: 'text-blue-600 font-medium' },
|
||||
{ label: 'Fair', range: '601 - 660', color: 'text-yellow-600 font-medium' },
|
||||
{ label: 'Poor', range: '300 - 600', color: 'text-red-500 font-medium' },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-bold text-slate-900">Al Etihad Credit Bureau (AECB)</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">Understanding your score range.</p>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<TableBody>
|
||||
{ranges.map((range, idx) => (
|
||||
<TableRow key={idx} className="hover:bg-slate-50/50 border-b-slate-100">
|
||||
<TableCell className={range.color}>{range.label}</TableCell>
|
||||
<TableCell className="text-right font-mono text-slate-600">{range.range}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<div className="mt-auto pt-6 border-t border-slate-100">
|
||||
<p className="text-sm text-slate-600 text-center font-medium bg-slate-50 p-3 rounded-lg border border-slate-200">
|
||||
Your current score of <span className="font-bold text-slate-900">785</span> puts you in the top <span className="text-green-600 font-bold">15%</span> of UAE residents!
|
||||
</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default AECBInfoCard;
|
||||
127
src/components/credit/CreditCardsList.tsx
Normal file
127
src/components/credit/CreditCardsList.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
const CreditCardsList: React.FC = () => {
|
||||
|
||||
// Detailed Card Data
|
||||
const cards = [
|
||||
{
|
||||
name: 'Emirates NBD Skywards Infinite',
|
||||
network: 'Visa',
|
||||
balance: 8500,
|
||||
limit: 50000,
|
||||
rate: 2.99,
|
||||
minDue: 450,
|
||||
dueDate: 'Feb 28',
|
||||
imageGradient: 'from-slate-800 to-slate-900'
|
||||
},
|
||||
{
|
||||
name: 'ADCB TouchPoints Platinum',
|
||||
network: 'Mastercard',
|
||||
balance: 12000,
|
||||
limit: 25000,
|
||||
rate: 3.25,
|
||||
minDue: 600,
|
||||
dueDate: 'Mar 05',
|
||||
imageGradient: 'from-blue-600 to-blue-800'
|
||||
},
|
||||
{
|
||||
name: 'FAB Cashback',
|
||||
network: 'Visa',
|
||||
balance: 3200,
|
||||
limit: 15000,
|
||||
rate: 3.10,
|
||||
minDue: 150,
|
||||
dueDate: 'Mar 12',
|
||||
imageGradient: 'from-teal-600 to-teal-800'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{cards.map((card, idx) => {
|
||||
const utilization = Math.round((card.balance / card.limit) * 100);
|
||||
|
||||
return (
|
||||
<GlassCard key={idx} className="p-0 overflow-hidden flex flex-col md:flex-row">
|
||||
{/* Visual Card Representation */}
|
||||
<div className={`w-full md:w-64 bg-gradient-to-br ${card.imageGradient} p-6 flex flex-col justify-between text-white relative`}>
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="w-10 h-6 bg-white/20 rounded-md backdrop-blur-sm" />
|
||||
<span className="font-bold italic opacity-80">{card.network}</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs opacity-70 mb-1">Current Balance</p>
|
||||
<p className="text-xl font-bold flex items-center gap-1"><DirhamIcon className="w-4 h-4" /> {formatAmount(card.balance)}</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-end">
|
||||
<span className="text-xs opacity-70">**** **** **** 4242</span>
|
||||
<span className="text-xs font-mono opacity-80">EXP 12/28</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Details & Actions */}
|
||||
<div className="flex-1 p-6">
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-900">{card.name}</h3>
|
||||
<div className="flex items-center gap-3 mt-1">
|
||||
<Badge variant="outline" className="text-xs font-normal text-slate-500 border-slate-200">{card.network}</Badge>
|
||||
<span className="text-xs text-slate-400">Due Date: {card.dueDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400">
|
||||
<MoreHorizontal className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div>
|
||||
<p className="text-xs text-slate-400 mb-1">Credit Limit</p>
|
||||
<p className="font-semibold text-slate-700 flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(card.limit)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400 mb-1">Utilization</p>
|
||||
<p className={`font-semibold ${utilization > 30 ? 'text-orange-600' : 'text-green-600'}`}>{utilization}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400 mb-1">Interest Rate</p>
|
||||
<p className="font-semibold text-slate-700">{card.rate}% / mo</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-400 mb-1">Available Crdt.</p>
|
||||
<p className="font-semibold text-slate-700 flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(card.limit - card.balance)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 pt-4 border-t border-slate-100">
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 font-medium">Min Payment Due</p>
|
||||
<p className="text-lg font-bold text-slate-900 flex items-center gap-1">
|
||||
<DirhamIcon className="w-4 h-4" /> {formatAmount(card.minDue)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3 w-full sm:w-auto">
|
||||
<Button variant="outline" className="flex-1 sm:flex-none border-slate-200 text-slate-700 hover:bg-slate-50">
|
||||
Pay Custom
|
||||
</Button>
|
||||
<Button className="flex-1 sm:flex-none bg-black text-white hover:bg-slate-800">
|
||||
Pay Min
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditCardsList;
|
||||
72
src/components/credit/CreditScoreProgress.tsx
Normal file
72
src/components/credit/CreditScoreProgress.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const CreditScoreProgress: React.FC = () => {
|
||||
const score = 785;
|
||||
const minScore = 300;
|
||||
const maxScore = 850;
|
||||
const range = maxScore - minScore;
|
||||
const percentage = ((score - minScore) / range) * 100;
|
||||
|
||||
const factors = [
|
||||
{ name: 'Payment History', status: 'Excellent', color: 'text-green-600', icon: CheckCircle2 },
|
||||
{ name: 'Credit Utilization', status: 'Good', color: 'text-green-600', icon: CheckCircle2 },
|
||||
{ name: 'Credit Age', status: 'Average', color: 'text-yellow-600', icon: AlertCircle },
|
||||
{ name: 'Total Accounts', status: 'Excellent', color: 'text-green-600', icon: CheckCircle2 },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Credit Score Progress</h3>
|
||||
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-end mb-2">
|
||||
<span className="text-sm text-slate-500 font-medium">Current Sore</span>
|
||||
<span className="text-4xl font-bold text-blue-600">{score}</span>
|
||||
</div>
|
||||
|
||||
{/* Custom Range Bar */}
|
||||
<div className="relative h-4 w-full bg-slate-100 rounded-full mb-1">
|
||||
<div className="absolute top-0 left-0 h-full w-full rounded-full bg-gradient-to-r from-red-400 via-yellow-400 to-green-500 opacity-30"></div>
|
||||
<motion.div
|
||||
className="absolute top-0 h-full w-1 bg-blue-600 rounded-full shadow-[0_0_10px_rgba(37,99,235,0.5)] z-10"
|
||||
style={{ left: `${percentage}%` }}
|
||||
initial={{ height: 0 }}
|
||||
animate={{ height: '16px' }}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute -top-1 w-3 h-6 bg-white border-2 border-blue-600 rounded-full z-20 shadow-md"
|
||||
style={{ left: `${percentage}%`, transform: 'translateX(-50%)' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-slate-400 font-medium mt-2">
|
||||
<span>300</span>
|
||||
<span>850</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-sm font-semibold text-slate-700 uppercase tracking-wider">Score Factors</h4>
|
||||
{factors.map((factor, idx) => (
|
||||
<div key={idx} className="flex items-center justify-between p-3 rounded-xl bg-slate-50/50 border border-slate-100">
|
||||
<div className="flex items-center gap-3">
|
||||
<factor.icon className={`w-4 h-4 ${factor.color}`} />
|
||||
<span className="text-sm font-medium text-slate-700">{factor.name}</span>
|
||||
</div>
|
||||
<Badge variant="outline" className={`${factor.color === 'text-green-600' ? 'text-green-700 bg-green-50 border-green-200' : 'text-yellow-700 bg-yellow-50 border-yellow-200'} text-xs`}>
|
||||
{factor.status}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditScoreProgress;
|
||||
58
src/components/credit/CreditScoreTips.tsx
Normal file
58
src/components/credit/CreditScoreTips.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Check, Info, AlertTriangle, ShieldCheck } from 'lucide-react';
|
||||
|
||||
const CreditScoreTips: React.FC = () => {
|
||||
|
||||
const tips = [
|
||||
{
|
||||
title: 'Keep Utilization Low',
|
||||
desc: 'Try to keep your balance below 30% of your limit.',
|
||||
icon: Info,
|
||||
color: 'bg-green-50 border-green-100',
|
||||
iconColor: 'bg-green-100 text-green-600'
|
||||
},
|
||||
{
|
||||
title: 'Pay On Time',
|
||||
desc: 'Missed payments have the biggest impact on score.',
|
||||
icon: Check,
|
||||
color: 'bg-blue-50 border-blue-100',
|
||||
iconColor: 'bg-blue-100 text-blue-600'
|
||||
},
|
||||
{
|
||||
title: "Don't Close Old Cards",
|
||||
desc: 'Credit age is a significant factor. Keep them open.',
|
||||
icon: ShieldCheck,
|
||||
color: 'bg-purple-50 border-purple-100',
|
||||
iconColor: 'bg-purple-100 text-purple-600'
|
||||
},
|
||||
{
|
||||
title: 'Limit New Applications',
|
||||
desc: 'Hard inquiries can temporarily lower your score.',
|
||||
icon: AlertTriangle,
|
||||
color: 'bg-yellow-50 border-yellow-100',
|
||||
iconColor: 'bg-yellow-100 text-yellow-600'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{tips.map((tip, idx) => (
|
||||
<GlassCard key={idx} className={`p-4 border ${tip.color} shadow-sm`}>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`p-2 rounded-lg ${tip.iconColor}`}>
|
||||
<tip.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-slate-800 text-sm">{tip.title}</h4>
|
||||
<p className="text-xs text-slate-500 mt-1 leading-relaxed">{tip.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditScoreTips;
|
||||
80
src/components/credit/CreditSummaryCards.tsx
Normal file
80
src/components/credit/CreditSummaryCards.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { ShieldCheck, Calendar, CreditCard, Percent } from 'lucide-react';
|
||||
|
||||
const CreditSummaryCards: React.FC = () => {
|
||||
// Hardcoded target values
|
||||
const data = {
|
||||
score: 785,
|
||||
balance: 23700,
|
||||
utilization: 12,
|
||||
minPayment: 850
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{/* Credit Score */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Credit Score</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-400 to-indigo-600 flex items-center justify-center shadow-lg shadow-indigo-200">
|
||||
<ShieldCheck className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{data.score}</p>
|
||||
<p className="text-xs text-green-600 font-medium mt-2">Excellent • Top 15%</p>
|
||||
</GlassCard>
|
||||
|
||||
{/* Total Balance */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Balance</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-red-400 to-red-600 flex items-center justify-center shadow-lg shadow-red-200">
|
||||
<CreditCard className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(data.balance)}</p>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<span className="text-xs text-red-500 font-medium">+ AED 1,200 from last month</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Utilization */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Utilization</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg shadow-blue-200">
|
||||
<Percent className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{data.utilization}%</p>
|
||||
<div className="w-full bg-slate-100 rounded-full h-1.5 mt-3 overflow-hidden">
|
||||
<div className="h-full bg-green-500" style={{ width: `${data.utilization}%` }}></div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Min Payment */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Min Payment Due</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center shadow-lg shadow-orange-200">
|
||||
<Calendar className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(data.minPayment)}</p>
|
||||
</div>
|
||||
<p className="text-xs text-orange-600 font-medium mt-2">Due in 3 days</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditSummaryCards;
|
||||
48
src/components/credit/CreditUtilizationList.tsx
Normal file
48
src/components/credit/CreditUtilizationList.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
const CreditUtilizationList: React.FC = () => {
|
||||
|
||||
const cards = [
|
||||
{ name: 'Emirates NBD Skywards', balance: 8500, limit: 50000, color: 'bg-indigo-500' },
|
||||
{ name: 'ADCB TouchPoints', balance: 12000, limit: 25000, color: 'bg-blue-500' },
|
||||
{ name: 'FAB Cashback', balance: 3200, limit: 15000, color: 'bg-teal-500' },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Credit Utilization</h3>
|
||||
<div className="space-y-6">
|
||||
{cards.map((card, index) => {
|
||||
const utilization = Math.round((card.balance / card.limit) * 100);
|
||||
let badgeColor = 'bg-green-100 text-green-700 border-green-200';
|
||||
if (utilization > 30) badgeColor = 'bg-yellow-100 text-yellow-700 border-yellow-200';
|
||||
if (utilization > 70) badgeColor = 'bg-red-100 text-red-700 border-red-200';
|
||||
|
||||
return (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium text-slate-700">{card.name}</span>
|
||||
<Badge variant="outline" className={`${badgeColor} text-xs`}>
|
||||
{utilization}% Utilized
|
||||
</Badge>
|
||||
</div>
|
||||
<Progress value={utilization} className="h-2" indicatorClassName={card.color} />
|
||||
<div className="flex justify-between text-xs text-slate-500 mt-1">
|
||||
<span className="font-semibold text-slate-900 flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(card.balance)}</span>
|
||||
<span className="flex items-center gap-0.5">Limit: <DirhamIcon className="w-3 h-3" /> {formatAmount(card.limit)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreditUtilizationList;
|
||||
55
src/components/dashboard/AIInsights.tsx
Normal file
55
src/components/dashboard/AIInsights.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { TrendingUp, AlertTriangle, ShieldCheck } from 'lucide-react';
|
||||
|
||||
const AIInsights: React.FC = () => {
|
||||
const insights = [
|
||||
{
|
||||
title: 'Portfolio Optimization',
|
||||
description: 'Rebalance to maintain 60/40 split.',
|
||||
type: 'optimization',
|
||||
icon: <TrendingUp className="w-5 h-5" />,
|
||||
colors: 'bg-green-50 border-green-100 text-green-700',
|
||||
iconColor: 'bg-green-100 text-green-600'
|
||||
},
|
||||
{
|
||||
title: 'Islamic Investment',
|
||||
description: 'New Sukuk opportunities available.',
|
||||
type: 'opportunity',
|
||||
icon: <DirhamIcon className="w-5 h-5" />,
|
||||
colors: 'bg-blue-50 border-blue-100 text-blue-700',
|
||||
iconColor: 'bg-blue-100 text-blue-600'
|
||||
},
|
||||
{
|
||||
title: 'Risk Management',
|
||||
description: 'Diversify sector exposure.',
|
||||
type: 'risk',
|
||||
icon: <ShieldCheck className="w-5 h-5" />,
|
||||
colors: 'bg-purple-50 border-purple-100 text-purple-700',
|
||||
iconColor: 'bg-purple-100 text-purple-600'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-4">AI Insights</h3>
|
||||
<div className="space-y-3">
|
||||
{insights.map((insight, idx) => (
|
||||
<div key={idx} className={`p-4 rounded-xl border flex gap-3 items-start ${insight.colors} transition-transform hover:scale-[1.02]`}>
|
||||
<div className={`p-2 rounded-lg shrink-0 ${insight.iconColor}`}>
|
||||
{insight.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-sm">{insight.title}</h4>
|
||||
<p className="text-xs opacity-80 mt-0.5">{insight.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIInsights;
|
||||
63
src/components/dashboard/GoalsList.tsx
Normal file
63
src/components/dashboard/GoalsList.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Target } from 'lucide-react';
|
||||
|
||||
const GoalsList: React.FC = () => {
|
||||
const goals = [
|
||||
{
|
||||
name: 'Emergency Fund',
|
||||
progress: 65,
|
||||
amount: '45k / 80k',
|
||||
color: 'bg-green-500',
|
||||
trackColor: 'bg-green-100'
|
||||
},
|
||||
{
|
||||
name: 'Dubai Marina Apt',
|
||||
progress: 42,
|
||||
amount: '850k / 2.0M',
|
||||
color: 'bg-blue-500',
|
||||
trackColor: 'bg-blue-100'
|
||||
},
|
||||
{
|
||||
name: 'Retirement Portfolio',
|
||||
progress: 28,
|
||||
amount: '1.2M / 4.5M',
|
||||
color: 'bg-purple-500',
|
||||
trackColor: 'bg-purple-100'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800 flex items-center gap-2">
|
||||
<Target className="w-5 h-5 text-purple-500" /> Financial Goals
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-6">
|
||||
{goals.map((goal, idx) => (
|
||||
<div key={idx} className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="font-medium text-slate-700">{goal.name}</span>
|
||||
<span className="text-slate-500 text-xs font-semibold">{goal.amount}</span>
|
||||
</div>
|
||||
<div className="h-2.5 w-full bg-slate-100 rounded-full overflow-hidden">
|
||||
<div className={`h-full ${goal.color} rounded-full`} style={{ width: `${goal.progress}%` }}></div>
|
||||
</div>
|
||||
<p className="text-xs text-right text-slate-400">{goal.progress}% Completed</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-6 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white shadow-md">
|
||||
Manage Goals
|
||||
</Button>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoalsList;
|
||||
85
src/components/dashboard/HeroStats.tsx
Normal file
85
src/components/dashboard/HeroStats.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
TrendingUp,
|
||||
DollarSign,
|
||||
PieChart,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { formatAmount } from '@/lib/utils'; // Keep import for potential reuse, though hardcoded values requested for now
|
||||
|
||||
const HeroStats: React.FC = () => {
|
||||
// Hardcoded data as per Target Design Reference
|
||||
const stats = [
|
||||
{
|
||||
label: 'Portfolio Value',
|
||||
value: '2,847,500',
|
||||
change: '+12.3%',
|
||||
isPositive: true,
|
||||
icon: <TrendingUp className="h-6 w-6 text-white" />,
|
||||
color: 'from-purple-500 to-indigo-600',
|
||||
iconBg: 'bg-white/20'
|
||||
},
|
||||
{
|
||||
label: 'Monthly Contributions',
|
||||
value: '12,470',
|
||||
change: '+5.7%',
|
||||
isPositive: true,
|
||||
icon: <DollarSign className="h-6 w-6 text-white" />,
|
||||
color: 'from-blue-500 to-cyan-600',
|
||||
iconBg: 'bg-white/20'
|
||||
},
|
||||
{
|
||||
label: 'Annual Returns',
|
||||
value: '12.7%',
|
||||
change: '+2.1%',
|
||||
isPositive: true,
|
||||
icon: <PieChart className="h-6 w-6 text-white" />,
|
||||
color: 'from-emerald-500 to-teal-600',
|
||||
iconBg: 'bg-white/20',
|
||||
isPercentage: true
|
||||
},
|
||||
{
|
||||
label: 'Risk Score',
|
||||
value: '3.2/10',
|
||||
change: '-0.3',
|
||||
isPositive: true, // Lower risk score change might be positive contextually, but design shows -0.3. Assuming red for negative change generally unless specified. Design says (-0.3).
|
||||
icon: <Shield className="h-6 w-6 text-white" />,
|
||||
color: 'from-orange-500 to-red-600',
|
||||
iconBg: 'bg-white/20',
|
||||
isScore: true
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{stats.map((stat, idx) => (
|
||||
<GlassCard key={idx} className="p-6 relative overflow-hidden group">
|
||||
<div className="relative z-10">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className={`p-3 rounded-xl bg-gradient-to-br ${stat.color} shadow-lg`}>
|
||||
{stat.icon}
|
||||
</div>
|
||||
<span className={`text-xs font-semibold px-2 py-1 rounded-full flex items-center ${stat.change.startsWith('+') ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{stat.change}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 font-medium">{stat.label}</p>
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
{!stat.isPercentage && !stat.isScore && <DirhamIcon className="w-5 h-5 text-slate-800" />}
|
||||
<h3 className="text-2xl font-bold text-slate-800">{stat.value}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Decorative background element */}
|
||||
<div className={`absolute -right-6 -bottom-6 w-24 h-24 rounded-full bg-gradient-to-br ${stat.color} opacity-10 blur-xl group-hover:opacity-20 transition-opacity duration-500`} />
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeroStats;
|
||||
73
src/components/dashboard/InvestmentPerformance.tsx
Normal file
73
src/components/dashboard/InvestmentPerformance.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
const InvestmentPerformance: React.FC = () => {
|
||||
// Dummy chart data for visualization
|
||||
const data = [
|
||||
{ name: 'Jan', value: 2400000 },
|
||||
{ name: 'Feb', value: 2450000 },
|
||||
{ name: 'Mar', value: 2550000 },
|
||||
{ name: 'Apr', value: 2500000 },
|
||||
{ name: 'May', value: 2650000 },
|
||||
{ name: 'Jun', value: 2847500 },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Investment Performance</h3>
|
||||
<select className="bg-slate-50 border-none text-sm text-slate-600 rounded-lg p-2 focus:ring-0 cursor-pointer">
|
||||
<option>Last 6 Months</option>
|
||||
<option>Year to Date</option>
|
||||
<option>All Time</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div>
|
||||
<p className="text-xs text-slate-500">Total Portfolio</p>
|
||||
<div className="flex items-center gap-1 font-bold text-slate-800 text-lg">
|
||||
<DirhamIcon className="w-4 h-4" /> 2.85M
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-500">Annual Return</p>
|
||||
<p className="font-bold text-green-600 text-lg">+12.7%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-500">Monthly Contrib.</p>
|
||||
<div className="flex items-center gap-1 font-bold text-slate-800 text-lg">
|
||||
<DirhamIcon className="w-4 h-4" /> 12.5K
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-slate-500">Risk Score</p>
|
||||
<p className="font-bold text-orange-500 text-lg">3.2/10</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-[200px] w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={data}>
|
||||
<defs>
|
||||
<linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="#8b5cf6" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis dataKey="name" axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: '#94a3b8' }} />
|
||||
<Tooltip
|
||||
contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
||||
/>
|
||||
<Area type="monotone" dataKey="value" stroke="#8b5cf6" strokeWidth={3} fillOpacity={1} fill="url(#colorValue)" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvestmentPerformance;
|
||||
49
src/components/dashboard/QuickActions.tsx
Normal file
49
src/components/dashboard/QuickActions.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { PlusCircle, Target, FileText, PiggyBank } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
const QuickActions: React.FC = () => {
|
||||
const actions = [
|
||||
{
|
||||
label: 'New Investment',
|
||||
icon: <PlusCircle className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-green-200 hover:bg-green-50/50 hover:text-green-700'
|
||||
},
|
||||
{
|
||||
label: 'Set Goals',
|
||||
icon: <Target className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-blue-200 hover:bg-blue-50/50 hover:text-blue-700'
|
||||
},
|
||||
{
|
||||
label: 'View Reports',
|
||||
icon: <FileText className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-purple-200 hover:bg-purple-50/50 hover:text-purple-700'
|
||||
},
|
||||
{
|
||||
label: 'Savings Plan',
|
||||
icon: <PiggyBank className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-pink-200 hover:bg-pink-50/50 hover:text-pink-700'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{actions.map((action, idx) => (
|
||||
<GlassCard
|
||||
key={idx}
|
||||
className={`p-4 flex flex-col items-center justify-center cursor-pointer transition-all active:scale-95 ${action.color} group`}
|
||||
hoverEffect
|
||||
>
|
||||
<div className="p-3 rounded-full bg-white shadow-sm mb-2 group-hover:scale-110 transition-transform">
|
||||
{action.icon}
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-700">{action.label}</span>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickActions;
|
||||
90
src/components/dashboard/TransactionsList.tsx
Normal file
90
src/components/dashboard/TransactionsList.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { ArrowUpRight, ArrowDownRight, Clock } from 'lucide-react';
|
||||
|
||||
const TransactionsList: React.FC = () => {
|
||||
const transactions = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Emirates NBD',
|
||||
date: 'Today, 2:30 PM',
|
||||
amount: '5,000',
|
||||
status: 'Successful',
|
||||
type: 'debit',
|
||||
icon: '🏦'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Dubai Properties',
|
||||
date: 'Yesterday, 10:00 AM',
|
||||
amount: '12,500',
|
||||
status: 'Processing',
|
||||
type: 'debit',
|
||||
icon: '🏢'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Gold Investment',
|
||||
date: 'Feb 12, 4:15 PM',
|
||||
amount: '3,200',
|
||||
status: 'Successful',
|
||||
type: 'credit', // Assuming investment purchase
|
||||
icon: '🪙'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'ADCB Islamic Fund',
|
||||
date: 'Feb 10, 9:00 AM',
|
||||
amount: '1,500',
|
||||
status: 'Successful',
|
||||
type: 'debit',
|
||||
icon: '📈'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Recent Transactions</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-4">
|
||||
{transactions.map((tx) => (
|
||||
<div key={tx.id} className="flex items-center justify-between p-3 hover:bg-slate-50 rounded-xl transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center text-xl shadow-sm group-hover:scale-105 transition-transform">
|
||||
{tx.icon}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800 text-sm">{tx.name}</p>
|
||||
<div className="flex items-center gap-1 text-xs text-slate-500">
|
||||
<Clock className="w-3 h-3" /> {tx.date}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center justify-end gap-1 font-bold text-slate-800">
|
||||
<DirhamIcon className="w-3 h-3" /> {tx.amount}
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${tx.status === 'Successful'
|
||||
? 'bg-green-50 text-green-600'
|
||||
: 'bg-yellow-50 text-yellow-600'
|
||||
}`}>
|
||||
{tx.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button variant="outline" className="w-full mt-6 text-slate-600 hover:text-purple-600 hover:border-purple-200">
|
||||
View All Transactions
|
||||
</Button>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionsList;
|
||||
@@ -1,197 +1,81 @@
|
||||
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, formatCurrency } from '@/lib/utils';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Plus, Settings } from 'lucide-react';
|
||||
|
||||
// Mock Data
|
||||
const budgetData = {
|
||||
limit: 15000,
|
||||
spent: 12500,
|
||||
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: 800000, current: 200000, color: 'bg-purple-500' },
|
||||
{ name: 'Retirement', target: 2000000, current: 150000, color: 'bg-indigo-500' },
|
||||
]
|
||||
};
|
||||
// Import New Modular Budget Components
|
||||
import BudgetSummaryCards from '@/components/budget/BudgetSummaryCards';
|
||||
import BudgetOverviewList from '@/components/budget/BudgetOverviewList';
|
||||
import RecentTransactionsCard from '@/components/budget/RecentTransactionsCard';
|
||||
import BudgetCategoryGrid from '@/components/budget/BudgetCategoryGrid';
|
||||
import FullExpensesTable from '@/components/budget/FullExpensesTable';
|
||||
import AddExpenseForm from '@/components/budget/AddExpenseForm';
|
||||
import SetBudgetForm from '@/components/budget/SetBudgetForm';
|
||||
|
||||
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 className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||
<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>
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900">
|
||||
Budget <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-green-600 to-teal-600">Manager</span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1">Track every dirham with precision.</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">
|
||||
{formatCurrency(budgetData.spent)}
|
||||
</span>
|
||||
<span className="text-xs sm:text-sm text-slate-400 mt-1">
|
||||
of {formatCurrency(budgetData.limit)}
|
||||
</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">{formatCurrency(cat.spent)}</span>
|
||||
<span className="text-slate-400">/ {formatCurrency(cat.limit)}</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>{formatCurrency(goal.current)}</span>
|
||||
<span>{formatCurrency(goal.target)}</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 className="flex gap-3">
|
||||
<Button variant="outline" className="border-slate-200 text-slate-700 bg-white hover:bg-slate-50">
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Expense
|
||||
</Button>
|
||||
<Button className="bg-black text-white hover:bg-slate-800 shadow-md">
|
||||
<Settings className="w-4 h-4 mr-2" /> Set Budget
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<BudgetSummaryCards />
|
||||
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-6 w-full">
|
||||
<TabsList className="bg-white/60 p-1.5 rounded-2xl backdrop-blur-md border border-white/40 inline-flex w-auto flex-wrap lg:flex-nowrap shadow-sm h-auto">
|
||||
<TabsTrigger value="overview" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="budgets" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Budgets</TabsTrigger>
|
||||
<TabsTrigger value="expenses" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Expenses</TabsTrigger>
|
||||
<TabsTrigger value="add-expense" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Add Expense</TabsTrigger>
|
||||
<TabsTrigger value="set-budget" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Set Budget</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Tab 1: Overview */}
|
||||
<TabsContent value="overview" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[450px]">
|
||||
<BudgetOverviewList />
|
||||
<RecentTransactionsCard />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 2: Budgets */}
|
||||
<TabsContent value="budgets" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<BudgetCategoryGrid />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 3: Expenses */}
|
||||
<TabsContent value="expenses" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<FullExpensesTable />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 4: Add Expense */}
|
||||
<TabsContent value="add-expense" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<AddExpenseForm />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 5: Set Budget */}
|
||||
<TabsContent value="set-budget" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<SetBudgetForm />
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,187 +1,65 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Calendar,
|
||||
List,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
CreditCard,
|
||||
Home,
|
||||
Zap,
|
||||
Clock,
|
||||
ArrowRight
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Calendar, Plus } from 'lucide-react';
|
||||
|
||||
// Mock Data
|
||||
const commitments = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Rent Payment',
|
||||
amount: 6500,
|
||||
dueDate: '2024-10-01',
|
||||
daysLeft: 3,
|
||||
icon: Home,
|
||||
status: 'Due Soon',
|
||||
color: 'bg-blue-100 text-blue-600'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Car Loan EMI',
|
||||
amount: 2100,
|
||||
dueDate: '2024-10-05',
|
||||
daysLeft: 7,
|
||||
icon: CreditCard,
|
||||
status: 'Upcoming',
|
||||
color: 'bg-purple-100 text-purple-600'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Electricity Bill',
|
||||
amount: 450,
|
||||
dueDate: '2024-10-10',
|
||||
daysLeft: 12,
|
||||
icon: Zap,
|
||||
status: 'Upcoming',
|
||||
color: 'bg-yellow-100 text-yellow-600'
|
||||
}
|
||||
];
|
||||
|
||||
const aiTips = [
|
||||
{
|
||||
title: 'Automate Rent',
|
||||
desc: 'Set up auto-pay to avoid late fees (AED 200 potential saving)',
|
||||
color: 'border-blue-200 bg-blue-50'
|
||||
},
|
||||
{
|
||||
title: 'Subscription Alert',
|
||||
desc: 'Unused Gym membership detected. Cancel to save AED 350.',
|
||||
color: 'border-red-200 bg-red-50'
|
||||
}
|
||||
];
|
||||
// Import New Modular Commitment Components
|
||||
import UpcomingCommitmentsList from '@/components/commitment/UpcomingCommitmentsList';
|
||||
import CommitmentAITips from '@/components/commitment/CommitmentAITips';
|
||||
import CommitmentCalendarTable from '@/components/commitment/CommitmentCalendarTable';
|
||||
import AddCommitmentForm from '@/components/commitment/AddCommitmentForm';
|
||||
|
||||
export default function CommitmentAdvisor() {
|
||||
const [view, setView] = useState<'list' | 'calendar'>('list');
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-6 sm:space-y-8 pb-20 animate-fade-in">
|
||||
<div className="header flex flex-col sm:flex-row justify-between items-start sm:items-end gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-slate-900 flex items-center gap-3">
|
||||
<Calendar className="w-6 h-6 sm:w-8 sm:h-8 text-blue-600" /> Commitment Advisor
|
||||
</h1>
|
||||
<p className="text-sm sm:text-base text-slate-500 mt-1">Stay on top of your bills and financial obligations.</p>
|
||||
</div>
|
||||
<div className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
|
||||
<div className="bg-slate-100 p-1 rounded-lg flex w-full sm:w-auto">
|
||||
<button
|
||||
onClick={() => setView('list')}
|
||||
className={cn("flex-1 sm:flex-none px-3 py-1.5 rounded-md text-sm font-medium transition-all flex items-center justify-center gap-2", view === 'list' ? "bg-white shadow-sm text-slate-900" : "text-slate-500 hover:text-slate-700")}
|
||||
>
|
||||
<List className="w-4 h-4" /> List
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setView('calendar')}
|
||||
className={cn("flex-1 sm:flex-none px-3 py-1.5 rounded-md text-sm font-medium transition-all flex items-center justify-center gap-2", view === 'calendar' ? "bg-white shadow-sm text-slate-900" : "text-slate-500 hover:text-slate-700")}
|
||||
>
|
||||
<Calendar className="w-4 h-4" /> Calendar
|
||||
</button>
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900 flex items-center gap-3">
|
||||
<Calendar className="w-6 h-6 text-blue-600" />
|
||||
<span>Commitment <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-cyan-600">Advisor</span></span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1 ml-9">Stay on top of your bills and financial obligations.</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="outline" className="border-slate-200 text-slate-700 bg-white hover:bg-slate-50">
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Commitment
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 sm:gap-8">
|
||||
{/* Main Content */}
|
||||
<div className="col-span-1 lg:col-span-2 space-y-4">
|
||||
<AnimatePresence mode="wait">
|
||||
{view === 'list' ? (
|
||||
<motion.div
|
||||
key="list"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
{commitments.map((item) => (
|
||||
<GlassCard key={item.id} className="p-5 flex items-center justify-between group hover:border-blue-200 transition-all">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={cn("p-3 rounded-xl", item.color)}>
|
||||
<item.icon className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-900">{item.title}</h3>
|
||||
<div className="flex items-center gap-2 text-sm text-slate-500">
|
||||
<Clock className="w-3.5 h-3.5" /> Due {item.dueDate}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-6 w-full">
|
||||
<TabsList className="bg-white/60 p-1.5 rounded-2xl backdrop-blur-md border border-white/40 inline-flex w-auto flex-wrap lg:flex-nowrap shadow-sm h-auto">
|
||||
<TabsTrigger value="overview" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="calendar" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Calendar</TabsTrigger>
|
||||
<TabsTrigger value="add" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Add</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="text-right flex items-center gap-6">
|
||||
<div>
|
||||
<p className="font-bold text-lg text-slate-900">AED {item.amount.toLocaleString()}</p>
|
||||
<Badge variant={item.daysLeft <= 3 ? "destructive" : "secondary"}>
|
||||
{item.status} ({item.daysLeft} days)
|
||||
</Badge>
|
||||
</div>
|
||||
<Button size="icon" variant="ghost" className="opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<CheckCircle className="w-6 h-6 text-green-500" />
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
))}
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="calendar"
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
>
|
||||
<GlassCard className="p-8 text-center min-h-[400px] flex flex-col items-center justify-center">
|
||||
<Calendar className="w-16 h-16 text-slate-200 mb-4" />
|
||||
<h3 className="text-lg font-semibold text-slate-700">Calendar View</h3>
|
||||
<p className="text-slate-500 max-w-sm">
|
||||
Calendar visualization is currently in development. Please use the List view to manage your upcoming payments.
|
||||
</p>
|
||||
</GlassCard>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="col-span-1 space-y-6">
|
||||
<div className="bg-slate-900 text-white p-6 rounded-3xl relative overflow-hidden">
|
||||
<div className="relative z-10">
|
||||
<h3 className="font-bold text-lg mb-2">Total Due this Month</h3>
|
||||
<p className="text-3xl font-bold">AED 9,050</p>
|
||||
<p className="text-slate-400 text-sm mt-1">3 payments remaining</p>
|
||||
<div className="h-1.5 w-full bg-slate-800 rounded-full mt-4 overflow-hidden">
|
||||
<div className="h-full bg-blue-500 w-2/3 rounded-full" />
|
||||
</div>
|
||||
{/* Tab 1: Overview */}
|
||||
<TabsContent value="overview" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 h-auto">
|
||||
<UpcomingCommitmentsList />
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 p-4 opacity-10">
|
||||
<CreditCard className="w-32 h-32" />
|
||||
<div className="lg:col-span-1 h-auto">
|
||||
<CommitmentAITips />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<h3 className="font-bold text-slate-800 flex items-center gap-2">
|
||||
<Zap className="w-5 h-5 text-yellow-500" /> AI Insights
|
||||
</h3>
|
||||
{/* Tab 2: Calendar */}
|
||||
<TabsContent value="calendar" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<CommitmentCalendarTable />
|
||||
</TabsContent>
|
||||
|
||||
{aiTips.map((tip, idx) => (
|
||||
<GlassCard key={idx} className={cn("p-4 border-l-4", tip.color)}>
|
||||
<h4 className="font-semibold text-sm mb-1">{tip.title}</h4>
|
||||
<p className="text-xs text-slate-600">{tip.desc}</p>
|
||||
<Button variant="link" className="h-auto p-0 text-xs mt-2 text-slate-900">
|
||||
Take Action <ArrowRight className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Tab 3: Add */}
|
||||
<TabsContent value="add" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<AddCommitmentForm />
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,179 +1,75 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import {
|
||||
ShieldCheck,
|
||||
TrendingUp,
|
||||
CreditCard,
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
Snowflake,
|
||||
Landmark
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from 'recharts';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ShieldCheck, FileText, Plus } from 'lucide-react';
|
||||
|
||||
// Mock Data
|
||||
const creditData = {
|
||||
score: 768,
|
||||
status: 'Excellent',
|
||||
history: [
|
||||
{ month: 'Jan', score: 720 },
|
||||
{ month: 'Mar', score: 745 },
|
||||
{ month: 'Jun', score: 755 },
|
||||
{ month: 'Sep', score: 768 },
|
||||
],
|
||||
debts: [
|
||||
{ name: 'Credit Card A', principal: 15000, interest: 2500, rate: '18%', minPay: 800 },
|
||||
{ name: 'Personal Loan', principal: 45000, interest: 8000, rate: '12%', minPay: 2200 },
|
||||
{ name: 'Car Loan', principal: 85000, interest: 12000, rate: '9%', minPay: 3500 },
|
||||
],
|
||||
strategy: {
|
||||
method: 'Snowball',
|
||||
recommendation: 'Pay off Credit Card A first to save AED 450 in interest.',
|
||||
savedInterest: 450
|
||||
}
|
||||
};
|
||||
|
||||
const CreditGauge = ({ score }: { score: number }) => {
|
||||
const percentage = (score / 900) * 100;
|
||||
const circumference = 2 * Math.PI * 80;
|
||||
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div className="relative w-48 h-48 flex items-center justify-center">
|
||||
<svg className="w-full h-full transform -rotate-90">
|
||||
<circle
|
||||
cx="96"
|
||||
cy="96"
|
||||
r="80"
|
||||
stroke="#e2e8f0"
|
||||
strokeWidth="16"
|
||||
fill="transparent"
|
||||
/>
|
||||
<motion.circle
|
||||
initial={{ strokeDashoffset: circumference }}
|
||||
animate={{ strokeDashoffset }}
|
||||
transition={{ duration: 1.5, ease: "easeOut" }}
|
||||
cx="96"
|
||||
cy="96"
|
||||
r="80"
|
||||
stroke="#10b981" // Green-500
|
||||
strokeWidth="16"
|
||||
fill="transparent"
|
||||
strokeDasharray={circumference}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center">
|
||||
<span className="text-4xl font-bold text-slate-900">{score}</span>
|
||||
<Badge className="bg-green-100 text-green-700 hover:bg-green-100 mt-1">Excellent</Badge>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// Import New Modular Credit Components
|
||||
import CreditSummaryCards from '@/components/credit/CreditSummaryCards';
|
||||
import CreditScoreProgress from '@/components/credit/CreditScoreProgress';
|
||||
import CreditUtilizationList from '@/components/credit/CreditUtilizationList';
|
||||
import CreditCardsList from '@/components/credit/CreditCardsList';
|
||||
import CreditScoreTips from '@/components/credit/CreditScoreTips';
|
||||
import AECBInfoCard from '@/components/credit/AECBInfoCard';
|
||||
|
||||
export default function CreditManager() {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-6 sm:space-y-8 pb-20 animate-fade-in">
|
||||
<div className="header">
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-slate-900 flex items-center gap-3">
|
||||
<ShieldCheck className="w-6 h-6 sm:w-8 sm:h-8 text-green-600" /> AI Credit Slashing System
|
||||
</h1>
|
||||
<p className="text-sm sm:text-base text-slate-500 mt-1">Master your credit health and eliminate debt faster.</p>
|
||||
</div>
|
||||
<div className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 sm:gap-8">
|
||||
{/* Credit Health Score */}
|
||||
<GlassCard className="col-span-1 p-6 flex flex-col items-center justify-center space-y-4">
|
||||
<h3 className="font-semibold text-slate-700 self-start w-full flex justify-between items-center">
|
||||
Credit Health
|
||||
<span className="text-green-600 text-xs sm:text-sm flex items-center bg-green-50 px-2 py-1 rounded">
|
||||
<TrendingUp className="w-3 h-3 mr-1" /> +13 pts
|
||||
</span>
|
||||
</h3>
|
||||
<CreditGauge score={creditData.score} />
|
||||
<p className="text-sm text-center text-slate-500 max-w-[200px]">
|
||||
Your score is higher than 85% of users in UAE. Keep it up!
|
||||
</p>
|
||||
</GlassCard>
|
||||
|
||||
{/* Strategy & Visualizer */}
|
||||
<div className="col-span-1 lg:col-span-2 space-y-6">
|
||||
{/* Strategy Card */}
|
||||
<GlassCard className="p-6 bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 bg-white rounded-xl shadow-sm text-blue-600">
|
||||
<Snowflake className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-900">Strategy: {creditData.strategy.method} Method Recommended</h3>
|
||||
<p className="text-slate-600 mt-1">{creditData.strategy.recommendation}</p>
|
||||
<Button size="sm" className="mt-4 bg-blue-600 hover:bg-blue-700 text-white gap-2">
|
||||
Apply Strategy <ArrowRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Debt Visualizer */}
|
||||
<GlassCard className="p-6">
|
||||
<h3 className="font-semibold text-slate-700 mb-6 flex items-center gap-2">
|
||||
<Landmark className="w-5 h-5 text-slate-500" /> Debt Breakdown
|
||||
</h3>
|
||||
<div className="h-64 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={creditData.debts} layout="vertical" barSize={20}>
|
||||
<XAxis type="number" hide />
|
||||
<YAxis dataKey="name" type="category" width={100} tick={{ fontSize: 12 }} />
|
||||
<Tooltip cursor={{ fill: 'transparent' }} contentStyle={{ borderRadius: '8px' }} />
|
||||
<Legend />
|
||||
<Bar dataKey="principal" stackId="a" fill="#94a3b8" name="Principal" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="interest" stackId="a" fill="#ef4444" name="Interest" radius={[0, 10, 10, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900 flex items-center gap-3">
|
||||
<ShieldCheck className="w-6 h-6 text-green-600" />
|
||||
<span>Credit <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-indigo-600">Manager</span></span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1 ml-9">Master your credit health and eliminate debt.</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" className="border-slate-200 text-slate-700 bg-white hover:bg-slate-50">
|
||||
<FileText className="w-4 h-4 mr-2" /> Credit Report
|
||||
</Button>
|
||||
<Button className="bg-black text-white hover:bg-slate-800 shadow-md">
|
||||
<Plus className="w-4 h-4 mr-2" /> Apply for Card
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Offers / Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<GlassCard className="p-5 flex items-center gap-4 bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
||||
<div className="p-3 bg-white/10 rounded-xl">
|
||||
<CreditCard className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-300">Total Credit Utilization</p>
|
||||
<p className="text-2xl font-bold">12%</p>
|
||||
</div>
|
||||
<Progress value={12} className="w-16 h-16 ml-auto rounded-full" />
|
||||
</GlassCard>
|
||||
{/* Summary Cards */}
|
||||
<CreditSummaryCards />
|
||||
|
||||
<GlassCard className="p-5 flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold text-slate-900">Next Payment</p>
|
||||
<p className="text-sm text-slate-500">Credit Card A • Due in 3 days</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold text-slate-900">AED 800</p>
|
||||
<Button size="sm" variant="outline" className="h-7 text-xs mt-1">Pay Now</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-6 w-full">
|
||||
<TabsList className="bg-white/60 p-1.5 rounded-2xl backdrop-blur-md border border-white/40 inline-flex w-auto flex-wrap lg:flex-nowrap shadow-sm h-auto">
|
||||
<TabsTrigger value="overview" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="cards" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Cards</TabsTrigger>
|
||||
<TabsTrigger value="score-tips" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Score Tips</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<GlassCard className="p-5 border-yellow-200 bg-yellow-50/50">
|
||||
<div className="flex gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-yellow-600" />
|
||||
<div>
|
||||
<h4 className="font-semibold text-yellow-800 text-sm">Action Required</h4>
|
||||
<p className="text-xs text-yellow-700 mt-1">Car Loan interest rate revised to 9.2%. Review refinance options.</p>
|
||||
{/* Tab 1: Overview */}
|
||||
<TabsContent value="overview" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[450px]">
|
||||
<CreditScoreProgress />
|
||||
<CreditUtilizationList />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 2: Cards */}
|
||||
<TabsContent value="cards" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<CreditCardsList />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 3: Score Tips */}
|
||||
<TabsContent value="score-tips" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-slate-800">Tips to Improve</h3>
|
||||
<CreditScoreTips />
|
||||
</div>
|
||||
<AECBInfoCard />
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
AlertCircle,
|
||||
Plus
|
||||
} from 'lucide-react';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import { cn, formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
const GoalPlanning: React.FC = () => {
|
||||
const financialGoals = [
|
||||
@@ -262,7 +263,10 @@ const GoalPlanning: React.FC = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Target Amount</p>
|
||||
<p className="text-2xl font-bold">{formatCurrency(progressInsights.totalTargetAmount)}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-5 h-5 text-slate-900" />
|
||||
<p className="text-2xl font-bold">{formatAmount(progressInsights.totalTargetAmount)}</p>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Total across goals</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center">
|
||||
@@ -277,7 +281,10 @@ const GoalPlanning: React.FC = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Monthly SIP</p>
|
||||
<p className="text-2xl font-bold">{formatCurrency(progressInsights.monthlyCommitment)}</p>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-5 h-5 text-slate-900" />
|
||||
<p className="text-2xl font-bold">{formatAmount(progressInsights.monthlyCommitment)}</p>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Total commitment</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center">
|
||||
@@ -334,7 +341,9 @@ const GoalPlanning: React.FC = () => {
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>Progress</span>
|
||||
<span>{formatCurrency(goal.currentAmount)} / {formatCurrency(goal.targetAmount)}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(goal.currentAmount)} / <DirhamIcon className="w-3 h-3" /> {formatAmount(goal.targetAmount)}
|
||||
</div>
|
||||
</div>
|
||||
<Progress value={(goal.currentAmount / goal.targetAmount) * 100} className="h-3" />
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
@@ -345,7 +354,9 @@ const GoalPlanning: React.FC = () => {
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-muted-foreground">Monthly SIP</p>
|
||||
<p className="font-semibold">{formatCurrency(goal.monthlyContribution)}</p>
|
||||
<div className="flex items-center gap-1 font-semibold">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(goal.monthlyContribution)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Time Remaining</p>
|
||||
@@ -406,15 +417,21 @@ const GoalPlanning: React.FC = () => {
|
||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-muted-foreground">Current</p>
|
||||
<p className="font-semibold">{formatCurrency(goal.currentAmount)}</p>
|
||||
<div className="flex items-center gap-0.5 font-semibold">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(goal.currentAmount)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Monthly SIP</p>
|
||||
<p className="font-semibold">{formatCurrency(goal.monthlyContribution)}</p>
|
||||
<div className="flex items-center gap-0.5 font-semibold">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(goal.monthlyContribution)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Remaining</p>
|
||||
<p className="font-semibold">{formatCurrency(goal.targetAmount - goal.currentAmount)}</p>
|
||||
<div className="flex items-center gap-0.5 font-semibold">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(goal.targetAmount - goal.currentAmount)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -511,30 +528,26 @@ const GoalPlanning: React.FC = () => {
|
||||
<div className="space-y-3">
|
||||
{goal.milestones.map((milestone, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 rounded-lg bg-gray-50">
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center gap-3 w-full">
|
||||
{milestone.completed ? (
|
||||
<CheckCircle className="w-5 h-5 text-green-500 mr-3" />
|
||||
<CheckCircle className="w-5 h-5 text-green-500 shrink-0" />
|
||||
) : (
|
||||
<Clock className="w-5 h-5 text-gray-400 mr-3" />
|
||||
<Clock className="w-5 h-5 text-gray-400 shrink-0" />
|
||||
)}
|
||||
<div>
|
||||
<p className="font-medium">{formatCurrency(milestone.amount)}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Target: {new Date(milestone.date).toLocaleDateString()}
|
||||
</p>
|
||||
<div className="flex-1 flex justify-between items-center text-sm">
|
||||
<div className="flex items-center gap-1 font-medium">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(milestone.amount)}
|
||||
</div>
|
||||
<span className={cn(
|
||||
"px-2.5 py-0.5 rounded-full text-xs font-medium border",
|
||||
milestone.completed
|
||||
? "bg-green-50 text-green-700 border-green-200"
|
||||
: "bg-slate-50 text-slate-600 border-slate-200"
|
||||
)}>
|
||||
{milestone.completed ? 'Completed' : 'Pending'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
{milestone.completed ? (
|
||||
<Badge className="text-green-600 bg-green-50 border-green-200">
|
||||
Completed
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="text-gray-600 bg-gray-50 border-gray-200">
|
||||
Pending
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -545,7 +558,7 @@ const GoalPlanning: React.FC = () => {
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
BarChart3,
|
||||
Target,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
PieChart,
|
||||
Wallet,
|
||||
Coins // Replaced IndianRupee with Coins as a generic financial icon or could use a custom Dirham icon
|
||||
} from 'lucide-react';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
// Import New Modular Portfolio Components
|
||||
import PortfolioSummaryCards from '@/components/portfolio/PortfolioSummaryCards';
|
||||
import AssetAllocation from '@/components/portfolio/AssetAllocation';
|
||||
import QuickInvestmentForm from '@/components/portfolio/QuickInvestmentForm';
|
||||
import HoldingsTable from '@/components/portfolio/HoldingsTable';
|
||||
import RiskAnalysis from '@/components/portfolio/RiskAnalysis';
|
||||
import PortfolioAIInsights from '@/components/portfolio/PortfolioAIInsights';
|
||||
|
||||
const PortfolioManager: React.FC = () => {
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('1M');
|
||||
@@ -76,28 +72,28 @@ const PortfolioManager: React.FC = () => {
|
||||
}
|
||||
];
|
||||
|
||||
const sectorAllocation = [
|
||||
{ sector: 'Banking', percentage: 35, value: 122500, color: 'from-blue-400 to-blue-600' },
|
||||
{ sector: 'Real Estate', percentage: 25, value: 87500, color: 'from-green-400 to-green-600' },
|
||||
{ sector: 'Utilities', percentage: 20, value: 70000, color: 'from-purple-400 to-purple-600' },
|
||||
{ sector: 'Telecom', percentage: 15, value: 52500, color: 'from-red-400 to-red-600' },
|
||||
{ sector: 'Others', percentage: 5, value: 17500, color: 'from-yellow-400 to-yellow-600' }
|
||||
];
|
||||
|
||||
const recommendations = [
|
||||
{
|
||||
type: 'rebalance',
|
||||
title: 'Rebalance Portfolio',
|
||||
description: 'Your real estate allocation is 5% above target. Consider reducing exposure.',
|
||||
priority: 'medium',
|
||||
action: `Sell ${formatCurrency(5000)} worth of real estate stocks`
|
||||
description: 'Your real estate exposure is above target allocation.',
|
||||
priority: 'high',
|
||||
action: (
|
||||
<span className="flex items-center gap-1">
|
||||
Sell <DirhamIcon className="w-3 h-3 inline" /> {formatAmount(5000)} worth of real estate stocks
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: 'opportunity',
|
||||
title: 'Investment Opportunity',
|
||||
description: 'Renewable energy ETF showing strong momentum with low correlation to your existing holdings.',
|
||||
priority: 'high',
|
||||
action: `Consider investing ${formatCurrency(10000)}`
|
||||
title: 'Sector Opportunity',
|
||||
description: 'Tech sector is showing strong momentum.',
|
||||
priority: 'medium',
|
||||
action: (
|
||||
<span className="flex items-center gap-1">
|
||||
Consider investing <DirhamIcon className="w-3 h-3 inline" /> {formatAmount(10000)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: 'risk',
|
||||
@@ -108,173 +104,63 @@ const PortfolioManager: React.FC = () => {
|
||||
}
|
||||
];
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high': return 'text-red-600 bg-red-50 border-red-200';
|
||||
case 'medium': return 'text-yellow-600 bg-yellow-50 border-yellow-200';
|
||||
case 'low': return 'text-green-600 bg-green-50 border-green-200';
|
||||
default: return 'text-slate-600 bg-slate-50 border-slate-200';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in text-slate-800">
|
||||
<div className="fade-in mb-8">
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900">
|
||||
Portfolio <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-cyan-600">Manager</span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-2">
|
||||
AI-powered insights for your investments.
|
||||
</p>
|
||||
<div className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900">
|
||||
Portfolio <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-cyan-600">Manager</span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1">Smart insights for your investments.</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" className="border-slate-200 text-slate-700 bg-white hover:bg-slate-50">
|
||||
Auto Rebalance
|
||||
</Button>
|
||||
<Button className="bg-black text-white hover:bg-slate-800 shadow-md">
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Investment
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Portfolio Overview */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 slide-up">
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Value</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-green-400 to-green-600 flex items-center justify-center shadow-lg shadow-green-200">
|
||||
<BarChart3 className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{formatCurrency(portfolioOverview.totalValue)}</p>
|
||||
<div className="flex items-center mt-2">
|
||||
<TrendingUp className="w-4 h-4 text-green-500 mr-1" />
|
||||
<span className="text-sm text-green-600 font-medium">+{portfolioOverview.gainPercentage}%</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* Summary Cards */}
|
||||
<PortfolioSummaryCards overview={portfolioOverview} holdingsCount={holdings.length} />
|
||||
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Gains</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg shadow-blue-200">
|
||||
<TrendingUp className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{formatCurrency(portfolioOverview.totalGains)}</p>
|
||||
<p className="text-xs text-slate-400 mt-2">vs {formatCurrency(portfolioOverview.totalInvested)} invested</p>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Day Change</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-400 to-cyan-600 flex items-center justify-center shadow-lg shadow-cyan-200">
|
||||
<Coins className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{formatCurrency(portfolioOverview.dayChange)}</p>
|
||||
<div className="flex items-center mt-2">
|
||||
<ArrowUpRight className="w-4 h-4 text-green-500 mr-1" />
|
||||
<span className="text-sm text-green-600 font-medium">+{portfolioOverview.dayChangePercent}%</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Risk Score</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center shadow-lg shadow-purple-200">
|
||||
<Target className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">7.2</p>
|
||||
<p className="text-xs text-yellow-600 font-medium mt-2 bg-yellow-100 inline-block px-2 py-0.5 rounded-full">Moderate</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="holdings" className="space-y-6 w-full">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-white/40 p-1 rounded-2xl backdrop-blur-md border border-white/20">
|
||||
<TabsTrigger value="holdings" className="rounded-xl data-[state=active]:bg-white data-[state=active]:shadow-sm">Holdings</TabsTrigger>
|
||||
<TabsTrigger value="allocation" className="rounded-xl data-[state=active]:bg-white data-[state=active]:shadow-sm">Allocation</TabsTrigger>
|
||||
<TabsTrigger value="recommendations" className="rounded-xl data-[state=active]:bg-white data-[state=active]:shadow-sm">AI Insights</TabsTrigger>
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-6 w-full">
|
||||
<TabsList className="bg-white/60 p-1.5 rounded-2xl backdrop-blur-md border border-white/40 inline-flex w-auto shadow-sm">
|
||||
<TabsTrigger value="overview" className="rounded-xl px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="holdings" className="rounded-xl px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Holdings</TabsTrigger>
|
||||
<TabsTrigger value="analytics" className="rounded-xl px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Analytics</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="holdings" className="space-y-4 outline-none">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{holdings.map((holding, index) => (
|
||||
<GlassCard key={index} className="p-5 flex flex-col justify-between h-full" hoverEffect>
|
||||
<div>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="p-2 bg-slate-100 rounded-lg">
|
||||
<span className="font-bold text-slate-700">{holding.symbol.substring(0, 4)}</span>
|
||||
</div>
|
||||
<Badge variant="outline" className="bg-white/50">{holding.sector}</Badge>
|
||||
</div>
|
||||
<h3 className="font-bold text-lg text-slate-900">{holding.name}</h3>
|
||||
<p className="text-sm text-slate-500">{holding.shares} shares @ {formatCurrency(holding.currentPrice)}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-slate-100">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<p className="text-xs text-slate-400">Current Value</p>
|
||||
<p className="font-semibold text-lg">{formatCurrency(holding.currentValue)}</p>
|
||||
</div>
|
||||
<div className={`text-right ${holding.gain > 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
<p className="text-xs font-medium flex items-center justify-end">
|
||||
{holding.gain > 0 ? <ArrowUpRight className="w-3 h-3 mr-1" /> : <ArrowDownRight className="w-3 h-3 mr-1" />}
|
||||
{holding.gainPercent}%
|
||||
</p>
|
||||
<p className="text-sm font-bold">
|
||||
{holding.gain > 0 ? '+' : ''}{formatCurrency(holding.gain)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
))}
|
||||
{/* Tab 1: Overview */}
|
||||
<TabsContent value="overview" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-[400px]">
|
||||
<AssetAllocation holdings={holdings} totalValue={portfolioOverview.totalValue} />
|
||||
<QuickInvestmentForm holdings={holdings} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="allocation" className="space-y-4 outline-none">
|
||||
<GlassCard className="p-6">
|
||||
<div className="space-y-6">
|
||||
{sectorAllocation.map((sector, index) => (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-3 h-3 rounded-full bg-gradient-to-r ${sector.color}`}></div>
|
||||
<span className="font-medium text-slate-700">{sector.sector}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="font-bold text-slate-900">{sector.percentage}%</span>
|
||||
<p className="text-xs text-slate-500">{formatCurrency(sector.value)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
className={`h-full bg-gradient-to-r ${sector.color} transition-all duration-1000`}
|
||||
style={{ width: `${sector.percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Tab 2: Holdings */}
|
||||
<TabsContent value="holdings" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<HoldingsTable holdings={holdings} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 3: Analytics */}
|
||||
<TabsContent value="analytics" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-1 h-full">
|
||||
<RiskAnalysis />
|
||||
</div>
|
||||
<div className="lg:col-span-2 h-full">
|
||||
<PortfolioAIInsights recommendations={recommendations} />
|
||||
</div>
|
||||
</GlassCard>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="recommendations" className="space-y-4 outline-none">
|
||||
<div className="grid gap-4">
|
||||
{recommendations.map((rec, index) => (
|
||||
<GlassCard key={index} className={`border-l-4 ${getPriorityColor(rec.priority)} p-6`}>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-800">{rec.title}</h3>
|
||||
<p className="text-slate-600 mt-1">{rec.description}</p>
|
||||
</div>
|
||||
<Badge variant="outline" className={`${getPriorityColor(rec.priority)} bg-white/50 backdrop-blur-sm`}>
|
||||
{rec.priority.charAt(0).toUpperCase() + rec.priority.slice(1)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between pt-4 border-t border-slate-100/50">
|
||||
<p className="text-sm font-medium text-slate-700">{rec.action}</p>
|
||||
<Button variant="ghost" size="sm" className="hover:bg-slate-100">
|
||||
Take Action
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useTheme } from '@/context/ThemeContext';
|
||||
import {
|
||||
User,
|
||||
ShieldCheck,
|
||||
@@ -15,10 +16,12 @@ import {
|
||||
Trash2,
|
||||
LogOut,
|
||||
CheckCircle,
|
||||
Building
|
||||
Building,
|
||||
Sparkles
|
||||
} from 'lucide-react';
|
||||
|
||||
export const Profile = () => {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [isShariaEnabled, setIsShariaEnabled] = useState(false);
|
||||
const [notifications, setNotifications] = useState(true);
|
||||
const [currency, setCurrency] = useState('AED');
|
||||
@@ -137,6 +140,40 @@ export const Profile = () => {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Appearance & Themes */}
|
||||
<GlassCard className="p-6 space-y-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 bg-pink-100 rounded-lg text-pink-600">
|
||||
<Sparkles className="w-5 h-5" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold">Appearance</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{['default', 'dubai', 'minimal', 'nature'].map((themeName) => (
|
||||
<button
|
||||
key={themeName}
|
||||
onClick={() => setTheme(themeName as any)}
|
||||
className={`relative h-20 rounded-xl overflow-hidden border-2 transition-all ${theme === themeName ? 'border-blue-600 shadow-md scale-[1.02]' : 'border-transparent hover:scale-[1.02]'}`}
|
||||
>
|
||||
{themeName === 'default' && <div className="absolute inset-0 bg-gradient-to-br from-purple-100 to-blue-100" />}
|
||||
{themeName === 'dubai' && <img src="https://images.unsplash.com/photo-1512453979798-5ea9ba6a80f4?q=80&w=300&auto=format&fit=crop" className="absolute inset-0 w-full h-full object-cover" />}
|
||||
{themeName === 'minimal' && <img src="https://images.unsplash.com/photo-1550684848-fac1c5b4e853?q=80&w=300&auto=format&fit=crop" className="absolute inset-0 w-full h-full object-cover" />}
|
||||
{themeName === 'nature' && <img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=300&auto=format&fit=crop" className="absolute inset-0 w-full h-full object-cover" />}
|
||||
|
||||
<div className="absolute inset-0 bg-black/20 flex items-center justify-center">
|
||||
<span className="text-white font-medium text-xs capitalized drop-shadow-md capitalize">{themeName}</span>
|
||||
</div>
|
||||
{theme === themeName && (
|
||||
<div className="absolute top-1 right-1 bg-blue-600 rounded-full p-0.5">
|
||||
<CheckCircle className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Data Privacy (UAE PDPL) */}
|
||||
<GlassCard className="p-6 space-y-6 border-red-100/50">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
|
||||
@@ -1,235 +1,82 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
TrendingUp,
|
||||
Zap,
|
||||
ArrowRight,
|
||||
Repeat,
|
||||
Layers,
|
||||
CheckCircle2,
|
||||
PlayCircle
|
||||
} from 'lucide-react';
|
||||
import { cn, formatCurrency } from '@/lib/utils';
|
||||
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip } from 'recharts';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Zap, Plus, Calculator } from 'lucide-react';
|
||||
|
||||
// Mock Data
|
||||
const savingsData = {
|
||||
current: 5000,
|
||||
potential: 7200,
|
||||
score: 6.8, // Out of 10
|
||||
yearlyProjection: [
|
||||
{ month: 'Jan', current: 5000, optimized: 7200 },
|
||||
{ month: 'Mar', current: 15000, optimized: 21600 },
|
||||
{ month: 'Jun', current: 30000, optimized: 43200 },
|
||||
{ month: 'Sep', current: 45000, optimized: 64800 },
|
||||
{ month: 'Dec', current: 60000, optimized: 86400 },
|
||||
],
|
||||
opportunities: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'Duplicate',
|
||||
title: 'Netflix & Prime Video (Entertainment)',
|
||||
action: 'Merge/Cancel',
|
||||
impact: '+ AED 45/mo',
|
||||
icon: Repeat,
|
||||
color: 'text-red-500 bg-red-100'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'Alternative',
|
||||
title: 'Switch Credit Card (Cashback 5% vs 1%)',
|
||||
action: 'Apply Now',
|
||||
impact: '+ AED 150/mo',
|
||||
icon: Shuffle,
|
||||
color: 'text-blue-500 bg-blue-100'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'Bundle',
|
||||
title: 'Etisalat Home + Mobile Bundle',
|
||||
action: 'View Deal',
|
||||
impact: '+ AED 80/mo',
|
||||
icon: Layers,
|
||||
color: 'text-purple-500 bg-purple-100'
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
function Shuffle(props: any) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M2 18h1.4c1.3 0 2.5-.6 3.3-1.7l14.2-9.4c.5-.3 1.1-.5 1.7-.5H22" />
|
||||
<path d="M2 5h1.4c1.3 0 2.5.6 3.3 1.7l14.2 9.4c.5.3 1.1.5 1.7.5H22" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const RadialGauge = ({ value }: { value: number }) => {
|
||||
const rotation = -90 + (value / 10) * 180; // Map 0-10 to -90 to 90 degrees
|
||||
const color = value < 5 ? 'text-red-500' : value < 8 ? 'text-yellow-500' : 'text-green-500';
|
||||
|
||||
return (
|
||||
<div className="relative w-40 h-24 overflow-hidden flex items-end justify-center">
|
||||
{/* Background Arc */}
|
||||
<div className="absolute w-40 h-40 rounded-full border-[12px] border-slate-100 top-0 left-0" />
|
||||
|
||||
{/* Value Arc (Simulated with rotation) */}
|
||||
<div
|
||||
className="absolute w-40 h-40 rounded-full border-[12px] border-transparent border-t-current top-0 left-0 transition-transform duration-1000 ease-out origin-bottom"
|
||||
style={{
|
||||
transform: `rotate(${rotation}deg)`,
|
||||
color: value < 5 ? '#ef4444' : value < 8 ? '#eab308' : '#22c55e'
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="text-center z-10 mb-2">
|
||||
<span className={cn("text-4xl font-bold", color)}>{value}</span>
|
||||
<span className="text-xs text-slate-400 block uppercase tracking-wider">Score</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// Import New Modular Savings Components
|
||||
import SavingsSummaryCards from '@/components/savings/SavingsSummaryCards';
|
||||
import SavingsGoalsList from '@/components/savings/SavingsGoalsList';
|
||||
import SavingsAccountsList from '@/components/savings/SavingsAccountsList';
|
||||
import SavingsGoalsGrid from '@/components/savings/SavingsGoalsGrid';
|
||||
import SavingsAccountsTable from '@/components/savings/SavingsAccountsTable';
|
||||
import CompoundInterestCalculator from '@/components/savings/CompoundInterestCalculator';
|
||||
import AddGoalForm from '@/components/savings/AddGoalForm';
|
||||
|
||||
export default function SavingsBooster() {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-6 sm:space-y-8 pb-20 animate-fade-in">
|
||||
<div className="header">
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-slate-900 flex items-center gap-3">
|
||||
<Zap className="w-6 h-6 sm:w-8 sm:h-8 text-yellow-500 fill-yellow-500" /> Personal Savings Booster
|
||||
</h1>
|
||||
<p className="text-sm sm:text-base text-slate-500 mt-1">AI-driven optimization to maximize your wealth.</p>
|
||||
</div>
|
||||
<div className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6">
|
||||
{/* Hero Comparison */}
|
||||
<div className="col-span-1 lg:col-span-2 space-y-4 sm:space-y-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<GlassCard className="p-6 flex flex-col items-center justify-center text-center">
|
||||
<span className="text-slate-400 text-sm mb-2">Optimization Score</span>
|
||||
<RadialGauge value={savingsData.score} />
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-6 flex flex-col justify-center space-y-2 relative overflow-hidden">
|
||||
<span className="text-slate-500 text-sm">Monthly Potential</span>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-3xl font-bold text-slate-900">{formatCurrency(savingsData.potential)}</span>
|
||||
<span className="text-sm text-slate-400">/mo</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-green-600 text-sm font-medium bg-green-50 px-2 py-1 rounded w-fit">
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
+ {formatCurrency(savingsData.potential - savingsData.current)} vs current
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 p-4 opacity-10">
|
||||
<TrendingUp className="w-24 h-24 text-green-500" />
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-6 flex flex-col justify-center space-y-4">
|
||||
<h3 className="font-semibold text-slate-700">Quick Actions</h3>
|
||||
<Button className="w-full justify-between bg-slate-900 text-white hover:bg-slate-800">
|
||||
Auto-Optimize <PlayCircle className="w-4 h-4" />
|
||||
</Button>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Projection Chart */}
|
||||
<GlassCard className="p-6">
|
||||
<h3 className="font-semibold text-slate-700 mb-6">Yearly Projection: Current vs Optimized</h3>
|
||||
<div className="h-64 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={savingsData.yearlyProjection}>
|
||||
<defs>
|
||||
<linearGradient id="colorOptimized" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#22c55e" stopOpacity={0.1} />
|
||||
<stop offset="95%" stopColor="#22c55e" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis dataKey="month" axisLine={false} tickLine={false} tick={{ fill: '#94a3b8', fontSize: 12 }} />
|
||||
<YAxis hide />
|
||||
<Tooltip
|
||||
contentStyle={{ backgroundColor: 'white', borderRadius: '12px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="current"
|
||||
stroke="#94a3b8"
|
||||
strokeWidth={2}
|
||||
fill="transparent"
|
||||
strokeDasharray="5 5"
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="optimized"
|
||||
stroke="#22c55e"
|
||||
strokeWidth={3}
|
||||
fill="url(#colorOptimized)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</GlassCard>
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-light tracking-tight text-slate-900 flex items-center gap-3">
|
||||
<Zap className="w-6 h-6 text-yellow-500 fill-yellow-500" />
|
||||
<span>Savings <span className="font-semibold bg-clip-text text-transparent bg-gradient-to-r from-indigo-600 to-purple-600">Booster</span></span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1 ml-9">Accelerate your wealth with smart goals.</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" className="border-slate-200 text-slate-700 bg-white hover:bg-slate-50">
|
||||
<Calculator className="w-4 h-4 mr-2" /> Calculator
|
||||
</Button>
|
||||
<Button className="bg-black text-white hover:bg-slate-800 shadow-md">
|
||||
<Plus className="w-4 h-4 mr-2" /> New Goal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* AI Opportunities Sidebar */}
|
||||
<GlassCard className="col-span-1 p-6 h-full">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="font-bold text-lg text-slate-800">AI Opportunities</h3>
|
||||
<Badge variant="outline" className="bg-blue-50 text-blue-600 border-blue-100">3 New</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{savingsData.opportunities.map((opp, index) => (
|
||||
<motion.div
|
||||
key={opp.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="group relative p-4 rounded-2xl bg-white/50 border border-white/60 hover:bg-white/80 transition-all hover:shadow-sm cursor-pointer"
|
||||
>
|
||||
<div className="flex gap-3 mb-3">
|
||||
<div className={cn("p-2 rounded-xl h-fit", opp.color)}>
|
||||
<opp.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-sm text-slate-900 leading-tight mb-1">{opp.title}</h4>
|
||||
<span className="text-xs text-slate-500 font-medium">{opp.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-2 pl-1">
|
||||
<span className="text-green-600 font-bold text-sm bg-green-50 px-2 py-1 rounded-md">
|
||||
{opp.impact}
|
||||
</span>
|
||||
<Button size="sm" variant="ghost" className="text-blue-600 hover:text-blue-700 hover:bg-blue-50 p-0 h-auto font-medium flex gap-1">
|
||||
{opp.action} <ArrowRight className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-4 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600 text-white text-center">
|
||||
<p className="text-sm font-medium opacity-90 mb-3">Unlock Premium Insights to save an extra AED 450/mo</p>
|
||||
<Button size="sm" className="bg-white text-indigo-600 hover:bg-white/90 w-full rounded-xl">
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<SavingsSummaryCards />
|
||||
|
||||
{/* Main Content Tabs */}
|
||||
<Tabs defaultValue="overview" className="space-y-6 w-full">
|
||||
<TabsList className="bg-white/60 p-1.5 rounded-2xl backdrop-blur-md border border-white/40 inline-flex w-auto flex-wrap lg:flex-nowrap shadow-sm h-auto">
|
||||
<TabsTrigger value="overview" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="goals" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Goals</TabsTrigger>
|
||||
<TabsTrigger value="accounts" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Accounts</TabsTrigger>
|
||||
<TabsTrigger value="calculator" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Calculator</TabsTrigger>
|
||||
<TabsTrigger value="add-goal" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Add Goal</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Tab 1: Overview */}
|
||||
<TabsContent value="overview" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[450px]">
|
||||
<SavingsGoalsList />
|
||||
<SavingsAccountsList />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 2: Goals */}
|
||||
<TabsContent value="goals" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<SavingsGoalsGrid />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 3: Accounts */}
|
||||
<TabsContent value="accounts" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<SavingsAccountsTable />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 4: Calculator */}
|
||||
<TabsContent value="calculator" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<CompoundInterestCalculator />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 5: Add Goal */}
|
||||
<TabsContent value="add-goal" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<AddGoalForm />
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
67
src/components/portfolio/AssetAllocation.tsx
Normal file
67
src/components/portfolio/AssetAllocation.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
interface AssetAllocationProps {
|
||||
holdings: Array<{
|
||||
name: string;
|
||||
symbol: string;
|
||||
currentValue: number;
|
||||
sector: string; // Keep for color logic if needed
|
||||
shares?: number;
|
||||
units?: number;
|
||||
}>;
|
||||
totalValue: number;
|
||||
}
|
||||
|
||||
const AssetAllocation: React.FC<AssetAllocationProps> = ({ holdings, totalValue }) => {
|
||||
// Sort holdings by value descending
|
||||
const sortedHoldings = [...holdings].sort((a, b) => b.currentValue - a.currentValue);
|
||||
|
||||
// Helper to get color based on index
|
||||
const getGradient = (index: number) => {
|
||||
const gradients = [
|
||||
'from-blue-500 to-blue-600',
|
||||
'from-green-500 to-green-600',
|
||||
'from-purple-500 to-purple-600',
|
||||
'from-orange-500 to-orange-600',
|
||||
'from-pink-500 to-pink-600',
|
||||
'from-teal-500 to-teal-600',
|
||||
];
|
||||
return gradients[index % gradients.length];
|
||||
};
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Asset Allocation</h3>
|
||||
<div className="space-y-6">
|
||||
{sortedHoldings.map((stock, index) => {
|
||||
const percentage = ((stock.currentValue / totalValue) * 100).toFixed(1);
|
||||
return (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-bold text-slate-700 w-12">{stock.symbol}</span>
|
||||
<span className="text-slate-500 truncate max-w-[120px]">{stock.name}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="font-bold text-slate-900">{percentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
className={`h-full bg-gradient-to-r ${getGradient(index)} transition-all duration-1000`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssetAllocation;
|
||||
90
src/components/portfolio/HoldingsTable.tsx
Normal file
90
src/components/portfolio/HoldingsTable.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { ArrowUpRight, ArrowDownRight, Plus, Minus } from 'lucide-react';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
interface HoldingsTableProps {
|
||||
holdings: Array<{
|
||||
name: string;
|
||||
symbol: string;
|
||||
shares?: number;
|
||||
units?: number;
|
||||
currentPrice: number;
|
||||
currentValue: number;
|
||||
gain: number;
|
||||
gainPercent: number;
|
||||
sector: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const HoldingsTable: React.FC<HoldingsTableProps> = ({ holdings }) => {
|
||||
return (
|
||||
<GlassCard className="overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-slate-50/50">
|
||||
<TableRow>
|
||||
<TableHead className="w-[200px]">Asset Name</TableHead>
|
||||
<TableHead className="text-right">Price</TableHead>
|
||||
<TableHead className="text-right">Shares</TableHead>
|
||||
<TableHead className="text-right">Value</TableHead>
|
||||
<TableHead className="text-right">Change</TableHead>
|
||||
<TableHead className="text-center w-[100px]">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{holdings.map((holding) => (
|
||||
<TableRow key={holding.symbol} className="hover:bg-slate-50/50 transition-colors">
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-slate-900 font-bold">{holding.symbol}</span>
|
||||
<span className="text-slate-500 text-xs">{holding.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1 font-medium">
|
||||
<DirhamIcon className="w-3 h-3 text-slate-400" /> {formatAmount(holding.currentPrice)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-slate-600">{holding.shares || holding.units}</TableCell>
|
||||
<TableCell className="text-right font-bold text-slate-800">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<DirhamIcon className="w-3 h-3 text-slate-400" /> {formatAmount(holding.currentValue)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className={`flex items-center justify-end gap-1 font-medium ${holding.gain >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{holding.gain >= 0 ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
|
||||
{Math.abs(holding.gainPercent)}%
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button size="icon" variant="outline" className="h-7 w-7 rounded-lg hover:bg-slate-100 text-slate-600">
|
||||
<Minus className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button size="icon" variant="outline" className="h-7 w-7 rounded-lg hover:bg-slate-100 text-slate-600">
|
||||
<Plus className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default HoldingsTable;
|
||||
57
src/components/portfolio/PortfolioAIInsights.tsx
Normal file
57
src/components/portfolio/PortfolioAIInsights.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
interface Recommendation {
|
||||
type: string;
|
||||
title: string;
|
||||
description: string;
|
||||
priority: string;
|
||||
action: React.ReactNode;
|
||||
}
|
||||
|
||||
interface PortfolioAIInsightsProps {
|
||||
recommendations: Recommendation[];
|
||||
}
|
||||
|
||||
const PortfolioAIInsights: React.FC<PortfolioAIInsightsProps> = ({ recommendations }) => {
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high': return 'text-red-700 bg-red-50 border-red-200';
|
||||
case 'medium': return 'text-yellow-700 bg-yellow-50 border-yellow-200';
|
||||
case 'low': return 'text-green-700 bg-green-50 border-green-200';
|
||||
default: return 'text-slate-700 bg-slate-50 border-slate-200';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 h-full">
|
||||
{recommendations.map((rec, index) => (
|
||||
<GlassCard key={index} className={`border-l-4 ${getPriorityColor(rec.priority)} p-5 hover:scale-[1.01] transition-transform`}>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-slate-800">{rec.title}</h3>
|
||||
<p className="text-sm text-slate-600 mt-1">{rec.description}</p>
|
||||
</div>
|
||||
<Badge variant="outline" className={`${getPriorityColor(rec.priority)} bg-white/50 backdrop-blur-sm shadow-sm`}>
|
||||
{rec.priority.charAt(0).toUpperCase() + rec.priority.slice(1)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between pt-3 border-t border-slate-100/50 mt-2">
|
||||
<div className="text-sm font-semibold text-slate-700">
|
||||
{rec.action}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="hover:bg-slate-100 text-xs h-8">
|
||||
Act Now
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PortfolioAIInsights;
|
||||
97
src/components/portfolio/PortfolioSummaryCards.tsx
Normal file
97
src/components/portfolio/PortfolioSummaryCards.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
TrendingUp,
|
||||
BarChart3,
|
||||
ArrowUpRight,
|
||||
Coins,
|
||||
Library
|
||||
} from 'lucide-react';
|
||||
|
||||
interface PortfolioSummaryCardsProps {
|
||||
overview: {
|
||||
totalValue: number;
|
||||
totalInvested: number;
|
||||
totalGains: number;
|
||||
gainPercentage: number;
|
||||
dayChange: number;
|
||||
dayChangePercent: number;
|
||||
};
|
||||
holdingsCount: number;
|
||||
}
|
||||
|
||||
const PortfolioSummaryCards: React.FC<PortfolioSummaryCardsProps> = ({ overview, holdingsCount }) => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{/* Total Value */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Value</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-green-400 to-green-600 flex items-center justify-center shadow-lg shadow-green-200">
|
||||
<BarChart3 className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(overview.totalValue)}</p>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<TrendingUp className="w-4 h-4 text-green-500 mr-1" />
|
||||
<span className="text-sm text-green-600 font-medium">+{overview.gainPercentage}%</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Today's Change */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Today's Change</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-400 to-cyan-600 flex items-center justify-center shadow-lg shadow-cyan-200">
|
||||
<Coins className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(overview.dayChange)}</p>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<ArrowUpRight className="w-4 h-4 text-green-500 mr-1" />
|
||||
<span className="text-sm text-green-600 font-medium">+{overview.dayChangePercent}%</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Holdings Count */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Holdings</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center shadow-lg shadow-purple-200">
|
||||
<Library className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{holdingsCount}</p>
|
||||
<p className="text-sm text-slate-500 mt-2">Active Positions</p>
|
||||
</GlassCard>
|
||||
|
||||
{/* Performance / Total Gains */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Gains</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg shadow-blue-200">
|
||||
<TrendingUp className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(overview.totalGains)}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs text-slate-400 mt-2">
|
||||
vs <DirhamIcon className="w-3 h-3" /> {formatAmount(overview.totalInvested)} invested
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PortfolioSummaryCards;
|
||||
56
src/components/portfolio/QuickInvestmentForm.tsx
Normal file
56
src/components/portfolio/QuickInvestmentForm.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
interface QuickInvestmentFormProps {
|
||||
holdings: Array<{ symbol: string; name: string }>;
|
||||
}
|
||||
|
||||
const QuickInvestmentForm: React.FC<QuickInvestmentFormProps> = ({ holdings }) => {
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col justify-center">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Quick Investment</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="holding" className="text-slate-600">Select Holding</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="holding" className="bg-white/50 border-slate-200">
|
||||
<SelectValue placeholder="Choose asset..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{holdings.map((h) => (
|
||||
<SelectItem key={h.symbol} value={h.symbol}>{h.symbol} - {h.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="amount" className="text-slate-600">Amount (AED)</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
||||
<DirhamIcon className="w-4 h-4 text-slate-400" />
|
||||
</div>
|
||||
<Input id="amount" placeholder="0.00" className="pl-9 bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<Button variant="outline" className="text-green-600 border-green-200 hover:bg-green-50 hover:text-green-700 w-full">
|
||||
Buy Asset
|
||||
</Button>
|
||||
<Button variant="outline" className="text-red-600 border-red-200 hover:bg-red-50 hover:text-red-700 w-full">
|
||||
Sell Asset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickInvestmentForm;
|
||||
34
src/components/portfolio/RiskAnalysis.tsx
Normal file
34
src/components/portfolio/RiskAnalysis.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Target, Info } from 'lucide-react';
|
||||
|
||||
const RiskAnalysis: React.FC = () => {
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Target className="w-5 h-5 text-purple-600" />
|
||||
<h3 className="text-lg font-semibold text-slate-800">Risk Analysis</h3>
|
||||
</div>
|
||||
<p className="text-slate-600 text-sm leading-relaxed mb-6">
|
||||
Your portfolio currently aligns with a balanced growth strategy. Consider increasing diversification in global markets to hedge against local volatility.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-xl border border-slate-100">
|
||||
<span className="text-sm font-medium text-slate-600">Current Score</span>
|
||||
<span className="text-2xl font-bold text-slate-900">7.2<span className="text-sm text-slate-400 font-normal">/10</span></span>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<span className="px-4 py-1.5 bg-yellow-100 text-yellow-700 rounded-full text-sm font-bold border border-yellow-200 shadow-sm">
|
||||
Moderate Risk
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default RiskAnalysis;
|
||||
72
src/components/savings/AddGoalForm.tsx
Normal file
72
src/components/savings/AddGoalForm.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Calendar } from 'lucide-react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { format } from 'date-fns';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Calendar as CalendarComponent } from '@/components/ui/calendar'; // Assuming shadcn Calendar exists
|
||||
|
||||
const AddGoalForm: React.FC = () => {
|
||||
const [date, setDate] = React.useState<Date>();
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[450px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
<h3 className="text-xl font-bold text-slate-800 mb-6 text-center">Create New Savings Goal</h3>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="goal-name" className="text-slate-600">Goal Name</Label>
|
||||
<Input id="goal-name" placeholder="e.g. Dream Vacation" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="target-amount" className="text-slate-600">Target Amount (AED)</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
||||
<DirhamIcon className="w-4 h-4 text-slate-400" />
|
||||
</div>
|
||||
<Input id="target-amount" placeholder="0.00" className="pl-9 bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-slate-600">Target Date</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full justify-start text-left font-normal bg-white/50 border-slate-200",
|
||||
!date && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
{/* Placeholder for standard Calendar since exact import path might vary */}
|
||||
<div className="p-4 bg-white border rounded shadow-md">
|
||||
<p className="text-sm text-slate-500 mb-2">Select Date</p>
|
||||
<Input type="date" className="w-full" onChange={(e) => setDate(new Date(e.target.value))} />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md">
|
||||
Create Savings Goal
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddGoalForm;
|
||||
94
src/components/savings/CompoundInterestCalculator.tsx
Normal file
94
src/components/savings/CompoundInterestCalculator.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { Calculator } from 'lucide-react';
|
||||
|
||||
const CompoundInterestCalculator: React.FC = () => {
|
||||
const [initialAmount, setInitialAmount] = useState<number>(10000);
|
||||
const [monthlyContribution, setMonthlyContribution] = useState<number>(2000);
|
||||
const [years, setYears] = useState<number>(10);
|
||||
const [rate, setRate] = useState<number>(5);
|
||||
|
||||
const calculateResult = () => {
|
||||
// Simplified compound interest calculation
|
||||
const r = rate / 100 / 12;
|
||||
const n = years * 12;
|
||||
const futureValue = initialAmount * Math.pow(1 + r, n) + (monthlyContribution * (Math.pow(1 + r, n) - 1)) / r;
|
||||
return futureValue;
|
||||
};
|
||||
|
||||
const finalAmount = calculateResult();
|
||||
const totalInvested = initialAmount + (monthlyContribution * years * 12);
|
||||
const totalInterest = finalAmount - totalInvested;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-full">
|
||||
{/* Input Form */}
|
||||
<GlassCard className="p-6">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6 flex items-center gap-2">
|
||||
<Calculator className="w-5 h-5 text-slate-500" /> Parameters
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-slate-600">Initial Amount (AED)</Label>
|
||||
<Input type="number" value={initialAmount} onChange={(e) => setInitialAmount(Number(e.target.value))} className="bg-white/50" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-slate-600">Monthly Contribution (AED)</Label>
|
||||
<Input type="number" value={monthlyContribution} onChange={(e) => setMonthlyContribution(Number(e.target.value))} className="bg-white/50" />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<Label className="text-slate-600">Time Period (Years)</Label>
|
||||
<span className="text-sm font-bold text-slate-800">{years} Years</span>
|
||||
</div>
|
||||
<Slider value={[years]} onValueChange={(v) => setYears(v[0])} max={50} step={1} className="py-2" />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<Label className="text-slate-600">Interest Rate (%)</Label>
|
||||
<span className="text-sm font-bold text-slate-800">{rate}%</span>
|
||||
</div>
|
||||
<Slider value={[rate]} onValueChange={(v) => setRate(v[0])} max={15} step={0.1} className="py-2" />
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Results */}
|
||||
<GlassCard className="p-8 flex flex-col justify-center bg-gradient-to-br from-white/60 to-indigo-50/50">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-8 text-center">Projected Savings</h3>
|
||||
|
||||
<div className="text-center mb-10">
|
||||
<p className="text-sm text-slate-500 mb-2">In {years} years, you will have</p>
|
||||
<div className="text-4xl md:text-5xl font-bold text-green-600 flex items-center justify-center gap-2">
|
||||
<DirhamIcon className="w-8 h-8 md:w-10 md:h-10" />
|
||||
{formatAmount(Math.round(finalAmount))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6 border-t border-slate-200 pt-8">
|
||||
<div className="text-center">
|
||||
<p className="text-xs text-slate-500 mb-1">Total Contributions</p>
|
||||
<p className="text-lg font-bold text-slate-800 flex items-center justify-center gap-1">
|
||||
<DirhamIcon className="w-4 h-4 text-slate-400" /> {formatAmount(Math.round(totalInvested))}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-xs text-slate-500 mb-1">Total Interest Earned</p>
|
||||
<p className="text-lg font-bold text-indigo-600 flex items-center justify-center gap-1">
|
||||
<DirhamIcon className="w-4 h-4 text-indigo-400" /> {formatAmount(Math.round(totalInterest))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompoundInterestCalculator;
|
||||
50
src/components/savings/SavingsAccountsList.tsx
Normal file
50
src/components/savings/SavingsAccountsList.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Building2, ArrowUpRight } from 'lucide-react';
|
||||
|
||||
const SavingsAccountsList: React.FC = () => {
|
||||
|
||||
const accounts = [
|
||||
{ name: 'ADCB Active Saver', balance: 145000, growth: 423, apy: 3.5, type: 'High Yield' },
|
||||
{ name: 'Emirates NBD Savings', balance: 85000, growth: 120, apy: 1.7, type: 'Standard' },
|
||||
{ name: 'Wio Personal', balance: 25000, growth: 104, apy: 5.0, type: 'High Yield' },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Savings Accounts</h3>
|
||||
<div className="space-y-4 flex-1">
|
||||
{accounts.map((acc, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 rounded-xl hover:bg-slate-50/80 transition-colors border border-transparent hover:border-slate-100 group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center text-slate-600 group-hover:bg-indigo-50 group-hover:text-indigo-600 transition-colors">
|
||||
<Building2 className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-slate-900">{acc.name}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-slate-500">{acc.type}</span>
|
||||
<span className="text-xs text-slate-300">•</span>
|
||||
<span className="text-xs text-green-600 font-medium">{acc.apy}% APY</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-bold text-slate-900 flex items-center justify-end gap-1">
|
||||
<DirhamIcon className="w-3.5 h-3.5" /> {formatAmount(acc.balance)}
|
||||
</div>
|
||||
<div className="text-xs text-green-600 font-medium flex items-center justify-end gap-0.5 mt-0.5">
|
||||
+<DirhamIcon className="w-2.5 h-2.5" /> {acc.growth}/mo
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default SavingsAccountsList;
|
||||
73
src/components/savings/SavingsAccountsTable.tsx
Normal file
73
src/components/savings/SavingsAccountsTable.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Settings } from 'lucide-react';
|
||||
|
||||
const SavingsAccountsTable: React.FC = () => {
|
||||
|
||||
// Mock Data
|
||||
const accounts = [
|
||||
{ name: 'ADCB Active Saver', type: 'High Yield', balance: 145000, rate: 3.5, growth: 423 },
|
||||
{ name: 'Emirates NBD Savings', type: 'Standard', balance: 85000, rate: 1.7, growth: 120 },
|
||||
{ name: 'Wio Personal', type: 'High Yield', balance: 25000, rate: 5.0, growth: 104 },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader className="bg-slate-50/50">
|
||||
<TableRow>
|
||||
<TableHead className="w-[200px]">Account Name</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead className="text-right">Balance (AED)</TableHead>
|
||||
<TableHead className="text-right">Interest Rate</TableHead>
|
||||
<TableHead className="text-right">Monthly Growth</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{accounts.map((acc, idx) => (
|
||||
<TableRow key={idx} className="hover:bg-slate-50/50">
|
||||
<TableCell className="font-semibold text-slate-800">{acc.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className={acc.type === 'High Yield' ? 'bg-indigo-50 text-indigo-600 border-indigo-200' : 'bg-slate-50 text-slate-600 border-slate-200'}>
|
||||
{acc.type}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-bold text-slate-900">
|
||||
<span className="flex items-center justify-end gap-1">
|
||||
<DirhamIcon className="w-3 h-3" /> {formatAmount(acc.balance)}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-medium text-slate-600">{acc.rate}%</TableCell>
|
||||
<TableCell className="text-right font-bold text-green-600">
|
||||
<span className="flex items-center justify-end gap-1">
|
||||
+<DirhamIcon className="w-3 h-3" /> {acc.growth}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<Settings className="w-4 h-4 text-slate-400" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default SavingsAccountsTable;
|
||||
69
src/components/savings/SavingsGoalsGrid.tsx
Normal file
69
src/components/savings/SavingsGoalsGrid.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Clock, Plus } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const SavingsGoalsGrid: React.FC = () => {
|
||||
|
||||
const goals = [
|
||||
{ name: 'Emergency Fund', saved: 25000, target: 50000, daysLeft: 120, color: 'bg-yellow-500' },
|
||||
{ name: 'Vacation', saved: 5000, target: 15000, daysLeft: 65, color: 'bg-blue-500' },
|
||||
{ name: 'New Car', saved: 85000, target: 120000, daysLeft: 411, color: 'bg-green-500' },
|
||||
{ name: 'Wedding', saved: 10000, target: 80000, daysLeft: 500, color: 'bg-pink-500' },
|
||||
{ name: 'Home Downpayment', saved: 125000, target: 200000, daysLeft: 730, color: 'bg-indigo-500' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{goals.map((goal, index) => {
|
||||
const percentage = Math.round((goal.saved / goal.target) * 100);
|
||||
|
||||
return (
|
||||
<GlassCard key={index} className="p-6 flex flex-col justify-between h-full" hoverEffect>
|
||||
<div>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-800">{goal.name}</h3>
|
||||
<div className="flex items-center gap-1 text-slate-500 text-xs mt-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
<span>{goal.daysLeft} days remaining</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-1 rounded-full bg-slate-100 text-slate-600 text-xs font-bold border border-slate-200">
|
||||
{percentage}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 mb-6">
|
||||
<Progress value={percentage} className="h-2.5" indicatorClassName={goal.color} />
|
||||
<div className="flex justify-between text-xs text-slate-500">
|
||||
<span className="font-semibold text-slate-900 flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(goal.saved)}</span>
|
||||
<span className="flex items-center gap-0.5"><DirhamIcon className="w-3 h-3" /> {formatAmount(goal.target)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button variant="outline" size="sm" className="h-8 text-xs border-slate-200 hover:bg-slate-50 hover:text-slate-900">
|
||||
+1K
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="h-8 text-xs border-slate-200 hover:bg-slate-50 hover:text-slate-900">
|
||||
+5K
|
||||
</Button>
|
||||
<Button size="sm" className="h-8 text-xs bg-black text-white hover:bg-slate-800">
|
||||
Custom
|
||||
</Button>
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SavingsGoalsGrid;
|
||||
58
src/components/savings/SavingsGoalsList.tsx
Normal file
58
src/components/savings/SavingsGoalsList.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const SavingsGoalsList: React.FC = () => {
|
||||
|
||||
const goals = [
|
||||
{ name: 'Emergency Fund', saved: 25000, target: 50000, status: 'Good Progress', color: 'bg-yellow-500', badgeColor: 'bg-yellow-100 text-yellow-700 border-yellow-200' },
|
||||
{ name: 'Vacation', saved: 5000, target: 15000, status: 'Good Progress', color: 'bg-blue-500', badgeColor: 'bg-blue-100 text-blue-700 border-blue-200' },
|
||||
{ name: 'New Car', saved: 85000, target: 120000, status: 'Good Progress', color: 'bg-green-500', badgeColor: 'bg-green-100 text-green-700 border-green-200' },
|
||||
{ name: 'Wedding', saved: 10000, target: 80000, status: 'Getting Started', color: 'bg-slate-500', badgeColor: 'bg-slate-100 text-slate-600 border-slate-200' },
|
||||
];
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Savings Goals</h3>
|
||||
<div className="space-y-6">
|
||||
{goals.map((goal, index) => {
|
||||
const percentage = Math.min((goal.saved / goal.target) * 100, 100);
|
||||
return (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="font-medium text-slate-700">{goal.name}</span>
|
||||
<Badge variant="outline" className={`${goal.badgeColor} text-[10px] px-2 py-0 border`}>
|
||||
{goal.status}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs text-slate-500 mb-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-semibold text-slate-900"><DirhamIcon className="w-3 h-3 inline" /> {formatAmount(goal.saved)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
of <DirhamIcon className="w-3 h-3 inline" /> {formatAmount(goal.target)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${percentage}%` }}
|
||||
transition={{ duration: 1, delay: index * 0.1 }}
|
||||
className={`h-full ${goal.color} transition-all duration-1000`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default SavingsGoalsList;
|
||||
86
src/components/savings/SavingsSummaryCards.tsx
Normal file
86
src/components/savings/SavingsSummaryCards.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
Wallet,
|
||||
TrendingUp,
|
||||
Target,
|
||||
Percent
|
||||
} from 'lucide-react';
|
||||
|
||||
const SavingsSummaryCards: React.FC = () => {
|
||||
// Hardcoded design values
|
||||
const data = {
|
||||
totalSavings: 255000,
|
||||
monthlyGrowth: 4500,
|
||||
goalsProgress: 68,
|
||||
annualYield: 3.5
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{/* Total Savings */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Total Savings</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-400 to-indigo-600 flex items-center justify-center shadow-lg shadow-indigo-200">
|
||||
<Wallet className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(data.totalSavings)}</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-2">Across all accounts</p>
|
||||
</GlassCard>
|
||||
|
||||
{/* Monthly Growth */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Monthly Growth</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-green-400 to-green-600 flex items-center justify-center shadow-lg shadow-green-200">
|
||||
<TrendingUp className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<DirhamIcon className="w-6 h-6 text-slate-900" />
|
||||
<p className="text-2xl font-bold text-slate-900">{formatAmount(data.monthlyGrowth)}</p>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<TrendingUp className="w-3 h-3 text-green-500 mr-1" />
|
||||
<span className="text-xs text-green-600 font-medium">+1.8% vs last month</span>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Goals Progress */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Goals Progress</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg shadow-blue-200">
|
||||
<Target className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{data.goalsProgress}%</p>
|
||||
<div className="w-full bg-slate-100 rounded-full h-1.5 mt-3 overflow-hidden">
|
||||
<div className="h-full bg-blue-500" style={{ width: `${data.goalsProgress}%` }}></div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{/* Annual Yield */}
|
||||
<GlassCard className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<p className="text-sm font-medium text-slate-500">Annual Yield (APY)</p>
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center shadow-lg shadow-purple-200">
|
||||
<Percent className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-slate-900">{data.annualYield}%</p>
|
||||
<p className="text-xs text-slate-400 mt-2">Average across liquid assets</p>
|
||||
</GlassCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SavingsSummaryCards;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
|
||||
interface GlassCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
@@ -13,11 +14,23 @@ export const GlassCard = ({
|
||||
hoverEffect = false,
|
||||
...props
|
||||
}: GlassCardProps) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const getGlassStyle = () => {
|
||||
if (theme === 'default') {
|
||||
return "bg-white/40 border-white/40 shadow-sm backdrop-blur-xl";
|
||||
}
|
||||
// "Super Glass" for image backgrounds
|
||||
// Increased transparency, higher blur, and adjusting border for contrast
|
||||
return "bg-white/10 border-white/20 shadow-xl backdrop-blur-2xl text-current";
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"glass-card bg-white/40 border border-white/40 shadow-sm backdrop-blur-xl",
|
||||
hoverEffect && "hover:bg-white/50 transition-all duration-300 hover:scale-[1.02]",
|
||||
"glass-card border",
|
||||
getGlassStyle(),
|
||||
hoverEffect && "hover:bg-white/20 transition-all duration-300 hover:scale-[1.02]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
17
src/components/ui/custom-icons.tsx
Normal file
17
src/components/ui/custom-icons.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface IconProps extends React.SVGProps<SVGSVGElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const DirhamIcon = ({ className, ...props }: IconProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 344.84 299.91"
|
||||
className={cn("w-4 h-4 fill-current", className)}
|
||||
{...props}
|
||||
>
|
||||
<path d="M342.14,140.96l2.7,2.54v-7.72c0-17-11.92-30.84-26.56-30.84h-23.41C278.49,36.7,222.69,0,139.68,0c-52.86,0-59.65,0-109.71,0,0,0,15.03,12.63,15.03,52.4v52.58h-27.68c-5.38,0-10.43-2.08-14.61-6.01l-2.7-2.54v7.72c0,17.01,11.92,30.84,26.56,30.84h18.44s0,29.99,0,29.99h-27.68c-5.38,0-10.43-2.07-14.61-6.01l-2.7-2.54v7.71c0,17,11.92,30.82,26.56,30.82h18.44s0,54.89,0,54.89c0,38.65-15.03,50.06-15.03,50.06h109.71c85.62,0,139.64-36.96,155.38-104.98h32.46c5.38,0,10.43,2.07,14.61,6l2.7,2.54v-7.71c0-17-11.92-30.83-26.56-30.83h-18.9c.32-4.88.49-9.87.49-15s-.18-10.11-.51-14.99h28.17c5.37,0,10.43,2.07,14.61,6.01ZM89.96,15.01h45.86c61.7,0,97.44,27.33,108.1,89.94l-153.96.02V15.01ZM136.21,284.93h-46.26v-89.98l153.87-.02c-9.97,56.66-42.07,88.38-107.61,90ZM247.34,149.96c0,5.13-.11,10.13-.34,14.99l-157.04.02v-29.99l157.05-.02c.22,4.84.33,9.83.33,15Z" />
|
||||
</svg>
|
||||
);
|
||||
74
src/context/ThemeContext.tsx
Normal file
74
src/context/ThemeContext.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type Theme = 'default' | 'dubai' | 'minimal' | 'nature';
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const savedTheme = localStorage.getItem('app-theme');
|
||||
return (savedTheme as Theme) || 'default';
|
||||
}
|
||||
return 'default';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('app-theme', theme);
|
||||
const root = window.document.documentElement;
|
||||
|
||||
// Remove old theme classes
|
||||
root.classList.remove('theme-default', 'theme-dubai', 'theme-minimal', 'theme-nature');
|
||||
|
||||
// Add new theme class
|
||||
root.classList.add(`theme-${theme}`);
|
||||
|
||||
// Check if we need to force dark mode for specific themes
|
||||
if (theme === 'dubai') {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
|
||||
}, [theme]);
|
||||
|
||||
// Background Image Map
|
||||
const getBackgroundImage = () => {
|
||||
switch (theme) {
|
||||
case 'dubai': return "url('https://images.unsplash.com/photo-1512453979798-5ea9ba6a80f4?q=80&w=2000&auto=format&fit=crop')";
|
||||
case 'minimal': return "url('https://images.unsplash.com/photo-1550684848-fac1c5b4e853?q=80&w=2000&auto=format&fit=crop')";
|
||||
case 'nature': return "url('https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=2000&auto=format&fit=crop')";
|
||||
default: return 'none'; // Gradient handles default
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
{/* Background Layer managed here for global coverage */}
|
||||
{theme !== 'default' && (
|
||||
<div
|
||||
className="fixed inset-0 z-[-1] transition-all duration-700 ease-in-out bg-cover bg-center bg-no-repeat bg-fixed"
|
||||
style={{ backgroundImage: getBackgroundImage() }}
|
||||
/>
|
||||
)}
|
||||
{/* Overlay for contrast if needed */}
|
||||
{theme === 'dubai' && <div className="fixed inset-0 z-[-1] bg-black/40 pointer-events-none" />}
|
||||
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -5,10 +5,9 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export const formatCurrency = (amount: number) => {
|
||||
export const formatAmount = (amount: number) => {
|
||||
return new Intl.NumberFormat('en-AE', {
|
||||
style: 'currency',
|
||||
currency: 'AED',
|
||||
style: 'decimal',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
|
||||
Reference in New Issue
Block a user