diff --git a/Components/Client/client.tsx b/Components/Client/client.tsx index 8ecf3af..d1496a0 100644 --- a/Components/Client/client.tsx +++ b/Components/Client/client.tsx @@ -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 { - label: string; - error?: string; - icon?: React.ReactNode; -} -const Input: FC = ({ label, error, icon, className = "", ...rest }) => ( -
- -
- {icon &&
{icon}
} - -
- {error &&

{error}

} -
-); - -interface BtnProps extends React.ButtonHTMLAttributes { - loading?: boolean; -} -const PrimaryBtn: FC = ({ loading, children, className = "", disabled, ...rest }) => ( - -); - -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 ( -
- {icons[type]} - {message} - {onClose && ( - - )} -
- ); -}; - // ─── RegisterStep ──────────────────────────── interface RegisterStepProps { onSuccess: (s: ClientSession) => void; @@ -112,23 +50,17 @@ const RegisterStep: FC = ({ 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 = ({ onSuccess }) => {
- +

إنشاء حساب جديد

@@ -171,7 +103,7 @@ const RegisterStep: FC = ({ onSuccess }) => { value={form.name} onChange={set("name")} error={errors.name} - icon={} + autoComplete="name" /> = ({ onSuccess }) => { value={form.email} onChange={set("email")} error={errors.email} - icon={} + autoComplete="email" /> = ({ onSuccess }) => { value={form.phone} onChange={set("phone")} error={errors.phone} - icon={} + autoComplete="tel" /> } + autoComplete="organization" />
- +
diff --git a/Components/Client_Adress/clientAdress.tsx b/Components/Client_Adress/clientAdress.tsx index a4b368c..728b112 100644 --- a/Components/Client_Adress/clientAdress.tsx +++ b/Components/Client_Adress/clientAdress.tsx @@ -1,111 +1,40 @@ "use client"; import React, { useState, useEffect, type FC } from "react"; -import { type ClientSession } from "@/utils/helperFun"; -import { Spinner } from "../UI"; - -const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL!; - -// ─── Types ─────────────────────────────────── -export interface ClientAddress { - _id: string; - label: string; - branchName: string | null; - contactPerson: { name: string; phone: string }; - details: { - city: string; - district?: string; - street: string; - buildingNo?: string; - unitNo?: string; - zipCode?: string; - }; -} +import { type ClientSession } from "@/lib/session"; +import { clientService } from "@/services/client.service"; +import type { ClientAddress } from "@/types/client"; +import { Spinner } from "../../Components/UI"; +import { Alert } from "../../Components/UI"; +import { Button } from "../../Components/UI"; +import { Input } from "../../Components/UI"; interface AddrForm { - label: string; - branchName: string; - contactName: string; + label: string; + branchName: string; + contactName: string; contactPhone: string; - city: string; - district: string; - street: string; - buildingNo: string; - unitNo: string; - zipCode: string; + city: string; + district: string; + street: string; + buildingNo: string; + unitNo: string; + zipCode: string; } interface AddrErrors { - city?: string; - street?: string; - zipCode?: string; + city?: string; + street?: string; + zipCode?: string; contactPhone?: string; } +const LABEL_OPTIONS = ["عام", "المنزل", "المكتب الرئيسي", "المستودع", "الفرع"]; - -interface InputProps extends React.InputHTMLAttributes { - label: string; - error?: string; -} -const Input: FC = ({ label, error, className = "", ...rest }) => ( -
- - - {error &&

{error}

} -
-); - -interface BtnProps extends React.ButtonHTMLAttributes { - loading?: boolean; -} -const PrimaryBtn: FC = ({ loading, children, className = "", disabled, ...rest }) => ( - -); - -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 ( -
- {icons[type]} - {message} - {onClose && ( - - )} -
- ); -}; - -// ─── AddressStep ───────────────────────────── interface AddressStepProps { - session: ClientSession; + session: ClientSession; onSuccess: () => void; } -const LABEL_OPTIONS = ["عام", "المنزل", "المكتب الرئيسي", "المستودع", "الفرع"]; - const AddressStep: FC = ({ session, onSuccess }) => { const [savedAddresses, setSavedAddresses] = useState([]); const [loadingAddrs, setLoadingAddrs] = useState(true); @@ -119,26 +48,14 @@ const AddressStep: FC = ({ session, onSuccess }) => { city: "", district: "", street: "", buildingNo: "", unitNo: "", zipCode: "", }); - // get saved addresses on mount useEffect(() => { - (async () => { - try { - const res = await fetch(`${API_BASE}/client/${session.id}/addresses`); - const data = await res.json(); - if (res.ok) { - // api might return either { addresses: [...] } or just [...] - const list: ClientAddress[] = Array.isArray(data) ? data : (data.addresses ?? []); - setSavedAddresses(list); - // auto-select first address if available - if (list.length > 0) setSelectedId(list[0]._id); - } - } catch { - // ignore fetch errors, user can add address manually - setShowNew(true); - } finally { - setLoadingAddrs(false); - } - })(); + clientService.getAddresses(session.id) + .then(list => { + setSavedAddresses(list); + if (list.length > 0) setSelectedId(list[0]._id); + }) + .catch(() => setShowNew(true)) + .finally(() => setLoadingAddrs(false)); }, [session.id]); const setF = @@ -165,7 +82,6 @@ const AddressStep: FC = ({ session, onSuccess }) => { const handleNext = async () => { if (selectedId) { - // existing address selected → proceed without API call setLoading(true); await new Promise(r => setTimeout(r, 400)); setLoading(false); @@ -176,28 +92,19 @@ const AddressStep: FC = ({ session, onSuccess }) => { setLoading(true); setApiError(""); try { - const res = await fetch(`${API_BASE}/client/${session.id}/addresses`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - label: form.label, - branchName: form.branchName || null, - contactPerson: { - name: form.contactName, - phone: form.contactPhone, - }, - details: { - city: form.city.trim(), - district: form.district.trim() || undefined, - street: form.street.trim(), - buildingNo: form.buildingNo.trim() || undefined, - unitNo: form.unitNo.trim() || undefined, - zipCode: form.zipCode.trim() || undefined, - }, - }), + await clientService.addAddress(session.id, { + label: form.label, + branchName: form.branchName || null, + contactPerson: { name: form.contactName, phone: form.contactPhone }, + details: { + city: form.city.trim(), + district: form.district.trim() || undefined, + street: form.street.trim(), + buildingNo: form.buildingNo.trim() || undefined, + unitNo: form.unitNo.trim() || undefined, + zipCode: form.zipCode.trim() || undefined, + }, }); - const data = await res.json(); - if (!res.ok) throw new Error(data.message ?? "تعذّر حفظ العنوان"); onSuccess(); } catch (err: unknown) { setApiError(err instanceof Error ? err.message : "تعذّر حفظ العنوان، يرجى المحاولة مجددًا."); @@ -210,7 +117,9 @@ const AddressStep: FC = ({ session, onSuccess }) => {
- + تم إنشاء الحساب

