create user pages crud also update pages ui build order and car ,driver first page interfase create model layer to componants

This commit is contained in:
m7amedez5511
2026-06-10 16:58:17 +03:00
parent 53c03e9867
commit e0e38dc87a
34 changed files with 2963 additions and 862 deletions

View File

@@ -1,89 +1,27 @@
"use client";
import React, { useState, type FC, type FormEvent } from "react";
import { saveSession, type ClientSession } from "@/utils/helperFun";
import { Spinner } from "../UI";
import { saveSession, type ClientSession } from "@/lib/session";
import { clientService } from "@/services/client.service";
import { Spinner } from "../../Components/UI";
import { Alert } from "../../Components/UI";
import { Button } from "../../Components/UI";
import { Input } from "../../Components/UI";
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL!;
console.log("API_BASE:", API_BASE);
//make sure API_BASE ends without slash
// ─── Types ───────────────────────────────────
interface RegForm {
name: string;
email: string;
phone: string;
name: string;
email: string;
phone: string;
companyName: string;
}
interface RegErrors {
name?: string;
name?: string;
email?: string;
phone?: string;
}
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
icon?: React.ReactNode;
}
const Input: FC<InputProps> = ({ label, error, icon, className = "", ...rest }) => (
<div className="flex flex-col gap-1">
<label className="text-[12px] font-semibold text-[#374151]">{label}</label>
<div className="relative">
{icon && <div className="absolute right-3 top-1/2 -translate-y-1/2 text-[#9CA3AF]">{icon}</div>}
<input
className={`w-full h-10 ${icon ? "pr-9" : "pr-3"} pl-3 border rounded-lg text-[13px] text-[#111827]
placeholder-[#9CA3AF] outline-none transition focus:border-[#1A73E8] focus:ring-2
focus:ring-[#1A73E8]/15 text-right
${error ? "border-red-400 bg-red-50" : "border-[#E5E7EB] bg-white"} ${className}`}
dir="rtl"
{...rest}
/>
</div>
{error && <p className="text-[11px] text-red-500 font-medium text-right">{error}</p>}
</div>
);
interface BtnProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
loading?: boolean;
}
const PrimaryBtn: FC<BtnProps> = ({ loading, children, className = "", disabled, ...rest }) => (
<button
disabled={loading || disabled}
className={`flex items-center justify-center gap-2 h-11 px-6 bg-[#1A73E8] hover:bg-[#1557B0]
active:scale-[.98] text-white font-bold text-[13px] rounded-lg transition-all duration-150
disabled:opacity-60 disabled:cursor-not-allowed ${className}`}
{...rest}
>
{loading && <Spinner />}
{children}
</button>
);
const Alert: FC<{ type?: "error" | "info" | "success"; message: string; onClose?: () => void }> = ({
type = "error", message, onClose,
}) => {
const styles = {
error: "bg-red-50 border-red-200 text-red-700",
info: "bg-blue-50 border-blue-200 text-blue-700",
success: "bg-emerald-50 border-emerald-200 text-emerald-700",
};
const icons = { error: "⚠", info: "", success: "✓" };
return (
<div className={`flex items-start gap-2 border rounded-lg px-3 py-2.5 text-[12px] font-medium ${styles[type]}`} dir="rtl">
<span className="flex-shrink-0">{icons[type]}</span>
<span className="flex-1">{message}</span>
{onClose && (
<button onClick={onClose} className="flex-shrink-0 opacity-60 hover:opacity-100 text-[14px] leading-none">×</button>
)}
</div>
);
};
// ─── RegisterStep ────────────────────────────
interface RegisterStepProps {
onSuccess: (s: ClientSession) => void;
@@ -112,23 +50,17 @@ const RegisterStep: FC<RegisterStepProps> = ({ onSuccess }) => {
if (!validate()) return;
setLoading(true);
setApiError("");
try {
const res = await fetch(`${API_BASE}/client`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: form.name.trim(),
email: form.email.trim(),
phone: form.phone.trim(),
companyName: form.companyName.trim() || undefined,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.message ?? "فشل التسجيل");
// save session and proceed
try {
const data = await clientService.create({
name: form.name.trim(),
email: form.email.trim(),
phone: form.phone.trim(),
companyName: form.companyName.trim() || undefined,
});
const session: ClientSession = {
id: data.id ?? data._id,
id: data.id,
name: data.name,
email: data.email,
phone: data.phone,
@@ -151,7 +83,7 @@ const RegisterStep: FC<RegisterStepProps> = ({ onSuccess }) => {
<div dir="rtl">
<div className="mb-6">
<div className="inline-flex items-center gap-1.5 bg-[#EBF3FF] border border-[#BFDBFE] rounded-full px-3 py-1 text-[11px] font-bold text-[#1E3A8A] mb-3">
<span className="w-1.5 h-1.5 rounded-full bg-[#1A73E8]" />
<span aria-hidden="true" className="w-1.5 h-1.5 rounded-full bg-[#1A73E8]" />
عميل جديد
</div>
<h2 className="text-[22px] font-bold text-[#111827] tracking-tight leading-tight">إنشاء حساب جديد</h2>
@@ -171,7 +103,7 @@ const RegisterStep: FC<RegisterStepProps> = ({ onSuccess }) => {
value={form.name}
onChange={set("name")}
error={errors.name}
icon={<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>}
autoComplete="name"
/>
<Input
label="البريد الإلكتروني *"
@@ -180,7 +112,7 @@ const RegisterStep: FC<RegisterStepProps> = ({ onSuccess }) => {
value={form.email}
onChange={set("email")}
error={errors.email}
icon={<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="2,4 12,13 22,4"/></svg>}
autoComplete="email"
/>
<Input
label="رقم الجوال *"
@@ -189,21 +121,20 @@ const RegisterStep: FC<RegisterStepProps> = ({ onSuccess }) => {
value={form.phone}
onChange={set("phone")}
error={errors.phone}
icon={<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 10.5 19.79 19.79 0 0 1 1.64 1.9a2 2 0 0 1 1.99-2.18h3a2 2 0 0 1 2 1.72c.127.96.36 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 7.91a16 16 0 0 0 6.18 6.18l.96-.96a2 2 0 0 1 2.11-.45c.907.34 1.85.573 2.81.7A2 2 0 0 1 21.9 16.9l.02.02z"/></svg>}
autoComplete="tel"
/>
<Input
label="اسم الشركة (اختياري)"
placeholder="شركة لوجي فلو للتوصيل"
value={form.companyName}
onChange={set("companyName")}
icon={<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="2" y="7" width="20" height="15" rx="1"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/></svg>}
autoComplete="organization"
/>
<div className="pt-2">
<PrimaryBtn type="submit" loading={loading} className="w-full">
<Button type="submit" loading={loading} fullWidth>
إنشاء الحساب
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="15 18 9 12 15 6"/></svg>
</PrimaryBtn>
</Button>
</div>
</form>
</div>