style: update

This commit is contained in:
Nathnael
2026-06-10 07:27:42 +00:00
parent 02621d05d8
commit f423fc2e6f
8 changed files with 852 additions and 790 deletions

View File

@@ -1,2 +1,33 @@
@import "tailwindcss"; @import "tailwindcss";
@import "@edr/ui-common/theme.css" layer(theme); @import "@edr/ui-common/theme.css" layer(theme);
/* Bridge the central Mantine theme into Tailwind. Mantine (createTheme) is the
single source of truth; these just alias its generated CSS variables so
`bg-edr-*`, `text-edr-*`, `border-edr-*` utilities resolve to the same tokens. */
@theme {
--color-edr-primary: var(--mantine-color-edr-green-5);
--color-edr-primary-dark: var(--mantine-color-edr-green-7);
--color-edr-bg: var(--mantine-color-edr-bg-6);
--color-edr-card: var(--mantine-color-edr-card-6);
--color-edr-border: var(--mantine-color-edr-border-6);
--color-edr-divider: var(--mantine-color-edr-divider-6);
--color-edr-text: var(--mantine-color-edr-text-6);
--color-edr-muted: var(--mantine-color-edr-muted-6);
--color-edr-soft: var(--mantine-color-edr-soft-6);
--color-edr-ink: var(--mantine-color-edr-ink-6);
--color-edr-accent: var(--mantine-color-edr-accent-6);
--color-edr-amber-soft: var(--mantine-color-edr-amber-soft-6);
--color-edr-amber-text: var(--mantine-color-edr-amber-text-6);
--color-edr-blue: var(--mantine-color-edr-blue-6);
--color-edr-blue-soft: var(--mantine-color-edr-blue-soft-6);
--color-edr-blue-dot: var(--mantine-color-edr-blue-dot-6);
--color-edr-red: var(--mantine-color-edr-red-6);
--color-edr-red-soft: var(--mantine-color-edr-red-soft-6);
--color-edr-slate: var(--mantine-color-edr-slate-6);
--color-edr-slate-soft: var(--mantine-color-edr-slate-soft-6);
--color-edr-slate-soft2: var(--mantine-color-edr-slate-soft2-6);
--color-edr-step: var(--mantine-color-edr-step-6);
--color-edr-step-idle: var(--mantine-color-edr-step-idle-6);
--color-edr-conn-idle: var(--mantine-color-edr-conn-idle-6);
}

View File

