first action update user pages .
2-update driver pages [fixed image display and notyfi] 3-update car pages [fixed image] 4-build tripe pages crud 5-build some role pages 6-build audit page 7-update ui compounants 8-update saidebar and topbar 9-cheange view to be ar view 10-cheange stractcher to be app,src 11-add validation layer by yup 12-add api image-proxy to broke image corse bloken
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import Logo from "@/utils/logo";
|
||||
import Logo from "@/src/utils/logo";
|
||||
import Link from "next/link";
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const navLinks = [
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<nav dir="rtl" className="w-full border-b border-slate-200 bg-white text-slate-900 shadow-sm">x
|
||||
<nav dir="rtl" className="w-full border-b border-slate-200 bg-white text-slate-900 shadow-sm">
|
||||
<div className="mx-auto flex h-14 max-w-7xl items-center gap-4 px-6 lg:px-8">
|
||||
<a href="/" className="flex items-center gap-3 text-right">
|
||||
<Logo />
|
||||
@@ -33,8 +33,21 @@ export default function Navbar() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mr-auto flex items-center gap-2">
|
||||
|
||||
{/*
|
||||
Was `mr-auto`. `mr-*` is a PHYSICAL Tailwind utility — it means
|
||||
"margin-right" no matter what `dir` says, so it would have
|
||||
pinned the login button to the visual left in both LTR and
|
||||
RTL, fighting the rest of the nav. `ms-auto` ("margin-inline-
|
||||
start: auto") pushes against the start edge instead, which is
|
||||
right in RTL and left in LTR — it tracks `dir` automatically.
|
||||
This is the single most common RTL bug: Tailwind's l/r-prefixed
|
||||
utilities (ml, mr, pl, pr, left-, right-, text-left, text-right,
|
||||
rounded-l, rounded-r, border-l, border-r) are all physical and
|
||||
need their logical (s/e) equivalents — ms, me, ps, pe, start-,
|
||||
end-, text-start, text-end, rounded-s, rounded-e, border-s,
|
||||
border-e — anywhere direction should matter.
|
||||
*/}
|
||||
<div className="ms-auto flex items-center gap-2">
|
||||
<Link href="/login" className="flex h-[34px] items-center rounded-lg bg-blue-600 px-3 text-[13px] font-bold text-white shadow-sm transition hover:bg-blue-700">
|
||||
تسجيل الدخول ←
|
||||
</Link>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { getStoredUser } from "../../../lib/auth";
|
||||
import Logo from "../../../utils/logo";
|
||||
import { getStoredUser } from "@/src/lib/auth";
|
||||
import Logo from "@/src/utils/logo";
|
||||
|
||||
const navSections = [
|
||||
{
|
||||
@@ -40,39 +40,110 @@ export function Sidebar() {
|
||||
const permissions = user?.permissions ?? navSections.flatMap(s => s.items.map(i => i.permission));
|
||||
|
||||
return (
|
||||
<aside
|
||||
suppressHydrationWarning
|
||||
className="hidden lg:flex lg:flex-col"
|
||||
style={{
|
||||
width: "var(--sidebar-width)",
|
||||
flexShrink: 0,
|
||||
background: "linear-gradient(175deg, #1E3A8A 0%, #1D4ED8 60%, #1565C0 100%)",
|
||||
minHeight: "100vh",
|
||||
padding: "20px 12px",
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{/*
|
||||
Full sidebar — visible lg+.
|
||||
No `left`/`right` is set anywhere: placement comes purely from
|
||||
document order + `dir="rtl"` on <html>. A flex row in RTL lays
|
||||
its first child against the inline-start edge, which is
|
||||
physically the RIGHT of the screen. Because <Sidebar /> is
|
||||
still the first child in DashboardLayout, it now renders on
|
||||
the right automatically — no positional CSS to flip.
|
||||
*/}
|
||||
<aside
|
||||
suppressHydrationWarning
|
||||
className="hidden lg:flex lg:flex-col"
|
||||
style={{
|
||||
width: "var(--sidebar-width)",
|
||||
flexShrink: 0,
|
||||
background: "linear-gradient(175deg, #1E3A8A 0%, #1D4ED8 60%, #1565C0 100%)",
|
||||
minHeight: "100vh",
|
||||
padding: "20px 12px",
|
||||
}}
|
||||
>
|
||||
<SidebarContent
|
||||
pathname={pathname}
|
||||
permissions={permissions}
|
||||
user={user}
|
||||
compact={false}
|
||||
/>
|
||||
</aside>
|
||||
|
||||
{/*
|
||||
Compact icon rail — md and below (replaces the old behavior of
|
||||
hiding the sidebar entirely under `lg`). Fixed to the
|
||||
inline-end... no: fixed to the RIGHT edge explicitly, because a
|
||||
`position: fixed` element takes no part in flex flow and so
|
||||
gets no automatic RTL placement from document order. This is
|
||||
exactly the kind of element logical properties exist for:
|
||||
`inset-inline-end: 0` means "right in RTL, left in LTR"
|
||||
without a manual swap if direction ever toggles.
|
||||
*/}
|
||||
<aside
|
||||
suppressHydrationWarning
|
||||
className="hidden md:flex lg:hidden md:flex-col md:items-center"
|
||||
style={{
|
||||
position: "fixed",
|
||||
insetBlockStart: 0,
|
||||
insetBlockEnd: 0,
|
||||
insetInlineEnd: 0,
|
||||
width: "var(--sidebar-width-compact)",
|
||||
background: "linear-gradient(175deg, #1E3A8A 0%, #1D4ED8 60%, #1565C0 100%)",
|
||||
padding: "16px 0",
|
||||
zIndex: 30,
|
||||
overflowY: "auto",
|
||||
}}
|
||||
aria-label="التنقل المصغر"
|
||||
>
|
||||
<SidebarContent
|
||||
pathname={pathname}
|
||||
permissions={permissions}
|
||||
user={user}
|
||||
compact={true}
|
||||
/>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarContent({
|
||||
pathname,
|
||||
permissions,
|
||||
user,
|
||||
compact,
|
||||
}: {
|
||||
pathname: string;
|
||||
permissions: string[];
|
||||
user: ReturnType<typeof getStoredUser>;
|
||||
compact: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* Logo */}
|
||||
<div style={{ marginBottom: 28 }}>
|
||||
<div style={{ marginBottom: 28, display: "flex", justifyContent: compact ? "center" : "flex-start" }}>
|
||||
<Logo white />
|
||||
</div>
|
||||
|
||||
{/* Nav sections */}
|
||||
<nav
|
||||
style={{ flex: 1, display: "flex", flexDirection: "column", gap: 0 }}
|
||||
style={{ flex: 1, display: "flex", flexDirection: "column", gap: 0, width: "100%" }}
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
{navSections.map((section) => (
|
||||
<div key={section.label} style={{ marginBottom: 8 }}>
|
||||
<p style={{
|
||||
fontSize: 9,
|
||||
letterSpacing: "0.14em",
|
||||
textTransform: "uppercase",
|
||||
color: "rgba(255,255,255,0.45)",
|
||||
padding: "8px 8px 4px",
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
{section.label}
|
||||
</p>
|
||||
{!compact && (
|
||||
<p style={{
|
||||
fontSize: 9,
|
||||
letterSpacing: "0.14em",
|
||||
textTransform: "uppercase",
|
||||
color: "rgba(255,255,255,0.45)",
|
||||
padding: "8px 8px 4px",
|
||||
fontWeight: 600,
|
||||
textAlign: "start",
|
||||
}}>
|
||||
{section.label}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{section.items.map((item) => {
|
||||
const allowed = item.permission === "read-dashboard"
|
||||
@@ -86,11 +157,14 @@ export function Sidebar() {
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
title={compact ? item.label : undefined}
|
||||
aria-label={item.label}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: compact ? "center" : "flex-start",
|
||||
gap: 9,
|
||||
padding: "7px 8px",
|
||||
padding: compact ? "10px" : "7px 8px",
|
||||
borderRadius: 8,
|
||||
fontSize: 13,
|
||||
fontWeight: active ? 500 : 400,
|
||||
@@ -99,14 +173,15 @@ export function Sidebar() {
|
||||
textDecoration: "none",
|
||||
transition: "var(--transition-base)",
|
||||
marginBottom: 2,
|
||||
textAlign: "start",
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={`ti ${item.icon}`}
|
||||
aria-hidden="true"
|
||||
style={{ fontSize: 16 }}
|
||||
style={{ fontSize: compact ? 18 : 16, flexShrink: 0 }}
|
||||
/>
|
||||
{item.label}
|
||||
{!compact && item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -114,20 +189,43 @@ export function Sidebar() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* User badge */}
|
||||
<div style={{
|
||||
background: "rgba(255,255,255,0.12)",
|
||||
borderRadius: 10,
|
||||
padding: "10px 12px",
|
||||
marginTop: 8,
|
||||
}}>
|
||||
<p suppressHydrationWarning style={{ fontSize: 13, fontWeight: 500, color: "#fff", margin: 0 }}>
|
||||
{user?.name ?? user?.userName ?? "—"}
|
||||
</p>
|
||||
<p suppressHydrationWarning style={{ fontSize: 11, color: "rgba(255,255,255,0.55)", marginTop: 2 }}>
|
||||
{user?.role ?? "Signed in"}
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
{/* User badge — collapses to an avatar dot in compact mode */}
|
||||
{compact ? (
|
||||
<div
|
||||
suppressHydrationWarning
|
||||
title={user?.name ?? user?.userName ?? "—"}
|
||||
style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: "var(--radius-full)",
|
||||
background: "rgba(255,255,255,0.18)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: "#fff",
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
{(user?.name ?? user?.userName ?? "—").slice(0, 1)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
background: "rgba(255,255,255,0.12)",
|
||||
borderRadius: 10,
|
||||
padding: "10px 12px",
|
||||
marginTop: 8,
|
||||
width: "100%",
|
||||
}}>
|
||||
<p suppressHydrationWarning style={{ fontSize: 13, fontWeight: 500, color: "#fff", margin: 0, textAlign: "start" }}>
|
||||
{user?.name ?? user?.userName ?? "—"}
|
||||
</p>
|
||||
<p suppressHydrationWarning style={{ fontSize: 11, color: "rgba(255,255,255,0.55)", marginTop: 2, textAlign: "start" }}>
|
||||
{user?.role ?? "Signed in"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { clearAuth, getStoredUser } from "../../../lib/auth";
|
||||
import { clearAuth, getStoredUser } from "@/src/lib/auth";
|
||||
|
||||
export function Topbar() {
|
||||
const router = useRouter();
|
||||
@@ -24,14 +24,29 @@ export function Topbar() {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
/*
|
||||
`justify-content: space-between` and `display: flex` are
|
||||
direction-agnostic — they already flip with `dir` for free.
|
||||
The title block (first child) lands at the inline-start edge
|
||||
(right, in RTL) and the actions cluster (second child) lands
|
||||
at inline-end (left). That matches the brief's "primary
|
||||
actions easily accessible" requirement: in Arabic dashboards
|
||||
the page title reads first at the right, and the role badge
|
||||
+ logout sit together at the natural reading end — unchanged
|
||||
from before, just now correctly positioned by `dir` instead
|
||||
of by accident.
|
||||
*/
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<p style={{ fontSize: 14, fontWeight: 600, color: "var(--color-text-primary)", margin: 0 }}>
|
||||
<p style={{ fontSize: 14, fontWeight: 600, color: "var(--color-text-primary)", margin: 0, textAlign: "start" }}>
|
||||
لوحة التحكم
|
||||
</p>
|
||||
<p style={{ fontSize: 11, color: "var(--color-text-muted)", margin: 0 }}>
|
||||
Operations dashboard
|
||||
<p style={{ fontSize: 11, color: "var(--color-text-muted)", margin: 0, textAlign: "start" }}>
|
||||
{/* English product label intentionally stays LTR-embedded so
|
||||
"Operations dashboard" doesn't get its word order or
|
||||
punctuation reversed by the surrounding RTL run. */}
|
||||
<span className="ltr-embed">Operations dashboard</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user