소스 검색

Add order history and compatibility modal features

Introduces order history and order detail pages, a device compatibility modal, and related API methods. Updates branding from InfiGate to Getgo, refactors account/logout logic, enhances popup and header components for user actions, and improves product price formatting. Removes unused HandbookView and updates company info in the footer.
trunghieubui 3 주 전
부모
커밋
b25875e9fa
26개의 변경된 파일1643개의 추가작업 그리고 249개의 파일을 삭제
  1. 1 1
      EsimLao/esim-vite/index.html
  2. 9 0
      EsimLao/esim-vite/src/App.tsx
  3. 8 8
      EsimLao/esim-vite/src/apis/axios.ts
  4. 10 0
      EsimLao/esim-vite/src/apis/baseApi.ts
  5. 5 0
      EsimLao/esim-vite/src/apis/contentApi.ts
  6. 12 0
      EsimLao/esim-vite/src/apis/productApi.ts
  7. 2 0
      EsimLao/esim-vite/src/app/store.ts
  8. 308 0
      EsimLao/esim-vite/src/components/CompatibilityModal.tsx
  9. 127 22
      EsimLao/esim-vite/src/components/Footer.tsx
  10. 98 19
      EsimLao/esim-vite/src/components/Header.tsx
  11. 29 12
      EsimLao/esim-vite/src/components/Popup.tsx
  12. 1 2
      EsimLao/esim-vite/src/components/ProductCard.tsx
  13. 3 2
      EsimLao/esim-vite/src/features/account/accuntSlice.ts
  14. 21 2
      EsimLao/esim-vite/src/features/popup/popupSlice.ts
  15. 27 9
      EsimLao/esim-vite/src/pages/checkout/CheckoutView.tsx
  16. 51 12
      EsimLao/esim-vite/src/pages/contact/ContactView.tsx
  17. 0 136
      EsimLao/esim-vite/src/pages/handbook/HandbookView.tsx
  18. 28 5
      EsimLao/esim-vite/src/pages/home/HomeView.tsx
  19. 7 4
      EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx
  20. 14 6
      EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx
  21. 3 5
      EsimLao/esim-vite/src/pages/login/LoginView.tsx
  22. 516 0
      EsimLao/esim-vite/src/pages/order-detail/OrderDetailView.tsx
  23. 273 0
      EsimLao/esim-vite/src/pages/order-history/OrderHistoryView.tsx
  24. 17 4
      EsimLao/esim-vite/src/pages/product-detail/ProductDetailView.tsx
  25. 24 0
      EsimLao/esim-vite/src/services/content/types.ts
  26. 49 0
      EsimLao/esim-vite/src/services/product/type.ts

+ 1 - 1
EsimLao/esim-vite/index.html

@@ -4,7 +4,7 @@
 <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <title>InfiGate | Stay Connected Everywhere</title>
+  <title>Getgo | Stay Connected Everywhere</title>
   <link rel="preconnect" href="https://fonts.googleapis.com" />
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
   <link

+ 9 - 0
EsimLao/esim-vite/src/App.tsx

@@ -14,6 +14,9 @@ import ContactView from "./pages/contact/ContactView";
 import TopLoader from "./components/TopLoader";
 import CheckoutView from "./pages/checkout/CheckoutView";
 import Popup from "./components/Popup";
