From 41e86d30b3af11d7bf6948665594ad48b6c25db6 Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Sat, 29 Aug 2026 11:48:30 +0330 Subject: [PATCH 1/7] add empty order component & server error component & orders skeleton & order item --- src/app/(customer)/(shop)/orders/page.tsx | 89 ++++++++++++++-------- src/components/orders/OrderEmpty.tsx | 19 +++++ src/components/orders/OrderItem.tsx | 74 ++++++++++++++++++ src/components/orders/OrderServerError.tsx | 19 +++++ src/components/orders/OrdersSkeleton.tsx | 47 ++++++++++++ src/proxy.ts | 2 +- 6 files changed, 216 insertions(+), 34 deletions(-) create mode 100644 src/components/orders/OrderEmpty.tsx create mode 100644 src/components/orders/OrderItem.tsx create mode 100644 src/components/orders/OrderServerError.tsx create mode 100644 src/components/orders/OrdersSkeleton.tsx diff --git a/src/app/(customer)/(shop)/orders/page.tsx b/src/app/(customer)/(shop)/orders/page.tsx index ecdd790..42f4ee1 100644 --- a/src/app/(customer)/(shop)/orders/page.tsx +++ b/src/app/(customer)/(shop)/orders/page.tsx @@ -2,42 +2,65 @@ import Link from "next/link"; import { requireSession } from "@/lib/auth/session"; import { getOrderGroups } from "@/lib/api/orders"; import { PAYMENT_STATUS_LABELS_FA } from "@/lib/order-status"; +import OrderItem from "@/components/orders/OrderItem"; +import OrderEmpty from "@/components/orders/OrderEmpty"; +import OrderServerError from "@/components/orders/OrderServerError"; +import OrdersSkeleton from "@/components/orders/OrdersSkeleton"; export default async function OrdersPage() { - await requireSession("/orders"); - const orderGroups = await getOrderGroups(); + return
+ + {/* */} + {/* */} +{/* */} + + {/* + + + + + + + + + + + */} +
+ // await requireSession("/orders"); + // const orderGroups = await getOrderGroups(); - return ( -
-

سفارش‌های من

+ // return ( + //
+ //

سفارش‌های من

- {orderGroups.length === 0 &&

هنوز سفارشی ثبت نکرده‌اید.

} + // {orderGroups.length === 0 &&

هنوز سفارشی ثبت نکرده‌اید.

} -
- {orderGroups.map((group) => ( - -
- {new Date(group.created_at).toLocaleDateString("fa-IR")} - {PAYMENT_STATUS_LABELS_FA[group.payment_status]} -
-
- {group.orders.map((order) => ( - - {order.store.name} - - ))} -
-
- مجموع - {group.total_amount.toLocaleString("fa-IR")} تومان -
- - ))} -
-
- ); + //
+ // {orderGroups.map((group) => ( + // + //
+ // {new Date(group.created_at).toLocaleDateString("fa-IR")} + // {PAYMENT_STATUS_LABELS_FA[group.payment_status]} + //
+ //
+ // {group.orders.map((order) => ( + // + // {order.store.name} + // + // ))} + //
+ //
+ // مجموع + // {group.total_amount.toLocaleString("fa-IR")} تومان + //
+ // + // ))} + //
+ //
+ // ); } diff --git a/src/components/orders/OrderEmpty.tsx b/src/components/orders/OrderEmpty.tsx new file mode 100644 index 0000000..e7c9452 --- /dev/null +++ b/src/components/orders/OrderEmpty.tsx @@ -0,0 +1,19 @@ +import { ShoppingBagIcon } from 'lucide-react' +import React from 'react' + +const OrderEmpty = () => { + return ( +
+
+
+ +
+

هنوز سفارشی ثبت نکرده‌اید

+

لیست سفارش‌های شما در حال حاضر خالی است. برای خرید از فروشگاه‌های اطراف روی دکمه زیر کلیک کنید.

