style: ui fixes

This commit is contained in:
ghost2023
2026-06-11 09:24:06 +03:00
parent 12b1e54563
commit 2250a468cb
6 changed files with 900 additions and 268 deletions

View File

@@ -8,6 +8,7 @@ import {
Settings,
User,
} from "lucide-react";
import { useEffect } from "react";
import {
Outlet,
Route,
@@ -18,7 +19,16 @@ import {
import useAuth from "./hooks/useAuth";
import { useEffect } from "react";
function LogoutHandler() {
const { logout } = useAuth();
const navigate = useNavigate();
useEffect(() => {
logout().then(() => navigate("/login", { replace: true }));
}, []);
return null;
}
import EDRFreightLandingPage from "./pages/EDRFreightLandingPage";
import MyPortalPage from "./pages/MyPortalPage";
import ProfilePage from "./pages/ProfilePage";
@@ -71,7 +81,7 @@ const sidebarItems: SidebarItem[] = [
const App = () => {
const navigate = useNavigate();
const location = useLocation();
const { user, isPending, logout, customer, customerQuery } = useAuth();
const { user, isPending, customer, customerQuery } = useAuth();
useEffect(() => {
if (isPending || customerQuery.isPending) return;
@@ -103,6 +113,7 @@ const App = () => {
<Routes>
<Route>
<Route index element={<EDRFreightLandingPage />} />
<Route path="/logout" element={<LogoutHandler />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/otp" element={<VerificationOtpPage />} />
@@ -123,7 +134,6 @@ const App = () => {
enableThemeToggle
userName={displayName}
userEmail={userEmail}
onLogout={logout}
>
<Outlet />
</AppLayout>

View File

@@ -10,6 +10,9 @@ import {
Stack,
Text,
UnstyledButton,
useComputedColorScheme,
useMantineColorScheme,
useMantineTheme,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import {
@@ -18,11 +21,14 @@ import {
LogOut,
Menu as MenuIcon,
Moon,
Plus,
Search,
Settings,
Sun,
User,
X,
} from "lucide-react";
import { type CSSProperties, Fragment, type ReactNode, useState } from "react";
import { type CSSProperties, Fragment, type ReactNode } from "react";
export interface SidebarItem {
label: string;
@@ -40,32 +46,9 @@ export interface AppLayoutProps {
enableThemeToggle?: boolean;
userName?: string;
userEmail?: string;
onLogout?: () => void;
children: ReactNode;
}
type Theme = "light" | "dark";
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 {
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(" ")
@@ -75,15 +58,23 @@ function getInitials(name: string): string {
.join("");
}
function getActivePage(items: SidebarItem[], activePath: string): { label: string } | null {
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() + "/")) {
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() + "/"),
(c) =>
path === c.href.toLowerCase() ||
path.startsWith(c.href.toLowerCase() + "/"),
);
if (childMatch) return { label: childMatch.label };
}
@@ -101,8 +92,8 @@ const navClassNames = (active: boolean) => {
}
return {
root: `rounded-[10px] font-medium transition-all duration-150 hover:bg-[#F1F4F7]!`,
label: `text-[#3D4D5C]! font-semibold! hover:text-[#0C1A2B]!`,
section: `text-[#54657A]! hover:text-[#0C1A2B]!`,
label: `text-edr-text! font-semibold! hover:text-[#0C1A2B]!`,
section: `text-edr-text! hover:text-[#0C1A2B]!`,
};
};
@@ -114,22 +105,26 @@ export function AppLayout({
enableThemeToggle = false,
userName = "User",
userEmail,
onLogout,
children,
}: AppLayoutProps) {
const [mobileOpen, { toggle: toggleMobile }] = useDisclosure();
const [theme, setTheme] = useState<Theme>(() =>
enableThemeToggle ? getStoredTheme() : "light",
);
const theme = useMantineTheme();
const { setColorScheme } = useMantineColorScheme();
const computedColorScheme = useComputedColorScheme("light");
const borderColor = theme.colors["edr-border"][6];
const mutedColor = theme.colors["edr-muted"][6];
const textColor = theme.colors["edr-text"][6];
const accentColor = theme.colors["edr-accent"][6];
const bgColor = theme.colors["edr-bg"][6];
const primaryColor = theme.colors["edr-green"][5];
const primaryDarkColor = theme.colors["edr-green"][7];
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);
setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
};
const initials = getInitials(userName);
@@ -144,7 +139,7 @@ export function AppLayout({
width: 36,
height: 36,
borderRadius: 999,
border: `1px solid ${EDR.border}`,
border: `1px solid ${borderColor}`,
backgroundColor: "#fff",
display: "flex",
alignItems: "center",
@@ -157,8 +152,12 @@ export function AppLayout({
return (
<AppShell
layout="alt"
navbar={{ width: 260, breakpoint: "sm", collapsed: { mobile: !mobileOpen } }}
header={{ height: 56 }}
navbar={{
width: 260,
breakpoint: "sm",
collapsed: { mobile: !mobileOpen },
}}
header={{ height: 72 }}
padding={0}
>
{/* ── Header (frosted glass; compact floating islands, mirrors the pen) ── */}
@@ -169,21 +168,32 @@ export function AppLayout({
WebkitBackdropFilter: "blur(14px)",
border: "none",
boxShadow: "none",
background:'transparent',
background: "transparent",
}}
>
<Group h="100%" px={24} justify="space-between" wrap="nowrap">
<Group
h="100%"
px={24}
pt={12}
pb={8}
justify="space-between"
wrap="nowrap"
>
{/* Left: sidebar toggle + page title */}
<Group gap={12} wrap="nowrap" align="center">
<UnstyledButton onClick={toggleMobile} style={toggleStyle} aria-label="Toggle sidebar">
<MenuIcon size={18} color={EDR.text} strokeWidth={1.8} />
<UnstyledButton
onClick={toggleMobile}
hiddenFrom="sm"
aria-label="Toggle sidebar"
>
<MenuIcon size={18} color={textColor} strokeWidth={1.8} />
</UnstyledButton>
<Text
style={{
fontFamily: '"Inter", sans-serif',
fontSize: 16,
fontWeight: 700,
color: EDR.text,
color: textColor,
lineHeight: 1,
}}
>
@@ -202,14 +212,14 @@ export function AppLayout({
width: 260,
height: 36,
borderRadius: 999,
border: `1px solid ${EDR.border}`,
border: `1px solid ${borderColor}`,
backgroundColor: "#fff",
padding: "0 14px",
cursor: "text",
}}
>
<Search size={15} color={EDR.muted} strokeWidth={1.8} />
<Text size="sm" style={{ color: EDR.muted, userSelect: "none" }}>
<Search size={15} color={mutedColor} strokeWidth={1.8} />
<Text size="sm" style={{ color: mutedColor, userSelect: "none" }}>
Search shipments, bookings
</Text>
</Group>
@@ -217,7 +227,7 @@ export function AppLayout({
{/* Bell */}
<Box style={{ position: "relative" }}>
<UnstyledButton style={islandStyle} aria-label="Notifications">
<Bell size={17} color={EDR.text} strokeWidth={1.8} />
<Bell size={17} color={textColor} strokeWidth={1.8} />
</UnstyledButton>
<Box
style={{
@@ -227,7 +237,7 @@ export function AppLayout({
width: 7,
height: 7,
borderRadius: "50%",
backgroundColor: EDR.accent,
backgroundColor: accentColor,
border: "1.5px solid #fff",
pointerEvents: "none",
}}
@@ -235,21 +245,34 @@ export function AppLayout({
</Box>
{enableThemeToggle && (
<UnstyledButton onClick={toggleTheme} style={islandStyle} aria-label="Toggle theme">
{theme === "dark"
? <Sun size={17} color={EDR.text} strokeWidth={1.8} />
: <Moon size={17} color={EDR.text} strokeWidth={1.8} />}
<UnstyledButton
onClick={toggleTheme}
style={islandStyle}
aria-label="Toggle theme"
>
{computedColorScheme === "dark" ? (
<Sun size={17} color={textColor} strokeWidth={1.8} />
) : (
<Moon size={17} color={textColor} strokeWidth={1.8} />
)}
</UnstyledButton>
)}
{/* Avatar pill */}
<Menu width={220} position="bottom-end" withinPortal shadow="md" offset={8} radius="md">
<Menu
width={220}
position="bottom-end"
withinPortal
shadow="md"
offset={8}
radius="md"
>
<Menu.Target>
<UnstyledButton
style={{
height: 36,
borderRadius: 999,
border: `1px solid ${EDR.border}`,
border: `1px solid ${borderColor}`,
backgroundColor: "#fff",
padding: "0 10px 0 4px",
display: "flex",
@@ -259,22 +282,57 @@ export function AppLayout({
}}
>
<Avatar radius="xl" size={28} color="edr-green.5">
<Text fw={700} fz={12} c="white">{initials}</Text>
<Text fw={700} fz={12} c="white">
{initials}
</Text>
</Avatar>
<ChevronDown size={15} color={EDR.muted} strokeWidth={1.8} />
<ChevronDown size={15} color={mutedColor} strokeWidth={1.8} />
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
<Box px="sm" py="xs">
<Text size="sm" fw={600} truncate style={{ color: EDR.text }}>{userName}</Text>
{userEmail && <Text size="xs" c="dimmed" truncate>{userEmail}</Text>}
<Text
size="sm"
fw={600}
truncate
style={{ color: textColor }}
>
{userName}
</Text>
{userEmail && (
<Text size="xs" c="dimmed" truncate>
{userEmail}
</Text>
)}
</Box>
<Divider />
<Menu.Item leftSection={<User size={15} />} onClick={() => navigate("/profile")}>
<Menu.Item
leftSection={<User size={15} />}
onClick={() => navigate("/profile")}
>
Profile
</Menu.Item>
<Menu.Item leftSection={<LogOut size={15} />} color="red" onClick={onLogout}>
<Menu.Item
leftSection={<Settings size={15} />}
onClick={() => navigate("/settings")}
>
Settings
</Menu.Item>
<Divider />
<Menu.Item
leftSection={<Plus size={15} />}
color="edr-green"
onClick={() => navigate("/bookings/new")}
>
New Booking
</Menu.Item>
<Divider />
<Menu.Item
leftSection={<LogOut size={15} />}
color="red"
onClick={() => navigate("/logout")}
>
Logout
</Menu.Item>
</Menu.Dropdown>
@@ -288,7 +346,7 @@ export function AppLayout({
withBorder={false}
style={{
backgroundColor: "#ffffff",
borderRight: `1px solid ${EDR.border}`,
borderRight: `1px solid ${borderColor}`,
display: "flex",
flexDirection: "column",
}}
@@ -301,13 +359,19 @@ export function AppLayout({
display: "flex",
alignItems: "center",
padding: "0 18px",
justifyContent: "space-between",
}}
>
<Group gap={11} wrap="nowrap">
<img
src="/assets/edr-logo.png"
alt="EDR"
style={{ width: 40, height: 40, objectFit: "contain", flexShrink: 0 }}
style={{
width: 40,
height: 40,
objectFit: "contain",
flexShrink: 0,
}}
/>
<Box>
<Text
@@ -316,7 +380,7 @@ export function AppLayout({
fontWeight: 800,
fontSize: 19,
letterSpacing: "0.03em",
color: EDR.primary,
color: primaryColor,
lineHeight: 1.15,
}}
>
@@ -326,7 +390,7 @@ export function AppLayout({
style={{
fontSize: 10.5,
fontWeight: 500,
color: EDR.muted,
color: mutedColor,
lineHeight: 1.2,
}}
>
@@ -334,6 +398,13 @@ export function AppLayout({
</Text>
</Box>
</Group>
<UnstyledButton
onClick={toggleMobile}
hiddenFrom="sm"
aria-label="Close sidebar"
>
<X size={18} color={mutedColor} strokeWidth={1.8} />
</UnstyledButton>
</Box>
{/* Nav */}
@@ -343,7 +414,9 @@ export function AppLayout({
const active = isItemActive(item);
const hasChildren = !!item.children?.length;
const childActive =
item.children?.some((c) => activePath.startsWith(c.href.toLowerCase())) ?? false;
item.children?.some((c) =>
activePath.startsWith(c.href.toLowerCase()),
) ?? false;
const prevSection = sidebarItems[i - 1]?.section;
const sectionLabel =
@@ -357,9 +430,8 @@ export function AppLayout({
mb={4}
style={{
fontWeight: 600,
letterSpacing: "0.08em",
color: EDR.muted,
fontSize: 11,
color: textColor,
fontSize: 12,
}}
>
{item.section}
@@ -427,9 +499,9 @@ export function AppLayout({
alt=""
style={{
position: "absolute",
bottom:"-16px",
left:0,
right:"-70px",
bottom: "-16px",
left: 0,
right: "-70px",
width: "120%",
objectFit: "cover",
}}
@@ -440,18 +512,40 @@ export function AppLayout({
style={{
position: "absolute",
inset: 0,
top:"-75%",
background: "linear-gradient(165deg, #006149 40%, #0DC9A4 70%, #0DC9A402 80%)",
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 }}>
<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 }}>
<Text
style={{
fontSize: 11,
color: "rgba(255,255,255,0.7)",
lineHeight: 1.5,
}}
>
Reliable. Efficient. Connected.
</Text>
</Box>
@@ -472,8 +566,16 @@ export function AppLayout({
cursor: "pointer",
}}
>
<Text style={{ fontSize: 12, fontWeight: 600, color: EDR.primaryDark }}>Learn More</Text>
<span style={{ fontSize: 12, color: EDR.primaryDark }}></span>
<Text
style={{
fontSize: 12,
fontWeight: 600,
color: primaryDarkColor,
}}
>
Learn More
</Text>
<span style={{ fontSize: 12, color: primaryDarkColor }}></span>
</Box>
</Box>
@@ -481,7 +583,7 @@ export function AppLayout({
<Box
style={{
borderRadius: 12,
border: `1px solid ${EDR.border}`,
border: `1px solid ${borderColor}`,
padding: "10px",
display: "flex",
alignItems: "center",
@@ -495,19 +597,34 @@ export function AppLayout({
color="edr-green.5"
style={{ flexShrink: 0 }}
>
<Text fw={600} fz={13} c="white">{initials}</Text>
<Text fw={600} fz={13} c="white">
{initials}
</Text>
</Avatar>
<Box style={{ minWidth: 0, flex: 1 }}>
<Text size="sm" fw={600} truncate style={{ color: EDR.text, lineHeight: 1.3 }}>
<Text
size="sm"
fw={600}
truncate
style={{ color: textColor, lineHeight: 1.3 }}
>
{userName}
</Text>
{userEmail && (
<Text size="xs" truncate style={{ color: EDR.muted, lineHeight: 1.3 }}>
<Text
size="xs"
truncate
style={{ color: mutedColor, lineHeight: 1.3 }}
>
{userEmail}
</Text>
)}
</Box>
<ChevronDown size={16} color={EDR.muted} style={{ flexShrink: 0 }} />
<ChevronDown
size={16}
color={mutedColor}
style={{ flexShrink: 0 }}
/>
</Box>
</Box>
</AppShell.Navbar>
@@ -515,7 +632,7 @@ export function AppLayout({
{/* ── Main ── */}
<AppShell.Main
style={{
backgroundColor: EDR.bg,
backgroundColor: bgColor,
backgroundImage:
"radial-gradient(58% 42% at 100% 0%, rgba(14,163,113,0.18) 0%, rgba(14,163,113,0.0.8) 38%, rgba(14,163,113,0.04) 72%)",
backgroundRepeat: "no-repeat",

View File

@@ -124,7 +124,7 @@ export default function NewBookingPage() {
const cargoTypeId =
data.cargoType === "container"
? findContainerCargoTypeId()
: selectedChild?.id ?? "";
: (selectedChild?.id ?? "");
const cargoFreeText =
data.cargoType === "container"
@@ -200,9 +200,21 @@ export default function NewBookingPage() {
}}
>
{/* ── Page header ─────────────────────────────────────────────────── */}
<Group justify="space-between" px={"lg"} align="flex-end" wrap="wrap" gap="md" mb="lg">
<Group
justify="space-between"
px="24px"
align="flex-end"
wrap="wrap"
gap="md"
mb="lg"
>
<Box>
<Title order={1} fw={800} fz={26} style={{ letterSpacing: "-0.01em" }}>
<Title
order={1}
fw={800}
fz={26}
style={{ letterSpacing: "-0.01em" }}
>
New Booking
</Title>
<Text size="sm" c="edr-muted" mt={4}>
@@ -226,9 +238,8 @@ export default function NewBookingPage() {
onSubmit={handleSubmit}
>
{/* Step indicator */}
<Box
>
<Box className="mx-auto max-w-5xl" style={{ paddingInline: "16px" , }}>
<Box>
<Box className="mx-auto max-w-5xl" style={{ paddingInline: "16px" }}>
<StepIndicator step={step} />
</Box>
</Box>
@@ -272,7 +283,11 @@ export default function NewBookingPage() {
/>
)}
{step === 5 && (
<Step8Review form={form} setStep={setStep} direction={direction} />
<Step8Review
form={form}
setStep={setStep}
direction={direction}
/>
)}
</Box>
</Box>

View File

@@ -1,4 +1,8 @@
import { colorsTuple, createTheme, type MantineColorsTuple } from "@mantine/core";
import {
colorsTuple,
createTheme,
type MantineColorsTuple,
} from "@mantine/core";
const edrGreen: MantineColorsTuple = [
"#ecfdf5",
@@ -33,7 +37,7 @@ export const mantineTheme = createTheme({
// Brand surface + text tokens (single-value semantic colors).
// Each generates --mantine-color-{name}-{0..9} CSS variables.
"edr-bg": colorsTuple("#F4F7FA"),
"edr-bg": colorsTuple("#F7FAFC"),
"edr-card": colorsTuple("#FFFFFF"),
"edr-border": colorsTuple("#E6ECF2"),
"edr-divider": colorsTuple("#EEF1F5"),

4
pnpm-lock.yaml generated
View File

@@ -297,10 +297,10 @@ importers:
version: link:../../../packages/ui-common
'@hookform/resolvers':
specifier: ^5.4.0
version: 5.4.0(react-hook-form@7.76.0(react@19.2.6))
version: 5.4.0(react-hook-form@7.77.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)
version: 9.3.0(@mantine/hooks@9.3.0(react@19.2.6))(@types/react@18.3.31)(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)