- {data?.result?.configs?.length !== 0 && (
+ {configs?.length !== 0 && (
@@ -40,7 +43,7 @@ const ProductConfigList = ({
className="w-full h-full p-3 max-sm:px-0 flex flex-col gap-3
justify-center items-center"
>
- {isLoading || isFetching ? (
+ {showInitialLoading ? (
Array(3)
.fill(null)
.map((_, i) => (
@@ -52,7 +55,7 @@ const ProductConfigList = ({
- ) : data?.result?.configs?.length === 0 ? (
+ ) : configs?.length === 0 ? (
);
diff --git a/src/pages/shop/section/productList/components/ListAllProduct.tsx b/src/pages/shop/section/productList/components/ListAllProduct.tsx
index 1f0d093..6365cd4 100644
--- a/src/pages/shop/section/productList/components/ListAllProduct.tsx
+++ b/src/pages/shop/section/productList/components/ListAllProduct.tsx
@@ -9,11 +9,25 @@ import { RootState } from "../../../../../redux/store";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
-const ListAllProduct = ({ activeId }: { activeId: number }) => {
+interface Category {
+ id: number | string;
+ label?: string;
+}
+
+const ListAllProduct = ({
+ activeId,
+ categories,
+}: {
+ activeId: number;
+ categories?: Category[];
+}) => {
const axios = useAxios();
const [selectedConfigId, setSelectedConfigId] = useState
(null);
const { token } = useSelector((state: RootState) => state.token);
const navigate = useNavigate();
+ const isDnsCategory =
+ categories?.find((category) => Number(category.id) === activeId)?.label?.toLowerCase() ===
+ "dns";
const { data, error, isLoading, isFetching } = useQuery({
queryKey: ["configs", activeId],
queryFn: () => {
@@ -70,6 +84,7 @@ const ListAllProduct = ({ activeId }: { activeId: number }) => {
isOpen={selectedConfigId !== null}
setIsOpen={(open) => { if (!open) setSelectedConfigId(null); }}
configId={selectedConfigId}
+ isDns={isDnsCategory}
/>
)}
{
- const axios = useAxios();
- const dispatch = useDispatch();
- const navigate = useNavigate();
- const separateByThree = (num: number): string => {
- return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+const ModalBuy = ({ isOpen, setIsOpen, configId, isDns }: ModalBuyProps) => {
+ const axios = useAxios();
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const separateByThree = (num: number): string => {
+ return (num ?? 0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ };
+ const toNumber = (value: unknown): number => {
+ const normalizedValue =
+ typeof value === "string" ? value.replace(/,/g, "") : value;
+ const parsedValue = Number(normalizedValue);
+ return Number.isFinite(parsedValue) ? parsedValue : 0;
};
const { data, error, isLoading, isFetching, refetch } = useQuery({
queryKey: ["configSee", configId],
@@ -32,29 +44,59 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
enabled: isOpen,
});
const [price, setPrice] = useState(0);
+ const [isInfoOpen, setIsInfoOpen] = useState(false);
+ const payablePrice = toNumber(
+ price === 0 ? data?.data?.result?.price || 0 : price
+ );
+ const advancedVerificationLimit = toNumber(
+ data?.data?.result?.advanced_verification_limit ??
+ ADVANCED_VERIFICATION_PRICE_LIMIT
+ );
+ const advancedVerificationMessage =
+ buildAdvancedVerificationMessage(advancedVerificationLimit);
+ const advancedVerificationFromApi =
+ data?.data?.result?.requires_advanced_verification;
+ const hasAdvancedVerificationApiFlag =
+ advancedVerificationFromApi !== undefined &&
+ advancedVerificationFromApi !== null;
+ const requiresAdvancedVerification =
+ hasAdvancedVerificationApiFlag
+ ? Boolean(advancedVerificationFromApi)
+ : payablePrice > advancedVerificationLimit;
+ const goToAdvancedVerification = () => {
+ setIsOpen(false);
+ navigate(ADVANCED_VERIFICATION_ROUTE, {
+ state: {
+ payablePrice,
+ limit: advancedVerificationLimit,
+ },
+ });
+ };
const formik = useFormik({
- initialValues: {
- count: 1,
- discount: "",
- status: "view",
- },
- validationSchema: Yup.object({
- count: Yup.number().min(1, "حداقل یک کانفیگ باید باشد"),
- discount: Yup.string(),
- status: Yup.string(),
- }),
+ initialValues: {
+ count: 1,
+ discount: "",
+ status: "view",
+ },
+ validationSchema: Yup.object({
+ count: Yup.number().min(1, "حداقل یک کانفیگ باید باشد"),
+ discount: Yup.string(),
+ status: Yup.string(),
+ }),
onSubmit: (values) => {
console.log(values);
},
});
+ const { setFieldValue } = formik;
+
useEffect(() => {
- if (!isOpen) {
- formik.setFieldValue("count", 1);
- formik.setFieldValue("discount", "");
- setPrice(0);
- }
- }, [isOpen]);
+ if (!isOpen) {
+ setFieldValue("count", 1);
+ setFieldValue("discount", "");
+ setPrice(0);
+ }
+ }, [isOpen, setFieldValue]);
const mutation = useMutation({
mutationKey: ["buyConfig"],
mutationFn: (values: BuyConfigDataProps) => {
@@ -81,7 +123,8 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
return `${res.data.alert}`;
},
error: (err: AxiosError) => {
- formik.setFieldValue("discount", "");
+ setFieldValue("discount", "");
+ setPrice(0);
let errorMessage = "یک خطای غیرمنتظره رخ داد";
if (
err.response?.data &&
@@ -94,7 +137,8 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
},
});
};
- const chnageCountDebouncedMutation = useCallback(
+ const chnageCountDebouncedMutation = useMemo(
+ () =>
debounce((values) => {
toast.promise(mutation.mutateAsync(values), {
loading: "درحال بررسی...",
@@ -103,7 +147,8 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
return `${res.data.alert}`;
},
error: (err: AxiosError) => {
- formik.setFieldValue("discount", "");
+ setFieldValue("discount", "");
+ setPrice(0);
refetch();
let errorMessage = "یک خطای غیرمنتظره رخ داد";
if (
@@ -117,17 +162,17 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
},
});
}, 1000),
- []
+ [mutation, refetch, setFieldValue]
);
const chnageCountHandler = (status: "inc" | "dec") => {
if (status === "inc") {
- formik.setFieldValue("count", formik.values.count + 1);
+ setFieldValue("count", formik.values.count + 1);
chnageCountDebouncedMutation({
...formik.values,
count: formik.values.count + 1,
});
} else if (status === "dec") {
- formik.setFieldValue("count", formik.values.count - 1);
+ setFieldValue("count", formik.values.count - 1);
chnageCountDebouncedMutation({
...formik.values,
count: formik.values.count - 1,
@@ -136,7 +181,12 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
};
const submitHandler = () => {
- console.log(formik.values);
+ if (requiresAdvancedVerification) {
+ goToAdvancedVerification();
+ return;
+ }
+
+ console.log(formik.values);
toast.promise(mutation.mutateAsync({ ...formik.values, status: "buy" }), {
loading: "درحال انتقال به درگاه پرداخت...",
success: (res: AxiosResponse) => {
@@ -144,31 +194,62 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
return `${res.data.alert}`;
},
error: (err: AxiosError) => {
- formik.setFieldValue("discount", "");
- let errorMessage = "یک خطای غیرمنتظره رخ داد";
- if (
- err.response?.data &&
- typeof err.response.data === "object" &&
- "alert" in err.response.data
- ) {
- errorMessage = (err.response.data as { alert: string }).alert;
- }
- toast.error(`خطایی رخ داد : ${errorMessage}`);
- dispatch(setIsPaymentGateway(false));
- navigate("/noactive-payment");
- return errorMessage;
- },
+ setFieldValue("discount", "");
+ setPrice(0);
+ let errorMessage = "یک خطای غیرمنتظره رخ داد";
+ if (
+ err.response?.data &&
+ typeof err.response.data === "object" &&
+ "alert" in err.response.data
+ ) {
+ errorMessage = (err.response.data as { alert: string }).alert;
+ }
+
+ const responseData = err.response?.data;
+ const isResponseObject =
+ responseData !== null && typeof responseData === "object";
+ const requiresAdvancedVerificationFromApi =
+ isResponseObject &&
+ (("requires_advanced_verification" in responseData &&
+ Boolean(
+ (responseData as { requires_advanced_verification?: boolean })
+ .requires_advanced_verification
+ )) ||
+ ("code" in responseData &&
+ (responseData as { code?: string }).code ===
+ "ADVANCED_VERIFICATION_REQUIRED"));
+ const isAdvancedVerificationError =
+ requiresAdvancedVerificationFromApi ||
+ (err.response?.status === 422 &&
+ errorMessage.includes("احراز هویت پیشرفته"));
+
+ if (isAdvancedVerificationError) {
+ goToAdvancedVerification();
+ }
+
+ if (err.response?.status === 500) {
+ dispatch(setIsPaymentGateway(false));
+ navigate("/noactive-payment");
+ }
+
+ return errorMessage;
+ },
});
};
return (
+ <>
+
{
) : (
+ {isDns && (
+
+ )}
{" "}
کاربر گرامی لطفا از خاموش بودن vpn خود اطمینان حاصل فرمایید تا در
@@ -224,11 +314,15 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
مبلغ قابل پرداخت :{" "}
- {separateByThree(
- price === 0 ? data?.data?.result?.price || 0 : price
- )}
+ {separateByThree(payablePrice)}
+ {requiresAdvancedVerification &&
+ data?.data?.result?.max_config !== 0 && (
+
+ {advancedVerificationMessage}
+
+ )}
{data?.data?.result?.max_config === 0 ? (
<>
@@ -238,7 +332,7 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
>
) : (
<>
-
+
در صورت نداشتن کد تخفیف پرداخت رو بزنید
محدودیت کاربر
-
{data?.data?.result?.limit ?? "بدون محدودیت کاربر"}
+ {!data?.data?.result?.limit ? (
+
نامحدود
+ ) : (
+
{data?.data?.result?.limit}
+ )}
مدت زمان
@@ -318,8 +416,15 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {