feat : dynamic tutorial page

This commit is contained in:
alij
2024-06-22 16:28:15 +04:30
parent 0dce3ba39f
commit 1cfadd711b
4 changed files with 205 additions and 106 deletions

View File

@@ -1,27 +1,19 @@
import { useState } from "react"; import { useState } from "react";
import Learn from "./components/Leran"; import Learn from "./components/Leran";
import ListTutorials from "./components/ListTutorial"; import ListTutorials from "./components/ListTutorial";
import { TutorialList } from "../../../types/props"; import useAxios from "../../../hooks/useAxios";
import { useQuery } from "@tanstack/react-query";
const Tutorials = () => { const Tutorials = () => {
const tutorialList: TutorialList = [ const axios = useAxios()
{ const {data,error,isLoading,isFetching} = useQuery({
id: 1, queryKey:["categoryLearning"],
name: "آموزش اشتراکی اندروید", queryFn:()=>{
}, return axios.get('/home/category/learning')
{ }
id: 2, })
name: "آموزش اشتراکی آیفون", console.log(data?.data?.result);
},
{
id: 3,
name: "آموزش سرویس مولتی",
},
{
id: 4,
name: "آموزش open vpn در اندروید",
},
];
const [activeTutorial, setActiveTutorial] = useState<number>(1); const [activeTutorial, setActiveTutorial] = useState<number>(1);
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">
@@ -31,10 +23,13 @@ const Tutorials = () => {
</p> </p>
<ListTutorials <ListTutorials
activeTutorial={activeTutorial} activeTutorial={activeTutorial}
tutorialList={tutorialList} tutorialList={data?.data?.result}
isLoading={isLoading}
isFetching={isFetching}
error={error}
setActiveTutorial={setActiveTutorial} setActiveTutorial={setActiveTutorial}
/> />
<Learn activeTutorial={activeTutorial} tutorialList={tutorialList} /> <Learn activeTutorial={activeTutorial} tutorialList={data?.data?.result} />
</div> </div>
); );
}; };

View File

@@ -1,94 +1,83 @@
import { SyntheticEvent, useEffect, useState } from "react";
import { LearnProps } from "../../../../types/props"; import { LearnProps } from "../../../../types/props";
import { BiLoaderAlt } from "react-icons/bi"; import { useQuery } from "@tanstack/react-query";
import useAxios from "../../../../hooks/useAxios";
import Alert from "../../../../components/alert/Alert";
import { MdErrorOutline } from "react-icons/md";
const Learn = ({ activeTutorial, tutorialList }: LearnProps) => { const Learn = ({ activeTutorial }: LearnProps) => {
const tutorialImages = [ const axios = useAxios();
{ const { data, isLoading, isFetching, error } = useQuery({
id: 1, queryKey: ["learn", activeTutorial],
images: [{ urlimg: "https://persain-game.com/front_end/img/v2ray.png" }], queryFn: () => {
return axios.get(`/home/category/learning/${activeTutorial}`);
}, },
{ });
id: 2,
images: [
{ urlimg: "https://persain-game.com/front_end/img/1-eshterki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/2-eshteraki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/3-eshteraki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/4-eshteraki.jpg" },
],
},
{
id: 3,
images: [
{ urlimg: "https://persain-game.com/front_end/img/1-eshterki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/2-eshteraki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/3-eshteraki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/4-eshteraki.jpg" },
],
},
{
id: 4,
images: [
{ urlimg: "https://persain-game.com/front_end/img/1-eshterki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/2-eshteraki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/3-eshteraki.jpg" },
{ urlimg: "https://persain-game.com/front_end/img/4-eshteraki.jpg" },
],
},
];
const [loading, setLoading] = useState<boolean>(true);
const [list, setList] = useState(
tutorialList?.filter((id) => id.id === activeTutorial)
);
const [tutorialImagesState, setTutorialImagesState] = useState(
tutorialImages?.filter((id) => id.id === activeTutorial)
);
useEffect(() => {
setList(tutorialList?.filter((id) => id.id === activeTutorial));
setTutorialImagesState(
tutorialImages?.filter((id) => id.id === activeTutorial)
);
setLoading(true);
}, [activeTutorial]);
const handleLoadingImage = (e: SyntheticEvent<HTMLImageElement>) => {
if (e.type === "load") setLoading(false);
};
return ( return (
<div className="w-full h-full border-4 flex flex-col justify-center items-center border-solid mt-4 p-4 rounded-md border-gray-800 border-spacing-1"> <div className="w-full h-full border-4 flex flex-col justify-center items-center border-solid mt-4 p-4 rounded-md border-gray-800 border-spacing-1">
{list?.map((i) => { {isLoading || isFetching ? (
<div className="w-full h-full p-5 flex justify-center items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width={100}
viewBox="0 0 300 150"
>
<path
fill="none"
className="stroke-gray-600"
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>
) : error ? (
<Alert type="danger" message="خطایی با این موضوع رخ داد: ">
{error.message}
</Alert>
) : data?.data?.result.length === 0 ? (
<div className="w-full h-full flex flex-col gap-3 justify-center items-center">
<MdErrorOutline className="w-[5rem] h-[5rem]" />
<p className="text-sm md:text-base"> اموزشی وجود ندارد </p>
</div>
) : (
data?.data?.result.map(
(i: { id: string; title: string; medias: string[] }) => {
return ( return (
<div <div
key={i.id} key={i.id}
className="relative w-full h-full flex flex-col justify-center items-center" className="relative w-full h-full flex flex-col justify-center items-center"
> >
<span className="p-2 bg-gray-800 rounded-md mb-4">{i.name}</span> <span className="p-2 bg-gray-800 rounded-md mb-4">
{loading && ( {i.title}
<div className="relative w-full h-60 flex justify-center items-center"> </span>
<BiLoaderAlt className="w-16 text-white h-16 animate-spin" />
</div>
)}
<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">
{tutorialImagesState?.map((i) => { {i.medias.map((medias, index) => {
return (
<div key={i.id}>
{i.images?.map((item, index) => {
return ( return (
<img <img
className="w-full md:w-1/2 mb-3 rounded-lg mx-auto h-full" className="w-full md:w-1/2 mb-3 rounded-lg mx-auto h-full"
src={item.urlimg} src={medias}
alt="images" alt="images"
key={index} key={index}
onLoad={(e) => handleLoadingImage(e)}
/> />
); );
})} })}
</div> </div>
);
})}
</div>
</div> </div>
); );
})} }
)
)}
</div> </div>
); );
}; };

