zhangzf 1 тиждень тому
батько
коміт
448207baa3
30 змінених файлів з 2649 додано та 389 видалено
  1. 74 0
      src/app/(public)/customer/account/_components/AccountBottomHrefSwiper.tsx
  2. 19 2
      src/app/(public)/customer/account/_components/AccountHeader.tsx
  3. 112 0
      src/app/(public)/customer/account/_components/HelpModal.tsx
  4. 183 0
      src/app/(public)/customer/account/mypoints/_components/GetPoints.tsx
  5. 28 0
      src/app/(public)/customer/account/mypoints/_components/PointsRule.tsx
  6. 15 70
      src/app/(public)/customer/account/mypoints/page.tsx
  7. 5 27
      src/app/(public)/customer/account/page.tsx
  8. 100 221
      src/app/(public)/customer/account/pointlist/page.tsx
  9. 5 1
      src/app/(public)/customer/address/page.tsx
  10. 286 0
      src/app/(public)/customer/order/history/_components/OrderHistory.tsx
  11. 10 0
      src/app/(public)/customer/order/history/page.tsx
  12. 47 0
      src/app/(public)/customer/order/track/order_id/[id]/_components/TrackDetail.tsx
  13. 74 0
      src/app/(public)/customer/order/track/order_id/[id]/page.tsx
  14. 30 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/OrderHeader.tsx
  15. 50 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/OrderInfoBlock.tsx
  16. 39 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/PriceSummary.tsx
  17. 40 0
      src/app/(public)/customer/order/view/order_id/[id]/_components/ProductList.tsx
  18. 93 0
      src/app/(public)/customer/order/view/order_id/[id]/page.tsx
  19. 207 0
      src/app/(public)/hair-care-tips/page.tsx
  20. 688 0
      src/app/(public)/help-faqs/_components/Faqs.tsx
  21. 10 0
      src/app/(public)/help-faqs/page.tsx
  22. 76 0
      src/app/(public)/track_order/_components/TrackFaqs.tsx
  23. 32 0
      src/app/(public)/track_order/_components/TrackOrder.tsx
  24. 47 0
      src/app/(public)/track_order/page.tsx
  25. 50 0
      src/app/api/customer/reward-points/browse-product/route.ts
  26. 40 0
      src/app/api/customer/reward-points/follow-status/route.ts
  27. 62 0
      src/app/api/customer/reward-points/follow/route.ts
  28. 61 0
      src/app/api/customer/reward-points/history/route.ts
  29. 126 68
      src/components/customer/SettingModal.tsx
  30. 40 0
      src/components/head/HeadBack.tsx

+ 74 - 0
src/app/(public)/customer/account/_components/AccountBottomHrefSwiper.tsx

@@ -0,0 +1,74 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+import { Swiper, SwiperSlide } from "swiper/react";
+// 只导入 Autoplay 即可!loop 内置了
+import { Autoplay } from "swiper/modules";
+
+import "swiper/css";
+
+const AccountBottomHrefSwiper = () => {
+  const swiperRef = useRef<any>(null);
+
+  const footerCategorySlides = [
+    { url: "/new-arrival.html", title: "New Arrival" },
+    { url: "/hd-lace-wig.html", title: "HD Lace" },
+    { url: "/effortless-wig.html", title: "Ready To Go Wigs" },
+    { url: "/ombrecolorwigs.html", title: "Color" },
+    { url: "/lace-frontal-wig.html", title: "Lace Frontal Wigs" },
+    { url: "/closure-wigs.html", title: "Closure Wigs" },
+  
+  ];
+
+  // 登录/注册/找回密码页面隐藏
+  useEffect(() => {
+    const path = window.location.pathname;
+    const hidePaths = [
+      "/customer/account/login",
+      "/customer/account/forgotpassword",
+      "/customer/account/create",
+    ];
+
+    if (hidePaths.some((p) => path.includes(p))) {
+      const el = document.querySelector(".footer-bottom-category");
+      if (el) el.remove();
+    }
+  }, []);
+
+  return (
+    <div className="footer-bottom-category bg-white py-3">
+        <p className="underline text-center font-bold bg-[#eee] leading-8 ">
+            HOW TO GET MORE POINTS?
+        </p>
+      <div className="footer-bottom-category-list container mx-auto px-4 bg-[#fff8f2] height-12.5 flex items-center ">
+        <Swiper
+          ref={swiperRef}
+          // ✅ 只需要 Autoplay
+          modules={[Autoplay]}
+          // ✅ loop 直接写,不需要模块!
+        //   loop={true}
+          slidesPerView="auto"
+          spaceBetween={12}
+          autoplay={{
+            delay: 1000,
+            disableOnInteraction: false,
+          }}
+          className="footer-bottom-category-swiper"
+        >
+          {footerCategorySlides.map((item, index) => (
+            <SwiperSlide key={index} className="!w-auto footer-bottom-category-item whitespace-nowrap px-4 py-2 text-sm text-gray-700 rounded-lg border border-solid border-[#b32400]">
+              <a
+                href={item.url}
+                className=" "
+              >
+                {item.title}
+              </a>
+            </SwiperSlide>
+          ))}
+        </Swiper>
+      </div>
+    </div>
+  );
+};
+
+export default AccountBottomHrefSwiper;

+ 19 - 2
src/app/(public)/customer/account/_components/AccountHeader.tsx

