feat : add page profile (private component) and add section , modal for profile page

This commit is contained in:
ali josheghani
2024-06-06 09:55:28 +03:30
parent 18cc4948e6
commit 5300f9ec01
13 changed files with 267 additions and 58 deletions

View File

@@ -8,6 +8,7 @@ import About from "./pages/about";
import ProgressBar from "./common/ProgressBar";
import AuthLayout from "./layout/AuthLayout";
import Login from "./pages/login";
import Profile from "./pages/profile";
const App = () => {
return (
@@ -19,11 +20,11 @@ const App = () => {
<Route path="tutorial" element={<Tutorial />} />
<Route path="rule" element={<Rule />} />
<Route path="about" element={<About />} />
<Route element={<PrivateRoute />}>
<Route path="private" element={<p>private</p>} />
<Route path="private" element={<PrivateRoute />}>
<Route index element={<Profile />} />
</Route>
</Route>
<Route path="login" element={<AuthLayout />} >
<Route path="profile" element={<AuthLayout />} >
<Route index element={<Login />} />
</Route>
</Routes>

View File

@@ -4,13 +4,15 @@ import { motion } from "framer-motion";
import { IoClose } from "react-icons/io5";
import Button from "../../components/button";
import { MuiModalProps } from "../../types/props";
import { useNavigate } from "react-router-dom";
import Input from "../../components/input";
import { PiMinus, PiPlus } from "react-icons/pi";
export default function MuiModal({
isOpenModal,
setIsOpenModal,
title,
nameService,
submitText,
submitOnClick,
children,
}: MuiModalProps) {
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
@@ -18,7 +20,6 @@ export default function MuiModal({
setOpen(false);
setIsOpenModal(false);
};
const navigate = useNavigate();
useEffect(() => {
if (isOpenModal) handleOpen();
@@ -41,58 +42,18 @@ export default function MuiModal({
>
<motion.div className="w-full h-full flex justify-between">
<h1 className="text-lg md:text-xl font-bold">
خرید سرویس <span className="text-green-600"> مولتی 1 </span>
{title} {nameService && <span className="text-green-600"> {nameService} </span>}
</h1>
<button onClick={handleClose}>
<IoClose className="w-7 h-7" />
</button>
</motion.div>
<motion.div className="w-full h-full flex flex-col justify-between text-center items-center">
<div className="w-full h-full px-2 py-2 flex flex-col gap-3">
<h1 className="text-lg md:text-xl">
مبلغ قابل پرداخت :{" "}
<span className="px-2 py-[1px] rounded-md bg-secondTheme text-black">
100/000
</span>
</h1>
<p className="text-sm md:text-base text-gray-500">
در صورت نداشتن کد تخفیف پرداخت رو بزنید
</p>
</div>
<div className="w-full h-full p-2 flex gap-2 justify-center">
<Input type="text" id="1" name="offer" />
<Button extra="!bg-green-600"> اعمال </Button>
</div>
<div className="w-full h-full px-4 py-2 flex gap-3 justify-center">
<p className="text-sm md:text-base text-gray-500">تعداد:</p>
<div className="bg-gray-700 rounded-md flex justify-between gap-1 items-center px-1">
<PiMinus className="cursor-pointer" />
<input
type="number"
className="w-10 bg-transparent focus:ring-0 outline-none border-none ring-0"
value={1}
/>
<PiPlus className="cursor-pointer" />
</div>
</div>
<div className="w-full h-full px-4 py-2 flex flex-col gap-3 justify-center">
<div className="flex justify-between px-4 py-1 shadow-md shadow-gray-800 rounded-md bg-gray-800">
<p className="text-sm md:text-base">حجم :</p>
<p className="text-sm md:text-base">10 گیگ</p>
</div>
<div className="flex justify-between px-4 py-1 shadow-md shadow-gray-800 rounded-md bg-gray-800">
<p className="text-sm md:text-base">محدودیت کاربر :</p>
<p className="text-sm md:text-base">بدون محدودیت</p>
</div>
<div className="flex justify-between px-4 py-1 shadow-md shadow-gray-800 rounded-md bg-gray-800">
<p className="text-sm md:text-base">مدت زمان :</p>
<p className="text-sm md:text-base">یک ماه</p>
</div>
</div>
{children}
</motion.div>
<motion.div className="w-full h-full flex justify-end items-center gap-2 md:gap-3 text-lg md:text-xl">
<Button onClick={() => handleClose()}> بستن </Button>
<Button onClick={() => navigate("login")}> باشه </Button>
<Button onClick={submitOnClick}> {submitText} </Button>
</motion.div>
</motion.div>
</Modal>

View File

@@ -1,6 +1,6 @@
import { ButtonProps } from "../../types/props";
function Button({ children, onClick, extra, active }: ButtonProps) {
function Button({ children, onClick, extra, active,...attrs }: ButtonProps) {
return (
<button
className={`px-4 py-1 md:px-5 md:py-2 rounded-md transition duration-300 ease-in-out transform bg-white text-[#333] hover:bg-gray-600 hover:text-white ${
@@ -10,6 +10,7 @@ function Button({ children, onClick, extra, active }: ButtonProps) {
} ${extra}`}
onClick={onClick}
type="button"
{...attrs}
>
{children}
</button>

View File

@@ -6,6 +6,7 @@ const Input = ({
type,
name,
placeholder,
labelStyle,
extra,
...attrs
}: InputProps) => {
@@ -14,7 +15,7 @@ const Input = ({
{label && (
<label
htmlFor={id}
className="block mb-2 text-sm font-medium text-white"
className={`block mb-2 text-sm font-medium text-white ${labelStyle}`}
>
{label}
</label>

View File

@@ -0,0 +1,67 @@
import { Dispatch, SetStateAction } from "react";
import MuiModal from "../../../common/modal";
import Input from "../../input";
import Button from "../../button";
import { PiMinus, PiPlus } from "react-icons/pi";
const MuiModalBuy = ({
isOpen,
setIsOpen,
}: {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}) => {
return (
<MuiModal
isOpenModal={isOpen}
setIsOpenModal={setIsOpen}
submitText="خرید"
title="خرید سرویس"
nameService="مولتی 1"
>
<div className="w-full h-full px-2 py-2 flex flex-col gap-3">
<h1 className="text-lg md:text-xl">
مبلغ قابل پرداخت :{" "}
<span className="px-2 py-[1px] rounded-md bg-secondTheme text-black">
100/000
</span>
</h1>
<p className="text-sm md:text-base text-gray-500">
در صورت نداشتن کد تخفیف پرداخت رو بزنید
</p>
</div>
<div className="w-full h-full p-2 flex gap-2 justify-center">
<Input type="text" id="1" name="offer" />
<Button extra="!bg-green-600"> اعمال </Button>
</div>
<div className="w-full h-full px-4 py-2 flex gap-3 justify-center">
<p className="text-sm md:text-base text-gray-500">تعداد:</p>
<div className="bg-gray-700 rounded-md flex justify-between gap-1 items-center px-1">
<PiMinus className="cursor-pointer" />
<input
type="number"
className="w-10 bg-transparent focus:ring-0 outline-none border-none ring-0"
value={1}
/>
<PiPlus className="cursor-pointer" />
</div>
</div>
<div className="w-full h-full px-4 py-2 flex flex-col gap-3 justify-center">
<div className="flex justify-between px-4 py-1 shadow-md shadow-gray-800 rounded-md bg-gray-800">
<p className="text-sm md:text-base">حجم :</p>
<p className="text-sm md:text-base">10 گیگ</p>
</div>
<div className="flex justify-between px-4 py-1 shadow-md shadow-gray-800 rounded-md bg-gray-800">
<p className="text-sm md:text-base">محدودیت کاربر :</p>
<p className="text-sm md:text-base">بدون محدودیت</p>
</div>
<div className="flex justify-between px-4 py-1 shadow-md shadow-gray-800 rounded-md bg-gray-800">
<p className="text-sm md:text-base">مدت زمان :</p>
<p className="text-sm md:text-base">یک ماه</p>
</div>
</div>
</MuiModal>
);
};
export default MuiModalBuy;

View File

@@ -1,8 +1,8 @@
import { AM, EC, GA, US, GD, GB } from "country-flag-icons/react/1x1";
import { ProductBoxProps } from "../../types/props";
import Button from "../button";
import MuiModal from "../../common/modal";
import { useState } from "react";
import MuiModalBuy from "./components/MuiModalBuy";
const ProductBox = ({
title,
@@ -26,7 +26,7 @@ const ProductBox = ({
: variant === 3 && "shadow-indigo-800 bg-indigo-600"
} bg-cover bg-center bg-banner`}
>
<MuiModal isOpenModal={isOpen} setIsOpenModal={setIsOpen} />
<MuiModalBuy isOpen={isOpen} setIsOpen={setIsOpen} />
<h1 className="max-md:text-xl md:text-2xl font-bold">{title}</h1>
<div className="w-full h-full p-2 flex gap-2 justify-between items-center">
<div className="w-full h-full flex flex-col gap-2">

View File

@@ -0,0 +1,13 @@
import ProfileDetails from "./section/ProfileDetails";
import ProductConfigList from "./section/ProductConfigList";
const Profile = () => {
return (
<div className="relative h-full w-full flex flex-col gap-4">
<ProfileDetails />
<ProductConfigList />
</div>
);
};
export default Profile;

View File

@@ -0,0 +1,33 @@
import ConfigItem from "./components/ConfigItem";
const ProductConfigList = () => {
return (
<div className="w-full h-full p-4 glass flex flex-col gap-3 pt-14">
<p className="max-md:text-lg text-xl font-bold text-center">
لیست کانفیگ ها
</p>
<div
className="w-full h-full p-3 flex flex-col gap-3
justify-center items-center"
>
<ConfigItem
configName="سرویس تست 1 روزه"
dateBuy="1402/2/3"
isDisabled={false}
/>
<ConfigItem
configName="سرویس مولتی 1"
dateBuy="1403/2/3"
isDisabled={false}
/>
<ConfigItem
configName="سرویس کاهش پینگ"
dateBuy="1402/4/3"
isDisabled={true}
/>
</div>
</div>
);
};
export default ProductConfigList;

View File

@@ -0,0 +1,60 @@
import { MdEdit } from "react-icons/md";
import Button from "../../../components/button";
import ModalEditProfile from "./components/ModalEditProfile";
import { useState } from "react";
const ProfileDetails = () => {
return (
<div className="relative z-[10] w-full h-32 max-md:h-20 flex flex-row-reverse justify-between glass items-center py-2 px-4">
<EditProfile />
<ProfileImage />
<ProductDetails />
</div>
);
};
export default ProfileDetails;
const EditProfile = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="w-full h-full flex justify-end items-center">
<ModalEditProfile isOpen={isOpen} setIsOpen={setIsOpen} />
<Button
extra="!py-4 max-md:!py-2 max-md:px-2 md:ml-4"
onClick={() => setIsOpen(true)}
>
<MdEdit className="scale-[1.4] max-md:scale-100" />
</Button>
</div>
);
};
const ProfileImage = () => {
return (
<div className="relative w-full h-full flex justify-center items-center">
<div className="absolute w-auto h-auto border-4 border-secondTheme rounded-full max-md:scale-90 max-md:top-1/3 top-1/2">
<svg height="100" width="100" xmlns="http://www.w3.org/2000/svg">
<circle r="45" cx="50" cy="50" fill="#111" />
</svg>
</div>
</div>
);
};
const ProductDetails = () => {
return (
<div className="w-full h-full flex justify-start items-center">
<div className="md:mr-4 w-auto h-auto flex flex-col max-md:text-[12px] text-lg md:gap-2">
<p className="text-nowrap">
کانفیگ های فعال :{" "}
<span className="px-2 rounded-md bg-green-600"> 2 </span>
</p>
<p className="text-nowrap">
کانفیگ های غیر فعال :{" "}
<span className="px-2 rounded-md bg-yellow-400"> 10 </span>
</p>
</div>
</div>
);
};

View File

@@ -0,0 +1,34 @@
import { IoCopy } from "react-icons/io5";
import Button from "../../../../components/button";
import { ConfigItemProps } from "../../../../types/props";
const ConfigItem = ({ dateBuy, configName, isDisabled }: ConfigItemProps) => {
return (
<div className="relative w-full h-full rounded-md">
{isDisabled && (
<div className="absolute inset-0 flex items-center justify-center bg-opacity-50 rounded-md backdrop-blur-sm z-10">
<p className="text-white font-bold px-1 bg-red-600 text-xs md:text-base rounded-md shadow-md shadow-red-600">
کانفیگ <span className="text-secondTheme">{configName}</span> غیر فعال است
</p>
</div>
)}
<div
className={`w-full h-full p-3 rounded-md text-xs md:text-base gap-2 flex justify-between items-center text-secondTheme bg-gray-900 shadow-md shadow-gray-900 ${
isDisabled ? "filter blur-sm" : ""
}`}
>
<p>تاریخ خرید : {dateBuy}</p>
<li className="px-2 py-1 bg-secondTheme shadow-md shadow-secondTheme rounded-md w-auto h-auto text-gray-900 font-bold list-none">
{configName}
</li>
<div className="flex flex-row-reverse gap-2 justify-end items-center">
<Button extra="!m-0 !px-2 !py-1.5 shadow-md shadow-white">
<IoCopy />
</Button>
<Button extra="!m-0 !px-2 !py-1 shadow-md shadow-white">آموزش</Button>
</div>
</div>
</div>
);
};
export default ConfigItem;

View File

@@ -0,0 +1,26 @@
import { Dispatch, SetStateAction } from "react";
import MuiModal from "../../../../common/modal";
import Input from "../../../../components/input";
const ModalEditProfile = ({
isOpen,
setIsOpen,
}: {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}) => {
return (
<MuiModal
isOpenModal={isOpen}
setIsOpenModal={setIsOpen}
submitText="تایید"
title="ویرایش پروفایل"
>
<div className="relative w-full h-full px-2 md:px-4">
<Input type="text" label="ویرایش نام :" labelStyle="!text-start" extra="!w-full" />
</div>
</MuiModal>
);
};
export default ModalEditProfile;

View File

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

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

@@ -50,7 +50,7 @@ export interface NavData {
}
// begin ListTutorial.tsx
import { Component, InputHTMLAttributes, SetStateAction } from "react";
import React, { Component, InputHTMLAttributes, SetStateAction } from "react";
export type TutorialList = { id: number; name: string }[];
@@ -92,10 +92,22 @@ export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
type: string;
name?: string;
placeholder?: string;
labelStyle?: string;
extra?: string;
}
export interface MuiModalProps {
isOpenModal: boolean;
setIsOpenModal:SetStateAction
setIsOpenModal: SetStateAction;
title: string;
nameService?: string;
submitText: string;
submitOnClick?: () => void;
children: React.ReactNode;
}
export interface ConfigItemProps {
dateBuy: string;
configName: string;
isDisabled: boolean;
}