diff --git a/app/api/proxy/[...path]/route.ts b/app/api/proxy/[...path]/route.ts
new file mode 100644
index 0000000..18f6fb2
--- /dev/null
+++ b/app/api/proxy/[...path]/route.ts
@@ -0,0 +1,65 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const BACKEND_BASE_URL = "https://logiapi.slash.sa/api";
+
+function buildHeaders(request: NextRequest) {
+ const headers = new Headers();
+
+ request.headers.forEach((value, key) => {
+ if (key === "host" || key === "content-length") return;
+ headers.set(key, value);
+ });
+
+ headers.set("x-forwarded-host", request.headers.get("host") || "localhost");
+ headers.delete("origin");
+
+ return headers;
+}
+
+async function proxy(request: NextRequest, params: { path: string[] }) {
+ const path = params.path?.join("/") ?? "";
+ const targetUrl = `${BACKEND_BASE_URL}/${path}${request.nextUrl.search}`;
+
+ const isBodyless = request.method === "GET" || request.method === "HEAD";
+ const body = isBodyless ? undefined : await request.text();
+
+ const upstream = await fetch(targetUrl, {
+ method: request.method,
+ headers: buildHeaders(request),
+ body,
+ cache: "no-store",
+ });
+
+ const contentType = upstream.headers.get("content-type") || "application/json";
+ const responseBody = contentType.includes("application/json")
+ ? await upstream.json().catch(() => null)
+ : await upstream.text();
+
+ return new NextResponse(typeof responseBody === "string" ? responseBody : JSON.stringify(responseBody), {
+ status: upstream.status,
+ headers: {
+ "content-type": contentType,
+ "cache-control": "no-store",
+ },
+ });
+}
+
+export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+ return proxy(request, await params);
+}
+
+export async function POST(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+ return proxy(request, await params);
+}
+
+export async function PUT(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+ return proxy(request, await params);
+}
+
+export async function PATCH(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+ return proxy(request, await params);
+}
+
+export async function DELETE(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+ return proxy(request, await params);
+}
diff --git a/app/audit/page.tsx b/app/audit/page.tsx
new file mode 100644
index 0000000..c5f1983
--- /dev/null
+++ b/app/audit/page.tsx
@@ -0,0 +1,3 @@
+export default function AuditPage() {
+ return Audit log module is ready for compliance and activity review.;
+}
diff --git a/app/branches/page.tsx b/app/branches/page.tsx
new file mode 100644
index 0000000..94cc3d6
--- /dev/null
+++ b/app/branches/page.tsx
@@ -0,0 +1,3 @@
+export default function BranchesPage() {
+ return Branches module is ready for the location and hub management table.;
+}
diff --git a/app/cars/page.tsx b/app/cars/page.tsx
new file mode 100644
index 0000000..0f6ba59
--- /dev/null
+++ b/app/cars/page.tsx
@@ -0,0 +1,3 @@
+export default function CarsPage() {
+ return Cars module is ready for the fleet management dashboard.;
+}
diff --git a/app/client/page.tsx b/app/client/page.tsx
new file mode 100644
index 0000000..8d0e359
--- /dev/null
+++ b/app/client/page.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+export default function ClientHomePage() {
+ return (
+
+
+
+
+
+
+
+
+ {[
+ ['الطلبات النشطة', '12', 'text-blue-600'],
+ ['المسلمة هذا الشهر', '86', 'text-emerald-600'],
+ ['العناوين المحفوظة', '4', 'text-slate-900'],
+ ].map(([label, value, tone]) => (
+
+ {label}
+ {value}
+
+ ))}
+
+
+
+
+ الطلب النشط
+
+
+
+
ORD-202606-00142
+
المركز الشمالي → مخزن وسط المدينة
+
+
في الطريق
+
+
+
+
+
+
+ الطلبات الأخيرة
+
+ {[
+ ['ORD-202606-00110', 'تم التوصيل', 'تم التوصيل', 'green'],
+ ['ORD-202606-00115', 'ملغى', 'ملغى', 'red'],
+ ].map(([ref, status, note, tone]) => (
+ -
+
+ {status}
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/app/clients/page.tsx b/app/clients/page.tsx
new file mode 100644
index 0000000..462fd72
--- /dev/null
+++ b/app/clients/page.tsx
@@ -0,0 +1,3 @@
+export default function ClientsPage() {
+ return Clients module is ready for the address and order management views.;
+}
diff --git a/app/components/layout/Navbar.tsx b/app/components/layout/Navbar.tsx
new file mode 100644
index 0000000..8bb8ff0
--- /dev/null
+++ b/app/components/layout/Navbar.tsx
@@ -0,0 +1,61 @@
+
+
+
+const navLinks = [
+ { label: "الرئيسية", active: true },
+ { label: "المميزات" },
+ { label: "كيف يعمل" },
+ { label: "الأسعار" },
+ { label: "الأسئلة الشائعة" },
+];
+
+export default function Navbar() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx
new file mode 100644
index 0000000..2fee0d8
--- /dev/null
+++ b/app/components/layout/Sidebar.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import Link from "next/link";
+
+const navItems = [
+ { href: "/dashboard", label: "Dashboard", permission: "read-dashboard" },
+ { href: "/users", label: "Users", permission: "read-user" },
+ { href: "/cars", label: "Cars", permission: "read-car" },
+ { href: "/drivers", label: "Drivers", permission: "read-driver" },
+ { href: "/clients", label: "Clients", permission: "read-client" },
+ { href: "/orders", label: "Orders", permission: "read-order" },
+ { href: "/trips", label: "Trips", permission: "read-trip" },
+ { href: "/branches", label: "Branches", permission: "read-branch" },
+ { href: "/roles", label: "Roles", permission: "read-role" },
+ { href: "/audit", label: "Audit", permission: "read-audit" },
+];
+
+export function Sidebar() {
+ const permissions = navItems.map((item) => item.permission);
+
+ return (
+
+ );
+}
diff --git a/app/components/layout/Topbar.tsx b/app/components/layout/Topbar.tsx
new file mode 100644
index 0000000..40d810c
--- /dev/null
+++ b/app/components/layout/Topbar.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { clearAuth } from "../../lib/auth";
+
+export function Topbar() {
+ const router = useRouter();
+
+ function handleLogout() {
+ clearAuth();
+ router.replace("/login");
+ }
+
+ return (
+
+
+
+
Operations dashboard
+
Logistics delivery management
+
+
+
+
Overview
+
Manager
+
+
+
+
+ );
+}
diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx
new file mode 100644
index 0000000..6f7e2a5
--- /dev/null
+++ b/app/dashboard/layout.tsx
@@ -0,0 +1,16 @@
+import { Sidebar } from "../components/layout/Sidebar";
+import { Topbar } from "../components/layout/Topbar";
+
+export default function DashboardLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ );
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
new file mode 100644
index 0000000..b2f42f7
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,153 @@
+"use client";
+
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useEffect, useMemo, useState } from "react";
+import { getDashboardSummary, type DashboardSummaryResponse } from "../lib/api";
+import { getStoredToken } from "../lib/auth";
+
+export default function DashboardPage() {
+ const router = useRouter();
+ const [data, setData] = useState(null);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ if (!getStoredToken()) {
+ router.replace("/login");
+ return;
+ }
+
+ let mounted = true;
+
+ async function load() {
+ try {
+ const summary = await getDashboardSummary();
+ if (mounted) setData(summary);
+ } catch (err) {
+ if (mounted) setError(err instanceof Error ? err.message : "Unable to load dashboard data.");
+ } finally {
+ if (mounted) setLoading(false);
+ }
+ }
+
+ load();
+ return () => {
+ mounted = false;
+ };
+ }, [router]);
+
+ const stats = data?.data?.stats;
+ const alerts = data?.data?.alerts;
+ const activeTrips = data?.data?.activeTrips || [];
+
+ const summaryCards = useMemo(
+ () => [
+ { label: "العملاء", value: stats?.clients ?? 0, tone: "from-cyan-500 to-sky-600" },
+ { label: "الطلبات", value: stats?.orders ?? 0, tone: "from-violet-500 to-fuchsia-600" },
+ { label: "الرحلات", value: stats?.trips ?? 0, tone: "from-emerald-500 to-green-600" },
+ { label: "المركبات", value: (stats?.cars ?? 0) + (stats?.drivers ?? 0), tone: "from-amber-400 to-orange-500" },
+ ],
+ [stats],
+ );
+
+ return (
+
+
+
+
+ {loading && جارٍ تحميل مقاييس الخلفية…
}
+ {error && {error}
}
+
+ {!loading && !error && (
+ <>
+
+ {summaryCards.map((item) => (
+
+
+ {item.label}
+ {item.value}
+
+ ))}
+
+
+
+
+
+
+
الرحلات النشطة
+
تقدم الرحلات
+
+
مباشر من /v1/dashboard/summary
+
+
+ {activeTrips.length ? activeTrips.map((trip) => (
+
+
+
+
{trip.tripNumber}
+
{trip.title}
+
+
{trip.progress}%
+
+
+
+ )) :
لا توجد رحلات في-progress حالياً.
}
+
+
+
+
+ تنبيهات الالتزام
+ التجديدات القادمة
+
+ {(alerts?.expiringCars || []).slice(0, 3).map((item, index) => (
+
{String((item as Record).message || "Vehicle expiry alert")}
+ ))}
+ {(alerts?.expiringDrivers || []).slice(0, 3).map((item, index) => (
+
{String((item as Record).message || "Driver expiry alert")}
+ ))}
+ {(alerts?.upcomingMaint || []).slice(0, 3).map((item, index) => (
+
{String((item as Record).message || "Maintenance alert")}
+ ))}
+
+
+
+
+
+
+ الأمان
+ لمحة الحساب
+
+
آخر تسجيل دخول: {data?.data?.accountSecurity?.lastLogin || "لم يتم العثور على حدث تسجيل دخول"}
+
بيانات الجهاز: {data?.data?.accountSecurity?.requestMeta ? JSON.stringify(data.data.accountSecurity.requestMeta) : "غير متاح"}
+
+
+
+
+ خريطة الخلفية
+ النقاط النهائية المستندة إلى Swagger
+
+ - /v1/dashboard/summary — نظرة عامة تشغيلية
+ - /v1/client — إدارة العملاء
+ - /v1/orders — دورة شحن الطلبات
+ - /v1/trip — تنظيم الرحلات
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/app/drivers/page.tsx b/app/drivers/page.tsx
new file mode 100644
index 0000000..514ddb0
--- /dev/null
+++ b/app/drivers/page.tsx
@@ -0,0 +1,3 @@
+export default function DriversPage() {
+ return Drivers module is ready for the workforce and report tools.;
+}
diff --git a/app/forbidden/page.tsx b/app/forbidden/page.tsx
new file mode 100644
index 0000000..b3c885b
--- /dev/null
+++ b/app/forbidden/page.tsx
@@ -0,0 +1,12 @@
+export default function ForbiddenPage() {
+ return (
+
+
+ Access denied
+ You do not have permission to open this section.
+ Contact an administrator to request access to the required module.
+ Return to dashboard
+
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
index 394f799..d66e747 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,9 +1,29 @@
+@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap");
@import "tailwindcss";
-
:root {
- --background: #ffffff;
- --foreground: #171717;
+ --blue: #1A73E8;
+ --blue-dark: #1557B0;
+ --blue-light: #EBF3FF;
+ --green: #34A853;
+ --green-light: #D1FAE5;
+ --amber: #F59E0B;
+ --amber-light: #FEF3C7;
+ --red: #E53935;
+ --red-light: #FEE2E2;
+ --text: #111827;
+ --text-muted: #6B7280;
+ --text-hint: #9CA3AF;
+ --border: #E5E7EB;
+ --surface: #F9FAFB;
+ --white: #fff;
+ --sidebar: 230px;
+ --radius: 10px;
+ --radius-sm: 7px;
+ --font: 'Plus Jakarta Sans', sans-serif;
+ --mono: 'IBM Plex Mono', monospace;
+ --background: #ECEEF2;
+ --foreground: #111827;
}
@theme inline {
@@ -13,15 +33,12 @@
--font-mono: var(--font-geist-mono);
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
+* { box-sizing: border-box; }
+html { scroll-behavior: smooth; font-size: 14px; }
body {
- background: var(--background);
+ margin: 0;
+ min-height: 100vh;
+ background: #ECEEF2;
color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ font-family: var(--font);
}
diff --git a/app/home/page.tsx b/app/home/page.tsx
new file mode 100644
index 0000000..687292b
--- /dev/null
+++ b/app/home/page.tsx
@@ -0,0 +1,8 @@
+import { Topbar } from "../components/layout/Topbar";
+
+export default function HomePage() {
+ return ;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 976eb90..4def342 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Logistics Client Portal",
+ description: "Modern frontend for the LogisticsApp backend with dashboard and client views.",
};
export default function RootLayout({
diff --git a/app/lib/api.ts b/app/lib/api.ts
new file mode 100644
index 0000000..dacd061
--- /dev/null
+++ b/app/lib/api.ts
@@ -0,0 +1,90 @@
+const DEFAULT_BASE_URL = "/api/proxy";
+
+export const API_BASE_URL =
+ process.env.NEXT_PUBLIC_API_BASE_URL || DEFAULT_BASE_URL;
+
+function normalizePath(path: string) {
+ return path.startsWith("/") ? path : `/${path}`;
+}
+
+async function requestJson(path: string, init: RequestInit = {}): Promise {
+ const token = typeof window !== "undefined" ? localStorage.getItem("auth_token") : null;
+ 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 endpoint = `${API_BASE_URL}${normalizePath(path)}`;
+
+ let attempt = 0;
+ const maxAttempts = 3;
+
+ while (attempt < maxAttempts) {
+ try {
+ const response = await fetch(endpoint, {
+ ...init,
+ headers,
+ cache: "no-store",
+ method: init.method || "GET",
+ });
+
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(text || `Request failed with status ${response.status}`);
+ }
+
+ return (await response.json()) as T;
+ } catch (error) {
+ attempt += 1;
+ if (attempt >= maxAttempts) {
+ throw error;
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 300));
+ }
+ }
+
+ throw new Error("Request failed after retries.");
+}
+
+export type DashboardSummaryResponse = {
+ success: boolean;
+ message: string;
+ data: {
+ stats: {
+ clients: number;
+ orders: number;
+ trips: number;
+ cars: number;
+ drivers: number;
+ };
+ alerts: {
+ expiringCars: Array>;
+ expiringDrivers: Array>;
+ upcomingMaint: Array>;
+ };
+ activeTrips: Array<{
+ id: string;
+ tripNumber: string;
+ title: string;
+ progress: number;
+ }>;
+ accountSecurity: {
+ lastLogin: string | null;
+ requestMeta: Record | null;
+ };
+ };
+};
+
+export async function getDashboardSummary() {
+ return requestJson("/v1/dashboard/summary");
+}
+
+export async function getHealth() {
+ return requestJson<{ status: string; message: string }>("/health");
+}
diff --git a/app/lib/auth.ts b/app/lib/auth.ts
new file mode 100644
index 0000000..2c6a36f
--- /dev/null
+++ b/app/lib/auth.ts
@@ -0,0 +1,92 @@
+import { API_BASE_URL } from "./api";
+
+const AUTH_TOKEN_KEY = "auth_token";
+const AUTH_USER_KEY = "auth_user";
+
+export interface AuthUser {
+ id?: string;
+ name?: string;
+ email?: string;
+ role?: string;
+ permissions?: string[];
+ roleId?: string;
+ userName?: string;
+ phone?: string;
+ [key: string]: unknown;
+}
+
+export interface LoginResponse {
+ token: string;
+ user: AuthUser;
+}
+
+export function getStoredToken() {
+ if (typeof window === "undefined") return null;
+ return localStorage.getItem(AUTH_TOKEN_KEY);
+}
+
+export function getStoredUser(): AuthUser | null {
+ if (typeof window === "undefined") return null;
+
+ const raw = localStorage.getItem(AUTH_USER_KEY);
+ if (!raw) return null;
+
+ try {
+ return JSON.parse(raw) as AuthUser;
+ } catch {
+ return null;
+ }
+}
+
+export function saveAuth(token: string, user: AuthUser) {
+ if (typeof window === "undefined") return;
+ localStorage.setItem(AUTH_TOKEN_KEY, token);
+ localStorage.setItem(AUTH_USER_KEY, JSON.stringify(user));
+}
+
+export function clearAuth() {
+ if (typeof window === "undefined") return;
+ localStorage.removeItem(AUTH_TOKEN_KEY);
+ localStorage.removeItem(AUTH_USER_KEY);
+}
+
+export async function loginUser(identity: string, password: string) {
+ const response = await fetch(`${API_BASE_URL}/v1/auth/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ identity, password }),
+ });
+
+ const payload = (await response.json().catch(() => ({}))) as {
+ success?: boolean;
+ message?: string;
+ data?: {
+ token?: string;
+ user?: AuthUser;
+ };
+ token?: string;
+ user?: AuthUser;
+ };
+
+ if (!response.ok) {
+ throw new Error(payload.message || "Login failed. Please check your credentials.");
+ }
+
+ const token = payload.data?.token ?? payload.token;
+ const user = payload.data?.user ?? payload.user;
+
+ if (!token || !user) {
+ throw new Error("The server did not return a valid session payload.");
+ }
+
+ const rolePermissions = (user as { role?: { permissions?: Array<{ permission?: { slug?: string } }> } }).role?.permissions ?? [];
+ const permissions = Array.isArray(rolePermissions)
+ ? rolePermissions
+ .map((entry) => entry?.permission?.slug)
+ .filter((slug): slug is string => Boolean(slug))
+ : [];
+
+ saveAuth(token, { ...user, permissions });
+
+ return { token, user: { ...user, permissions } } as LoginResponse;
+}
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..0a40b78
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,115 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { loginUser } from "../lib/auth";
+
+export default function LoginPage() {
+ const router = useRouter();
+ const [identity, setIdentity] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ async function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+ setError(null);
+ setLoading(true);
+
+ try {
+ await loginUser(identity, password);
+ router.replace("/dashboard");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "System temporarily unavailable. Please try later.");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/app/login/page.tsx.new b/app/login/page.tsx.new
new file mode 100644
index 0000000..da29008
--- /dev/null
+++ b/app/login/page.tsx.new
@@ -0,0 +1,115 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { loginUser } from "../lib/auth";
+
+export default function LoginPage() {
+ const router = useRouter();
+ const [identity, setIdentity] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ async function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+ setError(null);
+ setLoading(true);
+
+ try {
+ await loginUser(identity, password);
+ router.replace("/dashboard");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "System temporarily unavailable. Please try later.");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/app/orders/page.tsx b/app/orders/page.tsx
new file mode 100644
index 0000000..ca4253b
--- /dev/null
+++ b/app/orders/page.tsx
@@ -0,0 +1,3 @@
+export default function OrdersPage() {
+ return Orders module is ready for the order lifecycle and transfer workflows.;
+}
diff --git a/app/page.tsx b/app/page.tsx
index 3f36f7c..8e18d64 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,65 +1,177 @@
-import Image from "next/image";
+import Navbar from "./components/layout/Navbar";
+
+const stats = [
+ { label: "الطلبات النشطة", value: "48", color: "text-blue-600" },
+ { label: "مُسلَّم هذا الشهر", value: "312", color: "text-emerald-600" },
+ { label: "العملاء المسجلون", value: "127", color: "text-slate-900" },
+];
+
+const features = [
+ {
+ icon: "truck",
+ title: "إدارة الطلبات",
+ text: "إنشاء الطلبات وتتبعها وتحديثها في الوقت الفعلي.",
+ tone: "blue",
+ },
+ {
+ icon: "clock",
+ title: "التتبع المباشر",
+ text: "راقب تقدم الاستلام والتسليم عبر خريطة مرئية.",
+ tone: "emerald",
+ },
+ {
+ icon: "users",
+ title: "صلاحيات حسب الدور",
+ text: "لوحات تحكم منفصلة للمشرفين والعملاء والمستخدمين.",
+ tone: "amber",
+ },
+];
+
+const roles = [
+ {
+ tone: "dark",
+ tag: "مشرف",
+ title: "تحكم كامل",
+ items: ["عرض جميع طلبات العملاء", "تحديث حالات الطلبات", "إدارة العملاء والسائقين", "سجل التدقيق", "تعيين الرحلات"],
+ },
+ {
+ tone: "blue",
+ tag: "عميل",
+ title: "طلباتي",
+ items: ["إضافة طلبات جديدة", "تتبع الشحنات النشطة", "عرض سجل الطلبات", "إدارة العناوين", "التواصل مع الدعم"],
+ },
+ {
+ tone: "green",
+ tag: "مستخدم",
+ title: "وصول ميداني",
+ items: ["عرض الرحلات المعينة", "تحديث حالة التسليم", "الملف الشخصي", "إعدادات الإشعارات", "تفضيلات التطبيق"],
+ },
+];
+
+const faqs = [
+ { q: "كيف أنشئ حساب عميل؟", a: "اضغط على “إنشاء حساب عميل” ثم أكمل بياناتك الأساسية لتفعيل الحساب في دقائق." },
+ { q: "ماذا يحدث إن أدخلت كلمة مرور خاطئة؟", a: "ستظهر لك رسالة خطأ واضحة مع خيار إعادة المحاولة أو استرجاع كلمة المرور." },
+ { q: "هل بياناتي الشخصية محمية؟", a: "نعم، يتم تشفير البيانات باستخدام AES-256-GCM وإجراءات أمان متقدمة." },
+ { q: "هل يمكن للعميل رؤية طلبات عملاء آخرين؟", a: "لا، كل عميل يرى فقط طلباته الخاصة ضمن لوحة التحكم المخصصة له." },
+];
export default function Home() {
return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
+
+
+
+
+
+
+
+
+
+ منصة مباشرة • 3 صلاحيات
+
+
+ إدارة لوجستية سهلة، توصيل مضمون في كل مرة.
+
+
+ أدِر كل شحنة وعميل وسائق من منصة واحدة آمنة. مصممة للمشرفين والعملاء والمستخدمين الميدانيين.
+
+
+
+
+
+
+
+
-
+
+
+ المميزات الأساسية
+ كل ما يحتاجه فريق اللوجستيك
+ من إدارة الطلبات إلى التتبع المباشر، كل شيء في مكان واحد.
+
+ {features.map((feature) => (
+
+
+ {feature.icon === "truck" ?
: feature.icon === "clock" ?
:
}
+
+ {feature.title}
+ {feature.text}
+
+ ))}
+
+
+
+
+
+
+
+ أدوار المستخدمين
+ منصة واحدة، ثلاثة مداخل
+
+
+ {roles.map((role) => (
+
+ {role.tag}
+ {role.title}
+
+ {role.items.map((item) => - ✓{item}
)}
+
+
+ ))}
+
+
+
+
+
+
+ الأسئلة الشائعة
+ أسئلة متكررة
+
+ {faqs.map((faq) => (
+
+ {faq.q}
+ {faq.a}
+
+ ))}
+
+
+
+
+
+
);
}
diff --git a/app/register/page.tsx b/app/register/page.tsx
new file mode 100644
index 0000000..2aaac2b
--- /dev/null
+++ b/app/register/page.tsx
@@ -0,0 +1,146 @@
+"use client";
+
+import Link from "next/link";
+import { useMemo, useState } from "react";
+
+export default function RegisterPage() {
+ const [step, setStep] = useState(1);
+ const [form, setForm] = useState({ fullName: "", email: "", phone: "", company: "", address: "" });
+ const [errors, setErrors] = useState
>({});
+ const [loading, setLoading] = useState(false);
+
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ const phonePattern = /^\+?[0-9]{10,15}$/;
+
+ function validateStep1() {
+ const nextErrors: Record = {};
+ if (!form.fullName.trim()) nextErrors.fullName = "This field is required.";
+ if (!form.email.trim()) nextErrors.email = "This field is required.";
+ else if (!emailPattern.test(form.email)) nextErrors.email = "Please enter a valid email address (e.g. name@domain.com).";
+ if (!form.phone.trim()) nextErrors.phone = "This field is required.";
+ else if (!phonePattern.test(form.phone)) nextErrors.phone = "Phone number must be 10–15 digits, with optional country code.";
+ setErrors(nextErrors);
+ return Object.keys(nextErrors).length === 0;
+ }
+
+ function handleNext() {
+ if (validateStep1()) setStep(2);
+ }
+
+ function handleSubmit() {
+ setLoading(true);
+ setTimeout(() => {
+ setLoading(false);
+ window.location.href = "/client";
+ }, 600);
+ }
+
+ const progressSteps: Array<[number, string, boolean]> = [
+ [1, "بياناتك", step >= 1],
+ [2, "التفضيلات", step >= 2],
+ [3, "التأكيد", step >= 3],
+ ];
+
+ const summaryRows = useMemo(
+ () => [
+ ["Full Name", form.fullName || "—"],
+ ["Email", form.email || "—"],
+ ["Phone", form.phone || "—"],
+ ["Company", form.company || "—"],
+ ["Address", form.address || "—"],
+ ],
+ [form],
+ );
+
+ return (
+
+
+
+
+
تسجيل العميل
+
إنشاء حساب LogiFlow
+
+ العودة لتسجيل الدخول
+
+
+
+ {progressSteps.map(([index, label, done]) => (
+
+
+ {index}
+
+
{label}
+ {index < 3 ?
: null}
+
+ ))}
+
+
+
+ {step === 1 ? (
+
+ ) : null}
+
+ {step === 2 ? (
+
+
+
التفضيلات
+
راجع التفاصيل الاختيارية وانتقل إلى المراجعة.
+
+
سيتم إنشاء الحساب بشكل آمن. يتم تشفير البيانات الحساسة أثناء التخزين.
+
+
+
+
+
+ ) : null}
+
+ {step === 3 ? (
+
+
+
تأكيد وإرسال
+
راجع بياناتك قبل إنشاء الحساب.
+
+
+ {summaryRows.map(([label, value]) => | {label} | {value} |
)}
+
+
سيتم إنشاء الحساب بشكل آمن. يتم تشفير البيانات الحساسة أثناء التخزين.
+
+
+
+
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/app/roles/page.tsx b/app/roles/page.tsx
new file mode 100644
index 0000000..6355bc6
--- /dev/null
+++ b/app/roles/page.tsx
@@ -0,0 +1,3 @@
+export default function RolesPage() {
+ return Roles and permissions module is ready for RBAC assignment and matrix views.;
+}
diff --git a/app/trips/page.tsx b/app/trips/page.tsx
new file mode 100644
index 0000000..08ce785
--- /dev/null
+++ b/app/trips/page.tsx
@@ -0,0 +1,3 @@
+export default function TripsPage() {
+ return Trips module is ready for scheduling, status tracking, and manifest generation.;
+}
diff --git a/app/users/page.tsx b/app/users/page.tsx
new file mode 100644
index 0000000..61e9093
--- /dev/null
+++ b/app/users/page.tsx
@@ -0,0 +1,3 @@
+export default function UsersPage() {
+ return Users module is ready for the full CRUD UI.;
+}
diff --git a/package.json b/package.json
index d1d75d9..3713035 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "next dev --webpack",
"build": "next build",
"start": "next start",
"lint": "eslint"