@@ -345,7 +354,9 @@ const GoalPlanning: React.FC = () => {
Monthly SIP
-
{formatCurrency(goal.monthlyContribution)}
+
+ {formatAmount(goal.monthlyContribution)}
+
Time Remaining
@@ -406,15 +417,21 @@ const GoalPlanning: React.FC = () => {
Current
-
{formatCurrency(goal.currentAmount)}
+
+ {formatAmount(goal.currentAmount)}
+
Monthly SIP
-
{formatCurrency(goal.monthlyContribution)}
+
+ {formatAmount(goal.monthlyContribution)}
+
Remaining
-
{formatCurrency(goal.targetAmount - goal.currentAmount)}
+
+ {formatAmount(goal.targetAmount - goal.currentAmount)}
+
@@ -511,30 +528,26 @@ const GoalPlanning: React.FC = () => {
{goal.milestones.map((milestone, index) => (
-
+
{milestone.completed ? (
-
+
) : (
-
+
)}
-
-
{formatCurrency(milestone.amount)}
-
- Target: {new Date(milestone.date).toLocaleDateString()}
-
+
+
+ {formatAmount(milestone.amount)}
+
+
+ {milestone.completed ? 'Completed' : 'Pending'}
+
-
- {milestone.completed ? (
-
- Completed
-
- ) : (
-
- Pending
-
- )}
-
))}
@@ -545,7 +558,7 @@ const GoalPlanning: React.FC = () => {
-
+
);
};
diff --git a/src/components/features/PortfolioManager.tsx b/src/components/features/PortfolioManager.tsx
index bde6322..866b94b 100644
--- a/src/components/features/PortfolioManager.tsx
+++ b/src/components/features/PortfolioManager.tsx
@@ -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: (
+
+ Sell {formatAmount(5000)} worth of real estate stocks
+
+ )
},
{
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: (
+
+ Consider investing {formatAmount(10000)}
+
+ )
},
{
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 (
-
-
-
- Portfolio Manager
-
-
- AI-powered insights for your investments.
-
+
+
+ {/* Header Section */}
+
+
+
+ Portfolio Manager
+
+
Smart insights for your investments.
+
+
+
+ Auto Rebalance
+
+
+ Add Investment
+
+
- {/* Portfolio Overview */}
-
-
-
- {formatCurrency(portfolioOverview.totalValue)}
-
-
- +{portfolioOverview.gainPercentage}%
-
-
+ {/* Summary Cards */}
+
-
-
- {formatCurrency(portfolioOverview.totalGains)}
- vs {formatCurrency(portfolioOverview.totalInvested)} invested
-
-
-
-
- {formatCurrency(portfolioOverview.dayChange)}
-
-
-
+{portfolioOverview.dayChangePercent}%
-
-
-
-
-
- 7.2
- Moderate
-
-
-
-
-
- Holdings
- Allocation
- AI Insights
+ {/* Main Content Tabs */}
+
+
+ Overview
+ Holdings
+ Analytics
-
-
- {holdings.map((holding, index) => (
-
-
-
-
- {holding.symbol.substring(0, 4)}
-
-
{holding.sector}
-
-
{holding.name}
-
{holding.shares} shares @ {formatCurrency(holding.currentPrice)}
-
-
-
-
-
-
Current Value
-
{formatCurrency(holding.currentValue)}
-
-
0 ? 'text-green-600' : 'text-red-600'}`}>
-
- {holding.gain > 0 ? : }
- {holding.gainPercent}%
-
-
- {holding.gain > 0 ? '+' : ''}{formatCurrency(holding.gain)}
-
-
-
-
-
- ))}
+ {/* Tab 1: Overview */}
+
+
-
-
-
- {sectorAllocation.map((sector, index) => (
-
-
-
-
-
{sector.percentage}%
-
{formatCurrency(sector.value)}
-
-
-
-
- ))}
+ {/* Tab 2: Holdings */}
+
+
+
+
+ {/* Tab 3: Analytics */}
+
+
+
+
+
+
-
-
-
-
-
- {recommendations.map((rec, index) => (
-
-
-
-
{rec.title}
-
{rec.description}
-
-
- {rec.priority.charAt(0).toUpperCase() + rec.priority.slice(1)}
-
-
-
-
{rec.action}
-
- Take Action
-
-
-
- ))}
+
);
diff --git a/src/components/features/Profile.tsx b/src/components/features/Profile.tsx
index 5d71acb..cff4aeb 100644
--- a/src/components/features/Profile.tsx
+++ b/src/components/features/Profile.tsx
@@ -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 = () => {
+ {/* Appearance & Themes */}
+
+
+
+
+ {['default', 'dubai', 'minimal', 'nature'].map((themeName) => (
+
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' &&
}
+ {themeName === 'dubai' && }
+ {themeName === 'minimal' && }
+ {themeName === 'nature' && }
+
+
+ {themeName}
+
+ {theme === themeName && (
+
+
+
+ )}
+
+ ))}
+
+
+
{/* Data Privacy (UAE PDPL) */}
diff --git a/src/components/features/SavingsBooster.tsx b/src/components/features/SavingsBooster.tsx
index 27b8d53..b9949ac 100644
--- a/src/components/features/SavingsBooster.tsx
+++ b/src/components/features/SavingsBooster.tsx
@@ -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 (
-
-
-
-
- )
-}
-
-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 (
-
- {/* Background Arc */}
-
-
- {/* Value Arc (Simulated with rotation) */}
-
-
-
- {value}
- Score
-
-
- );
-};
+// 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 (
-
-
-
- Personal Savings Booster
-
-
AI-driven optimization to maximize your wealth.
-
+
-
- {/* Hero Comparison */}
-
-
-
- Optimization Score
-
-
-
-
- Monthly Potential
-
- {formatCurrency(savingsData.potential)}
- /mo
-
-
-
- + {formatCurrency(savingsData.potential - savingsData.current)} vs current
-
-
-
-
-
-
-
- Quick Actions
-
- Auto-Optimize
-
-
-
-
- {/* Projection Chart */}
-
- Yearly Projection: Current vs Optimized
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {/* Header Section */}
+
+
+
+
+ Savings Booster
+
+
Accelerate your wealth with smart goals.
+
+
+
+ Calculator
+
+
+ New Goal
+
-
- {/* AI Opportunities Sidebar */}
-
-
-
AI Opportunities
- 3 New
-
-
-
- {savingsData.opportunities.map((opp, index) => (
-
-
-
-
-
-
-
{opp.title}
- {opp.type}
-
-
-
-
-
- {opp.impact}
-
-
- {opp.action}
-
-
-
- ))}
-
-
-
-
Unlock Premium Insights to save an extra AED 450/mo
-
- Upgrade to Pro
-
-
-
+
+ {/* Summary Cards */}
+
+
+ {/* Main Content Tabs */}
+
+
+ Overview
+ Goals
+ Accounts
+ Calculator
+ Add Goal
+
+
+ {/* Tab 1: Overview */}
+
+
+
+
+
+
+
+ {/* Tab 2: Goals */}
+
+
+
+
+ {/* Tab 3: Accounts */}
+
+
+
+
+ {/* Tab 4: Calculator */}
+
+
+
+
+ {/* Tab 5: Add Goal */}
+
+
+
+
+
);
}
\ No newline at end of file
diff --git a/src/components/portfolio/AssetAllocation.tsx b/src/components/portfolio/AssetAllocation.tsx
new file mode 100644
index 0000000..3fa5d3f
--- /dev/null
+++ b/src/components/portfolio/AssetAllocation.tsx
@@ -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
= ({ 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 (
+
+ Asset Allocation
+
+ {sortedHoldings.map((stock, index) => {
+ const percentage = ((stock.currentValue / totalValue) * 100).toFixed(1);
+ return (
+
+
+
+ {stock.symbol}
+ {stock.name}
+
+
+ {percentage}%
+
+
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default AssetAllocation;
diff --git a/src/components/portfolio/HoldingsTable.tsx b/src/components/portfolio/HoldingsTable.tsx
new file mode 100644
index 0000000..44afcef
--- /dev/null
+++ b/src/components/portfolio/HoldingsTable.tsx
@@ -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 = ({ holdings }) => {
+ return (
+
+
+
+
+ Asset Name
+ Price
+ Shares
+ Value
+ Change
+ Actions
+
+
+
+ {holdings.map((holding) => (
+
+
+
+ {holding.symbol}
+ {holding.name}
+
+
+
+
+ {formatAmount(holding.currentPrice)}
+
+
+ {holding.shares || holding.units}
+
+
+ {formatAmount(holding.currentValue)}
+
+
+
+ = 0 ? 'text-green-600' : 'text-red-600'}`}>
+ {holding.gain >= 0 ?
:
}
+ {Math.abs(holding.gainPercent)}%
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default HoldingsTable;
diff --git a/src/components/portfolio/PortfolioAIInsights.tsx b/src/components/portfolio/PortfolioAIInsights.tsx
new file mode 100644
index 0000000..9353dbf
--- /dev/null
+++ b/src/components/portfolio/PortfolioAIInsights.tsx
@@ -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 = ({ 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 (
+
+ {recommendations.map((rec, index) => (
+
+
+
+
{rec.title}
+
{rec.description}
+
+
+ {rec.priority.charAt(0).toUpperCase() + rec.priority.slice(1)}
+
+
+
+
+ {rec.action}
+
+
+ Act Now
+
+
+
+ ))}
+
+ );
+};
+
+export default PortfolioAIInsights;
diff --git a/src/components/portfolio/PortfolioSummaryCards.tsx b/src/components/portfolio/PortfolioSummaryCards.tsx
new file mode 100644
index 0000000..b4cf946
--- /dev/null
+++ b/src/components/portfolio/PortfolioSummaryCards.tsx
@@ -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 = ({ overview, holdingsCount }) => {
+ return (
+
+ {/* Total Value */}
+
+
+
+
+
{formatAmount(overview.totalValue)}
+
+
+
+ +{overview.gainPercentage}%
+
+
+
+ {/* Today's Change */}
+
+
+
+
+
{formatAmount(overview.dayChange)}
+
+
+
+
+{overview.dayChangePercent}%
+
+
+
+ {/* Holdings Count */}
+
+
+ {holdingsCount}
+ Active Positions
+
+
+ {/* Performance / Total Gains */}
+
+
+
+
+
{formatAmount(overview.totalGains)}
+
+
+ vs {formatAmount(overview.totalInvested)} invested
+
+
+
+ );
+};
+
+export default PortfolioSummaryCards;
diff --git a/src/components/portfolio/QuickInvestmentForm.tsx b/src/components/portfolio/QuickInvestmentForm.tsx
new file mode 100644
index 0000000..fef3053
--- /dev/null
+++ b/src/components/portfolio/QuickInvestmentForm.tsx
@@ -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 = ({ holdings }) => {
+ return (
+
+ Quick Investment
+
+
+ Select Holding
+
+
+
+
+
+ {holdings.map((h) => (
+ {h.symbol} - {h.name}
+ ))}
+
+
+
+
+
+
+
+
+ Buy Asset
+
+
+ Sell Asset
+
+
+
+
+ );
+};
+
+export default QuickInvestmentForm;
diff --git a/src/components/portfolio/RiskAnalysis.tsx b/src/components/portfolio/RiskAnalysis.tsx
new file mode 100644
index 0000000..149d010
--- /dev/null
+++ b/src/components/portfolio/RiskAnalysis.tsx
@@ -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 (
+
+
+
+
+
Risk Analysis
+
+
+ Your portfolio currently aligns with a balanced growth strategy. Consider increasing diversification in global markets to hedge against local volatility.
+
+
+
+
+
+ Current Score
+ 7.2/10
+
+
+
+ Moderate Risk
+
+
+
+
+ );
+};
+
+export default RiskAnalysis;
diff --git a/src/components/savings/AddGoalForm.tsx b/src/components/savings/AddGoalForm.tsx
new file mode 100644
index 0000000..d7dbefa
--- /dev/null
+++ b/src/components/savings/AddGoalForm.tsx
@@ -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();
+
+ return (
+
+
+ Create New Savings Goal
+
+
+
+ Goal Name
+
+
+
+
+
Target Amount (AED)
+
+
+
+
+
Target Date
+
+
+
+
+ {date ? format(date, "PPP") : Pick a date }
+
+
+
+ {/* Placeholder for standard Calendar since exact import path might vary */}
+
+
Select Date
+
setDate(new Date(e.target.value))} />
+
+
+
+
+
+
+ Create Savings Goal
+
+
+
+
+ );
+};
+
+export default AddGoalForm;
diff --git a/src/components/savings/CompoundInterestCalculator.tsx b/src/components/savings/CompoundInterestCalculator.tsx
new file mode 100644
index 0000000..c7166b2
--- /dev/null
+++ b/src/components/savings/CompoundInterestCalculator.tsx
@@ -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(10000);
+ const [monthlyContribution, setMonthlyContribution] = useState(2000);
+ const [years, setYears] = useState(10);
+ const [rate, setRate] = useState(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 (
+
+ {/* Input Form */}
+
+
+ Parameters
+
+
+
+ Initial Amount (AED)
+ setInitialAmount(Number(e.target.value))} className="bg-white/50" />
+
+
+ Monthly Contribution (AED)
+ setMonthlyContribution(Number(e.target.value))} className="bg-white/50" />
+
+
+
+ Time Period (Years)
+ {years} Years
+
+
setYears(v[0])} max={50} step={1} className="py-2" />
+
+
+
+ Interest Rate (%)
+ {rate}%
+
+
setRate(v[0])} max={15} step={0.1} className="py-2" />
+
+
+
+
+ {/* Results */}
+
+ Projected Savings
+
+
+
In {years} years, you will have
+
+
+ {formatAmount(Math.round(finalAmount))}
+
+
+
+
+
+
Total Contributions
+
+ {formatAmount(Math.round(totalInvested))}
+
+
+
+
Total Interest Earned
+
+ {formatAmount(Math.round(totalInterest))}
+
+
+
+
+
+ );
+};
+
+export default CompoundInterestCalculator;
diff --git a/src/components/savings/SavingsAccountsList.tsx b/src/components/savings/SavingsAccountsList.tsx
new file mode 100644
index 0000000..b26c5da
--- /dev/null
+++ b/src/components/savings/SavingsAccountsList.tsx
@@ -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 (
+
+ Savings Accounts
+
+ {accounts.map((acc, index) => (
+
+
+
+
+
+
+
{acc.name}
+
+ {acc.type}
+ •
+ {acc.apy}% APY
+
+
+
+
+
+ {formatAmount(acc.balance)}
+
+
+ + {acc.growth}/mo
+
+
+
+ ))}
+
+
+ );
+};
+
+export default SavingsAccountsList;
diff --git a/src/components/savings/SavingsAccountsTable.tsx b/src/components/savings/SavingsAccountsTable.tsx
new file mode 100644
index 0000000..c699765
--- /dev/null
+++ b/src/components/savings/SavingsAccountsTable.tsx
@@ -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 (
+
+
+
+
+ Account Name
+ Type
+ Balance (AED)
+ Interest Rate
+ Monthly Growth
+ Actions
+
+
+
+ {accounts.map((acc, idx) => (
+
+ {acc.name}
+
+
+ {acc.type}
+
+
+
+
+ {formatAmount(acc.balance)}
+
+
+ {acc.rate}%
+
+
+ + {acc.growth}
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default SavingsAccountsTable;
diff --git a/src/components/savings/SavingsGoalsGrid.tsx b/src/components/savings/SavingsGoalsGrid.tsx
new file mode 100644
index 0000000..26a658d
--- /dev/null
+++ b/src/components/savings/SavingsGoalsGrid.tsx
@@ -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 (
+
+ {goals.map((goal, index) => {
+ const percentage = Math.round((goal.saved / goal.target) * 100);
+
+ return (
+
+
+
+
+
{goal.name}
+
+
+ {goal.daysLeft} days remaining
+
+
+
+ {percentage}%
+
+
+
+
+
+
+ {formatAmount(goal.saved)}
+ {formatAmount(goal.target)}
+
+
+
+
+
+
+ +1K
+
+
+ +5K
+
+
+ Custom
+
+
+
+ );
+ })}
+
+ );
+};
+
+export default SavingsGoalsGrid;
diff --git a/src/components/savings/SavingsGoalsList.tsx b/src/components/savings/SavingsGoalsList.tsx
new file mode 100644
index 0000000..15dbf49
--- /dev/null
+++ b/src/components/savings/SavingsGoalsList.tsx
@@ -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 (
+
+ Savings Goals
+
+ {goals.map((goal, index) => {
+ const percentage = Math.min((goal.saved / goal.target) * 100, 100);
+ return (
+
+
+ {goal.name}
+
+ {goal.status}
+
+
+
+
+
+ {formatAmount(goal.saved)}
+
+
+ of {formatAmount(goal.target)}
+
+
+
+
+
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default SavingsGoalsList;
diff --git a/src/components/savings/SavingsSummaryCards.tsx b/src/components/savings/SavingsSummaryCards.tsx
new file mode 100644
index 0000000..dd8c519
--- /dev/null
+++ b/src/components/savings/SavingsSummaryCards.tsx
@@ -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 (
+
+ {/* Total Savings */}
+
+
+
+
+
{formatAmount(data.totalSavings)}
+
+ Across all accounts
+
+
+ {/* Monthly Growth */}
+
+
+
+
+
{formatAmount(data.monthlyGrowth)}
+
+
+
+ +1.8% vs last month
+
+
+
+ {/* Goals Progress */}
+
+
+ {data.goalsProgress}%
+
+
+
+ {/* Annual Yield */}
+
+
+ {data.annualYield}%
+ Average across liquid assets
+
+
+ );
+};
+
+export default SavingsSummaryCards;
diff --git a/src/components/ui/GlassCard.tsx b/src/components/ui/GlassCard.tsx
index be10d34..3a228b1 100644
--- a/src/components/ui/GlassCard.tsx
+++ b/src/components/ui/GlassCard.tsx
@@ -1,5 +1,6 @@
import { cn } from "@/lib/utils";
import React from "react";
+import { useTheme } from "@/context/ThemeContext";
interface GlassCardProps extends React.HTMLAttributes {
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 (
{
+ className?: string;
+}
+
+export const DirhamIcon = ({ className, ...props }: IconProps) => (
+
+
+
+);
diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx
new file mode 100644
index 0000000..58da95e
--- /dev/null
+++ b/src/context/ThemeContext.tsx
@@ -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
(undefined);
+
+export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [theme, setTheme] = useState(() => {
+ 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 (
+
+ {/* Background Layer managed here for global coverage */}
+ {theme !== 'default' && (
+
+ )}
+ {/* Overlay for contrast if needed */}
+ {theme === 'dubai' &&
}
+
+ {children}
+
+ );
+};
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index c0ef83f..7d6ca63 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -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);