# Slash.sa Logistics Frontend — Architecture Refactor ## 1. Current Issues Analysis ### 1.1 File Structure Problems | Issue | Location | Impact | |---|---|---| | Components mixed into `src/Components/` with PascalCase subdirectories, but `app/components/` also exists | Both directories | Confusing, inconsistent import paths | | Empty component files (`admen.tsx`, `driver.tsx`, `tripe.tsx`, etc.) | `src/Components/` | Dead weight, misleading tree | | API logic scattered (`src/lib/api.ts`, direct `fetch()` calls in components) | Multiple files | Hard to mock/test, duplicated base URL logic | | No barrel (`index.ts`) exports anywhere | All directories | Verbose imports everywhere | | `helperFun.ts` mixes session management, formatting utilities, and order-status config | `src/utils/` | Violates single-responsibility principle | | Proxy route handler in `app/api/proxy/` but also `NEXT_PUBLIC_API_BASE_URL` pointing to the same origin | Both | Redundant, confusing config | ### 1.2 Design Inconsistencies | Issue | Example | |---|---| | Dark `slate-950` dashboard theme vs. light `slate-50` login/register theme — no shared token layer | `app/login/page.tsx` vs `app/dashboard/page.tsx` | | Three different spinner implementations (inline SVG in `spinner.tsx`, div spinner in `NewOrder/page.tsx`, none elsewhere) | Multiple files | | `Plus Jakarta Sans` loaded via `@import` in `globals.css` but Geist fonts loaded via `next/font` in `layout.tsx` — double font loading | `app/layout.tsx`, `app/globals.css` | | Tailwind v4 used (`@import "tailwindcss"`) but `tailwind.config.js` targets v3 API | Config mismatch | | RTL direction set per-page inconsistently (`dir="rtl"` on `
` or `
`) | All pages | | No shared color CSS variables consumed by Tailwind — raw hex strings in JSX | Throughout | ### 1.3 API / State Issues | Issue | Location | |---|---| | `API_BASE` read from `process.env.NEXT_PUBLIC_API_BASE_URL!` with `!` assertion and a `console.log` | `client.tsx`, `order.tsx`, `clientAdress.tsx` | | Auth token cookie set manually with string concatenation | `src/lib/auth.ts` | | No centralised error-boundary or query-state helper | Everywhere | | `requestJson` retries 3× with exponential back-off but components also retry inline | `api.ts` + components | --- ## 2. Proposed Folder Structure ``` / ├── app/ │ ├── (admin)/ # Route group — requires auth + admin role │ │ ├── dashboard/page.tsx │ │ ├── users/page.tsx │ │ ├── cars/page.tsx │ │ ├── drivers/page.tsx │ │ ├── clients/page.tsx │ │ ├── orders/page.tsx │ │ ├── trips/page.tsx │ │ ├── branches/page.tsx │ │ ├── roles/page.tsx │ │ ├── audit/page.tsx │ │ └── layout.tsx # DashboardLayout (Sidebar + Topbar) │ │ │ ├── (client)/ # Route group — public client portal │ │ ├── NewOrder/page.tsx │ │ ├── client/page.tsx │ │ └── register/page.tsx │ │ │ ├── (auth)/ # Route group — unauthenticated │ │ ├── login/page.tsx │ │ └── forgot-password/page.tsx │ │ │ ├── forbidden/page.tsx │ ├── api/ │ │ └── proxy/[...path]/route.ts │ ├── globals.css │ └── layout.tsx # Root layout (fonts, metadata) │ ├── components/ # Shared UI primitives │ ├── ui/ │ │ ├── Spinner.tsx ✅ Single canonical spinner │ │ ├── Alert.tsx ✅ Typed alert banner │ │ ├── Button.tsx ✅ PrimaryBtn + variants │ │ ├── Input.tsx ✅ Labelled input with error │ │ └── index.ts barrel │ │ │ ├── layout/ │ │ ├── Sidebar.tsx │ │ ├── Topbar.tsx │ │ ├── Navbar.tsx │ │ └── index.ts │ │ │ ├── auth/ │ │ └── LoginForm.tsx │ │ │ ├── client/ │ │ ├── RegisterStep.tsx │ │ ├── AddressStep.tsx │ │ └── OrderDashboard.tsx (was DashboardStep) │ │ │ └── dashboard/ │ ├── SummaryCard.tsx │ ├── ActiveTripCard.tsx │ └── AlertsCard.tsx │ ├── services/ # All network calls — no UI imports │ ├── api.ts base requestJson helper │ ├── auth.service.ts loginUser, logout helpers │ ├── dashboard.service.ts getDashboardSummary │ ├── client.service.ts getClientOrders, postClientAddress … │ └── index.ts barrel │ ├── lib/ # Non-UI utilities │ ├── auth.ts token storage, cookie helpers │ ├── session.ts (extracted from helperFun.ts) │ ├── formatters.ts fmtDate, fmtAmount │ ├── order-status.ts statusColor, statusLabel, OrderStatus type │ └── index.ts │ ├── hooks/ # Custom React hooks │ ├── useAuth.ts │ ├── useDashboardSummary.ts │ └── useClientOrders.ts │ ├── styles/ │ └── tokens.css CSS custom properties (design tokens) │ ├── types/ │ ├── auth.ts │ ├── dashboard.ts │ └── client.ts │ └── public/ ``` --- ## 3. Design System ### 3.1 Color Tokens (`styles/tokens.css`) ```css /* ── Brand ─────────────────────────────── */ --color-brand-600: #1A73E8; --color-brand-700: #1557B0; --color-brand-50: #EBF3FF; /* ── Semantic ───────────────────────────── */ --color-success: #34A853; --color-warning: #F59E0B; --color-danger: #E53935; --color-info: #1A73E8; /* ── Surface (light) ────────────────────── */ --color-surface: #FFFFFF; --color-surface-muted: #F9FAFB; --color-border: #E5E7EB; /* ── Surface (dark — admin dashboard) ───── */ --color-surface-dark: #0F172A; /* slate-950 */ --color-surface-dark-card: #1E293B; /* slate-800 */ --color-border-dark: rgba(255,255,255,0.1); /* ── Text ───────────────────────────────── */ --color-text-primary: #111827; --color-text-muted: #6B7280; --color-text-hint: #9CA3AF; --color-text-inverse: #FFFFFF; /* ── Font ───────────────────────────────── */ --font-sans: 'Plus Jakarta Sans', sans-serif; --font-mono: 'IBM Plex Mono', monospace; /* ── Radius ─────────────────────────────── */ --radius-sm: 7px; --radius-md: 10px; --radius-lg: 14px; --radius-xl: 20px; --radius-full: 9999px; /* ── Sidebar width ──────────────────────── */ --sidebar-width: 280px; ``` These are imported once in `app/globals.css` and referenced via Tailwind's `theme.extend` for utility access. ### 3.2 Typography | Role | Font | Weight | Size | |---|---|---|---| | Page heading | Plus Jakarta Sans | 700 | 28–38px | | Section title | Plus Jakarta Sans | 600 | 18–22px | | Body | Plus Jakarta Sans | 400 | 13–14px | | Label/caps | Plus Jakarta Sans | 700 | 10–11px | | Code/mono | IBM Plex Mono | 400/500 | 12–13px | Load **once** via `next/font/google` in `app/layout.tsx`. Remove `@import url(...)` from `globals.css`. --- ## 4. Key Component Implementations ### 4.1 Canonical Spinner (`components/ui/Spinner.tsx`) ```tsx import { cn } from "@/lib/utils"; interface SpinnerProps { size?: "sm" | "md" | "lg"; className?: string; } const sizes = { sm: "h-4 w-4", md: "h-6 w-6", lg: "h-10 w-10" }; export function Spinner({ size = "md", className }: SpinnerProps) { return ( ); } ``` **Usage:** ``, ``, `` ### 4.2 Alert (`components/ui/Alert.tsx`) ```tsx interface AlertProps { type?: "error" | "info" | "success" | "warning"; message: string; onClose?: () => void; } const config = { error: { bg: "bg-red-50 border-red-200 text-red-700", icon: "⚠" }, info: { bg: "bg-blue-50 border-blue-200 text-blue-700", icon: "ℹ" }, success: { bg: "bg-emerald-50 border-emerald-200 text-emerald-700", icon: "✓" }, warning: { bg: "bg-amber-50 border-amber-200 text-amber-700", icon: "!" }, }; export function Alert({ type = "error", message, onClose }: AlertProps) { const { bg, icon } = config[type]; return (
{message} {onClose && ( )}
); } ``` ### 4.3 Button (`components/ui/Button.tsx`) ```tsx import { Spinner } from "./Spinner"; import { cn } from "@/lib/utils"; interface ButtonProps extends React.ButtonHTMLAttributes { loading?: boolean; variant?: "primary" | "secondary" | "ghost" | "danger"; size?: "sm" | "md" | "lg"; } const variants = { primary: "bg-[var(--color-brand-600)] hover:bg-[var(--color-brand-700)] text-white shadow-sm", secondary: "bg-white border border-[var(--color-border)] text-[var(--color-text-primary)] hover:bg-[var(--color-surface-muted)]", ghost: "text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)]", danger: "bg-red-600 hover:bg-red-700 text-white", }; const sizes = { sm: "h-8 px-3 text-[12px]", md: "h-10 px-4 text-[13px]", lg: "h-11 px-6 text-[14px]", }; export function Button({ loading, variant = "primary", size = "md", children, className, disabled, ...rest }: ButtonProps) { return ( ); } ``` ### 4.4 Input (`components/ui/Input.tsx`) ```tsx interface InputProps extends React.InputHTMLAttributes { label: string; error?: string; hint?: string; } export function Input({ label, error, hint, id, className, ...rest }: InputProps) { const inputId = id ?? label.toLowerCase().replace(/\s+/g, "-"); return (
{error && ( )} {hint && !error && (

{hint}

)}
); } ``` --- ## 5. Services Layer ### 5.1 `services/api.ts` (base) ```ts // Single source of truth for all HTTP calls. // Never import React or UI code here. const API_BASE = typeof window === "undefined" ? process.env.INTERNAL_API_BASE_URL ?? "" : "/api/proxy"; export class ApiError extends Error { constructor(public status: number, message: string) { super(message); this.name = "ApiError"; } } export async function request( path: string, init: RequestInit = {}, token?: string | null, ): Promise { const headers = new Headers(init.headers); if (!headers.has("Content-Type") && !(init.body instanceof FormData)) { headers.set("Content-Type", "application/json"); } if (token) headers.set("Authorization", `Bearer ${token}`); const res = await fetch(`${API_BASE}/${path.replace(/^\//, "")}`, { ...init, headers, cache: "no-store", }); if (!res.ok) { const json = await res.json().catch(() => null); throw new ApiError(res.status, json?.message ?? `HTTP ${res.status}`); } return res.json() as Promise; } ``` ### 5.2 `services/dashboard.service.ts` ```ts import { request } from "./api"; import type { DashboardSummary } from "@/types/dashboard"; export const dashboardService = { getSummary: (token: string) => request<{ success: boolean; data: DashboardSummary }>("dashboard/summary", {}, token), }; ``` ### 5.3 `services/auth.service.ts` ```ts import { request } from "./api"; import type { LoginResponse } from "@/types/auth"; export const authService = { login: (identity: string, password: string) => request("auth/login", { method: "POST", body: JSON.stringify({ identity, password }), }), }; ``` --- ## 6. Custom Hooks ### `hooks/useDashboardSummary.ts` ```ts "use client"; import { useEffect, useState } from "react"; import { dashboardService } from "@/services/dashboard.service"; import { getStoredToken } from "@/lib/auth"; import type { DashboardSummary } from "@/types/dashboard"; export function useDashboardSummary() { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const token = getStoredToken(); if (!token) { setLoading(false); return; } dashboardService.getSummary(token) .then(r => setData(r.data)) .catch(e => setError(e.message)) .finally(() => setLoading(false)); }, []); return { data, error, loading }; } ``` --- ## 7. Accessibility Checklist | Area | Implementation | |---|---| | **Focus management** | All interactive elements reachable via Tab; custom components include `focus:ring-2` | | **ARIA roles** | `role="alert"` on ``, `role="status"` + `aria-label="Loading"` on `` | | **Form labels** | `` always renders a `