}
/>
}
/>
}
/>
diff --git a/app/dashboard/goals/page.tsx b/app/dashboard/goals/page.tsx
index 69ec500..197ea10 100644
--- a/app/dashboard/goals/page.tsx
+++ b/app/dashboard/goals/page.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useState } from 'react'
+import { useState, useMemo } from 'react'
import {
GraduationCap,
HeartPulse,
@@ -10,9 +10,13 @@ import {
import PageHeader from '@/components/PageHeader'
import SavingsGoalCard from '@/components/Dashboard/SavingsGoalCard'
import SavingsGoalsStatsCards from './components/SavingsGoalsStatsCards'
+import SavingsGoalModal from './components/SavingsGoalModal'
+import { SavingsGoal } from './types'
+import { calculateDaysLeft, checkIsOverdue } from './utils'
+import { useClientTranslator } from '@/lib/i18n/client'
// Sample data matching Figma design
-const goalsData = [
+const initialGoals: SavingsGoal[] = [
{
id: 1,
title: "Children's Education",
@@ -21,9 +25,7 @@ const goalsData = [
iconGradient: { from: '#DC2626', to: '#B91C1C' },
currentAmount: 3600,
targetAmount: 5000,
- targetDate: 'Dec 31, 2025',
- daysLeft: 335,
- isOverdue: false,
+ targetDate: '2026-12-31',
},
{
id: 2,
@@ -33,9 +35,7 @@ const goalsData = [
iconGradient: { from: '#F87171', to: '#EF4444' },
currentAmount: 1800,
targetAmount: 2000,
- targetDate: 'Mar 15, 2025',
- daysLeft: 44,
- isOverdue: false,
+ targetDate: '2026-08-15',
},
{
id: 3,
@@ -45,8 +45,7 @@ const goalsData = [
iconGradient: { from: '#DC2626', to: '#B91C1C' },
currentAmount: 8500,
targetAmount: 25000,
- targetDate: 'Jan 15, 2025',
- isOverdue: true,
+ targetDate: '2026-05-15', // This will be overdue as of June 17, 2026
},
{
id: 4,
@@ -56,36 +55,56 @@ const goalsData = [
iconGradient: { from: '#F87171', to: '#EF4444' },
currentAmount: 3000,
targetAmount: 3000,
- targetDate: 'Jul 1, 2025',
- isOverdue: false,
+ targetDate: '2026-07-01',
},
]
-// Calculate summary stats
-const totalGoals = goalsData.length
-const totalTarget = goalsData.reduce((sum, goal) => sum + goal.targetAmount, 0)
-const totalSaved = goalsData.reduce((sum, goal) => sum + goal.currentAmount, 0)
+export default function SavingsGoalsPage() {
+ const { t } = useClientTranslator()
+ const [goals, setGoals] = useState
(initialGoals)
+ const [showModal, setShowModal] = useState(false)
+ const [editingGoal, setEditingGoal] = useState(null)
-function formatCurrency(amount: number): string {
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: 'USD',
- minimumFractionDigits: 0,
- maximumFractionDigits: 0,
- }).format(amount)
-}
+ // Calculate summary stats dynamically
+ const stats = useMemo(() => {
+ const totalGoals = goals.length
+ const totalTarget = goals.reduce((sum, goal) => sum + goal.targetAmount, 0)
+ const totalSaved = goals.reduce((sum, goal) => sum + goal.currentAmount, 0)
+ return { totalGoals, totalTarget, totalSaved }
+ }, [goals])
-export default function SavingsGoalsPage() {
- const [showNewGoalModal, setShowNewGoalModal] = useState(false)
+ const handleNewGoal = () => {
+ setEditingGoal(null)
+ setShowModal(true)
+ }
+
+ const handleEditGoal = (goal: SavingsGoal) => {
+ setEditingGoal(goal)
+ setShowModal(true)
+ }
+
+ const handleSaveGoal = (goalData: Partial) => {
+ if (editingGoal) {
+ setGoals(goals.map(g => g.id === editingGoal.id ? { ...g, ...goalData } as SavingsGoal : g))
+ } else {
+ const newGoal: SavingsGoal = {
+ ...goalData,
+ id: Date.now(),
+ currentAmount: 0,
+ } as SavingsGoal
+ setGoals([...goals, newGoal])
+ }
+ setShowModal(false)
+ }
return (
{/* Header */}
setShowNewGoalModal(true)}
+ title={t('savingsGoals.title')}
+ subtitle={t('savingsGoals.subtitle')}
+ ctaLabel={t('savingsGoals.newGoal')}
+ onCtaClick={handleNewGoal}
showBottomDivider
/>
@@ -93,9 +112,9 @@ export default function SavingsGoalsPage() {
{/* Savings Goals Stats Cards */}
@@ -105,7 +124,7 @@ export default function SavingsGoalsPage() {
{/* Goals Grid */}
- {goalsData.map((goal) => (
+ {goals.map((goal) => (
console.log('Add funds to', goal.title)}
- onDetails={() => console.log('View details for', goal.title)}
+ onEdit={() => handleEditGoal(goal)}
/>
))}
- {/* New Goal Modal Placeholder */}
- {showNewGoalModal && (
- setShowNewGoalModal(false)}
- >
-
e.stopPropagation()}
- >
-
Create New Goal
-
- Goal creation form will be implemented here. Connect to savings_goals smart contract.
-
-
setShowNewGoalModal(false)}
- className="touch-target-wide w-full rounded-[14px] text-sm 375:text-base font-semibold text-white"
- style={{
- background: 'linear-gradient(180deg, #DC2626 0%, #B91C1C 100%)',
- }}
- >
- Close
-
-
-
- )}
+ {/* Savings Goal Modal */}
+ setShowModal(false)}
+ onSave={handleSaveGoal}
+ editingGoal={editingGoal}
+ />
)
}
+
diff --git a/app/dashboard/goals/types.ts b/app/dashboard/goals/types.ts
new file mode 100644
index 0000000..c8f42db
--- /dev/null
+++ b/app/dashboard/goals/types.ts
@@ -0,0 +1,20 @@
+import { ReactNode } from 'react'
+
+export interface SavingsGoal {
+ id: number | string
+ title: string
+ description: string
+ icon: ReactNode
+ iconGradient: { from: string; to: string }
+ currentAmount: number
+ targetAmount: number
+ targetDate: string
+}
+
+export interface SavingsGoalFormData {
+ title: string
+ description: string
+ targetAmount: number
+ targetDate: string
+ iconType: 'education' | 'medical' | 'home' | 'vacation'
+}
diff --git a/app/dashboard/goals/utils.ts b/app/dashboard/goals/utils.ts
new file mode 100644
index 0000000..e8f58b3
--- /dev/null
+++ b/app/dashboard/goals/utils.ts
@@ -0,0 +1,31 @@
+/**
+ * Calculates the number of days left until a target date.
+ * Returns 0 if the date is today or in the past.
+ */
+export function calculateDaysLeft(targetDate: string): number {
+ const target = new Date(targetDate)
+ const today = new Date()
+
+ // Set times to midnight for accurate day calculation
+ target.setHours(0, 0, 0, 0)
+ today.setHours(0, 0, 0, 0)
+
+ const diffTime = target.getTime() - today.getTime()
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
+
+ return Math.max(0, diffDays)
+}
+
+/**
+ * Checks if a target date is in the past (overdue).
+ */
+export function checkIsOverdue(targetDate: string): boolean {
+ const target = new Date(targetDate)
+ const today = new Date()
+
+ // Set times to midnight
+ target.setHours(0, 0, 0, 0)
+ today.setHours(0, 0, 0, 0)
+
+ return target < today
+}
diff --git a/app/dashboard/insight/page.tsx b/app/dashboard/insight/page.tsx
index 89eaf63..2aa1632 100644
--- a/app/dashboard/insight/page.tsx
+++ b/app/dashboard/insight/page.tsx
@@ -1,4 +1,4 @@
-import SixMonthTrendsWidget from '@/components/dashboard/SixMonthTrendsWidget'
+import SixMonthTrendsWidget from '@/components/Dashboard/SixMonthTrendsWidget'
export default function InsightPage() {
return (
{
} finally {
setLoading(false);
}
- }, [statusFilter]);
+ }, [statusFilter, t]);
useEffect(() => {
fetchTransactions(undefined, true);
diff --git a/app/insurance/page.tsx b/app/insurance/page.tsx
index eec4c19..e86d186 100644
--- a/app/insurance/page.tsx
+++ b/app/insurance/page.tsx
@@ -1,143 +1,21 @@
"use client"
import Link from 'next/link'
import { ArrowLeft, Plus, Shield, Loader2, CalendarClock } from 'lucide-react'
-import PrimaryButton from '@/components/ui/PrimaryButton'
import { ActionState } from '@/lib/auth/middleware';
import { useFormAction } from '@/lib/hooks/useFormAction';
import { getPolicyPaymentPresentation } from '@/lib/ui/status-semantics';
import NewPolicyForm from '@/components/forms/NewPolicyForm';
export default function Insurance() {
-
- type AddInsuranceResponse = ActionState & { policyName?: string; coverageAmount?: number, monthlyPremium?: number, coverageType?: string };
+ type AddInsuranceResponse = ActionState & {
+ policyName?: string;
+ coverageAmount?: number;
+ monthlyPremium?: number;
+ coverageType?: string
+ };
const [state, formAction, pending] = useFormAction
("/api/insurance");
- return (
-
- {/* Header */}
-
-
-
-
-
-
-
-
Micro-Insurance
-
-
-
- New Policy
-
-
Policy creation will be available once contract integration is live.
-
-
-
-
- {/* New Policy Form */}
-
-
-
- {/* Active Policies */}
-
-
Active Policies
-
-function PolicyCard({ name, coverageType, monthlyPremium, coverageAmount, nextPayment, active }: {
- name: string,
- coverageType: string,
- monthlyPremium: number,
- coverageAmount: number,
- nextPayment: string,
- active: boolean
-}) {
- const paymentStatus = getPolicyPaymentPresentation(nextPayment, active);
- const StatusIcon = paymentStatus.icon;
-
- return (
-
-
-
-
-
{name}
-
-
-
- {paymentStatus.label}
-
-
-
-
-
- Coverage Type
- {coverageType}
-
-
- Monthly Premium
- ${monthlyPremium}
-
-
- Coverage Amount
- ${coverageAmount}
-
-
- Next Payment
- {nextPayment}
-
-
-
-
-
-
-
{paymentStatus.label}
-
{paymentStatus.emphasis}
-
-
- Next scheduled payment: {nextPayment}
-
-
-
-
-
- {active && (
-
- Pay Premium Now
-"use client"
-import Link from 'next/link'
-import { ArrowLeft, Plus, Shield, Loader2, CalendarClock } from 'lucide-react'
-import PrimaryButton from '@/components/ui/PrimaryButton'
-import { ActionState } from '@/lib/auth/middleware';
-import { useFormAction } from '@/lib/hooks/useFormAction';
-import { getPolicyPaymentPresentation } from '@/lib/ui/status-semantics';
-import NewPolicyForm from '@/components/forms/NewPolicyForm';
-
-export default function Insurance() {
-
- type AddInsuranceResponse = ActionState & { policyName?: string; coverageAmount?: number, monthlyPremium?: number, coverageType?: string };
-
- const [state, formAction, pending] = useFormAction("/api/insurance");
return (
{/* Header */}
@@ -150,14 +28,16 @@ export default function Insurance() {
Micro-Insurance
-
-
- New Policy
-
- Policy creation will be available once contract integration is live.
+
+
+
+ New Policy
+
+
Policy creation will be available once contract integration is live.
+
@@ -193,13 +73,20 @@ export default function Insurance() {
)
}
-function PolicyCard({ name, coverageType, monthlyPremium, coverageAmount, nextPayment, active }: {
- name: string,
- coverageType: string,
- monthlyPremium: number,
- coverageAmount: number,
- nextPayment: string,
- active: boolean
+function PolicyCard({
+ name,
+ coverageType,
+ monthlyPremium,
+ coverageAmount,
+ nextPayment,
+ active
+}: {
+ name: string;
+ coverageType: string;
+ monthlyPremium: number;
+ coverageAmount: number;
+ nextPayment: string;
+ active: boolean;
}) {
const paymentStatus = getPolicyPaymentPresentation(nextPayment, active);
const StatusIcon = paymentStatus.icon;
diff --git a/app/settings/page.tsx b/app/settings/page.tsx
index d60f4d0..f5e64f4 100644
--- a/app/settings/page.tsx
+++ b/app/settings/page.tsx
@@ -16,6 +16,7 @@ import {
Smartphone,
} from "lucide-react";
import { useDensity } from "@/lib/context/DensityContext";
+import { useToast } from "@/lib/context/ToastContext";
const SECTIONS = [
diff --git a/components/Bills/UnpaidBillsSection.tsx b/components/Bills/UnpaidBillsSection.tsx
index c8a3575..70e1528 100644
--- a/components/Bills/UnpaidBillsSection.tsx
+++ b/components/Bills/UnpaidBillsSection.tsx
@@ -37,10 +37,11 @@ export function UnpaidBillsSection() {
{statusOrder.map((status) => {
const bills = billsByStatus[status] ?? [];
if (!bills.length) return null;
- const headerStyles = {
+ const headerStyles: Record = {
overdue: "text-red-400",
urgent: "text-amber-400",
upcoming: "text-white/60",
+ paid: "text-green-400",
};
return (
diff --git a/components/Dashboard/SavingsGoalCard.tsx b/components/Dashboard/SavingsGoalCard.tsx
index 02c9431..cc8550d 100644
--- a/components/Dashboard/SavingsGoalCard.tsx
+++ b/components/Dashboard/SavingsGoalCard.tsx
@@ -9,6 +9,7 @@ import {
Clock3,
Sparkles,
} from 'lucide-react'
+import { useClientTranslator } from '@/lib/i18n/client'
export interface SavingsGoalCardProps {
title: string
@@ -21,7 +22,7 @@ export interface SavingsGoalCardProps {
daysLeft?: number
isOverdue?: boolean
onAddFunds?: () => void
- onDetails?: () => void
+ onEdit?: () => void
}
function formatCurrency(amount: number): string {
@@ -52,8 +53,9 @@ export default function SavingsGoalCard({
daysLeft,
isOverdue = false,
onAddFunds,
- onDetails,
+ onEdit,
}: SavingsGoalCardProps) {
+ const { t } = useClientTranslator()
const percentage = targetAmount > 0 ? Math.min((currentAmount / targetAmount) * 100, 100) : 0
const remaining = Math.max(targetAmount - currentAmount, 0)
const isComplete = !isOverdue && percentage >= 100
@@ -69,28 +71,28 @@ export default function SavingsGoalCard({
const statusStyles = {
overdue: {
- label: 'Overdue',
+ label: t('savingsGoals.card.overdue'),
icon: AlertTriangle,
background: 'rgba(220, 38, 38, 0.18)',
border: 'rgba(220, 38, 38, 0.35)',
text: '#FCA5A5',
},
complete: {
- label: 'Completed',
+ label: t('savingsGoals.card.complete'),
icon: CheckCircle2,
background: 'rgba(16, 185, 129, 0.16)',
border: 'rgba(34, 197, 94, 0.28)',
text: '#A7F3D0',
},
'near-complete': {
- label: 'Almost there',
+ label: t('savingsGoals.card.nearComplete'),
icon: Sparkles,
background: 'rgba(245, 158, 11, 0.18)',
border: 'rgba(245, 158, 11, 0.3)',
text: '#FCD34D',
},
'on-track': {
- label: 'On track',
+ label: t('savingsGoals.card.onTrack'),
icon: Clock3,
background: 'rgba(56, 189, 248, 0.18)',
border: 'rgba(56, 189, 248, 0.3)',
@@ -99,12 +101,12 @@ export default function SavingsGoalCard({
}[state]
const targetInfoLabel = isOverdue
- ? 'Overdue'
+ ? t('savingsGoals.card.overdue')
: isComplete
- ? 'Goal met'
+ ? t('savingsGoals.card.goalMet')
: daysLeft !== undefined
- ? `${daysLeft} days left`
- : 'No deadline'
+ ? t('savingsGoals.card.daysLeft', { count: daysLeft })
+ : t('savingsGoals.card.noDeadline')
return (
-
{title}
-
{description}
+
{title}
+
{description}
@@ -160,12 +162,12 @@ export default function SavingsGoalCard({
{isComplete
- ? 'All set — target reached'
- : `Need ${formatCurrency(remaining)} more`}
+ ? t('savingsGoals.card.complete')
+ : t('savingsGoals.card.needMore', { amount: formatCurrency(remaining) })}
-
Target
+
{t('savingsGoals.card.target')}
{formatDate(targetDate)}
{targetInfoLabel}
@@ -193,7 +195,7 @@ export default function SavingsGoalCard({
{percentage.toFixed(0)}% complete
- {isComplete ? 'Goal reached' : `${formatCurrency(remaining)} remaining`}
+ {isComplete ? t('savingsGoals.card.goalMet') : t('savingsGoals.card.remaining', { amount: formatCurrency(remaining) })}
@@ -204,14 +206,14 @@ export default function SavingsGoalCard({
onClick={onAddFunds}
className="touch-target-wide rounded-[14px] bg-gradient-to-b from-[#DC2626] to-[#B91C1C] px-4 py-3 text-sm font-semibold text-white transition hover:brightness-110"
>
- Add Funds
+ {t('savingsGoals.card.addFunds')}
- Details
+ {t('savingsGoals.card.edit')}
diff --git a/components/Insights/remittanceTrendChart.tsx b/components/Insights/remittanceTrendChart.tsx
index e1335d8..b57a90c 100644
--- a/components/Insights/remittanceTrendChart.tsx
+++ b/components/Insights/remittanceTrendChart.tsx
@@ -1,5 +1,16 @@
'use client'
+import { Activity } from 'lucide-react';
+import {
+ ResponsiveContainer,
+ AreaChart,
+ CartesianGrid,
+ XAxis,
+ YAxis,
+ Tooltip,
+ ReferenceLine,
+ Area
+} from 'recharts';
import { INSIGHTS_PALETTE } from './palette';
const LINE_COLOR = INSIGHTS_PALETTE[0];
@@ -30,7 +41,6 @@ export const MOCK_TREND_DATA: TrendDataPoint[] = [
const AXIS_COLOR = '#6b7280'
const GRID_COLOR = 'rgba(255,255,255,0.06)'
-const LINE_COLOR = '#D72323'
// ── Custom tooltip ────────────────────────────────────────────────────────────
interface CustomTooltipProps {
@@ -45,7 +55,7 @@ function CustomTooltip({ active, payload, label }: CustomTooltipProps) {
return (