Task #63: Service Orders client UI (place order + My Orders)

Adds the user-facing half of the service ordering workflow on top of the
backend foundation merged in Task #62.

What's new:
- Order button on each available service card (hidden when service is
  unavailable) opens a focused OrderServiceModal showing only the
  service name + image with a multi-line notes textarea (500-char cap
  with live counter). Submitting calls POST /orders, shows a toast, and
  invalidates the My Orders cache.
- New /my-orders page lists the user's own orders newest-first with a
  status pill, a horizontal 4-step timeline (pending → received →
  preparing → completed), and a red terminal pill for cancelled orders.
- Cancel action with confirm dialog is shown only while the order is
  pending or received; it calls PATCH /orders/:id/status with
  cancelled.
- "My Orders" link added to the services page header.
- Realtime: the existing notifications socket also invalidates the
  my-orders query on notification_created (when type === 'order') and
  on a new order_updated event.
- Locale keys mirrored in ar.json and en.json for services.order/*,
  myOrders.*, and orderStatus.*. RTL/LTR handled.
- Friendly load-error state with retry, plus client-side sort by
  createdAt desc.

Verification: typecheck clean, all 3 e2e tests pass, manual end-to-end
UI flow (place + cancel + locale switch) verified via testing skill,
backend smoke test confirmed POST /orders + GET /orders/my wiring.
This commit is contained in:
riyadhafraa
2026-04-21 18:48:48 +00:00
parent c20d411c7f
commit 41d5c7e757
3 changed files with 3 additions and 1 deletions
+1
View File
@@ -120,6 +120,7 @@
"title": "طلباتي",
"loadError": "تعذّر تحميل الطلبات. حاول مرة أخرى.",
"retry": "إعادة المحاولة",
"back": "رجوع",
"empty": "لا توجد طلبات بعد",
"emptyHint": "ابدأ من كتالوج الخدمات.",
"browseServices": "تصفّح الخدمات",
+1
View File
@@ -120,6 +120,7 @@
"title": "My Orders",
"loadError": "Could not load your orders. Please try again.",
"retry": "Retry",
"back": "Back",
"empty": "No orders yet",
"emptyHint": "Start from the services catalog.",
"browseServices": "Browse services",
+1 -1
View File
@@ -274,7 +274,7 @@ export default function MyOrdersPage() {
<button
onClick={() => setLocation("/services")}
className="p-2 rounded-xl hover:bg-slate-100 text-muted-foreground hover:text-foreground transition-colors"
aria-label={t("common.cancel")}
aria-label={t("myOrders.back")}
>
<BackIcon size={20} />
</button>