|
|
@@ -0,0 +1,288 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import { useState } from "react";
|
|
|
+import dynamic from "next/dynamic";
|
|
|
+import Image from "next/image";
|
|
|
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
|
|
|
+import { useRouter } from 'next/navigation';
|
|
|
+import { useCustomToast } from "@/utils/hooks/useToast";
|
|
|
+import {useConfig} from "@/utils/hooks/useConfig";
|
|
|
+import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
|
|
|
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
|
|
|
+import { OrderDetails, ProductsInOrderDetails } from "@/types/customer/type";
|
|
|
+import CommonModal from "@/components/theme/ui/CommonModal";
|
|
|
+const CountDown = dynamic(() => import("@/components/common/CountDown"), {
|
|
|
+ ssr: false,
|
|
|
+});
|
|
|
+export function UnpaidOrderSection({
|
|
|
+ orderDetail
|
|
|
+}: {
|
|
|
+ orderDetail: Omit<OrderDetails, 'addresses'>
|
|
|
+}) {
|
|
|
+ const { cancelOrder } = usePlaceOrder();
|
|
|
+ const router = useRouter();
|
|
|
+ const { showToast } = useCustomToast();
|
|
|
+ const {currencies,getCurrentCurrencyItem} = useConfig();
|
|
|
+ const currentCurrency = getCurrentCurrencyItem();
|
|
|
+ const currencySymbol = currentCurrency.symbol;
|
|
|
+ const orderCurrency = currencies.find(item => item.code === orderDetail.orderCurrencyCode) || null;
|
|
|
+ const orderSymbol = orderCurrency?.symbol || currencySymbol;
|
|
|
+
|
|
|
+ const [isShowUpaidOrder, setIsShowUpaidOrder] = useState(true);
|
|
|
+ const [showProductsModal, setShowProductsModal] = useState(false);
|
|
|
+ const [showDetails, setShowDetails] = useState(false);
|
|
|
+
|
|
|
+ const countDownEnd = (new Date(orderDetail.createdAt)).getTime() + 24*3600*1000;
|
|
|
+ const productsData: ProductsInOrderDetails[] = orderDetail.items.edges.map((edge) => {
|
|
|
+ return edge.node;
|
|
|
+ });
|
|
|
+ const totalItemTxt = orderDetail.totalItemCount > 1 ? `${orderDetail.totalItemCount} items` : `${orderDetail.totalItemCount} item`;
|
|
|
+
|
|
|
+
|
|
|
+ const cancelOrderHandler = async () => {
|
|
|
+ const confirmRes = await confirmDialog({
|
|
|
+ title: "Warning",
|
|
|
+ content: "Would you want to cancel the unpaid order?",
|
|
|
+ });
|
|
|
+ if(!confirmRes) return;
|
|
|
+ overlayLoading.start();
|
|
|
+ const res = await cancelOrder({
|
|
|
+ orderId: Number(orderDetail._id),
|
|
|
+ userManual: true
|
|
|
+ });
|
|
|
+ overlayLoading.stop();
|
|
|
+ if(!res.error) {
|
|
|
+ showToast('Unpaid order has been canceled!', 'success');
|
|
|
+ setIsShowUpaidOrder(false);
|
|
|
+ } else {
|
|
|
+ showToast(res.msg, 'danger');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 倒计时结束不再显示未支付订单(取消未支付订单)
|
|
|
+ const handleCountDownEnd = () => {
|
|
|
+ setIsShowUpaidOrder(false);
|
|
|
+ };
|
|
|
+ const openProductsModal = () => {
|
|
|
+ setShowProductsModal(true);
|
|
|
+ };
|
|
|
+ const closeProductsModal = () => {
|
|
|
+ setShowProductsModal(false);
|
|
|
+ };
|
|
|
+ const toContinuePay = () => {
|
|
|
+ if(isShowUpaidOrder) {
|
|
|
+ router.push("/checkout/continuetopay?orderid=" + encodeURIComponent(orderDetail._id));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const openDetails = () => {
|
|
|
+ setShowDetails(true);
|
|
|
+ };
|
|
|
+ const closeDetails = () => {
|
|
|
+ setShowDetails(false);
|
|
|
+ };
|
|
|
+ if(!isShowUpaidOrder) return null;
|
|
|
+ return (<div className="w-full box-border p-4">
|
|
|
+ <div className="relative w-full box-border p-3 bg-ly-lightgray rounded-lg">
|
|
|
+ <div className="flex items-center w-full">
|
|
|
+ Unpaid Order ({totalItemTxt})
|
|
|
+ <div className="ml-2">
|
|
|
+ <CountDown
|
|
|
+ endTime={countDownEnd}
|
|
|
+ onCountDownEnd={handleCountDownEnd}
|
|
|
+ boxClassName={"flex items-center gap-2 flex-none text-ly-14"}
|
|
|
+ timerClassName={"flex items-center justify-center w-6 h-6 bg-ly-middlegreen rounded-xs text-ly-12 font-medium text-white"}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="w-full mt-3 overflow-x-scroll" onClick={openProductsModal}>
|
|
|
+ <div className="w-full flex gap-3">
|
|
|
+ {productsData.map((product) => {
|
|
|
+ const baseImage = JSON.parse(product.baseImage);
|
|
|
+ return(
|
|
|
+ <div className="relative w-16.5 h-22 flex-none" key={product.id}>
|
|
|
+ <Image className="object-cover" fill
|
|
|
+ src={baseImage.medium_image_url}
|
|
|
+ alt={product.name}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className=" flex items-center justify-between w-full mt-3">
|
|
|
+ <span className="text-ly-14 font-medium">Total: {orderSymbol}{orderDetail.grandTotal}</span>
|
|
|
+ <button className="flex-none flex justify-center items-center px-2 h-8 text-ly-14 text-white bg-ly-middlegreen rounded-md"
|
|
|
+ onClick={toContinuePay}
|
|
|
+ >
|
|
|
+ Pay Now
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button className="absolute top-3 right-3 w-6 h-6" onClick={cancelOrderHandler}>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.2" strokeLinejoin="round" strokeLinecap="round" d="M7 7L17 17"></path><path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.2" strokeLinejoin="round" strokeLinecap="round" d="M7 17L17 7"></path></svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <CommonModal
|
|
|
+ isOpen={showProductsModal}
|
|
|
+ onClose={closeProductsModal}
|
|
|
+ header={
|
|
|
+ <div className="w-full box-border p-4">
|
|
|
+ <div className="w-full box-border flex justify-between">
|
|
|
+ <span className="text-ly-16 font-medium">Order Summary (2 items)</span>
|
|
|
+ <button className="w-6 h-6" onClick={closeProductsModal}>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ footer={
|
|
|
+ <div className="w-full box-border p-4 inset-shadow-gray-50">
|
|
|
+ <div className="flex w-full justify-between items-center">
|
|
|
+ <span className="text-ly-14 font-medium">Total:</span>
|
|
|
+ <button onClick={openDetails} className="flex items-center text-ly-14 font-medium">
|
|
|
+ {orderSymbol}{orderDetail.grandTotal}
|
|
|
+ <svg className="flex-none w-6 h-6" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square" d="M18 9L12 15L6 9"></path></svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <button className="w-full h-12 rounded-3xl mt-3 flex justify-center items-center bg-ly-middlegreen text-ly-16 font-medium text-white"
|
|
|
+ onClick={toContinuePay}
|
|
|
+ >
|
|
|
+ Pay Now
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ body={
|
|
|
+ <div className="relative box-border w-full h-auto px-4 pt-2 pb-8">
|
|
|
+ {productsData.map((product) => {
|
|
|
+ const baseImage = JSON.parse(product.baseImage);
|
|
|
+ return(
|
|
|
+
|
|
|
+ <div key={product.id} className="w-full flex gap-2 mt-4 first:mt-0 pb-4 border-b-1 border-b-[#f0f0f0] last:border-none">
|
|
|
+ <div className="relative w-33 h-44 flex-none">
|
|
|
+ <Image fill className="object-cover"
|
|
|
+ src={baseImage.medium_image_url}
|
|
|
+ alt={product.name}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="w-full flex flex-col justify-between">
|
|
|
+ <div className="w-full">
|
|
|
+ <div className="w-full">
|
|
|
+ <div className="w-full line-clamp-2 text-ly-12 text-ly-placeholder leading-5">
|
|
|
+ {product.name}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="w-full mt-2 text-ly-12 border-1 rounded-sm border-[#d9d9d9] px-2.5 py-1.5 leading-5">
|
|
|
+ {product.sku}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <div className="flex items-center gap-1.5">
|
|
|
+ <span className="text-ly-14 font-bold">{orderSymbol}{product.price}</span>
|
|
|
+ {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span className="flex-none text-ly-14">x{product.qtyOrdered}</span>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+
|
|
|
+
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ />
|
|
|
+
|
|
|
+ {/**金额明细 */}
|
|
|
+ <CommonModal
|
|
|
+ zIndex="z-51"
|
|
|
+ isOpen={showDetails}
|
|
|
+ onClose={closeDetails}
|
|
|
+ contentClassName={"top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 w-82 bg-white rounded-xl"}
|
|
|
+ body={
|
|
|
+ <div className="relative box-border w-full h-auto p-4">
|
|
|
+ <div className="flex justify-between items-center text-ly-14 font-bold">
|
|
|
+ Promotion Details
|
|
|
+ <button className="flex-none w-6 h-6" onClick={closeDetails}>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div className="w-full mt-4">
|
|
|
+
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Subtotal</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.subTotal}</p>
|
|
|
+ </div>
|
|
|
+ {orderDetail.rewardPointsEarned > 0 &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">You Earn</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">{orderDetail.rewardPointsEarned} Reward Points</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {orderDetail.shippingAmount !== 0 &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Shipping</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.shippingAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Shipping</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {orderDetail.taxAmount !== 0 &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Tax</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.taxAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null) &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Plus Fee</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.vipPlusAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {orderDetail.discountAmount !== 0 &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Discount</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.discountAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Gift Card</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.giftcardAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">Plus Save</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ {orderDetail.rewardPointsAmount > 0 &&
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-12 leading-ly-20">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
|
|
|
+ <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <div className="flex justify-between mt-3 first:mt-0">
|
|
|
+ <p className="text-ly-16 leading-ly-24">Grand Total</p>
|
|
|
+ <p className="text-ly-16 leading-ly-24 font-bold">{orderSymbol}{orderDetail.grandTotal}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ />
|
|
|
+
|
|
|
+ </div>);
|
|
|
+}
|