Przeglądaj źródła

购物车页样式

fogwind 1 tydzień temu
rodzic
commit
0b21e29325

+ 3 - 2
src/app/(checkout)/checkout/_components/CheckoutProducts/CheckoutProducts.tsx

@@ -6,7 +6,7 @@ import type {CartDetail, CartItem} from "@/types/cart/type";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import {useConfig} from "@/utils/hooks/useConfig";
-import { formatCartDetail } from "@/utils/cartDetailTools";
+import { formatCartDetail, formatCartProductOptions } from "@/utils/cartDetailTools";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 
@@ -107,6 +107,7 @@ export default function CheckoutProducts({
             <div className="relative box-border w-full h-auto">
                 {productList.map((product) => {
                     const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
+                    const productOptions = product.options.edges.map((edge) => edge.node);
                     return (
                         <div key={product.id} className="w-full flex gap-2 mt-4 pt-4 border-t-1 border-t-[#f0f0f0]">
                             <div className="relative w-33 h-44 flex-none">
@@ -123,7 +124,7 @@ export default function CheckoutProducts({
                                         </button>
                                     </div>
                                     <div className="w-full mt-2 text-ly-14 border-1 rounded-sm border-[#d9d9d9] px-2.5 py-1.5 leading-5">
-                                        {product.sku}
+                                        {formatCartProductOptions(productOptions)}
                                     </div>
                                 </div>
                                 <div className="flex items-center justify-between">

Plik diff jest za duży
+ 165 - 186
src/app/(checkout)/checkout/cart/_components/CartWrapper.tsx


+ 31 - 12
src/app/(checkout)/checkout/cart/_components/EmptyCart.tsx

@@ -1,40 +1,59 @@
 "use client";
 
+import { useState } from "react";
 import Image from "next/image";
+import clsx from "clsx";
 import { useRouter } from 'next/navigation';
+import type { OrderDetails } from "@/types/customer/type";
+import { UnpaidOrderSection } from "./UnpaidOrderSection";
 
-
-export function EmptyCart({isGuest}:{isGuest: boolean;}) { 
+export function EmptyCart({
+    isGuest,
+    hasUnPaidOrder,
+    unpaidOrder
+}:{
+    isGuest: boolean;
+    hasUnPaidOrder: boolean;
+    unpaidOrder: Omit<OrderDetails, 'addresses'> | null;
+}) { 
 
     const router = useRouter();
+    const [showTips, setShowTips] = useState(true);
     return (
-        <div className="w-full box-border">
-            <div className="w-37.5 h-37.5 mt-6 mx-auto">
+        <div className="w-full box-border mt-12">
+            {(hasUnPaidOrder && unpaidOrder !== null) &&
+                <div className="w-130 mx-auto">
+                    <UnpaidOrderSection 
+                        orderDetail={unpaidOrder}
+                    />
+                </div>
+            }
+            <div className="w-50 h-50 mx-auto mt-6">
                 <Image src="/image/empty-cart.webp"
-                    width={150}
-                    height={150}
+                    width={200}
+                    height={200}
                     alt="empty cart"
                 />
             </div>
-            <p className="text-center text-ly-16">Your Cart Is Empty</p>
+            <p className="text-center text-ly-18 mt-6">Your Cart Is Empty</p>
             {isGuest && <>
-                <p className="mt-2 text-center text-ly-12 leading-5 text-ly-placeholder">Have an account? Sign in to view your cart</p>
-                <div className="relative border-box w-full px-4 mt-15">
+                <p className="mt-2 text-center text-ly-14 leading-5 text-ly-placeholder">Have an account? Sign in to view your cart</p>
+                <div className="relative border-box w-85.75 px-4 mt-15 mx-auto">
                     <button className="flex items-center justify-center w-full bg-ly-middlegreen text-ly-16 text-white h-12 rounded-3xl"
                         onClick={() => {router.replace('/customer/login')}}
                     >
                         Sign In
                     </button>
-                    <button className="mt-3 flex items-center justify-center w-full  text-ly-16 h-12 rounded-3xl border-1"
+                    <button className="mt-4 flex items-center justify-center w-full text-ly-16 h-12 rounded-3xl border-1"
                         onClick={() => {router.replace('/')}}
                     >
                         Back To Shop
                     </button>
-                    <div className="text-ly-12 absolute -top-4 left-1/2 -translate-x-1/2 -translate-y-full flex items-center bg-[#ffe033] w-max h-7 px-2 rounded-sm">
+                    <div className={clsx("text-ly-12 absolute -top-4 left-1/2 -translate-x-1/2 -translate-y-full flex items-center bg-[#ffe033] w-max h-7 px-2 rounded-sm", !showTips && "hidden")}>
                         <span className="absolute left-12.5 bottom-0 translate-y-2/5 bg-[#ffe033] w-3 h-3 rotate-z-45 rotate-y-30"></span>
                         <span className="font-bold px-1 leading-5 rounded-xs bg-white mr-2">$200+</span>
                         Log In To Unlock VIP CouponPack
-                        <button className="ml-1 w-6 h-6">
+                        <button className="ml-1 w-6 h-6" onClick={() => setShowTips(false)}>
                             <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.5" strokeLinejoin="round" strokeLinecap="square"  d="M7 7L17 17"></path><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M7 17L17 7"></path></svg>
                         </button>
                         

+ 5 - 5
src/app/(checkout)/checkout/cart/_components/PromiseInCart.tsx

@@ -5,19 +5,19 @@
 export function PromiseInCart() { 
 
 
-    return (
-        <div className="flex items-center justify-center w-full box-border bg-[#EAE9E1] h-10">
+    return (<div className="w-full box-border bg-[#EAE9E1]">
+        <div className="flex items-center justify-between box-border max-w-248 px-4 h-10 mx-auto">
             <div className="flex-none text-center text-ly-14 leading-4">
                 30 Days Free Return
             </div>
-            <span className="h-5 border-[#cccccc] border-l-1 mx-32.5"></span>
+            <span className="h-5 border-[#cccccc] border-l-1"></span>
             <div className="flex-none text-center text-ly-14 leading-4 ">
                 24-48 Hrs Free Fast Shipping
             </div>
-            <span className="h-5 border-[#cccccc] border-l-1 mx-32.5"></span>
+            <span className="h-5 border-[#cccccc] border-l-1"></span>
             <div className="flex-none text-center text-ly-14 leading-4">
                 Buy Now & Pay Later
             </div>
         </div>
-    );
+    </div>);
 }

+ 5 - 4
src/app/(checkout)/checkout/cart/_components/UnpaidOrderSection.tsx

@@ -9,6 +9,7 @@ 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 {getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import type { OrderDetails, ProductsInOrderDetails } from "@/types/customer/type";
 import CommonModal from "@/components/theme/ui/CommonModal";
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
@@ -82,7 +83,7 @@ export function UnpaidOrderSection({
         setShowDetails(false);
     };
     if(!isShowUpaidOrder) return null; 
-    return (<div className="w-full box-border p-4">
+    return (<>
         <div className="relative w-full box-border p-3 bg-ly-lightgray rounded-lg">
             <div className="flex items-center w-full">
                 Unpaid Order ({totalItemTxt})
@@ -95,7 +96,7 @@ export function UnpaidOrderSection({
                     />
                 </div>
             </div>
-            <div className="w-full mt-3 overflow-x-scroll" onClick={openProductsModal}>
+            <div className="w-full mt-3 overflow-x-auto scrollbar-thin scrollbar-thumb-gray-200" onClick={openProductsModal}>
                 <div className="w-full flex gap-3">
                     {productsData.map((product) => {
                         const baseImage = JSON.parse(product.baseImage);
@@ -176,7 +177,7 @@ export function UnpaidOrderSection({
                                             </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}
+                                            {getProductAdditionalInfo(JSON.parse(product.additional))}
                                         </div>
                                     </div>
                                     <div className="flex items-center justify-between">
@@ -284,5 +285,5 @@ export function UnpaidOrderSection({
             }
         />
 
-    </div>);
+    </>);
 }

+ 11 - 12
src/app/(checkout)/checkout/cart/page.tsx

@@ -7,7 +7,7 @@ import type { OrderListData, OrderListDataVariables, OrderDetails } from "@/type
 import { EmptyCart } from "./_components/EmptyCart";
 import { PromiseInCart } from "./_components/PromiseInCart";
 import { CartWrapper } from "./_components/CartWrapper";
-import { UnpaidOrderSection } from "./_components/UnpaidOrderSection";
+
 /**
  * 购物车页 
  * 登录用户的话,获取用户地址列表 从auth session 中获取email
@@ -55,28 +55,27 @@ export default async function CheckoutPage() {
         cartDatahash = createHash('sha256').update(JSON.stringify(cartDetails)).digest('hex');
     }
 
-    return (
-        <>
-            <PromiseInCart />
-            {(unpaidOrder && !unpaidOrderExpiried) &&
-
-                <UnpaidOrderSection 
-                    orderDetail={unpaidOrder}
-                />
+    const hasUnPaidOrder = (unpaidOrder !== null && !unpaidOrderExpiried);
 
-            }
+    return (<>
+        <PromiseInCart />
+        <div className="mt-6 mb-30 max-w-312 mx-auto px-6">
             
             {!cartDetails && 
                 <EmptyCart 
                     isGuest={isGuest}
+                    hasUnPaidOrder={hasUnPaidOrder}
+                    unpaidOrder={unpaidOrder}
                 />
             }
             {cartDetails &&
                 <CartWrapper key={cartDatahash}
                     cartDetailData={cartDetails}
+                    hasUnPaidOrder={hasUnPaidOrder}
+                    unpaidOrder={unpaidOrder}
                 />
             }
-        </>
-    );
+        </div>
+    </>);
   
 }

+ 1 - 0
src/graphql/cart/mutations/AddProductToCart.ts

@@ -38,6 +38,7 @@ export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCa
               type
               productUrlKey
               canChangeQty
+              options
             }
           }
         }

+ 15 - 14
src/graphql/cart/mutations/CreateApplyCoupon.ts

@@ -15,22 +15,23 @@ export const CREATE_APPLY_COUPON: TypedDocumentNode<CreateApplyCouponData,{coupo
                     isGuest
                     itemsCount
                     items {
-                    edges {
-                        node {
-                        id
-                        cartId
-                        productId
-                        name
-                        price
-                        baseImage
-                        sku
-                        quantity
-                        type
-                        productUrlKey
-                        canChangeQty
+                        edges {
+                            node {
+                                id
+                                cartId
+                                productId
+                                name
+                                price
+                                baseImage
+                                sku
+                                quantity
+                                type
+                                productUrlKey
+                                canChangeQty
+                                options
+                            }
                         }
                     }
-                    }
                     earnableRewardPoints
                     rewardPoints
                     rewardPointsUsed

+ 1 - 0
src/graphql/cart/mutations/CreateMergeCart.ts

@@ -30,6 +30,7 @@ export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
               type
               productUrlKey
               canChangeQty
+              options
             }
           }
         }

+ 1 - 0
src/graphql/cart/mutations/CreateRemoveCoupon.ts

@@ -24,6 +24,7 @@ export const CREATE_REMOVE_COUPON: TypedDocumentNode<CreateRemoveCouponData> = g
                         type
                         productUrlKey
                         canChangeQty
+                        options
                         }
                     }
                     }

+ 1 - 0
src/graphql/cart/mutations/GetCartItem.ts

@@ -24,6 +24,7 @@ export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
               type
               productUrlKey
               canChangeQty
+              options
             }
           }
         }

+ 1 - 0
src/graphql/cart/mutations/RemoveCartItem.ts

@@ -24,6 +24,7 @@ export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData,{cartItemId:
               type
               productUrlKey
               canChangeQty
+              options
             }
           }
         }

+ 1 - 0
src/graphql/cart/mutations/UpdateCartItems.ts

@@ -33,6 +33,7 @@ export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId:
               type
               productUrlKey
               canChangeQty
+              options
             }
           }
         }

+ 1 - 0
src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts

@@ -42,6 +42,7 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,S
                             type
                             productUrlKey
                             canChangeQty
+                            options
                         }
                     }
                 }

+ 12 - 0
src/types/cart/type.ts

@@ -1,4 +1,13 @@
 // Cart Item (node)
+export interface CartItemOptions {
+  value_id: number;
+  option_id: number;
+  value_code: string;
+  option_code: string;
+  value_label: string;
+  option_label: string;
+  attribute_name: string;                 
+}
 export interface CartItem {
   id: string;
   cartId: string;
@@ -11,6 +20,9 @@ export interface CartItem {
   type: string;
   productUrlKey: string;
   canChangeQty: boolean;
+  options: {
+    edges: Array<{node:CartItemOptions}>
+  }
 }
 
 // GraphQL Edge wrapper

+ 15 - 1
src/utils/cartDetailTools.ts

@@ -1,4 +1,4 @@
-import type { CartDetail, CartAddress } from "@/types/cart/type";
+import type { CartDetail, CartAddress, CartItemOptions } from "@/types/cart/type";
 import type {ShipAddressFormData,BillAddressFormData} from "@/types/checkout/type";
 export function formatCartDetail(cart:CartDetail): CartDetail { 
     return {
@@ -98,4 +98,18 @@ export function billingAddressToCartAddress(address: BillAddressFormData):CartAd
         phone: address.billingPhoneNumber
     }
     return res;
+}
+
+export function formatCartProductOptions(options:CartItemOptions[]) {
+    let res = '';
+    if(options.length > 0) {
+        for(let i = 0; i < options.length; i++) {
+            if(res) {
+                res += '/' + options[i].option_label + ':' + options[i].value_label;
+            } else {
+                res = options[i].option_label + ':' + options[i].value_label;
+            }
+        }
+    }
+    return res;
 }