5.8 KiB
Design System
Everything needed to reproduce the visual look of the SAR Link Portal. The UI is built on shadcn/ui (style: new-york, base color: neutral, icon set: lucide) over Tailwind CSS v4 (CSS-first config — there is no tailwind.config.ts; theming lives in app/globals.css via @theme).
Brand color
| Token | Value | Use |
|---|---|---|
--color-sarLinkOrange / text-sarLinkOrange |
#f49b5b |
Primary brand accent — page headings, logo text, highlights, top loader |
| Pattern orange (dark) | #e06f10 |
Diagonal SVG background pattern (opacity 0.35) |
| Pattern orange (light) | #f49b5b |
.title-bg SVG background pattern (opacity 0.1) |
The brand orange is used mainly as an accent on top of a neutral (grayscale) shadcn palette — it is not the shadcn --primary. Primary/secondary/muted etc. remain the default neutral shadcn tokens.
Theme tokens
Colors use the oklch color space, neutral base. Defined as CSS variables in app/globals.css under :root (light) and .dark (dark). Standard shadcn token set:
--background --foreground --card --card-foreground --popover --popover-foreground --primary --primary-foreground --secondary --secondary-foreground --muted --muted-foreground --accent --accent-foreground --destructive --border --input --ring plus a sidebar group --sidebar --sidebar-foreground --sidebar-primary --sidebar-accent --sidebar-border --sidebar-ring.
Key values:
- Light:
--background: oklch(1 0 0)(white),--foreground: oklch(0.145 0 0)(near-black),--primary: oklch(0.205 0 0). - Dark:
--background: oklch(0.145 0 0),--foreground: oklch(0.985 0 0),--primary: oklch(0.922 0 0). - Radius:
--radius: 0.625rem, withsm/md/lg/xlderived (calc(var(--radius) ± n)). - App background (body):
bg-gray-100light /bg-blackdark (set in root layout).
Dark mode is class-based (@custom-variant dark (&:is(.dark *))) via next-themes (attribute="class", default system).
Custom utility classes (in app/globals.css)
.title-bg— subtle diagonal SVG pattern in brand orange at 0.1 opacity. Used behind page-heading blocks and auth/login cards to give the faint textured background.- A second diagonal-line SVG pattern using
#e06f10at 0.35 opacity.
Typography
Two Google fonts loaded in app/layout.tsx as CSS variables:
| Font | Variable | Weights | Use |
|---|---|---|---|
| Barlow | --font-barlow (also the font-sans body font) |
100,300,400,500,600,700,800,900 | Body text, UI, everything by default |
| Bokor | --font-bokor |
400 | Display / decorative headings (brand) |
| mono | --font-mono |
— | Monospace bits (e.g. "Profile Status" label) |
Body element applies ${barlow.variable} ${bokor.variable} antialiased font-sans.
Tailwind plugins in use
tailwindcss-animate, @pyncz/tailwind-mask-image, tailwindcss-motion. Animations also via motion (Framer Motion) — used by the welcome banner and some transitions. TextShimmer component for shimmer loading states.
Status color conventions
These recur across badges and table rows (payments, topups, devices, users). Reproduce consistently:
| State | Color | Typical classes |
|---|---|---|
| Paid / Verified / Credit / success | green / lime | bg-green-500 text-white, bg-lime-*, green row tint |
| Pending / awaiting | yellow | bg-yellow-500 text-white, yellow row tint |
| Failed / Cancelled / Rejected / Debit | red | bg-red-500 text-white, destructive buttons |
| Expired | gray | gray row tint / muted |
| Unknown | yellow | bg-yellow-500 |
| Active device (until date) | green accent | brand/green text |
| Inactive / blocked | red / muted | red text, block dialog |
Recurring layout patterns
- Page heading block — a flex row with a dashed border,
title-bgbackground, rounded,text-sarLinkOrange text-2xlheading on the left, optional status/action on the right. (Seeprofile, most list pages.) - Dual list layout — every list screen renders an HTML
<table>on desktop and a stack of cards on mobile (MobilePaymentDetails,MobileTopupDetails,MobileTransactionDetails,device-card), showing the same data. Footer showsTotal N item(s).and pagination. - Read-only field grid — labeled read-only values in a responsive grid (
grid-cols-1 sm:grid-cols-2 md:grid-cols-3), used by profile & user details. - Drawers (shadcn
drawer/vaul) — device cart, wallet top-up, and filter panels open as bottom/side drawers. - Filter drawer + active-filter chips — filters applied to URL query params (via
nuqs), shown as dismissible badges. - Skeletons —
DevicesTableSkeletonand per-routeloading.tsxfiles provide loading UI;FullPageLoaderfor full-screen spins. - Countdown —
ExpiryTimeCountdownshowsTime left: …with a progress bar for unpaid payments/topups, ticking every second.
App shell (authenticated)
Rendered by components/auth/application-layout.tsx:
- Sidebar (
AppSidebar) on the left insideSidebarProvider— see navigation.md. - Sticky header (
h-16,border-b,sticky top-0,z-10): left = sidebar trigger + separator; right = Wallet balance button, theme toggle, account popover. - WelcomeBanner — animated "Welcome, {first} {last}" that auto-hides after 4s.
- DeviceCartDrawer — floating cart (hidden on payment pages).
- Main content —
p-4, rounded,bg-background, wrapped inNuqsAdapterfor URL state.
Auth-page shell
app/(auth)/auth/layout.tsx: centered full-screen container (bg-gray-100 light / bg-black dark) holding a single card. Login card uses title-bg and a border-2 border-sarLinkOrange/50 rounded-lg shadow.
Global chrome
- NextTopLoader — top progress bar, color
#f49d1b(orange), no spinner. - Toaster (
sonner,richColors) — all success/error toasts.