Sfoglia il codice sorgente

Add architecture diagrams and update UI components

Added new draw.io architecture diagrams for documentation. Updated multiple React components, API logic, i18n files, and styles for improved UI/UX and functionality. Introduced ScrollToTop component and made various enhancements across product, modal, and view files.
hieubt 2 settimane fa
parent
commit
74e0c58434
27 ha cambiato i file con 934 aggiunte e 230 eliminazioni
  1. 276 0
      EsimLao/docs/.$skyhub.drawio.bkp
  2. 276 0
      EsimLao/docs/skyhub.drawio
  3. 6 3
      EsimLao/esim-vite/src/App.tsx
  4. 2 2
      EsimLao/esim-vite/src/apis/productApi.ts
  5. 18 17
      EsimLao/esim-vite/src/components/CompatibilityModal.tsx
  6. 1 0
      EsimLao/esim-vite/src/components/Footer.tsx
  7. 13 17
      EsimLao/esim-vite/src/components/Header.tsx
  8. 3 3
      EsimLao/esim-vite/src/components/Popup.tsx
  9. 7 5
      EsimLao/esim-vite/src/components/ProductCard.tsx
  10. 1 1
      EsimLao/esim-vite/src/components/ProductInfoModal.tsx
  11. 1 3
      EsimLao/esim-vite/src/components/QRCodeModal.tsx
  12. 18 0
      EsimLao/esim-vite/src/components/ScrollToTop.tsx
  13. 20 4
      EsimLao/esim-vite/src/i18n/locales/en.json
  14. 20 4
      EsimLao/esim-vite/src/i18n/locales/vi.json
  15. 4 4
      EsimLao/esim-vite/src/index.css
  16. 36 2
      EsimLao/esim-vite/src/logic/loigicUtils.ts
  17. 1 1
      EsimLao/esim-vite/src/pages/buy-sim/BuySimView.tsx
  18. 29 13
      EsimLao/esim-vite/src/pages/checkout/CheckoutView.tsx
  19. 17 10
      EsimLao/esim-vite/src/pages/home/HomeView.tsx
  20. 1 1
      EsimLao/esim-vite/src/pages/home/components/HomeBanner.tsx
  21. 9 32
      EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx
  22. 11 7
      EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx
  23. 2 1
      EsimLao/esim-vite/src/pages/home/components/HomeTestimonial.tsx
  24. 101 59
      EsimLao/esim-vite/src/pages/order-detail/OrderDetailView.tsx
  25. 10 8
      EsimLao/esim-vite/src/pages/order-history/OrderHistoryView.tsx
  26. 47 33
      EsimLao/esim-vite/src/pages/product-detail/ProductDetailView.tsx
  27. 4 0
      EsimLao/esim-vite/src/services/product/type.ts

+ 276 - 0
EsimLao/docs/.$skyhub.drawio.bkp