View File

@@ -1,10 +1,47 @@
import { Skeleton } from "@mui/material";
import { ListTutorialsProps } from "../../../../types/props"; import { ListTutorialsProps } from "../../../../types/props";
import Alert from "../../../../components/alert/Alert";
import Button from "../../../../components/button";
const ListTutorials = ({ const ListTutorials = ({
tutorialList, tutorialList,
activeTutorial, activeTutorial,
isLoading,
isFetching,
error,
setActiveTutorial, setActiveTutorial,
}: ListTutorialsProps) => { }: 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">
{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"
key={i}
>
<Button
extra={`!h-auto !text-center !text-sm md:!text-lg !bg-gray-800 !rounded-md !px-2 !py-2 !cursor-pointer`}
>
<Skeleton animation="pulse" width={150} variant="text" />
</Button>
</div>
))}
</div>
);
}
if (error) {
return (
<div className="w-full h-full mt-4">
<Alert type="danger" message="خطایی با این موضوع رخ داد: ">
{error.message}
</Alert>
</div>
);
}
return ( return (
<div className="w-full h-full mt-4"> <div className="w-full h-full mt-4">
<ul className="flex flex-wrap max-sm:grid max-sm:grid-cols-1 gap-2 justify-center items-center"> <ul className="flex flex-wrap max-sm:grid max-sm:grid-cols-1 gap-2 justify-center items-center">

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

@@ -5,6 +5,7 @@ export interface AlertProps {
children: React.ReactNode; children: React.ReactNode;
type?: "info" | "danger" | "success" | "warning" | "dark"; type?: "info" | "danger" | "success" | "warning" | "dark";
message?: string; message?: string;
extra?: string;
} }
// begin Button.tsx // begin Button.tsx
@@ -58,12 +59,16 @@ import React, {
SetStateAction, SetStateAction,
} from "react"; } from "react";
import { FormikProps } from "formik"; import { FormikProps } from "formik";
import { AxiosError } from "axios";
export type TutorialList = { id: number; name: string }[]; export type TutorialList = { id: number; name: string }[];
export interface ListTutorialsProps { export interface ListTutorialsProps {
tutorialList: TutorialList; tutorialList: TutorialList;
activeTutorial: number; activeTutorial: number;
isLoading: boolean;
isFetching: boolean;
error: Error | AxiosError | null;
setActiveTutorial: SetStateAction; setActiveTutorial: SetStateAction;
} }
@@ -79,7 +84,10 @@ export interface TypewriterProps {
} }
export interface ButtonListProps { export interface ButtonListProps {
ProductsList: { id: number; title: string }[]; ProductsList: { id: number; name: string; label?: string }[];
isLoading: boolean;
isFetching: boolean;
error: Error | AxiosError | null;
activeId: number; activeId: number;
setActiveId: SetStateAction; setActiveId: SetStateAction;
} }
@@ -110,6 +118,7 @@ export interface MuiModalProps {
nameService?: string; nameService?: string;
submitText?: string; submitText?: string;
submitOnClick?: () => void; submitOnClick?: () => void;
disabled?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
@@ -117,6 +126,8 @@ export interface ConfigItemProps {
dateBuy: string; dateBuy: string;
configName: string; configName: string;
isDisabled: boolean; isDisabled: boolean;
isShowTraffic: boolean;
textCopy: string;
} }
// نوع Item برای آیتم‌های داخل Dropdown // نوع Item برای آیتم‌های داخل Dropdown
@@ -147,3 +158,70 @@ export interface LoginFormValues {
export interface LoginFromikProps { export interface LoginFromikProps {
formik: FormikProps<LoginFormValues>; formik: FormikProps<LoginFormValues>;
} }
interface BuyResponse {
message: string;
}
interface BuyError {
message: string;
}
interface ModalBuyProps {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
// mutation: UseMutationResult<
// AxiosResponse<BuyResponse>,
// BuyError,
// BuyConfigData,
// unknown
// >;
configId: number;
}
interface BuyConfigDataProps {
count?: number;
discount?: string;
status?: string;
}
// تعریف تایپ داده‌های واقعی پروفایل کاربر
type UserProfile = {
id: number;
name: string;
email: string;
mobile: string;
};
// تعریف تایپ پیکربندی‌ها
type Config = {
id: number;
title: string;
url: string;
created_at: string | null;
show_traffic: boolean;
max_traffic: number;
traffic: number;
};
// تعریف تایپ داده‌های برگشتی
type ProfileResponseData = {
result: {
user_profile: UserProfile;
enable_config: number;
disable_config: number;
configs: Config[];
};
status: boolean;
alert: string;
};
// تایپ خطا
type QueryError = Error | AxiosError | null;
interface ProfileDetailsProps {
data: ProfileResponseData;
isLoading: boolean;
isFetching: boolean;
error: Error | AxiosError | null;
userTitle: string;
}