From f7724db4015345d20a44fd8bdb325ad4af3cee00 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 7 Apr 2026 16:08:52 +0300 Subject: [PATCH 1/7] refactor: initial migration to remix --- web/app/root.tsx | 84 ++++----- web/app/routes/_index.tsx | 5 + web/components/drawers/delivery-drawer.tsx | 114 ------------ web/components/logs-list.tsx | 123 ++++++------- .../modals/create-webhook-modal.tsx | 108 ----------- web/components/pages/deliveries.tsx | 16 +- web/components/pages/overview.tsx | 158 +++++++++------- web/components/pages/settings.tsx | 168 +++++++++++------- web/components/pages/webhooks.tsx | 22 +-- web/components/sidebar.tsx | 35 ++-- web/components/stat-card.tsx | 2 +- web/components/top-nav.tsx | 105 ++++++++++- web/components/webhook-table.tsx | 111 ++++++++---- web/package.json | 158 ++++++++-------- web/pnpm-lock.yaml | 154 ++++++++++++++++ web/vite.config.ts | 2 +- 16 files changed, 759 insertions(+), 606 deletions(-) create mode 100644 web/app/routes/_index.tsx diff --git a/web/app/root.tsx b/web/app/root.tsx index d94fdf4..036e6ba 100644 --- a/web/app/root.tsx +++ b/web/app/root.tsx @@ -4,55 +4,55 @@ import { Providers } from "./providers"; import stylesheet from "./globals.css?url"; export const links: LinksFunction = () => [ - { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap", - }, - { rel: "stylesheet", href: stylesheet }, - { - rel: "icon", - href: "/icon-light-32x32.png", - media: "(prefers-color-scheme: light)", - }, - { - rel: "icon", - href: "/icon-dark-32x32.png", - media: "(prefers-color-scheme: dark)", - }, - { - rel: "icon", - href: "/icon.svg", - type: "image/svg+xml", - }, - { - rel: "apple-touch-icon", - href: "/apple-icon.png", - }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap", + }, + { rel: "stylesheet", href: stylesheet }, + { + rel: "icon", + href: "/icon-light-32x32.png", + media: "(prefers-color-scheme: light)", + }, + { + rel: "icon", + href: "/icon-dark-32x32.png", + media: "(prefers-color-scheme: dark)", + }, + { + rel: "icon", + href: "/icon.svg", + type: "image/svg+xml", + }, + { + rel: "apple-touch-icon", + href: "/apple-icon.png", + }, ]; export const meta: MetaFunction = () => [ - { title: "Webhook Dashboard" }, - { name: "description", content: "Manage webhook endpoints and deliveries" }, + { title: "Webhook Dashboard" }, + { name: "description", content: "Manage webhook endpoints and deliveries" }, ]; export function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - ); + return ( + + + + + + + + + {children} + + + + + ); } export default function App() { - return ; + return ; } diff --git a/web/app/routes/_index.tsx b/web/app/routes/_index.tsx new file mode 100644 index 0000000..0ca2915 --- /dev/null +++ b/web/app/routes/_index.tsx @@ -0,0 +1,5 @@ +import { DashboardLayout } from "@/components/dashboard-layout"; + +export default function Index() { + return ; +} diff --git a/web/components/drawers/delivery-drawer.tsx b/web/components/drawers/delivery-drawer.tsx index 9a33960..e69de29 100644 --- a/web/components/drawers/delivery-drawer.tsx +++ b/web/components/drawers/delivery-drawer.tsx @@ -1,114 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; -import { Separator } from "@/components/ui/separator"; - -interface DeliveryDrawerProps { - delivery: any; - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export function DeliveryDrawer({ - delivery, - open, - onOpenChange, -}: DeliveryDrawerProps) { - return ( - - - - Delivery Details - - -
-
-

- Event Information -

-
-
-

Event Name

-

{delivery.event}

-
-
-

Status Code

- - {delivery.status} - -
-
-

Duration

-

- {delivery.duration} -

-
-
-

Timestamp

-

- {delivery.timestamp} -

-
-
-
- - - -
-

- Request Payload -

-
-							{`{
-  "id": "evt_123456",
-  "event": "${delivery.event}",
-  "timestamp": "${delivery.timestamp}",
-  "data": {
-    "userId": "user_789",
-    "email": "user@example.com"
-  }
-}`}
-						
-
- -
-

- Response Payload -

-
-							{`{
-  "received": true,
-  "id": "evt_123456"
-}`}
-						
-
- - - -
-

- Retries -

-
-
-
-
-

- Attempt 1 - Success -

-

- 2025-12-08 14:32:00 -

-
-
-
-
-
- - - ); -} diff --git a/web/components/logs-list.tsx b/web/components/logs-list.tsx index bbd012b..97ea87d 100644 --- a/web/components/logs-list.tsx +++ b/web/components/logs-list.tsx @@ -1,99 +1,102 @@ -import { Badge } from "@/components/ui/badge"; +import { FileText } from "lucide-react"; import { Card } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Table, + TableBody, + TableCell, + TableHeader, + TableRow, +} from "@/components/ui/table"; const logs = [ { id: 1, level: "info", - timestamp: "2025-12-08 14:35:22", + timestamp: "14:35:22", message: "Webhook delivered successfully to user.created endpoint", }, { id: 2, level: "info", - timestamp: "2025-12-08 14:32:15", + timestamp: "14:32:15", message: "New webhook endpoint registered: Payment Notifications", }, { id: 3, level: "warn", - timestamp: "2025-12-08 14:28:42", + timestamp: "14:28:42", message: "Webhook delivery timeout - retrying in 60s", }, { id: 4, level: "error", - timestamp: "2025-12-08 14:25:30", + timestamp: "14:25:30", message: "Failed to deliver webhook after 3 retry attempts", }, { id: 5, level: "info", - timestamp: "2025-12-08 14:20:10", + timestamp: "14:20:10", message: "Webhook endpoint disabled due to repeated failures", }, ]; -const levelVariant = { - info: "secondary" as const, - warn: "outline" as const, - error: "destructive" as const, +const levelDot = { + info: "bg-muted-foreground/70", + warn: "bg-yellow-500/80", + error: "bg-red-500/80", }; -const levelColors = { - info: "bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-200", - warn: "bg-yellow-100 text-yellow-800 dark:bg-yellow-950 dark:text-yellow-200", - error: "", -}; - -const dotColors = { - info: "bg-blue-500", - warn: "bg-yellow-500", - error: "bg-red-500", -}; - -interface LogsListProps { - logLevel: string; -} - -export function LogsList({ logLevel }: LogsListProps) { - const filteredLogs = - logLevel === "all" ? logs : logs.filter((log) => log.level === logLevel); +export function LogsList() { + if (logs.length === 0) { + return ( + +
+ +

No logs

+

+ Logs will appear here as activity occurs. +

+
+
+ ); + } return ( - + -
- {filteredLogs.map((log) => ( -
-
-
-
-

{log.message}

-

- {log.timestamp} -

-
- - {log.level.toUpperCase()} - -
-
- ))} -
+ + + + {logs.map((log) => ( + + {/* Dot */} + + + + + {/* Time */} + + {log.timestamp} + + + {/* Message */} + + {log.message} + + + ))} + +
); diff --git a/web/components/modals/create-webhook-modal.tsx b/web/components/modals/create-webhook-modal.tsx index ec8ff17..e69de29 100644 --- a/web/components/modals/create-webhook-modal.tsx +++ b/web/components/modals/create-webhook-modal.tsx @@ -1,108 +0,0 @@ -import { useState } from "react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Checkbox } from "@/components/ui/checkbox"; -import { ScrollArea } from "@/components/ui/scroll-area"; - -interface CreateWebhookModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; -} - -const events = [ - "user.created", - "user.updated", - "user.deleted", - "order.created", - "order.completed", - "payment.processed", -]; - -export function CreateWebhookModal({ - open, - onOpenChange, -}: CreateWebhookModalProps) { - const [name, setName] = useState(""); - const [url, setUrl] = useState(""); - const [selectedEvents, setSelectedEvents] = useState([]); - - return ( - - - - Create Webhook - - Set up a new webhook endpoint to receive event notifications. - - - -
-
- - setName(e.target.value)} - placeholder="My Webhook" - /> -
- -
- - setUrl(e.target.value)} - placeholder="https://api.example.com/webhooks" - /> -
- -
- - -
- {events.map((event) => ( - - ))} -
-
-
-
- - - - - -
-
- ); -} diff --git a/web/components/pages/deliveries.tsx b/web/components/pages/deliveries.tsx index 6a4e82b..fdb9f7f 100644 --- a/web/components/pages/deliveries.tsx +++ b/web/components/pages/deliveries.tsx @@ -1,11 +1,7 @@ -import { useState } from "react"; import { DeliveriesTable } from "../deliveries-table"; -import { DeliveryDrawer } from "../drawers/delivery-drawer"; import { Card } from "@/components/ui/card"; export function DeliveriesPage() { - const [selectedDelivery, setSelectedDelivery] = useState(null); - return (
@@ -18,18 +14,8 @@ export function DeliveriesPage() {
- + {}} /> - - {selectedDelivery && ( - { - if (!open) setSelectedDelivery(null); - }} - /> - )}
); } diff --git a/web/components/pages/overview.tsx b/web/components/pages/overview.tsx index 881c4dc..351f766 100644 --- a/web/components/pages/overview.tsx +++ b/web/components/pages/overview.tsx @@ -1,14 +1,14 @@ -import { CheckCircle, Zap } from "lucide-react"; +import { + CheckCircle, + TrendingUp, + Zap, + Radio, + Activity, + Gauge, +} from "lucide-react"; import { StatCard } from "../stat-card"; import { RecentEventsTable } from "../recent-events-table"; -import { Progress } from "@/components/ui/progress"; -import { - Card, - CardAction, - CardContent, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; export function OverviewPage() { return ( @@ -27,93 +27,123 @@ export function OverviewPage() { value="22" change="+1" trend="up" - icon="✓" + icon={CheckCircle} + iconColor="#22c55e" />
- - - Endpoint Status - - - - - -
- Healthy - 20 + + +
+ + Endpoint Status
-
- Degraded - 2 +
+ 22
-
- Failing - 0 + + +
+
+
+
+
+ +
+
+
+ + Healthy +
+ 20 +
+ +
+
+ + Degraded +
+ 2 +
+ +
+
+ + Failing +
+ 0 +
- - - Performance - - - + + +
+ Performance +
- -
-
- - P50 Latency - - - 145ms - + +
+
+ P50{" "} + 145ms +
+
+
-
-
-
- - P95 Latency - - - 512ms - + +
+
+ P95 + 512ms +
+
+
-
-
-
- - P99 Latency - - - 1.2s - + +
+
+ P99 + 1.2s +
+
+
-
diff --git a/web/components/pages/settings.tsx b/web/components/pages/settings.tsx index cc2dec3..605298b 100644 --- a/web/components/pages/settings.tsx +++ b/web/components/pages/settings.tsx @@ -19,105 +19,122 @@ export function SettingsPage() { const handleCopy = (text: string, id: string) => { navigator.clipboard.writeText(text); setCopied(id); - setTimeout(() => setCopied(""), 2000); + setTimeout(() => setCopied(""), 1500); }; return ( -
+
-

- Settings -

-

- Configure webhook behavior and security +

Settings

+

+ Manage your webhook configuration and API access

- -
+
- - Signing Settings - - Secure your webhooks with signing keys + + + Signing Secret + + + Used to verify webhook payloads - -
- -
- - {showSecret - ? "whk_secret_abc123xyz" - : "••••••••••••••••••••"} - + +
+ + +
+ + + +
+ + {copied === "secret" && ( +

Copied

+ )}
-
- - API Keys - Manage API access credentials + + API Key + + Used for authenticating API requests + - -
- -
- - {showApiKey - ? "sk_live_51234567890abcdef" - : "••••••••••••••••••••"} - + +
+ + +
+ + +
+ {copied === "apikey" && ( -

Copied to clipboard

+

Copied

)}
@@ -125,20 +142,45 @@ export function SettingsPage() {
- - Delivery Configuration + + + Delivery Configuration + + + Control webhook retry behavior + - -
-
- - + + +
+
+ +
-
- - + +
+ +
+ +
+ +
diff --git a/web/components/pages/webhooks.tsx b/web/components/pages/webhooks.tsx index a103a0a..ff17e6c 100644 --- a/web/components/pages/webhooks.tsx +++ b/web/components/pages/webhooks.tsx @@ -1,14 +1,13 @@ -import { useState } from "react"; +import { Link } from "react-router"; import { Plus, Search } from "lucide-react"; +import { useState } from "react"; import { WebhookTable } from "../webhook-table"; -import { CreateWebhookModal } from "../modals/create-webhook-modal"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card } from "@/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; export function WebhooksPage() { - const [showModal, setShowModal] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); @@ -16,9 +15,7 @@ export function WebhooksPage() {

Hooky

-

- Easily create, monitor, and manage your webhooks -

+

current webhooks

@@ -32,9 +29,11 @@ export function WebhooksPage() { />
-
@@ -49,11 +48,6 @@ export function WebhooksPage() { - -
); } diff --git a/web/components/sidebar.tsx b/web/components/sidebar.tsx index 35dc975..7a7ea62 100644 --- a/web/components/sidebar.tsx +++ b/web/components/sidebar.tsx @@ -5,7 +5,6 @@ import { SidebarContent, SidebarGroup, SidebarGroupContent, - SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, @@ -30,22 +29,25 @@ export function AppSidebar(props: React.ComponentProps) { - -
- + +
+
+
- Webhooks - Dashboard + Hooky + + Dashboard +
+ - Navigation {navItems.map((item) => { @@ -53,16 +55,28 @@ export function AppSidebar(props: React.ComponentProps) { item.to === "/" ? pathname === "/" : pathname.startsWith(item.to); + return ( - - - {item.label} + + + {item.label} @@ -72,6 +86,7 @@ export function AppSidebar(props: React.ComponentProps) { + ); diff --git a/web/components/stat-card.tsx b/web/components/stat-card.tsx index 37e1ece..07f885b 100644 --- a/web/components/stat-card.tsx +++ b/web/components/stat-card.tsx @@ -6,7 +6,7 @@ interface StatCardProps { value: string; change: string; trend: "up" | "down"; - icon: string; + icon: React.ReactNode; } export function StatCard({ title, value, change, trend, icon }: StatCardProps) { diff --git a/web/components/top-nav.tsx b/web/components/top-nav.tsx index 6c708fd..ac2e202 100644 --- a/web/components/top-nav.tsx +++ b/web/components/top-nav.tsx @@ -1,14 +1,70 @@ -import { Moon, Sun } from "lucide-react"; +import { Moon, Sun, LogOut, User } from "lucide-react"; import { useTheme } from "next-themes"; import { useState, useEffect } from "react"; +import { useLocation, Link } from "react-router"; import { SidebarTrigger } from "@/components/ui/sidebar"; import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; + +const routeLabels: Record = { + "/": "Overview", + "/webhooks": "Webhooks", + "/webhooks/new": "Create Webhook", + "/deliveries": "Deliveries", + "/logs": "Logs", + "/settings": "Settings", +}; + +function getBreadcrumbs(pathname: string) { + if (pathname === "/") { + return [{ label: "Overview", to: "/" }]; + } + + const segments = pathname.split("/").filter(Boolean); + const crumbs: { label: string; to: string }[] = [ + { label: "Dashboard", to: "/" }, + ]; + + let currentPath = ""; + for (let i = 0; i < segments.length; i++) { + currentPath += `/${segments[i]}`; + const label = routeLabels[currentPath]; + if (label) { + crumbs.push({ label, to: currentPath }); + } else { + // Dynamic segment like /deliveries/:id + const parentPath = currentPath.substring(0, currentPath.lastIndexOf("/")); + const parentLabel = routeLabels[parentPath]; + if (parentLabel && !crumbs.some((c) => c.to === parentPath)) { + crumbs.push({ label: parentLabel, to: parentPath }); + } + crumbs.push({ label: `#${segments[i]}`, to: currentPath }); + } + } + + return crumbs; +} export function TopNav() { const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); + const location = useLocation(); useEffect(() => { setMounted(true); @@ -24,11 +80,35 @@ export function TopNav() { } }; + const currentLabel = routeLabels[location.pathname] ?? "Dashboard"; + const isRoot = location.pathname === "/"; + const crumbs = getBreadcrumbs(location.pathname); + return (
+ + + {crumbs.map((crumb, index) => { + const isLast = index === crumbs.length - 1; + return ( + + {index > 0 && } + {isLast ? ( + {crumb.label} + ) : ( + + {crumb.label} + + )} + + ); + })} + + +
@@ -42,9 +122,26 @@ export function TopNav() { Toggle theme )} - - A - + + + + + + + + Profile + + + + + Sign out + + +
); diff --git a/web/components/webhook-table.tsx b/web/components/webhook-table.tsx index 597f634..a918a11 100644 --- a/web/components/webhook-table.tsx +++ b/web/components/webhook-table.tsx @@ -1,5 +1,4 @@ -import { MoreVertical, Edit, Trash2 } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; +import { MoreVertical, Edit, Trash2, Webhook } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -48,57 +47,107 @@ const webhooks = [ ]; export function WebhookTable() { + if (webhooks.length === 0) { + return ( +
+ +

No webhooks

+

+ Create your first webhook endpoint to get started. +

+
+ ); + } + return ( - - Name - Target URL - Status - Created At - Actions + + + Name + + + Target + + + Status + + + Created + + + Actions + + {webhooks.map((webhook) => ( - - {webhook.name} - + + {/* Name */} + +
+ {webhook.name} +
+
+ + {/* URL */} + {webhook.url} - - - {webhook.status === "active" ? "● " : "○ "} - {webhook.status.charAt(0).toUpperCase() + - webhook.status.slice(1)} - + + {/* Status */} + +
+ + + {webhook.status} + +
- + + {/* Created */} + {webhook.createdAt} - + + {/* Actions */} + - + Edit - + Delete diff --git a/web/package.json b/web/package.json index 649b131..31f7c38 100644 --- a/web/package.json +++ b/web/package.json @@ -1,80 +1,80 @@ { - "name": "hooky-web", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "build": "react-router build", - "dev": "react-router dev", - "start": "react-router-serve ./build/server/index.js", - "lint": "eslint ." - }, - "dependencies": { - "@hookform/resolvers": "^3.10.0", - "@radix-ui/react-accordion": "1.2.2", - "@radix-ui/react-alert-dialog": "1.1.4", - "@radix-ui/react-aspect-ratio": "1.1.1", - "@radix-ui/react-avatar": "1.1.2", - "@radix-ui/react-checkbox": "1.1.3", - "@radix-ui/react-collapsible": "1.1.2", - "@radix-ui/react-context-menu": "2.2.4", - "@radix-ui/react-dialog": "1.1.4", - "@radix-ui/react-dropdown-menu": "2.1.4", - "@radix-ui/react-hover-card": "1.1.4", - "@radix-ui/react-label": "2.1.1", - "@radix-ui/react-menubar": "1.1.4", - "@radix-ui/react-navigation-menu": "1.2.3", - "@radix-ui/react-popover": "1.1.4", - "@radix-ui/react-progress": "1.1.1", - "@radix-ui/react-radio-group": "1.2.2", - "@radix-ui/react-scroll-area": "1.2.2", - "@radix-ui/react-select": "2.1.4", - "@radix-ui/react-separator": "1.1.1", - "@radix-ui/react-slider": "1.2.2", - "@radix-ui/react-slot": "latest", - "@radix-ui/react-switch": "1.1.2", - "@radix-ui/react-tabs": "1.1.2", - "@radix-ui/react-toast": "1.2.4", - "@radix-ui/react-toggle": "1.1.1", - "@radix-ui/react-toggle-group": "1.1.1", - "@radix-ui/react-tooltip": "1.1.6", - "@react-router/fs-routes": "^7.14.0", - "@react-router/node": "^7.0.0", - "@react-router/serve": "^7.0.0", - "autoprefixer": "^10.4.20", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "1.0.4", - "date-fns": "4.1.0", - "embla-carousel-react": "8.5.1", - "input-otp": "1.4.1", - "isbot": "^5.1.0", - "lucide-react": "^0.454.0", - "next-themes": "latest", - "react": "^18.3.1", - "react-day-picker": "9.8.0", - "react-dom": "^18.3.1", - "react-hook-form": "^7.60.0", - "react-resizable-panels": "^2.1.7", - "react-router": "^7.0.0", - "recharts": "latest", - "sonner": "^1.7.4", - "tailwind-merge": "^2.5.5", - "tailwindcss-animate": "^1.0.7", - "vaul": "^1.1.2", - "zod": "3.25.76" - }, - "devDependencies": { - "@react-router/dev": "^7.0.0", - "@tailwindcss/postcss": "^4.1.9", - "@types/node": "^22", - "@types/react": "^18", - "@types/react-dom": "^18", - "postcss": "^8.5", - "tailwindcss": "^4.1.9", - "tw-animate-css": "1.3.3", - "typescript": "^5", - "vite": "^5.4.0", - "vite-tsconfig-paths": "^4.3.0" - } -} \ No newline at end of file + "name": "hooky-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "react-router build", + "dev": "react-router dev", + "start": "react-router-serve ./build/server/index.js", + "lint": "eslint ." + }, + "dependencies": { + "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-accordion": "1.2.2", + "@radix-ui/react-alert-dialog": "1.1.4", + "@radix-ui/react-aspect-ratio": "1.1.1", + "@radix-ui/react-avatar": "1.1.2", + "@radix-ui/react-checkbox": "1.1.3", + "@radix-ui/react-collapsible": "1.1.2", + "@radix-ui/react-context-menu": "2.2.4", + "@radix-ui/react-dialog": "1.1.4", + "@radix-ui/react-dropdown-menu": "2.1.4", + "@radix-ui/react-hover-card": "1.1.4", + "@radix-ui/react-label": "2.1.1", + "@radix-ui/react-menubar": "1.1.4", + "@radix-ui/react-navigation-menu": "1.2.3", + "@radix-ui/react-popover": "1.1.4", + "@radix-ui/react-progress": "1.1.1", + "@radix-ui/react-radio-group": "1.2.2", + "@radix-ui/react-scroll-area": "1.2.2", + "@radix-ui/react-select": "2.1.4", + "@radix-ui/react-separator": "1.1.1", + "@radix-ui/react-slider": "1.2.2", + "@radix-ui/react-slot": "latest", + "@radix-ui/react-switch": "1.1.2", + "@radix-ui/react-tabs": "1.1.2", + "@radix-ui/react-toast": "1.2.4", + "@radix-ui/react-toggle": "1.1.1", + "@radix-ui/react-toggle-group": "1.1.1", + "@radix-ui/react-tooltip": "1.1.6", + "@react-router/fs-routes": "^7.14.0", + "@react-router/node": "^7.0.0", + "@react-router/serve": "^7.0.0", + "autoprefixer": "^10.4.20", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "1.0.4", + "date-fns": "4.1.0", + "embla-carousel-react": "8.5.1", + "input-otp": "1.4.1", + "isbot": "^5.1.0", + "lucide-react": "^0.454.0", + "next-themes": "latest", + "react": "^18.3.1", + "react-day-picker": "9.8.0", + "react-dom": "^18.3.1", + "react-hook-form": "^7.60.0", + "react-resizable-panels": "^2.1.7", + "react-router": "^7.0.0", + "recharts": "latest", + "sonner": "^1.7.4", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.2", + "zod": "3.25.76" + }, + "devDependencies": { + "@react-router/dev": "^7.0.0", + "@tailwindcss/postcss": "^4.1.9", + "@types/node": "^22", + "@types/react": "^18", + "@types/react-dom": "^18", + "postcss": "^8.5", + "tailwindcss": "^4.1.9", + "tw-animate-css": "1.3.3", + "typescript": "^5", + "vite": "^5.4.0", + "vite-tsconfig-paths": "^4.3.0" + } +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 0d4e6a4..25ce94c 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1468,6 +1468,12 @@ packages: '@tailwindcss/postcss@4.2.2': resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1682,6 +1688,10 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + data-uri-to-buffer@3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -2265,6 +2275,25 @@ packages: redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + remark-frontmatter@4.0.1: + resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} + + remark-mdx-frontmatter@1.1.1: + resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} + engines: {node: '>=12.2.0'} + + remark-mdx@2.3.0: + resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} + + remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} + + remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + + require-like@0.1.2: + resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -2360,6 +2389,21 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2384,6 +2428,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turbo-stream@2.4.1: + resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} + tw-animate-css@1.3.3: resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==} @@ -2396,6 +2443,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -2456,6 +2506,12 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + + vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} @@ -2509,6 +2565,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3750,6 +3809,12 @@ snapshots: postcss: 8.5.8 tailwindcss: 4.2.2 + '@types/acorn@4.0.6': + dependencies: + '@types/estree': 1.0.8 + + '@types/cookie@0.6.0': {} + '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3791,6 +3856,8 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/unist@2.0.11': {} + '@types/use-sync-external-store@0.0.6': {} accepts@1.3.8: @@ -3973,6 +4040,8 @@ snapshots: d3-timer@3.0.1: {} + data-uri-to-buffer@3.0.1: {} + date-fns-jalali@4.1.0-0: {} date-fns@4.1.0: {} @@ -4490,6 +4559,44 @@ snapshots: redux@5.0.1: {} + remark-frontmatter@4.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-frontmatter: 1.0.1 + micromark-extension-frontmatter: 1.1.1 + unified: 10.1.2 + + remark-mdx-frontmatter@1.1.1: + dependencies: + estree-util-is-identifier-name: 1.1.0 + estree-util-value-to-estree: 1.3.0 + js-yaml: 4.1.1 + toml: 3.0.0 + + remark-mdx@2.3.0: + dependencies: + mdast-util-mdx: 2.0.1 + micromark-extension-mdxjs: 1.0.1 + transitivePeerDependencies: + - supports-color + + remark-parse@10.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-rehype@10.1.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-to-hast: 12.3.0 + unified: 10.1.2 + + require-like@0.1.2: {} + reselect@5.1.1: {} rollup@4.60.1: @@ -4622,6 +4729,35 @@ snapshots: tapable@2.3.2: {} + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + tiny-invariant@1.3.3: {} tinyglobby@0.2.15: @@ -4637,6 +4773,8 @@ snapshots: tslib@2.8.1: {} + turbo-stream@2.4.1: {} + tw-animate-css@1.3.3: {} type-is@1.6.18: @@ -4646,6 +4784,8 @@ snapshots: typescript@5.9.3: {} + ufo@1.6.3: {} + undici-types@6.21.0: {} unpipe@1.0.0: {} @@ -4692,6 +4832,18 @@ snapshots: - '@types/react' - '@types/react-dom' + vfile-message@3.1.4: + dependencies: + '@types/unist': 2.0.11 + unist-util-stringify-position: 3.0.3 + + vfile@5.3.7: + dependencies: + '@types/unist': 2.0.11 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -4751,3 +4903,5 @@ snapshots: yallist@3.1.1: {} zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/web/vite.config.ts b/web/vite.config.ts index f910ad4..8c20dc0 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -3,5 +3,5 @@ import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [reactRouter(), tsconfigPaths()], + plugins: [reactRouter(), tsconfigPaths()], }); From 1b34429853d5a650f22830d29d9ba182e2aba6ea Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 7 Apr 2026 18:30:48 +0300 Subject: [PATCH 2/7] feat: upgrade to react router v7 from remix --- web/app/routes.ts | 7 ++- web/pnpm-lock.yaml | 154 --------------------------------------------- 2 files changed, 6 insertions(+), 155 deletions(-) diff --git a/web/app/routes.ts b/web/app/routes.ts index fb58729..160b18a 100644 --- a/web/app/routes.ts +++ b/web/app/routes.ts @@ -1,4 +1,9 @@ -import { type RouteConfig, layout, route, index } from "@react-router/dev/routes"; +import { + type RouteConfig, + layout, + route, + index, +} from "@react-router/dev/routes"; export default [ layout("routes/dashboard-layout.tsx", [ diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 25ce94c..0d4e6a4 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1468,12 +1468,6 @@ packages: '@tailwindcss/postcss@4.2.2': resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} - '@types/acorn@4.0.6': - resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1688,10 +1682,6 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} - data-uri-to-buffer@3.0.1: - resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} - engines: {node: '>= 6'} - date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -2275,25 +2265,6 @@ packages: redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - remark-frontmatter@4.0.1: - resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} - - remark-mdx-frontmatter@1.1.1: - resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} - engines: {node: '>=12.2.0'} - - remark-mdx@2.3.0: - resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} - - remark-parse@10.0.2: - resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} - - remark-rehype@10.1.0: - resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} - - require-like@0.1.2: - resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} - reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -2389,21 +2360,6 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2428,9 +2384,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-stream@2.4.1: - resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} - tw-animate-css@1.3.3: resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==} @@ -2443,9 +2396,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -2506,12 +2456,6 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} - - vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} - victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} @@ -2565,9 +2509,6 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3809,12 +3750,6 @@ snapshots: postcss: 8.5.8 tailwindcss: 4.2.2 - '@types/acorn@4.0.6': - dependencies: - '@types/estree': 1.0.8 - - '@types/cookie@0.6.0': {} - '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3856,8 +3791,6 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@types/unist@2.0.11': {} - '@types/use-sync-external-store@0.0.6': {} accepts@1.3.8: @@ -4040,8 +3973,6 @@ snapshots: d3-timer@3.0.1: {} - data-uri-to-buffer@3.0.1: {} - date-fns-jalali@4.1.0-0: {} date-fns@4.1.0: {} @@ -4559,44 +4490,6 @@ snapshots: redux@5.0.1: {} - remark-frontmatter@4.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-frontmatter: 1.0.1 - micromark-extension-frontmatter: 1.1.1 - unified: 10.1.2 - - remark-mdx-frontmatter@1.1.1: - dependencies: - estree-util-is-identifier-name: 1.1.0 - estree-util-value-to-estree: 1.3.0 - js-yaml: 4.1.1 - toml: 3.0.0 - - remark-mdx@2.3.0: - dependencies: - mdast-util-mdx: 2.0.1 - micromark-extension-mdxjs: 1.0.1 - transitivePeerDependencies: - - supports-color - - remark-parse@10.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - - remark-rehype@10.1.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-to-hast: 12.3.0 - unified: 10.1.2 - - require-like@0.1.2: {} - reselect@5.1.1: {} rollup@4.60.1: @@ -4729,35 +4622,6 @@ snapshots: tapable@2.3.2: {} - tar-fs@2.1.4: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.4 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - tiny-invariant@1.3.3: {} tinyglobby@0.2.15: @@ -4773,8 +4637,6 @@ snapshots: tslib@2.8.1: {} - turbo-stream@2.4.1: {} - tw-animate-css@1.3.3: {} type-is@1.6.18: @@ -4784,8 +4646,6 @@ snapshots: typescript@5.9.3: {} - ufo@1.6.3: {} - undici-types@6.21.0: {} unpipe@1.0.0: {} @@ -4832,18 +4692,6 @@ snapshots: - '@types/react' - '@types/react-dom' - vfile-message@3.1.4: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position: 3.0.3 - - vfile@5.3.7: - dependencies: - '@types/unist': 2.0.11 - is-buffer: 2.0.5 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -4903,5 +4751,3 @@ snapshots: yallist@3.1.1: {} zod@3.25.76: {} - - zwitch@2.0.4: {} From 9f077036e1dc23a15f2ee79821b6e2953ad06d62 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 7 Apr 2026 20:19:52 +0300 Subject: [PATCH 3/7] refactor: add shared routes for the dashboard and change --- web/app/routes/_index.tsx | 5 ----- web/vite.config.ts | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 web/app/routes/_index.tsx diff --git a/web/app/routes/_index.tsx b/web/app/routes/_index.tsx deleted file mode 100644 index 0ca2915..0000000 --- a/web/app/routes/_index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DashboardLayout } from "@/components/dashboard-layout"; - -export default function Index() { - return ; -} diff --git a/web/vite.config.ts b/web/vite.config.ts index 8c20dc0..fdfb2cc 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -3,5 +3,9 @@ import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ +<<<<<<< HEAD plugins: [reactRouter(), tsconfigPaths()], +======= + plugins: [reactRouter(), tsconfigPaths()], +>>>>>>> 3c2136e (refactor: add shared routes for the dashboard and change) }); From 4da0a4e127851f404279ddb145436d3efb6568a0 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Fri, 10 Apr 2026 10:50:28 +0300 Subject: [PATCH 4/7] feat: enhance dashboard ui for settings page, overview page and logs page --- web/app/globals.css | 194 ++++++++++++++------- web/components/deliveries-table.tsx | 124 ++++++++----- web/components/pages/settings.tsx | 6 +- web/components/recent-events-table.tsx | 230 ++++++++++++++++--------- web/components/stat-card.tsx | 28 +-- web/components/ui/badge.tsx | 78 +++++---- 6 files changed, 417 insertions(+), 243 deletions(-) diff --git a/web/app/globals.css b/web/app/globals.css index f087a05..ef21a84 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -5,87 +5,127 @@ :root { /* Updated to Linear's vibrant color palette */ - --background: oklch(0.98 0 0); - --foreground: oklch(0.2 0 0); + --background: oklch(0.9900 0 0); + --foreground: oklch(0 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0.2 0 0); - --popover: oklch(0.98 0 0); - --popover-foreground: oklch(0.2 0 0); - --primary: oklch(0.5 0.15 280); + --card-foreground: oklch(0 0 0); + --popover: oklch(0.9900 0 0); + --popover-foreground: oklch(0 0 0); + --primary: oklch(0 0 0); --primary-foreground: oklch(1 0 0); - --secondary: oklch(0.95 0 0); - --secondary-foreground: oklch(0.2 0 0); - --muted: oklch(0.92 0 0); - --muted-foreground: oklch(0.5 0 0); - --accent: oklch(0.57 0.18 260); - --accent-foreground: oklch(1 0 0); - --destructive: oklch(0.62 0.22 27); + --secondary: oklch(0.9400 0 0); + --secondary-foreground: oklch(0 0 0); + --muted: oklch(0.9700 0 0); + --muted-foreground: oklch(0.4400 0 0); + --accent: oklch(0.9400 0 0); + --accent-foreground: oklch(0 0 0); + --destructive: oklch(0.6300 0.1900 23.0300); --destructive-foreground: oklch(1 0 0); - --border: oklch(0.92 0 0); - --input: oklch(0.95 0 0); - --ring: oklch(0.57 0.18 260); - --chart-1: oklch(0.57 0.18 260); - --chart-2: oklch(0.65 0.15 280); - --chart-3: oklch(0.72 0.12 300); - --chart-4: oklch(0.45 0.16 250); - --chart-5: oklch(0.6 0.14 270); + --border: oklch(0.9200 0 0); + --input: oklch(0.9400 0 0); + --ring: oklch(0 0 0); + --chart-1: oklch(0.8100 0.1700 75.3500); + --chart-2: oklch(0.5500 0.2200 264.5300); + --chart-3: oklch(0.7200 0 0); + --chart-4: oklch(0.9200 0 0); + --chart-5: oklch(0.5600 0 0); --radius: 0.5rem; - --sidebar: oklch(0.96 0 0); - --sidebar-foreground: oklch(0.2 0 0); - --sidebar-primary: oklch(0.5 0.15 280); + --sidebar: oklch(0.9900 0 0); + --sidebar-foreground: oklch(0 0 0); + --sidebar-primary: oklch(0 0 0); --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.57 0.18 260); - --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(0.92 0 0); - --sidebar-ring: oklch(0.57 0.18 260); + --sidebar-accent: oklch(0.9400 0 0); + --sidebar-accent-foreground: oklch(0 0 0); + --sidebar-border: oklch(0.9400 0 0); + --sidebar-ring: oklch(0 0 0); --success: oklch(0.65 0.18 130); --success-foreground: oklch(1 0 0); --warning: oklch(0.75 0.15 60); --warning-foreground: oklch(0.2 0 0); + --font-sans: Geist, sans-serif; + --font-serif: Georgia, serif; + --font-mono: Geist Mono, monospace; + --shadow-color: hsl(0 0% 0%); + --shadow-opacity: 0.18; + --shadow-blur: 2px; + --shadow-spread: 0px; + --shadow-offset-x: 0px; + --shadow-offset-y: 1px; + --letter-spacing: 0em; + --spacing: 0.25rem; + --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); + --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); + --shadow-sm: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); + --shadow: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); + --shadow-md: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18); + --shadow-lg: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18); + --shadow-xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18); + --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45); + --tracking-normal: 0em; } .dark { - --background: oklch(0.1 0 0); - --foreground: oklch(0.95 0 0); - --card: oklch(0.15 0 0); - --card-foreground: oklch(0.95 0 0); - --popover: oklch(0.1 0 0); - --popover-foreground: oklch(0.95 0 0); - --primary: oklch(0.65 0.18 260); - --primary-foreground: oklch(0.1 0 0); - --secondary: oklch(0.22 0 0); - --secondary-foreground: oklch(0.95 0 0); - --muted: oklch(0.32 0 0); - --muted-foreground: oklch(0.65 0 0); - --accent: oklch(0.68 0.2 260); - --accent-foreground: oklch(0.1 0 0); - --destructive: oklch(0.62 0.22 27); - --destructive-foreground: oklch(0.95 0 0); - --border: oklch(0.22 0 0); - --input: oklch(0.18 0 0); - --ring: oklch(0.68 0.2 260); - --chart-1: oklch(0.68 0.2 260); - --chart-2: oklch(0.7 0.18 280); - --chart-3: oklch(0.75 0.16 300); - --chart-4: oklch(0.6 0.18 250); - --chart-5: oklch(0.7 0.16 270); - --sidebar: oklch(0.13 0 0); - --sidebar-foreground: oklch(0.95 0 0); - --sidebar-primary: oklch(0.68 0.2 260); - --sidebar-primary-foreground: oklch(0.1 0 0); - --sidebar-accent: oklch(0.32 0 0); - --sidebar-accent-foreground: oklch(0.95 0 0); - --sidebar-border: oklch(0.22 0 0); - --sidebar-ring: oklch(0.68 0.2 260); + --background: oklch(0 0 0); + --foreground: oklch(1 0 0); + --card: oklch(0.1400 0 0); + --card-foreground: oklch(1 0 0); + --popover: oklch(0.1800 0 0); + --popover-foreground: oklch(1 0 0); + --primary: oklch(1 0 0); + --primary-foreground: oklch(0 0 0); + --secondary: oklch(0.2500 0 0); + --secondary-foreground: oklch(1 0 0); + --muted: oklch(0.2300 0 0); + --muted-foreground: oklch(0.7200 0 0); + --accent: oklch(0.3200 0 0); + --accent-foreground: oklch(1 0 0); + --destructive: oklch(0.6900 0.2000 23.9100); + --destructive-foreground: oklch(0 0 0); + --border: oklch(0.2600 0 0); + --input: oklch(0.3200 0 0); + --ring: oklch(0.7200 0 0); + --chart-1: oklch(0.8100 0.1700 75.3500); + --chart-2: oklch(0.5800 0.2100 260.8400); + --chart-3: oklch(0.5600 0 0); + --chart-4: oklch(0.4400 0 0); + --chart-5: oklch(0.9200 0 0); + --sidebar: oklch(0.1800 0 0); + --sidebar-foreground: oklch(1 0 0); + --sidebar-primary: oklch(1 0 0); + --sidebar-primary-foreground: oklch(0 0 0); + --sidebar-accent: oklch(0.3200 0 0); + --sidebar-accent-foreground: oklch(1 0 0); + --sidebar-border: oklch(0.3200 0 0); + --sidebar-ring: oklch(0.7200 0 0); --success: oklch(0.7 0.2 130); --success-foreground: oklch(0.1 0 0); --warning: oklch(0.8 0.18 60); --warning-foreground: oklch(0.1 0 0); + --radius: 0.5rem; + --font-sans: Geist, sans-serif; + --font-serif: Georgia, serif; + --font-mono: Geist Mono, monospace; + --shadow-color: hsl(0 0% 0%); + --shadow-opacity: 0.18; + --shadow-blur: 2px; + --shadow-spread: 0px; + --shadow-offset-x: 0px; + --shadow-offset-y: 1px; + --letter-spacing: 0em; + --spacing: 0.25rem; + --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); + --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09); + --shadow-sm: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); + --shadow: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18); + --shadow-md: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18); + --shadow-lg: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18); + --shadow-xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18); + --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45); } @theme inline { - --font-sans: "Geist", "Geist Fallback"; - --font-mono: "Geist Mono", "Geist Mono Fallback"; + --font-sans: Geist, sans-serif; + --font-mono: Geist Mono, monospace; --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -126,6 +166,33 @@ --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); + --font-serif: Georgia, serif; + --radius: 0.5rem; + --tracking-tighter: calc(var(--tracking-normal) - 0.05em); + --tracking-tight: calc(var(--tracking-normal) - 0.025em); + --tracking-wide: calc(var(--tracking-normal) + 0.025em); + --tracking-wider: calc(var(--tracking-normal) + 0.05em); + --tracking-widest: calc(var(--tracking-normal) + 0.1em); + --tracking-normal: var(--tracking-normal); + --shadow-2xl: var(--shadow-2xl); + --shadow-xl: var(--shadow-xl); + --shadow-lg: var(--shadow-lg); + --shadow-md: var(--shadow-md); + --shadow: var(--shadow); + --shadow-sm: var(--shadow-sm); + --shadow-xs: var(--shadow-xs); + --shadow-2xs: var(--shadow-2xs); + --spacing: var(--spacing); + --letter-spacing: var(--letter-spacing); + --shadow-offset-y: var(--shadow-offset-y); + --shadow-offset-x: var(--shadow-offset-x); + --shadow-spread: var(--shadow-spread); + --shadow-blur: var(--shadow-blur); + --shadow-opacity: var(--shadow-opacity); + --color-shadow-color: var(--shadow-color); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); } @layer base { @@ -134,5 +201,6 @@ } body { @apply bg-background text-foreground; + letter-spacing: var(--tracking-normal); } -} +} \ No newline at end of file diff --git a/web/components/deliveries-table.tsx b/web/components/deliveries-table.tsx index 4623020..3c5f51b 100644 --- a/web/components/deliveries-table.tsx +++ b/web/components/deliveries-table.tsx @@ -1,4 +1,4 @@ -import { Badge } from "@/components/ui/badge"; +import { Inbox } from "lucide-react"; import { Table, TableBody, @@ -56,51 +56,85 @@ interface DeliveriesTableProps { } export function DeliveriesTable({ onRowClick }: DeliveriesTableProps) { + if (deliveries.length === 0) { + return ( +
+ +

No deliveries

+

+ Deliveries will appear here once webhooks start firing. +

+
+ ); + } + return ( -
- - - Event - HTTP Status - Duration - Timestamp - Result - - - - {deliveries.map((delivery) => ( - onRowClick(delivery)} - className="cursor-pointer" - > - {delivery.event} - - - {delivery.status} - - - - {delivery.duration} - - - {delivery.timestamp} - - - - {delivery.success ? "✓ Success" : "✗ Failed"} - - +
+
+ + + + Event + + + Status + + + Duration + + + Time + - ))} - -
+ + + + {deliveries.map((delivery) => { + const isError = delivery.status >= 400; + + return ( + onRowClick(delivery)} + className="cursor-pointer border-b last:border-0 hover:bg-muted/40 transition-colors" + > + {/* Event */} + + + {delivery.event} + + + + {/* HTTP Status */} + + + {delivery.status} + + + + {/* Duration */} + + {delivery.duration} + + + {/* Time */} + + {delivery.timestamp} + + + ); + })} + + +
); } diff --git a/web/components/pages/settings.tsx b/web/components/pages/settings.tsx index 605298b..1ff6141 100644 --- a/web/components/pages/settings.tsx +++ b/web/components/pages/settings.tsx @@ -56,7 +56,7 @@ export function SettingsPage() { />