+import OrderHistoryView from "./pages/order-history/OrderHistoryView";
+import OrderDetailView from "./pages/order-detail/OrderDetailView";
+import CompatibilityModal from "./components/CompatibilityModal";
 
 const App: React.FC = () => {
   const location = useLocation();
@@ -34,6 +37,7 @@ const App: React.FC = () => {
       {!isPlainView && <Header />}
 
       <Popup />
+      <CompatibilityModal />
 
       <div className={`flex flex-1 ${isAiView ? "overflow-hidden" : ""}`}>
         {isAiView && <Sidebar />}
@@ -73,6 +77,11 @@ const App: React.FC = () => {
               <Route path="/news/:id" element={<ArticleDetailView />} />
               <Route path="/contact" element={<ContactView />} />
               <Route path="/login" element={<LoginView />} />
+              <Route path="/order-history" element={<OrderHistoryView />} />
+              <Route
+                path="/order-history-detail/:id"
+                element={<OrderDetailView />}
+              />
 
               {/* Fallback */}
               <Route path="*" element={<Navigate to="/" replace />} />

+ 8 - 8
EsimLao/esim-vite/src/apis/axios.ts

@@ -1,20 +1,19 @@
-import axios, { AxiosInstance } from 'axios';
+import axios, { AxiosInstance } from "axios";
 
 export const createAxiosInstance = (baseURL: string): AxiosInstance => {
   const instance = axios.create({
     baseURL,
     timeout: 10000,
     headers: {
-      'Content-Type': 'application/json',
+      "Content-Type": "application/json",
     },
   });
 
   instance.interceptors.request.use((config) => {
-    const token = localStorage.getItem('token');
-    const lang = localStorage.getItem('selectedLanguage') || 'en';
-
+    const token = localStorage.getItem("token");
+    const lang = localStorage.getItem("selectedLanguage") || "en";
     if (lang) {
-      config.headers['Accept-Language'] = lang;
+      config.headers["Accept-Language"] = lang;
     }
     if (token) {
       config.headers.Authorization = `Bearer ${token}`;
@@ -26,8 +25,9 @@ export const createAxiosInstance = (baseURL: string): AxiosInstance => {
     (response) => response.data,
     (error) => {
       if (error.response?.status === 401) {
-        localStorage.removeItem('token');
-        window.location.href = '/login';
+        localStorage.removeItem("token");
+        localStorage.removeItem("refreshToken");
+        window.location.href = "/login";
       }
       return Promise.reject(error);
     }

+ 10 - 0
EsimLao/esim-vite/src/apis/baseApi.ts

@@ -48,4 +48,14 @@ export class BaseApi {
       timeout: timeout ?? this.defaultTimeout,
     });
   }
+
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  async authGet<T>(
+    endpoint: string = "",
+    timeout?: number
+  ): Promise<Response<T>> {
+    return authApiInstance.get(`${this.baseUrl}${endpoint}`, {
+      timeout: timeout ?? this.defaultTimeout,
+    });
+  }
 }

+ 5 - 0
EsimLao/esim-vite/src/apis/contentApi.ts

@@ -4,6 +4,7 @@ import {
 } from "../services/article/types";
 import {
   LoadBannerResponse,
+  LoadDeviceMetaResponse,
   LoadFaqCategoryResponse,
   LoadFaqResponse,
   LoadReviewResponse,
@@ -52,6 +53,10 @@ class ContentApi extends BaseApi {
       isFeatured,
     });
   }
+
+  async LoadDeviceMetaData() {
+    return this.authGet<LoadDeviceMetaResponse>("/device-metadata");
+  }
 }
 
 export const contentApi = new ContentApi();

+ 12 - 0
EsimLao/esim-vite/src/apis/productApi.ts

@@ -48,6 +48,18 @@ class ProductApi extends BaseApi {
   async loadPaymentChannel() {
     return this.productPost("/list_payment_channel", {});
   }
+
+  async getOrderHistory({ fromDate, toDate, status }) {
+    return this.productPost("/list_order", { fromDate, toDate, status });
+  }
+
+  async getOrderDetail({ orderId }) {
+    return this.productPost("/get_order_detail", { orderId });
+  }
+
+  async checkDataUsage({ iccid }) {
+    return this.productPost("/check_data_usage", { iccid });
+  }
 }
 
 export const productApi = new ProductApi();

+ 2 - 0
EsimLao/esim-vite/src/app/store.ts

@@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit";
 import loadingReducer from "../features/loading/loadingSlice";
 import areasReducer from "../features/areas/areasSlice";
 import popupReducer from "../features/popup/popupSlice";
+import accountReducer from "../features/account/accuntSlice";
 
 export const store = configureStore({
   reducer: {
     loading: loadingReducer,
     areas: areasReducer,
     popup: popupReducer,
+    account: accountReducer,
   },
 });
 

+ 308 - 0
EsimLao/esim-vite/src/components/CompatibilityModal.tsx

@@ -0,0 +1,308 @@
+import React, { useState, useEffect } from "react";
+import { useAppDispatch, useAppSelector } from "../hooks/useRedux";
+import { closeCompatibilityModal } from "../features/popup/popupSlice";
+import { useMutation } from "@tanstack/react-query";
+import { startLoading, stopLoading } from "../features/loading/loadingSlice";
+import { contentApi } from "../apis/contentApi";
+import { DeviceMeta } from "../services/content/types";
+
+const CompatibilityModal = () => {
+  const popup = useAppSelector((state) => state.popup);
+  const dispatch = useAppDispatch();
+  const [brands, setBrands] = useState<DeviceMeta[] | []>([]);
+  const [deviceList, setDeviceList] = useState<Record<string, string[]>>([]);
+  const [selectedBrand, setSelectedBrand] = useState("");
+
+  useEffect(() => {
+    getDeviceMetaMutation.mutate();
+  }, []);
+
+  const getDeviceMetaMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await contentApi.LoadDeviceMetaData();
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get device metadata response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Get device metadata successful");
+        setBrands(data.data.brands || []);
+        let deviceMap: Record<string, string[]> = {};
+        data.data.brands.forEach((element) => {
+          deviceMap[element.brand] = element.devices.map(
+            (device) => device.modelName
+          );
+        });
+
+        setDeviceList(deviceMap);
+
+        setSelectedBrand(
+          data.data.brands.length > 0 ? data.data.brands[0].brand : ""
+        );
+      } else {
+        console.error("Get device metadata failed, no token received");
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get device metadata error:", error.response.data);
+    },
+  });
+
+  // const brands = [
+  //   {
+  //     id: "apple",
+  //     label: "Apple",
+  //     icon: (
+  //       <svg className="w-8 h-8" viewBox="0 0 384 512" fill="currentColor">
+  //         <path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 52.3-11.4 69.5-34.3z" />
+  //       </svg>
+  //     ),
+  //   },
+  //   {
+  //     id: "samsung",
+  //     label: "Samsung",
+  //     icon: (
+  //       <svg className="w-12 h-8" viewBox="0 0 24 24" fill="currentColor">
+  //         <path d="M17.8 4.8C14.3 3.6 9.7 3.6 6.2 4.8 1.9 6.3 0 10.2 0 14.9s1.9 8.6 6.2 10.1c3.5 1.2 8.1 1.2 11.6 0 4.3-1.5 6.2-5.4 6.2-10.1s-1.9-8.6-6.2-10.1zM8 17.9c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4zm0-5.7c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4zm8 5.7c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4zm0-5.7c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4z" />
+  //       </svg>
+  //     ),
+  //   },
+  //   {
+  //     id: "google",
+  //     label: "Google Pixel",
+  //     icon: (
+  //       <svg className="w-8 h-8" viewBox="0 0 24 24" fill="currentColor">
+  //         <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" />
+  //       </svg>
+  //     ),
+  //   },
+  //   {
+  //     id: "others",
+  //     label: "Others",
+  //     icon: (
+  //       <svg className="w-6 h-8" viewBox="0 0 24 24" fill="currentColor">
+  //         <path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z" />
+  //       </svg>
+  //     ),
+  //   },
+  // ];
+
+  if (popup.isOpenCompatibilityModal === false) {
+    return null;
+  }
+  return (
+    <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 animate-in fade-in duration-200">
+      <div
+        className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"
+        onClick={() => {
+          dispatch(closeCompatibilityModal());
+        }}
+      ></div>
+
+      {/* Container is now a flex column with hidden overflow to manage scroll internally */}
+      <div className="relative bg-white rounded-[32px] w-full max-w-4xl max-h-[90vh] shadow-2xl flex flex-col overflow-hidden">
+        {/* Fixed Close Button (Absolute) */}
+        <button
+          onClick={() => {
+            dispatch(closeCompatibilityModal());
+          }}
+          className="absolute top-4 right-4 p-2 text-slate-400 hover:text-[#EE0434] transition-colors bg-white/80 backdrop-blur-sm rounded-full shadow-sm z-20"
+        >
+          <svg
+            className="w-6 h-6"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              strokeLinecap="round"
+              strokeLinejoin="round"
+              strokeWidth={2}
+              d="M6 18L18 6M6 6l12 12"
+            />
+          </svg>
+        </button>
+
+        {/* Fixed Header */}
+        <div className="shrink-0 p-8 md:p-10 pb-2 text-center bg-white z-10 relative">
+          <h2 className="text-3xl md:text-5xl font-black text-slate-900 mb-2 tracking-tight">
+            Does my phone <br />
+            <span className="text-[#0091ff]">support eSIM ?</span>
+          </h2>
+        </div>
+
+        {/* Scrollable Content Body */}
+        <div className="flex-1 overflow-y-auto p-8 md:p-12 pt-4 custom-scrollbar">
+          <div className="text-center mb-8">
+            <h3 className="text-xl md:text-2xl font-black text-[#0091ff] mb-6">
+              Method 1: Quickly check using the list below
+            </h3>
+
+            <div className="text-left bg-slate-50 p-6 rounded-2xl border border-slate-100 text-sm md:text-base text-slate-600 space-y-4 mb-8">
+              <p className="font-bold">
+                Important Note: Please make sure your device supports eSIM
+                before purchasing. The information below is for reference
+                purposes only. We are not responsible for any changes in device
+                information. We will not refund any order if the device does not
+                support eSIM, including the following cases:
+              </p>
+              <ul className="list-disc pl-5 space-y-1 marker:text-[#0091ff]">
+                <li>
+                  Some dual physical SIM phone models do not support eSIM.
+                </li>
+                <li>
+                  Carrier-locked devices (contract-based subscription devices)
+                  may not support eSIM.
+                </li>
+                <li>
+                  Some models purchased in certain countries may not support
+                  eSIM (see table below).
+                </li>
+              </ul>
+              <p>
+                To make sure your device supports eSIM, please contact the
+                store/provider where you purchased the device for the most
+                accurate confirmation.
+              </p>
+            </div>
+          </div>
+
+          {/* Brand Tabs */}
+          <div className="flex justify-center space-x-8 mb-10 overflow-x-auto py-2">
+            {brands.map((brand) => (
+              <button
+                key={brand.brand}
+                onClick={() => setSelectedBrand(brand.brand)}
+                className={`flex flex-col items-center space-y-3 group min-w-[80px] focus:outline-none`}
+              >
+                <div
+                  className={`w-16 h-16 flex items-center justify-center transition-colors ${
+                    selectedBrand === brand.brand
+                      ? "text-slate-900 scale-110"
+                      : "text-slate-300 group-hover:text-slate-500"
+                  }`}
+                >
+                  {brand.brand == "Apple" ? (
+                    <svg
+                      className="w-8 h-8"
+                      viewBox="0 0 384 512"
+                      fill="currentColor"
+                    >
+                      <path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 52.3-11.4 69.5-34.3z" />
+                    </svg>
+                  ) : brand.brand == "Samsung" ? (
+                    <svg
+                      className="w-12 h-8"
+                      viewBox="0 0 24 24"
+                      fill="currentColor"
+                    >
+                      <path d="M17.8 4.8C14.3 3.6 9.7 3.6 6.2 4.8 1.9 6.3 0 10.2 0 14.9s1.9 8.6 6.2 10.1c3.5 1.2 8.1 1.2 11.6 0 4.3-1.5 6.2-5.4 6.2-10.1s-1.9-8.6-6.2-10.1zM8 17.9c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4zm0-5.7c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4zm8 5.7c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4zm0-5.7c-1.3 0-2.4-.3-2.4-1.4 0-1.1 1.1-1.4 2.4-1.4 1.3 0 2.4.3 2.4 1.4 0 1.1-1.1 1.4-2.4 1.4z" />
+                    </svg>
+                  ) : brand.brand == "Google" ? (
+                    <svg
+                      className="w-8 h-8"
+                      viewBox="0 0 24 24"
+                      fill="currentColor"
+                    >
+                      <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" />
+                    </svg>
+                  ) : (
+                    <svg
+                      className="w-6 h-8"
+                      viewBox="0 0 24 24"
+                      fill="currentColor"
+                    >
+                      <path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z" />
+                    </svg>
+                  )}
+                </div>
+                <span
+                  className={`font-bold text-lg transition-colors ${
+                    selectedBrand === brand.brand
+                      ? "text-[#0091ff]"
+                      : "text-slate-300 group-hover:text-slate-500"
+                  }`}
+                >
+                  {brand.brand}
+                </span>
+                {selectedBrand === brand.brand && (
+                  <div className="w-12 h-1 bg-[#0091ff] rounded-full"></div>
+                )}
+              </button>
+            ))}
+          </div>
+
+          {/* Device List (Scrolls with the body) */}
+          <div className="bg-white rounded-t-3xl border-t border-slate-100">
+            <div className="divide-y divide-slate-50">
+              {deviceList[selectedBrand].map((device, idx) => (
+                <div
+                  key={idx}
+                  className="py-4 px-4 text-center hover:bg-slate-50 transition-colors text-slate-700 font-bold text-lg"
+                >
+                  {device}
+                </div>
+              ))}
+            </div>
+          </div>
+
+          {/* Divider */}
+          <div className="my-10 border-t border-dashed border-slate-300"></div>
+
+          {/* Apple Specific Note */}
+          <div className="text-left text-slate-600 space-y-4 mb-12">
+            <p className="font-bold text-[#EE0434]">
+              *Note: The following apple devices do NOT support eSIM:
+            </p>
+            <ul className="list-disc pl-5 space-y-2 text-sm md:text-base">
+              <li>
+                Phone models with dual physical SIMs do NOT support eSIM. Please
+                check Settings &gt; Cellular to ensure the option "Add eSIM" is
+                available.
+              </li>
+              <li>
+                All iPhones purchased from China, Hong Kong, and Macau DO NOT
+                support eSIM.
+              </li>
+            </ul>
+          </div>
+
+          {/* Divider */}
+          <div className="my-10 border-t border-dashed border-slate-300"></div>
+
+          {/* Method 2 */}
+          <div className="text-center mb-12">
+            <h3 className="text-xl md:text-3xl font-black text-[#0091ff] mb-4">
+              Method 2: Check directly on your device
+            </h3>
+            <p className="text-slate-600 text-lg">
+              Check if your device supports eSIM and whether it is
+              carrier-locked by following{" "}
+              <button className="text-[#0091ff] font-bold hover:underline">
+                the steps below
+              </button>
+            </p>
+          </div>
+
+          {/* Not Support */}
+          <div className="text-center mb-8">
+            <h3 className="text-xl md:text-3xl font-black text-[#0091ff] mb-4">
+              Your device does not support eSIM?
+            </h3>
+            <p className="text-slate-600 text-lg">
+              Try finding a physical SIM that is compatible with your device{" "}
+              <button className="text-[#0091ff] font-bold hover:underline">
+                here
+              </button>
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default CompatibilityModal;

+ 127 - 22
EsimLao/esim-vite/src/components/Footer.tsx

@@ -1,5 +1,4 @@
-
-import React from 'react';
+import React from "react";
 
 const Footer: React.FC = () => {
   return (
@@ -9,50 +8,156 @@ const Footer: React.FC = () => {
         <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
           <div className="space-y-6">
             <div className="flex items-center space-x-2">
-              <svg className="w-8 h-8 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
+              <svg
+                className="w-8 h-8 text-[#EE0434]"
+                viewBox="0 0 24 24"
+                fill="currentColor"
+              >
                 <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
               </svg>
-              <span className="text-xl font-black text-[#EE0434]">InfiGate</span>
+              <span className="text-xl font-black text-[#EE0434]">Getgo</span>
             </div>
-            <p className="text-sm font-bold text-[#EE0434] leading-tight">WellZone Technology Solutions Joint Stock Company</p>
-            <p className="text-sm font-medium text-slate-600">Tax Code: 0109310641</p>
+            <p className="text-sm font-bold text-[#EE0434] leading-tight">
+              Công ty TNHH Phát triển toàn cầu VIETTECH
+            </p>
+            <p className="text-sm font-medium text-slate-600">
+              Tax Code: 0901210362
+            </p>
             <div className="space-y-3 text-sm text-slate-500">
-              <p className="flex items-start"><svg className="w-4 h-4 mr-2 mt-0.5 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg> 10th Floor, 83B Ly Thuong Kiet, Cua Nam Ward, Hanoi City</p>
-              <p className="flex items-center"><svg className="w-4 h-4 mr-2 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg> info@infigate.global</p>
-              <p className="flex items-center"><svg className="w-4 h-4 mr-2 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg> Zalo/Whatsapp: +84989145663</p>
+              <p className="flex items-start">
+                <svg
+                  className="w-4 h-4 mr-2 mt-0.5 text-[#EE0434]"
+                  fill="none"
+                  stroke="currentColor"
+                  viewBox="0 0 24 24"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
+                  />
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
+                  />
+                </svg>{" "}
+                218 Đảo Dừa 1, Khu đô thị Vinhomes Oceanpark 2, Xã Nghĩa Trụ,
+                Tỉnh Hưng Yên, Việt Nam
+              </p>
+              <p className="flex items-center">
+                <svg
+                  className="w-4 h-4 mr-2 text-[#EE0434]"
+                  fill="none"
+                  stroke="currentColor"
+                  viewBox="0 0 24 24"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
+                  />
+                </svg>{" "}
+                sale.admin@viettech.asia
+              </p>
+              <p className="flex items-center">
+                <svg
+                  className="w-4 h-4 mr-2 text-[#EE0434]"
+                  fill="none"
+                  stroke="currentColor"
+                  viewBox="0 0 24 24"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={2}
+                    d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
+                  />
+                </svg>{" "}
+                Zalo/Whatsapp: +84972.978.296
+              </p>
             </div>
           </div>
           <div>
-            <h3 className="text-base font-bold text-slate-900 mb-6">InfiGate</h3>
+            <h3 className="text-base font-bold text-slate-900 mb-6">Getgo</h3>
             <ul className="space-y-4 text-sm text-slate-500">
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">About Us</a></li>
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Terms Of Service</a></li>
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Privacy Policy</a></li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  About Us
+                </a>
+              </li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  Terms Of Service
+                </a>
+              </li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  Privacy Policy
+                </a>
+              </li>
             </ul>
           </div>
           <div>
-            <h3 className="text-base font-bold text-slate-900 mb-6">Travel SIM</h3>
+            <h3 className="text-base font-bold text-slate-900 mb-6">
+              Travel SIM
+            </h3>
             <ul className="space-y-4 text-sm text-slate-500">
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">China SIM</a></li>
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Thailand SIM</a></li>
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Japan SIM</a></li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  China SIM
+                </a>
+              </li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  Thailand SIM
+                </a>
+              </li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  Japan SIM
+                </a>
+              </li>
             </ul>
           </div>
           <div>
             <h3 className="text-base font-bold text-slate-900 mb-6">Guide</h3>
             <ul className="space-y-4 text-sm text-slate-500">
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">What is eSIM</a></li>
-              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Support</a></li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  What is eSIM
+                </a>
+              </li>
+              <li>
+                <a href="#" className="hover:text-[#EE0434] transition-colors">
+                  Support
+                </a>
+              </li>
             </ul>
           </div>
         </div>
 
         {/* Bottom */}
         <div className="pt-8 border-t border-slate-100 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
-          <p className="text-sm text-slate-400">© 2025 InfiGate All rights reserved</p>
+          <p className="text-sm text-slate-400">
+            © 2025 Getgo All rights reserved
+          </p>
           <div className="bg-[#EE0434] px-4 py-2 rounded-lg flex items-center space-x-2">
-            <div className="w-5 h-5 bg-white rounded-full flex items-center justify-center text-[#EE0434]"><svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"/></svg></div>
-            <span className="text-[10px] text-white font-bold uppercase tracking-widest">Official Store Verified</span>
+            <div className="w-5 h-5 bg-white rounded-full flex items-center justify-center text-[#EE0434]">
+              <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
+                <path
+                  fillRule="evenodd"
+                  d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
+                  clipRule="evenodd"
+                />
+              </svg>
+            </div>
+            <span className="text-[10px] text-white font-bold uppercase tracking-widest">
+              Official Store Verified
+            </span>
           </div>
         </div>
       </div>

+ 98 - 19
EsimLao/esim-vite/src/components/Header.tsx

@@ -11,6 +11,7 @@ import {
 } from "../features/loading/loadingSlice";
 import { productApi } from "../apis/productApi";
 import { Area } from "../services/product/type";
+import { accountLogout } from "../features/account/accuntSlice";
 
 const Header: React.FC = () => {
   const navigate = useNavigate();
@@ -18,6 +19,7 @@ const Header: React.FC = () => {
   const [isMenuOpen, setIsMenuOpen] = useState(false);
   const [isBuySimExpanded, setIsBuySimExpanded] = useState(false);
   const [isGuideExpanded, setIsGuideExpanded] = useState(false);
+  const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
   const [isBuySimMegaVisible, setIsBuySimMegaVisible] = useState(false);
   const [isGuideMegaVisible, setIsGuideMegaVisible] = useState(false);
   const [isLangMenuOpen, setIsLangMenuOpen] = useState(false);
@@ -29,6 +31,7 @@ const Header: React.FC = () => {
   const [products, setProducts] = useState<Area[]>([]);
   const dispatch = useAppDispatch();
   const langMenuRef = useRef<HTMLDivElement>(null);
+  const userMenuRef = useRef<HTMLDivElement>(null);
 
   const guideItems = [
     { label: "What is eSIM", path: "/support" },
@@ -128,7 +131,7 @@ const Header: React.FC = () => {
                 {/* <svg className="w-8 h-8 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
                   <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
                 </svg> */}
-                <img src={logo} alt="InfiGate Logo" className="w-35" />
+                <img src={logo} alt="Getgo Logo" className="w-35" />
                 {/* <span className="text-2xl font-bold tracking-tighter">
                   <span className="text-[#EE0434]">Infi</span>
                   <span className="text-[#333]">Gate</span>
@@ -393,24 +396,83 @@ const Header: React.FC = () => {
 
             {/* Icons */}
             <div className="flex items-center space-x-5">
-              <Link
-                to="/login"
-                className="p-2 text-slate-700 hover:text-[#EE0434] transition-colors"
-              >
-                <svg
-                  className="w-6 h-6"
-                  fill="none"
-                  stroke="currentColor"
-                  viewBox="0 0 24 24"
+              {/* Account Dropdown */}
+              <div className="relative hidden lg:block" ref={userMenuRef}>
+                <button
+                  onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
+                  className="p-2 text-slate-700 hover:text-[#EE0434] transition-colors rounded-full hover:bg-slate-50"
+                  title="Account"
                 >
-                  <path
-                    strokeLinecap="round"
-                    strokeLinejoin="round"
-                    strokeWidth={2}
-                    d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
-                  />
-                </svg>
-              </Link>
+                  <svg
+                    className="w-6 h-6"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
+                    />
+                  </svg>
+                </button>
+
+                {isUserMenuOpen && (
+                  <div className="absolute top-full right-0 mt-3 w-56 bg-white rounded-[24px] shadow-[0_20px_40px_rgba(0,0,0,0.1)] border border-slate-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200 z-50">
+                    <div className="flex flex-col py-2">
+                      <button
+                        onClick={() => {
+                          // onViewChange(ViewMode.ORDER_HISTORY);
+                          navigate("/order-history");
+                          setIsUserMenuOpen(false);
+                        }}
+                        className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
+                      >
+                        <svg
+                          className="w-5 h-5 text-slate-400"
+                          fill="none"
+                          stroke="currentColor"
+                          viewBox="0 0 24 24"
+                        >
+                          <path
+                            strokeLinecap="round"
+                            strokeLinejoin="round"
+                            strokeWidth={2}
+                            d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
+                          />
+                        </svg>
+                        <span className="font-bold text-sm">Orders</span>
+                      </button>
+                      <div className="h-px bg-slate-50 mx-4 my-1"></div>
+                      <button
+                        onClick={() => {
+                          // onViewChange(ViewMode.LOGIN); // Simulate logout
+                          dispatch(accountLogout());
+                          navigate("/login");
+                          setIsUserMenuOpen(false);
+                        }}
+                        className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
+                      >
+                        <svg
+                          className="w-5 h-5 text-slate-400"
+                          fill="none"
+                          stroke="currentColor"
+                          viewBox="0 0 24 24"
+                        >
+                          <path
+                            strokeLinecap="round"
+                            strokeLinejoin="round"
+                            strokeWidth={2}
+                            d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
+                          />
+                        </svg>
+                        <span className="font-bold text-sm">Logout</span>
+                      </button>
+                    </div>
+                  </div>
+                )}
+              </div>
               <button className="hidden sm:flex p-2 text-slate-700 hover:text-[#EE0434] relative">
                 <svg
                   className="w-6 h-6"
@@ -708,6 +770,20 @@ const Header: React.FC = () => {
               >
                 Contact
               </Link>
+              <button
+                onClick={() => {
+                  // onViewChange(ViewMode.ORDER_HISTORY);
+                  navigate("/order-history");
+                  setIsMenuOpen(false);
+                }}
+                className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${
+                  isActive("/order-history")
+                    ? "bg-red-50 text-[#EE0434]"
+                    : "text-slate-800 hover:bg-slate-50"
+                }`}
+              >
+                Transaction History
+              </button>
 
               <div className="w-full pt-4">
                 <p className="text-center text-slate-400 font-bold text-xs uppercase tracking-widest mb-4">
@@ -740,7 +816,10 @@ const Header: React.FC = () => {
           <div className="p-8 border-t border-slate-50 bg-slate-50/50">
             <Link
               to="/login"
-              onClick={() => setIsMenuOpen(false)}
+              onClick={() => {
+                setIsMenuOpen(false);
+                dispatch(accountLogout());
+              }}
               className="w-full bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white py-5 rounded-[40px] font-black text-2xl shadow-xl active:scale-[0.98] transition-all flex justify-center"
             >
               Login / Register

+ 29 - 12
EsimLao/esim-vite/src/components/Popup.tsx

@@ -1,10 +1,12 @@
 import React from "react";
 import { useAppDispatch, useAppSelector } from "../hooks/useRedux";
 import { closePopup } from "../features/popup/popupSlice";
+import { useNavigate } from "react-router-dom";
 
 const Popup = () => {
   const popup = useAppSelector((state) => state.popup);
   const dispatch = useAppDispatch();
+  const navigate = useNavigate();
   if (!popup.isOpen) return null;
   return (
     <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 animate-in fade-in duration-200">
@@ -70,18 +72,33 @@ const Popup = () => {
         </p>
 
         {/* Button */}
-        <button
-          onClick={() => {
-            dispatch(closePopup());
-          }}
-          className={`w-full py-4 rounded-2xl font-black text-lg shadow-lg active:scale-95 transition-all ${
-            popup.isSuccess
-              ? "bg-[#00c087] hover:bg-[#00a876] text-white shadow-green-200"
-              : "bg-[#EE0434] hover:bg-[#d10029] text-white shadow-red-200"
-          }`}
-        >
-          {popup.buttonText}
-        </button>
+        <div className="w-full flex flex-col md:flex-row gap-4 md:gap-6">
+          {popup.hasRightButton && (
+            <button
+              onClick={() => {
+                if (popup.rightButtonAction == "OPEN_ORDER_HISTORY") {
+                  navigate("/order-history");
+                }
+                dispatch(closePopup());
+              }}
+              className="mr-4 w-full py-4 rounded-2xl font-black text-lg shadow-lg active:scale-95 transition-all bg-[#EE0434] hover:bg-[#d10029] text-white shadow-red-200"
+            >
+              {popup.rightButtonText}
+            </button>
+          )}
+          <button
+            onClick={() => {
+              dispatch(closePopup());
+            }}
+            className={`w-full py-4 rounded-2xl font-black text-lg shadow-lg active:scale-95 transition-all ${
+              popup.isSuccess
+                ? "bg-[#00c087] hover:bg-[#00a876] text-white shadow-green-200"
+                : "bg-[#EE0434] hover:bg-[#d10029] text-white shadow-red-200"
+            }`}
+          >
+            {popup.buttonText}
+          </button>
+        </div>
       </div>
     </div>
   );

+ 1 - 2
EsimLao/esim-vite/src/components/ProductCard.tsx

@@ -28,8 +28,7 @@ const ProductCard: React.FC<{
       <p className="text-[#EE0434] font-bold text-xs md:text-lg">
         from:{" "}
         <span className="font-black">
-          {p.curency}
-          {p.minDisplayPrice}
+          {p.minDisplayPrice} {p.curency}
         </span>
       </p>
     </div>

+ 3 - 2
EsimLao/esim-vite/src/features/account/accuntSlice.ts

@@ -8,9 +8,10 @@ const accountSlice = createSlice({
   },
   reducers: {
     accountLogin: (state, action: PayloadAction<AccountInfo>) => {
+      console.log("data in slice: ", action.payload);
       localStorage.setItem("token", action.payload.accessToken);
       localStorage.setItem("refreshToken", action.payload.refreshToken);
-      localStorage.setItem("accountInfo", JSON.stringify(action.payload));
+      // localStorage.setItem("accountInfo", JSON.stringify(action.payload));
       return {
         ...state,
         account: action.payload,
@@ -19,7 +20,7 @@ const accountSlice = createSlice({
     accountLogout: (state) => {
       localStorage.removeItem("token");
       localStorage.removeItem("refreshToken");
-      localStorage.removeItem("accountInfo");
+      // localStorage.removeItem("accountInfo");
       return {
         ...state,
         account: null,

+ 21 - 2
EsimLao/esim-vite/src/features/popup/popupSlice.ts

@@ -5,10 +5,14 @@ const popupSlice = createSlice({
   name: "popup",
   initialState: {
     isOpen: false,
+    isOpenCompatibilityModal: false,
     isSuccess: true,
     title: "Notification",
     message: "",
-    buttonText: "OK",
+    buttonText: "Close",
+    hasRightButton: false,
+    rightButtonText: "Confirm",
+    rightButtonAction: "",
   },
   reducers: {
     openPopup: (state, action: PayloadAction<any>) => {
@@ -18,13 +22,28 @@ const popupSlice = createSlice({
         isSuccess: action.payload?.isSuccess ?? true,
         title: action.payload?.title ?? "Notification",
         message: action.payload?.message,
+        buttonText: action.payload?.buttonText ?? "Close",
+        hasRightButton: action.payload?.hasRightButton ?? false,
+        rightButtonText: action.payload?.rightButtonText ?? "Confirm",
+        rightButtonAction: action.payload?.rightButtonAction ?? "",
       };
     },
     closePopup: (state) => {
       state.isOpen = false;
     },
+    openCompatibilityModal: (state) => {
+      state.isOpenCompatibilityModal = true;
+    },
+    closeCompatibilityModal: (state) => {
+      state.isOpenCompatibilityModal = false;
+    },
   },
 });
 
-export const { openPopup, closePopup } = popupSlice.actions;
+export const {
+  openPopup,
+  closePopup,
+  openCompatibilityModal,
+  closeCompatibilityModal,
+} = popupSlice.actions;
 export default popupSlice.reducer;

+ 27 - 9
EsimLao/esim-vite/src/pages/checkout/CheckoutView.tsx

@@ -292,10 +292,18 @@ const CheckoutView = () => {
               </div>
               <div className="text-right">
                 <p className="text-xs text-slate-400 line-through font-bold">
-                  ${state.checkoutDetails.paymentMoney}
+                  $
+                  {state.checkoutDetails.paymentMoney.toLocaleString("vi-VN", {
+                    minimumFractionDigits: 2,
+                    maximumFractionDigits: 2,
+                  })}
                 </p>
                 <p className="text-[#0071e3] font-black text-xl">
-                  ${state.checkoutDetails.totalMoney}
+                  $
+                  {state.checkoutDetails.totalMoney.toLocaleString("vi-VN", {
+                    minimumFractionDigits: 2,
+                    maximumFractionDigits: 2,
+                  })}
                 </p>
               </div>
             </div>
@@ -401,14 +409,23 @@ const CheckoutView = () => {
                 <span>Subtotal:</span>
                 <span>
                   $
-                  {(state.checkoutDetails.totalMoney * state.quantity).toFixed(
-                    1
-                  )}
+                  {(
+                    state.checkoutDetails.totalMoney * state.quantity
+                  ).toLocaleString("vi-VN", {
+                    minimumFractionDigits: 2,
+                    maximumFractionDigits: 2,
+                  })}
                 </span>
               </div>
               <div className="flex justify-between text-green-600 font-bold">
                 <span>Discount:</span>
-                <span>-${(0 * state.quantity).toFixed(1)}</span>
+                <span>
+                  -$
+                  {(0 * state.quantity).toLocaleString("vi-VN", {
+                    minimumFractionDigits: 2,
+                    maximumFractionDigits: 2,
+                  })}
+                </span>
               </div>
               <div className="flex justify-between items-center pt-4 border-t border-slate-200/50">
                 <span className="text-[#0071e3] font-black text-xl">
@@ -416,9 +433,10 @@ const CheckoutView = () => {
                 </span>
                 <span className="text-[#0071e3] font-black text-3xl">
                   $
-                  {(
-                    state.checkoutDetails.paymentMoney * state.quantity
-                  ).toFixed(1)}
+                  {state.checkoutDetails.paymentMoney.toLocaleString("vi-VN", {
+                    minimumFractionDigits: 2,
+                    maximumFractionDigits: 2,
+                  })}
                 </span>
               </div>
             </div>

+ 51 - 12
EsimLao/esim-vite/src/pages/contact/ContactView.tsx

@@ -1,5 +1,4 @@
-
-import React from 'react';
+import React from "react";
 
 const ContactView: React.FC = () => {
   return (
@@ -7,29 +6,69 @@ const ContactView: React.FC = () => {
       <section className="max-w-7xl mx-auto px-6 md:px-12 py-16 md:py-24">
         <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
           <div className="space-y-8">
-            <h1 className="text-5xl md:text-7xl font-black text-[#EE0434] tracking-tight leading-tight">Get in touch <br /> with us</h1>
-            <p className="text-slate-500 text-lg md:text-xl font-medium leading-relaxed max-w-lg">We're always ready to listen and support you with any questions. Leave your information, and our team will get back to you as soon as possible.</p>
+            <h1 className="text-5xl md:text-7xl font-black text-[#EE0434] tracking-tight leading-tight">
+              Get in touch <br /> with us
+            </h1>
+            <p className="text-slate-500 text-lg md:text-xl font-medium leading-relaxed max-w-lg">
+              We're always ready to listen and support you with any questions.
+              Leave your information, and our team will get back to you as soon
+              as possible.
+            </p>
             <div className="space-y-6 pt-4">
               <div className="space-y-2">
                 <div className="flex items-center space-x-3 text-slate-800">
-                   <svg className="w-6 h-6 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/></svg>
-                   <span className="font-bold text-lg">Email</span>
+                  <svg
+                    className="w-6 h-6 text-[#EE0434]"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
+                      strokeWidth={2}
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                    />
+                  </svg>
+                  <span className="font-bold text-lg">Email</span>
                 </div>
-                <a href="mailto:info@infigate.global" className="block text-[#EE0434] font-black text-lg md:text-xl pl-9 hover:underline">info@infigate.global</a>
+                <a
+                  href="mailto:info@getgo.global"
+                  className="block text-[#EE0434] font-black text-lg md:text-xl pl-9 hover:underline"
+                >
+                  info@getgo.global
+                </a>
               </div>
             </div>
           </div>
           <div className="bg-white p-2">
             <form className="space-y-8" onSubmit={(e) => e.preventDefault()}>
               <div className="space-y-3">
-                <label className="block text-slate-800 font-bold text-lg">Full Name</label>
-                <input type="text" placeholder="Enter your full name" className="w-full bg-white border border-slate-200 rounded-xl py-4 px-6 focus:outline-none focus:ring-2 focus:ring-red-50 focus:border-[#EE0434] transition-all" />
+                <label className="block text-slate-800 font-bold text-lg">
+                  Full Name
+                </label>
+                <input
+                  type="text"
+                  placeholder="Enter your full name"
+                  className="w-full bg-white border border-slate-200 rounded-xl py-4 px-6 focus:outline-none focus:ring-2 focus:ring-red-50 focus:border-[#EE0434] transition-all"
+                />
               </div>
               <div className="space-y-3">
-                <label className="block text-slate-800 font-bold text-lg">Specific Requirements</label>
-                <textarea rows={6} placeholder="Describe your requirements" className="w-full bg-white border border-slate-200 rounded-2xl py-4 px-6 focus:outline-none focus:ring-2 focus:ring-red-50 focus:border-[#EE0434] transition-all" />
+                <label className="block text-slate-800 font-bold text-lg">
+                  Specific Requirements
+                </label>
+                <textarea
+                  rows={6}
+                  placeholder="Describe your requirements"
+                  className="w-full bg-white border border-slate-200 rounded-2xl py-4 px-6 focus:outline-none focus:ring-2 focus:ring-red-50 focus:border-[#EE0434] transition-all"
+                />
               </div>
-              <button type="submit" className="w-full bg-[#EE0434] text-white py-5 rounded-[40px] font-black text-2xl shadow-lg hover:shadow-xl transition-all">Submit</button>
+              <button
+                type="submit"
+                className="w-full bg-[#EE0434] text-white py-5 rounded-[40px] font-black text-2xl shadow-lg hover:shadow-xl transition-all"
+              >
+                Submit
+              </button>
             </form>
           </div>
         </div>

+ 0 - 136
EsimLao/esim-vite/src/pages/handbook/HandbookView.tsx

@@ -1,136 +0,0 @@
-
-import React from 'react';
-import { HandbookArticle } from '../types';
-
-interface HandbookViewProps {
-  onBack: () => void;
-  onArticleSelect: (article: HandbookArticle) => void;
-}
-
-const HandbookView: React.FC<HandbookViewProps> = ({ onBack, onArticleSelect }) => {
-  const articles: HandbookArticle[] = [
-    {
-      id: '1',
-      title: 'Siêu Sale Mùa Lễ Hội Cùng InfiGate – Giảm Đến 35% Khi Mua eSIM Du Lịch Quốc Tế',
-      date: '01/12/2025',
-      excerpt: 'Siêu Sale Mùa Lễ Hội Cùng InfiGate – Giảm Đến 35% Thời gian áp dụng ưu đãi: 01/12 – 31/12/2025',
-      image: 'https://images.unsplash.com/photo-1549463599-231a5401977e?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '2',
-      title: 'Du lịch châu Á tháng 12: Mùa lễ hội rực rỡ và những trải nghiệm không thể bỏ lỡ – Đừng quên chuẩn bị SIM du lịch trước khi khởi hành!',
-      date: '28/11/2025',
-      excerpt: 'Tháng 12 là thời điểm lý tưởng để khám phá châu Á: từ mùa tuyết trắng Nhật – Hàn, biển ấm Đông...',
-      image: 'https://images.unsplash.com/photo-1493976040374-85c8e12f0c0e?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '3',
-      title: 'InfiGate ra mắt bộ đôi eSIM & SIM du lịch – Mở rộng lựa chọn, tối ưu kết nối toàn cầu',
-      date: '27/11/2025',
-      excerpt: 'Khi nhu cầu du lịch bùng nổ mạnh mẽ, Internet trở thành “vũ khí tối thượng” cho mọi chuyến đi: dẫn...',
-      image: 'https://images.unsplash.com/photo-1526772662000-3f88f10c053b?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '4',
-      title: 'Hướng dẫn cách cài đặt eSIM du lịch trên Samsung',
-      date: '14/11/2025',
-      excerpt: 'Hướng dẫn cài đặt eSIM du lịch trên Samsung nhanh và đơn giản. Áp dụng cho Galaxy S, A, Z. Gợi ý eSIM...',
-      image: 'https://images.unsplash.com/photo-1610945265064-0e34e5519bbf?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '5',
-      title: 'Hướng dẫn cách cài đặt eSIM du lịch trên iPhone',
-      date: '14/11/2025',
-      excerpt: 'Hướng dẫn chi tiết cách kích hoạt eSIM trên các dòng iPhone từ XS trở lên. Giải pháp kết nối tối ưu...',
-      image: 'https://images.unsplash.com/photo-1510557880182-3d4d3cba35a5?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '6',
-      title: 'Top 5 điểm đến mùa thu tuyệt đẹp tại Nhật Bản',
-      date: '05/11/2025',
-      excerpt: 'Khám phá vẻ đẹp của lá đỏ tại Kyoto, Tokyo và Hokkaido. Kinh nghiệm chuẩn bị SIM 4G tốc độ cao...',
-      image: 'https://images.unsplash.com/photo-1528114039593-4366cc08227d?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '7',
-      title: 'Sự khác biệt giữa SIM vật lý và eSIM cho người đi du lịch',
-      date: '01/11/2025',
-      excerpt: 'Lựa chọn nào tốt nhất cho chuyến đi của bạn? So sánh tính tiện dụng, khả năng tương thích...',
-      image: 'https://images.unsplash.com/photo-1551817958-1110f7abe310?q=80&w=800&auto=format&fit=crop'
-    },
-    {
-      id: '8',
-      title: 'Bí kíp du lịch Thái Lan tự túc tiết kiệm',
-      date: '25/10/2025',
-      excerpt: 'Tổng hợp kinh nghiệm ăn chơi, di chuyển và đặt eSIM Thái Lan giá rẻ nhất chỉ từ 15k/ngày...',
-      image: 'https://images.unsplash.com/photo-1528181304800-2f140819898f?q=80&w=1000&auto=format&fit=crop'
-    }
-  ];
-
-  return (
-    <div className="bg-[#fcfdfe] min-h-screen">
-      {/* Breadcrumbs */}
-      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
-        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
-          <button onClick={onBack} className="hover:text-blue-500">Home</button>
-          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-            <path d="M9 5l7 7-7 7" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/>
-          </svg>
-          <span className="text-slate-900 font-bold">Handbook</span>
-        </nav>
-      </div>
-
-      <div className="max-w-7xl mx-auto px-4 py-12">
-        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
-          {articles.map((article) => (
-            <div 
-              key={article.id} 
-              onClick={() => onArticleSelect(article)}
-              className="bg-white rounded-[24px] overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 flex flex-col group cursor-pointer border border-slate-50"
-            >
-              {/* Card Image */}
-              <div className="relative aspect-[4/3] overflow-hidden">
-                <img 
-                  src={article.image} 
-                  alt={article.title} 
-                  className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
-                />
-              </div>
-
-              {/* Card Content */}
-              <div className="p-6 flex flex-col flex-1 space-y-3">
-                <div className="flex items-center text-slate-400 space-x-2">
-                   <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                     <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
-                   </svg>
-                   <span className="text-xs font-bold">{article.date}</span>
-                </div>
-                
-                <h3 className="text-[16px] md:text-[18px] font-black text-slate-800 leading-tight group-hover:text-[#004ea8] transition-colors line-clamp-3">
-                  {article.title}
-                </h3>
-                
-                <p className="text-xs md:text-sm text-slate-500 font-medium line-clamp-3 leading-relaxed">
-                  {article.excerpt}
-                </p>
-              </div>
-            </div>
-          ))}
-        </div>
-
-        {/* Pagination Placeholder */}
-        <div className="mt-16 flex justify-center items-center space-x-2">
-           <button className="w-10 h-10 rounded-full bg-[#004ea8] text-white font-black text-sm">1</button>
-           <button className="w-10 h-10 rounded-full bg-white text-slate-400 border border-slate-100 font-black text-sm hover:border-[#004ea8] hover:text-[#004ea8] transition-colors">2</button>
-           <button className="w-10 h-10 rounded-full bg-white text-slate-400 border border-slate-100 font-black text-sm hover:border-[#004ea8] hover:text-[#004ea8] transition-colors">
-             <svg className="w-4 h-4 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
-             </svg>
-           </button>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default HandbookView;

+ 28 - 5
EsimLao/esim-vite/src/pages/home/HomeView.tsx

@@ -7,6 +7,7 @@ import HomeProduct from "./components/HomeProduct";
 import HomeSearch from "./components/HomeSearch";
 import HomeFaq from "./components/HomeFaq";
 import { useAppDispatch } from "../../hooks/useRedux";
+import { openPopup } from "../../features/popup/popupSlice";
 
 const HomeView: React.FC = () => {
   const [simType, setSimType] = useState<"eSIM" | "Physical">("eSIM");
@@ -16,9 +17,31 @@ const HomeView: React.FC = () => {
 
   useEffect(() => {
     const params = new URLSearchParams(window.location.search);
-    const id = params.get("vpc_TxnResponseCode");
-    if (id) {
-      dis;
+    const status = params.get("vpc_TxnResponseCode");
+    if (status) {
+      console.log("URL Params:", params);
+      if (status === "0") {
+        dispatch(
+          openPopup({
+            isSuccess: true,
+            message: "Payment successful!",
+            title: "Payment Status",
+            buttonText: "Close",
+            hasRightButton: true,
+            rightButtonText: "View Order",
+            rightButtonAction: "OPEN_ORDER_HISTORY",
+          })
+        );
+      } else {
+        dispatch(
+          openPopup({
+            isSuccess: false,
+            message: "Payment failed or cancelled.",
+            title: "Payment Status",
+            buttonText: "Close",
+          })
+        );
+      }
     }
   }, []);
 
@@ -69,7 +92,7 @@ const HomeView: React.FC = () => {
       <section className="py-16 md:py-24 bg-white px-4">
         <div className="max-w-7xl mx-auto">
           <h2 className="text-3xl md:text-6xl font-black text-center mb-12 md:mb-20 tracking-tight text-slate-900">
-            Why <span className="text-[#EE0434]">choose InfiGate</span>
+            Why <span className="text-[#EE0434]">choose Getgo</span>
           </h2>
           <div className="bg-gradient-to-br from-[#E21c34] to-[#500B28] rounded-[32px] md:rounded-[60px] p-8 md:p-16 lg:p-24 shadow-2xl relative overflow-hidden">
             <div className="absolute top-0 right-0 w-[500px] h-[500px] bg-white/5 rounded-full blur-[100px] -translate-y-1/2 translate-x-1/2"></div>
@@ -227,7 +250,7 @@ const HomeView: React.FC = () => {
       <section className="py-12 md:py-20 bg-white px-4">
         <div className="max-w-7xl mx-auto space-y-10 md:space-y-16">
           <h2 className="text-3xl md:text-6xl font-black text-center tracking-tight text-slate-900">
-            InfiGate has <span className="text-[#EE0434]">partnered with</span>
+            Getgo has <span className="text-[#EE0434]">partnered with</span>
           </h2>
 
           <div className="space-y-8 md:space-y-12 max-w-5xl mx-auto">

+ 7 - 4
EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx

@@ -105,12 +105,15 @@ const HomeProduct = () => {
                 {p.areaName1}
               </h3>
               <p className="text-xs text-slate-400 line-through mb-1">
-                {p.curency}
-                {p.minSellPrice}
+                {p.minSellPrice} {p.curency}
               </p>
               <p className="text-lg md:text-2xl font-black text-[#EE0434]">
-                From {p.curency}
-                {p.minDisplayPrice}
+                From{" "}
+                {p.minDisplayPrice.toLocaleString("vi-VN", {
+                  minimumFractionDigits: 2,
+                  maximumFractionDigits: 2,
+                })}{" "}
+                {p.curency}
               </p>
             </div>
           ))}

+ 14 - 6
EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx

@@ -1,4 +1,8 @@
+import { openCompatibilityModal } from "../../../features/popup/popupSlice";
+import { useDispatch } from "react-redux";
+
 const HomeSearch = () => {
+  const dispatch = useDispatch();
   return (
     <section className="pt-8 pb-4 px-4 relative z-40 bg-white">
       <div className="max-w-4xl mx-auto w-full relative">
@@ -28,18 +32,22 @@ const HomeSearch = () => {
           </button>
         </div>
         <div className="mt-3 flex justify-center gap-2 text-sm text-slate-400 font-medium">
-          <span>Popular:</span>
-          <button className="hover:text-[#EE0434] transition-colors">
-            Japan
-          </button>
-          ,
+          <a
+            className="hover:text-[#EE0434] transition-colors underline color-hover cursor-pointer text-[18px] font-medium"
+            onClick={() => {
+              dispatch(openCompatibilityModal());
+            }}
+          >
+            Does my phone support eSIM?
+          </a>
+          {/* ,
           <button className="hover:text-[#EE0434] transition-colors">
             Thailand
           </button>
           ,
           <button className="hover:text-[#EE0434] transition-colors">
             China
-          </button>
+          </button> */}
         </div>
       </div>
     </section>

+ 3 - 5
EsimLao/esim-vite/src/pages/login/LoginView.tsx

@@ -16,7 +16,7 @@ const LoginView: React.FC = () => {
   const navigate = useNavigate();
   const loading = useAppSelector((state) => state.loading);
   const dispatch = useAppDispatch();
-  const [email, setEmail] = useState("");
+  const [email, setEmail] = useState("abc@gmail.com");
   const [step, setStep] = useState<"email" | "otp">("email");
 
   const [otp, setOtp] = useState("");
@@ -97,11 +97,9 @@ const LoginView: React.FC = () => {
       dispatch(stopSmallLoading());
       console.log("Verify otp response data:", data);
       if (data && data.errorCode === "0") {
-        console.log("Verify otp successful");
+        console.log("Verify otp successful " + data.data.accessToken);
         // save token to local storage
         dispatch(accountLogin(data.data));
-        // redirect to home
-        dispatch(startLoading({ message: "Logging in..." }));
         navigate("/");
       } else {
         console.error("Verify otp failed, no token received");
@@ -166,7 +164,7 @@ const LoginView: React.FC = () => {
               Welcome Back!
             </h1>
             <p className="text-slate-400 font-medium text-base md:text-lg">
-              Stay connected everywhere with InfiGate.
+              Stay connected everywhere with Getgo.
             </p>
           </div>
           {step === "email" && (

+ 516 - 0
EsimLao/esim-vite/src/pages/order-detail/OrderDetailView.tsx

@@ -0,0 +1,516 @@
+import {
+  DataUsage,
+  OrderDetail,
+  OrderHistory,
+} from "../../services/product/type";
+import { productApi } from "../../apis/productApi";
+import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
+import { useAppDispatch } from "../../hooks/useRedux";
+import { useMutation } from "@tanstack/react-query";
+import React, { useState, useEffect } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+
+const OrderDetailView = () => {
+  const location = useLocation();
+  const dispatch = useAppDispatch();
+  const navigate = useNavigate();
+  const [activeTab, setActiveTab] = useState<"detail" | "manage">("detail");
+  const [orderDetails, setOrderDetails] = useState<OrderDetail[] | []>([]);
+  const [dataUsage, setDataUsage] = useState<DataUsage[] | []>([]);
+  const state = location.state as {
+    id: number;
+    orderHistory: OrderHistory;
+  };
+
+  useEffect(() => {
+    getOrderDetailMutation.mutate();
+  }, []);
+
+  const getOrderDetailMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await productApi.getOrderDetail({
+        orderId: state.id,
+      });
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get order detail response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Get order detail successful");
+        setOrderDetails(data.data ?? []);
+      } else {
+        console.error("Get order detail failed, no token received");
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get order detail error:", error.response.data);
+    },
+  });
+
+  const handleTabChange = async (tab: "detail" | "manage") => {
+    if (tab === "manage") {
+      // Load manage data if needed
+      dispatch(startLoading({}));
+      try {
+        const results = await Promise.all(
+          orderDetails.map(async (element) => {
+            const res = await productApi.checkDataUsage({
+              iccid: element?.iccid,
+            });
+
+            if (res && res.errorCode === "0") {
+              return res.data;
+            }
+
+            return null;
+          })
+        );
+
+        const validResults = results.filter(Boolean);
+        setDataUsage(validResults);
+
+        console.log("All data usage results:", validResults);
+      } catch (error) {
+        console.error("Check data usage error:", error);
+      } finally {
+        dispatch(stopLoading());
+      }
+    }
+    setActiveTab(tab);
+  };
+
+  // Circular Progress Component
+  const CircularProgress = ({
+    used,
+    total,
+    label,
+  }: {
+    used: number;
+    total: number;
+    label: string;
+  }) => {
+    const radius = 50;
+    const circumference = 2 * Math.PI * radius;
+    const percentage = (used / total) * 100;
+    const strokeDashoffset = circumference - (percentage / 100) * circumference;
+
+    return (
+      <div className="flex flex-col items-center">
+        <div className="relative w-32 h-32 md:w-40 md:h-40 flex items-center justify-center mb-4">
+          <svg
+            className="transform -rotate-90 w-full h-full"
+            viewBox="0 0 120 120"
+          >
+            <circle
+              cx="60"
+              cy="60"
+              r={radius}
+              stroke="#f1f5f9"
+              strokeWidth="10"
+              fill="transparent"
+            />
+            <circle
+              cx="60"
+              cy="60"
+              r={radius}
+              stroke="#00b0f0"
+              strokeWidth="10"
+              fill="transparent"
+              strokeDasharray={circumference}
+              strokeDashoffset={strokeDashoffset}
+              strokeLinecap="round"
+            />
+          </svg>
+          <div className="absolute flex flex-col items-center">
+            <span className="text-xl md:text-2xl font-bold text-[#00b0f0]">
+              {used} / <br />
+              {total}
+            </span>
+            <span className="text-sm md:text-xl font-bold text-[#00b0f0]">
+              MB
+            </span>
+          </div>
+
+          {/* <div className="absolute bottom-2 left-0 text-[10px] font-bold text-slate-400">
+            0 MB
+          </div>
+          <div className="absolute bottom-2 right-0 text-[10px] font-bold text-slate-400">
+            {total} MB
+          </div> */}
+        </div>
+        <p className="text-slate-400 text-sm font-bold uppercase tracking-widest">
+          {label}
+        </p>
+      </div>
+    );
+  };
+  const getStatusColor = (status: number) => {
+    switch (status) {
+      case 2:
+        return "bg-[#00c087] text-white";
+      case 1:
+        return "bg-orange-400 text-white";
+      default:
+        return "bg-slate-800 text-white";
+    }
+  };
+  return (
+    <div className="bg-[#fcfdfe] min-h-screen pb-20">
+      {/* Breadcrumb */}
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
+          <button
+            // onClick={() => onViewChange(ViewMode.HOME)}
+            className="hover:text-[#EE0434] text-[16px]"
+          >
+            Home
+          </button>
+          <svg
+            className="w-3 h-3"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2.5}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <button
+            // onClick={() => onViewChange(ViewMode.ORDER_HISTORY)}
+            className="hover:text-[#EE0434] text-[16px] font-bold"
+          >
+            My eSIM
+          </button>
+        </nav>
+      </div>
+
+      <div className="max-w-5xl mx-auto px-4 py-8">
+        {/* Back Button */}
+        <button
+          onClick={() => navigate(-1)}
+          className="mb-6 flex items-center space-x-2 px-4 py-2 bg-white border border-slate-200 rounded-lg text-slate-600 font-bold hover:bg-slate-50 transition-colors shadow-sm"
+        >
+          <svg
+            className="w-4 h-4"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              strokeLinecap="round"
+              strokeLinejoin="round"
+              strokeWidth={2}
+              d="M10 19l-7-7 7-7m8 14l-7-7 7-7"
+            />
+          </svg>
+          <span>Back</span>
+        </button>
+
+        {/* Main Info Card */}
+        <div className="bg-white rounded-2xl border border-[#00b0f0]/30 shadow-sm overflow-hidden mb-8">
+          <div className="p-6">
+            {/* Header */}
+            <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
+              <div className="flex items-center gap-4">
+                <div className="w-10 h-10 bg-[#00b0f0] rounded-xl flex items-center justify-center text-white shadow-sm">
+                  <svg
+                    className="w-6 h-6"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
+                    />
+                  </svg>
+                </div>
+                <span className="text-xl md:text-2xl font-bold text-slate-800">
+                  {state.orderHistory?.id}
+                </span>
+              </div>
+              {state.orderHistory?.status === 2 ? (
+                <span
+                  className={`px-3 py-1 rounded-md text-sm font-bold shadow-sm ${getStatusColor(
+                    state.orderHistory?.status
+                  )}`}
+                >
+                  Success
+                </span>
+              ) : state.orderHistory?.status === 1 ? (
+                <span
+                  className={`px-3 py-1 rounded-md text-sm font-bold shadow-sm ${getStatusColor(
+                    state.orderHistory?.status
+                  )}`}
+                >
+                  Pending Payment
+                </span>
+              ) : (
+                <span
+                  className={`px-3 py-1 rounded-md text-sm font-bold shadow-sm ${getStatusColor(
+                    state.orderHistory?.status
+                  )}`}
+                >
+                  Failure
+                </span>
+              )}
+            </div>
+
+            {/* Tabs */}
+            <div className="flex space-x-1 mb-8">
+              <button
+                onClick={() => handleTabChange("detail")}
+                className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
+                  activeTab === "detail"
+                    ? "bg-[#00b0f0] text-white"
+                    : "text-[#00b0f0] hover:bg-[#00b0f0]/10"
+                }`}
+              >
+                Detail
+              </button>
+              <button
+                onClick={() => handleTabChange("manage")}
+                className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
+                  activeTab === "manage"
+                    ? "bg-[#00b0f0] text-white"
+                    : "text-[#00b0f0] hover:bg-[#00b0f0]/10"
+                }`}
+              >
+                Manage
+              </button>
+            </div>
+
+            {/* Divider with Total */}
+            <div className="relative flex items-center justify-center border-t border-slate-100 py-8">
+              <span className="bg-white px-4 text-lg md:text-xl font-bold text-slate-800 absolute">
+                Total:{" "}
+                {state.orderHistory?.totalMoney.toLocaleString("vi-VN", {
+                  minimumFractionDigits: 2,
+                  maximumFractionDigits: 2,
+                })}{" "}
+                <span className="text-slate-500 font-normal">
+                  ({state.orderHistory?.curency})
+                </span>
+              </span>
+            </div>
+
+            {/* Customer Info Grid */}
+            <div className="grid grid-cols-1 md:grid-cols-4 gap-6 md:gap-4 mt-4">
+              <div className="flex flex-col">
+                <div className="flex items-center gap-2 text-slate-400 mb-1">
+                  <svg
+                    className="w-4 h-4"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
+                    />
+                  </svg>
+                  <span className="text-xs font-bold text-[16px]">
+                    Full name
+                  </span>
+                </div>
+                <span className="text-slate-800 font-semibold text-sm text-[16px]">
+                  {state?.orderHistory.customerInfo.surName}{" "}
+                  {state?.orderHistory.customerInfo.lastName}
+                </span>
+              </div>
+              <div className="flex flex-col">
+                <div className="flex items-center gap-2 text-slate-400 mb-1">
+                  <svg
+                    className="w-4 h-4"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
+                    />
+                  </svg>
+                  <span className="text-xs font-bold text-[16px]">
+                    Phone number
+                  </span>
+                </div>
+                <span className="text-slate-800 font-semibold text-sm text-[16px]">
+                  {state?.orderHistory.customerInfo.phoneNumber}
+                </span>
+              </div>
+              <div className="flex flex-col">
+                <div className="flex items-center gap-2 text-slate-400 mb-1">
+                  <svg
+                    className="w-4 h-4"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
+                    />
+                  </svg>
+                  <span className="text-xs font-bold text-[16px]">Email</span>
+                </div>
+                <span className="text-slate-800 font-semibold text-sm text-[16px] break-all">
+                  {state?.orderHistory.customerInfo.email}
+                </span>
+              </div>
+              <div className="flex flex-col">
+                <div className="flex items-center gap-2 text-slate-400 mb-1">
+                  <svg
+                    className="w-4 h-4"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 00-3 3z"
+                    />
+                  </svg>
+                  <span className="text-xs font-bold text-[16px]">
+                    Payment method
+                  </span>
+                </div>
+                <span className="text-slate-800 font-semibold text-sm text-[16px]">
+                  ONEPAY
+                </span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        {/* Product Section */}
+        <div className="space-y-4">
+          {orderDetails.length > 0 && (
+            <div className="flex items-center space-x-3">
+              <img
+                src={`${orderDetails[0].areaIconUrl}`}
+                className="w-8 h-8 rounded-full object-cover border border-slate-200"
+              />
+              <h3 className="text-lg font-bold text-slate-900">
+                SIM {orderDetails[0].areaName}
+              </h3>
+            </div>
+          )}
+
+          <div
+            className={`rounded-2xl border border-slate-100 shadow-sm p-6 relative ${
+              activeTab === "manage" ? "bg-slate-50/50" : "bg-white"
+            }`}
+          >
+            {activeTab === "detail" ? (
+              // DETAIL TAB CONTENT
+              <div className="space-y-8">
+                {orderDetails.map((pkg, idx) => (
+                  <div
+                    key={idx}
+                    className={`flex flex-col md:flex-row justify-between items-start md:items-center ${
+                      idx !== orderDetails.length - 1
+                        ? "border-b border-slate-100 pb-8"
+                        : ""
+                    }`}
+                  >
+                    <div className="space-y-2">
+                      <div className="flex items-center gap-2">
+                        <h4 className="text-xl font-bold text-slate-800">
+                          {pkg.packageName}
+                        </h4>
+                        <span className="px-2 py-0.5 rounded bg-slate-100 text-slate-600 text-xs font-bold">
+                          {pkg.id}
+                        </span>
+                      </div>
+                      {/* <span className="inline-block bg-[#ff0050] text-white text-[10px] font-bold px-2 py-0.5 rounded">
+                        TikTok
+                      </span> */}
+                      <p className="text-slate-500 text-sm font-bold">
+                        Validity period: {pkg.dayDuration}
+                      </p>
+                    </div>
+
+                    <div className="mt-4 md:mt-0 flex flex-col items-end">
+                      <button className="flex items-center text-[#00b0f0] font-bold text-sm mb-2 hover:underline">
+                        <svg
+                          className="w-4 h-4 mr-1"
+                          fill="none"
+                          stroke="currentColor"
+                          viewBox="0 0 24 24"
+                        >
+                          <path
+                            strokeLinecap="round"
+                            strokeLinejoin="round"
+                            strokeWidth={2}
+                            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
+                          />
+                          <path
+                            strokeLinecap="round"
+                            strokeLinejoin="round"
+                            strokeWidth={2}
+                            d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
+                          />
+                        </svg>
+                        QR Code
+                      </button>
+                      <div className="text-right">
+                        <span className="text-xl font-bold text-slate-800">
+                          {pkg.paymentMoney.toLocaleString("vi-VN", {
+                            minimumFractionDigits: 2,
+                            maximumFractionDigits: 2,
+                          })}
+                        </span>
+                        <span className="text-slate-500 font-medium ml-1 font-bold">
+                          {" "}
+                          đ
+                        </span>
+                      </div>
+                    </div>
+                  </div>
+                ))}
+              </div>
+            ) : (
+              // MANAGE TAB CONTENT - Grid of Circular Progress
+              <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
+                {dataUsage.map((pkg, idx) => (
+                  <div
+                    key={idx}
+                    className="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm flex flex-col items-center hover:shadow-md transition-shadow"
+                  >
+                    <h4 className="text-lg font-bold text-slate-800 mb-2">
+                      {orderDetails[idx].packageName}
+                    </h4>
+                    <CircularProgress
+                      used={pkg.remainData}
+                      total={pkg.totalData}
+                      label={pkg.id}
+                    />
+                  </div>
+                ))}
+              </div>
+            )}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default OrderDetailView;

+ 273 - 0
EsimLao/esim-vite/src/pages/order-history/OrderHistoryView.tsx

@@ -0,0 +1,273 @@
+import { useNavigate } from "react-router-dom";
+import { productApi } from "../../apis/productApi";
+import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
+import { useAppDispatch } from "../../hooks/useRedux";
+import { OrderHistory } from "../../services/product/type";
+import { useMutation } from "@tanstack/react-query";
+import React, { useState, useEffect } from "react";
+
+const OrderHistoryView = () => {
+  const [searchOrder, setSearchOrder] = useState("");
+  // status is one of: -1, 1, 2
+  const [status, setStatus] = useState("-1");
+  const [fromDate, setFromDate] = useState("");
+  const [toDate, setToDate] = useState("");
+  const [orders, setOrders] = useState<OrderHistory[]>([]);
+
+  const dispatch = useAppDispatch();
+  const navigate = useNavigate();
+
+  useEffect(() => {
+    getOrderMutation.mutate();
+  }, []);
+
+  const getOrderMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await productApi.getOrderHistory({
+        fromDate,
+        toDate,
+        status,
+      });
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get order history response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Get order history successful");
+        setOrders(data.data || []);
+      } else {
+        console.error("Get order history failed, no token received");
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get order history error:", error.response.data);
+    },
+  });
+
+  // Input style matching LoginView
+  const inputClass =
+    "w-full bg-slate-50 border-2 border-transparent focus:border-[#EE0434]/20 rounded-2xl py-3 px-5 focus:outline-none focus:bg-white transition-all text-slate-700 font-bold placeholder:text-slate-300 text-sm md:text-base h-[50px]";
+
+  const getStatusColor = (status: number) => {
+    switch (status) {
+      case 2:
+        return "bg-[#00c087] text-white";
+      case 1:
+        return "bg-orange-400 text-white";
+      default:
+        return "bg-slate-800 text-white";
+    }
+  };
+
+  // const filteredOrders = orders.filter((order) => {
+  //   const matchesSearch = order.id.includes(searchOrder);
+  //   const matchesStatus = status === "-1" || order.status === status;
+  //   return matchesSearch && matchesStatus;
+  // });
+
+  return (
+    <div className="bg-[#fcfdfe] min-h-screen pb-20">
+      {/* Breadcrumb */}
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
+          <button
+            // onClick={() => onViewChange(ViewMode.HOME)}
+            className="hover:text-[#EE0434] text-[16px]"
+          >
+            Home
+          </button>
+          <svg
+            className="w-3 h-3"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2.5}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <span className="text-slate-900 font-bold text-[16px]">
+            Transaction History
+          </span>
+        </nav>
+      </div>
+
+      <div className="max-w-5xl mx-auto px-4 py-8 md:py-12">
+        {/* Filter Bar */}
+        <div className="grid grid-cols-1 md:grid-cols-12 gap-4 mb-8">
+          <div className="md:col-span-4 relative">
+            <input
+              type="text"
+              placeholder="Enter order code"
+              className={inputClass}
+              value={searchOrder}
+              onChange={(e) => {
+                setSearchOrder(e.target.value);
+                getOrderMutation.mutate();
+              }}
+            />
+            <button className="absolute right-2 top-1/2 -translate-y-1/2 w-8 h-8 bg-[#0091ff] rounded-xl flex items-center justify-center text-white shadow-md hover:scale-105 transition-transform">
+              <svg
+                className="w-4 h-4"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2.5}
+                  d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
+                />
+              </svg>
+            </button>
+          </div>
+
+          <div className="md:col-span-3">
+            <input
+              type="text"
+              placeholder="From date"
+              onFocus={(e) => (e.target.type = "date")}
+              onBlur={(e) => (e.target.type = "text")}
+              className={inputClass}
+              value={fromDate}
+              onChange={(e) => {
+                setFromDate(e.target.value);
+                getOrderMutation.mutate();
+              }}
+            />
+          </div>
+
+          <div className="md:col-span-3">
+            <input
+              type="text"
+              placeholder="To date"
+              onFocus={(e) => (e.target.type = "date")}
+              onBlur={(e) => (e.target.type = "text")}
+              className={inputClass}
+              value={toDate}
+              onChange={(e) => {
+                setToDate(e.target.value);
+                getOrderMutation.mutate();
+              }}
+            />
+          </div>
+
+          <div className="md:col-span-2 relative">
+            <select
+              className={`${inputClass} appearance-none cursor-pointer`}
+              value={status}
+              onChange={(e) => {
+                setStatus(e.target.value);
+                getOrderMutation.mutate();
+              }}
+            >
+              <option value="-1">Status</option>
+              <option value="2">Success</option>
+              <option value="1">Pending payment</option>
+            </select>
+            <svg
+              className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                d="M19 9l-7 7-7-7"
+              />
+            </svg>
+          </div>
+        </div>
+
+        {/* Order List */}
+        <div className="space-y-4">
+          {orders.length === 0 ? (
+            <div className="text-center py-20 opacity-50">
+              <p className="text-xl font-bold text-slate-400">
+                No orders found
+              </p>
+            </div>
+          ) : (
+            orders.map((order) => (
+              <div
+                key={order.id}
+                onClick={() =>
+                  navigate(`/order-history-detail/${order.id}`, {
+                    state: { id: order.id, orderHistory: order },
+                  })
+                }
+                className="bg-white rounded-[20px] p-6 shadow-sm border border-slate-100 hover:shadow-md transition-shadow flex flex-col md:flex-row justify-between items-start md:items-center gap-6"
+              >
+                <div className="space-y-3">
+                  <span
+                    className={`inline-block px-3 py-1 rounded-lg text-xs font-black uppercase tracking-wider ${getStatusColor(
+                      order.status
+                    )}`}
+                  >
+                    {order.status === 2
+                      ? "Success"
+                      : order.status === 1
+                      ? "Pending payment"
+                      : "Failed"}
+                  </span>
+                  <div>
+                    <p className="text-xl font-black text-slate-900 tracking-tight">
+                      {order.id}
+                    </p>
+                    <p className="text-sm font-medium text-slate-400 mt-1">
+                      {order.createdDate}
+                    </p>
+                  </div>
+                </div>
+
+                <div className="flex items-center justify-between w-full md:w-auto gap-8">
+                  <div className="flex -space-x-2">
+                    {/* {order.flags.map((flag, idx) => (
+                      <div
+                        key={idx}
+                        className="w-8 h-8 rounded-full border-2 border-white shadow-sm overflow-hidden z-0"
+                      >
+                        <img
+                          src={`https://flagcdn.com/w80/${flag}.png`}
+                          alt={flag}
+                          className="w-full h-full object-cover"
+                        />
+                      </div>
+                    ))} */}
+                  </div>
+                  <div className="text-right">
+                    <p className="text-xl font-black text-slate-900">
+                      {order.paymentMoney.toLocaleString("vi-VN", {
+                        minimumFractionDigits: 2,
+                        maximumFractionDigits: 2,
+                      })}{" "}
+                      ({order.curency})
+                    </p>
+                    <p className="text-sm font-bold text-slate-400">
+                      {order.totalMoney.toLocaleString("vi-VN", {
+                        minimumFractionDigits: 2,
+                        maximumFractionDigits: 2,
+                      })}{" "}
+                      ({order.curency})
+                    </p>
+                  </div>
+                </div>
+              </div>
+            ))
+          )}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default OrderHistoryView;

+ 17 - 4
EsimLao/esim-vite/src/pages/product-detail/ProductDetailView.tsx

@@ -219,7 +219,10 @@ const ProductDetailView: React.FC = () => {
     <div className="bg-white min-h-screen pb-20">
       <div className="max-w-7xl mx-auto px-4 py-4 md:py-6">
         <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500">
-          <Link to="/" className="hover:text-[#EE0434] transition-colors">
+          <Link
+            to="/"
+            className="hover:text-[#EE0434] transition-colors text-[16px]"
+          >
             Home
           </Link>
           <svg
@@ -235,7 +238,9 @@ const ProductDetailView: React.FC = () => {
               strokeLinejoin="round"
             />
           </svg>
-          <span className="text-slate-900 font-bold">{area.areaName1}</span>
+          <span className="text-slate-900 font-bold text-[16px]">
+            {area.areaName1}
+          </span>
         </nav>
       </div>
 
@@ -377,11 +382,19 @@ const ProductDetailView: React.FC = () => {
                   -{prices.discountPercent}%
                 </span>
                 <span className="text-slate-300 font-bold text-xs line-through">
-                  ${prices.original}
+                  $
+                  {prices.original.toLocaleString("vi-VN", {
+                    minimumFractionDigits: 2,
+                    maximumFractionDigits: 2,
+                  })}
                 </span>
               </div>
               <span className="text-[#EE0434] font-black text-2xl md:text-3xl">
-                ${prices.final}
+                $
+                {prices.final.toLocaleString("vi-VN", {
+                  minimumFractionDigits: 2,
+                  maximumFractionDigits: 2,
+                })}
               </span>
             </div>
             <button

+ 24 - 0
EsimLao/esim-vite/src/services/content/types.ts

@@ -60,3 +60,27 @@ export interface LoadFaqResponse {
   faqs: Faq[];
   pagination: Pagination;
 }
+
+export interface DeviceMeta {
+  brand: string;
+  deviceCount: number;
+  popularCount: number;
+  devices: DeviceInfo[];
+}
+
+export interface DeviceInfo {
+  id: number;
+  modelName: string;
+  category: string;
+  isPopular: boolean;
+  displayOrder: number;
+  modelNameEn: string;
+  modelNameLo: string;
+  notes: string;
+  notesEn: string;
+  notesLo: string;
+}
+
+export interface LoadDeviceMetaResponse {
+  brands: DeviceMeta[];
+}

+ 49 - 0
EsimLao/esim-vite/src/services/product/type.ts

@@ -83,3 +83,52 @@ export interface ConfirmOrderResponse {
   discountPercent: number;
   customerInfo: any;
 }
+
+export interface OrderHistory {
+  id: number;
+  customerId: number;
+  createdDate: string;
+  totalMoney: number;
+  discount: number;
+  paymentMoney: number;
+  paymentId: number;
+  status: number;
+  paymentStatus: number;
+  transactionId: string | null;
+  curency: string;
+  orderId: number;
+  orderDetailId: number;
+  paymentUrl: string | null;
+  discountPercent: number;
+  customerInfo: {
+    id: number;
+    surName: string | null;
+    lastName: string | null;
+    email: string | null;
+    phoneNumber: string | null;
+    createdDate: string | null;
+  };
+}
+
+export interface OrderDetail {
+  id: number;
+  orderDetailId: number;
+  qrcode: string;
+  qrcodeUrl: string;
+  iccid: string;
+  createdDate: string;
+  installationUrl: string;
+  packageName: string;
+  areaIconUrl: string;
+  packageTitle: string;
+  paymentMoney: string;
+  dayDuration: number;
+}
+
+export interface DataUsage {
+  remainData: number;
+  totalData: number;
+  expiredTime: string;
+  isUnlimited: number;
+  status: number;
+}