feat : formating code by prettier

This commit is contained in:
alij
2024-07-03 14:56:29 +03:30
parent 790ed79605
commit efa148c7ac
19 changed files with 289 additions and 84 deletions

View File

@@ -44,7 +44,7 @@ const Profile = () => {
}); });
useEffect(() => { useEffect(() => {
const trackId = query.get("trackId"); const trackId = query.get("trackId");
if (trackId) { if (trackId) {
toast.promise(mutation.mutateAsync({ trackId }), { toast.promise(mutation.mutateAsync({ trackId }), {
loading: "درحال تایید درگاه پرداخت...", loading: "درحال تایید درگاه پرداخت...",
@@ -68,12 +68,12 @@ const Profile = () => {
} }
}, []); }, []);
const {data,isLoading,isFetching,error} = useQuery({ const { data, isLoading, isFetching, error } = useQuery({
queryKey:["profile",mutation], queryKey: ["profile", mutation],
queryFn:()=>{ queryFn: () => {
return axios.get('/profile'); return axios.get("/profile");
} },
}) });
return ( return (
<> <>
{!mutation.isPending && ( {!mutation.isPending && (
@@ -85,8 +85,19 @@ const Profile = () => {
/> />
)} )}
<div className="relative h-full w-full flex flex-col gap-4"> <div className="relative h-full w-full flex flex-col gap-4">
<ProfileDetails data={data?.data} isLoading={isLoading} isFetching={isFetching} error={error} userTitle=""/> <ProfileDetails
<ProductConfigList data={data?.data} userTitle={data?.data?.result?.user_profile?.name} isLoading={isLoading} isFetching={isFetching} error={error} /> data={data?.data}
isLoading={isLoading}
isFetching={isFetching}
error={error}
/>
<ProductConfigList
data={data?.data}
userTitle={data?.data?.result?.user_profile?.name}
isLoading={isLoading}
isFetching={isFetching}
error={error}
/>
</div> </div>
</> </>
); );

View File

@@ -66,6 +66,8 @@ const ProductConfigList = ({
textCopy={item.url} textCopy={item.url}
maxTraffic={item.max_traffic} maxTraffic={item.max_traffic}
traffic={item.traffic} traffic={item.traffic}
type={item.type}
categoryId={item.category_id}
/> />
</Fragment> </Fragment>
); );

View File

@@ -2,11 +2,13 @@ import { IoCopy } from "react-icons/io5";
import Button from "../../../../components/button"; import Button from "../../../../components/button";
import { ConfigItemProps } from "../../../../types/props"; import { ConfigItemProps } from "../../../../types/props";
import ModalConfigDetails from "./ModalConfigDetails"; import ModalConfigDetails from "./ModalConfigDetails";
import { useState } from "react"; import { useEffect, useState } from "react";
import Dropdown, { DropdownItem } from "../../../../components/dropdown"; import Dropdown, { DropdownItem } from "../../../../components/dropdown";
import ProgressSpace from "../../../../components/progressSpace"; import ProgressSpace from "../../../../components/progressSpace";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { setCategoryId } from "../../../../redux/slices/categoryId";
const ConfigItem = ({ const ConfigItem = ({
dateBuy, dateBuy,
@@ -16,22 +18,45 @@ const ConfigItem = ({
textCopy, textCopy,
maxTraffic, maxTraffic,
traffic, traffic,
type,
categoryId,
}: ConfigItemProps) => { }: ConfigItemProps) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isOpenDropDown, setIsOpenDropDown] = useState(false); const [isOpenDropDown, setIsOpenDropDown] = useState(false);
const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const handleToggle = () => { const handleToggle = () => {
setIsOpenDropDown(!isOpen); setIsOpenDropDown(!isOpen);
}; };
const [copyText, setCopyText] = useState<string>("");
const copyToClipboard = async (textToCopy: string) => { const copyToClipboard = async (textToCopy: string) => {
try { try {
await navigator.clipboard.writeText(textToCopy); await navigator.clipboard.writeText(textToCopy);
toast.success("متن با موفقیت کپی شد!"); toast.success("متن با موفقیت کپی شد!");
} catch (err) { } catch (err) {
toast.error(`Failed to copy: , ${err}`); try {
await navigator.clipboard.writeText(copyText);
toast.success("متن با موفقیت کپی شد!");
} catch (err) {
toast.error(`Failed to copy: , ${err}`);
}
} }
}; };
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
useEffect(() => {
if (type === "gaming" && textCopy) {
const [user, pass] = textCopy.split(",");
setUsername(user);
setPassword(pass);
} else {
setUsername("");
setPassword("");
}
}, [textCopy]);
return ( return (
<> <>
<ModalConfigDetails <ModalConfigDetails
@@ -44,6 +69,10 @@ const ConfigItem = ({
textCopy={textCopy} textCopy={textCopy}
maxTraffic={maxTraffic} maxTraffic={maxTraffic}
traffic={traffic} traffic={traffic}
type={type}
username={username}
password={password}
categoryId={categoryId}
/> />
<div className="relative w-full h-full rounded-md"> <div className="relative w-full h-full rounded-md">
{isDisabled && ( {isDisabled && (
@@ -70,16 +99,49 @@ const ConfigItem = ({
{configName} {configName}
</li> </li>
<div className="max-md:hidden flex flex-row-reverse gap-2 justify-end items-center"> <div className="max-md:hidden flex flex-row-reverse gap-2 justify-end items-center">
<Button {type === "gaming" ? (
onClick={() => copyToClipboard(textCopy)} <>
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white" <Button
> onClick={() => {
کپی setCopyText(username);
<IoCopy /> copyToClipboard(username);
</Button> }}
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"
>
کپی یوزرنیم
<IoCopy />
</Button>
<Button
onClick={() => {
setCopyText(password);
copyToClipboard(password);
}}
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"
>
کپی پسورد
<IoCopy />
</Button>
</>
) : (
<Button
onClick={() => {
setCopyText(textCopy);
copyToClipboard(textCopy);
}}
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"
>
کپی
<IoCopy />
</Button>
)}
<Button <Button
extra="!m-0 !px-2 !py-1.5 shadow-md" extra="!m-0 !px-2 !py-1.5 shadow-md"
onClick={() => navigate("/tutorial")} onClick={() => {
if (categoryId) {
dispatch(setCategoryId(categoryId));
navigate("/tutorial");
}
}}
> >
آموزش آموزش
</Button> </Button>

View File

@@ -1,4 +1,4 @@
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction, useState } from "react";
import MuiModal from "../../../../common/modal"; import MuiModal from "../../../../common/modal";
import Button from "../../../../components/button"; import Button from "../../../../components/button";
import { IoCopy } from "react-icons/io5"; import { IoCopy } from "react-icons/io5";
@@ -6,6 +6,8 @@ import Alert from "../../../../components/alert/Alert";
import ProgressSpace from "../../../../components/progressSpace"; import ProgressSpace from "../../../../components/progressSpace";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { setCategoryId } from "../../../../redux/slices/categoryId";
const ModalConfigDetails = ({ const ModalConfigDetails = ({
isOpen, isOpen,
@@ -16,7 +18,11 @@ const ModalConfigDetails = ({
isShowTraffic, isShowTraffic,
textCopy, textCopy,
maxTraffic, maxTraffic,
traffic traffic,
type,
username,
password,
categoryId,
}: { }: {
isOpen: boolean; isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>; setIsOpen: Dispatch<SetStateAction<boolean>>;
@@ -27,17 +33,27 @@ const ModalConfigDetails = ({
textCopy: string; textCopy: string;
maxTraffic: number | "unlimited"; maxTraffic: number | "unlimited";
traffic: number; traffic: number;
type: string;
username: string;
password: string;
categoryId: number;
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate();
const dispatch = useDispatch();
const [copyText, setCopyText] = useState<string>("");
const copyToClipboard = async (textToCopy: string) => { const copyToClipboard = async (textToCopy: string) => {
try { try {
await navigator.clipboard.writeText(textToCopy); await navigator.clipboard.writeText(textToCopy);
toast.success("متن با موفقیت کپی شد!"); toast.success("متن با موفقیت کپی شد!");
} catch (err) { } catch (err) {
toast.error(`Failed to copy: , ${err}`); try {
await navigator.clipboard.writeText(copyText);
toast.success("متن با موفقیت کپی شد!");
} catch (err) {
toast.error(`Failed to copy: , ${err}`);
}
} }
}; };
return ( return (
<MuiModal <MuiModal
isOpenModal={isOpen} isOpenModal={isOpen}
@@ -49,20 +65,59 @@ const ModalConfigDetails = ({
<h1 className="w-full h-full text-center rounded-md p-1 md:p-2 bg-gray-800"> <h1 className="w-full h-full text-center rounded-md p-1 md:p-2 bg-gray-800">
تاریخ خرید : {dateBuy} تاریخ خرید : {dateBuy}
</h1> </h1>
<div className="w-full h-full flex flex-row-reverse gap-2 justify-center items-center"> <div className="w-full h-full flex flex-row-reverse gap-2 flex-wrap justify-center items-center">
{isDisabled ? ( {isDisabled ? (
<div className="w-full h-full grid grid-cols-1 justify-center items-center"> <div className="w-full h-full flex flex-wrap justify-center items-center">
<Button extra="!m-0 !px-2 !py-1 shadow-md shadow-secondTheme !bg-secondTheme font-bold hover:!bg-gray-800 hover:!shadow-gray-800 "> <Button extra="!m-0 !px-2 !py-1 shadow-md flex-1 shadow-secondTheme !bg-secondTheme font-bold hover:!bg-gray-800 hover:!shadow-gray-800 ">
تمدید تمدید
</Button> </Button>
</div> </div>
) : ( ) : (
<> <>
<Button onClick={() => copyToClipboard(textCopy)} extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"> {type === "gaming" ? (
کپی <>
<IoCopy /> <Button
</Button> onClick={() => {
<Button onClick={()=> navigate('/tutorial')} extra="!m-0 !px-2 !py-1.5 shadow-md shadow-white"> setCopyText(username);
copyToClipboard(username);
}}
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"
>
کپی یوزرنیم
<IoCopy />
</Button>
<Button
onClick={() => {
setCopyText(password);
copyToClipboard(password);
}}
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"
>
کپی پسورد
<IoCopy />
</Button>
</>
) : (
<Button
onClick={() => {
setCopyText(textCopy);
copyToClipboard(textCopy);
}}
extra="!m-0 !px-2 !py-1.5 flex flex-row-reverse justify-center items-center gap-1 shadow-md shadow-white"
>
کپی
<IoCopy />
</Button>
)}
<Button
onClick={() => {
if (categoryId) {
dispatch(setCategoryId(categoryId));
navigate("/tutorial");
}
}}
extra="!m-0 !px-2 !py-1.5 shadow-md shadow-white"
>
آموزش آموزش
</Button> </Button>
</> </>

View File

@@ -2,19 +2,19 @@ import PulsatingCircle from "../../../../components/pulsatingCircle";
const BestQuality = () => { const BestQuality = () => {
return ( return (
<div className="w-full h-full mt-4 md:mt-10 mb-10 flex gap-10 flex-col justify-center items-center"> <div className="w-full h-full mt-8 md:mt-10 mb-10 flex gap-10 flex-col justify-center items-center">
<div className="w-full h-full flex justify-center items-center"> <div className="w-full h-full flex justify-center items-center">
<PulsatingCircle /> <PulsatingCircle />
</div> </div>
<div className="w-full h-full flex flex-col gap-2 md:gap-4 justify-center items-center"> <div className="w-full h-full flex flex-col gap-2 md:gap-4 justify-center items-center">
<h1 className="max-md:text-xl md:text-4xl font-bold"> <h1 className="text-base md:text-3xl lg:text-4xl font-bold">
به عنوان{" "} به عنوان{" "}
<span className="max-md:text-xl md:text-5xl text-secondTheme font-bold"> <span className="text-base md:text-3xl lg:text-4xl text-secondTheme font-bold">
اولین و بهترین اولین و بهترین
</span>{" "} </span>{" "}
ارائه دهنده{" "} ارائه دهنده{" "}
</h1> </h1>
<h1 className="relative max-md:right-10 max-lg:right-20 lg:right-40 max-md:text-xl md:text-4xl font-bold"> <h1 className="relative max-md:right-5 max-lg:right-20 lg:right-40 text-base md:text-3xl lg:text-4xl font-bold">
مفتخریم که در خدمت شما باشیم مفتخریم که در خدمت شما باشیم
</h1> </h1>
</div> </div>

View File

@@ -4,8 +4,8 @@ import Typewriter from "../../../../components/typeWriter";
const FastSell = () => { const FastSell = () => {
return ( return (
<div className="relative w-full h-full md:mt-14 mb-8 flex justify-center items-center"> <div className="relative w-full h-full mb-6 flex justify-center items-center">
<div className="w-full h-full flex flex-col gap-5 justify-center items-center"> <div className="w-full h-full flex flex-col justify-center items-center">
<LoaderSpin> <LoaderSpin>
<FaPowerOff className="max-md:text-6xl text-8xl text-white" /> <FaPowerOff className="max-md:text-6xl text-8xl text-white" />
</LoaderSpin> </LoaderSpin>

View File

@@ -5,10 +5,10 @@ import SmoothScrollLink from "../../../../components/scrollLink";
const Intro = () => { const Intro = () => {
return ( return (
<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="relative w-full h-full gap-6 grid grid-cols-1 py-6 md:grid-cols-2">
<div className="w-full h-full flex flex-col max-md:gap-5 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 text-center">بزرگترین فروشگاه</span>
<span className="text-secondTheme font-bold max-md:text-center"> <span className="text-secondTheme font-bold max-md:text-center">
کاهش پینگ در ایران کاهش پینگ در ایران
</span> </span>

View File

@@ -2,7 +2,7 @@ import Content from "./components/Content";
const NowSell = () => { const NowSell = () => {
return ( return (
<div className="relative w-full h-full mt-16 p-5"> <div className="relative w-full h-full p-5">
<div className="relative w-full max-md:h-[15rem] md:h-[20rem] lg:h-[25rem] p-2 rounded-2xl bg-white shadow-lg bg-banner bg-center bg-cover shadow-current flex justify-center items-center"> <div className="relative w-full max-md:h-[15rem] md:h-[20rem] lg:h-[25rem] p-2 rounded-2xl bg-white shadow-lg bg-banner bg-center bg-cover shadow-current flex justify-center items-center">
<Content /> <Content />
</div> </div>

View File

@@ -1,11 +1,14 @@
import { useState } from "react"; import { useEffect, 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 { useQuery } from "@tanstack/react-query";
import useAxios from "../../../../hooks/useAxios"; import useAxios from "../../../../hooks/useAxios";
import { RootState } from "../../../../redux/store";
import { useSelector } from "react-redux";
const ProductList = () => { const ProductList = () => {
const [activeId, setActiveId] = useState(1); const { id } = useSelector((state: RootState) => state.configIdState);
const [activeId, setActiveId] = useState(3);
const axios = useAxios(); const axios = useAxios();
const { data, error, isLoading, isFetching } = useQuery({ const { data, error, isLoading, isFetching } = useQuery({
queryKey: ["categories"], queryKey: ["categories"],
@@ -13,12 +16,15 @@ const ProductList = () => {
return axios.get("/home/categories"); return axios.get("/home/categories");
}, },
}); });
useEffect(() => {
if (id !== "") setActiveId(Number(id));
}, [id]);
return ( return (
<div className="relative w-full h-full max-md:mt-4 md:mt-8 " id="configs"> <div className="relative w-full h-full max-md:mt-4 md:mt-8 " id="configs">
<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">
<h1 className="max-md:text-sm text-center text-2xl font-bold"> <h1 className="max-md:text-sm text-center text-2xl font-bold">
برای انتخاب سرویس مورد نظر کلیک کنید تا براتون نمایش داده برای انتخاب سرویس مورد نظر کلیک کنید تا براتون نمایش داده شود
شود
</h1> </h1>
<ButtonList <ButtonList
ProductsList={data?.data?.result} ProductsList={data?.data?.result}

View File

@@ -14,11 +14,11 @@ const ButtonList: React.FC<ButtonListProps> = ({
}) => { }) => {
if (isLoading || isFetching) { 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 items-center">
{Array(3) {Array(3)
.fill(null) .fill(null)
.map((_, i) => ( .map((_, i) => (
<Button extra="py-2 text-lg flex justify-center" key={i}> <Button extra="py-2 text-lg flex justify-center items-center" key={i}>
<Skeleton animation="wave" variant="text"> <Skeleton animation="wave" variant="text">
<Skeleton animation="pulse" width={100} variant="text" /> <Skeleton animation="pulse" width={100} variant="text" />
</Skeleton> </Skeleton>
@@ -36,12 +36,12 @@ const ButtonList: React.FC<ButtonListProps> = ({
); );
} }
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 flex-row-reverse justify-center">
{ProductsList?.map((product) => ( {ProductsList?.map((product) => (
<Button <Button
key={product.id} key={product.id}
active={product.id === activeId} active={product.id === activeId}
extra="py-2 text-lg" extra="md:py-2 md:text-lg max-sm:!px-1"
name={product.label || product.name} name={product.label || product.name}
onClick={() => setActiveId(product.id)} onClick={() => setActiveId(product.id)}
> >

View File

@@ -67,7 +67,7 @@ const ListAllProduct = ({ activeId }: { activeId: number }) => {
return ( return (
<> <>
<ModalBuy isOpen={isOpen} setIsOpen={setIsOpen} configId={configId} /> <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"> <div className="w-full h-full border-4 gap-5 border-spacing-1 shadow-lg shadow-white border-solid border-white rounded-lg p-3 grid max-sm:grid-cols-1 max-md:grid-cols-2 max-lg:grid-cols-3 lg:grid-cols-4" id="listConfig">
{data?.data?.result?.map((item: Record<string, string>) => { {data?.data?.result?.map((item: Record<string, string>) => {
return ( return (
<div className="w-full h-full flex flex-col" key={item.id}> <div className="w-full h-full flex flex-col" key={item.id}>
@@ -75,7 +75,7 @@ const ListAllProduct = ({ activeId }: { activeId: number }) => {
<div className="w-full h-full flex justify-center items-center"> <div className="w-full h-full flex justify-center items-center">
{token ? ( {token ? (
<Button <Button
extra="!w-4/5 !bg-secondTheme" extra="!w-4/5"
onClick={() => { onClick={() => {
setIsOpen(true); setIsOpen(true);
setConfigId(Number(item.id)); setConfigId(Number(item.id));
@@ -85,13 +85,13 @@ const ListAllProduct = ({ activeId }: { activeId: number }) => {
</Button> </Button>
) : ( ) : (
<Button <Button
extra="!w-4/5 !bg-secondTheme" extra="!w-4/5"
onClick={() => { onClick={() => {
toast.error("برای خرید باید وارد شوید"); toast.error("برای خرید باید وارد شوید");
navigate("/login"); navigate("/login");
}} }}
> >
ورود خرید
</Button> </Button>
)} )}
</div> </div>

View File

@@ -159,6 +159,7 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
setIsOpenModal={setIsOpen} setIsOpenModal={setIsOpen}
submitText="خرید" submitText="خرید"
submitOnClick={submitHandler} submitOnClick={submitHandler}
disabled={isLoading || isFetching}
nameService={ nameService={
isLoading || isFetching isLoading || isFetching
? "درحال بارگذاری..." ? "درحال بارگذاری..."
@@ -211,7 +212,7 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
)} )}
</span> </span>
</h1> </h1>
<h3 className="text-xs md:text-sm text-gray-700"> <h3 className="text-xs md:text-sm text-gray-400">
در صورت نداشتن کد تخفیف پرداخت رو بزنید در صورت نداشتن کد تخفیف پرداخت رو بزنید
</h3> </h3>
<form <form

View File

@@ -1,18 +1,27 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import Learn from "./components/Leran"; import Learn from "./components/Leran";
import ListTutorials from "./components/ListTutorial"; import ListTutorials from "./components/ListTutorial";
import useAxios from "../../../hooks/useAxios"; import useAxios from "../../../hooks/useAxios";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
const Tutorials = () => { const Tutorials = () => {
const axios = useAxios() const axios = useAxios();
const {data,error,isLoading,isFetching} = useQuery({ const { data, error, isLoading, isFetching } = useQuery({
queryKey:["categoryLearning"], queryKey: ["categoryLearning"],
queryFn:()=>{ queryFn: () => {
return axios.get('/home/category/learning') return axios.get("/home/category/learning");
} },
}) });
const [activeTutorial, setActiveTutorial] = useState<number>(1); const { id } = useSelector((state: RootState) => state.categoryIdState);
const [activeTutorial, setActiveTutorial] = useState<number>(2);
useEffect(() => {
if (id !== 0) setActiveTutorial(id);
}, [id]);
useEffect(() => {}, []);
return ( return (
<div className="relative w-full h-full flex flex-col px-4 py-4 text-start"> <div className="relative w-full h-full flex flex-col px-4 py-4 text-start">
<h1 className="font-bold text-xl md:text-2xl text-center">آموزش</h1> <h1 className="font-bold text-xl md:text-2xl text-center">آموزش</h1>
@@ -27,7 +36,10 @@ const Tutorials = () => {
error={error} error={error}
setActiveTutorial={setActiveTutorial} setActiveTutorial={setActiveTutorial}
/> />
<Learn activeTutorial={activeTutorial} tutorialList={data?.data?.result} /> <Learn
activeTutorial={activeTutorial}
tutorialList={data?.data?.result}
/>
</div> </div>
); );
}; };

View File

@@ -6,6 +6,19 @@ import { MdErrorOutline } from "react-icons/md";
import Button from "../../../../components/button"; import Button from "../../../../components/button";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
const getMediaType = (url: string) => {
const extension = url.split(".").pop()?.toLowerCase();
if (extension) {
if (["jpg", "jpeg", "png", "gif"].includes(extension)) {
return "image";
}
if (["mp4", "webm", "ogg"].includes(extension)) {
return "video";
}
}
return "unknown";
};
const Learn = ({ activeTutorial }: LearnProps) => { const Learn = ({ activeTutorial }: LearnProps) => {
const axios = useAxios(); const axios = useAxios();
const { data, isLoading, isFetching, error } = useQuery({ const { data, isLoading, isFetching, error } = useQuery({
@@ -86,14 +99,31 @@ const Learn = ({ activeTutorial }: LearnProps) => {
{i.title} {i.title}
</span> </span>
<div className="relative w-full h-full flex flex-col justify-center items-center"> <div className="relative w-full h-full flex flex-col justify-center items-center">
{i.medias.map((media, index) => ( {i.medias.map((media, index) => {
<img const mediaType = getMediaType(media);
className="w-full md:w-1/2 mb-3 rounded-lg mx-auto h-full" return (
src={media} <div
alt="images" key={index}
key={index} className="w-full md:w-1/2 mb-3 rounded-lg mx-auto h-full"
/> >
))} {mediaType === "image" ? (
<img
src={media}
alt="media"
className="w-full h-full object-cover rounded-lg"
/>
) : mediaType === "video" ? (
<video
src={media}
controls
className="w-full h-full object-cover rounded-lg"
/>
) : (
<p>نوع فایل ناشناخته است</p>
)}
</div>
);
})}
</div> </div>
</div> </div>
) )

View File

@@ -13,16 +13,16 @@ const ListTutorials = ({
}: ListTutorialsProps) => { }: ListTutorialsProps) => {
if (isLoading || isFetching) { 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 mt-4"> <div className="w-full h-full gap-4 flex flex-wrap justify-center mt-4">
{Array(3) {Array(3)
.fill(null) .fill(null)
.map((_, i) => ( .map((_, i) => (
<div <div
className="flex flex-wrap max-sm:grid max-sm:grid-cols-1 gap-2 justify-center items-center" className="flex flex-wrap flex-1 gap-2 justify-center items-center"
key={i} key={i}
> >
<Button <Button
extra={`!h-auto !text-center !text-sm md:!text-lg !bg-gray-800 !rounded-md !px-2 !py-2 !cursor-pointer !flex justify-center`} extra={`!h-auto !text-center flex-1 !text-sm md:!text-lg !bg-gray-800 !rounded-md !px-2 !py-2 !cursor-pointer !flex justify-center`}
> >
<Skeleton animation="pulse" width={150} variant="text" /> <Skeleton animation="pulse" width={150} variant="text" />
</Button> </Button>

View File

@@ -0,0 +1,23 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface categoryIdState {
id: number;
}
const initialState: categoryIdState = {
id: 0,
};
const categoryIdSlice = createSlice({
name: "categoryId",
initialState,
reducers: {
setCategoryId: (state, action: PayloadAction<number>) => {
state.id = action.payload;
},
},
});
export const { setCategoryId } = categoryIdSlice.actions;
export default categoryIdSlice.reducer;

View File

@@ -1,8 +1,8 @@
import { Navigate, Outlet } from "react-router-dom"; import { Navigate, Outlet } from "react-router-dom";
import { isAuthenticated } from "../utils/auth"; import { IsAuthenticated } from "../utils/auth";
const PrivateRoute = () => { const PrivateRoute = () => {
return isAuthenticated() ? <Outlet /> : <Navigate to="/login" />; return IsAuthenticated() ? <Outlet /> : <Navigate to="/login" />;
}; };
export default PrivateRoute; export default PrivateRoute;

11
src/types/props.d.ts vendored
View File

@@ -35,9 +35,6 @@ export interface MobileMenuButtonProps {
onClick: () => void; onClick: () => void;
} }
// begin NavbarLink.tsx
import { NavLinkProps } from "react-router-dom";
export interface NavbarLinkProps { export interface NavbarLinkProps {
to: string; to: string;
children: React.ReactNode; children: React.ReactNode;
@@ -52,7 +49,6 @@ export interface NavData {
// begin ListTutorial.tsx // begin ListTutorial.tsx
import React, { import React, {
ButtonHTMLAttributes, ButtonHTMLAttributes,
Component,
Dispatch, Dispatch,
InputHTMLAttributes, InputHTMLAttributes,
ReactNode, ReactNode,
@@ -130,6 +126,8 @@ export interface ConfigItemProps {
textCopy: string; textCopy: string;
maxTraffic: number | "unlimited"; maxTraffic: number | "unlimited";
traffic: number; traffic: number;
type: string;
categoryId: number;
} }
// نوع Item برای آیتم‌های داخل Dropdown // نوع Item برای آیتم‌های داخل Dropdown
@@ -203,6 +201,8 @@ type Config = {
show_traffic: boolean; show_traffic: boolean;
max_traffic: number; max_traffic: number;
traffic: number; traffic: number;
type: string;
category_id: number;
}; };
// تعریف تایپ داده‌های برگشتی // تعریف تایپ داده‌های برگشتی
@@ -225,7 +225,8 @@ interface ProfileDetailsProps {
isLoading: boolean; isLoading: boolean;
isFetching: boolean; isFetching: boolean;
error: Error | AxiosError | null; error: Error | AxiosError | null;
userTitle: string; userTitle?: string;
categoryId?: number;
} }
interface ScrollLinkProps { interface ScrollLinkProps {

View File

@@ -1,5 +1,6 @@
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../redux/store"; import { RootState } from "../redux/store";
import toast from "react-hot-toast";
const isTokenValid = (token: string): boolean => { const isTokenValid = (token: string): boolean => {
try { try {
@@ -8,11 +9,12 @@ const isTokenValid = (token: string): boolean => {
return !!token; return !!token;
} catch (error) { } catch (error) {
console.error("Token validation error:", error); console.error("Token validation error:", error);
toast.error(`Token validation error:${error}`);
return false; return false;
} }
}; };
export const isAuthenticated = () : boolean => { export const IsAuthenticated = (): boolean => {
const {token} = useSelector((state : RootState) => state.token) const { token } = useSelector((state: RootState) => state.token);
return isTokenValid(token); return isTokenValid(token);
}; };