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 ( 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) => { {Array(3)
return ( .fill(null)
<Button .map((_, i) => (
key={i.id} <Button extra="py-2 text-lg flex justify-center" key={i}>
active={i.id === activeId} <Skeleton animation="wave" variant="text">
extra="py-2 text-lg" <Skeleton animation="pulse" width={100} variant="text" />
onClick={() => setActiveId(i.id)} </Skeleton>
>
{i.title}
</Button> </Button>
))}
</div>
); );
})} }
if (error) {
return (
<div className="w-full h-full">
<Alert type="danger" message="خطایی با این موضوع رخ داد: ">{error.message}</Alert>
</div>
);
}
return (
<div className="w-full h-full gap-4 flex flex-wrap justify-center max-sm:grid max-sm:grid-cols-1">
{ProductsList?.map((product) => (
<Button
key={product.id}
active={product.id === activeId}
extra="py-2 text-lg"
name={product.label || product.name}
onClick={() => setActiveId(product.id)}
>
{product.name}
</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 }, });
], if (isLoading || isFetching) {
}, 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: 2, <svg
data: [ xmlns="http://www.w3.org/2000/svg"
{ width={100}
id: 1, viewBox="0 0 300 150"
title: " کاهش پینگ ۱", >
users: 2, <path
price: "1/000/000", fill="none"
space: 40, stroke="#71FBB2"
}, strokeWidth="15" // تغییر به camelCase
{ id: 2, title: "کاهش پینگ ۲", users: 2, price: "500/000", space: 60 }, strokeLinecap="round" // تغییر به camelCase
{ strokeDasharray="300 385" // تغییر به camelCase
id: 3, strokeDashoffset="0" // تغییر به camelCase
title: "کاهش پینگ ۳", 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"
users: 1, >
price: "800/000", <animate
space: 100, attributeName="stroke-dashoffset"
}, calcMode="spline"
{ id: 4, title: "کاهش پینگ ۴", users: 3, price: "200/000", space: 50 }, dur="2s" // برای مدت زمان، به 's' برای ثانیه نیاز داریم
], values="685;-685"
}, keySplines="0 0 1 1"
{ repeatCount="indefinite"
id: 3, ></animate>
data: [ </path>
{ id: 1, title: " سرور بازی ۱", users: 2, price: "50/000", space: 40 }, </svg>
{ id: 2, title: "سرور بازی ۲", users: 2, price: "100/000", space: 60 }, </div>
{
id: 3,
title: "سرور بازی ۳",
users: 1,
price: "700/000",
space: 100,
},
{ id: 4, title: "سرور بازی ۴", users: 3, price: "350/000", space: 50 },
],
},
];
const [dataProduct, setDataProduct] = useState(
Product?.filter((i) => i.id === activeId)
); );
useEffect(() => { }
setDataProduct(Product?.filter((i) => i.id === activeId)); if (error) {
}, [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"> <div className="w-full min-h-56 px-10 border-4 border-solid border-secondTheme rounded-lg flex justify-center items-center">
{dataProduct?.map((item) => { <Alert
return ( type="danger"
<Fragment key={item.id}> message="خطایی با این موضوع رخ داد: "
{item.data?.map((i) => { extra="!mb-0"
return ( >
<ProductBox {error.message}
key={i.id} </Alert>
variant={item.id} </div>
btnText="خرید"
price={i.price}
space={i.space}
title={i.title}
users={i.users}
/>
); );
})} }
</Fragment> return (
<>
<ModalBuy isOpen={isOpen} setIsOpen={setIsOpen} configId={configId} />
<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">
{data?.data?.result?.map((item: Record<string, string>) => {
return (
<div className="w-full h-full flex flex-col" key={item.id}>
<img src={item.image} alt={item.title} />
<div className="w-full h-full flex justify-center items-center">
<Button
extra="!w-4/5 !bg-secondTheme"
onClick={() => {
setIsOpen(true);
setConfigId(Number(item.id));
}}
>
خرید
</Button>
</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;