Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 61 additions & 38 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { Send, PiggyBank, FileText, Shield } from "lucide-react";
import CurrentMoneySplitWidget from '@/components/CurrentMoneySplitWidget'
import GoalProgress from "@/components/Dashboard/GoalProgress";
Expand All @@ -9,69 +11,89 @@ import TransactionHistoryItem, { Transaction } from "@/components/Dashboard/Tran
import MoneyDistributionWidget from "@/components/Dashboard/MoneyDistributionWidget";
import RecentTransactionsWidget from "@/components/Dashboard/RecentTransactionsWidget";
import QuickActions from "@/components/Dashboard/QuickActions";
import WidgetErrorBoundary from '@/components/ui/WidgetErrorBoundary'

export default function Dashboard() {
return (
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Stats Overview */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<StatCard
title="Total Sent"
value="$1,200"
percentage="+25%"
icon={<Send className="w-5 h-5" />}
trend="up"
/>
<StatCard
title="Savings"
value="$360"
percentage="+33%"
icon={<PiggyBank className="w-5 h-5" />}
trend="up"
/>
<StatCard
title="Bills Paid"
value="$180"
percentage="3 bills"
icon={<FileText className="w-5 h-5" />}
trend="none"
/>
<StatCard
title="Insurance"
value="$60"
percentage="2 policies"
icon={<Shield className="w-5 h-5" />}
trend="none"
/>
<WidgetErrorBoundary widgetName="StatCard-TotalSent">
<StatCard
title="Total Sent"
value="$1,200"
percentage="+25%"
icon={<Send className="w-5 h-5" />}
trend="up"
/>
</WidgetErrorBoundary>
<WidgetErrorBoundary widgetName="StatCard-Savings">
<StatCard
title="Savings"
value="$360"
percentage="+33%"
icon={<PiggyBank className="w-5 h-5" />}
trend="up"
/>
</WidgetErrorBoundary>
<WidgetErrorBoundary widgetName="StatCard-BillsPaid">
<StatCard
title="Bills Paid"
value="$180"
percentage="3 bills"
icon={<FileText className="w-5 h-5" />}
trend="none"
/>
</WidgetErrorBoundary>
<WidgetErrorBoundary widgetName="StatCard-Insurance">
<StatCard
title="Insurance"
value="$60"
percentage="2 policies"
icon={<Shield className="w-5 h-5" />}
trend="none"
/>
</WidgetErrorBoundary>
</div>

{/* Quick Actions Panel */}
<div className="mb-8">
<QuickActions />
<WidgetErrorBoundary widgetName="QuickActions">
<QuickActions />
</WidgetErrorBoundary>
</div>

{/* Money Split Visualization */}
<div className="mb-8">
<CurrentMoneySplitWidget />
<WidgetErrorBoundary widgetName="CurrentMoneySplitWidget">
<CurrentMoneySplitWidget />
</WidgetErrorBoundary>
</div>
<div className="mb-8">
<MoneyDistributionWidget />
<WidgetErrorBoundary widgetName="MoneyDistributionWidget">
<MoneyDistributionWidget />
</WidgetErrorBoundary>
</div>

{/* Recent Transactions */}
<div className="mb-8">
<RecentTransactionsWidget />
<WidgetErrorBoundary widgetName="RecentTransactionsWidget">
<RecentTransactionsWidget />
</WidgetErrorBoundary>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Savings Goals Progress */}
<SavingsByGoalWidget />
<WidgetErrorBoundary widgetName="SavingsByGoalWidget">
<SavingsByGoalWidget />
</WidgetErrorBoundary>

{/* Bills by Type */}
<div
className="rounded-2xl shadow-md p-6 border border-[#FFFFFF14] hover:border-white/30 transition-colors duration-300"
style={{ backgroundImage: "var(--card)" }}
>
<WidgetErrorBoundary widgetName="BillsByType">
<div
className="rounded-2xl shadow-md p-6 border border-[#FFFFFF14] hover:border-white/30 transition-colors duration-300"
style={{ backgroundImage: "var(--card)" }}
>
<div className="flex items-center gap-2 mb-4">
<span>
<FileText className="w-6 h-6 text-[var(--accent)]" />
Expand Down Expand Up @@ -106,7 +128,8 @@ export default function Dashboard() {
gradient={{ from: "#5F1515", to: "#4F1111" }}
/>
</div>
</div>
</div>
</WidgetErrorBoundary>
</div>
</main>
);
Expand Down
62 changes: 62 additions & 0 deletions components/ui/WidgetErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client"

import React from 'react'
import WidgetErrorState from '@/components/ui/WidgetErrorState'
import { logError } from '@/lib/logger'
import { generateRequestId } from '@/lib/requestId'

interface WidgetErrorBoundaryProps {
widgetName: string
children: React.ReactNode
}

interface WidgetErrorBoundaryState {
hasError: boolean
error: Error | null
}

/**
* Reusable React error boundary for dashboard widgets.
* Catches render-time errors in wrapped widgets, logs via logger,
* and renders WidgetErrorState with retry.
*/
class WidgetErrorBoundary extends React.Component<
WidgetErrorBoundaryProps,
WidgetErrorBoundaryState
> {
state: WidgetErrorBoundaryState = { hasError: false, error: null }

static getDerivedStateFromError(error: Error): WidgetErrorBoundaryState {
return { hasError: true, error }
}

componentDidCatch(error: Error, _info: React.ErrorInfo): void {
const requestId = generateRequestId()
logError(requestId, 'WIDGET_ERROR', `dashboard/${this.props.widgetName}`, error)
console.error(`Widget error in ${this.props.widgetName}:`, error)
}

reset = (): void => {
this.setState({ hasError: false, error: null })
}

render(): React.ReactNode {
if (this.state.hasError) {
return (
<div
role="region"
aria-label={`${this.props.widgetName} – error`}
>
<WidgetErrorState
message={this.state.error?.message}
onRetry={this.reset}
/>
</div>
)
}

return this.props.children
}
}

export default WidgetErrorBoundary