From 41061462ff4d1916e95faf9cf2f53fdb00cb6d17 Mon Sep 17 00:00:00 2001 From: ghost2023 Date: Mon, 8 Jun 2026 16:05:03 +0300 Subject: [PATCH] feat(ui): Integrate Mantine UI library, introduce AppLayout, and refactor dashboard --- apps/edr-freight-web/portal/package.json | 2 + apps/edr-freight-web/portal/src/App.tsx | 46 +- .../portal/src/components/AppLayout.tsx | 427 +++++++++ apps/edr-freight-web/portal/src/main.tsx | 15 +- .../portal/src/pages/MyPortalPage.tsx | 809 +++++++++++------- .../portal/src/theme/mantine.ts | 147 ++++ pnpm-lock.yaml | 6 + 7 files changed, 1110 insertions(+), 342 deletions(-) create mode 100644 apps/edr-freight-web/portal/src/components/AppLayout.tsx create mode 100644 apps/edr-freight-web/portal/src/theme/mantine.ts diff --git a/apps/edr-freight-web/portal/package.json b/apps/edr-freight-web/portal/package.json index 582a2fc0b..ae79d7562 100644 --- a/apps/edr-freight-web/portal/package.json +++ b/apps/edr-freight-web/portal/package.json @@ -15,6 +15,8 @@ "@edr/types": "workspace:*", "@edr/ui-common": "workspace:*", "@hookform/resolvers": "^5.4.0", + "@mantine/core": "^9.3.0", + "@mantine/hooks": "^9.3.0", "@tanstack/react-query": "^5.59.0", "@tria-plc/iamui-common": "1.1.2", "axios": "^1.7.7", diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx index 36922cf4a..048c11945 100644 --- a/apps/edr-freight-web/portal/src/App.tsx +++ b/apps/edr-freight-web/portal/src/App.tsx @@ -3,10 +3,9 @@ import { useLocation, Routes, Route, - Navigate, Outlet, } from "react-router-dom"; -import { DashboardLayout, type SidebarItem } from "@edr/ui-common"; +import { AppLayout, type SidebarItem } from "@/components/AppLayout"; import { CalendarCheck, MapPin, @@ -39,12 +38,37 @@ import BillingPage from "./pages/billing/BillingPage"; import { useEffect } from "react"; const sidebarItems: SidebarItem[] = [ - { label: "Home", href: "/portal", icon: }, - { label: "My Bookings", href: "/bookings", icon: }, - { label: "Tracking", href: "/tracking", icon: }, - { label: "Billing", href: "/billing", icon: }, - { label: "Profile", href: "/profile", icon: }, - { label: "Settings", href: "/settings", icon: }, + { section: "Overview", label: "Home", href: "/portal", icon: }, + { + section: "Operations", + label: "My Bookings", + href: "/bookings", + icon: , + }, + { + section: "Operations", + label: "Tracking", + href: "/tracking", + icon: , + }, + { + section: "Operations", + label: "Billing", + href: "/billing", + icon: , + }, + { + section: "Account", + label: "Profile", + href: "/profile", + icon: , + }, + { + section: "Account", + label: "Settings", + href: "/settings", + icon: , + }, ]; const App = () => { @@ -65,7 +89,7 @@ const App = () => { if (user && location.pathname === "/") navigate("/portal"); else if (!customer && !!isInProtectedRoutes) navigate("/onboarding"); - }, [user, location, customer]); + }, [user, location, customer, customerQuery.isPending]); if (isPending) { return ( @@ -94,7 +118,7 @@ const App = () => { { onLogout={logout} > - + } > } /> diff --git a/apps/edr-freight-web/portal/src/components/AppLayout.tsx b/apps/edr-freight-web/portal/src/components/AppLayout.tsx new file mode 100644 index 000000000..fcc26ea4a --- /dev/null +++ b/apps/edr-freight-web/portal/src/components/AppLayout.tsx @@ -0,0 +1,427 @@ +import { Fragment, type ReactNode, useState } from "react"; +import { + ActionIcon, + AppShell, + Avatar, + Box, + Burger, + Divider, + Group, + Indicator, + Menu, + NavLink, + ScrollArea, + Stack, + Text, + UnstyledButton, +} from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { + Bell, + ChevronDown, + Languages, + LogOut, + Moon, + Sun, + Train, + User, +} from "lucide-react"; + +export interface SidebarItem { + label: string; + href: string; + icon?: ReactNode; + children?: SidebarItem[]; + /** Optional group heading; a small label is rendered when it changes. */ + section?: string; +} + +export interface AppLayoutProps { + title?: string; + sidebarItems: SidebarItem[]; + activeHref?: string; + onNavigate?: (href: string) => void; + enableThemeToggle?: boolean; + userName?: string; + userEmail?: string; + onLogout?: () => void; + children: ReactNode; +} + +type Theme = "light" | "dark"; +const THEME_KEY = "edr-theme"; + +function getStoredTheme(): Theme { + if (typeof window === "undefined") return "light"; + const stored = localStorage.getItem(THEME_KEY); + if (stored === "dark" || stored === "light") return stored; + return window.matchMedia?.("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +function getInitials(name: string): string { + return name + .split(" ") + .filter(Boolean) + .slice(0, 2) + .map((n) => n[0].toUpperCase()) + .join(""); +} + +function getActivePage( + items: SidebarItem[], + activePath: string, +): { label: string } | null { + const path = activePath.toLowerCase(); + for (const item of items) { + if ( + path === item.href.toLowerCase() || + path.startsWith(item.href.toLowerCase() + "/") + ) { + return { label: item.label }; + } + if (item.children) { + const childMatch = item.children.find( + (c) => + path === c.href.toLowerCase() || + path.startsWith(c.href.toLowerCase() + "/"), + ); + if (childMatch) return { label: childMatch.label }; + } + } + return null; +} + +// Shared NavLink styling — green tint only when active, quiet neutral otherwise. +const navLinkStyles = { + root: { + borderRadius: "var(--mantine-radius-md)", + fontWeight: 500, + }, + label: { fontSize: "var(--mantine-font-size-sm)" }, +} as const; + +export function AppLayout({ + title = "EDR Freight", + sidebarItems, + activeHref = "", + onNavigate, + enableThemeToggle = false, + userName = "User", + userEmail, + onLogout, + children, +}: AppLayoutProps) { + const [mobileOpen, { toggle: toggleMobile }] = useDisclosure(); + const [theme, setTheme] = useState(() => + enableThemeToggle ? getStoredTheme() : "light", + ); + + const activePath = activeHref.toLowerCase(); + const navigate = (href: string) => onNavigate?.(href); + + const toggleTheme = () => { + const next: Theme = theme === "dark" ? "light" : "dark"; + setTheme(next); + document.documentElement.classList.toggle("dark", next === "dark"); + localStorage.setItem(THEME_KEY, next); + }; + + const initials = getInitials(userName); + const activePage = getActivePage(sidebarItems, activePath); + + const isItemActive = (item: SidebarItem) => + activePath === item.href.toLowerCase() || + activePath.startsWith(item.href.toLowerCase() + "/"); + + return ( + + {/* ── Header ──────────────────────────────────────────────────────────── */} + + + + + + {activePage ? activePage.label : title} + + + + {/* Right: utility actions + user menu */} + + + + + + + + + + + + {enableThemeToggle && ( + + {theme === "dark" ? : } + + )} + + + + + + + + {initials} + + + + {userName} + + + + + + + + + + {userName} + + {userEmail && ( + + {userEmail} + + )} + + + } + onClick={() => navigate("/profile")} + > + Profile + + } + color="red" + onClick={onLogout} + > + Logout + + + + + + + + {/* ── Sidebar ─────────────────────────────────────────────────────────── */} + + {/* Brand */} + + + + + + + {title} + + + + + {/* Nav links */} + + + {sidebarItems.map((item, i) => { + const active = isItemActive(item); + const hasChildren = !!item.children?.length; + const childActive = + item.children?.some((c) => + activePath.startsWith(c.href.toLowerCase()), + ) ?? false; + + const prevSection = sidebarItems[i - 1]?.section; + const sectionLabel = + item.section && item.section !== prevSection ? ( + + {item.section} + + ) : null; + + if (hasChildren) { + return ( + + {sectionLabel} + + {item.children!.map((child) => { + const cActive = + activePath === child.href.toLowerCase(); + return ( + navigate(child.href)} + styles={navLinkStyles} + /> + ); + })} + + + ); + } + + return ( + + {sectionLabel} + navigate(item.href)} + styles={navLinkStyles} + /> + + ); + })} + + + + {/* Bottom user */} + + + + + {initials} + + + + + {userName} + + {userEmail && ( + + {userEmail} + + )} + + + + + + {/* ── Main ────────────────────────────────────────────────────────────── */} + + {children} + + + ); +} + +export default AppLayout; diff --git a/apps/edr-freight-web/portal/src/main.tsx b/apps/edr-freight-web/portal/src/main.tsx index 8c4fbeb96..56d6838df 100644 --- a/apps/edr-freight-web/portal/src/main.tsx +++ b/apps/edr-freight-web/portal/src/main.tsx @@ -2,9 +2,12 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { MantineProvider } from "@mantine/core"; +import "@mantine/core/styles.css"; import "@edr/ui-common/styles.css"; import "../index.css"; import "@edr/ui-common/theme.css"; +import { mantineTheme } from "./theme/mantine"; import App from "./App"; @@ -31,10 +34,12 @@ if (!rootElement) { createRoot(document.getElementById("root")!).render( - - - - - + + + + + + + , ); diff --git a/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx b/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx index b6b17e71f..1c97a13f9 100644 --- a/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/MyPortalPage.tsx @@ -2,42 +2,43 @@ import { useMemo, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { format } from "date-fns"; import { useQuery } from "@tanstack/react-query"; +import { + Anchor, + Badge, + Box, + Button, + Card, + Grid, + Group, + Progress, + RingProgress, + SimpleGrid, + Skeleton, + Stack, + Table, + Text, + ThemeIcon, + Title, +} from "@mantine/core"; import { ArrowRight, - Building2, - CheckCircle2, + ArrowUpRight, + CalendarDays, Clock, - DollarSign, - Eye, - LoaderCircle, - Mail, MapPin, - Package, - Phone, + Package2, Plus, Receipt, - Truck, + Train, UploadCloud, - X, } from "lucide-react"; -import { - getCurrentCustomer, - getMyInvoices, - getMyShipments, -} from "@/lib/currentCustomer"; +import { getMyInvoices, getMyShipments } from "@/lib/currentCustomer"; import { formatCurrency } from "@/pages/billing/invoices.mock"; import type { ShipmentStatus } from "@/pages/tracking/shipments.mock"; import type { InvoiceStatus } from "@/pages/billing/invoices.mock"; -import { - Button, - Card, - CardContent, - CardHeader, - CardTitle, - CardDescription, -} from "@edr/ui-common"; import { api } from "@/services/api"; +import useAuth from "@/hooks/useAuth"; const ACTIVE_STATUSES = [ "DRAFT", @@ -46,12 +47,36 @@ const ACTIVE_STATUSES = [ "IN_TRANSIT", ]; +const BOOKING_STATUS_META: Record = { + DRAFT: { label: "Draft", color: "gray" }, + SUBMITTED: { label: "Submitted", color: "blue" }, + PENDING_APPROVAL: { label: "Pending", color: "orange" }, + IN_TRANSIT: { label: "In Transit", color: "teal" }, + COMPLETED: { label: "Completed", color: "edr-green" }, + CANCELLED: { label: "Cancelled", color: "red" }, + REJECTED: { label: "Rejected", color: "red" }, +}; + +const SHIPMENT_STATUS_META: Record = { + "In Transit": { color: "teal" }, + Delivered: { color: "edr-green" }, + Delayed: { color: "red" }, +}; + +const INVOICE_STATUS_META: Record = { + Draft: { color: "gray" }, + Sent: { color: "blue" }, + Paid: { color: "edr-green" }, + Overdue: { color: "red" }, + Cancelled: { color: "gray" }, +}; + export default function MyPortalPage() { - const me = useMemo(() => getCurrentCustomer(), []); + const { user, customer } = useAuth(); const myShipments = useMemo(() => getMyShipments(), []); const myInvoices = useMemo(() => getMyInvoices(), []); - const navigate = useNavigate(); + const [dismissed, setDismissed] = useState(false); const bookingsQuery = useQuery( api.bookings.list.queryOptions({ @@ -71,337 +96,469 @@ export default function MyPortalPage() { const outstandingInvoices = myInvoices.filter( (inv) => inv.status === "Sent" || inv.status === "Overdue", ); - const [dismissed, setDismissed] = useState(false); const totalOutstanding = outstandingInvoices .filter((inv) => inv.currency === "USD") .reduce((sum, inv) => sum + inv.amount, 0); - const totalSpent = myInvoices - .filter((inv) => inv.status === "Paid" && inv.currency === "USD") - .reduce((sum, inv) => sum + inv.amount, 0); - const recentBookings = myBookings.slice(0, 5); - const recentInvoices = [...myInvoices].slice(0, 4); + const recentBookings = myBookings.slice(0, 6); + const recentInvoices = myInvoices.slice(0, 4); + const completedInvoices = myInvoices.filter( + (inv) => inv.status === "Paid", + ).length; + const invoiceTotal = myInvoices.length || 1; + const paidPct = Math.round((completedInvoices / invoiceTotal) * 100); + + const displayName = user?.name?.en ?? user?.username ?? user?.email ?? "—"; + const documentsComplete = !!(customer as any)?.documentsComplete; + const hasOutstanding = outstandingInvoices.length > 0; return ( -
-
- {/* Documents banner */} - {!me.documentsComplete && !dismissed && ( -
- -
-

Upload your documents

-

- To enable all account features, please upload your Business - License, TIN Certificate, and National ID / Passport. -

- + {/* ── Document setup notice ───────────────────────────────────── */} + {!documentsComplete && !dismissed && ( + + + + + + + + Finish setting up your account + + + Upload your Business License,{" "} + TIN Certificate, and{" "} + National ID / Passport to unlock all features. + + + + + + )} + + {/* ── Welcome (branded band) ──────────────────────────────────── */} + + {/* faint rail-line motif */} + + + + + Welcome back + + + {displayName} + + + + + + + {/* ── Stats Row ───────────────────────────────────────────────── */} + + } + color="blue" + /> + } + color="teal" + /> + } + color={hasOutstanding ? "red" : "edr-green"} + /> + } + color="edr-green" + ring={{ value: paidPct, color: "edr-green" }} + /> + + + {/* ── Main Grid: bookings + side panel ────────────────────────── */} + + {/* Recent Bookings (left, wider) */} + + + + + Recent Bookings + + Your latest freight requests + + +
- -
- )} + View all + + - {/* Welcome banner */} -
-
-
-
- {me.company.charAt(0)} -
-
-