+ +
+
+ ) +} + +export default OrderEmpty \ No newline at end of file diff --git a/src/components/orders/OrderItem.tsx b/src/components/orders/OrderItem.tsx new file mode 100644 index 0000000..e721e62 --- /dev/null +++ b/src/components/orders/OrderItem.tsx @@ -0,0 +1,74 @@ +import React from 'react'; + +const OrderItem = () => { + const order = { + id: 1, + restaurant: 'فست‌فود برگر', + description: 'ساندویچ + نوشیدنی', + status: 'در حال آماده‌سازی', + statusColor: 'bg-orange-100 text-orange-700', + time: 'امروز، 11:30', + itemsCount: 3, + totalPrice: 45000, + images: Array.from({ length: 25 }, (_, i) => ({ + id: i, + src: `https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQqIof_DOo6I5-oOLJ1V9_HPfSudXfhewy_5C5Ry7YOO-UXSZ1jvK7-0hw&s=10`, // عکس رندوم + alt: `product-${i}`, + })), + }; + + return ( +
+
+
+
+ {order.restaurant.charAt(0)} +
+
+

{order.restaurant}

+

{order.description}

+
+
+ + {order.status} + +
+ +
+ +
+
+ {order.images.map((img) => ( +
+ {img.alt} +
+ ))} +
+ +
+
+ {order.time} +
+
+ {order.itemsCount} کالا • {order.totalPrice.toLocaleString('fa-IR')} تومان +
+
+
+ + +
+ ); +}; + +export default OrderItem; \ No newline at end of file diff --git a/src/components/orders/OrderServerError.tsx b/src/components/orders/OrderServerError.tsx new file mode 100644 index 0000000..afde5c6 --- /dev/null +++ b/src/components/orders/OrderServerError.tsx @@ -0,0 +1,19 @@ +import { ServerCrashIcon } from 'lucide-react' +import React from 'react' + +const OrderServerError = () => { + return ( +
+
+
+ +
+

خطا در برقراری ارتباط

+

متأسفانه در دریافت اطلاعات سفارش‌ها مشکلی پیش آمده است. لطفاً اتصال اینترنت خود را بررسی کرده و مجدداً تلاش کنید.

+ +
+
+ ) +} + +export default OrderServerError \ No newline at end of file diff --git a/src/components/orders/OrdersSkeleton.tsx b/src/components/orders/OrdersSkeleton.tsx new file mode 100644 index 0000000..ebf3b7f --- /dev/null +++ b/src/components/orders/OrdersSkeleton.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +const OrdersSkeleton = () => { + return ( +
+ {Array.from({ length: 10 }).map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ {Array.from({ length: 3 }).map((_, imgIndex) => ( +
+ ))} +
+ +
+
+
+
+
+ +
+
+ ))} +
+ ); +}; + +export default OrdersSkeleton; \ No newline at end of file diff --git a/src/proxy.ts b/src/proxy.ts index c834035..25033f6 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -8,7 +8,7 @@ const AUTH_REQUIRED_PREFIXES = [ "/seller", "/cart", "/checkout", - "/orders", + // "/orders", "/order-groups", "/addresses", "/account", -- 2.45.3 From 6d917c6cfa0fc62481489fe940f67a92a0714692 Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Sun, 30 Aug 2026 10:44:04 +0330 Subject: [PATCH 2/7] change more files --- .../orders/{ => (routes)/(landing)}/page.tsx | 27 ++++++++----- .../(shop)/orders/(routes)/[orderId]/page.tsx | 9 +++++ src/app/(customer)/(shop)/orders/layout.tsx | 15 +++++++ .../{OrderEmpty.tsx => order-empty.tsx} | 0 src/components/orders/order-header.tsx | 39 +++++++++++++++++++ .../orders/{OrderItem.tsx => order-item.tsx} | 12 +++++- ...ServerError.tsx => order-server-error.tsx} | 0 ...OrdersSkeleton.tsx => orders-skeleton.tsx} | 0 src/proxy.ts | 2 +- 9 files changed, 93 insertions(+), 11 deletions(-) rename src/app/(customer)/(shop)/orders/{ => (routes)/(landing)}/page.tsx (79%) create mode 100644 src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx create mode 100644 src/app/(customer)/(shop)/orders/layout.tsx rename src/components/orders/{OrderEmpty.tsx => order-empty.tsx} (100%) create mode 100644 src/components/orders/order-header.tsx rename src/components/orders/{OrderItem.tsx => order-item.tsx} (88%) rename src/components/orders/{OrderServerError.tsx => order-server-error.tsx} (100%) rename src/components/orders/{OrdersSkeleton.tsx => orders-skeleton.tsx} (100%) diff --git a/src/app/(customer)/(shop)/orders/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx similarity index 79% rename from src/app/(customer)/(shop)/orders/page.tsx rename to src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx index 42f4ee1..36b7e6e 100644 --- a/src/app/(customer)/(shop)/orders/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx @@ -2,18 +2,27 @@ import Link from "next/link"; import { requireSession } from "@/lib/auth/session"; import { getOrderGroups } from "@/lib/api/orders"; import { PAYMENT_STATUS_LABELS_FA } from "@/lib/order-status"; -import OrderItem from "@/components/orders/OrderItem"; -import OrderEmpty from "@/components/orders/OrderEmpty"; -import OrderServerError from "@/components/orders/OrderServerError"; -import OrdersSkeleton from "@/components/orders/OrdersSkeleton"; +import OrderItem from "@/components/orders/order-item"; +import OrderEmpty from "@/components/orders/order-empty"; +import OrderServerError from "@/components/orders/order-server-error"; +import OrdersSkeleton from "@/components/orders/orders-skeleton"; +import OrderHeader from "@/components/orders/order-header"; export default async function OrdersPage() { - return
- + return
+ + { + Array.from({ length: 20 }, (_, index) => ) + } + {/* + + */} + + {/* */} {/* */} {/* */} -{/* */} - + {/* */} + {/* @@ -26,7 +35,7 @@ export default async function OrdersPage() { */} -
+ // await requireSession("/orders"); // const orderGroups = await getOrderGroups(); diff --git a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx new file mode 100644 index 0000000..c2039ef --- /dev/null +++ b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const OrderDetail = () => { + return ( +
OrderDetail
+ ) +} + +export default OrderDetail \ No newline at end of file diff --git a/src/app/(customer)/(shop)/orders/layout.tsx b/src/app/(customer)/(shop)/orders/layout.tsx new file mode 100644 index 0000000..a4562ae --- /dev/null +++ b/src/app/(customer)/(shop)/orders/layout.tsx @@ -0,0 +1,15 @@ +import OrderHeader from '@/components/orders/order-header'; +import React from 'react' + +interface OrderLayoutInterface { + children: React.ReactNode; +} + +const OrderLayout = async ({ children }: OrderLayoutInterface) => { + return
+ + {children} +
+} + +export default OrderLayout \ No newline at end of file diff --git a/src/components/orders/OrderEmpty.tsx b/src/components/orders/order-empty.tsx similarity index 100% rename from src/components/orders/OrderEmpty.tsx rename to src/components/orders/order-empty.tsx diff --git a/src/components/orders/order-header.tsx b/src/components/orders/order-header.tsx new file mode 100644 index 0000000..bc46b24 --- /dev/null +++ b/src/components/orders/order-header.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { ArrowRight, ShoppingBagIcon } from 'lucide-react' +import { usePathname, useRouter } from 'next/navigation'; + +const OrderHeader = () => { + + const router = useRouter(); + const pathname = usePathname(); + const isExactOrders = pathname === '/orders' + + const onBackHandler = () => { + router.back(); + } + + const goToCartPage = () => { + router.push('/cart'); + } + + return
+
+ +

سفارش‌ها

+
+ {isExactOrders && +
+
+ +
+ + + 90 + +
+ } +
+} + +export default OrderHeader \ No newline at end of file diff --git a/src/components/orders/OrderItem.tsx b/src/components/orders/order-item.tsx similarity index 88% rename from src/components/orders/OrderItem.tsx rename to src/components/orders/order-item.tsx index e721e62..2aecb68 100644 --- a/src/components/orders/OrderItem.tsx +++ b/src/components/orders/order-item.tsx @@ -1,6 +1,13 @@ +'use client'; + +import { useRouter } from 'next/navigation'; import React from 'react'; const OrderItem = () => { + const router = useRouter(); + + const goToOrderDetail = () => router.push(`/orders/uuid`) + const order = { id: 1, restaurant: 'فست‌فود برگر', @@ -64,7 +71,10 @@ const OrderItem = () => {
-
diff --git a/src/components/orders/OrderServerError.tsx b/src/components/orders/order-server-error.tsx similarity index 100% rename from src/components/orders/OrderServerError.tsx rename to src/components/orders/order-server-error.tsx diff --git a/src/components/orders/OrdersSkeleton.tsx b/src/components/orders/orders-skeleton.tsx similarity index 100% rename from src/components/orders/OrdersSkeleton.tsx rename to src/components/orders/orders-skeleton.tsx diff --git a/src/proxy.ts b/src/proxy.ts index 25033f6..c834035 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -8,7 +8,7 @@ const AUTH_REQUIRED_PREFIXES = [ "/seller", "/cart", "/checkout", - // "/orders", + "/orders", "/order-groups", "/addresses", "/account", -- 2.45.3 From 8ff9266c554ef209769e1f7cdc5942611d1ed321 Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Mon, 31 Aug 2026 16:23:06 +0330 Subject: [PATCH 3/7] order detail first commit --- .../(shop)/orders/(routes)/(landing)/page.tsx | 1 + .../(shop)/orders/(routes)/[orderId]/page.tsx | 10 +- src/components/orders/order-empty.tsx | 8 +- .../orders/order-stepper-status.tsx | 103 ++++++++++++++++++ 4 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 src/components/orders/order-stepper-status.tsx diff --git a/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx index 36b7e6e..3a83cda 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx @@ -10,6 +10,7 @@ import OrderHeader from "@/components/orders/order-header"; export default async function OrdersPage() { return
+ {/* */} { Array.from({ length: 20 }, (_, index) => ) diff --git a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx index c2039ef..fbeef54 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx @@ -1,9 +1,13 @@ +import OrderStepperStatus from '@/components/orders/order-stepper-status' import React from 'react' const OrderDetail = () => { - return ( -
OrderDetail
- ) + return
+ +

در حال آماده‌سازی

+ + +
} export default OrderDetail \ No newline at end of file diff --git a/src/components/orders/order-empty.tsx b/src/components/orders/order-empty.tsx index e7c9452..3e434fc 100644 --- a/src/components/orders/order-empty.tsx +++ b/src/components/orders/order-empty.tsx @@ -1,16 +1,16 @@ import { ShoppingBagIcon } from 'lucide-react' -import React from 'react' +import Link from 'next/link' const OrderEmpty = () => { return (
- +

هنوز سفارشی ثبت نکرده‌اید

-

لیست سفارش‌های شما در حال حاضر خالی است. برای خرید از فروشگاه‌های اطراف روی دکمه زیر کلیک کنید.

- +

لیست سفارش‌های شما در حال حاضر خالی است. برای خرید از فروشگاه‌های اطراف روی دکمه زیر کلیک کنید.

+ شروع خرید
) diff --git a/src/components/orders/order-stepper-status.tsx b/src/components/orders/order-stepper-status.tsx new file mode 100644 index 0000000..8d0cba9 --- /dev/null +++ b/src/components/orders/order-stepper-status.tsx @@ -0,0 +1,103 @@ +import React from 'react' +import { Check } from 'lucide-react' + +type StepStatus = 'completed' | 'current' | 'upcoming' + +interface Step { + label: string + status: StepStatus +} + +interface OrderStepperStatusProps { + /** ایندکس مرحله فعلی (۰-based). مراحل قبل completed، خودش current، بعد upcoming */ + currentStep?: number + steps?: string[] +} + +const DEFAULT_STEPS = [ + 'ثبت شد', + 'پذیرفته شد', + 'آماده‌سازی', + 'ارسال شد', + 'تحویل', +] + +const OrderStepperStatus = ({ + currentStep = 2, + steps = DEFAULT_STEPS, +}: OrderStepperStatusProps) => { + const computedSteps: Step[] = steps.map((label, index) => { + if (index < currentStep) return { label, status: 'completed' } + if (index === currentStep) return { label, status: 'current' } + return { label, status: 'upcoming' } + }) + + return ( +
+ {/* ردیف دایره‌ها و خطوط — خط دقیقاً به لبه دایره می‌چسبد */} +
+ {computedSteps.map((step, index) => { + const isLast = index === computedSteps.length - 1 + const isCompleted = step.status === 'completed' + const isCurrent = step.status === 'current' + const isUpcoming = step.status === 'upcoming' + + const circleBg = isUpcoming ? 'bg-gray-300' : 'bg-primary-action' + const nextLineBg = + index < currentStep ? 'bg-primary-action' : 'bg-gray-300' + + return ( + +
+ {isCompleted && ( + + )} + {isCurrent && ( +
+ )} +
+ + {!isLast && ( +
+ )} + + ) + })} +
+ + {/* ردیف لیبل‌ها — دقیقاً زیر هر دایره */} +
+ {computedSteps.map((step, index) => { + const isLast = index === computedSteps.length - 1 + const isUpcoming = step.status === 'upcoming' + const textColor = isUpcoming + ? 'text-gray-500' + : 'text-primary-action' + + return ( + +
+ + {step.label} + +
+ {!isLast &&
} + + ) + })} +
+
+ ) +} + +export default OrderStepperStatus \ No newline at end of file -- 2.45.3 From f4259dee2633cb3f5f8b45508ec058bd1f0e8221 Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Tue, 1 Sep 2026 09:43:44 +0330 Subject: [PATCH 4/7] feat: add order components and server error handling --- public/server-error-icon.svg | 3 + .../(shop)/orders/(routes)/(landing)/page.tsx | 2 +- .../(shop)/orders/(routes)/[orderId]/page.tsx | 13 +++- src/app/(customer)/(shop)/orders/layout.tsx | 2 +- src/components/orders/order-header.tsx | 59 ++++++++------- src/components/orders/order-item.tsx | 38 ++++++---- src/components/orders/order-items.tsx | 20 +++++ .../orders/order-payment-sticky.tsx | 34 +++++++++ src/components/orders/order-server-error.tsx | 19 +++-- .../orders/order-stepper-status.tsx | 17 ++--- .../orders/order-time-address-info-alert.tsx | 23 ++++++ src/components/orders/orders-skeleton.tsx | 74 +++++++++---------- 12 files changed, 205 insertions(+), 99 deletions(-) create mode 100644 public/server-error-icon.svg create mode 100644 src/components/orders/order-items.tsx create mode 100644 src/components/orders/order-payment-sticky.tsx create mode 100644 src/components/orders/order-time-address-info-alert.tsx diff --git a/public/server-error-icon.svg b/public/server-error-icon.svg new file mode 100644 index 0000000..e3a5791 --- /dev/null +++ b/public/server-error-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx index 3a83cda..a348c2e 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx @@ -11,7 +11,7 @@ import OrderHeader from "@/components/orders/order-header"; export default async function OrdersPage() { return
{/* */} - + {/* */} { Array.from({ length: 20 }, (_, index) => ) } diff --git a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx index fbeef54..790f486 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx @@ -1,11 +1,18 @@ +import OrderItems from '@/components/orders/order-items' +import OrderPaymentSticky from '@/components/orders/order-payment-sticky' import OrderStepperStatus from '@/components/orders/order-stepper-status' +import OrderTimeAddressInfoAlert from '@/components/orders/order-time-address-info-alert' import React from 'react' const OrderDetail = () => { - return
- -

در حال آماده‌سازی

+ return
+ +

در حال آماده‌سازی

+ + + +
} diff --git a/src/app/(customer)/(shop)/orders/layout.tsx b/src/app/(customer)/(shop)/orders/layout.tsx index a4562ae..3bff83a 100644 --- a/src/app/(customer)/(shop)/orders/layout.tsx +++ b/src/app/(customer)/(shop)/orders/layout.tsx @@ -6,7 +6,7 @@ interface OrderLayoutInterface { } const OrderLayout = async ({ children }: OrderLayoutInterface) => { - return
+ return
{children}
diff --git a/src/components/orders/order-header.tsx b/src/components/orders/order-header.tsx index bc46b24..cd6ea49 100644 --- a/src/components/orders/order-header.tsx +++ b/src/components/orders/order-header.tsx @@ -1,39 +1,44 @@ 'use client'; -import { ArrowRight, ShoppingBagIcon } from 'lucide-react' +import { ArrowRight, ShoppingBagIcon } from 'lucide-react'; import { usePathname, useRouter } from 'next/navigation'; const OrderHeader = () => { + const router = useRouter(); + const pathname = usePathname(); + const isExactOrders = pathname === '/orders'; - const router = useRouter(); - const pathname = usePathname(); - const isExactOrders = pathname === '/orders' + const onBackHandler = () => { + router.back(); + }; - const onBackHandler = () => { - router.back(); - } + const goToCartPage = () => { + router.push('/cart'); + }; - const goToCartPage = () => { - router.push('/cart'); - } + return ( +
+
+ +

سفارش‌ها

+
- return
-
- -

سفارش‌ها

+ {isExactOrders && ( +
+
+ +
+ + + 90 +
- {isExactOrders && -
-
- -
- - - 90 - -
- } + )}
-} + ); +}; -export default OrderHeader \ No newline at end of file +export default OrderHeader; \ No newline at end of file diff --git a/src/components/orders/order-item.tsx b/src/components/orders/order-item.tsx index 2aecb68..6dd23b9 100644 --- a/src/components/orders/order-item.tsx +++ b/src/components/orders/order-item.tsx @@ -6,38 +6,48 @@ import React from 'react'; const OrderItem = () => { const router = useRouter(); - const goToOrderDetail = () => router.push(`/orders/uuid`) + const goToOrderDetail = () => router.push(`/orders/uuid`); + + const statusClasses: Record = { + 'ارسال شد': 'bg-teal-bg text-primary-action', + 'در حال آماده‌سازی': 'bg-[#E5F3FF] text-[#007BFF]', + 'در انتظار تأیید': 'bg-[#FFF4E5] text-[#E67E22]', + 'آماده تحویل حضوری': 'bg-[#EBE5FF] text-[#734DBF]', + 'لغو شد': 'bg-[#FCE8E8] text-error', + 'تحویل داده شد': 'bg-emerald-bg text-success', + }; const order = { id: 1, restaurant: 'فست‌فود برگر', description: 'ساندویچ + نوشیدنی', - status: 'در حال آماده‌سازی', - statusColor: 'bg-orange-100 text-orange-700', + status: 'آماده تحویل حضوری', time: 'امروز، 11:30', itemsCount: 3, totalPrice: 45000, - images: Array.from({ length: 25 }, (_, i) => ({ + images: Array.from({ length: 5 }, (_, i) => ({ id: i, - src: `https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQqIof_DOo6I5-oOLJ1V9_HPfSudXfhewy_5C5Ry7YOO-UXSZ1jvK7-0hw&s=10`, // عکس رندوم + src: `https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQqIof_DOo6I5-oOLJ1V9_HPfSudXfhewy_5C5Ry7YOO-UXSZ1jvK7-0hw&s=10`, alt: `product-${i}`, })), }; return ( -
+
-
+
{order.restaurant.charAt(0)}

{order.restaurant}

-

{order.description}

+

{order.description}

+ {order.status} @@ -62,17 +72,19 @@ const OrderItem = () => {
-
+
{order.time}
-
- {order.itemsCount} کالا • {order.totalPrice.toLocaleString('fa-IR')} تومان +
+ + {order.itemsCount} کالا • {order.totalPrice.toLocaleString('fa-IR')} تومان +
+
+ +
+
+
+
+ ) +} + +export default OrderPaymentSticky \ No newline at end of file diff --git a/src/components/orders/order-server-error.tsx b/src/components/orders/order-server-error.tsx index afde5c6..d9764ba 100644 --- a/src/components/orders/order-server-error.tsx +++ b/src/components/orders/order-server-error.tsx @@ -3,14 +3,19 @@ import React from 'react' const OrderServerError = () => { return ( -
-
-
- +
+
+
+ {/* */} + وینسو
-

خطا در برقراری ارتباط

-

متأسفانه در دریافت اطلاعات سفارش‌ها مشکلی پیش آمده است. لطفاً اتصال اینترنت خود را بررسی کرده و مجدداً تلاش کنید.

- +

خطا در برقراری ارتباط

+

+ متأسفانه در دریافت اطلاعات سفارش‌ها مشکلی پیش آمده است. لطفاً اتصال اینترنت خود را بررسی کرده و مجدداً تلاش کنید. +

+
) diff --git a/src/components/orders/order-stepper-status.tsx b/src/components/orders/order-stepper-status.tsx index 8d0cba9..e21217e 100644 --- a/src/components/orders/order-stepper-status.tsx +++ b/src/components/orders/order-stepper-status.tsx @@ -34,15 +34,13 @@ const OrderStepperStatus = ({ return (
- {/* ردیف دایره‌ها و خطوط — خط دقیقاً به لبه دایره می‌چسبد */}
{computedSteps.map((step, index) => { const isLast = index === computedSteps.length - 1 const isCompleted = step.status === 'completed' const isCurrent = step.status === 'current' - const isUpcoming = step.status === 'upcoming' - const circleBg = isUpcoming ? 'bg-gray-300' : 'bg-primary-action' + const circleBg = step.status === 'upcoming' ? 'bg-gray-300' : 'bg-primary-action' const nextLineBg = index < currentStep ? 'bg-primary-action' : 'bg-gray-300' @@ -55,10 +53,10 @@ const OrderStepperStatus = ({ `} > {isCompleted && ( - + )} {isCurrent && ( -
+
)}
@@ -70,14 +68,13 @@ const OrderStepperStatus = ({ })}
- {/* ردیف لیبل‌ها — دقیقاً زیر هر دایره */}
{computedSteps.map((step, index) => { const isLast = index === computedSteps.length - 1 - const isUpcoming = step.status === 'upcoming' - const textColor = isUpcoming - ? 'text-gray-500' - : 'text-primary-action' + const textColor = + step.status === 'upcoming' + ? 'text-gray-500' + : 'text-primary-action' return ( diff --git a/src/components/orders/order-time-address-info-alert.tsx b/src/components/orders/order-time-address-info-alert.tsx new file mode 100644 index 0000000..a005d76 --- /dev/null +++ b/src/components/orders/order-time-address-info-alert.tsx @@ -0,0 +1,23 @@ +import { ClockIcon, MapPinIcon } from 'lucide-react' +import React from 'react' + +const OrderTimeAddressInfoAlert = () => { + return ( +
+
+ +

+ زمان تخمینی تحویل: ۳۰–۴۵ دقیقه دیگر +

+
+
+ +

+ نارمک، خیابان شهید دستغیب، پلاک ۱۲، واحد ۳ +

+
+
+ ) +} + +export default OrderTimeAddressInfoAlert \ No newline at end of file diff --git a/src/components/orders/orders-skeleton.tsx b/src/components/orders/orders-skeleton.tsx index ebf3b7f..233ec2e 100644 --- a/src/components/orders/orders-skeleton.tsx +++ b/src/components/orders/orders-skeleton.tsx @@ -1,47 +1,47 @@ import React from 'react'; const OrdersSkeleton = () => { - return ( -
- {Array.from({ length: 10 }).map((_, index) => ( + return ( +
+ {Array.from({ length: 10 }).map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ {Array.from({ length: 3 }).map((_, imgIndex) => (
-
-
-
-
-
-
-
-
-
-
+ key={imgIndex} + className="size-10 flex-shrink-0 rounded-lg bg-gray-200" + /> + ))} +
-
+
+
+
+
+
-
-
- {Array.from({ length: 3 }).map((_, imgIndex) => ( -
- ))} -
- -
-
-
-
-
- -
-
- ))} +
- ); + ))} +
+ ); }; export default OrdersSkeleton; \ No newline at end of file -- 2.45.3 From 59ef2707b28f5608435f53730abafa64fa87eaf2 Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Tue, 1 Sep 2026 09:58:32 +0330 Subject: [PATCH 5/7] fix: update heading background color in OrderDetail component --- src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx index 790f486..fe00623 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx @@ -7,7 +7,9 @@ import React from 'react' const OrderDetail = () => { return
-

در حال آماده‌سازی

+

+ در حال آماده‌سازی +

-- 2.45.3 From 4e3eb2de9fe31b7b7486334003a1453c348723bc Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Mon, 7 Sep 2026 09:30:09 +0330 Subject: [PATCH 6/7] dynomic orders first commit --- package-lock.json | 29 ++- .../(shop)/orders/(routes)/(landing)/page.tsx | 101 ++++------ src/app/(customer)/(shop)/orders/layout.tsx | 31 ++- src/components/orders/order-item.tsx | 179 ++++++++++-------- src/lib/api/gooyal-accounts.ts | 8 +- src/lib/providers/orders-provider.tsx | 42 ++++ 6 files changed, 234 insertions(+), 156 deletions(-) create mode 100644 src/lib/providers/orders-provider.tsx diff --git a/package-lock.json b/package-lock.json index 003039d..956d819 100644 --- a/package-lock.json +++ b/package-lock.json @@ -292,9 +292,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", "optional": true, @@ -2996,6 +2996,18 @@ "node": ">=14.0.0" } }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", @@ -3007,6 +3019,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", diff --git a/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx index a348c2e..534c2ec 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/(landing)/page.tsx @@ -1,76 +1,43 @@ -import Link from "next/link"; -import { requireSession } from "@/lib/auth/session"; -import { getOrderGroups } from "@/lib/api/orders"; -import { PAYMENT_STATUS_LABELS_FA } from "@/lib/order-status"; +"use client"; + +import { useOrders } from "@/lib/providers/orders-provider"; import OrderItem from "@/components/orders/order-item"; import OrderEmpty from "@/components/orders/order-empty"; import OrderServerError from "@/components/orders/order-server-error"; import OrdersSkeleton from "@/components/orders/orders-skeleton"; -import OrderHeader from "@/components/orders/order-header"; -export default async function OrdersPage() { - return
- {/* */} - {/* */} - { - Array.from({ length: 20 }, (_, index) => ) +export default function OrdersPage() { + const { data, error, isLoading } = useOrders(); + + if (isLoading) { + return ( +
+ +
+ ); } - {/* - - */} - {/* */} - {/* */} - {/* */} - {/* */} + if (error) { + return ( +
+ +
+ ); + } - {/* - - - - - - - - - - - */} -
- // await requireSession("/orders"); - // const orderGroups = await getOrderGroups(); + if (!data || data.length === 0) { + return ( +
+ +
+ ); + } - // return ( - //
- //

سفارش‌های من

- - // {orderGroups.length === 0 &&

هنوز سفارشی ثبت نکرده‌اید.

} - - //
- // {orderGroups.map((group) => ( - // - //
- // {new Date(group.created_at).toLocaleDateString("fa-IR")} - // {PAYMENT_STATUS_LABELS_FA[group.payment_status]} - //
- //
- // {group.orders.map((order) => ( - // - // {order.store.name} - // - // ))} - //
- //
- // مجموع - // {group.total_amount.toLocaleString("fa-IR")} تومان - //
- // - // ))} - //
- //
- // ); -} + return ( +
+ {data.map((group) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/app/(customer)/(shop)/orders/layout.tsx b/src/app/(customer)/(shop)/orders/layout.tsx index 3bff83a..549d60a 100644 --- a/src/app/(customer)/(shop)/orders/layout.tsx +++ b/src/app/(customer)/(shop)/orders/layout.tsx @@ -1,15 +1,30 @@ import OrderHeader from '@/components/orders/order-header'; -import React from 'react' +import { requireSession } from "@/lib/auth/session"; +import { getOrderGroups } from "@/lib/api/orders"; +import { OrdersProvider } from "@/lib/providers/orders-provider"; interface OrderLayoutInterface { children: React.ReactNode; } -const OrderLayout = async ({ children }: OrderLayoutInterface) => { - return
- - {children} -
-} +export default async function OrderLayout({ children }: OrderLayoutInterface) { + await requireSession("/login"); -export default OrderLayout \ No newline at end of file + let orderGroups: Awaited> | null = null; + let error: string | null = null; + + try { + orderGroups = await getOrderGroups(); + } catch (e: any) { + error = e?.message ?? "خطا در دریافت سفارش‌ها"; + } + + return ( +
+ + + {children} + +
+ ); +} \ No newline at end of file diff --git a/src/components/orders/order-item.tsx b/src/components/orders/order-item.tsx index 6dd23b9..3d60c47 100644 --- a/src/components/orders/order-item.tsx +++ b/src/components/orders/order-item.tsx @@ -1,95 +1,126 @@ 'use client'; import { useRouter } from 'next/navigation'; -import React from 'react'; +import type { OrderGroup } from "@/types/api"; +import { PAYMENT_STATUS_LABELS_FA } from "@/lib/order-status"; // اگه داری -const OrderItem = () => { +interface OrderItemProps { + orderGroup: OrderGroup; +} + +const OrderItem = ({ orderGroup }: OrderItemProps) => { const router = useRouter(); - const goToOrderDetail = () => router.push(`/orders/uuid`); - - const statusClasses: Record = { - 'ارسال شد': 'bg-teal-bg text-primary-action', - 'در حال آماده‌سازی': 'bg-[#E5F3FF] text-[#007BFF]', - 'در انتظار تأیید': 'bg-[#FFF4E5] text-[#E67E22]', - 'آماده تحویل حضوری': 'bg-[#EBE5FF] text-[#734DBF]', - 'لغو شد': 'bg-[#FCE8E8] text-error', - 'تحویل داده شد': 'bg-emerald-bg text-success', + const goToOrderDetail = () => { + router.push(`/orders/${orderGroup.uuid}`); // یا مسیر واقعی جزئیات }; - const order = { - id: 1, - restaurant: 'فست‌فود برگر', - description: 'ساندویچ + نوشیدنی', - status: 'آماده تحویل حضوری', - time: 'امروز، 11:30', - itemsCount: 3, - totalPrice: 45000, - images: Array.from({ length: 5 }, (_, i) => ({ - id: i, - src: `https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQqIof_DOo6I5-oOLJ1V9_HPfSudXfhewy_5C5Ry7YOO-UXSZ1jvK7-0hw&s=10`, - alt: `product-${i}`, - })), + // وضعیت کلی (از اولین سفارش یا payment_status) + const mainOrder = orderGroup.orders[0]; + const status = mainOrder?.status ?? "placed"; + + // مپ وضعیت به فارسی و کلاس + const statusMap: Record = { + placed: { label: "ثبت شده", className: "bg-[#FFF4E5] text-[#E67E22]" }, + preparing: { label: "در حال آماده‌سازی", className: "bg-[#E5F3FF] text-[#007BFF]" }, + ready_to_ship: { label: "آماده ارسال", className: "bg-[#EBE5FF] text-[#734DBF]" }, + handed_to_courier: { label: "ارسال شد", className: "bg-teal-bg text-primary-action" }, + delivered: { label: "تحویل داده شد", className: "bg-emerald-bg text-success" }, + cancelled: { label: "لغو شد", className: "bg-[#FCE8E8] text-error" }, }; + const currentStatus = statusMap[status] ?? { label: status, className: "bg-gray-100 text-gray-700" }; + + // نام فروشگاه‌ها + const storeNames = orderGroup.orders.map(o => o.store.name).join(" + "); + + // تعداد کل کالاها + const totalItems = orderGroup.orders.reduce((sum, order) => { + return sum + order.items.reduce((s, item) => s + item.quantity, 0); + }, 0); + + // تصاویر محصولات (حداکثر ۵ تا) + const images = orderGroup.orders + .flatMap(order => order.items) + .slice(0, 5) + .map((item, index) => ({ + id: index, + src: "", // اگر تصویر محصول داری اینجا بذار، فعلاً خالی + alt: item.product_name_snapshot, + })); + + // تاریخ و ساعت + const createdDate = new Date(orderGroup.created_at); + const timeText = createdDate.toLocaleDateString("fa-IR", { + weekday: "long", + hour: "2-digit", + minute: "2-digit", + }); + return ( -
-
-
-
- {order.restaurant.charAt(0)} -
-
-

{order.restaurant}

-

{order.description}

-
-
- - - {order.status} - -
- -
- -
-
- {order.images.map((img) => ( -
- {img.alt} +
+ {/* هدر */} +
+
+
+ {mainOrder?.store.logo_url ? ( + {mainOrder.store.name} + ) : ( + mainOrder?.store.name?.charAt(0) ?? "س" + )}
- ))} +
+

{storeNames}

+

+ {orderGroup.orders.length > 1 + ? `${orderGroup.orders.length} فروشگاه` + : mainOrder?.items[0]?.product_name_snapshot ?? "سفارش"} +

+
+
+ + + {currentStatus.label} +
-
-
- {order.time} +
+ + {/* پایین کارت */} +
+
+ {images.length > 0 ? ( + images.map((img) => ( +
+ {img.src ? ( + {img.alt} + ) : ( +
+ {img.alt.charAt(0)} +
+ )} +
+ )) + ) : ( +
بدون تصویر
+ )}
-
- - {order.itemsCount} کالا • {order.totalPrice.toLocaleString('fa-IR')} تومان - + +
+
{timeText}
+
+ {totalItems} کالا • {orderGroup.total_amount.toLocaleString("fa-IR")} تومان +
+ +
- - -
); }; diff --git a/src/lib/api/gooyal-accounts.ts b/src/lib/api/gooyal-accounts.ts index 92a0e16..041b5ad 100644 --- a/src/lib/api/gooyal-accounts.ts +++ b/src/lib/api/gooyal-accounts.ts @@ -1,7 +1,7 @@ import "server-only"; import { getValidSession } from "@/lib/auth/session"; import { ApiError, type WinofyErrorDetails } from "./errors"; -import { logWinofyRequest, logWinofyResponse } from "./dev-logger"; +// import { logWinofyRequest, logWinofyResponse } from "./dev-logger"; const BASE_URL = process.env.GOOYAL_ACCOUNTS_BASE_URL!; @@ -35,7 +35,7 @@ export async function gooyalFetch(path: string, options: GooyalFetchOptions = } } - logWinofyRequest?.(method, url.toString(), body); + // logWinofyRequest?.(method, url.toString(), body); const res = await fetch(url.toString(), { ...rest, @@ -45,12 +45,12 @@ export async function gooyalFetch(path: string, options: GooyalFetchOptions = }); if (res.status === 204) { - logWinofyResponse?.(method, url.toString(), res.status, null); + // logWinofyResponse?.(method, url.toString(), res.status, null); return undefined as T; } const data = await res.json().catch(() => null); - logWinofyResponse?.(method, url.toString(), res.status, data); + // logWinofyResponse?.(method, url.toString(), res.status, data); if (!res.ok) { const envelope = data as { status_message?: string; details?: WinofyErrorDetails } | null; diff --git a/src/lib/providers/orders-provider.tsx b/src/lib/providers/orders-provider.tsx new file mode 100644 index 0000000..12b3900 --- /dev/null +++ b/src/lib/providers/orders-provider.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { createContext, useContext, type ReactNode } from "react"; +import type { OrderGroup } from "@/types/api"; + +type OrdersContextValue = { + data: OrderGroup[] | null; + error: string | null; + isLoading: boolean; +}; + +const OrdersContext = createContext(undefined); + +export function OrdersProvider({ + children, + orderGroups, + error = null, + }: { + children: ReactNode; + orderGroups: OrderGroup[] | null; + error?: string | null; +}) { + return ( + + {children} + + ); +} + +export function useOrders() { + const ctx = useContext(OrdersContext); + if (ctx === undefined) { + throw new Error("useOrders must be used within OrdersProvider"); + } + return ctx; +} \ No newline at end of file -- 2.45.3 From 7e9ab5d68fcd45253ce9bf9d4b9132f4888714cf Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Sat, 12 Sep 2026 16:32:41 +0330 Subject: [PATCH 7/7] change more files --- .idea/.gitignore | 10 +++ .idea/inspectionProfiles/Project_Default.xml | 6 ++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ .idea/winofy-front.iml | 8 +++ .../(shop)/orders/(routes)/[orderId]/page.tsx | 45 +++++++------ .../orders/order-detail-content.tsx | 63 +++++++++++++++++++ src/components/orders/order-items.tsx | 57 ++++++++++++----- .../orders/order-payment-sticky.tsx | 38 +++++++---- .../orders/order-time-address-info-alert.tsx | 52 +++++++++------ src/lib/providers/order-detail-provider.tsx | 42 +++++++++++++ 11 files changed, 268 insertions(+), 67 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/winofy-front.iml create mode 100644 src/components/orders/order-detail-content.tsx create mode 100644 src/lib/providers/order-detail-provider.tsx diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ebc0d49 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/winofy-front.iml b/.idea/winofy-front.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/winofy-front.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx index fe00623..df4adf9 100644 --- a/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx +++ b/src/app/(customer)/(shop)/orders/(routes)/[orderId]/page.tsx @@ -1,22 +1,29 @@ -import OrderItems from '@/components/orders/order-items' -import OrderPaymentSticky from '@/components/orders/order-payment-sticky' -import OrderStepperStatus from '@/components/orders/order-stepper-status' -import OrderTimeAddressInfoAlert from '@/components/orders/order-time-address-info-alert' -import React from 'react' +import { requireSession } from "@/lib/auth/session"; +import { getOrderGroup } from "@/lib/api/orders"; +import { OrderDetailProvider } from "@/lib/providers/order-detail-provider"; +import OrderDetailContent from "@/components/orders/order-detail-content"; -const OrderDetail = () => { - return
- -

- در حال آماده‌سازی -

- - - - - - -
+interface Props { + params: Promise<{ orderId: string }>; } -export default OrderDetail \ No newline at end of file +export default async function OrderDetailPage({ params }: Props) { + await requireSession("/login"); + + const { orderId } = await params; + + let orderGroup = null; + let error: string | null = null; + + try { + orderGroup = await getOrderGroup(orderId); + } catch (e: any) { + error = e?.message ?? "خطا در دریافت اطلاعات سفارش"; + } + + return ( + + + + ); +} \ No newline at end of file diff --git a/src/components/orders/order-detail-content.tsx b/src/components/orders/order-detail-content.tsx new file mode 100644 index 0000000..f8fdd13 --- /dev/null +++ b/src/components/orders/order-detail-content.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useOrderDetail } from "@/lib/providers/order-detail-provider"; +import OrderStepperStatus from "./order-stepper-status"; +import OrderTimeAddressInfoAlert from "./order-time-address-info-alert"; +import OrderItems from "./order-items"; +import OrderPaymentSticky from "./order-payment-sticky"; + +const STATUS_LABELS: Record = { + placed: "ثبت شد", + preparing: "در حال آماده‌سازی", + ready_to_ship: "آماده ارسال", + handed_to_courier: "ارسال شد", + delivered: "تحویل داده شد", + cancelled: "لغو شد", +}; + +export default function OrderDetailContent() { + const { data: orderGroup, error, isLoading } = useOrderDetail(); + + if (isLoading) { + return
در حال بارگذاری...
; + } + + if (error || !orderGroup) { + return ( +
+ {error ?? "سفارش یافت نشد"} +
+ ); + } + + // وضعیت اصلی رو از اولین سفارش می‌گیریم (یا می‌تونی منطق پیچیده‌تری بذاری) + const mainStatus = orderGroup.orders[0]?.status ?? "placed"; + + const statusToStep: Record = { + placed: 0, + preparing: 2, + ready_to_ship: 3, + handed_to_courier: 3, + delivered: 4, + cancelled: -1, + }; + + const currentStep = statusToStep[mainStatus] ?? 0; + const statusLabel = STATUS_LABELS[mainStatus] ?? mainStatus; + + return ( +
+

+ {statusLabel} +

+ + {mainStatus !== "cancelled" && ( + + )} + + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/orders/order-items.tsx b/src/components/orders/order-items.tsx index 0d857d1..5e86468 100644 --- a/src/components/orders/order-items.tsx +++ b/src/components/orders/order-items.tsx @@ -1,20 +1,45 @@ -import React from 'react' +"use client"; -const OrderItems = () => { - return ( -
-

اقلام سفارش

+import type { OrderGroup } from "@/types/api"; +import {formatTomanPrice} from "@/lib/utils/priceUtils"; - {/* order items */} -
-
- نان سنگک - 10 عدد -
- ۱۲،۰۰۰ تومان -
-
- ) +interface Props { + order: OrderGroup; } -export default OrderItems \ No newline at end of file +const OrderItems = ({ order }: Props) => { + // همه آیتم‌های همه فروشگاه‌ها رو جمع می‌کنیم + const allItems = order.orders.flatMap((o) => + o.items.map((item) => ({ + ...item, + storeName: o.store.name, + })) + ); + + return ( +
+

اقلام سفارش

+ + {allItems.map((item) => ( +
+
+
+ {item.product_name_snapshot} + + {item.quantity.toLocaleString("fa-IR")} عدد + +
+ {order.orders.length > 1 && ( + {item.storeName} + )} +
+ + {formatTomanPrice(item.line_total)} + +
+ ))} +
+ ); +}; + +export default OrderItems; \ No newline at end of file diff --git a/src/components/orders/order-payment-sticky.tsx b/src/components/orders/order-payment-sticky.tsx index 6aec3b6..afc47bb 100644 --- a/src/components/orders/order-payment-sticky.tsx +++ b/src/components/orders/order-payment-sticky.tsx @@ -1,26 +1,40 @@ -const OrderPaymentSticky = () => { +"use client"; + +import type { OrderGroup } from "@/types/api"; +import {formatTomanPrice} from "@/lib/utils/priceUtils"; + +interface Props { + order: OrderGroup; +} + +const OrderPaymentSticky = ({ order }: Props) => { + const paymentMethodLabel: Record = { + wallet: "پرداخت از کیف پول", + online: "پرداخت آنلاین", + cash_on_delivery: "پرداخت در محل", + }; + return (
- - مبلغ پرداختی - + مبلغ پرداختی - پرداخت از کیف پول - + {paymentMethodLabel[order.payment_method] ?? order.payment_method} +
- 1000000 تومان - + {formatTomanPrice(order.total_amount)} +
+ {/* فعلاً فقط نمایش، بعداً می‌تونی دکمه لغو اضافه کنی */}
@@ -28,7 +42,7 @@ const OrderPaymentSticky = () => {
- ) -} + ); +}; -export default OrderPaymentSticky \ No newline at end of file +export default OrderPaymentSticky; \ No newline at end of file diff --git a/src/components/orders/order-time-address-info-alert.tsx b/src/components/orders/order-time-address-info-alert.tsx index a005d76..b490233 100644 --- a/src/components/orders/order-time-address-info-alert.tsx +++ b/src/components/orders/order-time-address-info-alert.tsx @@ -1,23 +1,35 @@ -import { ClockIcon, MapPinIcon } from 'lucide-react' -import React from 'react' +"use client"; -const OrderTimeAddressInfoAlert = () => { - return ( -
-
- -

- زمان تخمینی تحویل: ۳۰–۴۵ دقیقه دیگر -

-
-
- -

- نارمک، خیابان شهید دستغیب، پلاک ۱۲، واحد ۳ -

-
-
- ) +import { ClockIcon, MapPinIcon } from "lucide-react"; +import type { OrderGroup } from "@/types/api"; + +interface Props { + order: OrderGroup; } -export default OrderTimeAddressInfoAlert \ No newline at end of file +const OrderTimeAddressInfoAlert = ({ order }: Props) => { + // زمان تخمینی (فعلاً ساده) + const estimatedTime = + order.delivery_type === "express" + ? "۳۰–۴۵ دقیقه دیگر" + : order.scheduled_at + ? new Date(order.scheduled_at).toLocaleString("fa-IR") + : "زمان مشخص نشده"; + + return ( +
+
+ +

+ زمان تخمینی تحویل: {estimatedTime} +

+
+
+ +

{order.full_address}

+
+
+ ); +}; + +export default OrderTimeAddressInfoAlert; \ No newline at end of file diff --git a/src/lib/providers/order-detail-provider.tsx b/src/lib/providers/order-detail-provider.tsx new file mode 100644 index 0000000..9d95b9a --- /dev/null +++ b/src/lib/providers/order-detail-provider.tsx @@ -0,0 +1,42 @@ +"use client"; + +import {createContext, useContext, type ReactNode} from "react"; +import type {Order, OrderGroup} from "@/types/api"; + +type OrderDetailContextValue = { + data: OrderGroup | null; + error: string | null; + isLoading: boolean; +}; + +const OrderDetailContext = createContext(undefined); + +export function OrderDetailProvider({ + children, + order, + error = null, + }: { + children: ReactNode; + order: OrderGroup | null; + error?: string | null; +}) { + return ( + + {children} + + ); +} + +export function useOrderDetail() { + const ctx = useContext(OrderDetailContext); + if (ctx === undefined) { + throw new Error("useOrderDetail must be used within OrderDetailProvider"); + } + return ctx; +} \ No newline at end of file -- 2.45.3