@@ -0,0 +1,276 @@
+<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.3.0 Chrome/140.0.7339.249 Electron/38.7.2 Safari/537.36" version="29.3.0">
+  <diagram id="kgpKYQtTHZ0yAKxKKP6v" name="Page-1">
+    <mxGraphModel dx="2483" dy="2183" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
+      <root>
+        <mxCell id="0" />
+        <mxCell id="1" parent="0" />
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="240" width="200" x="-40" y="80" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-2" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;B2C&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="30" y="50" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-3" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="Website / Mini app" vertex="1">
+          <mxGeometry height="49" width="59" x="31" y="110" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-5" parent="1" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;align=center;strokeColor=none;fillColor=#00BEF2;shape=mxgraph.azure.mobile_services;pointerEvents=1;" value="Application B2C" vertex="1">
+          <mxGeometry height="50" width="32.5" x="43.25" y="220" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-6" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="340" width="200" x="-40.5" y="390" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-7" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;B2B&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="29.5" y="360" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-8" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="Website Portal" vertex="1">
+          <mxGeometry height="49" width="59" x="30.5" y="420" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-9" parent="1" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;align=center;strokeColor=none;fillColor=#00BEF2;shape=mxgraph.azure.mobile_services;pointerEvents=1;" value="Application B2B" vertex="1">
+          <mxGeometry height="50" width="32.5" x="42.75" y="530" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-10" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="130" width="200" x="-41" y="840" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-11" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;CMS / CSKH&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="121" x="-1.5" y="810" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-12" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="Website CMS" vertex="1">
+          <mxGeometry height="49" width="59" x="30" y="870" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-15" parent="1" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=light-dark(#db1a1a, #ededed);" value="" vertex="1">
+          <mxGeometry height="890" width="930" x="280" y="80" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-16" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;SKYHUB&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="655" y="50" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-17" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.classic_load_balancer;fillColor=#F58534;gradientColor=none;" value="KONG/NGINX" vertex="1">
+          <mxGeometry height="72" width="69" x="370" y="478" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-18" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-1" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="500" y="490" as="sourcePoint" />
+            <mxPoint x="550" y="440" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-19" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-6" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="380" y="666" as="sourcePoint" />
+            <mxPoint x="220" y="400" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-20" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-10" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="320" y="470" as="sourcePoint" />
+            <mxPoint x="140" y="654" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-25" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="KEYCLOAK" vertex="1">
+          <mxGeometry height="63" width="60" x="374.5" y="168.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-26" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-25" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=light-dark(#CC6600,#EDEDED);" target="uRcrelpJeVWRNYr8ECgl-1" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="570" y="684" as="sourcePoint" />
+            <mxPoint x="300" y="370" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-27" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-25" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=light-dark(#CC6600,#EDEDED);" target="uRcrelpJeVWRNYr8ECgl-6" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="670" y="260" as="sourcePoint" />
+            <mxPoint x="400" y="260" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-28" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-25" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=light-dark(#CC6600,#EDEDED);" target="uRcrelpJeVWRNYr8ECgl-10" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="570" y="430" as="sourcePoint" />
+            <mxPoint x="300" y="740" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-29" parent="1" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;prIcon=api" value="APIs" vertex="1">
+          <mxGeometry height="48" width="50" x="34" y="650" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-31" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="333.5" width="260" x="560" y="306.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-33" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="ESIM" vertex="1">
+          <mxGeometry height="63" width="60" x="595" y="327" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-34" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="120" width="260" x="560" y="150" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-35" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="PARTNER ADAP 1" vertex="1">
+          <mxGeometry height="63" width="60" x="600" y="168.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-36" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;PARTNER GW&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="130" x="560" y="119.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-37" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="PARTNER ADAP 2" vertex="1">
+          <mxGeometry height="63" width="60" x="720" y="168.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-39" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-33" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=#0000FF;" target="uRcrelpJeVWRNYr8ECgl-34" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="580" y="420" as="sourcePoint" />
+            <mxPoint x="630" y="370" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-40" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="120" width="380" x="555" y="-130" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-41" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="JOY TELECOM" vertex="1">
+          <mxGeometry height="63" width="60" x="595" y="-111.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-44" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;ESIM PARTNERS&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="160" x="775" y="-160" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-45" edge="1" parent="1" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=#0000FF;" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="749.5" y="149.5" as="sourcePoint" />
+            <mxPoint x="750" y="-9" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-48" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="PORTAL" vertex="1">
+          <mxGeometry height="63" width="60" x="595" y="430" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-49" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="B2C APIS" vertex="1">
+          <mxGeometry height="63" width="60" x="720" y="327" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-50" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="B2B APIS" vertex="1">
+          <mxGeometry height="63" width="60" x="720" y="430" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-52" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;CORE&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="760" y="640" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-53" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="333.5" width="240" x="900" y="308.25" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-56" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="OnePay" vertex="1">
+          <mxGeometry height="63" width="60" x="1040" y="328.75" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-59" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;PAYMENT&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="90" x="1050" y="640" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-60" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="UniPay" vertex="1">
+          <mxGeometry height="63" width="60" x="1040" y="430" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-61" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="Momo" vertex="1">
+          <mxGeometry height="63" width="60" x="1040" y="540" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-62" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-31" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.998;exitY=0.467;exitDx=0;exitDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitPerimeter=0;" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="620" y="347" as="sourcePoint" />
+            <mxPoint x="920" y="461.5" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-63" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="890" width="200" x="1320" y="80" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-64" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;PAYMENTS&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="1390" y="50" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-67" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.dbes_database_expert_service;" value="&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;ONEPAY&lt;/div&gt;" vertex="1">
+          <mxGeometry height="47.400000000000006" width="47.400000000000006" x="1396.3" y="110" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-68" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.dbes_database_expert_service;" value="&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;MOMO&lt;/div&gt;" vertex="1">
+          <mxGeometry height="47.400000000000006" width="47.400000000000006" x="1396.3" y="210" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-69" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.dbes_database_expert_service;" value="&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;UNIPAY&lt;/div&gt;" vertex="1">
+          <mxGeometry height="47.400000000000006" width="47.400000000000006" x="1396.3" y="308.25" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-72" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=-0.007;entryY=0.622;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#99004D;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-31" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="531" y="524.44" as="sourcePoint" />
+            <mxPoint x="620" y="524.8800000000001" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-74" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-75" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-56" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="850" y="450" as="sourcePoint" />
+            <mxPoint x="951" y="450" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-75" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.vpc_nat_gateway;fillColor=#F58536;gradientColor=none;" value="PAYMENT GW" vertex="1">
+          <mxGeometry height="72" width="69" x="920" y="425.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-76" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-75" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-60" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="990" y="552" as="sourcePoint" />
+            <mxPoint x="1041" y="450" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-77" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-75" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-61" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="990" y="460" as="sourcePoint" />
+            <mxPoint x="1031" y="450" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-80" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-53" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.009;entryY=0.443;entryDx=0;entryDy=0;entryPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-63" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="1210" y="440" as="sourcePoint" />
+            <mxPoint x="1260" y="390" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-81" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-63" style="endArrow=classic;html=1;rounded=0;exitX=-0.016;exitY=0.962;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;strokeWidth=3;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-17" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <Array as="points">
+              <mxPoint x="230" y="936" />
+              <mxPoint x="230" y="514" />
+            </Array>
+            <mxPoint x="940" y="871" as="sourcePoint" />
+            <mxPoint x="1122" y="870" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-92" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="140" width="580" x="560" y="710" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-93" parent="1" style="image;html=1;image=img/lib/clip_art/computers/Database_128x128.png" value="&lt;div style=&quot;text-align: left;&quot;&gt;&lt;font face=&quot;Times New Roman, Times New Roman_EmbeddedFont, Times New Roman_MSFontService, serif&quot;&gt;&lt;span style=&quot;font-size: 16px; font-variant-ligatures: none; text-wrap-mode: wrap;&quot;&gt;&lt;b&gt;ORACLE&lt;/b&gt;&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;" vertex="1">
+          <mxGeometry height="80" width="80" x="595" y="739" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-94" parent="1" style="image;sketch=0;aspect=fixed;html=1;points=[];align=center;fontSize=12;image=img/lib/mscae/Cache_Redis_Product.svg;" value="&lt;font style=&quot;font-size: 16px;&quot;&gt;Redis&lt;/font&gt;" vertex="1">
+          <mxGeometry height="60" width="71.43" x="900" y="750" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-95" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.kafka;" value="&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;Kafka&lt;/font&gt;" vertex="1">
+          <mxGeometry height="61" width="82.35" x="737.6500000000001" y="748.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-96" parent="1" style="image;html=1;image=img/lib/clip_art/computers/Database_Move_Stack_128x128.png" value="&lt;span data-contrast=&quot;auto&quot; class=&quot;TextRun MacChromeBold SCXW86909208 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; -webkit-font-smoothing: antialiased; text-align: left; text-wrap-mode: wrap; font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif; font-weight: bold; font-variant-ligatures: none !important;&quot;&gt;PostgreSQL&lt;/span&gt;&lt;span class=&quot;EOP SCXW86909208 BCX0&quot; data-ccp-props=&quot;{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:120,&amp;quot;335559740&amp;quot;:360}&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; text-wrap-mode: wrap; font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;" vertex="1">
+          <mxGeometry height="80" width="80" x="1030" y="729.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-97" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-92" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.224;exitY=-0.001;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-31" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="610" y="510" as="sourcePoint" />
+            <mxPoint x="660" y="460" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-98" edge="1" parent="1" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.224;exitY=-0.001;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="1019.6800000000001" y="709" as="sourcePoint" />
+            <mxPoint x="1019.6800000000001" y="639" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-99" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-92" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=3;entryPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-25" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <Array as="points">
+              <mxPoint x="520" y="780" />
+              <mxPoint x="520" y="200" />
+            </Array>
+            <mxPoint x="690" y="730" as="sourcePoint" />
+            <mxPoint x="690" y="660" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-100" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="130" width="200" x="-41" y="-140" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-101" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 22px;&quot;&gt;&lt;span data-contrast=&quot;auto&quot; class=&quot;TextRun MacChromeBold SCXW168624917 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; -webkit-font-smoothing: antialiased; text-align: left; background-color: rgb(255, 255, 255); line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif; font-weight: bold; font-variant-ligatures: none !important;&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW168624917 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text;&quot;&gt;Monitoring&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP SCXW168624917 BCX0&quot; data-ccp-props=&quot;{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:120,&amp;quot;335559740&amp;quot;:360}&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; background-color: rgb(255, 255, 255); line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="121" x="-1.5" y="-170" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-102" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="&lt;span data-contrast=&quot;auto&quot; class=&quot;TextRun SCXW82762732 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; text-wrap-mode: wrap; background-color: rgb(255, 255, 255); font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif; font-variant-ligatures: none !important;&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW82762732 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text;&quot;&gt;Prometheus &amp;amp; Grafana&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP SCXW82762732 BCX0&quot; data-ccp-props=&quot;{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:120,&amp;quot;335559740&amp;quot;:360}&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; text-wrap-mode: wrap; background-color: rgb(255, 255, 255); font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;" vertex="1">
+          <mxGeometry height="49" width="59" x="30" y="-110" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-103" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.elasticsearch;" value="&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot; style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;Elasticsearch&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;" vertex="1">
+          <mxGeometry height="50.099999999999994" width="45.9" x="602.05" y="550" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-104" parent="1" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" value="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;Công nghệ sử dụng&lt;/h1&gt;&lt;div&gt;Front end:&lt;/div&gt;&lt;div&gt;+ Website/MiniApp: ReactJS vite tool&lt;/div&gt;&lt;div&gt;+ Application: Flutter&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Backend:&lt;/div&gt;&lt;div&gt;+ 100% sử dụng Dotnet core 7 API&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Database:&lt;/div&gt;&lt;div&gt;+ Oracle&lt;/div&gt;&lt;div&gt;+ PostgresSQL&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Cache:&lt;/div&gt;&lt;div&gt;+ Redis&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Queue:&lt;/div&gt;&lt;div&gt;+ Kafka&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Identify service:&lt;/div&gt;&lt;div&gt;+ Keycloak&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Api Gateway:&lt;/div&gt;&lt;div&gt;+ KONG&amp;nbsp;&lt;/div&gt;" vertex="1">
+          <mxGeometry height="380" width="490" x="1030" y="-329" as="geometry" />
+        </mxCell>
+      </root>
+    </mxGraphModel>
+  </diagram>
+</mxfile>

+ 276 - 0
EsimLao/docs/skyhub.drawio

@@ -0,0 +1,276 @@
+<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.3.0 Chrome/140.0.7339.249 Electron/38.7.2 Safari/537.36" version="29.3.0">
+  <diagram id="kgpKYQtTHZ0yAKxKKP6v" name="Page-1">
+    <mxGraphModel dx="2483" dy="2183" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
+      <root>
+        <mxCell id="0" />
+        <mxCell id="1" parent="0" />
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="240" width="200" x="-40" y="80" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-2" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;B2C&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="30" y="50" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-3" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="Website / Mini app" vertex="1">
+          <mxGeometry height="49" width="59" x="31" y="110" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-5" parent="1" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;align=center;strokeColor=none;fillColor=#00BEF2;shape=mxgraph.azure.mobile_services;pointerEvents=1;" value="Application B2C" vertex="1">
+          <mxGeometry height="50" width="32.5" x="43.25" y="220" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-6" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="340" width="200" x="-40.5" y="390" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-7" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;B2B&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="29.5" y="360" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-8" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="Website Portal" vertex="1">
+          <mxGeometry height="49" width="59" x="30.5" y="420" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-9" parent="1" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;align=center;strokeColor=none;fillColor=#00BEF2;shape=mxgraph.azure.mobile_services;pointerEvents=1;" value="Application B2B" vertex="1">
+          <mxGeometry height="50" width="32.5" x="42.75" y="530" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-10" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="130" width="200" x="-41" y="840" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-11" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;CMS / CSKH&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="121" x="-1.5" y="810" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-12" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="Website CMS" vertex="1">
+          <mxGeometry height="49" width="59" x="30" y="870" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-15" parent="1" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=light-dark(#db1a1a, #ededed);" value="" vertex="1">
+          <mxGeometry height="890" width="930" x="280" y="80" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-16" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;SKYHUB&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="655" y="50" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-17" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.classic_load_balancer;fillColor=#F58534;gradientColor=none;" value="KONG/NGINX" vertex="1">
+          <mxGeometry height="72" width="69" x="370" y="478" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-18" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-1" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="500" y="490" as="sourcePoint" />
+            <mxPoint x="550" y="440" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-19" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-6" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="380" y="666" as="sourcePoint" />
+            <mxPoint x="220" y="400" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-20" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-10" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="320" y="470" as="sourcePoint" />
+            <mxPoint x="140" y="654" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-25" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="KEYCLOAK" vertex="1">
+          <mxGeometry height="63" width="60" x="374.5" y="168.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-26" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-25" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=light-dark(#CC6600,#EDEDED);" target="uRcrelpJeVWRNYr8ECgl-1" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="570" y="684" as="sourcePoint" />
+            <mxPoint x="300" y="370" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-27" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-25" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=light-dark(#CC6600,#EDEDED);" target="uRcrelpJeVWRNYr8ECgl-6" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="670" y="260" as="sourcePoint" />
+            <mxPoint x="400" y="260" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-28" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-25" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=light-dark(#CC6600,#EDEDED);" target="uRcrelpJeVWRNYr8ECgl-10" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="570" y="430" as="sourcePoint" />
+            <mxPoint x="300" y="740" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-29" parent="1" style="aspect=fixed;sketch=0;html=1;dashed=0;whitespace=wrap;verticalLabelPosition=bottom;verticalAlign=top;fillColor=#2875E2;strokeColor=#ffffff;points=[[0.005,0.63,0],[0.1,0.2,0],[0.9,0.2,0],[0.5,0,0],[0.995,0.63,0],[0.72,0.99,0],[0.5,1,0],[0.28,0.99,0]];shape=mxgraph.kubernetes.icon2;prIcon=api" value="APIs" vertex="1">
+          <mxGeometry height="48" width="50" x="34" y="650" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-31" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="333.5" width="260" x="560" y="306.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-33" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="ESIM" vertex="1">
+          <mxGeometry height="63" width="60" x="595" y="327" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-34" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="120" width="260" x="560" y="150" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-35" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="PARTNER ADAP 1" vertex="1">
+          <mxGeometry height="63" width="60" x="600" y="168.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-36" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;PARTNER GW&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="130" x="560" y="119.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-37" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="PARTNER ADAP 2" vertex="1">
+          <mxGeometry height="63" width="60" x="720" y="168.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-39" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-33" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=#0000FF;" target="uRcrelpJeVWRNYr8ECgl-34" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="580" y="420" as="sourcePoint" />
+            <mxPoint x="630" y="370" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-40" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="120" width="380" x="555" y="-130" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-41" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="JOY TELECOM" vertex="1">
+          <mxGeometry height="63" width="60" x="595" y="-111.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-44" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;ESIM PARTNERS&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="160" x="775" y="-160" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-45" edge="1" parent="1" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;strokeColor=#0000FF;" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="749.5" y="149.5" as="sourcePoint" />
+            <mxPoint x="750" y="-9" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-48" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="PORTAL" vertex="1">
+          <mxGeometry height="63" width="60" x="595" y="430" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-49" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="B2C APIS" vertex="1">
+          <mxGeometry height="63" width="60" x="720" y="327" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-50" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="B2B APIS" vertex="1">
+          <mxGeometry height="63" width="60" x="720" y="430" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-52" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;CORE&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="760" y="640" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-53" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="333.5" width="240" x="900" y="308.25" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-56" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="OnePay" vertex="1">
+          <mxGeometry height="63" width="60" x="1040" y="328.75" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-59" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;PAYMENT&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="90" x="1050" y="640" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-60" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="UniPay" vertex="1">
+          <mxGeometry height="63" width="60" x="1040" y="430" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-61" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.worker;fillColor=#D9A741;gradientColor=none;" value="Momo" vertex="1">
+          <mxGeometry height="63" width="60" x="1040" y="540" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-62" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-31" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.998;exitY=0.467;exitDx=0;exitDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitPerimeter=0;" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="620" y="347" as="sourcePoint" />
+            <mxPoint x="920" y="461.5" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-63" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="890" width="200" x="1320" y="80" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-64" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 18px;&quot;&gt;PAYMENTS&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="60" x="1390" y="50" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-67" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.dbes_database_expert_service;" value="&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;ONEPAY&lt;/div&gt;" vertex="1">
+          <mxGeometry height="47.400000000000006" width="47.400000000000006" x="1396.3" y="110" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-68" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.dbes_database_expert_service;" value="&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;MOMO&lt;/div&gt;" vertex="1">
+          <mxGeometry height="47.400000000000006" width="47.400000000000006" x="1396.3" y="210" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-69" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.dbes_database_expert_service;" value="&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;UNIPAY&lt;/div&gt;" vertex="1">
+          <mxGeometry height="47.400000000000006" width="47.400000000000006" x="1396.3" y="308.25" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-72" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-17" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=-0.007;entryY=0.622;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#99004D;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-31" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="531" y="524.44" as="sourcePoint" />
+            <mxPoint x="620" y="524.8800000000001" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-74" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-75" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-56" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="850" y="450" as="sourcePoint" />
+            <mxPoint x="951" y="450" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-75" parent="1" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.vpc_nat_gateway;fillColor=#F58536;gradientColor=none;" value="PAYMENT GW" vertex="1">
+          <mxGeometry height="72" width="69" x="920" y="425.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-76" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-75" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-60" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="990" y="552" as="sourcePoint" />
+            <mxPoint x="1041" y="450" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-77" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-75" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#FF00FF;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-61" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="990" y="460" as="sourcePoint" />
+            <mxPoint x="1031" y="450" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-80" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-53" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.009;entryY=0.443;entryDx=0;entryDy=0;entryPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-63" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="1210" y="440" as="sourcePoint" />
+            <mxPoint x="1260" y="390" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-81" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-63" style="endArrow=classic;html=1;rounded=0;exitX=-0.016;exitY=0.962;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;strokeWidth=3;exitPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-17" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <Array as="points">
+              <mxPoint x="230" y="936" />
+              <mxPoint x="230" y="514" />
+            </Array>
+            <mxPoint x="940" y="871" as="sourcePoint" />
+            <mxPoint x="1122" y="870" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-92" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="140" width="580" x="560" y="710" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-93" parent="1" style="image;html=1;image=img/lib/clip_art/computers/Database_128x128.png" value="&lt;div style=&quot;text-align: left;&quot;&gt;&lt;font face=&quot;Times New Roman, Times New Roman_EmbeddedFont, Times New Roman_MSFontService, serif&quot;&gt;&lt;span style=&quot;font-size: 16px; font-variant-ligatures: none; text-wrap-mode: wrap;&quot;&gt;&lt;b&gt;ORACLE&lt;/b&gt;&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;" vertex="1">
+          <mxGeometry height="80" width="80" x="595" y="739" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-94" parent="1" style="image;sketch=0;aspect=fixed;html=1;points=[];align=center;fontSize=12;image=img/lib/mscae/Cache_Redis_Product.svg;" value="&lt;font style=&quot;font-size: 16px;&quot;&gt;Redis&lt;/font&gt;" vertex="1">
+          <mxGeometry height="60" width="71.43" x="900" y="750" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-95" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.kafka;" value="&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;Kafka&lt;/font&gt;" vertex="1">
+          <mxGeometry height="61" width="82.35" x="737.6500000000001" y="748.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-96" parent="1" style="image;html=1;image=img/lib/clip_art/computers/Database_Move_Stack_128x128.png" value="&lt;span data-contrast=&quot;auto&quot; class=&quot;TextRun MacChromeBold SCXW86909208 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; -webkit-font-smoothing: antialiased; text-align: left; text-wrap-mode: wrap; font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif; font-weight: bold; font-variant-ligatures: none !important;&quot;&gt;PostgreSQL&lt;/span&gt;&lt;span class=&quot;EOP SCXW86909208 BCX0&quot; data-ccp-props=&quot;{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:120,&amp;quot;335559740&amp;quot;:360}&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; text-wrap-mode: wrap; font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;" vertex="1">
+          <mxGeometry height="80" width="80" x="1030" y="729.5" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-97" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-92" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.224;exitY=-0.001;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" target="uRcrelpJeVWRNYr8ECgl-31" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="610" y="510" as="sourcePoint" />
+            <mxPoint x="660" y="460" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-98" edge="1" parent="1" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.224;exitY=-0.001;exitDx=0;exitDy=0;exitPerimeter=0;strokeWidth=3;" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <mxPoint x="1019.6800000000001" y="709" as="sourcePoint" />
+            <mxPoint x="1019.6800000000001" y="639" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-99" edge="1" parent="1" source="uRcrelpJeVWRNYr8ECgl-92" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=3;entryPerimeter=0;" target="uRcrelpJeVWRNYr8ECgl-25" value="">
+          <mxGeometry height="50" relative="1" width="50" as="geometry">
+            <Array as="points">
+              <mxPoint x="520" y="780" />
+              <mxPoint x="520" y="200" />
+            </Array>
+            <mxPoint x="690" y="730" as="sourcePoint" />
+            <mxPoint x="690" y="660" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-100" parent="1" style="rounded=0;whiteSpace=wrap;html=1;" value="" vertex="1">
+          <mxGeometry height="130" width="200" x="-41" y="-140" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-101" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;font style=&quot;font-size: 22px;&quot;&gt;&lt;span data-contrast=&quot;auto&quot; class=&quot;TextRun MacChromeBold SCXW168624917 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; -webkit-font-smoothing: antialiased; text-align: left; background-color: rgb(255, 255, 255); line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif; font-weight: bold; font-variant-ligatures: none !important;&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW168624917 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text;&quot;&gt;Monitoring&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP SCXW168624917 BCX0&quot; data-ccp-props=&quot;{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:120,&amp;quot;335559740&amp;quot;:360}&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; background-color: rgb(255, 255, 255); line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/font&gt;" vertex="1">
+          <mxGeometry height="30" width="121" x="-1.5" y="-170" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-102" parent="1" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#505050;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;align=center;shape=mxgraph.office.concepts.website;" value="&lt;span data-contrast=&quot;auto&quot; class=&quot;TextRun SCXW82762732 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; text-wrap-mode: wrap; background-color: rgb(255, 255, 255); font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif; font-variant-ligatures: none !important;&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW82762732 BCX0&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text;&quot;&gt;Prometheus &amp;amp; Grafana&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP SCXW82762732 BCX0&quot; data-ccp-props=&quot;{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:120,&amp;quot;335559740&amp;quot;:360}&quot; style=&quot;-webkit-user-drag: none; -webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; user-select: text; text-align: left; text-wrap-mode: wrap; background-color: rgb(255, 255, 255); font-size: 12pt; line-height: 28.5px; font-family: &amp;quot;Times New Roman&amp;quot;, &amp;quot;Times New Roman_EmbeddedFont&amp;quot;, &amp;quot;Times New Roman_MSFontService&amp;quot;, serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;" vertex="1">
+          <mxGeometry height="49" width="59" x="30" y="-110" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-103" parent="1" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.elasticsearch;" value="&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;&lt;p data-start=&quot;190&quot; data-end=&quot;216&quot;&gt;&lt;span data-start=&quot;190&quot; data-end=&quot;212&quot; style=&quot;background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;Elasticsearch&lt;/font&gt;&lt;/span&gt;&lt;/p&gt;" vertex="1">
+          <mxGeometry height="50.099999999999994" width="45.9" x="602.05" y="550" as="geometry" />
+        </mxCell>
+        <mxCell id="uRcrelpJeVWRNYr8ECgl-104" parent="1" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" value="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;Công nghệ sử dụng&lt;/h1&gt;&lt;div&gt;Front end:&lt;/div&gt;&lt;div&gt;+ Website/MiniApp: ReactJS vite tool&lt;/div&gt;&lt;div&gt;+ Application: Flutter&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Backend:&lt;/div&gt;&lt;div&gt;+ 100% sử dụng Dotnet core 7 API&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Database:&lt;/div&gt;&lt;div&gt;+ Oracle&lt;/div&gt;&lt;div&gt;+ PostgresSQL&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Cache:&lt;/div&gt;&lt;div&gt;+ Redis&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Queue:&lt;/div&gt;&lt;div&gt;+ Kafka&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Identify service:&lt;/div&gt;&lt;div&gt;+ Keycloak&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;Api Gateway:&lt;/div&gt;&lt;div&gt;+ KONG&amp;nbsp;&lt;/div&gt;" vertex="1">
+          <mxGeometry height="380" width="490" x="1030" y="-329" as="geometry" />
+        </mxCell>
+      </root>
+    </mxGraphModel>
+  </diagram>
+</mxfile>

+ 6 - 3
EsimLao/esim-vite/src/App.tsx

@@ -19,6 +19,7 @@ import OrderDetailView from "./pages/order-detail/OrderDetailView";
 import CompatibilityModal from "./components/CompatibilityModal";
 import QRCodeModal from "./components/QRCodeModal";
 import { useAppSelector } from "./hooks/useRedux";
+import ScrollToTop from "./components/ScrollToTop";
 
 const App: React.FC = () => {
   const location = useLocation();
@@ -38,7 +39,7 @@ const App: React.FC = () => {
       }`}
     >
       {!isPlainView && <Header />}
-
+      <ScrollToTop />
       <Popup />
       <CompatibilityModal />
       <QRCodeModal />
@@ -57,8 +58,10 @@ const App: React.FC = () => {
             <Routes>
               <Route path="/" element={<HomeView />} />
               <Route path="/buy-sim" element={<BuySimView />} />
-              <Route path="/product/:id" element={<ProductDetailView />} />
-              {/* <Route path="/product" element={<ProductDetailView />} /> */}
+              <Route
+                path="/product-detail/:id"
+                element={<ProductDetailView />}
+              />
               <Route path="/checkout" element={<CheckoutView />} />
               <Route path="/support" element={<SupportView />} />
               <Route path="/news" element={<NewsView />} />

+ 2 - 2
EsimLao/esim-vite/src/apis/productApi.ts

@@ -52,8 +52,8 @@ class ProductApi extends BaseApi {
     return this.productPost("/list_payment_channel", {});
   }
 
-  async getOrderHistory({ fromDate, toDate, status }) {
-    return this.productPost("/list_order", { fromDate, toDate, status });
+  async getOrderHistory({ searchOrder, fromDate, toDate, status }) {
+    return this.productPost("/list_order", { searchOrder, fromDate, toDate, status });
   }
 
   async getOrderDetail({ orderId }) {

+ 18 - 17
EsimLao/esim-vite/src/components/CompatibilityModal.tsx

@@ -90,23 +90,23 @@ const CompatibilityModal = () => {
         </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">
+        <div className="shrink-0 p-8 md:p-10 lg:p-10 pb-2 text-center bg-white z-10 relative">
+          <h2 className="text-xl md:text-3xl font-black text-slate-900 mb-2 tracking-tight">
             {t("doesEsimWorkWithMyDevice")} <br />
-            {/* <span className="text-[#0091ff]">support eSIM ?</span> */}
+            {/* <span className="text-[#EE0434]">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="flex-1 overflow-y-auto p-3 md:p-8 lg:p-10 pt-1 custom-scrollbar">
           <div className="text-center mb-8">
-            <h3 className="text-xl md:text-2xl font-black text-[#0091ff] mb-6">
+            <h3 className="text-xm md:text-base lg:text-xl font-black text-[#EE0434] mb-6">
               {t("selectTopicInstructions")}
             </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">
+            <div className="text-left bg-slate-50 p-3 rounded-2xl border border-slate-100 text-sm md:text-base text-slate-600 space-y-4 mb-8">
               <p className="font-bold">{t("importantNote")}</p>
-              <ul className="list-disc pl-5 space-y-1 marker:text-[#0091ff]">
+              <ul className="list-disc pl-5 space-y-1 marker:text-[#EE0434]">
                 <li>{t("someDualPhysicalSimModelsDoNotSupportEsim")}</li>
                 <li>{t("carrierLockedDevicesMayNotSupportEsim")}</li>
                 <li>
@@ -167,16 +167,16 @@ const CompatibilityModal = () => {
                   )}
                 </div>
                 <span
-                  className={`font-bold text-lg transition-colors ${
+                  className={`font-bold transition-colors text-lg md:text-lg lg:text-xl ${
                     selectedBrand === brand.brand
-                      ? "text-[#0091ff]"
+                      ? "text-[#EE0434]"
                       : "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>
+                  <div className="w-12 h-1 bg-[#EE0434] rounded-full"></div>
                 )}
               </button>
             ))}
@@ -188,7 +188,7 @@ const CompatibilityModal = () => {
               {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"
+                  className="py-4 px-4 text-center hover:bg-slate-50 transition-colors text-slate-700 font-bold text-x,"
                 >
                   {device}
                 </div>
@@ -219,14 +219,15 @@ const CompatibilityModal = () => {
 
           {/* Method 2 */}
           <div className="text-center mb-12">
-            <h3 className="text-xl md:text-3xl font-black text-[#0091ff] mb-4">
+            <h3 className="text-xl md:text-2xl font-black text-[#EE0434] mb-4">
               {t("method2CheckDirectlyOnYourDevice")}
             </h3>
-            <p className="text-slate-600 text-lg">
+            <p className="text-slate-600 text-xs md:text-lg lg:text-xl">
               {t(
                 "checkIfYourDeviceSupportsEsimAndWhetherItIsCarrierLockedByFollowing"
               )}
-              <button className="text-[#0091ff] font-bold hover:underline">
+              <button className="text-[#EE0434] font-bold hover:underline">
+                {" "}
                 {t("theStepsBelow")}
               </button>
             </p>
@@ -234,12 +235,12 @@ const CompatibilityModal = () => {
 
           {/* Not Support */}
           <div className="text-center mb-8">
-            <h3 className="text-xl md:text-3xl font-black text-[#0091ff] mb-4">
+            <h3 className="text-xl md:text-2xl font-black text-[#EE0434] mb-4">
               {t("yourDeviceDoesNotSupportEsim")}
             </h3>
-            <p className="text-slate-600 text-lg">
+            <p className="text-slate-600 text-xs md:text-lg lg:text-xl">
               {t("tryFindingAPhysicalSimThatIsCompatibleWithYourDevice")}{" "}
-              <button className="text-[#0091ff] font-bold hover:underline">
+              <button className="text-[#EE0434] font-bold hover:underline">
                 {t("here")}
               </button>
             </p>

+ 1 - 0
EsimLao/esim-vite/src/components/Footer.tsx

@@ -21,6 +21,7 @@ const Footer: React.FC = () => {
                 src={logo}
                 alt="Getgo Logo"
                 className="text-[#EE0434] mr-0"
+                style={{ width: "150px" }}
               />
               {/* <span className="text-xl font-black text-[#EE0434]">etgo</span> */}
             </div>

+ 13 - 17
EsimLao/esim-vite/src/components/Header.tsx

@@ -16,7 +16,11 @@ import { useSelector } from "react-redux";
 import { setAreas } from "../features/areas/areasSlice";
 import i18n from "../i18n";
 import { useTranslation } from "react-i18next";
-import { getWithExpiry, setWithExpiry } from "../logic/loigicUtils";
+import {
+  formatCurrency,
+  getWithExpiry,
+  setWithExpiry,
+} from "../logic/loigicUtils";
 
 const Header: React.FC = () => {
   const navigate = useNavigate();
@@ -56,8 +60,8 @@ const Header: React.FC = () => {
   const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
 
   const languages = [
-    { code: "en", label: "English", flag: "us" },
-    { code: "vi", label: "Tiếng Việt", flag: "vn" },
+    { code: "en", label: t("english"), flag: "us" },
+    { code: "vi", label: t("vietnamese"), flag: "vn" },
   ];
 
   // load product by country/region or popularity
@@ -85,16 +89,8 @@ const Header: React.FC = () => {
     },
   });
 
-  useEffect(() => {
-    const handleScroll = () => {
-      setIsScrolled(window.scrollY > 300);
-    };
-    window.addEventListener("scroll", handleScroll);
-    return () => window.removeEventListener("scroll", handleScroll);
-  }, []);
-
   const handleAreaClick = (c: { id: number }) => {
-    navigate(`/product/${c.id}`, {
+    navigate(`/product-detail/${c.id}`, {
       state: {
         ...c,
       },
@@ -124,8 +120,9 @@ const Header: React.FC = () => {
     return () => window.removeEventListener("resize", handleResize);
   }, []);
 
+  const langNow = localStorage.getItem("lang") || "en";
   const currentLangObj =
-    languages.find((l) => l.code === selectedLang) || languages[0];
+    languages.find((l) => l.code === (selectedLang || langNow)) || languages[0];
 
   const isActive = (path: string) => location.pathname === path;
 
@@ -137,7 +134,6 @@ const Header: React.FC = () => {
     if (!areas || areas.length === 0) getAreaMutation.mutate();
     else {
       setAreasList(areas);
-      console.log("Areas loaded from store:", areas);
     }
   }, []);
 
@@ -155,7 +151,7 @@ const Header: React.FC = () => {
       console.log("Get area response data:", data);
       if (data && data.errorCode === "0") {
         console.log("Get area successful");
-        setWithExpiry("areas", JSON.stringify(data.data));
+        setWithExpiry("areas", data.data);
         setAreasList(data.data as Area[]);
       } else {
         console.error("Get area failed, no token received");
@@ -170,7 +166,7 @@ const Header: React.FC = () => {
   const handleSelect = (area: Area) => {
     console.log("Selected area:", area);
     setIsSearchDropdownOpen(false);
-    navigate(`/product/${area.id}`, {
+    navigate(`/product-detail/${area.id}`, {
       state: {
         ...area,
       },
@@ -289,7 +285,7 @@ const Header: React.FC = () => {
                                   {t("from")}:
                                 </span>
                                 <span className="text-sm font-black text-[#EE0434]">
-                                  {p.minSellPrice.toLocaleString()} {p.curency}
+                                  {formatCurrency(p.minSellPrice, p.curency)}
                                 </span>
                               </div>
                             </button>

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

@@ -72,7 +72,7 @@ const Popup = () => {
         </p>
 
         {/* Button */}
-        <div className="w-full flex flex-col md:flex-row gap-4 md:gap-6">
+        <div className="w-full flex flex-col md:flex-row gap-1 md:gap-1">
           {popup.hasRightButton && (
             <button
               onClick={() => {
@@ -81,7 +81,7 @@ const Popup = () => {
                 }
                 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"
+              className="mr-4 w-full py-4 rounded-2xl font-black text-xm shadow-lg active:scale-95 transition-all bg-[#EE0434] hover:bg-[#d10029] text-white shadow-red-200 p-2"
             >
               {popup.rightButtonText}
             </button>
@@ -90,7 +90,7 @@ const Popup = () => {
             onClick={() => {
               dispatch(closePopup());
             }}
-            className={`w-full py-4 rounded-2xl font-black text-lg shadow-lg active:scale-95 transition-all ${
+            className={`w-full py-4 rounded-2xl font-black text-xm shadow-lg active:scale-95 transition-all p-2 ${
               popup.isSuccess
                 ? "bg-[#00c087] hover:bg-[#00a876] text-white shadow-green-200"
                 : "bg-[#EE0434] hover:bg-[#d10029] text-white shadow-red-200"

+ 7 - 5
EsimLao/esim-vite/src/components/ProductCard.tsx

@@ -3,7 +3,7 @@ import React from "react";
 import { SimProduct } from "../services/types";
 import { Area } from "../services/product/type";
 import { useTranslation } from "react-i18next";
-import { formatNumber } from "../logic/loigicUtils";
+import { formatCurrency, formatNumber } from "../logic/loigicUtils";
 
 const ProductCard: React.FC<{
   p: Area;
@@ -30,12 +30,14 @@ const ProductCard: React.FC<{
           {p.areaName1}
         </h3>
         <p className="text-xs text-slate-400 line-through mb-1">
-          {formatNumber(p.minDisplayPrice)} {p.curency}
+          {formatCurrency(p.minDisplayPrice, p.curency)}
         </p>
         <p className="text-[#EE0434] font-bold text-xs md:text-lg">
-          {t("from")}:{" "}
-          <span className="font-black">
-            {formatNumber(p.minSellPrice)} {p.curency}
+          <span className="text-black text-sm md:text-sm lg:text-base">
+            {t("from")}:{" "}
+          </span>
+          <span className="font-black text-lg md:text-xs lg:text-xl">
+            {formatCurrency(p.minSellPrice, p.curency)}
           </span>
         </p>
       </div>

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

@@ -274,7 +274,7 @@ const ProductInfoModal: React.FC<ProductInfoModalProps> = ({
       <div className="relative bg-white rounded-[32px] w-full max-w-2xl max-h-[85vh] shadow-2xl flex flex-col overflow-hidden">
         {/* Header */}
         <div className="p-6 pb-2 border-b border-slate-100 flex justify-between items-center bg-white z-10">
-          <h2 className="text-xl md:text-2xl font-black text-slate-900">
+          <h2 className="text-xl md:text-2xl font-black text-slate-800">
             {t("productInformation")}
           </h2>
           <button

+ 1 - 3
EsimLao/esim-vite/src/components/QRCodeModal.tsx

@@ -30,9 +30,7 @@ const QRCodeModal = () => {
             <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`}
+                src={popup.qrData}
                 alt="eSIM QR Code"
                 className="w-full h-full object-contain mix-blend-multiply"
               />

+ 18 - 0
EsimLao/esim-vite/src/components/ScrollToTop.tsx

@@ -0,0 +1,18 @@
+import { useEffect } from "react";
+import { useLocation } from "react-router-dom";
+
+const ScrollToTop = () => {
+  const { pathname } = useLocation();
+
+  useEffect(() => {
+    window.scrollTo({
+      top: 0,
+      left: 0,
+      behavior: "auto", // khuyến nghị: auto cho performance
+    });
+  }, [pathname]);
+
+  return null;
+};
+
+export default ScrollToTop;

+ 20 - 4
EsimLao/esim-vite/src/i18n/locales/en.json

@@ -45,7 +45,7 @@
   "dataData": "Data",
   "unlimitedUnlimited": "Unlimited",
   "simType": "SIM Type",
-  "quantityQuantity": "Quantity",
+  "quantity": "Quantity",
   "esimEsim": "eSIM",
   "days": "days",
   "discountDiscount": "Discount",
@@ -73,7 +73,7 @@
   "paymentStatus": "Payment Status",
   "paymentSuccessful": "Payment successful!",
   "viewOrder": "View Order",
-  "closeClose": "Close",
+  "close": "Close",
   "transactionHistory": "Transaction History",
   "enterCode": "Enter order code",
   "fromDate": "From date",
@@ -206,5 +206,21 @@
   "emailAndEsimQrCode": "Email and eSIM QR code",
   "installationGuideForIphoneIos": "Installation guide for iPhone (iOS)",
   "installationGuideForAndroid": "Installation guide for Android",
-  "buyNow": "Buy now"
-}
+  "buyNow": "Buy now",
+  "expiredAt": "Expired at",
+  "notActive": "Not Active",
+  "active": "Active",
+  "finished": "Finished",
+  "expired": "Expired",
+  "unknown": "Unknown",
+  "paymentSuccess": "Payment Successful",
+  "paymentFailed": "Payment Failed",
+  "activationMethod": "Activation Method",
+  "productDetailsMissing": "Please reload the page",
+  "goToShop": "Go to Shop",
+  "english": "English",
+  "vietnamese": "Vietnamese",
+  "numberOfDays": "Number of Days",
+  "verified": "Verified",
+  "highSpeed": "High Speed"
+}

+ 20 - 4
EsimLao/esim-vite/src/i18n/locales/vi.json

@@ -45,7 +45,7 @@
   "dataData": "Dữ liệu",
   "unlimitedUnlimited": "Không giới hạn",
   "simType": "Loại SIM",
-  "quantityQuantity": "Số lượng",
+  "quantity": "Số lượng",
   "esimEsim": "eSIM",
   "days": "ngày",
   "discountDiscount": "Giảm giá",
@@ -73,7 +73,7 @@
   "paymentStatus": "Trạng thái thanh toán",
   "paymentSuccessful": "Thanh toán thành công!",
   "viewOrder": "Xem đơn hàng",
-  "closeClose": "Đóng",
+  "close": "Đóng",
   "transactionHistory": "Lịch sử giao dịch",
   "enterCode": "Nhập mã đơn hàng",
   "fromDate": "Từ ngày",
@@ -206,5 +206,21 @@
   "emailAndEsimQrCode": "Email và mã QR eSIM",
   "installationGuideForIphoneIos": "Hướng dẫn cài đặt cho iPhone (iOS)",
   "installationGuideForAndroid": "Hướng dẫn cài đặt cho Android",
-  "buyNow": "Mua ngay"
-}
+  "buyNow": "Mua ngay",
+  "expiredAt": "Hết hạn vào",
+  "notActive": "Chưa kích hoạt",
+  "active": "Đang hoạt động",
+  "finished": "Đã sử dụng hết",
+  "expired": "Đã hết hạn",
+  "unknown": "Không xác định",
+  "paymentSuccess": "Thanh toán thành công",
+  "paymentFailed": "Thanh toán thất bại",
+  "activationMethod": "Phương thức kích hoạt",
+  "productDetailsMissing": "Vui lòng tải lại trang",
+  "goToShop": "Đi đến cửa hàng",
+  "english": "Tiếng Anh",
+  "vietnamese": "Tiếng Việt",
+  "numberOfDays": "Số ngày",
+  "verified": "Đã xác thực",
+  "highSpeed": "Tốc độ cao"
+}

+ 4 - 4
EsimLao/esim-vite/src/index.css

@@ -1,8 +1,8 @@
 @import "tailwindcss";
 
 .absolute-right-18 {
-    right: -18px;
-  }
+  right: -18px;
+}
 
 /* max 768px screen */
 @media (max-width: 768px) {
@@ -11,8 +11,8 @@
   }
 }
 
-.font-black{
-  font-weight: 700 !important;
+.font-black {
+  font-weight: 600 !important;
 }
 
 .color-EE0434 {

+ 36 - 2
EsimLao/esim-vite/src/logic/loigicUtils.ts

@@ -7,9 +7,9 @@ export const formatCarriers = (text) => {
   return text.replace(/,\s*/g, ", ");
 };
 
-export const setWithExpiry = (
+export const setWithExpiry = <T>(
   key: string,
-  value: string,
+  value: T,
   ttlMs?: number | null
 ) => {
   const now = Date.now();
@@ -40,3 +40,37 @@ export const getWithExpiry = <T>(key: string): T | null => {
     return null;
   }
 };
+
+type CurrencyCode =
+  | "VND"
+  | "USD"
+  | "EUR"
+  | "JPY"
+  | "KRW"
+  | "CNY"
+  | "SGD"
+  | "THB";
+
+export const formatCurrency = (
+  amount: number | string,
+  currency?: string,
+  locale?: string
+) => {
+  if (amount === null || amount === undefined || amount === "") return "";
+
+  const value = Number(amount);
+
+  const resolvedLocale =
+    locale ??
+    (currency === "VND"
+      ? "vi-VN"
+      : currency === "USD"
+        ? "en-US"
+        : "en-GB");
+
+  return new Intl.NumberFormat(resolvedLocale, {
+    style: "currency",
+    currency,
+    minimumFractionDigits: currency === "VND" ? 0 : 2,
+  }).format(value);
+};

+ 1 - 1
EsimLao/esim-vite/src/pages/buy-sim/BuySimView.tsx

@@ -54,7 +54,7 @@ const BuySimView: React.FC<BuySimViewProps> = ({
 
   const handleSelect = (p: Area) => {
     // open product detail view
-    navigate(`/product/${p.id}`, {
+    navigate(`/product-detail/${p.id}`, {
       state: {
         ...p,
       },

+ 29 - 13
EsimLao/esim-vite/src/pages/checkout/CheckoutView.tsx

@@ -18,8 +18,9 @@ import {
 import { useAppDispatch, useAppSelector } from "../../hooks/useRedux";
 import { productApi } from "../../apis/productApi";
 import { openPopup } from "../../features/popup/popupSlice";
-import { formatNumber } from "../../logic/loigicUtils";
+import { formatCurrency, formatNumber } from "../../logic/loigicUtils";
 import { useTranslation } from "react-i18next";
+import { format } from "path";
 
 const CheckoutView = () => {
   const navigate = useNavigate();
@@ -50,6 +51,12 @@ const CheckoutView = () => {
     checkoutDetails: CheckoutDetailResponse;
   };
 
+  if (state == null) {
+    // redirect to buy sim page
+    navigate("/buy-sim");
+    return null;
+  }
+
   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
     const { name, value } = e.target;
     setForm((prev) => ({ ...prev, [name]: value }));
@@ -300,12 +307,16 @@ const CheckoutView = () => {
               </div>
               <div className="text-right">
                 <p className="text-xs text-slate-400 line-through font-bold">
-                  {formatNumber(state.checkoutDetails.totalMoney)}{" "}
-                  {state.checkoutDetails.curency}
+                  {formatCurrency(
+                    state.checkoutDetails.totalMoney,
+                    state.checkoutDetails.curency
+                  )}
                 </p>
                 <p className="text-[#0071e3] font-black text-xl">
-                  {formatNumber(state.checkoutDetails.paymentMoney)}{" "}
-                  {state.checkoutDetails.curency}
+                  {formatCurrency(
+                    state.checkoutDetails.paymentMoney,
+                    state.checkoutDetails.curency
+                  )}
                 </p>
               </div>
             </div>
@@ -409,17 +420,20 @@ const CheckoutView = () => {
               <div className="flex justify-between text-slate-600 font-bold">
                 <span>{t("subtotal")}:</span>
                 <span>
-                  {formatNumber(
-                    state.checkoutDetails.totalMoney * state.quantity
-                  )}{" "}
-                  {state.checkoutDetails.curency}
+                  {formatCurrency(
+                    state.checkoutDetails.totalMoney * state.quantity,
+                    state.checkoutDetails.curency
+                  )}
                 </span>
               </div>
               <div className="flex justify-between text-green-600 font-bold">
                 <span>Discount:</span>
                 <span>
-                  -{formatNumber(0 * state.quantity)}{" "}
-                  {state.checkoutDetails.curency}
+                  -
+                  {formatCurrency(
+                    state.checkoutDetails.discount,
+                    state.checkoutDetails.curency
+                  )}
                 </span>
               </div>
               <div className="flex justify-between items-center pt-4 border-t border-slate-200/50">
@@ -427,8 +441,10 @@ const CheckoutView = () => {
                   {t("totalTotal")} ({state.quantity}):
                 </span>
                 <span className="text-[#0071e3] font-black text-3xl">
-                  {formatNumber(state.checkoutDetails.paymentMoney)}{" "}
-                  {state.checkoutDetails.curency}
+                  {formatCurrency(
+                    state.checkoutDetails.paymentMoney,
+                    state.checkoutDetails.curency
+                  )}
                 </span>
               </div>
             </div>

+ 17 - 10
EsimLao/esim-vite/src/pages/home/HomeView.tsx

@@ -13,6 +13,7 @@ import partner2 from "../../assets/img/partner2.png";
 import { authApi } from "../../apis/authApi";
 import { accountLogin } from "../../features/account/accuntSlice";
 import { useTranslation } from "react-i18next";
+import i18n from "../../i18n";
 
 const HomeView: React.FC = () => {
   const [simType, setSimType] = useState<"eSIM" | "Physical">("eSIM");
@@ -20,6 +21,13 @@ const HomeView: React.FC = () => {
   const navigate = useNavigate();
   const dispatch = useAppDispatch();
   const { t } = useTranslation();
+  const langNow = localStorage.getItem("lang") || "en";
+
+  useEffect(() => {
+    // set language in i18n
+    i18n.changeLanguage(langNow);
+    localStorage.setItem("lang", langNow);
+  }, [langNow]);
 
   useEffect(() => {
     const params = new URLSearchParams(window.location.search);
@@ -31,18 +39,17 @@ const HomeView: React.FC = () => {
 
     // google callback
     const code = searchParams.get("code");
-
     if (status) {
       console.log("URL Params:", params);
       if (status === "0") {
         dispatch(
           openPopup({
             isSuccess: true,
-            message: "Payment successful!",
-            title: "Payment Status",
-            buttonText: "Close",
+            message: t("paymentSuccess"),
+            title: t("paymentStatus"),
+            buttonText: t("close"),
             hasRightButton: true,
-            rightButtonText: "View Order",
+            rightButtonText: t("viewOrder"),
             rightButtonAction: "OPEN_ORDER_HISTORY",
           })
         );
@@ -50,9 +57,9 @@ const HomeView: React.FC = () => {
         dispatch(
           openPopup({
             isSuccess: false,
-            message: "Payment failed or cancelled.",
-            title: "Payment Status",
-            buttonText: "Close",
+            message: t("paymentFailed"),
+            title: t("paymentStatus"),
+            buttonText: t("close"),
           })
         );
       }
@@ -301,7 +308,7 @@ const HomeView: React.FC = () => {
             <div className="inline-flex p-1 bg-slate-100 rounded-full shadow-inner">
               <button
                 onClick={() => setSimType("eSIM")}
-                className={`px-6 md:px-12 py-2.5 md:py-3 rounded-full text-[10px] md:text-sm font-bold transition-all ${
+                className={`px-6 md:px-12 py-2.5 md:py-3 rounded-full text-lg md:text-xs lg:text-xl font-bold transition-all ${
                   simType === "eSIM"
                     ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-md"
                     : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
@@ -311,7 +318,7 @@ const HomeView: React.FC = () => {
               </button>
               <button
                 onClick={() => setSimType("Physical")}
-                className={`px-6 md:px-12 py-2.5 md:py-3 rounded-full text-[10px] md:text-sm font-bold transition-all ${
+                className={`px-6 md:px-12 py-2.5 md:py-3 rounded-full text-lg md:text-xs lg:text-xl font-bold transition-all ${
                   simType === "Physical"
                     ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-md"
                     : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"

+ 1 - 1
EsimLao/esim-vite/src/pages/home/components/HomeBanner.tsx

@@ -78,7 +78,7 @@ const HomeBanner = () => {
                 <div className="inline-block px-4 py-1.5 bg-white/20 backdrop-blur-xl rounded-full text-[10px] md:text-xs font-black uppercase tracking-[0.3em] border border-white/10">
                   {banner.title}
                 </div>
-                <h1 className="text-4xl md:text-7xl lg:text-8xl font-black leading-[1.1] tracking-tighter max-w-4xl">
+                <h1 className="text-4xl md:text-4xl lg:text-5xl font-black leading-[1.1] tracking-tighter max-w-4xl">
                   {banner.title} <br />
                   <span className="text-white/60 drop-shadow-sm">
                     {banner.subtitle}

+ 9 - 32
EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx

@@ -10,8 +10,9 @@ import {
 } from "../../../features/loading/loadingSlice";
 import { DataCacheKey, staleTime } from "../../../global/constants";
 import { Area } from "../../../services/product/type";
-import { formatNumber } from "../../../logic/loigicUtils";
+import { formatCurrency, formatNumber } from "../../../logic/loigicUtils";
 import { useTranslation } from "react-i18next";
+import ProductCard from "../../../components/ProductCard";
 
 const HomeProduct = () => {
   const [activeTab, setActiveTab] = useState<"country" | "region">("country");
@@ -31,11 +32,11 @@ const HomeProduct = () => {
         activeTab === "country"
           ? {
               isCountry: "1",
-              isPopular: "-1",
+              isPopular: "1",
             }
           : {
               isCountry: "0",
-              isPopular: "-1",
+              isPopular: "1",
             }
       );
       return res;
@@ -58,7 +59,7 @@ const HomeProduct = () => {
   });
 
   const handleProductClick = (p: Area) => {
-    navigate(`/product/${p.id}`, {
+    navigate(`/product-detail/${p.id}`, {
       state: {
         ...p,
       },
@@ -82,7 +83,7 @@ const HomeProduct = () => {
             setActiveTab("country");
             getAreaMutation.mutate();
           }}
-          className={`px-8 md:px-12 py-3 rounded-full text-xs md:text-sm font-black transition-all ${
+          className={`px-8 md:px-12 py-3 rounded-full text-lg md:text-xs lg:text-xl font-black transition-all ${
             activeTab === "country"
               ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-lg"
               : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
@@ -95,7 +96,7 @@ const HomeProduct = () => {
             setActiveTab("region");
             getAreaMutation.mutate();
           }}
-          className={`px-8 md:px-12 py-3 rounded-full text-xs md:text-sm font-black transition-all ${
+          className={`px-8 md:px-12 py-3 rounded-full text-lg md:text-xs lg:text-xl  font-black transition-all ${
             activeTab === "region"
               ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-lg"
               : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
@@ -108,31 +109,7 @@ const HomeProduct = () => {
       <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
         <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-8">
           {areasList.map((p) => (
-            <div
-              key={p.id}
-              onClick={() => handleProductClick(p)}
-              className="group bg-white border border-slate-100 rounded-[32px] p-6 md:p-10 shadow-sm hover:shadow-2xl transition-all relative overflow-hidden cursor-pointer flex flex-col items-center"
-            >
-              <div className="absolute top-4 right-4 bg-[#EE0434] text-white text-[10px] font-black px-2.5 py-1 rounded-lg z-10">
-                {p.promotionPercent}%
-              </div>
-              <div className="w-16 h-16 md:w-20 md:h-20 mb-6 rounded-full overflow-hidden shadow-xl border-4 border-white group-hover:scale-110 transition-transform">
-                <img
-                  src={`${p.iconUrl}`}
-                  alt={p.areaName1}
-                  className="w-full h-full object-cover scale-150"
-                />
-              </div>
-              <h3 className="text-lg md:text-xl font-black text-slate-800 mb-2 truncate w-full group-hover:text-[#EE0434] transition-colors">
-                {p.areaName1}
-              </h3>
-              <p className="text-xs text-slate-400 line-through mb-1">
-                {formatNumber(p.minDisplayPrice)} {p.curency}
-              </p>
-              <p className="text-lg md:text-2xl font-black text-[#EE0434]">
-                From {formatNumber(p.minSellPrice)} {p.curency}
-              </p>
-            </div>
+            <ProductCard key={p.id} p={p} onClick={handleProductClick} />
           ))}
         </div>
       </div>
@@ -156,7 +133,7 @@ const HomeProduct = () => {
               />
             </svg>
           </div>
-          <span className="ml-6 text-white text-2xl font-black">
+          <span className="ml-6 text-white text-lg font-black">
             {t("seeMore")}
           </span>
         </button>

+ 11 - 7
EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx

@@ -11,7 +11,11 @@ import { setAreas } from "../../../features/areas/areasSlice";
 import { Area } from "../../../services/product/type";
 import { useNavigate } from "react-router";
 import { useTranslation } from "react-i18next";
-import { getWithExpiry, setWithExpiry } from "@/src/logic/loigicUtils";
+import {
+  formatCurrency,
+  getWithExpiry,
+  setWithExpiry,
+} from "../../../logic/loigicUtils";
 import { get } from "http";
 
 const HomeSearch = () => {
@@ -27,8 +31,8 @@ const HomeSearch = () => {
   useEffect(() => {
     if (!areas || areas.length === 0) getAreaMutation.mutate();
     else {
+      console.log("Areas loaded from cache:", areas.length, areas);
       setAreasList(areas);
-      console.log("Areas loaded from store:", areas);
     }
   }, []);
 
@@ -47,7 +51,7 @@ const HomeSearch = () => {
       if (data && data.errorCode === "0") {
         console.log("Get area successful");
         // dispatch(setAreas(data.data as Area[]));
-        setWithExpiry("areas", JSON.stringify(data.data));
+        setWithExpiry("areas", data.data);
         setAreasList(data.data as Area[]);
       } else {
         console.error("Get area failed, no token received");
@@ -62,7 +66,7 @@ const HomeSearch = () => {
   const handleSelect = (area: Area) => {
     console.log("Selected area:", area);
     setIsDropdownOpen(false);
-    navigate(`/product/${area.id}`, {
+    navigate(`/product-detail/${area.id}`, {
       state: {
         ...area,
       },
@@ -144,8 +148,8 @@ const HomeSearch = () => {
                     {t("mostPopular")}
                   </h3>
                   <div className="space-y-1">
-                    {areasList.length > 0 ? (
-                      areasList.map((p) => (
+                    {areasList && areasList.length > 0 ? (
+                      areasList?.map((p) => (
                         <button
                           key={p.id}
                           onClick={() => handleSelect(p)}
@@ -168,7 +172,7 @@ const HomeSearch = () => {
                               {t("from")}:
                             </span>
                             <span className="text-lg font-black text-[#EE0434]">
-                              {p.minSellPrice.toLocaleString()} {p.curency}
+                              {formatCurrency(p.minSellPrice, p.curency)}
                             </span>
                           </div>
                         </button>

+ 2 - 1
EsimLao/esim-vite/src/pages/home/components/HomeTestimonial.tsx

@@ -44,7 +44,8 @@ const HomeTestimonial = () => {
         <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 items-center">
           <div className="lg:col-span-4 text-white space-y-4 md:space-y-6 text-center lg:text-left">
             <h2 className="text-2xl md:text-4xl lg:text-[36px] font-black leading-tight tracking-tight">
-              {t("whatUs")} <br className="hidden lg:block" /> {t("sayAboutUs")}
+              {t("whatUs")}
+              {/* <br className="hidden lg:block" /> {t("sayAboutUs")} */}
             </h2>
             <p className="text-sm md:text-xl font-medium text-white/80 max-w-sm mx-auto lg:mx-0">
               {t("overMillionSatisfiedCustomers")}

+ 101 - 59
EsimLao/esim-vite/src/pages/order-detail/OrderDetailView.tsx

@@ -10,8 +10,9 @@ 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";
-import { formatNumber } from "../../logic/loigicUtils";
+import { formatCurrency, formatNumber } from "../../logic/loigicUtils";
 import { useTranslation } from "react-i18next";
+import { format } from "path";
 
 const OrderDetailView = () => {
   const location = useLocation();
@@ -88,70 +89,112 @@ const OrderDetailView = () => {
   };
 
   // Circular Progress Component
-  const CircularProgress = ({
-    used,
-    total,
-    label,
-  }: {
-    used: number;
-    total: number;
-    label: string;
-  }) => {
-    const radius = 50;
+  const CircularProgress = ({ dataUsage }: { dataUsage: DataUsage }) => {
+    const size = 180;
+    const strokeWidth = 14;
+    const radius = (size - strokeWidth) / 2;
     const circumference = 2 * Math.PI * radius;
-    const percentage = (used / total) * 100;
-    const strokeDashoffset = circumference - (percentage / 100) * circumference;
+
+    // 240 degrees calculation
+    const arcAngle = 240;
+    const arcLength = (arcAngle / 360) * circumference;
+
+    const percentage = Math.min(
+      Math.max(dataUsage.usageData / dataUsage.totalData, 0),
+      1
+    );
+    const strokeDashoffset = arcLength - percentage * arcLength;
+
+    // Rotate the SVG to center the 240-degree gap at the bottom
+    // The gap is 120 degrees. We want it balanced.
+    // Default SVG circle starts at 3 o'clock.
+    // To have the 240 arc centered at top, we rotate.
+    const rotation = 150; // (360 - 240) / 2 + 90
 
     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">
+        <div className="flex flex-col items-center justify-center">
+          <span className="text-sm md:text-sm font-black text-[#000] mb-2">
+            {dataUsage.expiredTime
+              ? `${t("expiredAt")}: ${dataUsage.expiredTime}`
+              : ""}
+          </span>
+        </div>
+        <div className="relative" style={{ width: size, height: size }}>
           <svg
-            className="transform -rotate-90 w-full h-full"
-            viewBox="0 0 120 120"
+            width={size}
+            height={size}
+            style={{ transform: `rotate(${rotation}deg)` }}
+            className="overflow-visible"
           >
+            {/* Background Path (240 degrees) */}
             <circle
-              cx="60"
-              cy="60"
+              cx={size / 2}
+              cy={size / 2}
               r={radius}
-              stroke="#f1f5f9"
-              strokeWidth="10"
-              fill="transparent"
+              fill="none"
+              stroke="#f0f0f0"
+              strokeWidth={strokeWidth}
+              strokeLinecap="round"
+              strokeDasharray={`${arcLength} ${circumference}`}
             />
+            {/* Progress Path (240 degrees) */}
             <circle
-              cx="60"
-              cy="60"
+              cx={size / 2}
+              cy={size / 2}
               r={radius}
-              stroke="#00b0f0"
-              strokeWidth="10"
-              fill="transparent"
-              strokeDasharray={circumference}
-              strokeDashoffset={strokeDashoffset}
+              fill="none"
+              stroke="#EE0434"
+              strokeWidth={strokeWidth}
               strokeLinecap="round"
+              strokeDasharray={`${arcLength} ${circumference}`}
+              strokeDashoffset={strokeDashoffset}
+              className="transition-all duration-1000 ease-out"
             />
           </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
+          {/* Central Text */}
+          <div className="absolute inset-0 flex flex-col items-center justify-center">
+            <span className="text-3xl md:text-4xl font-black text-[#EE0434]">
+              {formatNumber(dataUsage.usageData)}
+              <span className="text-[16px]">{dataUsage.dataUnit}</span>
             </span>
           </div>
 
-          {/* <div className="absolute bottom-2 left-0 text-[10px] font-bold text-slate-400">
-            0 MB
+          {/* Min Label (Approximate position for 240deg start) */}
+          <div
+            className="absolute text-xl font-bold font-black text-[#000000]"
+            style={{ bottom: "5%", left: "12%" }}
+          >
+            {formatNumber(0)}
+            <span className="text-[16px]">{dataUsage.dataUnit}</span>
+          </div>
+          {/* Max Label (Approximate position for 240deg end) */}
+          <div
+            className="absolute text-xl font-bold font-black text-[#000000]"
+            style={{ bottom: "5%", right: "8%" }}
+          >
+            {formatNumber(dataUsage.totalData)}
+            <span className="text-[16px]">{dataUsage.dataUnit}</span>
           </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 className="mt-1 text-slate-400 text-sm font-bold uppercase tracking-widest">
+          {dataUsage.status === 0
+            ? t("notActive")
+            : dataUsage.status === 1
+            ? t("active")
+            : dataUsage.status === 2
+            ? t("finished")
+            : dataUsage.status === 3
+            ? t("expired")
+            : t("unknown")}
         </p>
+        {/* <p className="mt-1 text-slate-400 text-sm font-bold uppercase tracking-widest">
+          {100 - Math.round((used / total) * 100)}% {t("remaining")}
+        </p> */}
       </div>
     );
   };
+
   const getStatusColor = (status: number) => {
     switch (status) {
       case 2:
@@ -239,7 +282,7 @@ const OrderDetailView = () => {
                   </svg>
                 </div>
                 <span className="text-xl md:text-2xl font-bold text-slate-800">
-                  {state.orderHistory?.id}
+                  {state.orderHistory?.orderCode}
                 </span>
               </div>
               {state.orderHistory?.status === 2 ? (
@@ -297,10 +340,13 @@ const OrderDetailView = () => {
             <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">
                 {t("totalTotal")}:{" "}
-                {formatNumber(state.orderHistory?.totalMoney)}{" "}
-                <span className="text-slate-500 font-normal">
+                {formatCurrency(
+                  state.orderHistory?.paymentMoney,
+                  state.orderHistory?.curency
+                )}{" "}
+                {/* <span className="text-slate-500 font-normal">
                   ({state.orderHistory?.curency})
-                </span>
+                </span> */}
               </span>
             </div>
 
@@ -394,7 +440,7 @@ const OrderDetailView = () => {
                   </span>
                 </div>
                 <span className="text-slate-800 font-semibold text-sm text-[16px]">
-                  ONEPAY
+                  QR Code
                 </span>
               </div>
             </div>
@@ -437,15 +483,15 @@ const OrderDetailView = () => {
                         <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">
+                        {/* <span className="px-2 py-0.5 rounded bg-slate-100 text-slate-600 text-xs font-bold">
                           {pkg.id}
-                        </span>
+                        </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}
+                        {t("validityPeriod")}: {pkg.dayDuration} {t("days")}
                       </p>
                     </div>
 
@@ -453,7 +499,7 @@ const OrderDetailView = () => {
                       <button
                         className="flex items-center text-[#EE0434] font-bold text-sm mb-2 hover:underline"
                         onClick={() => {
-                          dispatch(openQRModal(pkg.installationUrl));
+                          dispatch(openQRModal(pkg.qrcodeUrl));
                         }}
                       >
                         <svg
@@ -479,12 +525,12 @@ const OrderDetailView = () => {
                       </button>
                       <div className="text-right">
                         <span className="text-xl font-bold text-slate-800">
-                          {formatNumber(pkg.paymentMoney)}{" "}
+                          {formatCurrency(pkg.paymentMoney, pkg.curency)}
                         </span>
-                        <span className="text-slate-500 font-medium ml-1 font-bold">
+                        {/* <span className="text-slate-500 font-medium ml-1 font-bold">
                           {" "}
                           đ
-                        </span>
+                        </span> */}
                       </div>
                     </div>
                   </div>
@@ -501,11 +547,7 @@ const OrderDetailView = () => {
                     <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}
-                    />
+                    <CircularProgress dataUsage={pkg} />
                   </div>
                 ))}
               </div>

+ 10 - 8
EsimLao/esim-vite/src/pages/order-history/OrderHistoryView.tsx

@@ -5,15 +5,16 @@ import { useAppDispatch } from "../../hooks/useRedux";
 import { OrderHistory } from "../../services/product/type";
 import { useMutation } from "@tanstack/react-query";
 import React, { useState, useEffect } from "react";
-import { formatNumber } from "../../logic/loigicUtils";
+import { formatCurrency, formatNumber } from "../../logic/loigicUtils";
 import { useTranslation } from "react-i18next";
 
 const OrderHistoryView = () => {
   const [searchOrder, setSearchOrder] = useState("");
+  const today = new Date().toISOString().split("T")[0];
   // status is one of: -1, 1, 2
   const [status, setStatus] = useState("-1");
-  const [fromDate, setFromDate] = useState("");
-  const [toDate, setToDate] = useState("");
+  const [fromDate, setFromDate] = useState(today);
+  const [toDate, setToDate] = useState(today);
   const [orders, setOrders] = useState<OrderHistory[]>([]);
   const { t } = useTranslation();
 
@@ -28,6 +29,7 @@ const OrderHistoryView = () => {
     mutationFn: async () => {
       dispatch(startLoading({}));
       const res = await productApi.getOrderHistory({
+        searchOrder: searchOrder,
         fromDate,
         toDate,
         status,
@@ -107,7 +109,7 @@ const OrderHistoryView = () => {
           <div className="md:col-span-4 relative">
             <input
               type="text"
-              placeholder="Enter order code"
+              placeholder="Enter order id"
               className={inputClass}
               value={searchOrder}
               onChange={(e) => {
@@ -115,7 +117,7 @@ const OrderHistoryView = () => {
                 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">
+            <button className="absolute right-2 top-1/2 -translate-y-1/2 w-8 h-8 bg-[#EE0434] rounded-xl flex items-center justify-center text-white shadow-md hover:scale-105 transition-transform">
               <svg
                 className="w-4 h-4"
                 fill="none"
@@ -224,7 +226,7 @@ const OrderHistoryView = () => {
                   </span>
                   <div>
                     <p className="text-xl font-black text-slate-900 tracking-tight">
-                      {order.id}
+                      {order.orderCode}
                     </p>
                     <p className="text-sm font-medium text-slate-400 mt-1">
                       {order.createdDate}
@@ -249,10 +251,10 @@ const OrderHistoryView = () => {
                   </div>
                   <div className="text-right">
                     <p className="text-xl font-black text-slate-900">
-                      {formatNumber(order.paymentMoney)} ({order.curency})
+                      {formatCurrency(order.paymentMoney, order.curency)}
                     </p>
                     <p className="text-sm font-bold text-slate-400">
-                      {formatNumber(order.totalMoney)} ({order.curency})
+                      {formatCurrency(order.totalMoney, order.curency)}
                     </p>
                   </div>
                 </div>

+ 47 - 33
EsimLao/esim-vite/src/pages/product-detail/ProductDetailView.tsx

@@ -8,18 +8,24 @@ import { useAppDispatch, useAppSelector } from "../../hooks/useRedux";
 import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
 import { productApi } from "../../apis/productApi";
 import { openPopup } from "../../features/popup/popupSlice";
-import { formatNumber } from "../../logic/loigicUtils";
+import {
+  formatCurrency,
+  formatNumber,
+  getWithExpiry,
+} from "../../logic/loigicUtils";
 import ProductInfoModal from "../../components/ProductInfoModal";
 import PackageOverview from "./components/PackageOverview";
 import ProductCard from "../../components/ProductCard";
 import { useTranslation } from "react-i18next";
+import { get } from "http";
 
 const ProductDetailView: React.FC = () => {
   const location = useLocation();
   const navigate = useNavigate();
   const dispatch = useAppDispatch();
   const { t } = useTranslation();
-  const area = location.state as Area;
+  // let area = location.state as Area;
+  const areas = getWithExpiry<Area[] | []>("areas");
   const { id } = useParams<{ id: string }>();
   const loading = useAppSelector((state) => state.loading);
   const [selectedDays, setSelectedDays] = useState<number>(null);
@@ -41,32 +47,26 @@ const ProductDetailView: React.FC = () => {
   const [simType, setSimType] = useState<"eSIM" | "Physical">("eSIM");
   const [quantity, setQuantity] = useState<number>(1);
   const [packages, setPackages] = useState<Package[]>([]);
+  const [area, setArea] = useState<Area | null>(null);
 
   const [selectedPackage, setSelectedPackage] = useState<Package | null>(null);
 
-  if (!id) {
-    return (
-      <div className="min-h-screen flex items-center justify-center">
-        <div className="text-center">
-          <p className="text-xl font-bold mb-4">Product details missing</p>
-          <Link to="/buy-sim" className="text-[#EE0434] font-bold underline">
-            Go to Shop
-          </Link>
-        </div>
-      </div>
-    );
-  }
-
   useEffect(() => {
-    getProductMutation.mutate();
-    getRelatedAreaMutation.mutate();
+    console.log("ProductDetailView loaded with id:", id);
+    if (areas && areas.length > 0) {
+      const areaTmp = areas.find((a) => a.id.toString() === id.toString());
+      setArea(areaTmp);
+      getProductMutation.mutate();
+      getRelatedAreaMutation.mutate();
+      console.log("Set area from cache:", areaTmp);
+    }
   }, [id]);
 
   const getProductMutation = useMutation({
     mutationFn: async () => {
       dispatch(startLoading({}));
       const res = await productApi.loadPackage({
-        areaId: id,
+        areaId: area?.id,
         dataType: "-1",
       });
       return res;
@@ -223,7 +223,7 @@ const ProductDetailView: React.FC = () => {
     setPrices({
       original: quantityToUse * selectedPackageTmp.displayPrice,
       final: quantityToUse * selectedPackageTmp.sellPrice,
-      discountPercent: "0",
+      discountPercent: selectedPackageTmp.discountPercent,
     });
     setSelectedPackage(selectedPackageTmp);
   };
@@ -277,6 +277,19 @@ const ProductDetailView: React.FC = () => {
     }
   };
 
+  if (!id || !area) {
+    return (
+      <div className="min-h-screen flex items-center justify-center">
+        <div className="text-center">
+          <p className="text-xl font-bold mb-4">{t("productDetailsMissing")}</p>
+          <Link to="/buy-sim" className="text-[#EE0434] font-bold underline">
+            {t("goToShop")}
+          </Link>
+        </div>
+      </div>
+    );
+  }
+
   return (
     <div className="bg-white min-h-screen pb-20">
       <div className="max-w-7xl mx-auto px-4 py-4 md:py-6">
@@ -301,7 +314,7 @@ const ProductDetailView: React.FC = () => {
             />
           </svg>
           <span className="text-slate-900 font-bold text-[18px]">
-            {area.areaName1}
+            {area?.areaName1}
           </span>
         </nav>
       </div>
@@ -310,24 +323,25 @@ const ProductDetailView: React.FC = () => {
         <div className="lg:col-span-5 space-y-6">
           <div className="relative aspect-[3/4] rounded-[24px] md:rounded-[32px] overflow-hidden shadow-2xl group">
             <img
-              src={area.imgUrl}
-              alt={area.areaName1}
+              src={area?.imgUrl}
+              alt={area?.areaName1}
               className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
             />
             <div className="absolute top-6 left-6 flex items-start space-x-4">
               <div className="w-12 h-12 md:w-16 md:h-16 rounded-full overflow-hidden border-2 border-white/50 shadow-lg">
                 <img
-                  src={`${area.iconUrl}`}
-                  alt={area.areaName1}
+                  src={`${area?.iconUrl}`}
+                  alt={area?.areaName1}
                   className="w-full h-full object-cover scale-150"
                 />
               </div>
               <div className="text-white">
                 <h1 className="text-2xl md:text-4xl font-black drop-shadow-md">
-                  SIM {area.areaName1}
+                  SIM {area?.areaName1}
                 </h1>
                 <p className="text-sm md:text-lg font-bold text-white/90">
-                  Verified: <span className="text-[#EE0434]">High Speed</span>
+                  {t("verified")}:{" "}
+                  <span className="text-[#EE0434]">{t("highSpeed")}</span>
                 </p>
               </div>
             </div>
@@ -340,7 +354,7 @@ const ProductDetailView: React.FC = () => {
         <div className="lg:col-span-7 space-y-8 md:space-y-10 pt-4">
           <div className="space-y-4">
             <h3 className="text-lg md:text-xl font-black text-slate-900 tracking-tight">
-              Number of days
+              {t("numberOfDays")}
             </h3>
             <div className="flex flex-wrap gap-3">
               {daysOptions.map((day) => (
@@ -393,7 +407,7 @@ const ProductDetailView: React.FC = () => {
           <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
             <div className="space-y-4">
               <h3 className="text-lg md:text-xl font-black text-slate-900 tracking-tight">
-                SIM Type
+                {t("simType")}
               </h3>
               <div className="flex p-1 bg-slate-50 rounded-2xl border border-slate-100">
                 <button
@@ -420,7 +434,7 @@ const ProductDetailView: React.FC = () => {
             </div>
             <div className="space-y-4">
               <h3 className="text-lg md:text-xl font-black text-slate-900 tracking-tight">
-                Quantity
+                {t("quantity")}
               </h3>
               <div className="flex items-center space-x-4 p-2 bg-slate-50 rounded-2xl border border-slate-100 h-[68px] md:h-[76px]">
                 <button
@@ -446,14 +460,14 @@ const ProductDetailView: React.FC = () => {
             <div className="flex justify-between items-end">
               <div className="flex items-center space-x-2">
                 <span className="text-[#EE0434] font-black text-lg">
-                  -{prices.discountPercent}%
+                  {prices.discountPercent}%
                 </span>
                 <span className="text-slate-300 font-bold text-xs line-through">
-                  {formatNumber(prices.original)} {area.curency}
+                  {formatCurrency(prices.original, area?.curency)}
                 </span>
               </div>
               <span className="text-[#EE0434] font-black text-2xl md:text-3xl">
-                {formatNumber(prices.final)} {area.curency}
+                {formatCurrency(prices.final, area?.curency)}
               </span>
             </div>
             <button
@@ -483,7 +497,7 @@ const ProductDetailView: React.FC = () => {
                   key={item.id}
                   p={item}
                   onClick={() => {
-                    navigate(`/product/${item.id}`, {
+                    navigate(`/product-detail/${item.id}`, {
                       state: {
                         ...item,
                       },

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

@@ -137,6 +137,7 @@ export interface OrderHistory {
   orderDetailId: number;
   paymentUrl: string | null;
   discountPercent: number;
+  orderCode: string;
   customerInfo: {
     id: number;
     surName: string | null;
@@ -160,6 +161,7 @@ export interface OrderDetail {
   packageTitle: string;
   paymentMoney: string;
   dayDuration: number;
+  curency: string;
 }
 
 export interface DataUsage {
@@ -168,4 +170,6 @@ export interface DataUsage {
   expiredTime: string;
   isUnlimited: number;
   status: number;
+  dataUnit: string;
+  usageData: number
 }