Просмотр исходного кода

Add QR code modal and refactor popup state

Introduces a new QRCodeModal component for displaying eSIM QR codes and updates the popup state management to use separate slices for notification, compatibility, and QR modals. Refactors related components to use the new state structure and updates button colors in OrderDetailView for consistency.
trunghieubui 3 недель назад
Родитель
Сommit
98823b8121

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

@@ -17,6 +17,7 @@ import Popup from "./components/Popup";
 import OrderHistoryView from "./pages/order-history/OrderHistoryView";
 import OrderHistoryView from "./pages/order-history/OrderHistoryView";
 import OrderDetailView from "./pages/order-detail/OrderDetailView";
 import OrderDetailView from "./pages/order-detail/OrderDetailView";
 import CompatibilityModal from "./components/CompatibilityModal";
 import CompatibilityModal from "./components/CompatibilityModal";
+import QRCodeModal from "./components/QRCodeModal";
 
 
 const App: React.FC = () => {
 const App: React.FC = () => {
   const location = useLocation();
   const location = useLocation();
@@ -38,6 +39,7 @@ const App: React.FC = () => {
 
 
       <Popup />
       <Popup />
       <CompatibilityModal />
       <CompatibilityModal />
+      <QRCodeModal />
 
 
       <div className={`flex flex-1 ${isAiView ? "overflow-hidden" : ""}`}>
       <div className={`flex flex-1 ${isAiView ? "overflow-hidden" : ""}`}>
         {isAiView && <Sidebar />}
         {isAiView && <Sidebar />}

+ 2 - 2
EsimLao/esim-vite/src/components/CompatibilityModal.tsx

@@ -7,7 +7,7 @@ import { contentApi } from "../apis/contentApi";
 import { DeviceMeta } from "../services/content/types";
 import { DeviceMeta } from "../services/content/types";
 
 
 const CompatibilityModal = () => {
 const CompatibilityModal = () => {
-  const popup = useAppSelector((state) => state.popup);
+  const popup = useAppSelector((state) => state.popup.compatibilityModal);
   const dispatch = useAppDispatch();
   const dispatch = useAppDispatch();
   const [brands, setBrands] = useState<DeviceMeta[] | []>([]);
   const [brands, setBrands] = useState<DeviceMeta[] | []>([]);
   const [deviceList, setDeviceList] = useState<Record<string, string[]>>([]);
   const [deviceList, setDeviceList] = useState<Record<string, string[]>>([]);
@@ -90,7 +90,7 @@ const CompatibilityModal = () => {
   //   },
   //   },
   // ];
   // ];
 
 
-  if (popup.isOpenCompatibilityModal === false) {
+  if (popup.isOpen === false) {
     return null;
     return null;
   }
   }
   return (
   return (

+ 1 - 1
EsimLao/esim-vite/src/components/Popup.tsx

@@ -4,7 +4,7 @@ import { closePopup } from "../features/popup/popupSlice";
 import { useNavigate } from "react-router-dom";
 import { useNavigate } from "react-router-dom";
 
 
 const Popup = () => {
 const Popup = () => {
-  const popup = useAppSelector((state) => state.popup);
+  const popup = useAppSelector((state) => state.popup.notification);
   const dispatch = useAppDispatch();
   const dispatch = useAppDispatch();
   const navigate = useNavigate();
   const navigate = useNavigate();
   if (!popup.isOpen) return null;
   if (!popup.isOpen) return null;

+ 56 - 0
EsimLao/esim-vite/src/components/QRCodeModal.tsx

@@ -0,0 +1,56 @@
+import React from "react";
+import { useAppDispatch, useAppSelector } from "../hooks/useRedux";
+import { closeQRModal } from "../features/popup/popupSlice";
+
+const QRCodeModal = () => {
+  const popup = useAppSelector((state) => state.popup.qrModal);
+  const dispatch = useAppDispatch();
+  if (popup.isOpen === false) {
+    return null;
+  }
+
+  return (
+    <div className="fixed inset-0 z-[150] flex items-center justify-center p-4 animate-in fade-in duration-200">
+      {/* Backdrop */}
+      <div
+        className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"
+        onClick={() => {
+          dispatch(closeQRModal());
+        }}
+      ></div>
+
+      {/* Modal Container */}
+      <div className="relative bg-white rounded-[32px] w-full max-w-[340px] shadow-2xl transform transition-all animate-in zoom-in-95 duration-200 overflow-hidden flex flex-col">
+        <div className="p-8 pb-6 text-center">
+          <h3 className="text-xl md:text-2xl font-black text-slate-900 mb-8 tracking-tight">
+            Mã QR eSIM
+          </h3>
+
+          <div className="bg-slate-50 p-4 rounded-[24px] mb-8 border border-slate-100">
+            <div className="aspect-square w-full bg-white rounded-xl flex items-center justify-center p-3 shadow-sm">
+              {/* Placeholder QR Code for demo purposes */}
+              <img
+                src={`https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(
+                  popup.qrData
+                )}&margin=10`}
+                alt="eSIM QR Code"
+                className="w-full h-full object-contain mix-blend-multiply"
+              />
+            </div>
+          </div>
+
+          <button
+            onClick={() => {
+              dispatch(closeQRModal());
+            }}
+            className="w-full py-3.5 rounded-xl font-bold text-white bg-[#EE0434] hover:bg-[#EE0434] active:scale-[0.98] transition-all shadow-lg shadow-cyan-100/50 text-base md:text-lg"
+          >
+            Đóng
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default QRCodeModal;

+ 39 - 20
EsimLao/esim-vite/src/features/popup/popupSlice.ts

@@ -4,38 +4,55 @@ import loadingSlice from "../loading/loadingSlice";
 const popupSlice = createSlice({
 const popupSlice = createSlice({
   name: "popup",
   name: "popup",
   initialState: {
   initialState: {
-    isOpen: false,
-    isOpenCompatibilityModal: false,
-    isSuccess: true,
-    title: "Notification",
-    message: "",
-    buttonText: "Close",
-    hasRightButton: false,
-    rightButtonText: "Confirm",
-    rightButtonAction: "",
+    notification: {
+      isOpen: false,
+      isSuccess: true,
+      title: "Notification",
+      message: "",
+      buttonText: "Close",
+      hasRightButton: false,
+      rightButtonText: "Confirm",
+      rightButtonAction: "",
+    },
+    compatibilityModal: {
+      isOpen: false,
+    },
+    qrModal: {
+      isOpen: false,
+      qrData: "",
+    },
   },
   },
   reducers: {
   reducers: {
     openPopup: (state, action: PayloadAction<any>) => {
     openPopup: (state, action: PayloadAction<any>) => {
       return {
       return {
         ...state,
         ...state,
-        isOpen: true,
-        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 ?? "",
+        notification: {
+          isOpen: true,
+          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) => {
     closePopup: (state) => {
-      state.isOpen = false;
+      state.notification.isOpen = false;
     },
     },
     openCompatibilityModal: (state) => {
     openCompatibilityModal: (state) => {
-      state.isOpenCompatibilityModal = true;
+      state.compatibilityModal.isOpen = true;
     },
     },
     closeCompatibilityModal: (state) => {
     closeCompatibilityModal: (state) => {
-      state.isOpenCompatibilityModal = false;
+      state.compatibilityModal.isOpen = false;
+    },
+    openQRModal: (state, action: PayloadAction<string>) => {
+      state.qrModal.isOpen = true;
+      state.qrModal.qrData = action.payload;
+    },
+    closeQRModal: (state) => {
+      state.qrModal.isOpen = false;
     },
     },
   },
   },
 });
 });
@@ -45,5 +62,7 @@ export const {
   closePopup,
   closePopup,
   openCompatibilityModal,
   openCompatibilityModal,
   closeCompatibilityModal,
   closeCompatibilityModal,
+  openQRModal,
+  closeQRModal,
 } = popupSlice.actions;
 } = popupSlice.actions;
 export default popupSlice.reducer;
 export default popupSlice.reducer;

+ 11 - 5
EsimLao/esim-vite/src/pages/order-detail/OrderDetailView.tsx

@@ -9,6 +9,7 @@ import { useAppDispatch } from "../../hooks/useRedux";
 import { useMutation } from "@tanstack/react-query";
 import { useMutation } from "@tanstack/react-query";
 import React, { useState, useEffect } from "react";
 import React, { useState, useEffect } from "react";
 import { useLocation, useNavigate } from "react-router-dom";
 import { useLocation, useNavigate } from "react-router-dom";
+import { openQRModal } from "../../features/popup/popupSlice";
 
 
 const OrderDetailView = () => {
 const OrderDetailView = () => {
   const location = useLocation();
   const location = useLocation();
@@ -270,8 +271,8 @@ const OrderDetailView = () => {
                 onClick={() => handleTabChange("detail")}
                 onClick={() => handleTabChange("detail")}
                 className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
                 className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
                   activeTab === "detail"
                   activeTab === "detail"
-                    ? "bg-[#00b0f0] text-white"
-                    : "text-[#00b0f0] hover:bg-[#00b0f0]/10"
+                    ? "bg-[#EE0434] text-white"
+                    : "text-[#EE0434] hover:bg-[#EE0434]/10"
                 }`}
                 }`}
               >
               >
                 Detail
                 Detail
@@ -280,8 +281,8 @@ const OrderDetailView = () => {
                 onClick={() => handleTabChange("manage")}
                 onClick={() => handleTabChange("manage")}
                 className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
                 className={`px-6 py-2 rounded-lg font-bold text-sm transition-colors text-[16px] ${
                   activeTab === "manage"
                   activeTab === "manage"
-                    ? "bg-[#00b0f0] text-white"
-                    : "text-[#00b0f0] hover:bg-[#00b0f0]/10"
+                    ? "bg-[#EE0434] text-white"
+                    : "text-[#EE0434] hover:bg-[#EE0434]/10"
                 }`}
                 }`}
               >
               >
                 Manage
                 Manage
@@ -448,7 +449,12 @@ const OrderDetailView = () => {
                     </div>
                     </div>
 
 
                     <div className="mt-4 md:mt-0 flex flex-col items-end">
                     <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">
+                      <button
+                        className="flex items-center text-[#EE0434] font-bold text-sm mb-2 hover:underline"
+                        onClick={() => {
+                          dispatch(openQRModal(pkg.installationUrl));
+                        }}
+                      >
                         <svg
                         <svg
                           className="w-4 h-4 mr-1"
                           className="w-4 h-4 mr-1"
                           fill="none"
                           fill="none"