feat : formating code by prettier
This commit is contained in:
@@ -68,12 +68,12 @@ const Profile = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const {data,isLoading,isFetching,error} = useQuery({
|
||||
queryKey:["profile",mutation],
|
||||
queryFn:()=>{
|
||||
return axios.get('/profile');
|
||||
}
|
||||
})
|
||||
const { data, isLoading, isFetching, error } = useQuery({
|
||||
queryKey: ["profile", mutation],
|
||||
queryFn: () => {
|
||||
return axios.get("/profile");
|
||||
},
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{!mutation.isPending && (
|
||||
@@ -85,8 +85,19 @@ const Profile = () => {
|
||||
/>
|
||||
)}
|
||||
<div className="relative h-full w-full flex flex-col gap-4">
|
||||
<ProfileDetails data={data?.data} isLoading={isLoading} isFetching={isFetching} error={error} userTitle=""/>
|
||||
<ProductConfigList data={data?.data} userTitle={data?.data?.result?.user_profile?.name} isLoading={isLoading} isFetching={isFetching} error={error} />
|
||||
<ProfileDetails
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -66,6 +66,8 @@ const ProductConfigList = ({
|
||||
textCopy={item.url}
|
||||
maxTraffic={item.max_traffic}
|
||||
traffic={item.traffic}
|
||||
type={item.type}
|
||||
categoryId={item.category_id}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -2,11 +2,13 @@ import { IoCopy } from "react-icons/io5";
|
||||
import Button from "../../../../components/button";
|
||||
import { ConfigItemProps } from "../../../../types/props";
|
||||
import ModalConfigDetails from "./ModalConfigDetails";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Dropdown, { DropdownItem } from "../../../../components/dropdown";
|
||||
import ProgressSpace from "../../../../components/progressSpace";
|
||||
import toast from "react-hot-toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setCategoryId } from "../../../../redux/slices/categoryId";
|
||||
|
||||
const ConfigItem = ({
|
||||
dateBuy,
|
||||
@@ -16,22 +18,45 @@ const ConfigItem = ({
|
||||
textCopy,
|
||||
maxTraffic,
|
||||
traffic,
|
||||
type,
|
||||
categoryId,
|
||||
}: ConfigItemProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isOpenDropDown, setIsOpenDropDown] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const handleToggle = () => {
|
||||
setIsOpenDropDown(!isOpen);
|
||||
};
|
||||
const [copyText, setCopyText] = useState<string>("");
|
||||
const copyToClipboard = async (textToCopy: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
toast.success("متن با موفقیت کپی شد!");
|
||||
} catch (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 (
|
||||
<>
|
||||
<ModalConfigDetails
|
||||
@@ -44,6 +69,10 @@ const ConfigItem = ({
|
||||
textCopy={textCopy}
|
||||
maxTraffic={maxTraffic}
|
||||
traffic={traffic}
|
||||
type={type}
|
||||
username={username}
|
||||
password={password}
|
||||
categoryId={categoryId}
|
||||
/>
|
||||
<div className="relative w-full h-full rounded-md">
|
||||
{isDisabled && (
|
||||
@@ -70,16 +99,49 @@ const ConfigItem = ({
|
||||
{configName}
|
||||
</li>
|
||||
<div className="max-md:hidden flex flex-row-reverse gap-2 justify-end items-center">
|
||||
{type === "gaming" ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => copyToClipboard(textCopy)}
|
||||
onClick={() => {
|
||||
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
|
||||
extra="!m-0 !px-2 !py-1.5 shadow-md"
|
||||
onClick={() => navigate("/tutorial")}
|
||||
onClick={() => {
|
||||
if (categoryId) {
|
||||
dispatch(setCategoryId(categoryId));
|
||||
navigate("/tutorial");
|
||||
}
|
||||
}}
|
||||
>
|
||||
آموزش
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import MuiModal from "../../../../common/modal";
|
||||
import Button from "../../../../components/button";
|
||||
import { IoCopy } from "react-icons/io5";
|
||||
@@ -6,6 +6,8 @@ import Alert from "../../../../components/alert/Alert";
|
||||
import ProgressSpace from "../../../../components/progressSpace";
|
||||
import toast from "react-hot-toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setCategoryId } from "../../../../redux/slices/categoryId";
|
||||
|
||||
const ModalConfigDetails = ({
|
||||
isOpen,
|
||||
@@ -16,7 +18,11 @@ const ModalConfigDetails = ({
|
||||
isShowTraffic,
|
||||
textCopy,
|
||||
maxTraffic,
|
||||
traffic
|
||||
traffic,
|
||||
type,
|
||||
username,
|
||||
password,
|
||||
categoryId,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
@@ -27,17 +33,27 @@ const ModalConfigDetails = ({
|
||||
textCopy: string;
|
||||
maxTraffic: number | "unlimited";
|
||||
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) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
toast.success("متن با موفقیت کپی شد!");
|
||||
} catch (err) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(copyText);
|
||||
toast.success("متن با موفقیت کپی شد!");
|
||||
} catch (err) {
|
||||
toast.error(`Failed to copy: , ${err}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MuiModal
|
||||
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">
|
||||
تاریخ خرید : {dateBuy}
|
||||
</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 ? (
|
||||
<div className="w-full h-full grid grid-cols-1 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 ">
|
||||
<div className="w-full h-full flex flex-wrap justify-center items-center">
|
||||
<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>
|
||||
</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" ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
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={()=> navigate('/tutorial')} extra="!m-0 !px-2 !py-1.5 shadow-md shadow-white">
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (categoryId) {
|
||||
dispatch(setCategoryId(categoryId));
|
||||
navigate("/tutorial");
|
||||
}
|
||||
}}
|
||||
extra="!m-0 !px-2 !py-1.5 shadow-md shadow-white"
|
||||
>
|
||||
آموزش
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -2,19 +2,19 @@ import PulsatingCircle from "../../../../components/pulsatingCircle";
|
||||
|
||||
const BestQuality = () => {
|
||||
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">
|
||||
<PulsatingCircle />
|
||||
</div>
|
||||
<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>{" "}
|
||||
ارائه دهنده{" "}
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@ import Typewriter from "../../../../components/typeWriter";
|
||||
|
||||
const FastSell = () => {
|
||||
return (
|
||||
<div className="relative w-full h-full md:mt-14 mb-8 flex justify-center items-center">
|
||||
<div className="w-full h-full flex flex-col gap-5 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 justify-center items-center">
|
||||
<LoaderSpin>
|
||||
<FaPowerOff className="max-md:text-6xl text-8xl text-white" />
|
||||
</LoaderSpin>
|
||||
|
||||
@@ -5,10 +5,10 @@ import SmoothScrollLink from "../../../../components/scrollLink";
|
||||
|
||||
const Intro = () => {
|
||||
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">
|
||||
<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>
|
||||
|
||||
@@ -2,7 +2,7 @@ import Content from "./components/Content";
|
||||
|
||||
const NowSell = () => {
|
||||
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">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ButtonList from "./components/ButtonList";
|
||||
import ListAllProduct from "./components/ListAllProduct";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import useAxios from "../../../../hooks/useAxios";
|
||||
import { RootState } from "../../../../redux/store";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const ProductList = () => {
|
||||
const [activeId, setActiveId] = useState(1);
|
||||
const { id } = useSelector((state: RootState) => state.configIdState);
|
||||
const [activeId, setActiveId] = useState(3);
|
||||
const axios = useAxios();
|
||||
const { data, error, isLoading, isFetching } = useQuery({
|
||||
queryKey: ["categories"],
|
||||
@@ -13,12 +16,15 @@ const ProductList = () => {
|
||||
return axios.get("/home/categories");
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
if (id !== "") setActiveId(Number(id));
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<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">
|
||||
<h1 className="max-md:text-sm text-center text-2xl font-bold">
|
||||
برای انتخاب سرویس مورد نظر کلیک کنید تا براتون نمایش داده
|
||||
شود
|
||||
برای انتخاب سرویس مورد نظر کلیک کنید تا براتون نمایش داده شود
|
||||
</h1>
|
||||
<ButtonList
|
||||
ProductsList={data?.data?.result}
|
||||
|
||||
@@ -14,11 +14,11 @@ const ButtonList: React.FC<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">
|
||||
<div className="w-full h-full gap-4 flex flex-wrap justify-center items-center">
|
||||
{Array(3)
|
||||
.fill(null)
|
||||
.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="pulse" width={100} variant="text" />
|
||||
</Skeleton>
|
||||
@@ -36,12 +36,12 @@ const ButtonList: React.FC<ButtonListProps> = ({
|
||||
);
|
||||
}
|
||||
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) => (
|
||||
<Button
|
||||
key={product.id}
|
||||
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}
|
||||
onClick={() => setActiveId(product.id)}
|
||||
>
|
||||
|
||||
@@ -67,7 +67,7 @@ const ListAllProduct = ({ activeId }: { activeId: number }) => {
|
||||
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">
|
||||
<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>) => {
|
||||
return (
|
||||
<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">
|
||||
{token ? (
|
||||
<Button
|
||||
extra="!w-4/5 !bg-secondTheme"
|
||||
extra="!w-4/5"
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
setConfigId(Number(item.id));
|
||||
@@ -85,13 +85,13 @@ const ListAllProduct = ({ activeId }: { activeId: number }) => {
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
extra="!w-4/5 !bg-secondTheme"
|
||||
extra="!w-4/5"
|
||||
onClick={() => {
|
||||
toast.error("برای خرید باید وارد شوید");
|
||||
navigate("/login");
|
||||
}}
|
||||
>
|
||||
ورود
|
||||
خرید
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -159,6 +159,7 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
|
||||
setIsOpenModal={setIsOpen}
|
||||
submitText="خرید"
|
||||
submitOnClick={submitHandler}
|
||||
disabled={isLoading || isFetching}
|
||||
nameService={
|
||||
isLoading || isFetching
|
||||
? "درحال بارگذاری..."
|
||||
@@ -211,7 +212,7 @@ const ModalBuy = ({ isOpen, setIsOpen, configId }: ModalBuyProps) => {
|
||||
)}
|
||||
</span>
|
||||
</h1>
|
||||
<h3 className="text-xs md:text-sm text-gray-700">
|
||||
<h3 className="text-xs md:text-sm text-gray-400">
|
||||
در صورت نداشتن کد تخفیف پرداخت رو بزنید
|
||||
</h3>
|
||||
<form
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Learn from "./components/Leran";
|
||||
import ListTutorials from "./components/ListTutorial";
|
||||
import useAxios from "../../../hooks/useAxios";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../redux/store";
|
||||
|
||||
const Tutorials = () => {
|
||||
const axios = useAxios()
|
||||
const {data,error,isLoading,isFetching} = useQuery({
|
||||
queryKey:["categoryLearning"],
|
||||
queryFn:()=>{
|
||||
return axios.get('/home/category/learning')
|
||||
}
|
||||
})
|
||||
const [activeTutorial, setActiveTutorial] = useState<number>(1);
|
||||
const axios = useAxios();
|
||||
const { data, error, isLoading, isFetching } = useQuery({
|
||||
queryKey: ["categoryLearning"],
|
||||
queryFn: () => {
|
||||
return axios.get("/home/category/learning");
|
||||
},
|
||||
});
|
||||
const { id } = useSelector((state: RootState) => state.categoryIdState);
|
||||
|
||||
const [activeTutorial, setActiveTutorial] = useState<number>(2);
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== 0) setActiveTutorial(id);
|
||||
}, [id]);
|
||||
useEffect(() => {}, []);
|
||||
return (
|
||||
<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>
|
||||
@@ -27,7 +36,10 @@ const Tutorials = () => {
|
||||
error={error}
|
||||
setActiveTutorial={setActiveTutorial}
|
||||
/>
|
||||
<Learn activeTutorial={activeTutorial} tutorialList={data?.data?.result} />
|
||||
<Learn
|
||||
activeTutorial={activeTutorial}
|
||||
tutorialList={data?.data?.result}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,19 @@ import { MdErrorOutline } from "react-icons/md";
|
||||
import Button from "../../../../components/button";
|
||||
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 axios = useAxios();
|
||||
const { data, isLoading, isFetching, error } = useQuery({
|
||||
@@ -86,14 +99,31 @@ const Learn = ({ activeTutorial }: LearnProps) => {
|
||||
{i.title}
|
||||
</span>
|
||||
<div className="relative w-full h-full flex flex-col justify-center items-center">
|
||||
{i.medias.map((media, index) => (
|
||||
<img
|
||||
className="w-full md:w-1/2 mb-3 rounded-lg mx-auto h-full"
|
||||
src={media}
|
||||
alt="images"
|
||||
{i.medias.map((media, index) => {
|
||||
const mediaType = getMediaType(media);
|
||||
return (
|
||||
<div
|
||||
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>
|
||||
)
|
||||
|
||||
@@ -13,16 +13,16 @@ const ListTutorials = ({
|
||||
}: ListTutorialsProps) => {
|
||||
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 mt-4">
|
||||
<div className="w-full h-full gap-4 flex flex-wrap justify-center mt-4">
|
||||
{Array(3)
|
||||
.fill(null)
|
||||
.map((_, i) => (
|
||||
<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}
|
||||
>
|
||||
<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" />
|
||||
</Button>
|
||||
|
||||
23
src/redux/slices/categoryId.ts
Normal file
23
src/redux/slices/categoryId.ts
Normal 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;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
import { isAuthenticated } from "../utils/auth";
|
||||
import { IsAuthenticated } from "../utils/auth";
|
||||
|
||||
const PrivateRoute = () => {
|
||||
return isAuthenticated() ? <Outlet /> : <Navigate to="/login" />;
|
||||
return IsAuthenticated() ? <Outlet /> : <Navigate to="/login" />;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
|
||||
11
src/types/props.d.ts
vendored
11
src/types/props.d.ts
vendored
@@ -35,9 +35,6 @@ export interface MobileMenuButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
// begin NavbarLink.tsx
|
||||
import { NavLinkProps } from "react-router-dom";
|
||||
|
||||
export interface NavbarLinkProps {
|
||||
to: string;
|
||||
children: React.ReactNode;
|
||||
@@ -52,7 +49,6 @@ export interface NavData {
|
||||
// begin ListTutorial.tsx
|
||||
import React, {
|
||||
ButtonHTMLAttributes,
|
||||
Component,
|
||||
Dispatch,
|
||||
InputHTMLAttributes,
|
||||
ReactNode,
|
||||
@@ -130,6 +126,8 @@ export interface ConfigItemProps {
|
||||
textCopy: string;
|
||||
maxTraffic: number | "unlimited";
|
||||
traffic: number;
|
||||
type: string;
|
||||
categoryId: number;
|
||||
}
|
||||
|
||||
// نوع Item برای آیتمهای داخل Dropdown
|
||||
@@ -203,6 +201,8 @@ type Config = {
|
||||
show_traffic: boolean;
|
||||
max_traffic: number;
|
||||
traffic: number;
|
||||
type: string;
|
||||
category_id: number;
|
||||
};
|
||||
|
||||
// تعریف تایپ دادههای برگشتی
|
||||
@@ -225,7 +225,8 @@ interface ProfileDetailsProps {
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | AxiosError | null;
|
||||
userTitle: string;
|
||||
userTitle?: string;
|
||||
categoryId?: number;
|
||||
}
|
||||
|
||||
interface ScrollLinkProps {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../redux/store";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const isTokenValid = (token: string): boolean => {
|
||||
try {
|
||||
@@ -8,11 +9,12 @@ const isTokenValid = (token: string): boolean => {
|
||||
return !!token;
|
||||
} catch (error) {
|
||||
console.error("Token validation error:", error);
|
||||
toast.error(`Token validation error:${error}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const isAuthenticated = () : boolean => {
|
||||
const {token} = useSelector((state : RootState) => state.token)
|
||||
export const IsAuthenticated = (): boolean => {
|
||||
const { token } = useSelector((state: RootState) => state.token);
|
||||
return isTokenValid(token);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user