From 53c03e9867f53b688dd212046a30fb88e0798bb8 Mon Sep 17 00:00:00 2001 From: m7amedez5511 Date: Mon, 8 Jun 2026 23:05:19 +0300 Subject: [PATCH] finshing architecture for project now we have all layers to build profitionl project app,components ,hooks ,lib,services,styel ,types --- ARCHITECTURE.md | 499 ++++++++++++++++++ .../Components => Components}/Admen/admen.tsx | 0 .../Client/client.tsx | 5 +- .../Client_Adress/clientAdress.tsx | 4 +- .../Driver/driver.tsx | 0 .../Driver_Report/driverReport.tsx | 0 {src/Components => Components}/Home/home.tsx | 0 .../Components => Components}/Order/order.tsx | 9 +- {src/Components => Components}/Trip/tripe.tsx | 0 .../Trip_Report/tripe_report.tsx | 0 Components/UI/Alert.tsx | 59 +++ Components/UI/Button.tsx | 74 +++ Components/UI/Input.tsx | 91 ++++ Components/UI/Spinner.tsx | 80 +++ Components/UI/index.ts | 12 + {src/Components => Components}/User/user.tsx | 0 {src/Components => Components}/car/car.tsx | 0 app/NewOrder/page.tsx | 8 +- app/components/layout/Navbar.tsx | 2 +- app/components/layout/Sidebar.tsx | 2 +- app/components/layout/Topbar.tsx | 2 +- app/dashboard/page.tsx | 4 +- app/globals.css | 65 ++- app/layout.tsx | 37 +- app/login/page.tsx | 7 +- hooks/useClientOrders.ts | 45 ++ hooks/useDashboardSummary.ts | 49 ++ {src/lib => lib}/api.ts | 0 {src/lib => lib}/auth.ts | 0 lib/formatters.ts | 20 + lib/order-status.ts | 28 + lib/session.ts | 53 ++ lib/utils.ts | 11 + services/api.ts | 69 +++ services/auth.service.ts | 31 ++ services/client.service.ts | 46 ++ services/dashboard.service.ts | 7 + services/index.ts | 4 + src/Components/Spinner/spinner.tsx | 10 - styles/tokens.css | 72 +++ types/auth.ts | 16 + types/client.ts | 24 + types/dashboard.ts | 41 ++ {src/utils => utils}/helperFun.ts | 0 {src/utils => utils}/logo.tsx | 0 45 files changed, 1408 insertions(+), 78 deletions(-) create mode 100644 ARCHITECTURE.md rename {src/Components => Components}/Admen/admen.tsx (100%) rename {src/Components => Components}/Client/client.tsx (98%) rename {src/Components => Components}/Client_Adress/clientAdress.tsx (99%) rename {src/Components => Components}/Driver/driver.tsx (100%) rename {src/Components => Components}/Driver_Report/driverReport.tsx (100%) rename {src/Components => Components}/Home/home.tsx (100%) rename {src/Components => Components}/Order/order.tsx (96%) rename {src/Components => Components}/Trip/tripe.tsx (100%) rename {src/Components => Components}/Trip_Report/tripe_report.tsx (100%) create mode 100644 Components/UI/Alert.tsx create mode 100644 Components/UI/Button.tsx create mode 100644 Components/UI/Input.tsx create mode 100644 Components/UI/Spinner.tsx create mode 100644 Components/UI/index.ts rename {src/Components => Components}/User/user.tsx (100%) rename {src/Components => Components}/car/car.tsx (100%) create mode 100644 hooks/useClientOrders.ts create mode 100644 hooks/useDashboardSummary.ts rename {src/lib => lib}/api.ts (100%) rename {src/lib => lib}/auth.ts (100%) create mode 100644 lib/formatters.ts create mode 100644 lib/order-status.ts create mode 100644 lib/session.ts create mode 100644 lib/utils.ts create mode 100644 services/api.ts create mode 100644 services/auth.service.ts create mode 100644 services/client.service.ts create mode 100644 services/dashboard.service.ts create mode 100644 services/index.ts delete mode 100644 src/Components/Spinner/spinner.tsx create mode 100644 styles/tokens.css create mode 100644 types/auth.ts create mode 100644 types/client.ts create mode 100644 types/dashboard.ts rename {src/utils => utils}/helperFun.ts (100%) rename {src/utils => utils}/logo.tsx (100%) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..6958625 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,499 @@ +# 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 `