@@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@600;700&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
</head> </head>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@@ -1,58 +1,55 @@
import {
useNavigate,
useLocation,
Routes,
Route,
Outlet,
} from "react-router-dom";
import { AppLayout, type SidebarItem } from "@/components/AppLayout"; import { AppLayout, type SidebarItem } from "@/components/AppLayout";
import { import {
CalendarCheck, CalendarCheck,
MapPin,
Receipt,
Home, Home,
Loader2, Loader2,
User, MapPin,
Receipt,
Settings, Settings,
User,
} from "lucide-react"; } from "lucide-react";
import {
Outlet,
Route,
Routes,
useLocation,
useNavigate,
} from "react-router-dom";
import useAuth from "./hooks/useAuth"; import useAuth from "./hooks/useAuth";
import { useEffect } from "react";
import EDRFreightLandingPage from "./pages/EDRFreightLandingPage";
import MyPortalPage from "./pages/MyPortalPage";
import ProfilePage from "./pages/ProfilePage"; import ProfilePage from "./pages/ProfilePage";
import SettingsPage from "./pages/SettingsPage"; import SettingsPage from "./pages/SettingsPage";
import MyPortalPage from "./pages/MyPortalPage";
import EDRFreightLandingPage from "./pages/EDRFreightLandingPage";
import SignupPage from "./pages/accounts/SignupPage";
import OnboardingPage from "./pages/accounts/OnboardingPage";
import VerificationOtpPage from "./pages/accounts/VerificationOtpPage";
import SetPasswordPage from "./pages/accounts/SetPasswordPage";
import LoginPage from "./pages/accounts/LoginPage"; import LoginPage from "./pages/accounts/LoginPage";
import MyBookings from "./pages/bookings/MyBookings"; import OnboardingPage from "./pages/accounts/OnboardingPage";
import SetPasswordPage from "./pages/accounts/SetPasswordPage";
import SignupPage from "./pages/accounts/SignupPage";
import VerificationOtpPage from "./pages/accounts/VerificationOtpPage";
import BillingPage from "./pages/billing/BillingPage";
import BookingContractPage from "./pages/bookings/BookingContractPage"; import BookingContractPage from "./pages/bookings/BookingContractPage";
import BookingDetailPage from "./pages/bookings/BookingDetailPage"; import BookingDetailPage from "./pages/bookings/BookingDetailPage";
import NewBookingPage from "./pages/bookings/NewBookingPage";
import EditBookingPage from "./pages/bookings/EditBookingPage"; import EditBookingPage from "./pages/bookings/EditBookingPage";
import MyBookings from "./pages/bookings/MyBookings";
import NewBookingPage from "./pages/bookings/NewBookingPage";
import CheckPaymentPage from "./pages/payments/CheckPaymentPage"; import CheckPaymentPage from "./pages/payments/CheckPaymentPage";
import TrackingPage from "./pages/tracking/TrackingPage"; import TrackingPage from "./pages/tracking/TrackingPage";
import BillingPage from "./pages/billing/BillingPage";
import { useEffect } from "react";
const sidebarItems: SidebarItem[] = [ const sidebarItems: SidebarItem[] = [
{ section: "Overview", label: "Home", href: "/portal", icon: <Home size={18} /> }, { label: "Home", href: "/portal", icon: <Home size={18} /> },
{ {
section: "Operations",
label: "My Bookings", label: "My Bookings",
href: "/bookings", href: "/bookings",
icon: <CalendarCheck size={18} />, icon: <CalendarCheck size={18} />,
}, },
{ {
section: "Operations",
label: "Tracking", label: "Tracking",
href: "/tracking", href: "/tracking",
icon: <MapPin size={18} />, icon: <MapPin size={18} />,
}, },
{ {
section: "Operations",
label: "Billing", label: "Billing",
href: "/billing", href: "/billing",
icon: <Receipt size={18} />, icon: <Receipt size={18} />,

View File

@@ -1,13 +1,9 @@
import { Fragment, type ReactNode, useState } from "react";
import { import {
ActionIcon,
AppShell, AppShell,
Avatar, Avatar,
Box, Box,
Burger,
Divider, Divider,
Group, Group,
Indicator,
Menu, Menu,
NavLink, NavLink,
ScrollArea, ScrollArea,
@@ -19,21 +15,20 @@ import { useDisclosure } from "@mantine/hooks";
import { import {
Bell, Bell,
ChevronDown, ChevronDown,
ChevronRight,
Languages,
LogOut, LogOut,
Menu as MenuIcon,
Moon, Moon,
Search,
Sun, Sun,
Train,
User, User,
} from "lucide-react"; } from "lucide-react";
import { type CSSProperties, Fragment, type ReactNode, useState } from "react";
export interface SidebarItem { export interface SidebarItem {
label: string; label: string;
href: string; href: string;
icon?: ReactNode; icon?: ReactNode;
children?: SidebarItem[]; children?: SidebarItem[];
/** Optional group heading; a small label is rendered when it changes. */
section?: string; section?: string;
} }
@@ -52,13 +47,23 @@ export interface AppLayoutProps {
type Theme = "light" | "dark"; type Theme = "light" | "dark";
const THEME_KEY = "edr-theme"; const THEME_KEY = "edr-theme";
const EDR = {
primary: "#0EA371",
primaryDark: "#0A6F4D",
soft: "#ECF6F1",
border: "#E6ECF2",
bg: "#F4F7FA",
text: "#10202F",
muted: "#6B7C8E",
ink: "#0C1A2B",
accent: "#F2A516",
};
function getStoredTheme(): Theme { function getStoredTheme(): Theme {
if (typeof window === "undefined") return "light"; if (typeof window === "undefined") return "light";
const stored = localStorage.getItem(THEME_KEY); const stored = localStorage.getItem(THEME_KEY);
if (stored === "dark" || stored === "light") return stored; if (stored === "dark" || stored === "light") return stored;
return window.matchMedia?.("(prefers-color-scheme: dark)").matches return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
? "dark"
: "light";
} }
function getInitials(name: string): string { function getInitials(name: string): string {
@@ -70,23 +75,15 @@ function getInitials(name: string): string {
.join(""); .join("");
} }
function getActivePage( function getActivePage(items: SidebarItem[], activePath: string): { label: string } | null {
items: SidebarItem[],
activePath: string,
): { label: string } | null {
const path = activePath.toLowerCase(); const path = activePath.toLowerCase();
for (const item of items) { for (const item of items) {
if ( if (path === item.href.toLowerCase() || path.startsWith(item.href.toLowerCase() + "/")) {
path === item.href.toLowerCase() ||
path.startsWith(item.href.toLowerCase() + "/")
) {
return { label: item.label }; return { label: item.label };
} }
if (item.children) { if (item.children) {
const childMatch = item.children.find( const childMatch = item.children.find(
(c) => (c) => path === c.href.toLowerCase() || path.startsWith(c.href.toLowerCase() + "/"),
path === c.href.toLowerCase() ||
path.startsWith(c.href.toLowerCase() + "/"),
); );
if (childMatch) return { label: childMatch.label }; if (childMatch) return { label: childMatch.label };
} }
@@ -94,22 +91,18 @@ function getActivePage(
return null; return null;
} }
// NavLink classNames for the dark sidebar — Tailwind utilities (with v4 `!`
// important suffix) override Mantine's default active styling.
const navClassNames = (active: boolean) => { const navClassNames = (active: boolean) => {
const base =
"rounded-[10px] font-medium transition-all duration-150 active:scale-[0.98]";
if (active) { if (active) {
return { return {
root: `${base} bg-gradient-to-br! from-emerald-600! to-emerald-500! text-white! shadow-[0_2px_8px_-4px_rgba(16,185,129,0.45)]`, root: `rounded-[10px] font-medium transition-all duration-150 bg-[#ECF6F1]! ring-1 ring-inset ring-[#0EA371]/15`,
label: "text-white!", label: `text-[#0A6F4D]! font-bold!`,
section: "text-white!", section: `text-[#0A6F4D]!`,
}; };
} }
return { return {
root: `${base} text-white/60! hover:bg-white/[0.07]! hover:text-white!`, root: `rounded-[10px] font-medium transition-all duration-150 hover:bg-[#F1F4F7]!`,
label: "text-inherit!", label: `text-[#3D4D5C]! font-semibold! hover:text-[#0C1A2B]!`,
section: "text-inherit! opacity-90", section: `text-[#54657A]! hover:text-[#0C1A2B]!`,
}; };
}; };
@@ -146,153 +139,142 @@ export function AppLayout({
activePath === item.href.toLowerCase() || activePath === item.href.toLowerCase() ||
activePath.startsWith(item.href.toLowerCase() + "/"); activePath.startsWith(item.href.toLowerCase() + "/");
// Shared header "island" styles — every control is a consistent 36px chip.
const islandStyle: CSSProperties = {
width: 36,
height: 36,
borderRadius: 999,
border: `1px solid ${EDR.border}`,
backgroundColor: "#fff",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
cursor: "pointer",
};
const toggleStyle: CSSProperties = { ...islandStyle, borderRadius: 10 };
return ( return (
<AppShell <AppShell
layout="alt" layout="alt"
navbar={{ navbar={{ width: 260, breakpoint: "sm", collapsed: { mobile: !mobileOpen } }}
width: 264, header={{ height: 56 }}
breakpoint: "sm",
collapsed: { mobile: !mobileOpen },
}}
header={{ height: 60 }}
padding={0} padding={0}
> >
{/* ── Header ──────────────────────────────────────────────────────────── */} {/* ── Header (frosted glass; compact floating islands, mirrors the pen) ── */}
<AppShell.Header <AppShell.Header
withBorder={false} withBorder={false}
className="border-b border-[var(--mantine-color-gray-2)]! bg-white/70! backdrop-blur-md backdrop-saturate-150" style={{
backdropFilter: "blur(14px)",
WebkitBackdropFilter: "blur(14px)",
border: "none",
boxShadow: "none",
background:'transparent',
}}
> >
<Group h="100%" px="lg" justify="space-between"> <Group h="100%" px={24} justify="space-between" wrap="nowrap">
<Group gap="sm"> {/* Left: sidebar toggle + page title */}
<Burger <Group gap={12} wrap="nowrap" align="center">
opened={mobileOpen} <UnstyledButton onClick={toggleMobile} style={toggleStyle} aria-label="Toggle sidebar">
onClick={toggleMobile} <MenuIcon size={18} color={EDR.text} strokeWidth={1.8} />
hiddenFrom="sm" </UnstyledButton>
size="sm" <Text
/> style={{
<Group gap={6} wrap="nowrap"> fontFamily: '"Inter", sans-serif',
<Text fontSize: 16,
size="sm" fontWeight: 700,
className="font-medium text-[var(--mantine-color-gray-5)]" color: EDR.text,
visibleFrom="xs" lineHeight: 1,
> }}
{title} >
</Text> {activePage ? activePage.label : title}
<Box visibleFrom="xs" className="flex"> </Text>
<ChevronRight size={14} color="var(--mantine-color-gray-4)" />
</Box>
<Text
size="md"
className="font-semibold tracking-tight text-[var(--mantine-color-gray-8)]"
>
{activePage ? activePage.label : title}
</Text>
</Group>
</Group> </Group>
{/* Right: utility actions + user menu */} {/* Right: search + bell + avatar */}
<Group gap={4}> <Group gap={10} wrap="nowrap" align="center">
<ActionIcon {/* Search pill */}
variant="subtle" <Group
color="gray" gap={8}
size="lg" align="center"
radius="md" visibleFrom="sm"
className="transition-transform hover:-translate-y-px" style={{
aria-label="Change language" width: 260,
height: 36,
borderRadius: 999,
border: `1px solid ${EDR.border}`,
backgroundColor: "#fff",
padding: "0 14px",
cursor: "text",
}}
> >
<Languages size={18} /> <Search size={15} color={EDR.muted} strokeWidth={1.8} />
</ActionIcon> <Text size="sm" style={{ color: EDR.muted, userSelect: "none" }}>
Search shipments, bookings
</Text>
</Group>
<Indicator color="red" size={7} offset={6} zIndex={10} withBorder> {/* Bell */}
<ActionIcon <Box style={{ position: "relative" }}>
variant="subtle" <UnstyledButton style={islandStyle} aria-label="Notifications">
color="gray" <Bell size={17} color={EDR.text} strokeWidth={1.8} />
size="lg" </UnstyledButton>
radius="md" <Box
className="transition-transform hover:-translate-y-px" style={{
aria-label="Notifications" position: "absolute",
> top: 7,
<Bell size={18} /> right: 7,
</ActionIcon> width: 7,
</Indicator> height: 7,
borderRadius: "50%",
backgroundColor: EDR.accent,
border: "1.5px solid #fff",
pointerEvents: "none",
}}
/>
</Box>
{enableThemeToggle && ( {enableThemeToggle && (
<ActionIcon <UnstyledButton onClick={toggleTheme} style={islandStyle} aria-label="Toggle theme">
variant="subtle" {theme === "dark"
color="gray" ? <Sun size={17} color={EDR.text} strokeWidth={1.8} />
size="lg" : <Moon size={17} color={EDR.text} strokeWidth={1.8} />}
radius="md" </UnstyledButton>
className="transition-transform hover:-translate-y-px"
onClick={toggleTheme}
aria-label="Toggle theme"
>
{theme === "dark" ? <Sun size={18} /> : <Moon size={18} />}
</ActionIcon>
)} )}
<Divider orientation="vertical" my={14} mx={6} /> {/* Avatar pill */}
<Menu width={220} position="bottom-end" withinPortal shadow="md" offset={8} radius="md">
<Menu
width={232}
position="bottom-end"
withinPortal
shadow="md"
offset={8}
radius="md"
>
<Menu.Target> <Menu.Target>
<UnstyledButton <UnstyledButton
px={6} style={{
py={4} height: 36,
className="rounded-[10px] transition-colors hover:bg-[var(--mantine-color-gray-1)]" borderRadius: 999,
border: `1px solid ${EDR.border}`,
backgroundColor: "#fff",
padding: "0 10px 0 4px",
display: "flex",
alignItems: "center",
gap: 7,
cursor: "pointer",
}}
> >
<Group gap={8} wrap="nowrap"> <Avatar radius="xl" size={28} color="edr-green.5">
<Avatar <Text fw={700} fz={12} c="white">{initials}</Text>
radius="xl" </Avatar>
size={32} <ChevronDown size={15} color={EDR.muted} strokeWidth={1.8} />
gradient={{ from: "edr-green.5", to: "edr-green.8", deg: 135 }}
variant="gradient"
>
<Text fw={600} fz={11} c="white">
{initials}
</Text>
</Avatar>
<Box visibleFrom="sm" className="min-w-0">
<Text size="sm" fw={600} maw={120} truncate lh={1.2}>
{userName}
</Text>
<Text size="xs" c="dimmed" lh={1.2}>
Customer
</Text>
</Box>
<ChevronDown size={14} color="var(--mantine-color-gray-5)" />
</Group>
</UnstyledButton> </UnstyledButton>
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
<Box px="sm" py="xs"> <Box px="sm" py="xs">
<Text size="sm" fw={600} truncate> <Text size="sm" fw={600} truncate style={{ color: EDR.text }}>{userName}</Text>
{userName} {userEmail && <Text size="xs" c="dimmed" truncate>{userEmail}</Text>}
</Text>
{userEmail && (
<Text size="xs" c="dimmed" truncate>
{userEmail}
</Text>
)}
</Box> </Box>
<Divider /> <Divider />
<Menu.Item <Menu.Item leftSection={<User size={15} />} onClick={() => navigate("/profile")}>
leftSection={<User size={15} />}
onClick={() => navigate("/profile")}
>
Profile Profile
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item leftSection={<LogOut size={15} />} color="red" onClick={onLogout}>
leftSection={<LogOut size={15} />}
color="red"
onClick={onLogout}
>
Logout Logout
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
@@ -301,38 +283,67 @@ export function AppLayout({
</Group> </Group>
</AppShell.Header> </AppShell.Header>
{/* ── Sidebar ─────────────────────────────────────────────────────────── */} {/* ── Sidebar ── */}
<AppShell.Navbar <AppShell.Navbar
withBorder={false} withBorder={false}
className="flex flex-col border-r border-white/[0.06]! bg-gradient-to-b from-[#1a2230] to-[#141a26]" style={{
backgroundColor: "#ffffff",
borderRight: `1px solid ${EDR.border}`,
display: "flex",
flexDirection: "column",
}}
> >
{/* Brand */} {/* Brand */}
<Box className="flex h-[60px] flex-shrink-0 items-center border-b border-white/[0.07] px-4"> <Box
<Group gap="sm" wrap="nowrap"> style={{
<Box className="flex h-[34px] w-[34px] flex-shrink-0 items-center justify-center rounded-[10px] bg-gradient-to-br from-emerald-500 to-emerald-700 shadow-[inset_0_1px_0_rgba(255,255,255,0.2)]"> height: 60,
<Train size={18} color="white" strokeWidth={2.2} /> flexShrink: 0,
</Box> display: "flex",
alignItems: "center",
padding: "0 18px",
}}
>
<Group gap={11} wrap="nowrap">
<img
src="/assets/edr-logo.png"
alt="EDR"
style={{ width: 40, height: 40, objectFit: "contain", flexShrink: 0 }}
/>
<Box> <Box>
<Text className="font-bold leading-tight tracking-tight text-white" size="md"> <Text
{title} style={{
fontFamily: '"Inter", sans-serif',
fontWeight: 800,
fontSize: 19,
letterSpacing: "0.03em",
color: EDR.primary,
lineHeight: 1.15,
}}
>
EDR FREIGHT
</Text> </Text>
<Text className="text-[10px] font-semibold uppercase tracking-[0.12em] text-white/40"> <Text
Logistics Portal style={{
fontSize: 10.5,
fontWeight: 500,
color: EDR.muted,
lineHeight: 1.2,
}}
>
EthioDjibouti Railway
</Text> </Text>
</Box> </Box>
</Group> </Group>
</Box> </Box>
{/* Nav links */} {/* Nav */}
<ScrollArea flex={1} type="never" p="sm"> <ScrollArea flex={1} type="never" p="sm">
<Stack gap={3}> <Stack gap={3}>
{sidebarItems.map((item, i) => { {sidebarItems.map((item, i) => {
const active = isItemActive(item); const active = isItemActive(item);
const hasChildren = !!item.children?.length; const hasChildren = !!item.children?.length;
const childActive = const childActive =
item.children?.some((c) => item.children?.some((c) => activePath.startsWith(c.href.toLowerCase())) ?? false;
activePath.startsWith(c.href.toLowerCase()),
) ?? false;
const prevSection = sidebarItems[i - 1]?.section; const prevSection = sidebarItems[i - 1]?.section;
const sectionLabel = const sectionLabel =
@@ -343,8 +354,13 @@ export function AppLayout({
tt="uppercase" tt="uppercase"
px="sm" px="sm"
mt={i === 0 ? 4 : "md"} mt={i === 0 ? 4 : "md"}
mb={6} mb={4}
className="font-bold tracking-[0.1em] text-white/30" style={{
fontWeight: 600,
letterSpacing: "0.08em",
color: EDR.muted,
fontSize: 11,
}}
> >
{item.section} {item.section}
</Text> </Text>
@@ -394,35 +410,118 @@ export function AppLayout({
</Stack> </Stack>
</ScrollArea> </ScrollArea>
{/* Bottom user */} {/* Promo card */}
<Box className="m-3 flex-shrink-0 rounded-xl border border-white/[0.07] bg-white/[0.04] p-2.5"> <Box style={{ padding: "0 12px 12px" }}>
<Group gap="sm" wrap="nowrap"> <Box
style={{
borderRadius: 16,
overflow: "hidden",
position: "relative",
height: 260,
marginBottom: 10,
}}
>
{/* Train image fills the full card */}
<img
src="/assets/train-edr.jpg"
alt=""
style={{
position: "absolute",
bottom:"-16px",
left:0,
right:"-70px",
width: "120%",
objectFit: "cover",
}}
/>
{/* Diagonal green overlay: lower-left → upper-right cut */}
<Box
style={{
position: "absolute",
inset: 0,
top:"-75%",
background: "linear-gradient(165deg, #006149 40%, #0DC9A4 70%, #0DC9A402 80%)",
clipPath: "polygon(0 0, 100% 0, 100% 58%, 0 72%)",
}}
/>
{/* Text sits on top of the green overlay */}
<Box style={{ position: "relative", zIndex: 1, padding: "16px 16px 0" }} className="max-w-[180px]">
<Text style={{ fontSize: 15, fontWeight: 800, color: "#fff", lineHeight: 1.3, marginBottom: 4 }}>
Moving Africa Forward
</Text>
<Text style={{ fontSize: 11, color: "rgba(255,255,255,0.7)", lineHeight: 1.5 }}>
Reliable. Efficient. Connected.
</Text>
</Box>
{/* Learn More — visible on the image area */}
<Box
style={{
position: "absolute",
bottom: 10,
right: 10,
zIndex: 2,
display: "inline-flex",
alignItems: "center",
gap: 5,
backgroundColor: "#fff",
borderRadius: 8,
padding: "6px 12px",
cursor: "pointer",
}}
>
<Text style={{ fontSize: 12, fontWeight: 600, color: EDR.primaryDark }}>Learn More</Text>
<span style={{ fontSize: 12, color: EDR.primaryDark }}></span>
</Box>
</Box>
{/* Profile */}
<Box
style={{
borderRadius: 12,
border: `1px solid ${EDR.border}`,
padding: "10px",
display: "flex",
alignItems: "center",
gap: 10,
cursor: "pointer",
}}
>
<Avatar <Avatar
radius="xl" radius="xl"
size={34} size={38}
gradient={{ from: "edr-green.5", to: "edr-green.8", deg: 135 }} color="edr-green.5"
variant="gradient" style={{ flexShrink: 0 }}
> >
<Text fw={600} fz={12} c="white"> <Text fw={600} fz={13} c="white">{initials}</Text>
{initials}
</Text>
</Avatar> </Avatar>
<Box className="min-w-0"> <Box style={{ minWidth: 0, flex: 1 }}>
<Text size="sm" fw={600} truncate className="text-white"> <Text size="sm" fw={600} truncate style={{ color: EDR.text, lineHeight: 1.3 }}>
{userName} {userName}
</Text> </Text>
{userEmail && ( {userEmail && (
<Text size="xs" truncate className="text-white/40"> <Text size="xs" truncate style={{ color: EDR.muted, lineHeight: 1.3 }}>
{userEmail} {userEmail}
</Text> </Text>
)} )}
</Box> </Box>
</Group> <ChevronDown size={16} color={EDR.muted} style={{ flexShrink: 0 }} />
</Box>
</Box> </Box>
</AppShell.Navbar> </AppShell.Navbar>
{/* ── Main ────────────────────────────────────────────────────────────── */} {/* ── Main ── */}
<AppShell.Main className="bg-[var(--mantine-color-gray-0)]"> <AppShell.Main
style={{
backgroundColor: EDR.bg,
backgroundImage:
"radial-gradient(58% 42% at 100% 0%, rgba(14,163,113,0.16) 0%, rgba(14,163,113,0.06) 38%, rgba(14,163,113,0) 72%)",
backgroundRepeat: "no-repeat",
backgroundAttachment: "fixed",
}}
>
{children} {children}
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,71 @@
import { createTheme, type MantineColorsTuple } from "@mantine/core"; import { colorsTuple, createTheme, type MantineColorsTuple } from "@mantine/core";
// Brand accent — used sparingly: primary actions, active nav, key highlights.
const edrGreen: MantineColorsTuple = [ const edrGreen: MantineColorsTuple = [
"#ecfdf5", "#ecfdf5",
"#d1fae5", "#d1fae5",
"#a7f3d0", "#a7f3d0",
"#6ee7b7", "#6ee7b7",
"#34d399", "#34d399",
"#10b981", "#0EA371",
"#059669", "#0A8A5F",
"#047857", "#0A6F4D",
"#065f46", "#065f46",
"#064e3b", "#064e3b",
]; ];
// Neutral gray ramp tuned for clean, low-contrast surfaces (Stripe/Notion feel).
const neutral: MantineColorsTuple = [ const neutral: MantineColorsTuple = [
"#f8fafc", "#F4F7FA",
"#f1f5f9", "#eef2f7",
"#e8edf2", "#E6ECF2",
"#dbe2ea", "#d0dae6",
"#c2cbd6", "#b0bfce",
"#9aa6b4", "#8fa0b2",
"#6b7785", "#6B7C8E",
"#4b5563", "#4b5a6a",
"#2f3742", "#10202F",
"#1c2129", "#0C1A2B",
]; ];
export const mantineTheme = createTheme({ export const mantineTheme = createTheme({
colors: { colors: {
"edr-green": edrGreen, "edr-green": edrGreen,
gray: neutral, gray: neutral,
// Brand surface + text tokens (single-value semantic colors).
// Each generates --mantine-color-{name}-{0..9} CSS variables.
"edr-bg": colorsTuple("#F4F7FA"),
"edr-card": colorsTuple("#FFFFFF"),
"edr-border": colorsTuple("#E6ECF2"),
"edr-divider": colorsTuple("#EEF1F5"),
"edr-text": colorsTuple("#10202F"),
"edr-muted": colorsTuple("#6B7C8E"),
"edr-soft": colorsTuple("#ECF6F1"),
"edr-ink": colorsTuple("#0C1A2B"),
"edr-accent": colorsTuple("#F2A516"),
// Semantic status shades (from the design system).
"edr-amber-soft": colorsTuple("#FDF3E0"),
"edr-amber-text": colorsTuple("#9A5B00"),
"edr-blue": colorsTuple("#2E5B96"),
"edr-blue-soft": colorsTuple("#E9F0F8"),
"edr-blue-dot": colorsTuple("#3B6FB0"),
"edr-red": colorsTuple("#C0392B"),
"edr-red-soft": colorsTuple("#FBEAE7"),
"edr-slate": colorsTuple("#475569"),
"edr-slate-soft": colorsTuple("#F1F4F7"),
"edr-slate-soft2": colorsTuple("#EEF2F6"),
"edr-step": colorsTuple("#94A3B8"),
"edr-step-idle": colorsTuple("#D5DBE2"),
"edr-conn-idle": colorsTuple("#E6EAEF"),
}, },
primaryColor: "edr-green", primaryColor: "edr-green",
primaryShade: { light: 6, dark: 5 }, primaryShade: { light: 5, dark: 4 },
white: "#ffffff", white: "#ffffff",
black: "#1c2129", black: "#10202F",
fontFamily: fontFamily:
'"Plus Jakarta Sans",ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', '"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
defaultRadius: "md", defaultRadius: "md",
@@ -49,7 +74,7 @@ export const mantineTheme = createTheme({
sm: "6px", sm: "6px",
md: "8px", md: "8px",
lg: "12px", lg: "12px",
xl: "16px", xl: "18px",
}, },
spacing: { spacing: {
@@ -76,17 +101,13 @@ export const mantineTheme = createTheme({
xl: "1.5", 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: { headings: {
fontFamily: '"Plus Jakarta Sans", var(--mantine-font-family)', fontFamily: '"Inter", var(--mantine-font-family)',
fontWeight: "600", fontWeight: "700",
sizes: { sizes: {
h1: { fontSize: "40px", lineHeight: "1.1", fontWeight: "800" }, h1: { fontSize: "36px", lineHeight: "1.1", fontWeight: "800" },
h2: { fontSize: "30px", lineHeight: "1.2", fontWeight: "700" }, h2: { fontSize: "28px", lineHeight: "1.2", fontWeight: "700" },
h3: { fontSize: "23px", lineHeight: "1.3", fontWeight: "600" }, h3: { fontSize: "22px", lineHeight: "1.3", fontWeight: "600" },
h4: { fontSize: "18px", lineHeight: "1.4", fontWeight: "600" }, h4: { fontSize: "18px", lineHeight: "1.4", fontWeight: "600" },
h5: { fontSize: "15px", lineHeight: "1.45", fontWeight: "600" }, h5: { fontSize: "15px", lineHeight: "1.45", fontWeight: "600" },
h6: { fontSize: "13px", lineHeight: "1.45", fontWeight: "600" }, h6: { fontSize: "13px", lineHeight: "1.45", fontWeight: "600" },
@@ -95,53 +116,39 @@ export const mantineTheme = createTheme({
shadows: { shadows: {
xs: "0 1px 2px rgba(16, 24, 40, 0.04)", 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)", sm: "0 1px 3px rgba(16, 24, 40, 0.06)",
md: "0 4px 12px rgba(16, 24, 40, 0.06)", md: "0 4px 12px rgba(16, 24, 40, 0.06)",
}, },
components: { components: {
Card: { Card: {
defaultProps: { defaultProps: {
radius: "lg", radius: "xl",
withBorder: true, withBorder: true,
shadow: "none", shadow: "none",
padding: "lg", padding: "lg",
}, },
styles: {
root: { borderColor: "#E6ECF2" },
},
}, },
Button: { Button: {
defaultProps: { defaultProps: { radius: "md" },
radius: "md", styles: { root: { fontWeight: 600 } },
},
styles: {
root: { fontWeight: 550 },
},
}, },
Badge: { Badge: {
defaultProps: { defaultProps: { radius: "xl", variant: "light" },
radius: "sm", styles: { root: { fontWeight: 600, textTransform: "none" } },
variant: "light",
},
styles: {
root: { fontWeight: 550, textTransform: "none" },
},
}, },
Paper: { Paper: {
defaultProps: { defaultProps: { radius: "xl", shadow: "none", withBorder: true },
radius: "lg", styles: { root: { borderColor: "#E6ECF2" } },
shadow: "none",
withBorder: true,
},
}, },
Table: { Table: {
defaultProps: { defaultProps: { verticalSpacing: "sm", horizontalSpacing: "md" },
verticalSpacing: "sm",
horizontalSpacing: "md",
},
}, },
Title: { Title: {
styles: { styles: { root: { letterSpacing: "-0.01em" } },
root: { letterSpacing: "-0.01em" },
},
}, },
}, },
}); });