Bläddra i källkod

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 veckor sedan
förälder
incheckning
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 OrderDetailView from "./pages/order-detail/OrderDetailView";
 import CompatibilityModal from "./components/CompatibilityModal";
+import QRCodeModal from "./components/QRCodeModal";
 
 const App: React.FC = () => {
   const location = useLocation();
@@ -38,6 +39,7 @@ const App: React.FC = () => {
 
       <Popup />
       <CompatibilityModal />
+      <QRCodeModal />
 
       <div className={`flex flex-1 ${isAiView ? "overflow-hidden" : ""}`}>
         {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";
 
 const CompatibilityModal = () => {
-  const popup = useAppSelector((state) => state.popup);
+  const popup = useAppSelector((state) => state.popup.compatibilityModal);
   const dispatch = useAppDispatch();
   const [brands, setBrands] = useState<DeviceMeta[] | []>([]);
   const [deviceList, setDeviceList] = useState<Record<string, string[]>>([]);
@@ -90,7 +90,7 @@ const CompatibilityModal = () => {
   //   },
   // ];
 
-  if (popup.isOpenCompatibilityModal === false) {
+  if (popup.isOpen === false) {
     return null;
   }
   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";
 
 const Popup = () => {
-  const popup = useAppSelector((state) => state.popup);
+  const popup = useAppSelector((state) => state.popup.notification);
   const dispatch = useAppDispatch();
   const navigate = useNavigate();
   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({
   name: "popup",
   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: {
     openPopup: (state, action: PayloadAction<any>) => {
       return {
         ...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) => {
-      state.isOpen = false;
+      state.notification.isOpen = false;
     },
     openCompatibilityModal: (state) => {
-      state.isOpenCompatibilityModal = true;
+      state.compatibilityModal.isOpen = true;
     },
     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,
   openCompatibilityModal,
   closeCompatibilityModal,
+  openQRModal,
+  closeQRModal,
 } = popupSlice.actions;
 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 React, { useState, useEffect } from "react";
 import { useLocation, useNavigate } from "react-router-dom";
+import { openQRModal } from "../../features/popup/popupSlice";
 
 const OrderDetailView = () => {
   const location = useLocation();
@@ -270,8 +271,8 @@ const OrderDetailView = () => {
                 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"
+                    ? "bg-[#EE0434] text-white"
+                    : "text-[#EE0434] hover:bg-[#EE0434]/10"
                 }`}
               >
                 Detail
@@ -280,8 +281,8 @@ const OrderDetailView = () => {
                 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"
+                    ? "bg-[#EE0434] text-white"
+                    : "text-[#EE0434] hover:bg-[#EE0434]/10"
                 }`}
               >
                 Manage
@@ -448,7 +449,12 @@ const OrderDetailView = () => {
                     </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">
+                      <button
+                        className="flex items-center text-[#EE0434] font-bold text-sm mb-2 hover:underline"
+                        onClick={() => {
+                          dispatch(openQRModal(pkg.installationUrl));
+                        }}
+                      >
                         <svg
                           className="w-4 h-4 mr-1"
                           fill="none"