+
+ {/* top bar: user info */}
+
+
+
{user.name?.trim().charAt(0) ?? "?"}
-
{user.name}
- {user.role?.name && (
-
- {user.role.name}
+
+
{user.name}
+
{user.email ?? user.phone}
+
+
+ {user.role?.name && (
+
+ {user.role.name}
+
+ )}
+
+ {user.isActive ? "نشط" : "غير نشط"}
- )}
+
-
- {/* عرض البيانات بشكل read-only بدون أي فورم أو زرار تعديل */}
-
- {fields.map((f) => (
-
-
- {f.label}
- - {f.value}
-
- ))}
-
- {/* بطاقة الدور والصلاحيات */}
- {user.role && (
-
-
-
الدور والصلاحيات
- {user.role.description && (
-
{user.role.description}
- )}
+ {/* main content, full width */}
+
+
+ {/* account details */}
+
+
بيانات الحساب
+
+ {fields.map((f) => (
+
+
- {f.label}
+ - {f.value}
+
+ ))}
+
- {modules.length === 0 ? (
-
لا توجد صلاحيات مسجّلة لهذا الدور.
- ) : (
-
- {modules.map((mod) => (
-
-
-
- {mod}
-
+ {/* role and permissions */}
+
+
+
+
الدور والصلاحيات
+ {user.role?.description && (
+
{user.role.description}
+ )}
+
+ {modules.length > 0 && (
+
+ {totalPermissions} صلاحية
+
+ )}
+
+
+ {modules.length === 0 ? (
+
لا توجد صلاحيات مسجّلة لهذا الدور.
+ ) : (
+
+ {modules.map((mod) => (
+
+
+
{mod}
+
{permissionsByModule[mod].length}
-
-
- ▾
-
-
-
- {permissionsByModule[mod].map((name) => (
-
- {name}
-
- ))}
+
+
+ {permissionsByModule[mod].map((name) => (
+
+ {name}
+
+ ))}
+
-
- ))}
-
- )}
+ ))}
+
+ )}
+
- )}
+
);
}
\ No newline at end of file
diff --git a/app/dashboard/users/page.tsx b/app/dashboard/users/page.tsx
index b5ba82b..1ce7977 100644
--- a/app/dashboard/users/page.tsx
+++ b/app/dashboard/users/page.tsx
@@ -65,6 +65,11 @@ export default function UsersPage() {
{/* Create / Edit modal */}
{formTarget !== false && (
تسجيل الدخول للوحة التحكم
-
- كيف تعمل المنصة؟
-
+
@@ -214,9 +209,7 @@ export default function HomePage() {
})}
-
- عرض كل المميزات ←
-
+
@@ -273,12 +266,7 @@ export default function HomePage() {
>
ابدأ الآن
-
- عرض الأسعار
-
+
diff --git a/app/layout.tsx b/app/layout.tsx
index da6e053..ac9ecdc 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import "@tabler/icons-webfont/dist/tabler-icons.min.css";
import "./globals.css";
import ConditionalNavbar from "./components/layout/ConditionalNavbar";
+import { ExtensionAttributeCleanup } from "./components/system/ExtensionAttributeCleanup"; // CHANGE: added — fixes hydration mismatch caused by browser extensions
import { Footer } from "./components/layout";
@@ -19,6 +20,9 @@ export default function RootLayout({
// ⚠️ اتصلح: الـ Navbar والـ footer كانوا برة / وده HTML غير صالح
+ {/* CHANGE: mounted globally so every route (not just the dashboard
+ form pages) gets protected from extension-injected attributes */}
+
{/* الناف بار بيظهر بس لو مفيش يوزر مسجل دخول */}
{children}
diff --git a/src/Components/User/Toast.tsx b/src/Components/User/Toast.tsx
deleted file mode 100644
index 43bc0fc..0000000
--- a/src/Components/User/Toast.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import type { Notification } from "@/src/hooks/useUser";
-
-interface ToastProps {
- notification: Notification | null;
-}
-
-export function Toast({ notification }: ToastProps) {
- const [visible, setVisible] = useState(false);
-
- useEffect(() => {
- if (notification) {
- setVisible(true);
- } else {
- // dismiss after 250ms to allow exit animation
- const t = setTimeout(() => setVisible(false), 300);
- return () => clearTimeout(t);
- }
- }, [notification]);
-
- if (!visible && !notification) return null;
-
- const isSuccess = notification?.type === "success";
-
- return (
-
-
- {/* icon */}
- {isSuccess ? "✓" : "⚠"}
- {notification?.message}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/Components/User/UserFormModal.tsx b/src/Components/User/UserFormModal.tsx
index 4176f36..46bcb06 100644
--- a/src/Components/User/UserFormModal.tsx
+++ b/src/Components/User/UserFormModal.tsx
@@ -1,11 +1,11 @@
"use client";
-
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import type { Resolver } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { Alert, Button, Input, Select } from "../UI";
import { createUserSchema, updateUserSchema } from "@/src/validations/user.validator";
+import { useEditFormSync } from "@/src/hooks/useEditFormSync"; // CHANGE: added hook — fixes roleId/branchId not pre-selecting on edit
import type { Branch } from "@/src/types/branch";
import type { Role } from "@/src/types/role";
import type { User, UserFormData } from "@/src/types/user";
@@ -30,11 +30,6 @@ export function UserFormModal({ editUser, roles, branches, onClose, onSubmit }:
const resolver: Resolver
= async (values, context, options) => {
const schema = isNew ? createUserSchema : updateUserSchema;
const data = !isNew && !values.password ? { ...values, password: undefined } : values;
- // Cast both the schema argument and yupResolver's own return value — same
- // fix already applied in TripFormModal/DriverFormModal/CarFormModal:
- // passing an explicit generic here forces a structural
- // comparison between yup's inferred optional-field shape and
- // UserFormData that fails the same way it did for those forms.
return (yupResolver(schema as any) as any)(data as UserFormData, context, options);
};
@@ -56,25 +51,26 @@ export function UserFormModal({ editUser, roles, branches, onClose, onSubmit }:
},
});
- // roles/branches are passed in as props rather than fetched inside this
- // modal (unlike Trip/Driver/Car), but the same timing issue applies if the
- // parent still has them loading when the modal first mounts: defaultValues
- // are applied before the matching