update laye out and update home page also create footer page

This commit is contained in:
m7amedez5511
2026-07-08 17:49:08 +03:00
parent c814c5b97c
commit 5707521b92
15 changed files with 182 additions and 84 deletions

View File

@@ -0,0 +1,16 @@
"use client";
import { usePathname } from "next/navigation";
import { getStoredUser } from "@/src/lib/auth";
import Navbar from "./Navbar";
export default function ConditionalNavbar() {
const pathname = usePathname();
const user = getStoredUser();
// لو المستخدم مسجل دخول، أو الصفحة الحالية جوه الداشبورد، منظهرش الناف بار العام
const isDashboardRoute = pathname?.startsWith("/dashboard");
if (user || isDashboardRoute) return null;
return <Navbar />;
}

View File

@@ -0,0 +1,13 @@
import Logo from "@/src/utils/logo";
export function Footer() {
return (
<footer className="bg-[#0F172A] text-white">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-6 py-6 text-[13px] lg:flex-row lg:items-center lg:justify-between lg:px-8">
<Logo white={true} />
<div className="text-center text-white/40">
© 2026 Slash.sa. جميع الحقوق محفوظة.
</div>
</div>
</footer>)
}

View File

@@ -176,6 +176,7 @@ export function Sidebar() {
flexShrink: 0,
minHeight: "100vh",
padding: "20px 12px",
}}
>
<SidebarContent

View File

