|
@@ -1,5 +1,5 @@
|
|
|
import React, { useState, useMemo, useEffect } from "react";
|
|
import React, { useState, useMemo, useEffect } from "react";
|
|
|
-import { useLocation, useNavigate, Link } from "react-router-dom";
|
|
|
|
|
|
|
+import { useLocation, useNavigate, Link, useParams } from "react-router-dom";
|
|
|
import { SelectedProduct } from "../../services/types";
|
|
import { SelectedProduct } from "../../services/types";
|
|
|
import { Area, Package } from "../../services/product/type";
|
|
import { Area, Package } from "../../services/product/type";
|
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
@@ -8,14 +8,19 @@ import { useAppDispatch, useAppSelector } from "../../hooks/useRedux";
|
|
|
import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
|
|
import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
|
|
|
import { productApi } from "../../apis/productApi";
|
|
import { productApi } from "../../apis/productApi";
|
|
|
import { openPopup } from "../../features/popup/popupSlice";
|
|
import { openPopup } from "../../features/popup/popupSlice";
|
|
|
-import { get } from "http";
|
|
|
|
|
import { formatNumber } from "../../logic/loigicUtils";
|
|
import { formatNumber } from "../../logic/loigicUtils";
|
|
|
|
|
+import ProductInfoModal from "../../components/ProductInfoModal";
|
|
|
|
|
+import PackageOverview from "./components/PackageOverview";
|
|
|
|
|
+import ProductCard from "../../components/ProductCard";
|
|
|
|
|
+import { useTranslation } from "react-i18next";
|
|
|
|
|
|
|
|
const ProductDetailView: React.FC = () => {
|
|
const ProductDetailView: React.FC = () => {
|
|
|
const location = useLocation();
|
|
const location = useLocation();
|
|
|
const navigate = useNavigate();
|
|
const navigate = useNavigate();
|
|
|
const dispatch = useAppDispatch();
|
|
const dispatch = useAppDispatch();
|
|
|
|
|
+ const { t } = useTranslation();
|
|
|
const area = location.state as Area;
|
|
const area = location.state as Area;
|
|
|
|
|
+ const { id } = useParams<{ id: string }>();
|
|
|
const loading = useAppSelector((state) => state.loading);
|
|
const loading = useAppSelector((state) => state.loading);
|
|
|
const [selectedDays, setSelectedDays] = useState<number>(null);
|
|
const [selectedDays, setSelectedDays] = useState<number>(null);
|
|
|
const [selectedData, setSelectedData] = useState<string>("Unlimited");
|
|
const [selectedData, setSelectedData] = useState<string>("Unlimited");
|
|
@@ -23,6 +28,7 @@ const ProductDetailView: React.FC = () => {
|
|
|
const [dataOptions, setDataOptions] = useState<string[]>([]);
|
|
const [dataOptions, setDataOptions] = useState<string[]>([]);
|
|
|
const [daysActiveOptions, setDaysActiveOptions] = useState<number[]>([]);
|
|
const [daysActiveOptions, setDaysActiveOptions] = useState<number[]>([]);
|
|
|
const [dataActiveOptions, setDataActiveOptions] = useState<string[]>([]);
|
|
const [dataActiveOptions, setDataActiveOptions] = useState<string[]>([]);
|
|
|
|
|
+ const [relatedAreas, setRelatedAreas] = useState<Area[]>([]);
|
|
|
const [prices, setPrices] = useState<{
|
|
const [prices, setPrices] = useState<{
|
|
|
original: string;
|
|
original: string;
|
|
|
final: string;
|
|
final: string;
|
|
@@ -36,7 +42,9 @@ const ProductDetailView: React.FC = () => {
|
|
|
const [quantity, setQuantity] = useState<number>(1);
|
|
const [quantity, setQuantity] = useState<number>(1);
|
|
|
const [packages, setPackages] = useState<Package[]>([]);
|
|
const [packages, setPackages] = useState<Package[]>([]);
|
|
|
|
|
|
|
|
- if (!area) {
|
|
|
|
|
|
|
+ const [selectedPackage, setSelectedPackage] = useState<Package | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ if (!id) {
|
|
|
return (
|
|
return (
|
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
|
<div className="text-center">
|
|
<div className="text-center">
|
|
@@ -49,38 +57,41 @@ const ProductDetailView: React.FC = () => {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // const { data: loadPackage = [] } = useQuery<Package[]>({
|
|
|
|
|
- // queryKey: [DataCacheKey.PACKAGES],
|
|
|
|
|
- // queryFn: async (): Promise<Package[]> => {
|
|
|
|
|
- // try {
|
|
|
|
|
- // dispatch(startLoading({}));
|
|
|
|
|
- // const res = await productApi.loadPackage({
|
|
|
|
|
- // areaId: area.id,
|
|
|
|
|
- // isUnlimited: "-1",
|
|
|
|
|
- // isDaily: "-1",
|
|
|
|
|
- // });
|
|
|
|
|
- // return res.data as Package[];
|
|
|
|
|
- // } catch (error) {
|
|
|
|
|
- // console.error(error);
|
|
|
|
|
- // return []; // 🔴 bắt buộc
|
|
|
|
|
- // } finally {
|
|
|
|
|
- // dispatch(stopLoading());
|
|
|
|
|
- // }
|
|
|
|
|
- // },
|
|
|
|
|
- // staleTime: staleTime,
|
|
|
|
|
- // });
|
|
|
|
|
-
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
getProductMutation.mutate();
|
|
getProductMutation.mutate();
|
|
|
- }, [area]);
|
|
|
|
|
|
|
+ getRelatedAreaMutation.mutate();
|
|
|
|
|
+ }, [id]);
|
|
|
|
|
|
|
|
const getProductMutation = useMutation({
|
|
const getProductMutation = useMutation({
|
|
|
mutationFn: async () => {
|
|
mutationFn: async () => {
|
|
|
dispatch(startLoading({}));
|
|
dispatch(startLoading({}));
|
|
|
const res = await productApi.loadPackage({
|
|
const res = await productApi.loadPackage({
|
|
|
- areaId: area.id,
|
|
|
|
|
- isUnlimited: "-1",
|
|
|
|
|
- isDaily: "-1",
|
|
|
|
|
|
|
+ areaId: id,
|
|
|
|
|
+ dataType: "-1",
|
|
|
|
|
+ });
|
|
|
|
|
+ return res;
|
|
|
|
|
+ },
|
|
|
|
|
+ onSuccess: (data) => {
|
|
|
|
|
+ dispatch(stopLoading());
|
|
|
|
|
+ console.log("Get package response data:", data);
|
|
|
|
|
+ if (data && data.errorCode === "0") {
|
|
|
|
|
+ const packages = data.data as Package[];
|
|
|
|
|
+ setPackages(packages);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.error("Get package failed, no token received");
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error: any) => {
|
|
|
|
|
+ dispatch(stopLoading());
|
|
|
|
|
+ console.error("Get package error:", error.response.data);
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const getRelatedAreaMutation = useMutation({
|
|
|
|
|
+ mutationFn: async () => {
|
|
|
|
|
+ dispatch(startLoading({}));
|
|
|
|
|
+ const res = await productApi.loadRelatedArea({
|
|
|
|
|
+ areaId: id,
|
|
|
});
|
|
});
|
|
|
return res;
|
|
return res;
|
|
|
},
|
|
},
|
|
@@ -88,8 +99,8 @@ const ProductDetailView: React.FC = () => {
|
|
|
dispatch(stopLoading());
|
|
dispatch(stopLoading());
|
|
|
console.log("Get package response data:", data);
|
|
console.log("Get package response data:", data);
|
|
|
if (data && data.errorCode === "0") {
|
|
if (data && data.errorCode === "0") {
|
|
|
- console.log("Get package successful");
|
|
|
|
|
- setPackages(data.data as Package[]);
|
|
|
|
|
|
|
+ const areas = data.data as Area[];
|
|
|
|
|
+ setRelatedAreas(areas);
|
|
|
} else {
|
|
} else {
|
|
|
console.error("Get package failed, no token received");
|
|
console.error("Get package failed, no token received");
|
|
|
}
|
|
}
|
|
@@ -130,6 +141,8 @@ const ProductDetailView: React.FC = () => {
|
|
|
setDataOptions(options.dataArray);
|
|
setDataOptions(options.dataArray);
|
|
|
handleSelectDay(options.daysArray[0]);
|
|
handleSelectDay(options.daysArray[0]);
|
|
|
handleSelectData(options.dataArray[0]);
|
|
handleSelectData(options.dataArray[0]);
|
|
|
|
|
+ // setSelectedPackage(packages.length > 0 ? packages[0] : null);
|
|
|
|
|
+ // console.log("Set package successful", packages.length, selectedPackage);
|
|
|
}, [options]);
|
|
}, [options]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -149,6 +162,13 @@ const ProductDetailView: React.FC = () => {
|
|
|
dataSet.has(selectedData) ? selectedData : Array.from(dataSet)[0]
|
|
dataSet.has(selectedData) ? selectedData : Array.from(dataSet)[0]
|
|
|
);
|
|
);
|
|
|
setDataActiveOptions(Array.from(dataSet));
|
|
setDataActiveOptions(Array.from(dataSet));
|
|
|
|
|
+ setSelectedPackage(
|
|
|
|
|
+ packages.find(
|
|
|
|
|
+ (p) =>
|
|
|
|
|
+ p.dayDuration === selectedDays &&
|
|
|
|
|
+ p.amountData.toString() === selectedData
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleSelectData = (data: string) => {
|
|
const handleSelectData = (data: string) => {
|
|
@@ -164,16 +184,25 @@ const ProductDetailView: React.FC = () => {
|
|
|
daysSet.has(selectedDays) ? selectedDays : Array.from(daysSet)[0]
|
|
daysSet.has(selectedDays) ? selectedDays : Array.from(daysSet)[0]
|
|
|
);
|
|
);
|
|
|
setDaysActiveOptions(Array.from(daysSet));
|
|
setDaysActiveOptions(Array.from(daysSet));
|
|
|
|
|
+ setSelectedPackage(
|
|
|
|
|
+ packages.find(
|
|
|
|
|
+ (p) =>
|
|
|
|
|
+ p.dayDuration === selectedDays &&
|
|
|
|
|
+ p.amountData.toString() === selectedData
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const getPrices = (quantityParam?: number) => {
|
|
const getPrices = (quantityParam?: number) => {
|
|
|
const quantityToUse =
|
|
const quantityToUse =
|
|
|
quantityParam !== undefined ? quantityParam : quantity;
|
|
quantityParam !== undefined ? quantityParam : quantity;
|
|
|
// find package based on selectedDays and selectedData
|
|
// find package based on selectedDays and selectedData
|
|
|
- const selectedPackage = packages.find(
|
|
|
|
|
- (p) =>
|
|
|
|
|
- p.dayDuration === selectedDays &&
|
|
|
|
|
- p.amountData.toString() === selectedData
|
|
|
|
|
|
|
+ setSelectedPackage(
|
|
|
|
|
+ packages.find(
|
|
|
|
|
+ (p) =>
|
|
|
|
|
+ p.dayDuration === selectedDays &&
|
|
|
|
|
+ p.amountData.toString() === selectedData
|
|
|
|
|
+ )
|
|
|
);
|
|
);
|
|
|
if (!selectedPackage) {
|
|
if (!selectedPackage) {
|
|
|
console.log(
|
|
console.log(
|
|
@@ -261,7 +290,7 @@ const ProductDetailView: React.FC = () => {
|
|
|
to="/"
|
|
to="/"
|
|
|
className="hover:text-[#EE0434] transition-colors text-[18px]"
|
|
className="hover:text-[#EE0434] transition-colors text-[18px]"
|
|
|
>
|
|
>
|
|
|
- Home
|
|
|
|
|
|
|
+ {t("home")}
|
|
|
</Link>
|
|
</Link>
|
|
|
<svg
|
|
<svg
|
|
|
className="w-3 h-3"
|
|
className="w-3 h-3"
|
|
@@ -308,6 +337,9 @@ const ProductDetailView: React.FC = () => {
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Product Information Card */}
|
|
|
|
|
+ <PackageOverview packageInfo={selectedPackage} />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="lg:col-span-7 space-y-8 md:space-y-10 pt-4">
|
|
<div className="lg:col-span-7 space-y-8 md:space-y-10 pt-4">
|
|
@@ -357,7 +389,7 @@ const ProductDetailView: React.FC = () => {
|
|
|
: "border-slate-100 text-slate-300"
|
|
: "border-slate-100 text-slate-300"
|
|
|
}`}
|
|
}`}
|
|
|
>
|
|
>
|
|
|
- {data === "0" ? "Unlimited" : data + " MB"}
|
|
|
|
|
|
|
+ {data === "0" ? "Unlimited" : data + " GB"}
|
|
|
</button>
|
|
</button>
|
|
|
))}
|
|
))}
|
|
|
</div>
|
|
</div>
|
|
@@ -441,9 +473,32 @@ const ProductDetailView: React.FC = () => {
|
|
|
{loading.isSmallLoading && (
|
|
{loading.isSmallLoading && (
|
|
|
<div className="w-5 h-5 border-3 border-white/30 border-t-red-500 rounded-full animate-spin"></div>
|
|
<div className="w-5 h-5 border-3 border-white/30 border-t-red-500 rounded-full animate-spin"></div>
|
|
|
)}
|
|
)}
|
|
|
- <span>Buy now</span>
|
|
|
|
|
|
|
+ <span>{t("buyNow")}</span>
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Suggestions Section */}
|
|
|
|
|
+ <div className="mt-12">
|
|
|
|
|
+ <h3 className="text-xl md:text-2xl font-black text-slate-900 mb-6">
|
|
|
|
|
+ {t("suggestionsEsim")} {area.areaName1}:
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <div className="grid grid-cols-2 gap-3 md:gap-4">
|
|
|
|
|
+ {relatedAreas.map((item) => (
|
|
|
|
|
+ <ProductCard
|
|
|
|
|
+ key={item.id}
|
|
|
|
|
+ p={item}
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ navigate(`/product/${item.id}`, {
|
|
|
|
|
+ state: {
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ window.scrollTo({ top: 0, behavior: "smooth" });
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|