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