diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1b7aa7b..5ac4040 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/forms": "^0.5.10",
@@ -29,6 +30,7 @@
"react-day-picker": "^9.10.0",
"react-dom": "^19.1.1",
"recharts": "^3.2.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"
@@ -1637,6 +1639,30 @@
}
}
},
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
+ "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-select": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
@@ -5765,6 +5791,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 082d736..5111dbf 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/forms": "^0.5.10",
@@ -31,6 +32,7 @@
"react-day-picker": "^9.10.0",
"react-dom": "^19.1.1",
"recharts": "^3.2.0",
+ "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"
diff --git a/frontend/src/components/AccountsOverview.tsx b/frontend/src/components/AccountsOverview.tsx
index 18e8aa0..95214f7 100644
--- a/frontend/src/components/AccountsOverview.tsx
+++ b/frontend/src/components/AccountsOverview.tsx
@@ -37,23 +37,23 @@ const getStatusIndicator = (status: string) => {
};
case "pending":
return {
- color: "bg-yellow-500",
+ color: "bg-amber-500",
tooltip: "Pending",
};
case "error":
case "failed":
return {
- color: "bg-red-500",
+ color: "bg-destructive",
tooltip: "Error",
};
case "inactive":
return {
- color: "bg-gray-500",
+ color: "bg-muted-foreground",
tooltip: "Inactive",
};
default:
return {
- color: "bg-blue-500",
+ color: "bg-primary",
tooltip: status,
};
}
@@ -201,8 +201,8 @@ export default function AccountsOverview() {
{uniqueBanks}
-
diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx
index 7da9df2..864ecbb 100644
--- a/frontend/src/components/ErrorBoundary.tsx
+++ b/frontend/src/components/ErrorBoundary.tsx
@@ -1,6 +1,9 @@
import { Component } from "react";
import type { ErrorInfo, ReactNode } from "react";
import { AlertTriangle, RefreshCw } from "lucide-react";
+import { Card, CardContent } from "./ui/card";
+import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
+import { Button } from "./ui/button";
interface Props {
children: ReactNode;
@@ -39,46 +42,49 @@ class ErrorBoundary extends Component {
}
return (
-
-
-
-
-
- Something went wrong
-
-
- An error occurred while rendering this component. Please try
- refreshing or check the console for more details.
-
+
+
+
+
+
+
+ Something went wrong
+
+
+ An error occurred while rendering this component. Please try
+ refreshing or check the console for more details.
+
- {this.state.error && (
-
-
- Error: {this.state.error.message}
-
- {this.state.error.stack && (
-
-
- Stack trace
-
-
- {this.state.error.stack}
-
-
- )}
-
- )}
+ {this.state.error && (
+
+
+ Error Details
+
+
+ Error: {this.state.error.message}
+
+ {this.state.error.stack && (
+
+
+ Stack trace
+
+
+ {this.state.error.stack}
+
+
+ )}
+
+
+ )}
-
+
+
-
-
+
+
);
}
diff --git a/frontend/src/components/FiltersSkeleton.tsx b/frontend/src/components/FiltersSkeleton.tsx
index 9ff3622..f5c2220 100644
--- a/frontend/src/components/FiltersSkeleton.tsx
+++ b/frontend/src/components/FiltersSkeleton.tsx
@@ -1,29 +1,32 @@
+import { Skeleton } from "./ui/skeleton";
+import { Card, CardContent } from "./ui/card";
+
export default function FiltersSkeleton() {
return (
-
-
+
+
-
+
{/* Quick Date Filters Skeleton */}
@@ -31,40 +34,40 @@ export default function FiltersSkeleton() {
{/* Filter Fields Skeleton */}
{/* Amount Range Filters Skeleton */}
-
+
{/* Results Summary Skeleton */}
-
-
+
+
+
+
);
}
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx
index 7025fa1..24be47f 100644
--- a/frontend/src/components/Header.tsx
+++ b/frontend/src/components/Header.tsx
@@ -49,15 +49,15 @@ export default function Header({ setSidebarOpen }: HeaderProps) {
{healthLoading ? (
<>
-
+
Checking...
>
) : healthError || healthStatus?.status !== "healthy" ? (
<>
-
-
Disconnected
+
+
Disconnected
>
) : (
<>
diff --git a/frontend/src/components/LoadingSpinner.tsx b/frontend/src/components/LoadingSpinner.tsx
index 1831094..a4c793d 100644
--- a/frontend/src/components/LoadingSpinner.tsx
+++ b/frontend/src/components/LoadingSpinner.tsx
@@ -1,17 +1,20 @@
import { RefreshCw } from "lucide-react";
+import { cn } from "../lib/utils";
interface LoadingSpinnerProps {
message?: string;
+ className?: string;
}
export default function LoadingSpinner({
message = "Loading...",
+ className,
}: LoadingSpinnerProps) {
return (
-
+
-
-
{message}
+
+
{message}
);
diff --git a/frontend/src/components/Notifications.tsx b/frontend/src/components/Notifications.tsx
index 356cc9b..099cad1 100644
--- a/frontend/src/components/Notifications.tsx
+++ b/frontend/src/components/Notifications.tsx
@@ -24,6 +24,7 @@ import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
+import { Badge } from "./ui/badge";
import {
Select,
SelectContent,
@@ -233,12 +234,8 @@ export default function Notifications() {
{service.name}
-
{service.enabled ? (
@@ -246,18 +243,14 @@ export default function Notifications() {
)}
{service.enabled ? "Enabled" : "Disabled"}
-
-
+
{service.configured
? "Configured"
: "Not Configured"}
-
+
diff --git a/frontend/src/components/TransactionSkeleton.tsx b/frontend/src/components/TransactionSkeleton.tsx
index 4dbd699..b92bd2a 100644
--- a/frontend/src/components/TransactionSkeleton.tsx
+++ b/frontend/src/components/TransactionSkeleton.tsx
@@ -1,3 +1,6 @@
+import { Skeleton } from "./ui/skeleton";
+import { Card } from "./ui/card";
+
interface TransactionSkeletonProps {
rows?: number;
view?: "table" | "mobile";
@@ -11,93 +14,89 @@ export default function TransactionSkeleton({
if (view === "mobile") {
return (
-
+
{skeletonRows.map((_, index) => (
-
+
);
}
return (
-
+
-
-
+
+
|
-
+
|
-
+
|
-
+
|
-
+
|
-
+
{skeletonRows.map((_, index) => (
-
+
|
|
|
|
-
+
|
))}
-
+
);
}
diff --git a/frontend/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx
new file mode 100644
index 0000000..6911671
--- /dev/null
+++ b/frontend/src/components/ui/progress.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
+
+import { cn } from "@/lib/utils";
+
+const Progress = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, value, ...props }, ref) => (
+
+
+
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
+
+export { Progress };
\ No newline at end of file
diff --git a/frontend/src/components/ui/sheet.tsx b/frontend/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..b6dc362
--- /dev/null
+++ b/frontend/src/components/ui/sheet.tsx
@@ -0,0 +1,138 @@
+import * as React from "react";
+import * as SheetPrimitive from "@radix-ui/react-dialog";
+import { cva, type VariantProps } from "class-variance-authority";
+import { X } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+const Sheet = SheetPrimitive.Root;
+
+const SheetTrigger = SheetPrimitive.Trigger;
+
+const SheetClose = SheetPrimitive.Close;
+
+const SheetPortal = SheetPrimitive.Portal;
+
+const SheetOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
+
+const sheetVariants = cva(
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+ {
+ variants: {
+ side: {
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ bottom:
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ right:
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ },
+ },
+ defaultVariants: {
+ side: "right",
+ },
+ }
+);
+
+interface SheetContentProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {}
+
+const SheetContent = React.forwardRef<
+ React.ElementRef,
+ SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+));
+SheetContent.displayName = SheetPrimitive.Content.displayName;
+
+const SheetHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+SheetHeader.displayName = "SheetHeader";
+
+const SheetFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+SheetFooter.displayName = "SheetFooter";
+
+const SheetTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SheetTitle.displayName = SheetPrimitive.Title.displayName;
+
+const SheetDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SheetDescription.displayName = SheetPrimitive.Description.displayName;
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+};
\ No newline at end of file
diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..0f90e0d
--- /dev/null
+++ b/frontend/src/components/ui/skeleton.tsx
@@ -0,0 +1,14 @@
+import { cn } from "@/lib/utils";
+
+interface SkeletonProps extends React.HTMLAttributes {}
+
+function Skeleton({ className, ...props }: SkeletonProps) {
+ return (
+
+ );
+}
+
+export { Skeleton };
\ No newline at end of file
diff --git a/frontend/src/components/ui/sonner.tsx b/frontend/src/components/ui/sonner.tsx
new file mode 100644
index 0000000..3380c7c
--- /dev/null
+++ b/frontend/src/components/ui/sonner.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import { Toaster as Sonner } from "sonner";
+
+type ToasterProps = React.ComponentProps;
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ return (
+
+ );
+};
+
+export { Toaster };
\ No newline at end of file