add: logout dialog and auth based side menu items

This commit is contained in:
Mahyar Khanbolooki
2025-07-18 20:20:55 +03:30
parent 77505d5098
commit ee056ee947
3 changed files with 115 additions and 44 deletions
+78 -11
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import React, { useMemo, useRef } from 'react' import React, { useMemo, useRef, useState } from 'react'
import { motion, PanInfo, Variants } from 'framer-motion'; import { motion, PanInfo, Variants } from 'framer-motion';
import MenuItemType from './SideMenuItem' import MenuItemType from './SideMenuItem'
import SideMenuItem from './SideMenuItem'; import SideMenuItem from './SideMenuItem';
@@ -17,11 +17,15 @@ import BlurredOverlayContainer from '../overlays/BlurredOverlayContainer';
import SettingIcon from '../icons/SettingsIcon'; import SettingIcon from '../icons/SettingsIcon';
import ExitIcon from '../icons/ExitIcon'; import ExitIcon from '../icons/ExitIcon';
import NightModeSwitch from '../button/NightModeSwitch'; import NightModeSwitch from '../button/NightModeSwitch';
import Button from '../button/PrimaryButton';
import { useAuthStore } from '@/zustand/authStore';
import { useRouter } from 'next/navigation';
type MenuItemType = { type MenuItemType = {
href: string | undefined; href: string | undefined;
title: string; title: string;
icon: React.ReactElement icon: React.ReactElement;
auth?: boolean;
} }
const menuItems: Array<Array<MenuItemType>> = [ const menuItems: Array<Array<MenuItemType>> = [
@@ -57,10 +61,10 @@ const menuItems: Array<Array<MenuItemType>> = [
[], [],
[ [
{ {
href: undefined, title: 'تنظیمات', icon: <SettingIcon width={20} height={20} className='text-icon-deactive' /> auth: true, href: undefined, title: 'تنظیمات', icon: <SettingIcon width={20} height={20} className='text-icon-deactive' />
}, },
{ {
href: undefined, title: 'خروج', icon: <ExitIcon width={20} height={20} className='text-icon-deactive' /> auth: true, href: '?logout', title: 'خروج', icon: <ExitIcon width={20} height={20} className='text-icon-deactive' />
}, },
] ]
] ]
@@ -74,15 +78,24 @@ type Props = {
function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeState }: Props) { function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeState }: Props) {
const menuStateMemo = useMemo(() => menuState, [menuState]); const menuStateMemo = useMemo(() => menuState, [menuState]);
const [logoutModalVisible, setLogoutModalVisible] = useState(false);
const closeRef: React.Ref<HTMLDivElement> | undefined = useRef(null); const closeRef: React.Ref<HTMLDivElement> | undefined = useRef(null);
const userIsAuthenticated = true; //useAuthStore((state) => state.isAuthenticated);
const authLogout = useAuthStore((state) => state.logout);
const router = useRouter();
const variants: Variants = { const variants: Variants = {
hidden: { hidden: {
x: '100%', x: '100%',
transition: {
duration: 0.1,
ease: 'easeInOut',
},
}, },
visible: { visible: {
x: 0, x: 0,
transition: { transition: {
delay: 0.075,
duration: 0.1, duration: 0.1,
ease: 'easeInOut', ease: 'easeInOut',
}, },
@@ -90,19 +103,37 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
}; };
const onDrag = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { const onDrag = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
if (info.offset.x > 100) { if (info.offset.x > 20) {
if (closeRef.current) { if (closeRef.current) {
closeRef.current.click(); closeRef.current.click();
} }
} }
} }
const toggleLogoutModalVisiblity = (e: React.MouseEvent<HTMLAnchorElement> | React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLDivElement> | undefined) => {
e?.stopPropagation();
setLogoutModalVisible((state) => !state);
}
const onLogout = (e: React.MouseEvent<HTMLButtonElement> | null) => {
if (!e) return;
if (userIsAuthenticated) {
authLogout();
router.replace('/');
}
};
const hrefOnClicks: Array<{ href: string, handler: React.MouseEventHandler<HTMLAnchorElement> | undefined }> = [
{ href: '?logout', handler: toggleLogoutModalVisiblity }
]
return ( return (
<>
<div className='w-full' ref={closeRef} onClick={toggleMenuState} > <div className='w-full' ref={closeRef} onClick={toggleMenuState} >
<BlurredOverlayContainer bgOpacity={40} visible={menuStateMemo} outDelay={150}> <BlurredOverlayContainer bgOpacity={40} visible={menuStateMemo} outDelay={150} inDuration={200}>
<motion.div <motion.div
drag='x' drag='x'
dragConstraints={{ left: 0, right: 0 }} dragConstraints={{ left: 0, right: 20 }}
transition={{ type: "spring", stiffness: 200, damping: 20 }} transition={{ type: "spring", stiffness: 200, damping: 20 }}
dragTransition={{ bounceDamping: 10, bounceStiffness: 600 }} dragTransition={{ bounceDamping: 10, bounceStiffness: 600 }}
dragElastic={0.05} dragElastic={0.05}
@@ -113,28 +144,64 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
data-visible={menuState} data-visible={menuState}
data-isvisible={menuStateMemo} data-isvisible={menuStateMemo}
onClick={(e) => { e.stopPropagation(); }} onClick={(e) => { e.stopPropagation(); }}
className={`fixed top-0 bottom-0 -right-full data-[isvisible=true]:right-0 transition-all duration-100 ease-in-out not-xl:!rounded-s-none overflow-clip bg-white w-[200px] h-dvh flex flex-col z-40`}> className={`fixed top-0 bottom-0 right-0 ease-in-out not-xl:!rounded-s-none overflow-clip bg-white w-[200px] h-dvh flex flex-col z-40`}>
<ul className="overflow-y-scroll noscrollbar pt-20 pb-10 flex flex-col justify-between h-full "> <ul className="overflow-y-scroll noscrollbar pt-20 pb-10 flex flex-col justify-between h-full ">
<li> <li>
<div className='flex flex-col gap-[60px]'> <div className='flex flex-col gap-[60px]'>
<div className='flex flex-col gap-[23px]'> <div className='flex flex-col gap-[23px]'>
<div className='pt-2 text-start px-12 text-menu-header'>منو</div> <div className='pt-2 text-start px-12 text-menu-header'>منو</div>
{menuItems[0].map((v, i) => { {menuItems[0].filter((x) => x.auth ? userIsAuthenticated && x : x).map((v, i) => {
return <SideMenuItem key={i} href={v.href || ''} title={v.title} icon={v.icon} /> return <SideMenuItem key={i} href={v.href || ''} title={v.title} icon={v.icon} />
})} })}
</div> </div>
</div> </div>
</li> </li>
<li className='flex flex-col gap-6 pt-16'> <li className='flex flex-col gap-6 pt-16'>
{menuItems[2].map((v, i) => { {menuItems[2].filter((x) => x.auth ? userIsAuthenticated && x : x).map((v, i) => {
return <SideMenuItem key={i} href={v.href || ''} title={v.title} icon={v.icon} /> return <SideMenuItem
key={i}
href={v.href || ''}
onClick={v.href && hrefOnClicks.find((i) => i.href === v.href)?.handler || undefined}
title={v.title}
icon={v.icon}
/>
})} })}
<SideMenuItem href={''} title={''} icon={<></>}><NightModeSwitch checked={nightModeState} onClick={togglenightModeState} /></SideMenuItem> <SideMenuItem href={''} title={''} icon={<></>}><NightModeSwitch checked={nightModeState} onClick={togglenightModeState} /></SideMenuItem>
</li> </li>
</ul> </ul>
</motion.div> </motion.div>
</BlurredOverlayContainer> </BlurredOverlayContainer>
</div> </div>
<BlurredOverlayContainer visible={logoutModalVisible} onClick={toggleLogoutModalVisiblity} inDuration={100} outDuration={100} outDelay={75}>
<div
className='absolute top-1/2 left-0 w-full px-6' onClick={toggleLogoutModalVisiblity}>
<motion.div
animate={{ y: logoutModalVisible ? [10, 0] : [0, 10], }}
transition={{ duration: 0.1, ease: 'easeInOut' }}
className='top-1/2 min-w-xs w-full max-w-sm justify-self-center text-center -translate-y-1/2 px-6 pt-8 pb-9 drop-shadow-container bg-white/63 rounded-4xl'
>
<div className='text-base font-medium'>خروج از سیستم</div>
<div className='text-xs font-medium mt-6'>آیا میخواهید از سیستم خارج شوید؟</div>
<div className='grid grid-cols-2 gap-5.5 mt-8'>
<Button
onClick={onLogout}
className='text-sm font-normal'
>
بله خارج میشوم
</Button>
<Button
className='bg-disabled! text-disabled2! text-sm font-normal'
onClick={toggleLogoutModalVisiblity}
>
منصرف شدم
</Button>
</div>
</motion.div>
</div>
</BlurredOverlayContainer>
</>
) )
} }
@@ -10,6 +10,8 @@ export type BlurredOverlayContainerProps = {
inDelay?: number inDelay?: number
outDelay?: number outDelay?: number
transitionDelay?: number transitionDelay?: number
inDuration?: number
outDuration?: number
children: ReactNode children: ReactNode
} & React.ComponentProps<typeof motion.div> } & React.ComponentProps<typeof motion.div>
@@ -17,7 +19,9 @@ const BlurredOverlayContainer = ({
visible = true, visible = true,
bgOpacity = 30, bgOpacity = 30,
inDelay = 0, inDelay = 0,
outDelay = 0, outDelay = 200,
inDuration = 300,
outDuration = 300,
transitionDelay: transitionDelay = 0, transitionDelay: transitionDelay = 0,
children, children,
className, className,
@@ -45,7 +49,7 @@ const BlurredOverlayContainer = ({
display: 'none', display: 'none',
transition: { transition: {
delay: transitionDelay / 1000, delay: transitionDelay / 1000,
duration: 0.3, duration: outDuration / 1000,
ease: 'easeInOut', ease: 'easeInOut',
}, },
}, },
@@ -55,7 +59,7 @@ const BlurredOverlayContainer = ({
display: 'block', display: 'block',
transition: { transition: {
delay: transitionDelay / 1000, delay: transitionDelay / 1000,
duration: 0.3, duration: inDuration / 1000,
ease: 'easeInOut', ease: 'easeInOut',
}, },
}, },