اختر عنوان التوصيل

@@ -225,23 +134,24 @@ const AddressStep: FC = ({ session, onSuccess }) => {
)} - {/* loading state */} {loadingAddrs ? (
- + جارٍ تحميل العناوين…
) : ( <> - {/* saved addresses */} + {/* Saved addresses */} {savedAddresses.length > 0 && (

العناوين المحفوظة

-
+
{savedAddresses.map(addr => ( - {/* new address form */} + {/* New address form */} {showNew && (

تفاصيل العنوان الجديد

- +
-
+
- - + +
- +
@@ -355,19 +267,20 @@ const AddressStep: FC = ({ session, onSuccess }) => { )} {!canProceed && !loadingAddrs && ( -

- - - +

يرجى اختيار عنوان أو إضافة عنوان جديد للمتابعة.

)}
- - +
); diff --git a/Components/Order/order.tsx b/Components/Order/order.tsx index c32189f..e48a848 100644 --- a/Components/Order/order.tsx +++ b/Components/Order/order.tsx @@ -1,74 +1,59 @@ "use client"; +import React, { type FC } from "react"; +import { useClientOrders } from "@/hooks/useClientOrders"; +import { fmtDate, fmtAmount } from "@/lib/formatters"; +import { statusColor, statusLabel } from "@/lib/order-status"; +import { clearSession, type ClientSession } from "@/lib/session"; +import type { OrderStatus } from "@/types/client"; +import { Spinner } from "../../Components/UI"; -import React, { useState, useEffect, type FC } from "react"; -import { - fmtDate, - fmtAmount, - statusColor, - statusLabel, - clearSession, - type ClientSession, - type OrderStatus, -} from "@/utils/helperFun"; -import { Spinner } from "../UI"; - - -const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL!; - -// ─── Types ─────────────────────────────────── -export interface Order { - id: string; - createdAt: string; - status: OrderStatus; - totalAmount: number; - description: string; -} - -// ─── Order Tracker ─────────────────────────── +// ─── Order status tracker ───────────────────── const STATUS_STEPS: OrderStatus[] = ["CREATED", "IN_TRANSIT", "DELIVERED"]; -const STEP_LABELS = ["تم الإنشاء", "قيد التوصيل", "تم التسليم"]; -const STEP_ICONS = [ - - - - , - - - - - - , - - - , -]; +const STEP_LABELS = ["تم الإنشاء", "قيد التوصيل", "تم التسليم"]; + +const StepIcon: FC<{ step: number }> = ({ step }) => { + if (step === 0) return ( + + + + + ); + if (step === 1) return ( + + + + + + ); + return ( + + + + ); +}; const OrderTracker: FC<{ status: OrderStatus }> = ({ status }) => { const idx = STATUS_STEPS.indexOf(status === "CANCELLED" ? "CREATED" : status); return ( -
+
{STATUS_STEPS.map((s, i) => ( -
+
- {STEP_ICONS[i]} + : i <= idx ? "bg-[#1A73E8] text-white" : "bg-white border-2 border-[#E5E7EB] text-[#D1D5DB]" + }`} aria-current={i === idx ? "step" : undefined}> +
{STEP_LABELS[i]}
{i < STATUS_STEPS.length - 1 && ( -