Welcome back

-

{me.name}

-

- - {me.company} - · - - {me.customerType} - -

-
-
- -
- - - - - - -
-
-
- - {/* Active Shipments */} - - -
- Active Shipments - - Live tracking for your in-flight cargo - -
- - View all - - -
- - - {activeShipments.length === 0 ? ( -

- No shipments currently in transit. -

- ) : ( -
- {activeShipments.slice(0, 4).map((shipment) => ( -
-
- - {shipment.reference} - - -
-

- {shipment.originStation} - - {shipment.destinationStation} -

-
- - - {shipment.currentLocation} - - ETA {shipment.eta} -
-
-
-
-
+ {bookingsQuery.isPending ? ( + + {[1, 2, 3, 4].map((i) => ( + ))} -
- )} - - - - {/* Recent bookings */} - - -
- Recent Bookings - Your latest freight requests -
- - View all - - -
- - - {recentBookings.length === 0 ? ( -

- You haven't booked any freight yet. -

+ + ) : recentBookings.length === 0 ? ( + ) : ( -
- - - - - - - - - - - - {recentBookings.map((booking) => ( - navigate(`/bookings/${booking.id}`)} + +
ReferenceRouteCargoDateStatus
+ + + Reference + Route + Date + Status + + + + {recentBookings.map((booking) => { + const meta = BOOKING_STATUS_META[booking.status]; + return ( + navigate(`/bookings/${booking.id}`)} + > + + + {booking.reference} + + + + + {booking.originYard?.label ?? + booking.originYard?.code ?? + "—"} + {" → "} + {booking.destinationYard?.label ?? + booking.destinationYard?.code ?? + "—"} + + + + + {format( + new Date(booking.createdAt), + "MMM d, yyyy", + )} + + + + + {meta?.label ?? booking.status.replace(/_/g, " ")} + + + + ); + })} + +
+ + )} + + + + {/* Right side panel */} + + + {/* Active Shipments */} + + + + Shipments + + In-flight cargo + + + + + All + + + + + {activeShipments.length === 0 ? ( + + ) : ( + + {activeShipments.slice(0, 3).map((shipment) => ( + + ))} + + )} + + + {/* Invoice Summary */} + + + + Invoices + + {outstandingInvoices.length} outstanding + + + + + All + + + + + {recentInvoices.length === 0 ? ( + + ) : ( + + {recentInvoices.map((invoice) => { + const meta = INVOICE_STATUS_META[invoice.status]; + return ( + - - {booking.reference} - - - {booking.originYard?.label ?? booking.originYard?.code ?? "—"} → {booking.destinationYard?.label ?? booking.destinationYard?.code ?? "—"} - - - {booking.freightType === "CONTAINER" ? "Container" : booking.freightType} - - - {format(new Date(booking.createdAt), "MMM d, yyyy HH:mm")} - - - - - - ))} - - -
- )} -
-
- - {/* Invoices */} - - -
- Recent Invoices - - {outstandingInvoices.length} outstanding · {myInvoices.length}{" "} - total - -
- - View all - - -
- - - {recentInvoices.length === 0 ? ( -

- No invoices yet. -

- ) : ( -
- {recentInvoices.map((invoice) => ( -
-
- - -
-

- {formatCurrency(invoice.amount, invoice.currency)} -

-

- - Due {invoice.dueDate} -

-
- ))} -
- )} -
-
-
-
+ + + + + + + {formatCurrency(invoice.amount, invoice.currency)} + + + + + Due {invoice.dueDate} + + + + + + {invoice.status} + + + ); + })} + + )} + + + + + ); } -function ProfileRow({ - icon, +// ── Sub-components ──────────────────────────────────────────────────────────── + +function StatCard({ label, value, + sub, + icon, + color, + ring, }: { - icon: React.ReactNode; label: string; value: string; + sub?: string; + icon: React.ReactNode; + color: string; + ring?: { value: number; color: string }; }) { return ( -
-
{icon}
-
-

{label}

-

{value}

-
-
+ + + + + {label} + + + {value} + + {sub && ( + + {sub} + + )} + + {ring ? ( + + {ring.value}% + + } + /> + ) : ( + + {icon} + + )} + + ); } -function ShipmentBadge({ status }: { status: ShipmentStatus }) { - const styles: Record = { - "In Transit": "bg-muted text-foreground", - Delivered: "bg-primary/10 text-primary", - Delayed: "bg-destructive/10 text-destructive", - }; +function ShipmentCard({ + shipment, +}: { + shipment: ReturnType[number]; +}) { + const meta = SHIPMENT_STATUS_META[shipment.status as ShipmentStatus]; + const color = meta?.color ?? "gray"; return ( - - {status} - + + + {shipment.reference} + + + {shipment.status} + + + + {shipment.originStation} → {shipment.destinationStation} + + + + + + + {shipment.currentLocation} + + + + ETA {shipment.eta} + + + ); } -function BookingBadge({ status }: { status: string }) { - const styles: Record = { - DRAFT: "bg-amber-100 text-amber-700", - SUBMITTED: "bg-primary/10 text-primary", - PENDING_APPROVAL: "bg-muted text-foreground", - IN_TRANSIT: "bg-muted text-foreground", - COMPLETED: "bg-primary/10 text-primary", - CANCELLED: "bg-destructive/10 text-destructive", - REJECTED: "bg-destructive/10 text-destructive", - }; +function EmptyState({ message }: { message: string }) { return ( - - {status.replace(/_/g, " ")} - - ); -} - -function InvoiceBadge({ status }: { status: InvoiceStatus }) { - const styles: Record = { - Draft: "bg-muted text-muted-foreground", - Sent: "bg-primary/10 text-primary", - Paid: "bg-primary/10 text-primary", - Overdue: "bg-destructive/10 text-destructive", - Cancelled: "bg-amber-100 text-amber-700", - }; - return ( - - {status} - + + {message} + + ); } diff --git a/apps/edr-freight-web/portal/src/theme/mantine.ts b/apps/edr-freight-web/portal/src/theme/mantine.ts new file mode 100644 index 000000000..1a90fc0f8 --- /dev/null +++ b/apps/edr-freight-web/portal/src/theme/mantine.ts @@ -0,0 +1,147 @@ +import { createTheme, type MantineColorsTuple } from "@mantine/core"; + +// Brand accent — used sparingly: primary actions, active nav, key highlights. +const edrGreen: MantineColorsTuple = [ + "#ecfdf5", + "#d1fae5", + "#a7f3d0", + "#6ee7b7", + "#34d399", + "#10b981", + "#059669", + "#047857", + "#065f46", + "#064e3b", +]; + +// Neutral gray ramp tuned for clean, low-contrast surfaces (Stripe/Notion feel). +const neutral: MantineColorsTuple = [ + "#f8fafc", + "#f1f5f9", + "#e8edf2", + "#dbe2ea", + "#c2cbd6", + "#9aa6b4", + "#6b7785", + "#4b5563", + "#2f3742", + "#1c2129", +]; + +export const mantineTheme = createTheme({ + colors: { + "edr-green": edrGreen, + gray: neutral, + }, + primaryColor: "edr-green", + primaryShade: { light: 6, dark: 5 }, + + white: "#ffffff", + black: "#1c2129", + + fontFamily: + 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + + defaultRadius: "md", + + radius: { + xs: "4px", + sm: "6px", + md: "8px", + lg: "12px", + xl: "16px", + }, + + spacing: { + xs: "8px", + sm: "12px", + md: "16px", + lg: "24px", + xl: "32px", + }, + + fontSizes: { + xs: "12px", + sm: "13px", + md: "14px", + lg: "16px", + xl: "18px", + }, + + lineHeights: { + xs: "1.4", + sm: "1.45", + md: "1.55", + lg: "1.55", + xl: "1.5", + }, + + // Hierarchy comes from a strong, exponential size scale (~1.25 modular ratio), + // not from heavy weights. Big steps at the top, calm weights throughout. + // "Plus Jakarta Sans" gives headings a distinctive geometric character while + // body text stays on the neutral system stack. + headings: { + fontFamily: '"Plus Jakarta Sans", var(--mantine-font-family)', + fontWeight: "600", + sizes: { + h1: { fontSize: "40px", lineHeight: "1.1", fontWeight: "700" }, + h2: { fontSize: "30px", lineHeight: "1.2", fontWeight: "650" }, + h3: { fontSize: "23px", lineHeight: "1.3", fontWeight: "600" }, + h4: { fontSize: "18px", lineHeight: "1.4", fontWeight: "600" }, + h5: { fontSize: "15px", lineHeight: "1.45", fontWeight: "600" }, + h6: { fontSize: "13px", lineHeight: "1.45", fontWeight: "600" }, + }, + }, + + shadows: { + xs: "0 1px 2px rgba(16, 24, 40, 0.04)", + sm: "0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04)", + md: "0 4px 12px rgba(16, 24, 40, 0.06)", + }, + + components: { + Card: { + defaultProps: { + radius: "lg", + withBorder: true, + shadow: "none", + padding: "lg", + }, + }, + Button: { + defaultProps: { + radius: "md", + }, + styles: { + root: { fontWeight: 550 }, + }, + }, + Badge: { + defaultProps: { + radius: "sm", + variant: "light", + }, + styles: { + root: { fontWeight: 550, textTransform: "none" }, + }, + }, + Paper: { + defaultProps: { + radius: "lg", + shadow: "none", + withBorder: true, + }, + }, + Table: { + defaultProps: { + verticalSpacing: "sm", + horizontalSpacing: "md", + }, + }, + Title: { + styles: { + root: { letterSpacing: "-0.01em" }, + }, + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8071fdce3..1f411c920 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -299,6 +299,12 @@ importers: '@hookform/resolvers': specifier: ^5.4.0 version: 5.4.0(react-hook-form@7.76.0(react@19.2.6)) + '@mantine/core': + specifier: ^9.3.0 + version: 9.3.0(@mantine/hooks@9.3.0(react@19.2.6))(@types/react@18.3.29)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@mantine/hooks': + specifier: ^9.3.0 + version: 9.3.0(react@19.2.6) '@tanstack/react-query': specifier: ^5.59.0 version: 5.100.11(react@19.2.6)