@@ -79,11 +79,28 @@ export default function AccountHeader() {
           </Link>
           <div>|</div>
           <Link
-            className="leading-5"
+            className="leading-5 flex flex-col items-center "
             href="/koc/user/mybalance"
             aria-label="Go to koc user mybalance page"
           >
-            <strong>$0</strong> <br /> Balance
+           
+            <svg
+              xmlns="http://www.w3.org/2000/svg"
+              className="h-6 w-6 "
+              fill="none"
+              viewBox="0 0 24 24"
+              stroke="currentColor"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={1.5}
+                d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
+              />
+            </svg>
+            <span className="">Wishlist</span>
+        
+            {/* <strong>$0</strong> <br /> Balance */}
           </Link>
         </div>
       </div>

+ 112 - 0
src/app/(public)/customer/account/_components/HelpModal.tsx

@@ -0,0 +1,112 @@
+import Link from "next/link";
+export default function HelpModal({
+  helpvisible,
+  helponClose,
+}: {
+  helpvisible: boolean;
+  helponClose: () => void;
+}) {
+  // 如果不显示,直接 return null
+  if (!helpvisible) return null;
+  return (
+    <div className="fixed inset-0 z-51 bg-black/50">
+      {/* 弹框主体 - 从底部滑上来 */}
+      <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
+        <div className="flex flex-col">
+          {/* 顶部导航 */}
+          <header className="bg-black text-white py-4 px-4 flex items-center">
+            <button onClick={helponClose} className="mr-4">
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-6 w-6"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M15 19l-7-7 7-7"
+                />
+              </svg>
+            </button>
+            <h1 className="text-lg font-medium">HELP & SUPPORT</h1>
+          </header>
+          {/* 菜单列表 */}
+          <div className="px-4 py-2">
+            {/* Information */}
+            <Link
+              href={"/help-faqs"}
+              className="py-4 border-b border-gray-100 flex justify-between items-center"
+            >
+              {/* <div > */}
+              <span className="text-black font-medium">Help & FAQ</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-5 w-5 text-gray-400"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M9 5l7 7-7 7"
+                />
+              </svg>
+              {/* </div> */}
+            </Link>
+            {/* Address */}
+            <Link
+              href={"/track_order"}
+              className="py-4 border-b border-gray-100 flex justify-between items-center"
+            >
+              {/* <div > */}
+              <span className="text-black font-medium">Track Order</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-5 w-5 text-gray-400"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M9 5l7 7-7 7"
+                />
+              </svg>
+              {/* </div> */}
+            </Link>
+            {/* help & Support */}
+            <Link
+              href={"/hair-care-tips"}
+              className="py-4 border-b border-gray-100 flex justify-between items-center"
+            >
+              {/* <div > */}
+              <span className="text-black font-medium">Hair Care Tips</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                className="h-5 w-5 text-gray-400"
+                fill="none"
+                viewBox="0 0 24 24"
+                stroke="currentColor"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M9 5l7 7-7 7"
+                />
+              </svg>
+              {/* </div> */}
+            </Link>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 183 - 0
src/app/(public)/customer/account/mypoints/_components/GetPoints.tsx

@@ -0,0 +1,183 @@
+"use client";
+import Link from "next/link";
+import React, { useState, useEffect } from "react";
+import { clientFetch } from "@/lib/restApiClient";
+
+export default function GetPoints() {
+  const [taskData, setTaskData] = useState<any>(null);
+  const [loading, setLoading] = useState(true);
+  useEffect(() => {
+    const fetchRewardPointsHistory = async () => {
+      try {
+        setLoading(true);
+        const res = await clientFetch("/api/customer/reward-points/follow-status");
+      
+        setTaskData(res.data.data);
+        console.log("积分任务数据:", res.data.data);
+      } catch (error) {
+        console.error("获取任务状态失败:", error);
+      } finally {
+        setLoading(false);
+      }
+    };
+    fetchRewardPointsHistory();
+  }, []);
+
+  function canTriggerTask(item: (typeof earnPointsItems)[0]) {
+  
+    if (!taskData) return false;
+
+    const { type } = item;
+    const { browsed_product_ids, platforms } = taskData;
+
+    if (!type) return true;
+
+   
+    if (browsed_product_ids.includes(type)) {
+      return false;
+    }
+
+    const platform = platforms.find((p: any) => p.code === type);
+    if (platform) {
+      return !platform.followed;
+    }
+
+    return true;
+  }
+
+  
+  const handleClaimPoints = async (item: any) => {
+ 
+    if (!canTriggerTask(item)) return;
+
+    try {
+      console.log("触发领取积分,任务类型:", item.type);
+      
+      // const getpointsres = await clientFetch("/api/customer/reward-points/follow", {  
+      //   method: "POST",
+      //   body: JSON.stringify({ platform: item.type }),
+      // });
+      // 这个浏览获取积分接口后面放到列表页去调
+      const getpointsres = await clientFetch("/api/customer/reward-points/browse-product", {  
+        method: "POST",
+        body: JSON.stringify({ categorys: item.type }),
+      });
+      if(getpointsres.data.success){
+        alert("Points claimed successfully!");
+      }
+      
+      const res = await clientFetch("/api/customer/reward-points/follow-status");
+      setTaskData(res.data.data);
+    } catch (err) {
+      console.error("领取失败", err);
+      alert("Failed to claim points");
+    }
+  };
+
+  const earnPointsItems = [
+    {
+      title: "Share a plain text review",
+      desc: "To Get 50 points",
+      points: "",
+      type: "",
+    },
+    {
+      title: "Share your wig review with photos/videos",
+      desc: "To Get 100 Points",
+      points: "",
+      type: "",
+    },
+    {
+      title: "Log In Our Website",
+      desc: "Every Day To Get Points",
+      points: "",
+      type: "",
+    },
+    {
+      title: "Browse New Arrival Page ≥ 15 Seconds",
+      desc: "To Get 10 Points",
+      points: "",
+      type: "aaa",
+    },
+    {
+      title: "Browse HD Lace Wigs ≥ 15 Seconds",
+      desc: "To Get 10 Points",
+      points: "",
+      type: "ccc",
+    },
+    {
+      title: "Browse Ready To Go Wigs ≥ 15 Seconds",
+      desc: "To Get 10 Points",
+      points: "",
+      type: "bbbb",
+    },
+    {
+      title: "Follow Our Instagram Account",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "ig",
+    },
+    {
+      title: "Follow Our Facebook Account",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "fb",
+    },
+    {
+      title: "Follow Our Youtube Channel",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "ytb",
+    },
+    {
+      title: "Follow Our Tiktok Account",
+      desc: "To Get 20 Points",
+      points: "",
+      type: "tt",
+    },
+  ];
+
+  return (
+    <div className="space-y-2">
+      {loading ? (
+        <div>Loading tasks...</div>
+      ) : (
+        earnPointsItems.map((item, index) => {
+          const canClick = canTriggerTask(item);
+          return (
+            <div
+              key={index}
+              className="h-15.5 flex items-center justify-between bg-[#F8F8F8] p-2.5 rounded-md "
+            >
+              <div>
+                <h3 className="font-medium text-black">{item.title}</h3>
+                {item.desc && <p className="text-sm text-gray-500">{item.desc}</p>}
+              </div>
+              <div className="flex items-center gap-6">
+                {item.points && (
+                  <span className="font-medium">{item.points}</span>
+                )}
+                <button
+                  onClick={() => handleClaimPoints(item)}
+                  disabled={!canClick}
+                  className={`transition-colors ${
+                    canClick ? "text-black" : "text-gray-300 cursor-not-allowed"
+                  }`}
+                >
+                  <svg
+                    viewBox="0 0 24 24"
+                    width="16"
+                    height="16"
+                    fill="currentColor"
+                  >
+                    <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z" />
+                  </svg>
+                </button>
+              </div>
+            </div>
+          );
+        })
+      )}
+    </div>
+  );
+}

+ 28 - 0
src/app/(public)/customer/account/mypoints/_components/PointsRule.tsx

@@ -0,0 +1,28 @@
+"use client";
+import React, { useState } from "react";
+import PointsRuleModal from "@/components/customer/PointsRule";
+export default function PointsRule() {
+  // 控制弹窗显隐的状态
+  const [isModalOpen, setIsModalOpen] = useState(false);
+
+  // 打开弹窗(页面点击触发)
+  const ruleClick = () => {
+    console.log("aaaa");
+    setIsModalOpen(true);
+  };
+  return (
+    <>
+      <div
+        onClick={ruleClick}
+        className="text-black text-sm font-medium underline"
+      >
+        Rules
+      </div>
+      {/* 积分规则组件 */}
+      <PointsRuleModal
+        visible={isModalOpen}
+        handleCloseModal={() => setIsModalOpen(false)}
+      />
+    </>
+  );
+}

+ 15 - 70
src/app/(public)/customer/account/mypoints/page.tsx

@@ -1,9 +1,7 @@
-"use client";
 import Image from "next/image";
 import Link from "next/link";
-import React, { useState } from "react";
-import PointsRuleModal from "@/components/customer/PointsRule";
-
+import PointsRule from "./_components/PointsRule"
+import GetPoints from "./_components/GetPoints"
 const AccountPointsPage = () => {
   // 模拟用户积分数据
   const userPoints = 100009;
@@ -30,34 +28,13 @@ const AccountPointsPage = () => {
     },
   ];
 
-  // 积分获取项配置
-  const earnPointsItems = [
-    { title: "Make a Purchase", desc: "", points: "1 Points Per $1" },
-    { title: "Post a Review", desc: "", points: "+50 points" },
-    {
-      title: "Log In",
-      desc: "",
-      points: "+10 points",
-    },
-    {
-      title: "Reward for Registering",
-      desc: "",
-      points: "+200 points",
-    },
-  ];
-   // 控制弹窗显隐的状态
-  const [isModalOpen, setIsModalOpen] = useState(false);
-
-  // 打开弹窗(页面点击触发)
-  const ruleClick = () => {
-    console.log('aaaa');
-    setIsModalOpen(true);
-  };
-
   return (
     <div className="w-full h-full">
       <div className="bg-[#fff] pr-2.5 pl-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
-        <Link href={"/customer/account"} className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2">
+        <Link
+          href={"/customer/account"}
+          className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2"
+        >
           <svg
             xmlns="http://www.w3.org/2000/svg"
             width="24"
@@ -73,7 +50,7 @@ const AccountPointsPage = () => {
               d="M15 18L9 12L15 6"
             ></path>
           </svg>
-          </Link>
+        </Link>
         <span className="vipReturnTitles">My Points</span>
       </div>
       <div className=" bg-[#FFF5F2] px-4 py-8 max-w-3xl mx-auto pt-6 pb-5">
@@ -138,12 +115,13 @@ const AccountPointsPage = () => {
           </div>
           {/* Details & Rules 链接 */}
           <div className="flex gap-4 text-sm font-medium">
-            <a href="#" className="text-black text-sm font-medium underline">
+            <Link
+              href="/customer/account/pointlist"
+              className="text-black text-sm font-medium underline"
+            >
               Details
-            </a>
-            <a href="#" onClick={ruleClick} className="text-black text-sm font-medium underline">
-              Rules
-            </a>
+            </Link>
+            <PointsRule/>
           </div>
         </div>
 
@@ -169,42 +147,9 @@ const AccountPointsPage = () => {
         <h2 className="text-base mt-6 mb-8  font-bold text-black text-center">
           Earn Points
         </h2>
-        <div className="space-y-2">
-          {earnPointsItems.map((item, index) => (
-            <div
-              key={index}
-              className="h-15.5 flex items-center justify-between bg-[#F8F8F8] p-2.5 rounded-md "
-            >
-              <div>
-                <h3 className="font-medium text-black">{item.title}</h3>
-                {item.desc && (
-                  <p className="text-sm text-gray-500">{item.desc}</p>
-                )}
-              </div>
-              <div className="flex items-center gap-6">
-                {item.points && (
-                  <span className="font-medium">{item.points}</span>
-                )}
-                <button className="text-gray-400 hover:text-black transition-colors">
-                  <svg
-                    viewBox="0 0 24 24"
-                    width="16"
-                    height="16"
-                    fill="currentColor"
-                  >
-                    <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z" />
-                  </svg>
-                </button>
-              </div>
-            </div>
-          ))}
-        </div>
+        <GetPoints />
       </div>
-      {/* 积分规则组件 */}
-      <PointsRuleModal 
-         visible={isModalOpen}
-          handleCloseModal={() => setIsModalOpen(false)}
-      />
+    
     </div>
   );
 };

+ 5 - 27
src/app/(public)/customer/account/page.tsx

@@ -1,5 +1,6 @@
 import AccountSwiper from "@components/customer/accountSwiper";
 import AccountHeader from "./_components/AccountHeader"
+import AccountBottomHrefSwiper from "./_components/AccountBottomHrefSwiper"
 import Link from "next/link";
 const orderNavs = [
   {
@@ -39,30 +40,6 @@ const MyAccountPage = () => {
       <div className="">
         <h3 className="mt-8 ml-4 text-base">My Order</h3>
         <div className="flex flex-wrap justify-between mt-5 pr-4 pl-4">
-          {/* <a href="/sales/order/history/#all" className="decoration-none color-black">
-            <div className="w-21 flex flex-col items-center font-medium text-xs">
-                <img src="/image/navigate/202309040910.png" className='w-6 h-6' alt="" />
-                <p className='text-center mt-2.5'>All</p>
-            </div>
-        </a>
-        <a href="/sales/order/history/#processing" className="decoration-none color-black">
-            <div className="width-21 flex flex-col items-center font-medium text-xs">
-                <img src="/image/navigate/202309040911.png" className='w-6 h-6' alt=""/>
-                <p className='text-center mt-2.5'>Processing</p>
-            </div>
-        </a>
-        <a href="/sales/order/history/#complete" className="decoration-none color-black">
-            <div className="width-21 flex flex-col items-center font-medium text-xs">
-                <img src="/image/navigate/202309040912.png" className='w-6 h-6' alt="" />
-                <p className='text-center mt-2.5'>Complete</p>
-            </div>
-        </a>
-        <a href="/sales/order/history/#paymentreview" className="decoration-none color-black">
-            <div className="width-21 flex flex-col items-center font-medium text-xs">
-                <img src="/image/navigate/202309040913.png" className='w-6 h-6 mb-0.5' alt="" />
-                <p className='text-center mt-2.5'>Payment <br/> Review</p>
-            </div>
-        </a> */}
           {orderNavs.map((item, index) => (
             <a
               key={index}
@@ -85,7 +62,7 @@ const MyAccountPage = () => {
       <div className="h-2.5 bg-[#F0F0F0]"></div>
 
       {/* 服务菜单 */}
-      <div className="">
+      {/* <div className="">
         <h3 className="text-base mt-7.5 ml-4 font-bold">Alipearl Service</h3>
         <div className="grid grid-cols-4 gap-1 text-center font-medium text-xs pr-4 pl-4 mb-7.5">
           <a className="flex flex-col items-center mt-5">
@@ -146,7 +123,7 @@ const MyAccountPage = () => {
             <span className="mt-2.5">Wholesale</span>
           </a>
         </div>
-      </div>
+      </div> */}
 
       <div className="h-2.5 bg-[#F0F0F0]"></div>
       {/* 商品推荐 */}
@@ -159,7 +136,8 @@ const MyAccountPage = () => {
         </div>
         {/* 引入轮播组件 */}
         <AccountSwiper />
-       
+       {/* 底部跳转轮播 */}
+       <AccountBottomHrefSwiper />
       </div>
     </div>
   );

+ 100 - 221
src/app/(public)/customer/account/pointlist/page.tsx

@@ -1,201 +1,93 @@
 "use client";
-// import Image from "next/image";
 import React, { useState, useEffect, useRef, useCallback } from "react";
+import Link from "next/link";
+import { clientFetch } from "@/lib/restApiClient";
 
+// 👉 你的 TS 类型(正确)
 interface ListDates {
+  amount: number;
+  balance: number;
+  created_at: string;
+  detail: string;
+  expired_day: number;
+  expired_time: string | null;
   id: number;
-  points: string;
-  title: string;
-  type: string;
-  // desc: string;
-  date: string;
-  // createTime: string;
-  currentPoints: string;
-  // 你自己加字段...
+  order_id: number | null;
+  remaining_points: number;
+  status: number;
+  status_text: string;
+  type: number;
+  type_text: string;
 }
-// 模拟积分明细数据(可替换为接口请求)
-const mockPointsData: ListDates[] = [
-  {
-    id: 1,
-    type: "rewarded",
-    title: "Check In",
-    points: "+10 Points",
-    date: "May 17, 2026 11:07 PM",
-    currentPoints: "My points:100009",
-  },
-  {
-    id: 2,
-    type: "rewarded",
-    title: "Points Return of Redeem Hair Usage",
-    points: "+12000 Points",
-    date: "Oct 23, 2025 1:11 AM",
-    currentPoints: "My points:99999",
-  },
-  {
-    id: 3,
-    type: "used",
-    title: "Points Redeem Hair",
-    points: "-6000 Points",
-    date: "Oct 17, 2025 10:27 PM",
-    currentPoints: "My points:87999",
-  },
-  {
-    id: 4,
-    type: "used",
-    title: "Points Redeem Hair",
-    points: "-6000 Points",
-    date: "Oct 17, 2025 10:25 PM",
-    currentPoints: "My points:93999",
-  },
-  {
-    id: 5,
-    type: "rewarded",
-    title: "Updated by Admin",
-    points: "+99999 Points",
-    date: "Oct 17, 2025 10:24 PM",
-    currentPoints: "My points:99999",
-  },
-  {
-    id: 6,
-    type: "used",
-    title: "Points Redeem Giftcard",
-    points: "-300 Points",
-    date: "Oct 13, 2025 8:41 PM",
-    currentPoints: "My points:0",
-  },
-  {
-    id: 7,
-    type: "rewarded",
-    title: "Reward for Registering",
-    points: "+200 Points",
-    date: "Jun 9, 2025 7:39 PM",
-    currentPoints: "My points:300",
-  },
-  {
-    id: 8,
-    type: "rewarded",
-    title: "Reward for Signing up Newsletter",
-    points: "+100 Points",
-    date: "Jun 9, 2025 7:39 PM",
-    currentPoints: "My points:100",
-  },
-  // 补充测试数据(凑够分页演示)
-  {
-    id: 9,
-    type: "rewarded",
-    title: "Check In",
-    points: "+10 Points",
-    date: "May 16, 2026 10:00 PM",
-    currentPoints: "My points:100000",
-  },
-  {
-    id: 10,
-    type: "used",
-    title: "Points Redeem Accessory",
-    points: "-500 Points",
-    date: "May 15, 2026 9:00 AM",
-    currentPoints: "My points:99990",
-  },
-  {
-    id: 11,
-    type: "rewarded",
-    title: "Review Product",
-    points: "+50 Points",
-    date: "May 14, 2026 8:00 PM",
-    currentPoints: "My points:100490",
-  },
-  {
-    id: 12,
-    type: "rewarded",
-    title: "Refer a Friend",
-    points: "+200 Points",
-    date: "May 13, 2026 7:00 PM",
-    currentPoints: "My points:100440",
-  },
-  {
-    id: 13,
-    type: "used",
-    title: "Points Redeem Cash",
-    points: "-1000 Points",
-    date: "May 12, 2026 6:00 PM",
-    currentPoints: "My points:100240",
-  },
-  {
-    id: 14,
-    type: "rewarded",
-    title: "Check In",
-    points: "+10 Points",
-    date: "May 11, 2026 5:00 PM",
-    currentPoints: "My points:101240",
-  },
-];
 
 const PointsDetails = () => {
-  // 状态管理
-  const [activeTab, setActiveTab] = useState("all"); // 激活标签:all/rewarded/used
-  const [listData, setListData] = useState<ListDates[]>([]); // 展示的列表数据
-  const [page, setPage] = useState(1); // 当前页码
-  const [loading, setLoading] = useState(false); // 加载状态
-  const [hasMore, setHasMore] = useState(true); // 是否有更多数据
-  const listRef = useRef<HTMLDivElement>(null); // 列表容器ref,用于监听滚动
+  // 状态
+  const [activeTab, setActiveTab] = useState<string>(""); // earned / redeemed / 空
+  const [listData, setListData] = useState<ListDates[]>([]);
+  const [page, setPage] = useState<number>(1);
+  const [loading, setLoading] = useState<boolean>(false);
+  const [hasMore, setHasMore] = useState<boolean>(true);
+  const listRef = useRef<HTMLDivElement>(null);
+  const PAGE_SIZE = 10;
 
-  // 每页展示12条
-  const PAGE_SIZE = 12;
-  const fetchData = useCallback((page: number, tab: string): Promise<ListDates[]> => {
-    return new Promise((resolve) => {
-        setTimeout(() => {
-          let data = mockPointsData;
-          const prePage = page - 1;
-          const start = prePage * PAGE_SIZE;
-          const end = page * PAGE_SIZE;
-          if (tab === "rewarded") {
-            data = mockPointsData.filter(
-              (item) => item.type === "rewarded",
-            );
-          } else if (tab === "used") {
-            data = mockPointsData.filter((item) => item.type === "used");
-          }
-          const res = data.slice(start, end);
-          resolve(res);
-        }, 800);
-    });
-  },[]);
-  // 加载更多数据
-  const loadMore = useCallback(() => {
+  // ==============================================
 
+  const loadData = useCallback(async (currentPage: number, tab: string) => {
     setLoading(true);
-    
-    fetchData(page+1,activeTab).then((res:any) => {
+    try {
+      const resp = await clientFetch(
+        `/api/customer/reward-points/history?page=${currentPage}&per_page=${PAGE_SIZE}&amountType=${tab}`,
+      );
 
-        setListData(res);
+      console.log("接口返回:", resp);
 
-        setLoading(false);
-        setPage(page+1);
-        if(res.length < PAGE_SIZE) {
-           setHasMore(false);
+      if (resp.status === 200) {
+        const formData = resp.data?.data || resp.data || { history: [] };
+        const newList = formData.history || [];
+        // 测试数据
+        //  const testList = [...newList, ...newList, ...newList, ...newList];
+        // 分页判断:如果返回不足10条 = 没有更多
+        if (newList.length < PAGE_SIZE) {
+          setHasMore(false);
+        } else {
+          setHasMore(true);
         }
-    });
-  },[fetchData,page,activeTab]);
-  
-  // 初始化
-  useEffect(() => {
-    // 筛选数据
-    fetchData(1,'all').then((result:any)=> {
-        
-        setListData(result);
-        // 判断是否有更多数据
-        setHasMore(!(result.length < PAGE_SIZE));
-    });
-    
+
+        if (currentPage === 1) {
+          setListData(newList);
+        } else {
+          setListData((prev) => [...prev, ...newList]);
+        }
+      }
+    } catch (err) {
+      console.error("加载失败:", err);
+    } finally {
+      setLoading(false);
+    }
   }, []);
 
-  // 监听滚动:触底加载
+  const loadMore = useCallback(() => {
+    console.log("loading:", loading, "hasMore:", hasMore);
+
+    if (loading || !hasMore) return;
+    const nextPage = page + 1;
+    loadData(nextPage, activeTab);
+    setPage(nextPage);
+  }, [loading, hasMore, page, activeTab, loadData]);
+
   useEffect(() => {
+    setListData([]); // 切换时清空列表
+    loadData(1, activeTab);
+    setPage(1);
+  }, [activeTab]);
+
+  useEffect(() => {
+    const container = listRef.current;
     const handleScroll = () => {
-      if (loading || !hasMore) return;
-      const container = listRef.current;
-      // 滚动到底部的判断:滚动高度 + 可视高度 ≥ 总高度 - 20(阈值)
-      if (container &&
+      // console.log("container:",container,"container.scrollTop:",container.scrollTop,"container.clientHeight:",container.clientHeight,"container.scrollHeight:",container.scrollHeight);
+
+      if (!container) return;
+      if (
         container.scrollTop + container.clientHeight >=
         container.scrollHeight - 20
       ) {
@@ -203,94 +95,82 @@ const PointsDetails = () => {
       }
     };
 
-    const container = listRef.current;
-    if (container) {
-      container.addEventListener("scroll", handleScroll);
-      return () => container.removeEventListener("scroll", handleScroll);
-    }
-  }, [loading, hasMore, loadMore]);
+    container?.addEventListener("scroll", handleScroll);
+    return () => container?.removeEventListener("scroll", handleScroll);
+  }, [loadMore]);
 
   const clickActiveTab = (tab: string) => {
-      setActiveTab(tab);
-      setPage(1);
-      fetchData(1, tab);
-  }
+    setActiveTab(tab);
+  };
 
   return (
     <div className="min-h-screen bg-white">
-      {/* 顶部导航栏 */}
+      {/* 顶部 */}
       <div className="sticky top-0 z-10 bg-white border-b border-gray-100 py-4 px-4 flex items-center">
-        {/* 返回按钮 */}
-        <button className="mr-4">
+        <Link href={"/customer/account/mypoints"} className="mr-4">
           <svg viewBox="0 0 24 24" width="20" height="20" fill="black">
             <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
           </svg>
-        </button>
-        {/* 标题 */}
+        </Link>
         <h1 className="text-xl font-bold text-black">Points Details</h1>
       </div>
 
-      {/* 标签切换栏 */}
+      {/* Tab */}
       <div className="flex border-b border-gray-200">
         <button
-          onClick={() => clickActiveTab("all")}
+          onClick={() => clickActiveTab("")}
           className={`px-6 py-3 font-medium text-sm ${
-            activeTab === "all"
-              ? "bg-black text-white"
-              : "bg-white text-gray-600 hover:bg-gray-50"
+            activeTab === "" ? "bg-black text-white" : "bg-white text-gray-600"
           }`}
         >
           ALL
         </button>
         <button
-          onClick={() => clickActiveTab("rewarded")}
+          onClick={() => clickActiveTab("earned")}
           className={`px-6 py-3 font-medium text-sm ${
-            activeTab === "rewarded"
+            activeTab === "earned"
               ? "bg-black text-white"
-              : "bg-white text-gray-600 hover:bg-gray-50"
+              : "bg-white text-gray-600"
           }`}
         >
           REWARDED
         </button>
         <button
-          onClick={() => clickActiveTab("used")}
+          onClick={() => clickActiveTab("redeemed")}
           className={`px-6 py-3 font-medium text-sm ${
-            activeTab === "used"
+            activeTab === "redeemed"
               ? "bg-black text-white"
-              : "bg-white text-gray-600 hover:bg-gray-50"
+              : "bg-white text-gray-600"
           }`}
         >
           USED
         </button>
       </div>
 
-      {/* 积分明细列表(带滚动监听) */}
+      {/* 列表 */}
       <div ref={listRef} className="h-[calc(100vh-120px)] overflow-y-auto">
         {listData.length > 0 ? (
           <div className="divide-y divide-gray-100">
             {listData.map((item) => (
               <div key={item.id} className="px-4 py-4">
-                {/* 左侧:标题+日期 */}
                 <div className="flex justify-between items-start">
                   <div>
                     <h3 className="text-base font-medium text-black">
-                      {item.title}
+                      {item.detail}
                     </h3>
-                    <p className="text-xs text-gray-500 mt-1">{item.date}</p>
+                    <p className="text-xs text-gray-500 mt-1">
+                      {item.created_at}
+                    </p>
                   </div>
-                  {/* 右侧:积分变动 */}
                   <div className="text-right">
                     <p
-                      className={`text-base font-medium ${
-                        item.points.startsWith("+")
-                          ? "text-black"
-                          : "text-black"
-                      }`}
+                      className={`text-base font-medium ${item.amount >= 0 ? "text-green-600" : "text-black"}`}
                     >
-                      {item.points}
+                      {item.amount >= 0 ? `+${item.amount}` : item.amount}{" "}
+                      Points
                     </p>
                     <p className="text-xs text-gray-500 mt-1">
-                      {item.currentPoints}
+                      My points: {item.balance}
                     </p>
                   </div>
                 </div>
@@ -298,16 +178,15 @@ const PointsDetails = () => {
             ))}
           </div>
         ) : (
-          // 空数据提示
           <div className="flex items-center justify-center h-32">
             <p className="text-gray-500 text-sm">NO DATA</p>
           </div>
         )}
 
-        {/* 加载中/无更多提示 */}
+        {/* 加载提示 */}
         <div className="px-4 py-3 text-center text-sm">
           {loading && <p className="text-gray-500">Loading...</p>}
-          {!loading && !hasMore && (
+          {!loading && !hasMore && listData.length > 0 && (
             <p className="text-gray-500">NO MORE DATA</p>
           )}
         </div>

+ 5 - 1
src/app/(public)/customer/address/page.tsx

@@ -25,7 +25,7 @@ const CustomerAddressPage = async () => {
   return (
     <div className="w-full h-full">
       <div className="bg-[#fff] pr-2.5 pl-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
-        <Link href={""}
+        <Link href={"/customer/account"}
           // onClick={() => window.history.back()}
           className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2"
         >
@@ -94,7 +94,11 @@ const CustomerAddressPage = async () => {
             </div>
           </div>
         </div>
+        <Link href={"/customer/address/new"} className="text-center bg-[#000] block text-white mt-5  width-full height-11 leading-11 text-lg" >
+          + New Address
+        </Link>
       </div>
+    
     </div>
   );
 };

+ 286 - 0
src/app/(public)/customer/order/history/_components/OrderHistory.tsx

@@ -0,0 +1,286 @@
+"use client";
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+
+// 模拟订单数据
+const mockOrders = [
+  {
+    id: 600217097,
+    date: "3/4/2026",
+    status: "Processing",
+    items: [
+      {
+        name: "Highlight Wigs For Women Headband Wigs with Blonde Highlights",
+        specs: "10, #022, Straight, 5 Free Headbands",
+        price: "88 points + $154.05x1",
+      },
+    ],
+    progress: "Preparing Order",
+    total: "88 Points + $158.67",
+  },
+  {
+    id: 100055662,
+    date: "3/3/2026",
+    status: "Processing",
+    items: [
+      {
+        name: "Alipearl Lace Front Wig 180% Density Body Wave Human Hair Wigs Pre...",
+        specs: "10+180%, Medium, No Adjustable Band",
+        price: "$128.82x1",
+      },
+    ],
+    progress: "Preparing Order",
+    total: "$132.68",
+  },
+  {
+    id: 600217055,
+    date: "3/3/2026",
+    status: "Processing",
+    items: [
+      {
+        name: "3 Bundles Deep Wave Hair With 4*4 Lace Closure Alipearl Brazilian Hair",
+        specs: "8 8 8, Free Part, 8",
+        price: "$142.14x1",
+      },
+      {
+        name: "Princess Braids Wig Blonde Barbie Pre-Styled Highlight Wig 13×4 Front Wig",
+        specs: "12",
+        price: "$279.32x1",
+        image:
+          "https://cdn.alipearlhair.com/media/catalog/product/cache/6/thumbnail/750x1000/9df78eab33525d08d6e5fb8d27136e95/b/o/body_wave_lace_front_wig_2.jpg", // 替换为实际图片路径
+      },
+      {
+        name: "Deep Wave Wig 100 Human Hair Swiss Lace Wig Lace Front Wig",
+        specs: "10, Medium, 100%",
+        price: "$252.00x1",
+      },
+      {
+        name: "Braids Wig Blonde Barbie Pre-Styled Highlight Wig 13×4 Front Wig",
+        specs: "12",
+        price: "6000 points + $279.32x1",
+        image:
+          "https://cdn.alipearlhair.com/media/catalog/product/cache/6/thumbnail/750x1000/9df78eab33525d08d6e5fb8d27136e95/b/o/body_wave_lace_front_wig_2.jpg", // 替换为实际图片路径
+      },
+      {
+        name: "random free wigs-2",
+        specs: "",
+        price: "$0.00x1",
+      },
+    ],
+    progress: "Preparing Order",
+    discount: "-$80.00",
+    total: "$872.78",
+  },
+];
+
+// 标签列表
+const tabs = [
+  { key: "all", label: "All" },
+  { key: "processing", label: "Processing" },
+  { key: "complete", label: "Complete" },
+  { key: "Payment", label: "Payment Review" }, // 补充模拟标签,测试滚动
+];
+
+export default function OrderHistory() {
+  const router = useRouter();
+  const [activeTab, setActiveTab] = useState("all");
+  const [filteredOrders, setFilteredOrders] = useState(mockOrders);
+
+  // 监听URL哈希值,初始化选中标签
+  useEffect(() => {
+    const hash = window.location.hash.slice(1) || "all"; // 解析 #all -> all
+    const validTab = tabs.find((t) => t.key === hash.toLowerCase());
+    setActiveTab(validTab ? validTab.key : "all");
+
+    // 模拟接口请求:根据标签传不同参数(实际项目替换为真实接口)
+    fetchOrders(validTab ? validTab.key : "all");
+
+    // 监听哈希变化
+    const handleHashChange = () => {
+      const newHash = window.location.hash.slice(1) || "all";
+      const validNewTab = tabs.find((t) => t.key === newHash.toLowerCase());
+      if (validNewTab) {
+        setActiveTab(validNewTab.key);
+        fetchOrders(validNewTab.key);
+      }
+    };
+    window.addEventListener("hashchange", handleHashChange);
+    return () => window.removeEventListener("hashchange", handleHashChange);
+  }, []);
+
+  // 模拟接口请求:不同标签传不同参数
+  const fetchOrders = (tabKey: string) => {
+    console.log("请求接口参数:", tabKey); // 实际项目替换为真实接口调用
+    // 这里简化逻辑,仅过滤Processing状态(实际接口返回对应数据)
+    if (tabKey === "processing") {
+      setFilteredOrders(mockOrders.filter((o) => o.status === "Processing"));
+    } else if (tabKey === "complete") {
+      setFilteredOrders([]); // 模拟无完成订单
+    } else {
+      setFilteredOrders(mockOrders);
+    }
+  };
+
+  // 点击标签切换
+  const handleTabClick = (tabKey: string) => {
+    window.location.hash = tabKey; // 更新URL哈希
+    setActiveTab(tabKey);
+    fetchOrders(tabKey);
+  };
+  
+  return (
+    <>
+      <div className="min-h-screen bg-gray-50">
+        {/* 顶部返回栏 + 标题 */}
+        <div className="sticky top-0 z-20 bg-white px-4 py-3 border-b flex justify-between items-center w-full">
+          <button className="text-black text-xl mr-4">
+            <svg
+             
+              className="w-6 h-6"
+              viewBox="0 0 1024 1024"
+              version="1.1"
+              xmlns="http://www.w3.org/2000/svg"
+              p-id="111033"
+              width="200"
+              height="200"
+            >
+              <path
+                d="M391.466667 512l339.882666-339.882667a46.592 46.592 0 1 0-65.877333-65.877333L292.693333 479.061333a46.592 46.592 0 0 0 0 65.877334l372.821334 372.821333a46.592 46.592 0 1 0 65.877333-65.877333L391.466667 512z"
+                p-id="111034"
+              ></path>
+            </svg>
+          </button>
+          <h1 className="text-xl font-bold mx-auto">Orders</h1>
+          <div className="w-6"></div>
+        </div>
+
+        {/* 标签栏(固定顶部 + 横向滚动) */}
+        <div className="sticky top-14 z-10 bg-white px-4 py-2 border-b">
+          <div className="flex items-center space-x-6 overflow-x-auto pb-1 scrollbar-hide">
+            {tabs.map((tab) => (
+              <button
+                key={tab.key}
+                onClick={() => handleTabClick(tab.key)}
+                className={`whitespace-nowrap py-1 px-3 rounded-full text-sm font-medium transition-colors
+                ${
+                  activeTab === tab.key
+                    ? "bg-black text-white"
+                    : "text-gray-600 hover:bg-gray-100"
+                }`}
+              >
+                {tab.label}
+              </button>
+            ))}
+          </div>
+        </div>
+
+        {/* 订单列表 */}
+        <div className="px-4 py-3">
+          {filteredOrders.length === 0 ? (
+            <div className="text-center py-8 text-gray-500">
+              No orders found
+            </div>
+          ) : (
+            filteredOrders.map((order) => (
+            
+              <div
+                key={order.id}
+                className="bg-white rounded-lg p-4 mb-4 border"
+              >
+                {/* 订单状态 + 编号 + 日期 */}
+                <div className="mb-3">
+                  <p className="font-medium text-gray-800">• {order.status}</p>
+                  <div className="flex justify-between text-sm text-gray-600 mt-1">
+                    <span>Order Number: {order.id}</span>
+                    <span>Date: {order.date}</span>
+                  </div>
+                </div>
+
+                {/* 订单商品列表 */}
+                <div className="mb-4 space-y-3">
+                  {order.items.map((item, idx) => (
+                    <div key={idx} className="flex gap-3">
+                      {/* 商品图片(有则显示,无则占位) */}
+                      {item.image ? (
+                        <img
+                          src={item.image}
+                          alt={item.name}
+                          className="w-22.5 h-22.5 object-cover rounded"
+                        />
+                      ) : (
+                        <div className="w-22.5 h-22.5 bg-gray-100 rounded"></div>
+                      )}
+                      {/* 商品信息 */}
+                      <div className="flex-1">
+                        <p className="text-sm text-gray-800 line-clamp-2">
+                          {item.name}
+                        </p>
+                        {item.specs && (
+                          <p className="text-xs text-gray-500 mt-1">
+                            {item.specs}
+                          </p>
+                        )}
+                        <p className="text-xs text-gray-600 mt-1">
+                          {item.price}
+                        </p>
+                      </div>
+                    </div>
+                  ))}
+                </div>
+
+                {/* 订单进度条 */}
+                <Link href={`/customer/order/track/order_id/${order.id}`} className="mb-4 h-8 bg-gray-100 rounded-full h-2 flex items-center px-2">
+                  <span className="ml-2 text-xs text-gray-700 flex-1 flex justify-between">
+                    {order.progress}
+                    <span>
+                      <svg
+                        className="w-4 h-4"
+                        viewBox="0 0 1024 1024"
+                        version="1.1"
+                        xmlns="http://www.w3.org/2000/svg"
+                        p-id="3784"
+                        width="200"
+                        height="200"
+                      >
+                        <path
+                          d="M761.056 532.128c0.512-0.992 1.344-1.824 1.792-2.848 8.8-18.304 5.92-40.704-9.664-55.424L399.936 139.744a48 48 0 0 0-65.984 69.76l316.96 299.84L335.2 813.632a48 48 0 0 0 66.624 69.12l350.048-337.376c0.672-0.672 0.928-1.6 1.6-2.304 0.512-0.48 1.056-0.832 1.568-1.344 2.72-2.848 4.16-6.336 6.016-9.6z"
+                          p-id="3785"
+                        ></path>
+                      </svg>
+                    </span>
+                  </span>
+                </Link>
+
+                {/* 折扣 + 总计 + 操作按钮 */}
+                <div className="text-right text-sm">
+                  {order.discount && (
+                    <p className="text-red-500 flex items-center justify-between">
+                      <span>Discount</span> <span>{order.discount}</span>
+                    </p>
+                )}
+                  <p className="font-bold mt-1 flex items-center justify-between">
+                    <span>
+                      Order Total ({order.items.length} item
+                      {order.items.length > 1 ? "s" : ""})
+                    </span>{" "}
+                    <span>{order.total}</span>
+                  </p>
+
+                  <div className="flex justify-between mt-3">
+                    <Link href={`/customer/order/view/order_id/${order.id}`} className="text-black-600 text-sm underline">
+                      View Order
+                    </Link>
+                    <Link href={`/customer/order/track/order_id/${order.id}`}  state={{ addressData: { /* 你的地址对象 */ } }}  className="border border-gray-300 rounded-full px-4 py-1 text-sm text-gray-800 hover:bg-gray-50">
+                      Track Order
+                    </Link>
+                  </div>
+                </div>
+              </div>
+            ))
+          )}
+        </div>
+      </div>
+    </>
+  );
+}

+ 10 - 0
src/app/(public)/customer/order/history/page.tsx

@@ -0,0 +1,10 @@
+
+import OrderHistory from "./_components/OrderHistory"
+export default function OderIndex(){
+
+  return (
+    <>
+     <OrderHistory />
+    </>
+  );
+}

+ 47 - 0
src/app/(public)/customer/order/track/order_id/[id]/_components/TrackDetail.tsx

@@ -0,0 +1,47 @@
+"use client";
+import { useEffect, useState } from "react";
+
+// 地址类型
+interface AddressData {
+  name: string;
+  line1: string;
+  line2: string;
+}
+
+export default function TrackAddress() {
+  const [addr, setAddr] = useState<AddressData | null>(null);
+
+  // 模拟接口拿单条订单地址数据
+  useEffect(() => {
+    const getAddress = async () => {
+      // 模拟接口返回假数据,和截图一致
+      const mockRes: AddressData = {
+        name: "zhang zhenfei",
+        line1: "aaaaaaaaaaaa, zzzzzzzz, Alabama",
+        line2: "90602, United States",
+      };
+      setAddr(mockRes);
+    };
+    getAddress();
+  }, []);
+
+  if (!addr) return <div className="p-4 text-gray-500">加载地址...</div>;
+
+  return (
+    <div className="flex items-start gap-3 py-3 border-b border-gray-200">
+      {/* 飞机图标 */}
+      <div className="mt-1 shrink-0 text-gray-700">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+          <path d="M17 16l-4-4V7h4l-4-4-4 4h4v5l-4 4" />
+          <line x1="12" y1="19" x2="12" y2="22" />
+        </svg>
+      </div>
+
+      {/* 地址文本 */}
+      <div className="flex flex-col">
+        <p className="text-gray-800">{addr.name}, {addr.line1}</p>
+        <p className="text-gray-600 mt-1">{addr.line2}</p>
+      </div>
+    </div>
+  );
+}

+ 74 - 0
src/app/(public)/customer/order/track/order_id/[id]/page.tsx

@@ -0,0 +1,74 @@
+import OrderHeader from "../../../view/order_id/[id]/_components/OrderHeader";
+import AccountSwiper from "@components/customer/accountSwiper";
+import TrackDetail from "./_components/TrackDetail"
+type Params = {
+  id: string;
+};
+
+export default async function TrackOrderDetail({ params }: { params: Params }) {
+  // 拿到动态订单ID:211600
+  const orderId = params.id;
+
+  // === 服务端请求订单接口,传入订单号 ===
+//   const res = await fetch(
+//     `${process.env.API_URL}/api/order/detail?id=${orderId}`,
+//     {
+//       next: { revalidate: 0 },
+//     },
+//   );
+  //   const orderData = await res.json();
+  console.log("orderId:",orderId);
+  const orderData = {
+    shipTo: "Zhang Zhenfei,\nAaaaaaaa, Zzzzzz, Alaska\n90602, United States",
+    orderNo: orderId, // 自动使用路由传入的订单号
+    status: "Processing",
+    orderDate: "March 6, 2026",
+    shippingMethod: "USPS/FedEx(2-7 Business Days)",
+    paymentMethod: "Klarna",
+    CustomerEmail:"18567768109@163.Com",
+    paymentDetail: {
+      Customer: "18567768109",
+      Email: "@163.Com",
+      "Payment Method": "PayoneerKlarna",
+      "Payment Status": "Processing",
+      "Order No": "100055676",
+      "Customer Firstname": "Zhang",
+      "Customer Lastname": "Zhenfei",
+    },
+    // 多商品数组,自动兼容1件/多件商品
+    items: [
+      {
+        name: "Human Hair Headband Wigs Curly Bob Wig Straight Hair Wigs",
+        spec: "8, Straight, 5 Free Headbands.",
+        price: "$114.11",
+        qty: 1,
+      },
+      // 你可以取消注释,测试多商品渲染
+      /*
+      {
+        name: "Deep Wave Lace Front Human Hair Wig",
+        spec: "13x4 Lace, Medium Cap, 180% Density",
+        price: "$252.00",
+        qty: 2,
+      },
+      */
+    ],
+    price: {
+      subtotal: "$114.11",
+      shipping: "$0.00",
+      insurance: "$3.42",
+      total: "$117.53",
+    },
+  };
+  return (
+    <div className="bg-gray-50 min-h-screen">
+      {/* 顶部固定导航栏(客户端组件:返回按钮) */}
+      <OrderHeader title="Track Order" />
+
+      <div className="px-5 py-4">
+        <TrackDetail />
+      </div>
+         <AccountSwiper/>
+    </div>
+  );
+}

+ 30 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/OrderHeader.tsx

@@ -0,0 +1,30 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+
+export default function OrderHeader({ title }: { title: string }) {
+  const router = useRouter();
+  return (
+    <div className="sticky top-0 z-50 bg-white px-5 py-4 border-b">
+      <div className="flex items-center w-full">
+        <button onClick={() => router.back()} className="text-xl">
+          <svg
+            className="w-6 h-6"
+            viewBox="0 0 1024 1024"
+            version="1.1"
+            xmlns="http://www.w3.org/2000/svg"
+            p-id="111033"
+            width="200"
+            height="200"
+          >
+            <path
+              d="M391.466667 512l339.882666-339.882667a46.592 46.592 0 1 0-65.877333-65.877333L292.693333 479.061333a46.592 46.592 0 0 0 0 65.877334l372.821334 372.821333a46.592 46.592 0 1 0 65.877333-65.877333L391.466667 512z"
+              p-id="111034"
+            ></path>
+          </svg>
+        </button>
+        <h1 className="flex-grow text-center text-lg font-bold">{title}</h1>
+      </div>
+    </div>
+  );
+}

+ 50 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/OrderInfoBlock.tsx

@@ -0,0 +1,50 @@
+interface Props {
+  data: {
+    shipTo: string;
+    orderNo: string;
+    status: string;
+    orderDate: string;
+    shippingMethod: string;
+    paymentMethod: string;
+    CustomerEmail:string;
+    paymentDetail: Record<string, string>;
+  };
+}
+
+export default function OrderInfoBlock({ data }: Props) {
+  return (
+    <>
+      <h2 className="text-xl font-bold mb-6 border-b pb-3">Order Information</h2>
+
+      {/* 两列网格布局,所有行严格对齐,Payment区域不会错位 */}
+      <div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-5">
+        <div className="font-medium text-gray-700">Ship To:</div>
+        <div>{data.shipTo}</div>
+
+        <div className="font-medium text-gray-700">Order Number:</div>
+        <div>{data.orderNo}</div>
+
+        <div className="font-medium text-gray-700">Status:</div>
+        <div>{data.status}</div>
+
+        <div className="font-medium text-gray-700">Order Date:</div>
+        <div>{data.orderDate}</div>
+
+        <div className="font-medium text-gray-700">Customer Email:</div>
+        <div>{data.CustomerEmail}</div>
+        <div className="font-medium text-gray-700">Shipping Method:</div>
+        <div>{data.shippingMethod}</div>
+        <div className="font-medium text-gray-700">Payment Method:</div>
+        <div className="bg-pink-300 px-3 py-1 rounded w-fit">Klarna</div>
+        {/* <div className="flex flex-col gap-2">
+          <span className="bg-pink-300 px-3 py-1 rounded w-fit">Klarna.</span>
+          <div className="space-y-1">
+            {Object.entries(data.paymentDetail).map(([key, val]) => (
+              <div key={key}>{key} {val}</div>
+            ))}
+          </div>
+        </div> */}
+      </div>
+    </>
+  );
+}

+ 39 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/PriceSummary.tsx

@@ -0,0 +1,39 @@
+interface PriceData {
+  subtotal: string;
+  shipping: string;
+  insurance: string;
+  total: string;
+}
+
+interface Props {
+  priceData: PriceData;
+}
+
+export default function PriceSummary({ priceData }: Props) {
+  return (
+    <div className="mt-6">
+      <div className="space-y-3 border-t pt-4">
+        <div className="flex justify-between text-gray-700">
+          <span>Subtotal</span>
+          <span>{priceData.subtotal}</span>
+        </div>
+        <div className="flex justify-between text-gray-700">
+          <span>Shipping & Handling</span>
+          <span>{priceData.shipping}</span>
+        </div>
+        <div className="flex justify-between text-gray-700">
+          <span>Insurance (Insurance for Lost)</span>
+          <span>{priceData.insurance}</span>
+        </div>
+      </div>
+
+      {/* 底部总价栏,浅黄色背景 */}
+      <div className="bg-amber-50 mt-4 py-3 px-2 rounded">
+        <div className="flex justify-between items-center">
+          <p className="text-lg font-bold">Grand Total</p>
+          <p className="text-lg font-bold">{priceData.total}</p>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 40 - 0
src/app/(public)/customer/order/view/order_id/[id]/_components/ProductList.tsx

@@ -0,0 +1,40 @@
+interface ProductItem {
+  name: string;
+  spec: string;
+  price: string;
+  qty: number;
+  image:string;
+}
+
+interface Props {
+  items: ProductItem[];
+}
+
+export default function ProductList({ items }: Props) {
+  return (
+    <div className="mb-6 space-y-4">
+      {items.map((item, index) => (
+        <div className="flex items-center justify-between gap-3" key={index}>
+          {item.image ? (
+            <img
+              src={item.image}
+              alt={item.name}
+              className="w-22.5 h-23 object-cover rounded shrink-0"
+            />
+          ) : (
+            <div className="w-31 h-23 bg-gray-100 rounded shrink-0"></div>
+          )}
+
+          <div>
+            <p className="font-medium">{item.name}</p>
+            <p className="text-sm text-gray-600 my-1">{item.spec}</p>
+            <div className="flex justify-between">
+              <span>{item.price}</span>
+              <span>X {item.qty}</span>
+            </div>
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+}

+ 93 - 0
src/app/(public)/customer/order/view/order_id/[id]/page.tsx

@@ -0,0 +1,93 @@
+import OrderHeader from "./_components/OrderHeader";
+import OrderInfoBlock from "./_components/OrderInfoBlock";
+import ProductList from "./_components/ProductList";
+import PriceSummary from "./_components/PriceSummary";
+
+type Params = {
+  id: string;
+};
+
+export default async function OrderDetail({ params }: { params: Params }) {
+  // 拿到动态订单ID:211600
+  const orderId = params.id;
+
+  // === 服务端请求订单接口,传入订单号 ===
+//   const res = await fetch(
+//     `${process.env.API_URL}/api/order/detail?id=${orderId}`,
+//     {
+//       next: { revalidate: 0 },
+//     },
+//   );
+  //   const orderData = await res.json();
+  console.log("orderId:",orderId);
+  const orderData = {
+    shipTo: "Zhang Zhenfei,\nAaaaaaaa, Zzzzzz, Alaska\n90602, United States",
+    orderNo: orderId, // 自动使用路由传入的订单号
+    status: "Processing",
+    orderDate: "March 6, 2026",
+    shippingMethod: "USPS/FedEx(2-7 Business Days)",
+    paymentMethod: "Klarna",
+    CustomerEmail:"18567768109@163.Com",
+    paymentDetail: {
+      Customer: "18567768109",
+      Email: "@163.Com",
+      "Payment Method": "PayoneerKlarna",
+      "Payment Status": "Processing",
+      "Order No": "100055676",
+      "Customer Firstname": "Zhang",
+      "Customer Lastname": "Zhenfei",
+    },
+    // 多商品数组,自动兼容1件/多件商品
+    items: [
+      {
+        name: "Human Hair Headband Wigs Curly Bob Wig Straight Hair Wigs",
+        spec: "8, Straight, 5 Free Headbands.",
+        price: "$114.11",
+        qty: 1,
+      },
+      // 你可以取消注释,测试多商品渲染
+      /*
+      {
+        name: "Deep Wave Lace Front Human Hair Wig",
+        spec: "13x4 Lace, Medium Cap, 180% Density",
+        price: "$252.00",
+        qty: 2,
+      },
+      */
+    ],
+    price: {
+      subtotal: "$114.11",
+      shipping: "$0.00",
+      insurance: "$3.42",
+      total: "$117.53",
+    },
+  };
+  return (
+    <div className="bg-gray-50 min-h-screen">
+      {/* 顶部固定导航栏(客户端组件:返回按钮) */}
+      <OrderHeader title="Order Information" />
+
+      <div className="px-5 py-4">
+        {/* 收货信息、支付信息区块 */}
+        <OrderInfoBlock data={orderData} />
+
+        {/* 分享按钮区域 */}
+        <div className="my-6">
+          <h3 className="text-lg font-bold mb-4">Share order:</h3>
+          <div className="flex gap-6">
+            <span>fb</span>
+            <span>X</span>
+            <span>pinterest</span>
+            <span>whatsapp</span>
+          </div>
+        </div>
+
+        {/* 商品列表:循环渲染,自动兼容1件或多件商品 */}
+        <ProductList items={orderData.items} />
+
+        {/* 金额汇总 */}
+        <PriceSummary priceData={orderData.price} />
+      </div>
+    </div>
+  );
+}

+ 207 - 0
src/app/(public)/hair-care-tips/page.tsx

@@ -0,0 +1,207 @@
+import HeadBack from "@/components/head/HeadBack"
+export default function HairCareTips() {
+  return (
+    <>
+    <HeadBack />
+      <div className="min-h-screen bg-gray-50">
+        <div className="max-w-4xl mx-auto bg-white  shadow-sm p-8 sm:p-10">
+          <h1 className="text-2xl font-bold text-center text-gray-800 mb-10">
+            Human Hair Care Instructions
+          </h1>
+
+          <div className="space-y-8">
+        
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                1. About Combing
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                When Brushing Use The Appropriate Tools For Different Textures,
+                Start At The Ends Of Your Hair And Gently Work Your Way Up, In
+                This Way It Avoids Tangling And Pulling Damage. Always Brush In
+                A Downward Motion And Do Not Brush Harshly.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                2. About Washing
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Detangle Your Wig Gently By Using A Wide Tooth Comb. Curly Wigs
+                Should Be Untangled By Hands, No Combs Or Brushes.
+                <br />
+                Fill A Basin Or A Container With Cool Water And Mix In A Bit Of
+                Shampoo And Conditioner.
+                <br />
+                Immerse Your Wig And Swirl It Around Gently.
+                <br />
+                Let It Soak For About 5 Minutes.
+                <br />
+                Rinse Your Wig By Gently Running Cool Water Over It. Do Not Rub
+                As It Will Tangle Up The Wig.
+                <br />
+                Pat Dry With A Towel. Do Not Wring Or Rub, As Again, It Will
+                Mess Up The Wig.
+                <br />
+                Place Your Wig On A Wig Stand And Leave It To Air Dry.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                3. About Day & Night Maintenance
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                We Highly Suggested The Hair Be Washed And Conditioned At Least
+                Twice A Week.
+                <br />
+                Please Use A Soft Fabric Or Satin Hair Scrunchie To Achieve A
+                High Ponytail In Order To Avoid Tangling And Dryness.
+                <br />
+                When Not Wearing Your Wig, Place Your Wig On A Wig Stand Or
+                Mannequin Head.
+                <br />
+                At Night, It Is Better To Tie Hair Up With A Satin Scrunchie Or
+                Into A Satin Bonnet, An Alternative Satin Pillowcase.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                4. About Moisturize
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                At Least Once A Week .Go Through The Rows Of Your Braid Base And
+                Gently Apply Moisturizing Hair With Scalp Cream Or Argan Oil
+                (Eg, Mizani Cream, Coconut Oil, Shea Butter, Lotion)
+                <br />
+                It Is Advisable To Use The Products Containing The Herb In The
+                Case Where The Hair Begins To Become Dry And Yellow And Use The
+                Products With Special Effects Or Further Maintenance Which Are
+                Made For Protection&Cleaning In The Case Where The Hair
+                Bifurcates Or Even Becomes Invulnerable To Fragility.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                5. Straight Hair Care
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Straight Hair Is Easy To Manage. Use Small Amounts Of Hair Care
+                Products Is Suggested.
+                <br />
+                We Suggest Lightweight Serums For Straight Hair Products.
+                <br />
+                When Combing, A Wide Tooth Comb Is The Best Choice.
+                <br />
+                Hair Care Products That Contain Sulfate, Parabens, Or Alcohol
+                Will Dry Out The Air And Cause The Tangling,Please Do Not Choose
+                These Kinds Of Hair Care Products.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                6. Curly Hair Care
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Due To The Curl's Characteristics, Curly Hair Needs More
+                Hydration.
+                <br />
+                We Suggest Lightweight Serums And Leave-In Conditioners For
+                Curly Hair Products. These Care Products Will Help Keep Curly
+                Hair Moisturized And Tangle-Free. When Applying The Products, A
+                Quarter Size Of Each Product Is Enough To Keep Hair Defined, No
+                Need For More.
+                <br />
+                When Combing, A Wide Tooth Comb Is The Best Choice.
+                <br />
+                Please Avoid Using A Fine Tooth Comb For Curly Hair Since It
+                Will Cause Tangling And Shedding.
+                <br />
+                Hair Care Products That Contain Sulfate, Parabens, Or Alcohol
+                Will Dry Out The Air And Cause Tangling, Please Do Not Choose
+                These Kinds Of Hair Care Products.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                7. About Hot Tools
+              </h2>
+              <p className="text-gray-700 leading-relaxed">
+                Heat Is One Of The Biggest Enemy, The More You Use It, The
+                Shorter Your Hair Extension Will Last. We Don’t Suggest You Use
+                Hot Tools Too Often.
+                <br />
+                It Is Better To Use Heat Protectant Before Styling Your Wig And
+                We Do Suggest You Guys Flat Iron Your Wig In High Temperatures
+                With Caution. For Colored Hair, 350F Degrees Is The Suggested
+                Temperature; Flat Ironing Your Wig In A Temperature Above 400F
+                Degrees Is Not Suggested For Natural Black Hair Since It Will
+                Cause The Dryness Of The Wig. Meantime, Please Be Aware Of That
+                Be Sure To Leave The Hot Tools On Your Wig For No More Than 30
+                Seconds.
+                <br />
+                Tips: When Curling Or Straightening Your Wig With Hot Tools,
+                Please Clean Your Tools In Time To Avoid There Have Few Loose
+                Hair On The Tools. It Is Normal That There Have Loose Hair As
+                Well As You Clean Up The Hot Tools When Restyling.
+              </p>
+            </div>
+
+            <div>
+              <h2 className="text-lg font-semibold text-gray-800 mb-2">
+                8. Notes
+              </h2>
+              <div className="text-gray-700 leading-relaxed space-y-1">
+                <p>
+                  (1) Oil Sheen, Oil Sheen Is Not Recommended For Blond Hair,
+                  Oil May Cause Weave To Ship Out.
+                </p>
+                <p>
+                  (2) Wear A Cap Or Keep Your Hair Out Of Water. When Swimming.
+                  Chlorine And Salt Water Can Cause The Hair To Tangle And Mat
+                  Up.
+                </p>
+                <p>
+                  (3) Even If It’s 100% Human Hair Made, It’s Not Advised To
+                  Perm, Color, Or Any Other Chemical Process To Your Own Hair
+                  Extensions. At Least, Not Perm Too Often.
+                </p>
+                <p>
+                  (4) It Is Okay To Bleach The Knots But It Is Better To Find
+                  The Professional Hair Stylist To Help. ( It Is Better To Use
+                  The Foundation Method Rather Than Bleaching The Knots Which
+                  Can Help The Hair Lasts Longer.)
+                </p>
+                <p>
+                  (5) As A General Rule It Is Easier To Darken The Hair Than To
+                  Lighten Hair. We Recommend To Dyeing Darker, Since It Is
+                  Difficult For The Original Color To Fade. Improper Dying May
+                  Ruin The Hair. It Is Better To Find The Professional Hair
+                  Stylist To Help Dye The Hair.
+                </p>
+                <p>
+                  (6) Keep Your Hair Clean. Hair Tangles When Dirt And Sweat
+                  Build Up, Ash It After Exercise, Swimming, Etc.
+                </p>
+                <p>
+                  (7) Use Good Shampoo ( Sulfate Free, Parabens Free, Or Alcohol
+                  Free ), Your Stylist Can Advise You What Product Is Best For
+                  Your Hair. When Lots Of Water Is Added At Once To Very Dry
+                  Hair, Hair Can Swell Up And Tangle.
+                </p>
+                <p>
+                  (8) When Removing, Please Consult Your Professional Stylist.
+                </p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </>
+  );
+}

+ 688 - 0
src/app/(public)/help-faqs/_components/Faqs.tsx

@@ -0,0 +1,688 @@
+"use client";
+import { useState } from "react";
+import HeadBack from "@/components/head/HeadBack";
+type FaqItem = {
+  q: React.ReactNode;
+  a: React.ReactNode;
+};
+
+type FaqSection = {
+  title: string;
+  items: FaqItem[];
+};
+
+type FaqGroup = {
+  groupTitle: string;
+  sections: FaqSection[];
+};
+
+const faqData: FaqGroup[] = [
+  {
+    groupTitle: "Question Before Purchase",
+    sections: [
+      {
+        title: "1. About Order",
+        items: [
+          {
+            q: "How do I pay for an order?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  To pay for your order on www.asteriahair.com, follow the steps
+                  below:
+                </p>
+                <p>
+                  (1) Select the hair items you would like to order and click
+                  the "Buy Now" button.
+                </p>
+                <p>
+                  (2) Fill in the address information and choose the shipping
+                  method and payment method on the checkout page, then click the
+                  "Place Order" button.
+                </p>
+                <p>(3) Go to the payment platform to complete the payment.</p>
+              </div>
+            ),
+          },
+          {
+            q: "How do I use my coupon code? Can I use two coupon codes in one order?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  The purchasing process on our website is: Buy Now &gt Checkout
+                  &gt Confirm &gt Payment.
+                </p>
+                <p>
+                  When you are in the process of Checkout or Confirm, you can
+                  apply the coupon code if you have one, and please note that
+                  only one coupon code is allowed for one order.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "I have bought something but seen it at a different price on your website.",
+            a: (
+              <p>
+                As an online business we act much faster in adjusting our prices
+                in response to trends, stock and demand from customers. It's our
+                general view not to refund the difference.
+              </p>
+            ),
+          },
+          {
+            q: "Is there any hidden charges to my order?",
+            a: (
+              <p>
+                Basically, the final amount you will pay is just the price of
+                the product plus shipping(if you choose a paid shipping).
+                Sometimes, because of the exchange rate of currency, when the
+                payment finally cleared through the bank, the total amount will
+                be a little different to the price of the product, but its only
+                less than 0.5% of the total price.
+              </p>
+            ),
+          },
+          {
+            q: "Do I have to pay the customs duties & taxes?",
+            a: (
+              <p>
+                Orders shipped outside of the United States may be subject to
+                import taxes, customs duties and fees levied by the destination
+                country. The recipient of an international shipment may be
+                subject to such import taxes, customs duties and fees.
+                Additional charges for customs clearance must be fulfilled by
+                the recipient. Customs policies vary widely, the customs duty is
+                determined by the custom of your country, and it is uncertain to
+                know how much about it, you should contact your local customs
+                office for more information.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "2. About Products",
+        items: [
+          {
+            q: "Is your hair 100% human hair?",
+            a: <p>Yes, we only sell high quality 100% human virgin hair.</p>,
+          },
+          {
+            q: "Can I ask for small/large cap?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  Our Stock Size is Medium cap which is 22.5 inches, and we have
+                  the combs and elastic band or adjustable straps inside to help
+                  fit the head. Kindly Reminder:
+                </p>
+                <p>
+                  For full Lace Wig and 360 Lace Wig: The size of our full lace
+                  wigs and 360 lace wigs could not be changed.
+                </p>
+                <p>
+                  For U-part Wig and Headband Wig: The cap size of U-part wigs
+                  and headband wigs could not be changed, but the U-part wig’s
+                  top is empty so you can adjust the wig according to your head
+                  size, and the headband wig’s cap can be available with
+                  adjustable strap, elastic velcro snapping design wig with 5
+                  Clips, which can fit all head sizes perfectly, cap can be
+                  available with adjustable strap.
+                </p>
+                <p>For lace front / lace closure wig:</p>
+                <p>
+                  (1) Small Size is 21.5 inches, Large Size is 23.5 inches, you
+                  should know your own size and make a suitable decision.
+                </p>
+                <p>
+                  (2) If you change to those special cap sizes, you may need to
+                  wait about another extra 5-10 days. Would you like to wait? If
+                  you can wait and you want to ask for the small/big cap, please
+                  remember to leave the comments about it when you check out or
+                  send emails to us. Customer service email:{" "}
+                  <a
+                    href="mailto:server@asteriahair.com"
+                    className="text-blue-600 underline"
+                  >
+                    server@asteriahair.com
+                  </a>
+                  .
+                </p>
+                <p>
+                  (3) Custom-sized hairnets are subject to an additional charge
+                  of approximately $10.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "Does your wig come in bleached?",
+            a: (
+              <p>
+                Yes, The lace of our wigs are slightly bleached under the
+                condition that the hair would not shed, if you need more
+                bleaching, we advise to find a professional hairstylist to help.
+              </p>
+            ),
+          },
+          {
+            q: "Is your wig pre plucked?",
+            a: (
+              <p>
+                Yes, all of our wigs come slightly pre plucked, you can
+                customize it to your liking once you receive it.
+              </p>
+            ),
+          },
+          {
+            q: "Can your hair be dyed / bleached?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  Yes. Our hairs are 100% human virgin hair and all our natural
+                  color hair can be dyed or bleached to #27, as a general rule
+                  it is easier to darken the hair than to lighter the hair. We
+                  do suggest getting a professional opinion by an experienced
+                  colorist and having your hairdresser dye the hair as you will
+                  get the results you want, coloring them yourself always poses
+                  a high risk of not coming out the shade you want.
+                </p>
+                <p>
+                  We would advise for a one clip weft to be tested prior to
+                  coloring or restyling the full set to make sure the resulting
+                  shade is what you anticipate.
+                </p>
+                <p>
+                  Note: Any dyeing and bleaching is done at the customer's own
+                  discretion and we cannot be held accountable if the results do
+                  not come out as intended.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "How many hair bundles can make a full head?",
+            a: (
+              <p>
+                Normally the weight of 1 bundle is 95g-105g,we suggest you to
+                take 3-4 bundles for a full head,Especially for the length from
+                20inch to 30inch,4 bundles are necessary.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "3. About Payment",
+        items: [
+          {
+            q: "What types of payment do you accept?",
+            a: (
+              <p>
+                We accept the following payment methods: Paypal, Credit Cards
+                (Visa Card, MasterCard, and American Express), Klarna, Western
+                Union, Moneygram. If you have any payment problems, please
+                contact with us:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+          {
+            q: "Do you accept payment by installment?",
+            a: <p>Yes, Now we support PayPal Pay later and Klarna payment.</p>,
+          },
+          {
+            q: "When I pay via PayPal, do I have to have a PayPal account? Can I use credit cards?",
+            a: (
+              <p>
+                It is not necessary to have a PayPal account to order, you can
+                pay with Credit/Debit Card directly. When you check out, please
+                click paypal button, then you can choose the "Pay with Credit
+                Card" on pop-up payment page.
+              </p>
+            ),
+          },
+          {
+            q: "Why is my card declined?",
+            a: (
+              <p>
+                Sometimes when your card is expired, insufficient balance or
+                poor credit which may have a record of Chargeback or others will
+                lead to card declined when paying. So we suggest customers not
+                take a dispute for no reasons, because your credit record will
+                be affected, and your next shopping will be declined. When your
+                card is declined, you can call your bank for the problem. Or you
+                can try another card. If you still have problems, please send an
+                email to us :{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+          {
+            q: "Why did my payment fail?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  If your payment for an order has failed, please check it is
+                  not due to the following situations:
+                </p>
+                <p>
+                  (1) Card security code failed, Please note that an incorrect
+                  card security code could cause payment failure. Card security
+                  code is the short numeric code on the back of your credit
+                  card. Please verify that you have entered the correct card
+                  security code and try again.
+                </p>
+                <p>
+                  (2) Insufficient fund, If you meet the insufficient fund error
+                  log when you make payment by credit card, please make sure
+                  that you have sufficient funds in your account and that
+                  payment for your order does not exceed your credit limit.
+                </p>
+                <p>
+                  (3) Please also make sure you have used a Visa or MasterCard
+                  personal credit card to finish payment, because at present
+                  debit card or business card is unable to be supported.
+                </p>
+                <p>
+                  (4) Exceed limit, Please make sure the order amount does not
+                  exceed your credit limit,or the payment is unable to go
+                  through smoothly.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "If I pay on your website, Is my information safe?",
+            a: (
+              <p>
+                We are sure that our website is legal, which has been certified
+                by the card organization. So please don’t worry about that. Your
+                payment information is kept by your bank or card organization,
+                but not saved by us. We only use your shipping info for
+                delivery. Your information is safe and we will keep your
+                information confidential.
+              </p>
+            ),
+          },
+          {
+            q: "Can you send me an invoice?",
+            a: (
+              <p>
+                Yes, we can send you the invoice via Paypal Or Klarna if you
+                need, just feel free to contact us via email:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "4. About Shipment",
+        items: [
+          {
+            q: "How long will my hair be shipped after I place an order?",
+            a: (
+              <p>
+                Generally, your package will be shipped within 24 hours after
+                order confirm (except Weekends and Holiday). However, we may
+                need to wait for 2 or 3 days for the stock at times, thus the
+                delivery will be delayed for 2 or 3 days.
+              </p>
+            ),
+          },
+          {
+            q: "Which express do you use? How long can I receive my order?",
+            a: (
+              <p>
+                We use DHL, FedEx , USPS, GES, TNT, DPEX or UPS. Generally, the
+                package will be delivered within 3-7 business days to most
+                countries once shipped out. The time you receive your order will
+                depend on many factors, such as the shipping method and the
+                location of the shipping address. Please be aware of that bad
+                weathers, pandemic may cause the shipping delays sometimes. Once
+                your order has shipped out, you will receive the tracking number
+                by email.
+              </p>
+            ),
+          },
+          {
+            q: "Can you provide the Overnight / Expedite shipping service?",
+            a: (
+              <p>
+                1-2 day expedite shipping is available for USA orders if the
+                hair you want is in stock in our US warehouse. Please contact us
+                to confirm it before you order. Our Customer service email:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                .
+              </p>
+            ),
+          },
+          {
+            q: "Can I receive my order on Saturday or Sunday?",
+            a: (
+              <p>
+                Most express companies are weekend off. If the package has been
+                arrived your city, you can go to the local sites to pick it up.
+              </p>
+            ),
+          },
+          {
+            q: "I am in a remote area, can you ship it?",
+            a: (
+              <p>
+                Generally, we can ship to most of the countries and regions
+                worldwide, but individual places may be charged remote fees. If
+                the zip code in the area can not be found on the express
+                delivery system, we will not be able to ship it, you need to
+                provide a new address.
+              </p>
+            ),
+          },
+          {
+            q: "Can I use Po.box as the shipping address?",
+            a: (
+              <p>
+                We don't accept PO.box (including FPO or APO) as the shipping
+                address, because the Express(DHL/FedEx/UPS/USPS/GES/TNT) can't
+                deliver and we only accept the physical address as shipping
+                address.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "5. About Account",
+        items: [
+          {
+            q: "How to register a member?",
+            a: (
+              <div className="space-y-1">
+                <p>(1) Open the Web page, click on the "sign up".</p>
+                <p>
+                  (2) You need fill out your mailbox address、password and
+                  telephone number.
+                </p>
+                <p>
+                  (3) Click “Create An Account” the specified page appears, you
+                  need to improve your information.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "How to reset my password?",
+            a: (
+              <p>
+                Go to sign in page and click forget password. We will quickly
+                email you a link to reset your password, if you don't get the
+                email, please check your spam email at first. If still no email,
+                please feel free to contact customer service, we can help reset
+                your password.
+              </p>
+            ),
+          },
+          {
+            q: "How can I check my reward points?",
+            a: (
+              <p>
+                Please be aware of that you need to be registered to earn and
+                spend Reward Points. Sign in your account and you can check your
+                reward points at “Points” Section. For more details, please
+                check our reward points page.
+              </p>
+            ),
+          },
+        ],
+      },
+    ],
+  },
+  {
+    groupTitle: "Question After Purchase",
+    sections: [
+      {
+        title: "1. About Track Order",
+        items: [
+          {
+            q: "How to track my order?",
+            a: (
+              <div className="space-y-2">
+                <p>
+                  Method 1: Log into “MY ACCOUNT” to view the status of your
+                  order Track your shipment by using the tracking number
+                  provided to you once your items have been shipped. You cannot
+                  track your order before it has been shipped. If you don’t know
+                  what your tracking number is or if you’re having difficulty
+                  tracking your package, please feel free to contact our
+                  customer service department for assistance.
+                </p>
+                <p>
+                  Method 2: You can use your order number or shipment number to
+                  track your order though our Track Order page.
+                </p>
+              </div>
+            ),
+          },
+          {
+            q: "Why haven't I received my order yet?",
+            a: (
+              <p>
+                Once the package has been shipped out, we will email you the
+                tracking number, you can check the status. If not, please feel
+                free to contact our customer service:{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                , we will help you to check it.
+              </p>
+            ),
+          },
+          {
+            q: "Why is the shipment of my order always at the pending status?",
+            a: (
+              <p>
+                If it shows abnormality or pending in short time, that maybe
+                normal transport state. If it lasts a long time( over 1 day),
+                you can call the DHL/FedEx/UPS/USPS/TNT to ask about what is
+                going on about it. Or you can send us the Email to{" "}
+                <a
+                  href="mailto:server@asteriahair.com"
+                  className="text-blue-600 underline"
+                >
+                  server@asteriahair.com
+                </a>
+                , we will help you out.
+              </p>
+            ),
+          },
+          {
+            q: "If I'm not at home, how can I sign for my package? Can I have a delivery service without signature",
+            a: (
+              <p>
+                If you are not at home, you can ask your family and neighbors to
+                sign for it for you, or you can contact the local express
+                customer service and reschedule with them to deliver your
+                package, or you can choose to pick up the package yourself. In
+                order for you to successfully receive the package, we recommend
+                that you sign for it yourself, after you applied the service
+                without signature, if the package is lost or stolen, we do not
+                take any responsibility.
+              </p>
+            ),
+          },
+          {
+            q: "Why the shipment status shows that I have signed. But I did not receive the parcel yet.",
+            a: (
+              <p>
+                Once that happens, it means someone has signed the package for
+                you. It may be your neighbor or family and friends, It is also
+                possible that the courier drop your package around your house.
+                Please confirm it first. If not, please contact the express
+                company first to tell them your situation and let them help you
+                find the package, and please ask them as many details as
+                possible, whch is important to find the package later.
+              </p>
+            ),
+          },
+        ],
+      },
+      {
+        title: "2. About Return / Refunds",
+        items: [
+          {
+            q: "What is your return & exchange policy?",
+            a: (
+              <p>
+                We offer 30 days no reason return if the hair is in its original
+                state. For more details please check our return policy /
+                exchange policy page.
+              </p>
+            ),
+          },
+          {
+            q: "When will I get a refund after returning the package?",
+            a: (
+              <p>
+                A full refund will be provided to the original method of payment
+                after we have received, inspected and confirmed. All customers
+                must contact us after sending the package back and provide us
+                with the tracking information in order for us to process the
+                refund. Some financial institutions can take up to 3 business
+                days to process the refund. Please allow a few business days for
+                your financial institution to reflect the refund on your
+                statement prior to contacting us.
+              </p>
+            ),
+          },
+          {
+            q: "If I refuse to accept the package, can I get a full refund?",
+            a: (
+              <p>
+                We do not accept the refusal without any reasons. If you refuse
+                to sign the package and lead to the return, you will need to pay
+                for the return shipping and the shipping fee we paid the first
+                time. Any expenses incurred during the return of the parcel will
+                also be deducted from your payment. So we suggest that customers
+                contact us within 24 hours via email before shipping if they
+                have any problems (such as place the wrong order/ want to cancel
+                /want to exchange), so that we can help you to solve it timely.
+              </p>
+            ),
+          },
+        ],
+      },
+    ],
+  },
+];
+export default function Faqs() {
+  const [openSections, setOpenSections] = useState<string[]>([]);
+  const [openItems, setOpenItems] = useState<string[]>([]);
+
+  const toggleSection = (g: number, s: number) => {
+    const k = `${g}-${s}`;
+    setOpenSections((p) =>
+      p.includes(k) ? p.filter((x) => x !== k) : [...p, k],
+    );
+  };
+
+  const toggleItem = (g: number, s: number, i: number) => {
+    const k = `${g}-${s}-${i}`;
+    setOpenItems((p) => (p.includes(k) ? p.filter((x) => x !== k) : [...p, k]));
+  };
+
+  return (
+    <>
+      <HeadBack />
+      <div className="max-w-4xl mx-auto px-4 py-12">
+        {faqData.map((group, groupIdx) => (
+          <div key={groupIdx} className="mb-14">
+            <h2 className="text-2xl font-bold mb-6 text-gray-800">
+              {group.groupTitle}
+            </h2>
+
+            <div className="space-y-4">
+              {group.sections.map((sec, secIdx) => {
+                const secKey = `${groupIdx}-${secIdx}`;
+                const secOpen = openSections.includes(secKey);
+
+                return (
+                  <div
+                    key={secKey}
+                    className="border border-gray-200 rounded-lg overflow-hidden"
+                  >
+                    <button
+                      onClick={() => toggleSection(groupIdx, secIdx)}
+                      className="w-full flex justify-between items-center px-5 py-3 bg-gray-50 text-left font-semibold text-gray-800"
+                    >
+                      {sec.title}
+                      <span className="text-xl">{secOpen ? "−" : "+"}</span>
+                    </button>
+
+                    {secOpen && (
+                      <div className="divide-y divide-gray-100">
+                        {sec.items.map((item, itemIdx) => {
+                          const itemKey = `${groupIdx}-${secIdx}-${itemIdx}`;
+                          const itemOpen = openItems.includes(itemKey);
+
+                          return (
+                            <div key={itemKey} className="px-5 py-4">
+                              <button
+                                onClick={() =>
+                                  toggleItem(groupIdx, secIdx, itemIdx)
+                                }
+                                className="w-full flex justify-between items-center text-left font-medium text-gray-700"
+                              >
+                                <span>{item.q}</span>
+                                <span className="text-xl ml-2">
+                                  {itemOpen ? "−" : "+"}
+                                </span>
+                              </button>
+
+                              {itemOpen && (
+                                <div className="mt-3 text-gray-600 text-sm md:text-base">
+                                  {item.a}
+                                </div>
+                              )}
+                            </div>
+                          );
+                        })}
+                      </div>
+                    )}
+                  </div>
+                );
+              })}
+            </div>
+          </div>
+        ))}
+      </div>
+    </>
+  );
+}

+ 10 - 0
src/app/(public)/help-faqs/page.tsx

@@ -0,0 +1,10 @@
+
+import Faqs from "./_components/Faqs"
+export default function FaqPage() {
+  
+  return (
+    <>
+        <Faqs />
+    </>
+  );
+}

+ 76 - 0
src/app/(public)/track_order/_components/TrackFaqs.tsx

@@ -0,0 +1,76 @@
+"use client";
+import { useState } from "react";
+// FAQ 数据
+const faqList = [
+  {
+    question: "Tracking Number & Where Can I Find It?",
+    answer: `Your tracking number will be sent to you via email along with detailed order information. You can use the order number or tracking number to track your package here. If you have any further questions, feel free to contact us at: service@asteriahair.com.`,
+  },
+  {
+    question: "Why Can't I Track My Order?",
+    answer: `The tracking information may not be updated immediately after shipping. Please recheck after 24-48 hours. Or you can contact us via live chat or mail to:service@asteriahair.com`,
+  },
+  {
+    question: "Why Is My Order Still Processing?",
+    answer: `Most of orders processed within 72 hours, but if the product is customized wig, hot-selling wig or length, the processing time may be delayed by 2-3 days. Please don't worry, we will try our best to ship your package out ASAP.`,
+  },
+  {
+    question: "Delivery Failed, What Should I Do?",
+    answer: `If the delivery failed, we will notify you to pick it up by SMS/Email. Please contact the logistics company for re-delivery or pick it up by yourself. Otherwise, the loss caused by not picking up the package in time shall be borne by yourself.`,
+  },
+];
+export default function TrackFaqs() {
+  // 当前展开的FAQ索引,null=全部收起
+  const [activeIndex, setActiveIndex] = useState<number | null>(null);
+
+  // 切换折叠面板(互斥,只展开一个)
+  const toggleFaq = (idx: number) => {
+    setActiveIndex(activeIndex === idx ? null : idx);
+  };
+
+  return (
+    <div>
+      {faqList.map((item, idx) => (
+        <div
+          key={idx}
+          className={`border-t border-gray-200 ${idx === 0 ? "border-t-0" : ""}`}
+        >
+          {/* 问题标题栏 */}
+          <button
+            type="button"
+            onClick={() => toggleFaq(idx)}
+            className="w-full flex justify-between items-center px-6 py-5 text-left text-lg"
+          >
+            <span>Q:{item.question}</span>
+            {/* 上下箭头图标 */}
+            <svg
+              width="20"
+              height="20"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              strokeWidth="1.8"
+              strokeLinecap="round"
+              strokeLinejoin="round"
+              className={`transition-transform duration-200 ${
+                activeIndex === idx ? "rotate-180" : ""
+              }`}
+            >
+              <path d="M18 15l-6-6-6 6" />
+            </svg>
+          </button>
+          {/* 回答内容,展开/收起 */}
+          <div
+            className={`px-6 overflow-hidden transition-all duration-300 ${
+              activeIndex === idx ? "max-h-[800px] pb-6" : "max-h-0"
+            }`}
+          >
+            <p className="text-base leading-relaxed text-gray-800">
+              A:{item.answer}
+            </p>
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+}

+ 32 - 0
src/app/(public)/track_order/_components/TrackOrder.tsx

@@ -0,0 +1,32 @@
+"use client";
+import { useState } from "react";
+const [orderNo, setOrderNo] = useState("");
+// 查询提交
+const handleTrackSubmit = (e: React.FormEvent) => {
+  e.preventDefault();
+  console.log("查询单号:", orderNo);
+  // 这里写你的查询接口逻辑
+};
+
+export default function TrackOrder() {
+  return (
+    <>
+      {/* 查询表单 */}
+      <form onSubmit={handleTrackSubmit} className="space-y-6">
+        <input
+          type="text"
+          value={orderNo}
+          onChange={(e) => setOrderNo(e.target.value)}
+          placeholder="* Please Input Your Number Here"
+          className="w-full px-5 py-4 text-base bg-white border-0 rounded-[8px] outline-none focus:ring-2 focus:ring-emerald-700"
+        />
+        <button
+          type="submit"
+          className="w-full py-5 bg-emerald-800 text-white text-xl rounded-full hover:bg-emerald-900 transition-colors"
+        >
+          Track Order
+        </button>
+      </form>
+    </>
+  );
+}

+ 47 - 0
src/app/(public)/track_order/page.tsx

@@ -0,0 +1,47 @@
+import HeadBack from "@/components/head/HeadBack";
+import TrackOrder from "./_components/TrackOrder";
+import TrackFaqs from "./_components/TrackFaqs";
+
+export default function TrackOrderPage() {
+  return (
+    <>
+      <HeadBack />
+      <div className="min-h-screen bg-[#f0ede4] text-black">
+        <div className="max-w-3xl mx-auto px-4 py-8">
+          {/* 顶部 Track Order 区域 */}
+          <section className="mb-10">
+            <h1 className="text-[clamp(1.8rem,4vw,2.5rem)] font-normal mb-6">
+              Track Order
+            </h1>
+            <div className="space-y-3 mb-8 text-base leading-relaxed">
+              <p>Want To Check The Status Of Your Order?</p>
+              <p>
+                Please Input Your Order Number And Click The Track Order Button
+                To Find The Tracking Information.
+              </p>
+            </div>
+
+            <TrackOrder />
+          </section>
+
+          {/* FAQ 区域 */}
+          <section className="bg-white rounded-[8px] overflow-hidden">
+            <h2 className="text-[clamp(1.5rem,3vw,2rem)] font-normal p-6 pb-4">
+              People Also Ask
+            </h2>
+            {/* 问答 */}
+            <TrackFaqs />
+          </section>
+
+          {/* You May Like 标题区域 */}
+          <div className="mt-10">
+            <h2 className="text-[clamp(1.4rem,3vw,1.8rem)] font-normal">
+              You May Like
+            </h2>
+            {/* 下方可自行添加商品列表 */}
+          </div>
+        </div>
+      </div>
+    </>
+  );
+}

+ 50 - 0
src/app/api/customer/reward-points/browse-product/route.ts

@@ -0,0 +1,50 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+export async function POST(req: NextRequest) {
+  try {
+    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    const params = await req.json();
+    const response = await restApiFetch<{
+      data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+      variables:{
+        categorys:string,
+      } ;
+    }>({
+      api: "/customer/reward-points/browse-product",
+      method: "POST",
+      cache: "no-store",
+      variables: params,
+      guestToken: authorizationToken,
+    });
+    // 打印后端原始返回结构
+    console.log(
+      "接口原始response.body =",
+      JSON.stringify(response.body, null, 2),
+    );
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    console.log("/customer/token/address---", error); // 调试用
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}

+ 40 - 0
src/app/api/customer/reward-points/follow-status/route.ts

@@ -0,0 +1,40 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+
+export async function GET(req: NextRequest) {
+  try {
+    const guestToken = getAuthToken(req);
+    const response = await restApiFetch<any>({
+      api: "/customer/reward-points/follow-status",
+      method: "GET",
+      cache: "no-store",
+      guestToken,
+    });
+
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+
+  } catch (error) {
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 }
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 }
+    );
+  }
+}

+ 62 - 0
src/app/api/customer/reward-points/follow/route.ts

@@ -0,0 +1,62 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+export async function POST(req: NextRequest) {
+  try {
+    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    const params = await req.json();
+    const response = await restApiFetch<{
+      data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+      variables:{
+        platform:string,
+      } ;
+    //   {
+    //     first_name: string;
+    //     last_name: string;
+    //     email: string;
+    //     phone: string;
+    //     address: string;
+    //     country: string;
+    //     state: string;
+    //     city: string;
+    //     postcode: string;
+    //     default_address: number;
+    //   }
+    }>({
+      api: "/customer/reward-points/follow",
+      method: "POST",
+      cache: "no-store",
+      variables: params,
+      guestToken: authorizationToken,
+    });
+    // 打印后端原始返回结构
+    console.log(
+      "接口原始response.body =",
+      JSON.stringify(response.body, null, 2),
+    );
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+  } catch (error) {
+    console.log("/customer/token/address---", error); // 调试用
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 },
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 },
+    );
+  }
+}

+ 61 - 0
src/app/api/customer/reward-points/history/route.ts

@@ -0,0 +1,61 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+import { isBagistoError } from "@/utils/type-guards";
+import { getAuthToken } from "@/utils/helper";
+
+export async function GET(req: NextRequest) {
+  try {
+    const guestToken = getAuthToken(req);
+
+    // 👇 取出所有前端传过来的查询参数
+    const searchParams = req.nextUrl.searchParams;
+    const page = searchParams.get("page");
+    const per_page = searchParams.get("per_page");
+    const amountType = searchParams.get("amountType");
+
+    // 👇 构建查询参数数组(自动过滤空值)
+    const queryParts: string[] = [];
+    if (page) queryParts.push(`page=${page}`);
+    if (per_page) queryParts.push(`per_page=${per_page}`);
+    if (amountType) queryParts.push(`amountType=${amountType}`);
+
+    // 👇 最终拼接 URL
+    let apiUrl = "/customer/reward-points/history";
+    if (queryParts.length > 0) {
+      apiUrl += "?" + queryParts.join("&");
+    }
+
+    console.log("最终请求API地址:", apiUrl);
+
+    const response = await restApiFetch<any>({
+      api: apiUrl,
+      method: "GET",
+      cache: "no-store",
+      guestToken,
+    });
+
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+
+  } catch (error) {
+    if (isBagistoError(error)) {
+      return NextResponse.json(
+        {
+          data: null,
+          error: error.cause ?? error,
+        },
+        { status: 200 }
+      );
+    }
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 }
+    );
+  }
+}

+ 126 - 68
src/components/customer/SettingModal.tsx

@@ -1,5 +1,7 @@
 "use client";
 import Link from "next/link";
+import { useState } from "react";
+import HelpModal from "../../app/(public)/customer/account/_components/HelpModal";
 export default function SettingModal({
   visible,
   onClose,
@@ -9,84 +11,140 @@ export default function SettingModal({
 }) {
   // 如果不显示,直接 return null
   if (!visible) return null;
-
+  const [showSetting, setShowSetting] = useState(false);
   return (
-    // 全屏遮罩
-    <div className="fixed inset-0 z-50 bg-black/50">
-      {/* 弹框主体 - 从底部滑上来 */}
-      <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
-        <div className="flex flex-col">
-          {/* 顶部导航 */}
-          <header className="bg-black text-white py-4 px-4 flex items-center">
-            <button onClick={onClose} className="mr-4">
-              <svg
-                xmlns="http://www.w3.org/2000/svg"
-                className="h-6 w-6"
-                fill="none"
-                viewBox="0 0 24 24"
-                stroke="currentColor"
-              >
-                <path
-                  strokeLinecap="round"
-                  strokeLinejoin="round"
-                  strokeWidth={2}
-                  d="M15 19l-7-7 7-7"
-                />
-              </svg>
-            </button>
-            <h1 className="text-lg font-medium">Setting</h1>
-          </header>
+    <>
+      // 全屏遮罩
+      <div className="fixed inset-0 z-50 bg-black/50">
+        {/* 弹框主体 - 从底部滑上来 */}
+        <div className="absolute bottom-0 rounded-none left-0 right-0 w-full h-full bg-white  overflow-auto">
+          <div className="flex flex-col">
+            {/* 顶部导航 */}
+            <header className="bg-black text-white py-4 px-4 flex items-center">
+              <button onClick={onClose} className="mr-4">
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-6 w-6"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M15 19l-7-7 7-7"
+                  />
+                </svg>
+              </button>
+              <h1 className="text-lg font-medium">Setting</h1>
+            </header>
 
-          {/* 头像区域 */}
-          <div className="bg-[#FFF5F0] py-8 flex flex-col items-center">
-            <div className="w-20 h-20 bg-white rounded-full flex items-center justify-center border-2 border-black">
-              <span className="text-black font-bold text-lg">LOGO</span>
+            {/* 头像区域 */}
+            <div className="bg-[#FFF5F0] py-8 flex flex-col items-center">
+              <div className="w-20 h-20 bg-white rounded-full flex items-center justify-center border-2 border-black">
+                <span className="text-black font-bold text-lg">LOGO</span>
+              </div>
             </div>
-          </div>
 
-          {/* 菜单列表 */}
-          <div className="px-4 py-2">
-            {/* Information */}
-            <Link href={"/customer/account/edit"} className="py-4 border-b border-gray-100 flex justify-between items-center" > 
-            {/* <div > */}
-              <span className="text-black font-medium">Information</span>
-              <svg
-                xmlns="http://www.w3.org/2000/svg"
-                className="h-5 w-5 text-gray-400"
-                fill="none"
-                viewBox="0 0 24 24"
-                stroke="currentColor"
+            {/* 菜单列表 */}
+            <div className="px-4 py-2">
+              {/* Information */}
+              <Link
+                href={"/customer/account/edit"}
+                className="py-4 border-b border-gray-100 flex justify-between items-center"
+              >
+                {/* <div > */}
+                <span className="text-black font-medium">Information</span>
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-5 w-5 text-gray-400"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M9 5l7 7-7 7"
+                  />
+                </svg>
+                {/* </div> */}
+              </Link>
+              {/* Address */}
+              <Link
+                href={"/customer/address"}
+                className="py-4 border-b border-gray-100 flex justify-between items-center"
               >
-                <path
-                  strokeLinecap="round"
-                  strokeLinejoin="round"
-                  strokeWidth={2}
-                  d="M9 5l7 7-7 7"
-                />
-              </svg>
-            {/* </div> */}
-            </Link>
+                {/* <div > */}
+                <span className="text-black font-medium">Address</span>
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-5 w-5 text-gray-400"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M9 5l7 7-7 7"
+                  />
+                </svg>
+                {/* </div> */}
+              </Link>
+              {/* help & Support */}
+              <div
+                onClick={() => {
+                  setShowSetting(true);
+                }}
+                className="py-4 border-b border-gray-100 flex justify-between items-center"
+              >
+                {/* <div > */}
+                <span className="text-black font-medium">help & Support</span>
+                <svg
+                  xmlns="http://www.w3.org/2000/svg"
+                  className="h-5 w-5 text-gray-400"
+                  fill="none"
+                  viewBox="0 0 24 24"
+                  stroke="currentColor"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M9 5l7 7-7 7"
+                  />
+                </svg>
+                {/* </div> */}
+              </div>
+              {/* Subscribe 开关 */}
+              <div className="py-4 border-b border-gray-100 flex justify-between items-center">
+                <span className="text-black font-medium">Subscribe</span>
+                <div className="relative w-12 h-6 rounded-full bg-black">
+                  <div className="absolute right-0.5 top-0.5 w-5 h-5 bg-white rounded-full"></div>
+                </div>
+              </div>
 
-            {/* Subscribe 开关 */}
-            <div className="py-4 border-b border-gray-100 flex justify-between items-center">
-              <span className="text-black font-medium">Subscribe</span>
-              <div className="relative w-12 h-6 rounded-full bg-black">
-                <div className="absolute right-0.5 top-0.5 w-5 h-5 bg-white rounded-full"></div>
+              {/* Log Out */}
+              <div className="py-4 border-b border-gray-100">
+                <button className="text-black font-medium w-full text-left">
+                  Log Out
+                </button>
               </div>
             </div>
 
-            {/* Log Out */}
-            <div className="py-4 border-b border-gray-100">
-              <button className="text-black font-medium w-full text-left">
-                Log Out
-              </button>
-            </div>
+            {/* 版本号 */}
+            <div className="py-6 text-center text-gray-400 text-sm">v1.1.0</div>
           </div>
-
-          {/* 版本号 */}
-          <div className="py-6 text-center text-gray-400 text-sm">v1.1.0</div>
         </div>
       </div>
-    </div>
+      <HelpModal
+        helpvisible={showSetting}
+        helponClose={() => setShowSetting(false)}
+      />
+    </>
   );
-}
+}

+ 40 - 0
src/components/head/HeadBack.tsx

@@ -0,0 +1,40 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+
+export default function HeadBack() {
+  const router = useRouter();
+
+  // 页面回退
+  const handleBack = () => {
+    router.back();
+  };
+
+  return (
+    <div className="relative flex items-center bg-[#eee] justify-between px-4 py-4  border-t-0">
+      <svg
+        className=" leading-none focus:outline-none w-6 h-6"
+        onClick={handleBack}
+        viewBox="0 0 1024 1024"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        p-id="111033"
+        width="200"
+        height="200"
+      >
+        <path
+          d="M391.466667 512l339.882666-339.882667a46.592 46.592 0 1 0-65.877333-65.877333L292.693333 479.061333a46.592 46.592 0 0 0 0 65.877334l372.821334 372.821333a46.592 46.592 0 1 0 65.877333-65.877333L391.466667 512z"
+          p-id="111034"
+        ></path>
+      </svg>
+
+      {/* 标题文字 */}
+      <h1 className="absolute left-1/2 -translate-x-1/2 text-black text-xl font-bold tracking-wider">
+        ASTERIA
+      </h1>
+
+      {/* 占位,保证标题居中 */}
+      <div className="w-12"></div>
+    </div>
+  );
+}