feat : complated but config and redirect to payment and remove img from intro component at mobile size

This commit is contained in:
alij
2024-06-22 16:29:15 +04:30
parent 1cfadd711b
commit 3dfed214a8
5 changed files with 430 additions and 110 deletions

View File

@@ -4,21 +4,20 @@ import Rocket3D from "../../../../assets/ui/Rocket.png";
const Intro = () => { const Intro = () => {
return ( return (
<div className="relative w-full h-full md:pt-40 mb-16 md:mb-32 gap-6 grid grid-cols-1 py-8 md:grid-cols-2"> <div className="relative w-full h-full md:pt-20 mb-6 md:mb-10 gap-6 grid grid-cols-1 py-8 md:grid-cols-2">
<div className="w-full h-full flex flex-col gap-8 justify-center mx-auto items-center"> <div className="w-full h-full flex flex-col max-md:gap-5 gap-8 justify-center mx-auto items-center">
<h1 className="text-start flex flex-col gap-3 md:gap-4 text-3xl md:text-4xl lg:text-5xl"> <h1 className="text-start flex flex-col gap-3 md:gap-4 text-3xl md:text-4xl lg:text-5xl">
<span className="inline-block">بزرگترین فروشگاه</span> <span className="inline-block">بزرگترین فروشگاه</span>
<span className="text-secondTheme font-bold max-md:text-center"> <span className="text-secondTheme font-bold max-md:text-center">
کاهش پینگ کاهش پینگ در ایران
</span>{" "} </span>
<span className="inline-block max-md:text-center">در ایران</span>
</h1> </h1>
<p className="text-center text-xl text-gray-600"> <p className="text-center text-xl text-gray-600">
سال ها تجربه پشت یک کانفیگ شماست سال ها تجربه پشت یک کانفیگ شماست
</p> </p>
<Button>همین الان میخرم</Button> <Button extra="max-md:!hidden">همین الان میخرم</Button>
</div> </div>
<div className="w-full h-full flex justify-center py-4 mx-auto items-center"> <div className="w-full h-full flex justify-center py-4 mx-auto items-center max-md:hidden">
<img <img
src={Rocket3D} src={Rocket3D}
alt="intro" alt="intro"

View File

@@ -1,14 +1,18 @@
import { useState } from "react"; import { useState } from "react";
import ButtonList from "./components/ButtonList"; import ButtonList from "./components/ButtonList";
import ListAllProduct from "./components/ListAllProduct"; import ListAllProduct from "./components/ListAllProduct";
import { useQuery } from "@tanstack/react-query";
import useAxios from "../../../../hooks/useAxios";
const ProductList = () => { const ProductList = () => {
const [activeId, setActiveId] = useState(1); const [activeId, setActiveId] = useState(1);
const ProductsList = [ const axios = useAxios();
{ id: 1, title: "سرویس اشتراکی" }, const { data, error, isLoading, isFetching } = useQuery({
{ id: 2, title: "سرویس گیمینگ" }, queryKey: ["categories"],
{ id: 3, title: "سرویس مولتی" }, queryFn: () => {
]; return axios.get("/home/categories");
},
});
return ( return (
<div className="relative w-full h-full max-md:mt-4 md:mt-8 "> <div className="relative w-full h-full max-md:mt-4 md:mt-8 ">
<div className="w-full h-full gap-4 md:gap-6 flex flex-col p-5 justify-center items-center"> <div className="w-full h-full gap-4 md:gap-6 flex flex-col p-5 justify-center items-center">
@@ -17,7 +21,10 @@ const ProductList = () => {
شود شود
</h1> </h1>
<ButtonList <ButtonList
ProductsList={ProductsList} ProductsList={data?.data?.result}
isLoading={isLoading}
isFetching={isFetching}
error={error}
activeId={activeId} activeId={activeId}
setActiveId={setActiveId} setActiveId={setActiveId}
/> />

View File

@@ -1,25 +1,53 @@
import React from "react";
import { Skeleton } from "@mui/material";
import Button from "../../../../../components/button"; import Button from "../../../../../components/button";
import { ButtonListProps } from "../../../../../types/props"; import { ButtonListProps } from "../../../../../types/props";
import Alert from "../../../../../components/alert/Alert";
const ButtonList = ({ const ButtonList: React.FC<ButtonListProps> = ({
ProductsList, ProductsList,
isLoading,
isFetching,
error,
activeId, activeId,
setActiveId, setActiveId,
}: ButtonListProps) => { }) => {
if (isLoading || isFetching) {
return (
<div className="w-full h-full gap-4 flex flex-wrap justify-center max-sm:grid max-sm:grid-cols-1">
{Array(3)
.fill(null)
.map((_, i) => (
<Button extra="py-2 text-lg flex justify-center" key={i}>
<Skeleton animation="wave" variant="text">
<Skeleton animation="pulse" width={100} variant="text" />
</Skeleton>
</Button>
))}
</div>
);
}
if (error) {
return (
<div className="w-full h-full">
<Alert type="danger" message="خطایی با این موضوع رخ داد: ">{error.message}</Alert>
</div>
);
}
return ( return (
<div className="w-full h-full gap-4 flex flex-wrap justify-center max-sm:grid max-sm:grid-cols-1 "> <div className="w-full h-full gap-4 flex flex-wrap justify-center max-sm:grid max-sm:grid-cols-1">
{ProductsList?.map((i) => { {ProductsList?.map((product) => (
return ( <Button
<Button key={product.id}
key={i.id} active={product.id === activeId}
active={i.id === activeId} extra="py-2 text-lg"
extra="py-2 text-lg" name={product.label || product.name}
onClick={() => setActiveId(i.id)} onClick={() => setActiveId(product.id)}
> >
{i.title} {product.name}
</Button> </Button>
); ))}
})}
</div> </div>
); );
}; };

View File

@@ -1,89 +1,87 @@
import { Fragment, useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query";
import ProductBox from "../../../../../components/productBox"; import useAxios from "../../../../../hooks/useAxios";
import Alert from "../../../../../components/alert/Alert";
import Button from "../../../../../components/button";
import ModalBuy from "./ModalBuy";
import { useState } from "react";
const ListAllProduct = ({ activeId }: { activeId: number }) => { const ListAllProduct = ({ activeId }: { activeId: number }) => {
const Product = [ const axios = useAxios();
{ const [isOpen, setIsOpen] = useState(false);
id: 1, const [configId, setConfigId] = useState(1);
data: [ const { data, error, isLoading, isFetching } = useQuery({
{ id: 1, title: "مولتی سرور ۱", users: 2, price: "100/000", space: 40 }, queryKey: ["configs", activeId],
{ id: 2, title: "مولتی سرور ۲", users: 2, price: "200/000", space: 60 }, queryFn: () => {
{ return axios.get(`/home/config/${activeId}`);
id: 3,
title: "مولتی سرور ۳",
users: 1,
price: "300/000",
space: 100,
},
{ id: 4, title: "مولتی سرور ۴", users: 3, price: "400/000", space: 50 },
],
}, },
{ });
id: 2, if (isLoading || isFetching) {
data: [ return (
{ <div className="w-full min-h-56 flex justify-center items-center border-4 border-spacing-1 border-solid border-secondTheme rounded-lg">
id: 1, <svg
title: " کاهش پینگ ۱", xmlns="http://www.w3.org/2000/svg"
users: 2, width={100}
price: "1/000/000", viewBox="0 0 300 150"
space: 40, >
}, <path
{ id: 2, title: "کاهش پینگ ۲", users: 2, price: "500/000", space: 60 }, fill="none"
{ stroke="#71FBB2"
id: 3, strokeWidth="15" // تغییر به camelCase
title: "کاهش پینگ ۳", strokeLinecap="round" // تغییر به camelCase
users: 1, strokeDasharray="300 385" // تغییر به camelCase
price: "800/000", strokeDashoffset="0" // تغییر به camelCase
space: 100, d="M275 75c0 31-27 50-50 50-58 0-92-100-150-100-28 0-50 22-50 50s23 50 50 50c58 0 92-100 150-100 24 0 50 19 50 50Z"
}, >
{ id: 4, title: "کاهش پینگ ۴", users: 3, price: "200/000", space: 50 }, <animate
], attributeName="stroke-dashoffset"
}, calcMode="spline"
{ dur="2s" // برای مدت زمان، به 's' برای ثانیه نیاز داریم
id: 3, values="685;-685"
data: [ keySplines="0 0 1 1"
{ id: 1, title: " سرور بازی ۱", users: 2, price: "50/000", space: 40 }, repeatCount="indefinite"
{ id: 2, title: "سرور بازی ۲", users: 2, price: "100/000", space: 60 }, ></animate>
{ </path>
id: 3, </svg>
title: "سرور بازی ۳", </div>
users: 1, );
price: "700/000", }
space: 100, if (error) {
}, return (
{ id: 4, title: "سرور بازی ۴", users: 3, price: "350/000", space: 50 }, <div className="w-full min-h-56 px-10 border-4 border-solid border-secondTheme rounded-lg flex justify-center items-center">
], <Alert
}, type="danger"
]; message="خطایی با این موضوع رخ داد: "
const [dataProduct, setDataProduct] = useState( extra="!mb-0"
Product?.filter((i) => i.id === activeId) >
); {error.message}
useEffect(() => { </Alert>
setDataProduct(Product?.filter((i) => i.id === activeId)); </div>
}, [activeId]); );
}
return ( return (
<div className="w-full h-full border-4 gap-5 border-spacing-1 border-solid border-secondTheme rounded-lg p-3 grid max-sm:grid-cols-1 max-md:grid-cols-2 max-lg:grid-cols-3 lg:grid-cols-4"> <>
{dataProduct?.map((item) => { <ModalBuy isOpen={isOpen} setIsOpen={setIsOpen} configId={configId} />
return ( <div className="w-full h-full border-4 gap-5 border-spacing-1 shadow-lg shadow-secondTheme border-solid border-secondTheme rounded-lg p-3 grid max-sm:grid-cols-1 max-md:grid-cols-2 max-lg:grid-cols-3 lg:grid-cols-4">
<Fragment key={item.id}> {data?.data?.result?.map((item: Record<string, string>) => {
{item.data?.map((i) => { return (
return ( <div className="w-full h-full flex flex-col" key={item.id}>
<ProductBox <img src={item.image} alt={item.title} />
key={i.id} <div className="w-full h-full flex justify-center items-center">
variant={item.id} <Button
btnText="خرید" extra="!w-4/5 !bg-secondTheme"
price={i.price} onClick={() => {
space={i.space} setIsOpen(true);
title={i.title} setConfigId(Number(item.id));
users={i.users} }}
/> >
); خرید
})} </Button>
</Fragment> </div>
); </div>
})} );
</div> })}
</div>
</>
); );
}; };

View File

@@ -0,0 +1,288 @@
import MuiModal from "../../../../../common/modal";
import Button from "../../../../../components/button";
import { BuyConfigDataProps, ModalBuyProps } from "../../../../../types/props";
import { useMutation, useQuery } from "@tanstack/react-query";
import * as Yup from "yup";
import useAxios from "../../../../../hooks/useAxios";
import toast from "react-hot-toast";
import Alert from "../../../../../components/alert/Alert";
import { useFormik } from "formik";
import Input from "../../../../../components/input";
import { BiMinus, BiPlus } from "react-icons/bi";
import { queryClient } from "../../../../../main";
import { AxiosError, AxiosResponse } from "axios";
import { useCallback, useEffect, useState } from "react";
import { debounce } from "@mui/material";
const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
const axios = useAxios();
const separateByThree = (num: number): string => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
const { data, error, isLoading, isFetching } = useQuery({
queryKey: ["configSee", configId],
queryFn: () => {
return axios.get(`/home/config/${configId}/see`);
},
});
const [price, setPrice] = useState(0);
const formik = useFormik({
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);
},
});
useEffect(() => {
if (!isOpen) {
formik.setFieldValue("count", 1);
formik.setFieldValue("discount", "");
setPrice(0);
}
}, [isOpen]);
const mutation = useMutation({
mutationKey: ["buyConfig"],
mutationFn: (values: BuyConfigDataProps) => {
return axios.post(`home/config/${configId}/buy`, values);
},
onSuccess: (data) => {
queryClient.invalidateQueries({
queryKey: ["configSee"],
});
console.log(data);
},
onError: (err) => {
queryClient.invalidateQueries({
queryKey: ["configSee"],
});
console.log(err);
},
});
const discountHandler = () => {
toast.promise(mutation.mutateAsync(formik.values), {
loading: "درحال بررسی...",
success: (res: AxiosResponse) => {
setPrice(res?.data?.result?.price);
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;
}
return errorMessage;
},
});
};
const chnageCountDebouncedMutation = useCallback(
debounce((values) => {
toast.promise(mutation.mutateAsync(values), {
loading: "درحال بررسی...",
success: (res: AxiosResponse) => {
setPrice(res?.data?.result?.price);
return `${res.data.alert}`;
},
error: (err: AxiosError) => {
let errorMessage = "یک خطای غیرمنتظره رخ داد";
if (
err.response?.data &&
typeof err.response.data === "object" &&
"alert" in err.response.data
) {
errorMessage = (err.response.data as { alert: string }).alert;
}
return errorMessage;
},
});
}, 1000),
[]
);
const chnageCountHandler = (status: "inc" | "dec") => {
if (status === "inc") {
formik.setFieldValue("count", formik.values.count + 1);
chnageCountDebouncedMutation({
count: formik.values.count + 1,
status: formik.values.status,
});
} else if (status === "dec") {
formik.setFieldValue("count", formik.values.count - 1);
chnageCountDebouncedMutation({
count: formik.values.count - 1,
status: formik.values.status,
});
}
};
const submitHandler = () => {
console.log(formik.values);
toast.promise(mutation.mutateAsync({ ...formik.values, status: "buy" }), {
loading: "درحال انتقال به درگاه پرداخت...",
success: (res: AxiosResponse) => {
window.location.href = res?.data?.result?.action;
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;
}
return errorMessage;
},
});
};
return (
<MuiModal
isOpenModal={isOpen}
setIsOpenModal={setIsOpen}
submitText="خرید"
submitOnClick={submitHandler}
nameService={
isLoading || isFetching
? "درحال بارگذاری..."
: data?.data?.result
? data?.data.result.title
: "خطا رخ داد"
}
>
<div className="relative w-full h-full px-2 md:px-4">
{error && (
<Alert type="danger" message="خطایی رخ داد :">
{" "}
{error.message}{" "}
</Alert>
)}
{isLoading || isFetching ? (
<div className="w-full h-full flex justify-center items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width={100}
viewBox="0 0 300 150"
>
<path
fill="none"
stroke="#71FBB2"
strokeWidth="15" // تغییر به camelCase
strokeLinecap="round" // تغییر به camelCase
strokeDasharray="300 385" // تغییر به camelCase
strokeDashoffset="0" // تغییر به camelCase
d="M275 75c0 31-27 50-50 50-58 0-92-100-150-100-28 0-50 22-50 50s23 50 50 50c58 0 92-100 150-100 24 0 50 19 50 50Z"
>
<animate
attributeName="stroke-dashoffset"
calcMode="spline"
dur="2s" // برای مدت زمان، به 's' برای ثانیه نیاز داریم
values="685;-685"
keySplines="0 0 1 1"
repeatCount="indefinite"
></animate>
</path>
</svg>
</div>
) : (
<div className="w-full h-full flex flex-col gap-3 justify-between items-center text-sm md:text-xl">
<h1 className="font-bold">
مبلغ قابل پرداخت :{" "}
<span className="px-[2px] rounded-md bg-secondTheme text-gray-950">
{separateByThree(
price === 0 ? data?.data?.result?.price : price
)}
</span>
</h1>
<h3 className="text-xs md:text-sm text-gray-700">
در صورت نداشتن کد تخفیف پرداخت رو بزنید
</h3>
<form
className="flex flex-col gap-3 justify-center items-center"
onSubmit={formik.handleSubmit}
>
<div className="w-full h-full flex justify-center items-center gap-2">
<Input
type="text"
extra="max-md:!p-1 md:!p-2"
{...formik.getFieldProps("discount")}
/>
<Button
extra="md:!py-1 disabled:!bg-gray-300"
onClick={discountHandler}
disabled={formik.values.discount === ""}
>
اعمال
</Button>
</div>
<div className="w-2/3 h-full flex justify-center items-center gap-1">
<Button
extra="!px-2 max-md:!py-1.5 md:!py-1 disabled:!bg-gray-300"
onClick={() => {
if (formik.values.count > 1) {
chnageCountHandler("dec");
}
}}
disabled={formik.values.count === 1}
>
<BiMinus />
</Button>
<Input
type="text"
extra="max-md:py-0 !p-1 !text-center"
{...formik.getFieldProps("count")}
/>
<Button
extra="!px-2 max-md:!py-1.5 md:!py-1 disabled:!bg-gray-300"
onClick={() => {
if (
formik.values.count.toString() <
data?.data?.result?.max_config
)
chnageCountHandler("inc");
}}
disabled={
formik.values.count.toString() ===
data?.data?.result?.max_config
}
>
<BiPlus />
</Button>
</div>
</form>
<div className="w-full h-full flex flex-col justify-between items-center gap-2 text-xs md:text-sm">
<div className="w-full h-full bg-gray-900/50 rounded-md shadow flex justify-between items-center p-3">
<p>حجم</p>
<p dir="ltr">{`${data?.data?.result?.traffic} GB`}</p>
</div>
<div className="w-full h-full bg-gray-900/50 rounded-md shadow flex justify-between items-center p-3">
<p>محدودیت کاربر</p>
<p>{data?.data?.result?.limit}</p>
</div>
{/* <div className="w-full h-full bg-gray-900/50 rounded-md shadow flex justify-between items-center p-3">
<p>مدت زمان</p>
<p>{data?.data?.result?.}</p>
</div> */}
</div>
</div>
)}
</div>
</MuiModal>
);
};
export default ModalBuy;