Bladeren bron

Enhance area search, authentication, and UI consistency

Added advanced area search dropdowns to Header and HomeSearch, improved area state management, and unified area fetching logic. Updated authentication to support OTP resend and proper localStorage handling. Refined UI for login, checkout, and navigation, and replaced hardcoded partner images with local assets. Fixed TestimonialCard prop casing and updated contact email.
trunghieubui 3 weken geleden
bovenliggende
commit
6279553265

+ 6 - 2
EsimLao/esim-vite/src/apis/authApi.ts

@@ -1,4 +1,4 @@
-import { AccountInfo } from "../services/auth/types";
+import { AccountInfo, RequestOtpResponse } from "../services/auth/types";
 import { Area } from "../services/product/type";
 import { BaseApi } from "./baseApi";
 
@@ -8,12 +8,16 @@ class AuthApi extends BaseApi {
   }
 
   async requestOtp({ email }) {
-    return this.authPost("/request-otp", { email });
+    return this.authPost<RequestOtpResponse>("/request-otp", { email });
   }
 
   async verifyOtp({ email, otp }) {
     return this.authPost<AccountInfo>("/verify-otp", { email, otpCode: otp });
   }
+
+  async resendOtp({ email }) {
+    return this.authPost<RequestOtpResponse>("/resend-otp", { email });
+  }
 }
 
 export const authApi = new AuthApi();

+ 1 - 0
EsimLao/esim-vite/src/apis/axios.ts

@@ -27,6 +27,7 @@ export const createAxiosInstance = (baseURL: string): AxiosInstance => {
       if (error.response?.status === 401) {
         localStorage.removeItem("token");
         localStorage.removeItem("refreshToken");
+        localStorage.removeItem("accountInfo");
         window.location.href = "/login";
       }
       return Promise.reject(error);

BIN
EsimLao/esim-vite/src/assets/img/partner1.png


BIN
EsimLao/esim-vite/src/assets/img/partner2.png


+ 275 - 127
EsimLao/esim-vite/src/components/Header.tsx

@@ -12,9 +12,13 @@ import {
 import { productApi } from "../apis/productApi";
 import { Area } from "../services/product/type";
 import { accountLogout } from "../features/account/accuntSlice";
+import { useSelector } from "react-redux";
+import { setAreas } from "../features/areas/areasSlice";
 
 const Header: React.FC = () => {
   const navigate = useNavigate();
+  const areas = useSelector((state: any) => state.areas.areas);
+
   const location = useLocation();
   const [isMenuOpen, setIsMenuOpen] = useState(false);
   const [isBuySimExpanded, setIsBuySimExpanded] = useState(false);
@@ -32,6 +36,7 @@ const Header: React.FC = () => {
   const dispatch = useAppDispatch();
   const langMenuRef = useRef<HTMLDivElement>(null);
   const userMenuRef = useRef<HTMLDivElement>(null);
+  const account = localStorage.getItem("accountInfo");
 
   const guideItems = [
     { label: "What is eSIM", path: "/support" },
@@ -39,6 +44,10 @@ const Header: React.FC = () => {
     { label: "Support", path: "/support" },
     { label: "Order Tracking Search", path: "/support" },
   ];
+  const dropdownRef = useRef<HTMLDivElement>(null);
+  const [areasList, setAreasList] = useState<Area[]>([]);
+  const [searchQuery, setSearchQuery] = useState("");
+  const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
 
   const languages = [
     { code: "en", label: "English", flag: "us" },
@@ -52,23 +61,21 @@ const Header: React.FC = () => {
       const res = await productApi.loadArea(
         activeDesktopTab === "popular"
           ? { isCountry: "-1", isPopular: "1" }
-          : { isCountry: "1", isPopular: "1" }
+          : { isCountry: "0", isPopular: "-1" }
       );
-      setProducts(res.data);
       return res;
     },
     onSuccess: (data) => {
       dispatch(stopLoading());
-      console.log("Get otp response data:", data);
       if (data && data.errorCode === "0") {
-        console.log("Get otp successful");
+        setProducts(data.data);
       } else {
-        console.error("Get otp failed, no token received");
+        console.error("Get area failed, no token received");
       }
     },
     onError: (error: any) => {
       dispatch(stopLoading());
-      console.error("Get otp error:", error.response.data);
+      console.error("Get area error:", error.response.data);
     },
   });
 
@@ -120,6 +127,77 @@ const Header: React.FC = () => {
     getProductMutation.mutate();
   }, [activeDesktopTab]);
 
+  useEffect(() => {
+    if (!areas || areas.length === 0) getAreaMutation.mutate();
+    else {
+      setAreasList(areas);
+      console.log("Areas loaded from store:", areas);
+    }
+  }, []);
+
+  const getAreaMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await productApi.loadArea({
+        isCountry: "-1",
+        isPopular: "-1",
+      });
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get area response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Get area successful");
+        dispatch(setAreas(data.data as Area[]));
+        setAreasList(data.data as Area[]);
+      } else {
+        console.error("Get area failed, no token received");
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get area error:", error.response.data);
+    },
+  });
+
+  const handleSelect = (area: Area) => {
+    console.log("Selected area:", area);
+    setIsSearchDropdownOpen(false);
+    navigate(`/product/${area.id}`, {
+      state: {
+        ...area,
+      },
+    });
+  };
+
+  const handleSearch = (query: string) => {
+    setSearchQuery(query);
+    if (query.trim() === "") {
+      setAreasList(areas);
+    } else {
+      const filtered = areas.filter((area: Area) =>
+        area.areaName1.toLowerCase().includes(query.toLowerCase())
+      );
+      setAreasList(filtered);
+    }
+  };
+
+  useEffect(() => {
+    const handleClickOutside = (event: MouseEvent) => {
+      if (
+        dropdownRef.current &&
+        !dropdownRef.current.contains(event.target as Node)
+      ) {
+        setIsSearchDropdownOpen(false);
+      }
+    };
+    document.addEventListener("mousedown", handleClickOutside);
+    return () => {
+      document.removeEventListener("mousedown", handleClickOutside);
+    };
+  }, []);
+
   return (
     <>
       <header className="sticky top-0 z-[60] w-full bg-white border-b border-slate-100 shadow-sm transition-all duration-300">
@@ -141,15 +219,21 @@ const Header: React.FC = () => {
 
             {/* Desktop Search on Scroll */}
             <div
-              className={`hidden lg:flex items-center transition-all duration-500 overflow-hidden ${
+              ref={dropdownRef}
+              className={`hidden lg:flex items-center transition-all duration-500 ${
                 isScrolled
-                  ? "flex-1 max-w-md mx-8 opacity-100"
-                  : "max-w-0 opacity-0 pointer-events-none"
+                  ? "flex-1 max-w-md mx-8 opacity-100 overflow-visible"
+                  : "max-w-0 opacity-0 pointer-events-none overflow-hidden"
               }`}
             >
               <div className="relative w-full">
                 <input
                   type="text"
+                  value={searchQuery}
+                  onChange={(e) => {
+                    handleSearch(e.target.value);
+                  }}
+                  onFocus={() => setIsSearchDropdownOpen(true)}
                   placeholder="Search country..."
                   className="w-full bg-slate-50 border border-slate-200 rounded-full py-2.5 px-6 pl-12 text-sm focus:outline-none focus:ring-2 focus:ring-red-100 focus:border-[#EE0434] transition-all"
                 />
@@ -166,9 +250,55 @@ const Header: React.FC = () => {
                     d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
                   />
                 </svg>
+
+                {/* Dropdown Menu */}
+                {isSearchDropdownOpen && (
+                  <div className="absolute top-full left-0 right-0 mt-2 bg-white rounded-2xl shadow-[0_10px_40px_-10px_rgba(0,0,0,0.1)] border border-slate-100 overflow-hidden animate-in fade-in zoom-in-95 duration-200">
+                    <div className="max-h-[300px] overflow-y-auto custom-scrollbar p-2">
+                      <h3 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2 px-3 pt-2">
+                        Most Popular
+                      </h3>
+                      <div className="space-y-0.5">
+                        {areasList.length > 0 ? (
+                          areasList.map((p) => (
+                            <button
+                              key={p.id}
+                              onClick={() => handleSelect(p)}
+                              className="w-full flex items-center justify-between p-2 hover:bg-slate-50 rounded-xl transition-all group"
+                            >
+                              <div className="flex items-center space-x-3">
+                                <div className="w-8 h-8 rounded-full border border-slate-100 overflow-hidden shadow-sm shrink-0">
+                                  <img
+                                    src={`${p.iconUrl}`}
+                                    alt={p.areaName1}
+                                    className="w-full h-full object-cover scale-150"
+                                  />
+                                </div>
+                                <span className="font-bold text-slate-700 text-sm group-hover:text-[#EE0434] transition-colors">
+                                  {p.areaName1}
+                                </span>
+                              </div>
+                              <div className="text-right flex items-center space-x-1.5">
+                                <span className="text-xs text-slate-400 font-medium">
+                                  from:
+                                </span>
+                                <span className="text-sm font-black text-[#EE0434]">
+                                  {p.minSellPrice.toLocaleString()} {p.curency}
+                                </span>
+                              </div>
+                            </button>
+                          ))
+                        ) : (
+                          <div className="text-center py-4 text-slate-400 text-sm font-medium">
+                            No matches found
+                          </div>
+                        )}
+                      </div>
+                    </div>
+                  </div>
+                )}
               </div>
             </div>
-
             {/* Desktop Nav */}
             <nav
               className={`hidden lg:flex items-center h-full transition-all duration-300 ${
@@ -269,52 +399,26 @@ const Header: React.FC = () => {
                           Region
                         </button>
                       </div>
-
-                      {activeDesktopTab === "popular" ? (
-                        <div className="grid grid-cols-4 gap-y-8 gap-x-4">
-                          {products.map((c) => (
-                            <div
-                              key={c.areaName1}
-                              onClick={() => handleAreaClick(c)}
-                              className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors"
-                            >
-                              <div className="w-7 h-7 rounded-full overflow-hidden border border-slate-200 shadow-sm shrink-0">
-                                <img
-                                  src={`${c.iconUrl}`}
-                                  alt={c.areaName1}
-                                  className="w-full h-full object-cover"
-                                />
-                              </div>
-                              <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors">
-                                {c.areaName1}
-                              </span>
-                            </div>
-                          ))}
-                        </div>
-                      ) : (
-                        <div className="grid grid-cols-4 gap-y-6 gap-x-2">
-                          {products.map((region) => (
-                            <div
-                              key={region}
-                              onClick={() => handleAreaClick(region)}
-                              className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors min-w-0"
-                            >
-                              <div className="w-7 h-7 rounded-full bg-red-50 flex items-center justify-center shrink-0 border border-red-100/50">
-                                <svg
-                                  className="w-4 h-4 text-red-300"
-                                  fill="currentColor"
-                                  viewBox="0 0 24 24"
-                                >
-                                  <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7.01-3.55-7.5-7.5H7v-1H3.5c.49-3.95 3.55-7.01 7.5-7.5v3.5h1V3.5c3.95.49 7.01 3.55 7.5 7.5H17v1h3.5c-.49 3.95-3.55-7.01-7.5 7.5v-3.5h-1v3.5z" />
-                                </svg>
-                              </div>
-                              <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors truncate">
-                                {region.areaName1}
-                              </span>
+                      <div className="grid grid-cols-4 gap-y-8 gap-x-4">
+                        {products.map((p) => (
+                          <div
+                            key={p.id}
+                            onClick={() => handleAreaClick(p)}
+                            className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors"
+                          >
+                            <div className="w-7 h-7 rounded-full overflow-hidden border border-slate-200 shadow-sm shrink-0">
+                              <img
+                                src={`${p.iconUrl}`}
+                                alt={p.areaName1}
+                                className="w-full h-full object-cover"
+                              />
                             </div>
-                          ))}
-                        </div>
-                      )}
+                            <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors">
+                              {p.areaName1}
+                            </span>
+                          </div>
+                        ))}
+                      </div>
                     </div>
                   </div>
                 )}
@@ -420,56 +524,84 @@ const Header: React.FC = () => {
 
                 {isUserMenuOpen && (
                   <div className="absolute top-full right-0 mt-3 w-56 bg-white rounded-[24px] shadow-[0_20px_40px_rgba(0,0,0,0.1)] border border-slate-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200 z-50">
-                    <div className="flex flex-col py-2">
-                      <button
-                        onClick={() => {
-                          // onViewChange(ViewMode.ORDER_HISTORY);
-                          navigate("/order-history");
-                          setIsUserMenuOpen(false);
-                        }}
-                        className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
-                      >
-                        <svg
-                          className="w-5 h-5 text-slate-400"
-                          fill="none"
-                          stroke="currentColor"
-                          viewBox="0 0 24 24"
+                    {account !== null && (
+                      <div className="flex flex-col py-2">
+                        <button
+                          onClick={() => {
+                            navigate("/order-history");
+                            setIsUserMenuOpen(false);
+                          }}
+                          className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
                         >
-                          <path
-                            strokeLinecap="round"
-                            strokeLinejoin="round"
-                            strokeWidth={2}
-                            d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
-                          />
-                        </svg>
-                        <span className="font-bold text-sm">Orders</span>
-                      </button>
-                      <div className="h-px bg-slate-50 mx-4 my-1"></div>
-                      <button
-                        onClick={() => {
-                          // onViewChange(ViewMode.LOGIN); // Simulate logout
-                          dispatch(accountLogout());
-                          navigate("/login");
-                          setIsUserMenuOpen(false);
-                        }}
-                        className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
-                      >
-                        <svg
-                          className="w-5 h-5 text-slate-400"
-                          fill="none"
-                          stroke="currentColor"
-                          viewBox="0 0 24 24"
+                          <svg
+                            className="w-5 h-5 text-slate-400"
+                            fill="none"
+                            stroke="currentColor"
+                            viewBox="0 0 24 24"
+                          >
+                            <path
+                              strokeLinecap="round"
+                              strokeLinejoin="round"
+                              strokeWidth={2}
+                              d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
+                            />
+                          </svg>
+                          <span className="font-bold text-sm">Orders</span>
+                        </button>
+                        <div className="h-px bg-slate-50 mx-4 my-1"></div>
+
+                        <button
+                          onClick={() => {
+                            dispatch(accountLogout());
+                            navigate("/login");
+                            setIsUserMenuOpen(false);
+                          }}
+                          className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
                         >
-                          <path
-                            strokeLinecap="round"
-                            strokeLinejoin="round"
-                            strokeWidth={2}
-                            d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
-                          />
-                        </svg>
-                        <span className="font-bold text-sm">Logout</span>
-                      </button>
-                    </div>
+                          <svg
+                            className="w-5 h-5 text-slate-400"
+                            fill="none"
+                            stroke="currentColor"
+                            viewBox="0 0 24 24"
+                          >
+                            <path
+                              strokeLinecap="round"
+                              strokeLinejoin="round"
+                              strokeWidth={2}
+                              d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
+                            />
+                          </svg>
+                          <span className="font-bold text-sm">Logout</span>
+                        </button>
+                      </div>
+                    )}
+                    {account === null && (
+                      <div className="flex flex-col py-2">
+                        <button
+                          onClick={() => {
+                            dispatch(accountLogout());
+                            navigate("/login");
+                            setIsUserMenuOpen(false);
+                          }}
+                          className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
+                        >
+                          <svg
+                            className="w-5 h-5 text-slate-400"
+                            fill="none"
+                            stroke="currentColor"
+                            viewBox="0 0 24 24"
+                          >
+                            <path
+                              strokeLinecap="round"
+                              strokeLinejoin="round"
+                              strokeWidth={2}
+                              d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
+                            />
+                          </svg>
+                          <span className="font-bold text-sm">Login</span>
+                        </button>
+                      </div>
+                    )}
                   </div>
                 )}
               </div>
@@ -770,20 +902,22 @@ const Header: React.FC = () => {
               >
                 Contact
               </Link>
-              <button
-                onClick={() => {
-                  // onViewChange(ViewMode.ORDER_HISTORY);
-                  navigate("/order-history");
-                  setIsMenuOpen(false);
-                }}
-                className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${
-                  isActive("/order-history")
-                    ? "bg-red-50 text-[#EE0434]"
-                    : "text-slate-800 hover:bg-slate-50"
-                }`}
-              >
-                Transaction History
-              </button>
+
+              {account !== null && (
+                <button
+                  onClick={() => {
+                    navigate("/order-history");
+                    setIsMenuOpen(false);
+                  }}
+                  className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${
+                    isActive("/order-history")
+                      ? "bg-red-50 text-[#EE0434]"
+                      : "text-slate-800 hover:bg-slate-50"
+                  }`}
+                >
+                  Transaction History
+                </button>
+              )}
 
               <div className="w-full pt-4">
                 <p className="text-center text-slate-400 font-bold text-xs uppercase tracking-widest mb-4">
@@ -814,16 +948,30 @@ const Header: React.FC = () => {
           </div>
 
           <div className="p-8 border-t border-slate-50 bg-slate-50/50">
-            <Link
-              to="/login"
-              onClick={() => {
-                setIsMenuOpen(false);
-                dispatch(accountLogout());
-              }}
-              className="w-full bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white py-5 rounded-[40px] font-black text-2xl shadow-xl active:scale-[0.98] transition-all flex justify-center"
-            >
-              Login / Register
-            </Link>
+            {account === null && (
+              <Link
+                to="/login"
+                onClick={() => {
+                  setIsMenuOpen(false);
+                  dispatch(accountLogout());
+                }}
+                className="w-full bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white py-5 rounded-[40px] font-black text-2xl shadow-xl active:scale-[0.98] transition-all flex justify-center"
+              >
+                Login / Register
+              </Link>
+            )}
+            {account !== null && (
+              <Link
+                to="/login"
+                onClick={() => {
+                  setIsMenuOpen(false);
+                  dispatch(accountLogout());
+                }}
+                className="w-full bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white py-5 rounded-[40px] font-black text-2xl shadow-xl active:scale-[0.98] transition-all flex justify-center"
+              >
+                Logout
+              </Link>
+            )}
           </div>
         </div>
       </div>

+ 4 - 4
EsimLao/esim-vite/src/components/TestimonialCard.tsx

@@ -6,19 +6,19 @@ const TestimonialCard: React.FC<{ item: Review }> = ({ item }) => (
   <div className="bg-white/10 backdrop-blur-md border border-white/20 rounded-[24px] md:rounded-[32px] p-5 md:p-6 min-w-[280px] md:min-w-[320px] max-w-[340px] shadow-xl text-white flex flex-col space-y-4">
     <div className="flex items-center space-x-3">
       <img
-        src={item.AvatarUrl}
-        alt={item.CustomerName}
+        src={item.avatarUrl}
+        alt={item.customerName}
         className="w-10 h-10 md:w-12 md:h-12 rounded-full border-2 border-white/50 object-cover"
       />
       <div>
-        <h4 className="font-bold text-sm md:text-lg">{item.CustomerName}</h4>
+        <h4 className="font-bold text-sm md:text-lg">{item.customerName}</h4>
         <p className="text-white/60 text-[10px] md:text-xs">
           {item.destination}
         </p>
       </div>
     </div>
     <div className="flex space-x-1">
-      {[...Array(item.Rating)].map((_, i) => (
+      {[...Array(item.rating)].map((_, i) => (
         <svg
           key={i}
           className="w-3.5 h-3.5 md:w-4 md:h-4 text-yellow-400"

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

@@ -11,7 +11,7 @@ const accountSlice = createSlice({
       console.log("data in slice: ", action.payload);
       localStorage.setItem("token", action.payload.accessToken);
       localStorage.setItem("refreshToken", action.payload.refreshToken);
-      // localStorage.setItem("accountInfo", JSON.stringify(action.payload));
+      localStorage.setItem("accountInfo", JSON.stringify(action.payload));
       return {
         ...state,
         account: action.payload,
@@ -20,7 +20,7 @@ const accountSlice = createSlice({
     accountLogout: (state) => {
       localStorage.removeItem("token");
       localStorage.removeItem("refreshToken");
-      // localStorage.removeItem("accountInfo");
+      localStorage.removeItem("accountInfo");
       return {
         ...state,
         account: null,

+ 1 - 1
EsimLao/esim-vite/src/features/areas/areasSlice.ts

@@ -8,7 +8,7 @@ const areaSlice = createSlice({
     areas: [] as Area[],
   },
   reducers: {
-    setAreas: (state, action: PayloadAction<any>) => {
+    setAreas: (state, action: PayloadAction<Area[]>) => {
       console.log("Setting areas state to: ", action.payload);
       return {
         ...state,

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

@@ -8,6 +8,7 @@ import { productApi } from "../../apis/productApi";
 import { Area } from "../../services/product/type";
 import { DataCacheKey, staleTime } from "../../global/constants";
 import { useNavigate } from "react-router-dom";
+import HomeSearch from "../home/components/HomeSearch";
 
 interface BuySimViewProps {
   onProductSelect: (product: SelectedProduct) => void;
@@ -55,10 +56,10 @@ const BuySimView: React.FC<BuySimViewProps> = ({
   return (
     <div className="bg-[#fcfdfe] min-h-screen">
       <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
-        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 ">
           <button
             onClick={() => onViewChange(ViewMode.HOME)}
-            className="hover:text-[#EE0434]"
+            className="hover:text-[#EE0434] text-[18px]"
           >
             Home
           </button>
@@ -75,34 +76,13 @@ const BuySimView: React.FC<BuySimViewProps> = ({
               strokeLinejoin="round"
             />
           </svg>
-          <span className="text-slate-900 font-bold">Buy Sim</span>
+          <span className="text-slate-900 font-bold text-[18px]">Buy Sim</span>
         </nav>
       </div>
 
-      <div className="max-w-7xl mx-auto px-4 py-8 md:py-12">
-        <div className="flex justify-center mb-12 md:mb-20">
-          <div className="w-full max-w-3xl relative group">
-            <input
-              type="text"
-              placeholder="Choose country you're going to"
-              className="w-full bg-white rounded-full py-4 md:py-6 px-10 md:px-12 text-base md:text-xl text-slate-700 shadow-sm border border-slate-100 outline-none focus:ring-4 focus:ring-red-50 transition-all placeholder:text-slate-300"
-            />
-            <button className="absolute right-2 md:right-3 top-2 md:top-3 bottom-2 md:bottom-3 aspect-square bg-[#EE0434] rounded-full flex items-center justify-center text-white shadow-lg shadow-red-200 hover:scale-105 transition-all">
-              <svg
-                className="w-5 h-5"
-                fill="none"
-                stroke="currentColor"
-                viewBox="0 0 24 24"
-              >
-                <path
-                  d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
-                  strokeWidth={2.5}
-                  strokeLinecap="round"
-                  strokeLinejoin="round"
-                />
-              </svg>
-            </button>
-          </div>
+      <div className="max-w-7xl mx-auto px-4">
+        <div className="mb-12">
+          <HomeSearch />
         </div>
         <section className="mb-12">
           <h2 className="text-xl md:text-[32px] font-black text-slate-900 mb-6 md:mb-8 tracking-tight">

+ 12 - 11
EsimLao/esim-vite/src/pages/checkout/CheckoutView.tsx

@@ -135,7 +135,7 @@ const CheckoutView = () => {
         <div className="flex items-center space-x-2">
           <button
             onClick={() => navigate(-1)}
-            className="flex items-center text-slate-900 font-bold hover:text-[#EE0434] transition-colors"
+            className="flex items-center text-slate-900 font-bold hover:text-[#EE0434] transition-colors text-[18px]"
           >
             <svg
               className="w-5 h-5 mr-1"
@@ -292,18 +292,18 @@ const CheckoutView = () => {
               </div>
               <div className="text-right">
                 <p className="text-xs text-slate-400 line-through font-bold">
-                  $
                   {state.checkoutDetails.paymentMoney.toLocaleString("vi-VN", {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
-                  })}
+                  })}{" "}
+                  {state.checkoutDetails.curency}
                 </p>
                 <p className="text-[#0071e3] font-black text-xl">
-                  $
                   {state.checkoutDetails.totalMoney.toLocaleString("vi-VN", {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
-                  })}
+                  })}{" "}
+                  {state.checkoutDetails.curency}
                 </p>
               </div>
             </div>
@@ -408,23 +408,24 @@ const CheckoutView = () => {
               <div className="flex justify-between text-slate-600 font-bold">
                 <span>Subtotal:</span>
                 <span>
-                  $
                   {(
                     state.checkoutDetails.totalMoney * state.quantity
                   ).toLocaleString("vi-VN", {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
-                  })}
+                  })}{" "}
+                  {state.checkoutDetails.curency}
                 </span>
               </div>
               <div className="flex justify-between text-green-600 font-bold">
                 <span>Discount:</span>
                 <span>
-                  -$
+                  -
                   {(0 * state.quantity).toLocaleString("vi-VN", {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
-                  })}
+                  })}{" "}
+                  {state.checkoutDetails.curency}
                 </span>
               </div>
               <div className="flex justify-between items-center pt-4 border-t border-slate-200/50">
@@ -432,11 +433,11 @@ const CheckoutView = () => {
                   Total ({state.quantity}):
                 </span>
                 <span className="text-[#0071e3] font-black text-3xl">
-                  $
                   {state.checkoutDetails.paymentMoney.toLocaleString("vi-VN", {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
-                  })}
+                  })}{" "}
+                  {state.checkoutDetails.curency}
                 </span>
               </div>
             </div>

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

@@ -33,10 +33,10 @@ const ContactView: React.FC = () => {
                   <span className="font-bold text-lg">Email</span>
                 </div>
                 <a
-                  href="mailto:info@getgo.global"
+                  href="mailto:sale.admin@viettech.asia"
                   className="block text-[#EE0434] font-black text-lg md:text-xl pl-9 hover:underline"
                 >
-                  info@getgo.global
+                  sale.admin@viettech.asia
                 </a>
               </div>
             </div>

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

@@ -8,6 +8,8 @@ import HomeSearch from "./components/HomeSearch";
 import HomeFaq from "./components/HomeFaq";
 import { useAppDispatch } from "../../hooks/useRedux";
 import { openPopup } from "../../features/popup/popupSlice";
+import partner1 from "../../assets/img/partner1.png";
+import partner2 from "../../assets/img/partner2.png";
 
 const HomeView: React.FC = () => {
   const [simType, setSimType] = useState<"eSIM" | "Physical">("eSIM");
@@ -276,15 +278,13 @@ const HomeView: React.FC = () => {
               <div className="flex flex-wrap gap-3 md:gap-8 justify-center lg:justify-start">
                 <div className="bg-white border border-[#EE0434]/20 rounded-xl px-4 py-2 w-36 md:w-56 h-14 flex items-center justify-center shadow-sm">
                   <img
-                    src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Vietnam_Airlines_logo_2015.svg/1280px-Vietnam_Airlines_logo_2015.svg.png"
-                    alt="Vietnam Airlines"
+                    src={partner1}
                     className="max-h-full max-w-full object-contain"
                   />
                 </div>
                 <div className="bg-white border border-[#EE0434]/20 rounded-xl px-4 py-2 w-36 md:w-56 h-14 flex items-center justify-center shadow-sm">
                   <img
-                    src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Logo_Viettel.svg/1280px-Logo_Viettel.svg.png"
-                    alt="Viettel"
+                    src={partner2}
                     className="max-h-full max-w-full object-contain"
                   />
                 </div>

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

@@ -64,6 +64,7 @@ const HomeBanner = () => {
               className={`absolute inset-0 bg-gradient-to-br ${
                 gradients[idx % gradients.length]
               } opacity-90 transition-all`}
+              style={{ backgroundImage: `url(${banner.imageUrl})` }}
             ></div>
 
             <div className="relative z-30 flex flex-col items-center justify-center p-6 md:p-24 w-full h-full text-center">

+ 63 - 23
EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx

@@ -10,36 +10,70 @@ import {
 } from "../../../features/loading/loadingSlice";
 import { DataCacheKey, staleTime } from "../../../global/constants";
 import { Area } from "../../../services/product/type";
-import { setAreas } from "../../../features/areas/areasSlice";
 
 const HomeProduct = () => {
   const [activeTab, setActiveTab] = useState<"country" | "region">("country");
   const navigate = useNavigate();
+  const [areasList, setAreasList] = useState<Area[]>([]);
 
   const dispatch = useAppDispatch();
 
-  useEffect(() => {}, []);
+  useEffect(() => {
+    getAreaMutation.mutate();
+  }, []);
 
-  const { data: loadArea = [] } = useQuery<Area[]>({
-    queryKey: [DataCacheKey.AREAS],
-    queryFn: async (): Promise<Area[]> => {
-      try {
-        dispatch(startLoading({}));
-        const res = await productApi.loadArea({
-          isCountry: "-1",
-          isPopular: "1",
-        });
-        // save to redux store
-        dispatch(setAreas(res.data as Area[]));
-        return res.data as Area[];
-      } catch (error) {
-        console.error(error);
-        return []; // 🔴 bắt buộc
-      } finally {
-        dispatch(stopLoading());
+  // const { data: loadArea = [] } = useQuery<Area[]>({
+  //   queryKey: [DataCacheKey.AREAS],
+  //   queryFn: async (): Promise<Area[]> => {
+  //     try {
+  //       dispatch(startLoading({}));
+  //       const res = await productApi.loadArea({
+  //         isCountry: "-1",
+  //         isPopular: "1",
+  //       });
+  //       // save to redux store
+  //       dispatch(setAreas(res.data as Area[]));
+  //       return res.data as Area[];
+  //     } catch (error) {
+  //       console.error(error);
+  //       return []; // 🔴 bắt buộc
+  //     } finally {
+  //       dispatch(stopLoading());
+  //     }
+  //   },
+  //   staleTime: staleTime,
+  // });
+
+  const getAreaMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await productApi.loadArea(
+        activeTab === "country"
+          ? {
+              isCountry: "1",
+              isPopular: "-1",
+            }
+          : {
+              isCountry: "0",
+              isPopular: "-1",
+            }
+      );
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get area response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Get area successful");
+        setAreasList(data.data as Area[]);
+      } else {
+        console.error("Get area failed, no token received");
       }
     },
-    staleTime: staleTime,
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get area error:", error.response.data);
+    },
   });
 
   const handleProductClick = (p: Area) => {
@@ -62,7 +96,10 @@ const HomeProduct = () => {
 
       <div className="inline-flex p-1.5 bg-slate-100 rounded-full mb-12 md:mb-16 shadow-inner">
         <button
-          onClick={() => setActiveTab("country")}
+          onClick={() => {
+            setActiveTab("country");
+            getAreaMutation.mutate();
+          }}
           className={`px-8 md:px-12 py-3 rounded-full text-xs md:text-sm font-black transition-all ${
             activeTab === "country"
               ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-lg"
@@ -72,7 +109,10 @@ const HomeProduct = () => {
           Country List
         </button>
         <button
-          onClick={() => setActiveTab("region")}
+          onClick={() => {
+            setActiveTab("region");
+            getAreaMutation.mutate();
+          }}
           className={`px-8 md:px-12 py-3 rounded-full text-xs md:text-sm font-black transition-all ${
             activeTab === "region"
               ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-lg"
@@ -85,7 +125,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">
-          {loadArea.map((p) => (
+          {areasList.map((p) => (
             <div
               key={p.id}
               onClick={() => handleProductClick(p)}

+ 166 - 26
EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx

@@ -1,37 +1,177 @@
 import { openCompatibilityModal } from "../../../features/popup/popupSlice";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
+import { useState, useRef, useEffect } from "react";
+import { useMutation } from "@tanstack/react-query";
+import {
+  startLoading,
+  stopLoading,
+} from "../../../features/loading/loadingSlice";
+import { productApi } from "../../../apis/productApi";
+import { setAreas } from "../../../features/areas/areasSlice";
+import { Area } from "../../../services/product/type";
+import { useNavigate } from "react-router";
 
 const HomeSearch = () => {
+  const areas = useSelector((state: any) => state.areas.areas);
   const dispatch = useDispatch();
+  const navigate = useNavigate();
+  const [searchQuery, setSearchQuery] = useState("");
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+  const dropdownRef = useRef<HTMLDivElement>(null);
+  const [areasList, setAreasList] = useState<Area[]>([]);
+
+  useEffect(() => {
+    if (!areas || areas.length === 0) getAreaMutation.mutate();
+    else {
+      setAreasList(areas);
+      console.log("Areas loaded from store:", areas);
+    }
+  }, []);
+
+  const getAreaMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await productApi.loadArea({
+        isCountry: "-1",
+        isPopular: "-1",
+      });
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get area response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Get area successful");
+        dispatch(setAreas(data.data as Area[]));
+        setAreasList(data.data as Area[]);
+      } else {
+        console.error("Get area failed, no token received");
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get area error:", error.response.data);
+    },
+  });
+
+  const handleSelect = (area: Area) => {
+    console.log("Selected area:", area);
+    setIsDropdownOpen(false);
+    navigate(`/product/${area.id}`, {
+      state: {
+        ...area,
+      },
+    });
+  };
+
+  const handleSearch = (query: string) => {
+    setSearchQuery(query);
+    if (query.trim() === "") {
+      setAreasList(areas);
+    } else {
+      const filtered = areas.filter((area: Area) =>
+        area.areaName1.toLowerCase().includes(query.toLowerCase())
+      );
+      setAreasList(filtered);
+    }
+  };
+
+  useEffect(() => {
+    const handleClickOutside = (event: MouseEvent) => {
+      if (
+        dropdownRef.current &&
+        !dropdownRef.current.contains(event.target as Node)
+      ) {
+        setIsDropdownOpen(false);
+      }
+    };
+    document.addEventListener("mousedown", handleClickOutside);
+    return () => {
+      document.removeEventListener("mousedown", handleClickOutside);
+    };
+  }, []);
+
   return (
     <section className="pt-8 pb-4 px-4 relative z-40 bg-white">
       <div className="max-w-4xl mx-auto w-full relative">
-        <div className="relative group">
-          <input
-            type="text"
-            placeholder="Where are you going?"
-            className="w-full bg-white text-slate-900 rounded-full py-4 md:py-6 px-8 md:px-12 text-base md:text-xl shadow-[0_10px_40px_-10px_rgba(0,0,0,0.15)] border border-slate-100 outline-none focus:ring-4 focus:ring-[#EE0434]/10 transition-all placeholder:text-slate-400 pr-16 md:pr-48"
-          />
-          <button className="absolute right-2 top-2 bottom-2 aspect-square md:aspect-auto md:px-10 bg-gradient-to-r from-[#E21c34] to-[#500B28] rounded-full flex items-center justify-center text-white shadow-lg hover:shadow-[#EE0434]/25 hover:scale-[1.02] active:scale-95 transition-all">
-            <svg
-              className="w-6 h-6 md:hidden"
-              fill="none"
-              stroke="currentColor"
-              viewBox="0 0 24 24"
-            >
-              <path
-                d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
-                strokeWidth={3}
-                strokeLinecap="round"
-                strokeLinejoin="round"
-              />
-            </svg>
-            <span className="hidden md:block font-bold text-lg tracking-wide">
-              Search Destination
-            </span>
-          </button>
+        <div className="flex justify-center mb-2 md:mb-2" ref={dropdownRef}>
+          <div className="w-full max-w-3xl relative group z-30">
+            <input
+              type="text"
+              placeholder="Choose country you're going to"
+              value={searchQuery}
+              onChange={(e) => {
+                handleSearch(e.target.value);
+                setIsDropdownOpen(true);
+              }}
+              onFocus={() => setIsDropdownOpen(true)}
+              className="w-full bg-white rounded-full py-4 md:py-6 px-10 md:px-12 text-base md:text-xl text-slate-700 shadow-sm border border-slate-100 outline-none focus:ring-4 focus:ring-red-50 transition-all placeholder:text-slate-300"
+            />
+            <button className="absolute right-2 md:right-3 top-2 md:top-3 bottom-2 md:bottom-3 aspect-square bg-[#EE0434] rounded-full flex items-center justify-center text-white shadow-lg shadow-red-200 hover:scale-105 transition-all">
+              <svg
+                className="w-5 h-5"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
+                  strokeWidth={2.5}
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                />
+              </svg>
+            </button>
+
+            {/* Dropdown Menu */}
+            {isDropdownOpen && (
+              <div className="absolute top-full left-0 right-0 mt-3 bg-white rounded-[24px] shadow-[0_20px_60px_-15px_rgba(0,0,0,0.15)] border border-slate-100 overflow-hidden animate-in fade-in zoom-in-95 duration-200">
+                <div className="max-h-[400px] overflow-y-auto custom-scrollbar p-4">
+                  <h3 className="text-xs font-bold text-slate-400 uppercase tracking-widest mb-4 px-2 sticky top-0 bg-white z-10 py-2">
+                    Most Popular
+                  </h3>
+                  <div className="space-y-1">
+                    {areasList.length > 0 ? (
+                      areasList.map((p) => (
+                        <button
+                          key={p.id}
+                          onClick={() => handleSelect(p)}
+                          className="w-full flex items-center justify-between p-3 hover:bg-slate-50 rounded-2xl transition-all group"
+                        >
+                          <div className="flex items-center space-x-4">
+                            <div className="w-10 h-10 rounded-full border border-slate-100 overflow-hidden shadow-sm">
+                              <img
+                                src={`${p.iconUrl}`}
+                                alt={p.areaName1}
+                                className="w-full h-full object-cover scale-150"
+                              />
+                            </div>
+                            <span className="font-bold text-slate-700 text-lg group-hover:text-[#EE0434] transition-colors">
+                              {p.areaName1}
+                            </span>
+                          </div>
+                          <div className="text-right flex items-center space-x-2">
+                            <span className="text-sm text-slate-400 font-medium">
+                              from:
+                            </span>
+                            <span className="text-lg font-black text-[#EE0434]">
+                              {p.minSellPrice.toLocaleString()} {p.curency}
+                            </span>
+                          </div>
+                        </button>
+                      ))
+                    ) : (
+                      <div className="text-center py-8 text-slate-400 font-medium">
+                        No countries found matching "{searchQuery}"
+                      </div>
+                    )}
+                  </div>
+                </div>
+              </div>
+            )}
+          </div>
         </div>
-        <div className="mt-3 flex justify-center gap-2 text-sm text-slate-400 font-medium">
+        <div className="flex justify-center gap-2 text-sm text-slate-400 font-medium">
           <a
             className="hover:text-[#EE0434] transition-colors underline color-hover cursor-pointer text-[18px] font-medium"
             onClick={() => {

+ 87 - 45
EsimLao/esim-vite/src/pages/login/LoginView.tsx

@@ -11,19 +11,21 @@ import React, { useState } from "react";
 import { useNavigate } from "react-router-dom";
 import { useEffect, useRef } from "react";
 import { accountLogin } from "../../features/account/accuntSlice";
+import { time } from "console";
 
 const LoginView: React.FC = () => {
   const navigate = useNavigate();
   const loading = useAppSelector((state) => state.loading);
   const dispatch = useAppDispatch();
-  const [email, setEmail] = useState("abc@gmail.com");
+  const [email, setEmail] = useState("");
   const [step, setStep] = useState<"email" | "otp">("email");
 
   const [otp, setOtp] = useState("");
-  const [timer, setTimer] = useState(60);
+  const [timer, setTimer] = useState(120);
+  const intervalRef = useRef<NodeJS.Timeout | null>(null);
   const inputRef = useRef<HTMLInputElement>(null);
   const [errorMessage, setErrorMessage] = useState("");
-
+  let interval;
   useEffect(() => {
     // Focus input on mount
     inputRef.current?.focus();
@@ -48,11 +50,21 @@ const LoginView: React.FC = () => {
     verifyOtpMutation.mutate();
   };
 
+  const resetTimer = (time: number) => {
+    setTimer(time);
+
+    if (intervalRef.current) {
+      clearInterval(intervalRef.current);
+    }
+
+    intervalRef.current = setInterval(() => {
+      setTimer((prev) => (prev > 0 ? prev - 1 : 0));
+    }, 1000);
+  };
+
   const handleResend = () => {
-    setTimer(60);
     setOtp("");
-    // Re-focus input after a slight delay to allow render update
-    setTimeout(() => inputRef.current?.focus(), 50);
+    resendOtpMutation.mutate();
   };
 
   const getOtpMutation = useMutation({
@@ -63,17 +75,15 @@ const LoginView: React.FC = () => {
     },
     onSuccess: (data) => {
       dispatch(stopSmallLoading());
+      setErrorMessage("");
       console.log("Get otp response data:", data);
       if (data && data.errorCode === "0") {
         console.log("Get otp successful");
         // show otp step
         // Countdown timer
         setStep("otp");
-
-        const interval = setInterval(() => {
-          setTimer((prev) => (prev > 0 ? prev - 1 : 0));
-        }, 1000);
-        return () => clearInterval(interval);
+        setErrorMessage("");
+        resetTimer(data.data.expireInSeconds || 120);
       } else {
         console.error("Get otp failed, no token received");
         setErrorMessage(
@@ -87,6 +97,34 @@ const LoginView: React.FC = () => {
     },
   });
 
+  const resendOtpMutation = useMutation({
+    mutationFn: async () => {
+      dispatch(startSmallLoading());
+      const res = await authApi.resendOtp({ email });
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopSmallLoading());
+      setErrorMessage("");
+      console.log("Resend otp response data:", data);
+      if (data && data.errorCode === "0") {
+        console.log("Resend otp successful");
+        // Countdown timer
+        setErrorMessage("");
+        resetTimer(data.data.expireInSeconds || 120);
+      } else {
+        console.error("Resend otp failed, no token received");
+        setErrorMessage(
+          data?.message || "Failed to request OTP. Please try again."
+        );
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopSmallLoading());
+      console.error("Get otp error:", error.response.data);
+    },
+  });
+
   const verifyOtpMutation = useMutation({
     mutationFn: async () => {
       dispatch(startSmallLoading());
@@ -123,24 +161,25 @@ const LoginView: React.FC = () => {
   return (
     <div className="min-h-screen bg-white flex flex-col lg:flex-row overflow-hidden">
       <div className="w-full lg:w-1/2 flex flex-col justify-center items-center p-6 md:p-12 lg:p-20 relative z-10 bg-white">
-        <button
-          onClick={() => navigate(-1)}
-          className="absolute top-6 left-6 lg:hidden p-2 text-slate-400 hover:text-[#EE0434] transition-colors"
-        >
-          <svg
-            className="w-6 h-6"
-            fill="none"
-            stroke="currentColor"
-            viewBox="0 0 24 24"
+        {/* <button
+            onClick={() => navigate(-1)}
+            className="absolute top-6 left-6 lg:hidden p-2 text-slate-400 hover:text-[#EE0434] transition-colors"
           >
-            <path
-              strokeLinecap="round"
-              strokeLinejoin="round"
-              strokeWidth={2.5}
-              d="M15 19l-7-7 7-7"
-            />
-          </svg>
-        </button>
+            <svg
+              className="w-6 h-6"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2.5}
+                d="M15 19l-7-7 7-7"
+              />
+            </svg>
+          </button> */}
+
         <div className="w-full max-w-[420px] space-y-10">
           <div className="space-y-4 text-center lg:text-left">
             <div className="lg:hidden flex justify-center mb-8">
@@ -206,24 +245,27 @@ const LoginView: React.FC = () => {
           )}
           {step === "otp" && (
             <>
-              <button
-                onClick={() => setStep("email")}
-                className="absolute top-6 left-6 p-2 text-slate-400 hover:text-[#EE0434] transition-colors"
-              >
-                <svg
-                  className="w-6 h-6"
-                  fill="none"
-                  stroke="currentColor"
-                  viewBox="0 0 24 24"
+              {" "}
+              {timer === 0 && step === "otp" && (
+                <button
+                  onClick={() => setStep("email")}
+                  className="absolute top-6 left-6 p-2 text-slate-400 hover:text-[#EE0434] transition-colors"
                 >
-                  <path
-                    strokeLinecap="round"
-                    strokeLinejoin="round"
-                    strokeWidth={2.5}
-                    d="M15 19l-7-7 7-7"
-                  />
-                </svg>
-              </button>
+                  <svg
+                    className="w-6 h-6"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2.5}
+                      d="M15 19l-7-7 7-7"
+                    />
+                  </svg>
+                </button>
+              )}
               <div className="w-full max-w-[420px] space-y-10">
                 <div className="space-y-4 text-center lg:text-left">
                   <h1 className="text-3xl md:text-4xl lg:text-5xl font-black text-slate-900 tracking-tight">

+ 1 - 1
EsimLao/esim-vite/src/pages/news/NewsDetailView.tsx

@@ -126,7 +126,7 @@ const ArticleDetailView: React.FC = () => {
         <div className="mb-8 md:mb-12">
           <div className="aspect-[21/9] rounded-[24px] md:rounded-[40px] overflow-hidden shadow-2xl">
             <img
-              src={article.coverImageUrl}
+              src={articleDetail?.coverImageUrl}
               alt={article.title}
               className="w-full h-full object-cover"
             />

+ 2 - 2
EsimLao/esim-vite/src/pages/news/NewsView.tsx

@@ -44,7 +44,7 @@ const NewsView: React.FC = () => {
     <div className="bg-[#fcfdfe] min-h-screen">
       <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
         <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
-          <Link to="/" className="hover:text-blue-500">
+          <Link to="/" className="hover:text-blue-500 text-[18px]">
             Home
           </Link>
           <svg
@@ -60,7 +60,7 @@ const NewsView: React.FC = () => {
               strokeLinejoin="round"
             />
           </svg>
-          <span className="text-slate-900 font-bold">News</span>
+          <span className="text-slate-900 font-bold text-[18px]">News</span>
         </nav>
       </div>
 

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

@@ -75,7 +75,7 @@ const OrderHistoryView = () => {
         <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
           <button
             // onClick={() => onViewChange(ViewMode.HOME)}
-            className="hover:text-[#EE0434] text-[16px]"
+            className="hover:text-[#EE0434] text-[18px]"
           >
             Home
           </button>
@@ -92,7 +92,7 @@ const OrderHistoryView = () => {
               strokeLinejoin="round"
             />
           </svg>
-          <span className="text-slate-900 font-bold text-[16px]">
+          <span className="text-slate-900 font-bold text-[18px]">
             Transaction History
           </span>
         </nav>

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

@@ -221,7 +221,7 @@ const ProductDetailView: React.FC = () => {
         <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500">
           <Link
             to="/"
-            className="hover:text-[#EE0434] transition-colors text-[16px]"
+            className="hover:text-[#EE0434] transition-colors text-[18px]"
           >
             Home
           </Link>
@@ -238,7 +238,7 @@ const ProductDetailView: React.FC = () => {
               strokeLinejoin="round"
             />
           </svg>
-          <span className="text-slate-900 font-bold text-[16px]">
+          <span className="text-slate-900 font-bold text-[18px]">
             {area.areaName1}
           </span>
         </nav>
@@ -382,19 +382,19 @@ const ProductDetailView: React.FC = () => {
                   -{prices.discountPercent}%
                 </span>
                 <span className="text-slate-300 font-bold text-xs line-through">
-                  $
                   {prices.original.toLocaleString("vi-VN", {
                     minimumFractionDigits: 2,
                     maximumFractionDigits: 2,
-                  })}
+                  })}{" "}
+                  {area.curency}
                 </span>
               </div>
               <span className="text-[#EE0434] font-black text-2xl md:text-3xl">
-                $
                 {prices.final.toLocaleString("vi-VN", {
                   minimumFractionDigits: 2,
                   maximumFractionDigits: 2,
-                })}
+                })}{" "}
+                {area.curency}
               </span>
             </div>
             <button

+ 96 - 25
EsimLao/esim-vite/src/pages/support/SupportView.tsx

@@ -1,42 +1,83 @@
-
-import React, { useState } from 'react';
-import { Link } from 'react-router-dom';
+import React, { useState } from "react";
+import { Link } from "react-router-dom";
 
 const SupportView: React.FC = () => {
-  const [activeItem, setActiveItem] = useState('What is travel eSIM/SIM?');
+  const [activeItem, setActiveItem] = useState("What is travel eSIM/SIM?");
 
   const categories = [
     {
-      title: 'What is travel eSIM/SIM?',
+      title: "What is travel eSIM/SIM?",
       count: 3,
       icon: (
-        <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 00-3 3z" />
+        <svg
+          className="w-6 h-6"
+          fill="none"
+          stroke="currentColor"
+          viewBox="0 0 24 24"
+        >
+          <path
+            strokeLinecap="round"
+            strokeLinejoin="round"
+            strokeWidth={2}
+            d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 00-3 3z"
+          />
         </svg>
       ),
-      items: ['What is travel eSIM/SIM', 'Physical travel SIM', 'How to buy travel eSIM/SIM'],
-      color: 'bg-red-500'
+      items: [
+        "What is travel eSIM/SIM",
+        "Physical travel SIM",
+        "How to buy travel eSIM/SIM",
+      ],
+      color: "bg-red-500",
     },
     {
-      title: 'eSIM installation and activation',
+      title: "eSIM installation and activation",
       count: 3,
       icon: (
-        <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
+        <svg
+          className="w-6 h-6"
+          fill="none"
+          stroke="currentColor"
+          viewBox="0 0 24 24"
+        >
+          <path
+            strokeLinecap="round"
+            strokeLinejoin="round"
+            strokeWidth={2}
+            d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"
+          />
         </svg>
       ),
-      items: ['Email and eSIM QR code', 'Installation guide for iPhone (iOS)', 'Installation guide for Android'],
-      color: 'bg-red-600'
-    }
+      items: [
+        "Email and eSIM QR code",
+        "Installation guide for iPhone (iOS)",
+        "Installation guide for Android",
+      ],
+      color: "bg-red-600",
+    },
   ];
 
   return (
     <div className="bg-white min-h-screen">
       <div className="max-w-7xl mx-auto px-4 py-6">
         <nav className="flex items-center space-x-2 text-sm text-slate-500 font-medium">
-          <Link to="/" className="hover:text-[#EE0434]">Home</Link>
-          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/></svg>
-          <span className="text-slate-900 font-bold">Support</span>
+          <Link to="/" className="hover:text-[#EE0434] text-[18px]">
+            Home
+          </Link>
+          <svg
+            className="w-4 h-4"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <span className="text-slate-900 font-bold text-[18px]">Support</span>
         </nav>
       </div>
 
@@ -46,14 +87,38 @@ const SupportView: React.FC = () => {
             {categories.map((cat, idx) => (
               <div key={idx} className="space-y-4">
                 <div className="flex items-center space-x-4">
-                  <div className={`w-10 h-10 rounded-xl ${cat.color} flex items-center justify-center text-white shadow-md`}>{cat.icon}</div>
-                  <h3 className="text-[17px] font-black text-[#EE0434] leading-tight">{cat.title}</h3>
+                  <div
+                    className={`w-10 h-10 rounded-xl ${cat.color} flex items-center justify-center text-white shadow-md`}
+                  >
+                    {cat.icon}
+                  </div>
+                  <h3 className="text-[17px] font-black text-[#EE0434] leading-tight">
+                    {cat.title}
+                  </h3>
                 </div>
                 <div className="pl-5 border-l-2 border-slate-50 space-y-4 ml-5">
                   {cat.items.map((item, i) => (
-                    <button key={i} onClick={() => setActiveItem(item)} className="group flex items-center justify-between w-full text-left relative">
-                      <div className={`absolute -left-[22px] w-2 h-2 rounded-full border-2 border-white ${activeItem === item ? 'bg-[#EE0434] scale-125' : 'bg-slate-200'}`}></div>
-                      <span className={`text-[14px] font-bold transition-colors pl-4 ${activeItem === item ? 'text-[#EE0434]' : 'text-slate-500 group-hover:text-[#EE0434]'}`}>{item}</span>
+                    <button
+                      key={i}
+                      onClick={() => setActiveItem(item)}
+                      className="group flex items-center justify-between w-full text-left relative"
+                    >
+                      <div
+                        className={`absolute -left-[22px] w-2 h-2 rounded-full border-2 border-white ${
+                          activeItem === item
+                            ? "bg-[#EE0434] scale-125"
+                            : "bg-slate-200"
+                        }`}
+                      ></div>
+                      <span
+                        className={`text-[14px] font-bold transition-colors pl-4 ${
+                          activeItem === item
+                            ? "text-[#EE0434]"
+                            : "text-slate-500 group-hover:text-[#EE0434]"
+                        }`}
+                      >
+                        {item}
+                      </span>
                     </button>
                   ))}
                 </div>
@@ -61,8 +126,14 @@ const SupportView: React.FC = () => {
             ))}
           </aside>
           <main className="lg:col-span-8">
-            <h1 className="text-4xl md:text-5xl font-black text-[#EE0434] mb-10">Guide & Help</h1>
-            <p className="text-lg leading-relaxed text-slate-600">Select a topic from the left to view detailed instructions for your travel SIM or eSIM. Our support team is also available 24/7 via chat.</p>
+            <h1 className="text-4xl md:text-5xl font-black text-[#EE0434] mb-10">
+              Guide & Help
+            </h1>
+            <p className="text-lg leading-relaxed text-slate-600">
+              Select a topic from the left to view detailed instructions for
+              your travel SIM or eSIM. Our support team is also available 24/7
+              via chat.
+            </p>
           </main>
         </div>
       </div>

+ 4 - 0
EsimLao/esim-vite/src/services/auth/types.ts

@@ -7,3 +7,7 @@ export interface AccountInfo {
   refreshToken: string;
   expiresAt: string;
 }
+
+export interface RequestOtpResponse {
+  expireInSeconds: number;
+}