@@ -28,7 +28,6 @@ export function Topbar() {
justifyContent: "space-between",
}}
>
{/* عنوان الصفحة */}
<div>
<p style={{ fontSize: 14, fontWeight: 600, color: "var(--color-text-primary)", margin: 0, textAlign: "start" }}>
لوحة التحكم
@@ -38,15 +37,21 @@ export function Topbar() {
</p>
</div>
{/* أكشنز */}
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
{/* زرار المنيو — sm فقط، globals.css بتتحكم في الـ display */}
<div id="topbar-menu-btn">
<BrandIconButton onClick={() => setOpen(true)} />
</div>
{/* الدور */}
{/* زرار "حسابي" — بيروح لصفحة البروفايل، مع hover بيغير اللون ويعمل scale بسيط */}
<button
type="button"
onClick={() => router.push("/dashboard/profile")}
className="group flex items-center gap-1.5 rounded-full border border-slate-200 bg-slate-50 px-3 py-[5px] text-xs font-medium text-slate-600 transition-all duration-150 hover:scale-105 hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600"
>
<i className="ti ti-user text-[14px]" aria-hidden="true" />
حسابي
</button>
<div
suppressHydrationWarning
style={{
@@ -61,7 +66,6 @@ export function Topbar() {
{user?.role ?? "—"}
</div>
{/* خروج */}
<button
type="button"
onClick={handleLogout}

View File

@@ -0,0 +1,4 @@
export { default } from "./Navbar";
export { Sidebar } from "./Sidebar";
export { Footer } from "./Footer";
export { Topbar } from "./Topbar";

View File

@@ -0,0 +1,62 @@
"use client";
import { useCurrentUser } from "@/src/hooks/useCurrentUser";
export default function ProfilePage() {
const { user, loading, error } = useCurrentUser();
if (loading) {
// حالة تحميل بسيطة تتماشى مع سبينر الصور اللي عملتها قبل كده في الصفحات التانية
return (
<div className="flex min-h-[60vh] items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-blue-600 border-t-transparent" />
</div>
);
}
if (error || !user) {
return (
<div className="mx-auto mt-10 max-w-md rounded-lg bg-red-50 p-4 text-center text-sm text-red-600">
{error ?? "تعذر عرض بيانات الحساب."}
</div>
);
}
const fields: { label: string; value: string }[] = [
{ label: "الاسم", value: user.name },
{ label: "اسم المستخدم", value: user.userName ?? "—" },
{ label: "البريد الإلكتروني", value: user.email ?? "—" },
{ label: "رقم الهاتف", value: user.phone },
{ label: "الحالة", value: user.isActive ? "نشط" : "غير نشط" },
{ label: "تاريخ الإنشاء", value: new Date(user.createdAt).toLocaleDateString("ar-SA") },
{ label: "آخر تحديث", value: new Date(user.updatedAt).toLocaleDateString("ar-SA") },
];
return (
<div className="mx-auto max-w-lg px-4 py-10" dir="rtl">
<div className="rounded-2xl bg-white p-6 shadow-md">
<div className="mb-6 flex flex-col items-center gap-3">
<img
src={user.photo ?? "/images/avatar-placeholder.png"}
alt={user.name}
className="h-20 w-20 rounded-full object-cover ring-2 ring-blue-100"
onError={(e) => {
(e.target as HTMLImageElement).src = "/images/avatar-placeholder.png";
}}
/>
<h1 className="text-base font-semibold text-slate-900">{user.name}</h1>
</div>
{/* عرض البيانات بشكل read-only بدون أي فورم أو زرار تعديل */}
<dl className="divide-y divide-slate-100">
{fields.map((f) => (
<div key={f.label} className="flex items-center justify-between py-2.5 text-[13px]">
<dt className="text-slate-500">{f.label}</dt>
<dd className="font-medium text-slate-800">{f.value}</dd>
</div>
))}
</dl>
</div>
</div>
);
}

View File

@@ -10,8 +10,7 @@ import {
ClipboardList,
FileBarChart2,
} from "lucide-react";
import Navbar from "@/app/components/layout/Navbar";
import Logo from "@/src/utils/logo";
type Tone = "blue" | "emerald" | "amber";
@@ -81,7 +80,7 @@ const featureGroups: { icon: typeof Car; tone: Tone; title: string; text: string
export default function FeaturesPage() {
return (
<main className="min-h-screen bg-[#ECEEF2] text-slate-900" dir="rtl">
<Navbar />
<section className="bg-[linear-gradient(145deg,#0D47A1_0%,#1A73E8_45%,#1565C0_100%)] text-white">
<div className="mx-auto max-w-7xl px-6 py-14 lg:px-8">
@@ -143,14 +142,6 @@ export default function FeaturesPage() {
</div>
</section>
<footer className="bg-[#0F172A] text-white">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-6 py-6 text-[13px] lg:flex-row lg:items-center lg:justify-between lg:px-8">
<Logo white={true} />
<div className="text-center text-white/40">
© 2026 Slash.sa. جميع الحقوق محفوظة.
</div>
</div>
</footer>
</main>
);
}

View File

@@ -3,8 +3,7 @@
import { useState } from "react";
import Link from "next/link";
import { ChevronDown } from "lucide-react";
import Navbar from "@/app/components/layout/Navbar";
import Logo from "@/src/utils/logo";
const faqs = [
{
@@ -42,7 +41,7 @@ export default function FaqPage() {
return (
<main className="min-h-screen bg-[#ECEEF2] text-slate-900" dir="rtl">
<Navbar />
<section className="bg-[linear-gradient(145deg,#0D47A1_0%,#1A73E8_45%,#1565C0_100%)] text-white">
<div className="mx-auto max-w-7xl px-6 py-14 lg:px-8">
@@ -105,14 +104,7 @@ export default function FaqPage() {
</div>
</section>
<footer className="bg-[#0F172A] text-white">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-6 py-6 text-[13px] lg:flex-row lg:items-center lg:justify-between lg:px-8">
<Logo white={true} />
<div className="text-center text-white/40">
© 2026 Slash.sa. جميع الحقوق محفوظة.
</div>
</div>
</footer>
</main>
);
}

View File

@@ -106,7 +106,7 @@ const roles = [
export default function HomePage() {
return (
<main className="min-h-screen bg-[#ECEEF2] text-slate-900" dir="rtl">
<Navbar />
{/* Hero */}
<section className="relative overflow-hidden bg-[linear-gradient(145deg,#0D47A1_0%,#1A73E8_45%,#1565C0_100%)] text-white">
@@ -284,19 +284,7 @@ export default function HomePage() {
</article>
</section>
<footer className="bg-[#0F172A] text-white">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-6 py-6 text-[13px] lg:flex-row lg:items-center lg:justify-between lg:px-8">
<Logo white={true} />
<div className="text-center text-white/40">
© 2026 Slash.sa. جميع الحقوق محفوظة.
</div>
<div className="flex items-center gap-4 text-white/40">
<Link href="/prices" className="hover:text-white/70">الأسعار</Link>
<Link href="/frequently_asked_questions" className="hover:text-white/70">الأسئلة الشائعة</Link>
<Link href="/how_work" className="hover:text-white/70">كيف يعمل</Link>
</div>
</div>
</footer>
</main>
);
}

View File

@@ -33,7 +33,7 @@ const steps = [
export default function HowWorkPage() {
return (
<main className="min-h-screen bg-[#ECEEF2] text-slate-900" dir="rtl">
<Navbar />
<section className="bg-[linear-gradient(145deg,#0D47A1_0%,#1A73E8_45%,#1565C0_100%)] text-white">
<div className="mx-auto max-w-7xl px-6 py-14 lg:px-8">
@@ -95,14 +95,7 @@ export default function HowWorkPage() {
</div>
</section>
<footer className="bg-[#0F172A] text-white">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-6 py-6 text-[13px] lg:flex-row lg:items-center lg:justify-between lg:px-8">
<Logo white={true} />
<div className="text-center text-white/40">
© 2026 Slash.sa. جميع الحقوق محفوظة.
</div>
</div>
</footer>
</main>
);
}

View File

@@ -1,29 +1,9 @@
import type { Metadata } from "next";
import "@tabler/icons-webfont/dist/tabler-icons.min.css";
import "./globals.css";
import ConditionalNavbar from "./components/layout/ConditionalNavbar";
/**
* NOT SHOWN IN THE ORIGINAL FILE SET.
* This is the one file every RTL conversion ultimately hinges on:
* `dir="rtl"` and `lang="ar"` belong on the root <html> element so
* (a) the browser's native bidi algorithm kicks in everywhere,
* (b) Tailwind v4's `rtl:`/`ltr:` variants have something to key off,
* (c) form controls, scrollbars, and native widgets mirror correctly.
*
* If your real app/layout.tsx differs (providers, fonts, etc.), keep
* everything else as-is and only add the import below.
*
* ICON FONT: the project already uses Tabler Icons class names
* everywhere (ti ti-layout-dashboard, ti ti-car, ti ti-route, ...)
* but no stylesheet that defines those classes was ever loaded, so
* every icon in the sidebar was silently rendering as nothing.
* Self-hosted via `npm install @tabler/icons-webfont` rather than a
* CDN <link> — no external request at runtime, works behind
* corporate proxies / restrictive network policies, and Next bundles
* + fingerprints the CSS and font files automatically. Run
* `npm install @tabler/icons-webfont` if it isn't already a
* dependency, then this import resolves on its own.
*/
import { Footer } from "./components/layout";
export const metadata: Metadata = {
title: "لوحة التحكم",
@@ -36,8 +16,14 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
// ⚠️ اتصلح: الـ Navbar والـ footer كانوا برة <html>/<body> وده HTML غير صالح
<html lang="ar" dir="rtl">
<body className="app-shell">{children}</body>
<body className="app-shell">
{/* الناف بار بيظهر بس لو مفيش يوزر مسجل دخول */}
<ConditionalNavbar />
{children}
<Footer />
</body>
</html>
);
}

View File

@@ -49,7 +49,7 @@ const plans = [
export default function PricesPage() {
return (
<main className="min-h-screen bg-[#ECEEF2] text-slate-900" dir="rtl">
<Navbar />
<section className="bg-[linear-gradient(145deg,#0D47A1_0%,#1A73E8_45%,#1565C0_100%)] text-white">
<div className="mx-auto max-w-7xl px-6 py-14 text-center lg:px-8">
@@ -121,14 +121,7 @@ export default function PricesPage() {
</div>
</section>
<footer className="bg-[#0F172A] text-white">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-6 py-6 text-[13px] lg:flex-row lg:items-center lg:justify-between lg:px-8">
<Logo white={true} />
<div className="text-center text-white/40">
© 2026 Slash.sa. جميع الحقوق محفوظة.
</div>
</div>
</footer>
</main>
);
}

View File

@@ -0,0 +1,34 @@
"use client";
import { useEffect, useState, useCallback } from "react";
import { getStoredToken } from "@/src/lib/auth";
import { userService, extractMeUser } from "@/src/services/user.service";
import type { UserMe } from "@/src/types/user";
export function useCurrentUser() {
const [user, setUser] = useState<UserMe | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const load = useCallback(async () => {
setLoading(true);
setError(null);
try {
const token = getStoredToken();
const res = await userService.getMe(token);
const u = extractMeUser(res.data ?? res);
if (!u) throw new Error("لا توجد بيانات");
setUser(u as UserMe);
} catch {
setError("تعذر تحميل بيانات الحساب. حاول مرة أخرى.");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
return { user, loading, error, reload: load };
}

View File

@@ -4,7 +4,15 @@ import type { ApiListResponse, User, UserFormData, UserResponse } from "@/src/ty
import type { Role } from "@/src/types/role";
import type { Branch } from "@/src/types/branch";
export interface MeApiResponse {
success: boolean;
message: string;
responseAt: string;
data: {
data: User[] | User;
meta?: { total: number; page: number; limit: number; totalPages: number };
};
}
/**Building a query string to fetch users with search and pagination*/
@@ -49,7 +57,14 @@ export const userService = {
branch: { name: string };
};
}>(`users/${id}`, token),
getMe: (token: string | null) =>
get<MeApiResponse>("users/me", token),
};
export function extractMeUser(res: MeApiResponse): User | null {
const d = res.data?.data;
if (Array.isArray(d)) return d[0] ?? null;
return d ?? null;
}
/** Converting the form data into a payload suitable for the API */
function buildPayload(

View File

@@ -111,4 +111,10 @@ export interface ApiErrorResponse {
path: string;
details: { field: string; code: string }[];
};
}
export interface UserMe extends User {
photo: string | null;
isDeleted: boolean;
updatedAt: string;
}