fix: keep same page in router.back in hash navigation

This commit is contained in:
sk1982 2024-04-07 02:49:47 -04:00
parent b0590c81f2
commit 6d0f663c7d
3 changed files with 81 additions and 34 deletions

View File

@ -19,6 +19,7 @@ import { FriendRequest, acceptFriendRequest, getFriendRequests, rejectFriendRequ
import { useWindowListener } from '@/helpers/use-window-listener'; import { useWindowListener } from '@/helpers/use-window-listener';
import { useErrorModal } from '@/components/error-modal'; import { useErrorModal } from '@/components/error-modal';
import { useSwipeable } from 'react-swipeable'; import { useSwipeable } from 'react-swipeable';
import { useReloaded } from '@/components/client-providers';
export type HeaderSidebarProps = { export type HeaderSidebarProps = {
children?: React.ReactNode, children?: React.ReactNode,
@ -44,6 +45,8 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
const [userDropdownOpen, setUserDropdownOpen] = useState(false); const [userDropdownOpen, setUserDropdownOpen] = useState(false);
const [menuTranslate, setMenuTranslate] = useState<number | null>(null); const [menuTranslate, setMenuTranslate] = useState<number | null>(null);
const [notificationsTranslate, setNotificationsTranslate] = useState<number | null>(null); const [notificationsTranslate, setNotificationsTranslate] = useState<number | null>(null);
const reloaded = useReloaded();
const menusOpened = useRef(false);
const path = pathname === '/' ? (user?.homepage ?? '/dashboard') : pathname; const path = pathname === '/' ? (user?.homepage ?? '/dashboard') : pathname;
@ -54,6 +57,13 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
const setError = useErrorModal(); const setError = useErrorModal();
const back = useCallback(() => {
if (document.referrer.includes(location.origin) || reloaded || menusOpened.current)
router.back();
else
router.replace('', { scroll: false });
}, [router, reloaded]);
const { ref } = useSwipeable({ const { ref } = useSwipeable({
touchEventOptions: { passive: false }, touchEventOptions: { passive: false },
onSwiped: e => { onSwiped: e => {
@ -63,11 +73,11 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
if (menuTranslate && ((Math.abs(menuTranslate) > 384 * 0.5) || fastSwipe)) { if (menuTranslate && ((Math.abs(menuTranslate) > 384 * 0.5) || fastSwipe)) {
if (isMenuOpen && window.location.hash === '#sidebar') if (isMenuOpen && window.location.hash === '#sidebar')
router.back(); back();
setMenuOpen(!isMenuOpen); setMenuOpen(!isMenuOpen);
} else if (notificationsTranslate && ((Math.abs(notificationsTranslate) > window.innerWidth * 0.4 || fastSwipe))) { } else if (notificationsTranslate && ((Math.abs(notificationsTranslate) > window.innerWidth * 0.4 || fastSwipe))) {
if (isNotificationsOpen && window.location.hash === '#notifications') if (isNotificationsOpen && window.location.hash === '#notifications')
router.back(); back();
setNotificationsOpen(!isNotificationsOpen); setNotificationsOpen(!isNotificationsOpen);
} }
@ -87,11 +97,6 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
return; return;
parent = parent.parentElement; parent = parent.parentElement;
} }
const data = (e.event.target as HTMLElement)?.dataset;
if (data?.slot === 'thumb' || data?.dragging)
return;
const allMenusClosed = !isMenuOpen && !isNotificationsOpen; const allMenusClosed = !isMenuOpen && !isNotificationsOpen;
const xPercent = e.initial[0] / window.innerWidth; const xPercent = e.initial[0] / window.innerWidth;
@ -126,16 +131,22 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
getFriendRequests().then(setFriendRequests); getFriendRequests().then(setFriendRequests);
}, [pathname, user]); }, [pathname, user]);
const setNotificationsOpen = (open: boolean) => { const setNotificationsOpen = (open: boolean, updateRef = true) => {
_setNotificationsOpen(open); _setNotificationsOpen(open);
if (open) if (open) {
router.push('#notifications', { scroll: false }); router.push('#notifications', { scroll: false });
if (updateRef)
menusOpened.current = true;
}
}; };
const setMenuOpen = (open: boolean) => { const setMenuOpen = (open: boolean, updateRef = true) => {
_setMenuOpen(open); _setMenuOpen(open);
if (open) if (open) {
router.push('#sidebar', { scroll: false }); router.push('#sidebar', { scroll: false });
if (updateRef)
menusOpened.current = true;
}
}; };
useWindowListener('hashchange', () => { useWindowListener('hashchange', () => {
@ -144,8 +155,8 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
}); });
useEffect(() => { useEffect(() => {
setMenuOpen(window.location.hash === '#sidebar'); setMenuOpen(window.location.hash === '#sidebar', false);
setNotificationsOpen(window.location.hash === '#notifications'); setNotificationsOpen(window.location.hash === '#notifications', false);
}, []); }, []);
const renderHeaderLink = useCallback((route: Subroute) => { const renderHeaderLink = useCallback((route: Subroute) => {
@ -310,7 +321,7 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
<div className={`transition bg-black z-[49] absolute inset-0 w-full h-full ${isMenuOpen ? 'bg-opacity-25' : 'bg-opacity-0 pointer-events-none'}`} onClick={() => { <div className={`transition bg-black z-[49] absolute inset-0 w-full h-full ${isMenuOpen ? 'bg-opacity-25' : 'bg-opacity-0 pointer-events-none'}`} onClick={() => {
setMenuOpen(false); setMenuOpen(false);
if (window.location.hash === '#sidebar') if (window.location.hash === '#sidebar')
router.back(); back();
}} /> }} />
<div className={`dark flex flex-col text-white absolute p-6 top-0 h-full max-w-full w-96 bg-gray-950 z-[49] left-0 ${menuTranslate ? '' : 'transition-all'} ${isMenuOpen ? 'shadow-2xl' : '-translate-x-full'}`} style={menuTranslate ? { <div className={`dark flex flex-col text-white absolute p-6 top-0 h-full max-w-full w-96 bg-gray-950 z-[49] left-0 ${menuTranslate ? '' : 'transition-all'} ${isMenuOpen ? 'shadow-2xl' : '-translate-x-full'}`} style={menuTranslate ? {
@ -321,7 +332,7 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
onClick={() => { onClick={() => {
setMenuOpen(false); setMenuOpen(false);
if (window.location.hash === '#sidebar') if (window.location.hash === '#sidebar')
router.back(); back();
}}> }}>
<ChevronLeftIcon className="h-6 mt-0.5" /> <ChevronLeftIcon className="h-6 mt-0.5" />
<span>{routeGroup.title}</span> <span>{routeGroup.title}</span>
@ -329,7 +340,7 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
<Button className="ml-auto" isIconOnly color="danger" onClick={() => { <Button className="ml-auto" isIconOnly color="danger" onClick={() => {
setMenuOpen(false); setMenuOpen(false);
if (window.location.hash === '#sidebar') if (window.location.hash === '#sidebar')
router.back(); back();
}}> }}>
<XMarkIcon className="w-5" /> <XMarkIcon className="w-5" />
</Button> </Button>
@ -384,7 +395,7 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
</Button> </Button>
</div> </div>
</div>); </div>);
}, [isMenuOpen, router, menuTranslate, routeGroup, user, path, cookies]); }, [isMenuOpen, router, menuTranslate, routeGroup, user, path, cookies, back]);
const rightSidebar = useMemo(() => { const rightSidebar = useMemo(() => {
return (<div className={`fixed inset-0 w-full h-full max-h-full z-[49] ${isNotificationsOpen ? '' : 'pointer-events-none'}`}> return (<div className={`fixed inset-0 w-full h-full max-h-full z-[49] ${isNotificationsOpen ? '' : 'pointer-events-none'}`}>
@ -407,7 +418,7 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
<Button isIconOnly className="ml-2" color="danger" onPress={() => { <Button isIconOnly className="ml-2" color="danger" onPress={() => {
setNotificationsOpen(false); setNotificationsOpen(false);
if (window.location.hash === '#notifications') if (window.location.hash === '#notifications')
router.back(); back();
}}> }}>
<XMarkIcon className="h-1/2" /> <XMarkIcon className="h-1/2" />
</Button> </Button>
@ -444,7 +455,7 @@ export const HeaderSidebar = ({ children }: HeaderSidebarProps) => {
</Button> </Button>
</div> </div>
</div>); </div>);
}, [isNotificationsOpen, notificationsTranslate, user, friendRequests, notifications, router]); }, [isNotificationsOpen, notificationsTranslate, user, friendRequests, notifications, router, back]);
return (<> return (<>
{ leftSidebar } { leftSidebar }

View File

@ -1,22 +1,50 @@
'use client'; 'use client';
import { ReactNode } from 'react'; import { ReactNode, createContext, useContext, useEffect, useRef, useState } from 'react';
import { NextUIProvider } from '@nextui-org/react'; import { NextUIProvider } from '@nextui-org/react';
import { ThemeProvider as NextThemesProvider } from 'next-themes'; import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { ErrorProvider } from '@/components/error-modal'; import { ErrorProvider } from '@/components/error-modal';
import { ConfirmProvider } from '@/components/confirm-modal'; import { ConfirmProvider } from '@/components/confirm-modal';
import { PromptProvider } from '@/components/prompt-modal'; import { PromptProvider } from '@/components/prompt-modal';
import { useWindowListener } from '@/helpers/use-window-listener';
import { usePathname } from 'next/navigation';
export function ClientProviders({ children }: { children: ReactNode }) { const ReloadContext = createContext(false);
return (<ErrorProvider>
<ConfirmProvider> export const useReloaded = () => useContext(ReloadContext);
<PromptProvider>
<NextUIProvider className="h-full flex"> export function ClientProviders({ children }: { children: ReactNode; }) {
<NextThemesProvider attribute="class" defaultTheme="dark" enableSystem> const [isReloaded, setReloaded] = useState(false);
{children} const pathname = usePathname();
</NextThemesProvider> const lastPathname = useRef<string | null>(null);
</NextUIProvider>
</PromptProvider> useEffect(() => {
</ConfirmProvider> const reloaded = sessionStorage.getItem('reload');
</ErrorProvider>); if (reloaded && Date.now() - +reloaded < 1000)
setReloaded(true);
sessionStorage.removeItem('reload');
}, []);
useWindowListener('beforeunload', () => sessionStorage.setItem('reload', Date.now().toString()));
useEffect(() => {
if (lastPathname.current !== null) {
if (lastPathname.current !== pathname)
setReloaded(false);
}
lastPathname.current = pathname;
}, [pathname]);
return (<ReloadContext.Provider value={isReloaded}>
<ErrorProvider>
<ConfirmProvider>
<PromptProvider>
<NextUIProvider className="h-full flex">
<NextThemesProvider attribute="class" defaultTheme="dark" enableSystem>
{children}
</NextThemesProvider>
</NextUIProvider>
</PromptProvider>
</ConfirmProvider>
</ErrorProvider>
</ReloadContext.Provider>);
} }

View File

@ -9,6 +9,7 @@ import { useDebounceCallback } from 'usehooks-ts';
import { CellMeasurerChildProps } from 'react-virtualized/dist/es/CellMeasurer'; import { CellMeasurerChildProps } from 'react-virtualized/dist/es/CellMeasurer';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useWindowListener } from '@/helpers/use-window-listener'; import { useWindowListener } from '@/helpers/use-window-listener';
import { useReloaded } from './client-providers';
@ -205,6 +206,8 @@ export const SelectModalButton = <T extends 'grid' | 'list', D extends { name?:
const router = useRouter(); const router = useRouter();
const [isOpen, setOpen] = useState(false); const [isOpen, setOpen] = useState(false);
const { modalId, footer, onSelectionChanged, gap, onSelected, selectedItem, renderItem, items, colSize, rowSize, displayMode, modalSize } = props; const { modalId, footer, onSelectionChanged, gap, onSelected, selectedItem, renderItem, items, colSize, rowSize, displayMode, modalSize } = props;
const historyPushed = useRef(false);
const reloaded = useReloaded();
useWindowListener('hashchange', () => { useWindowListener('hashchange', () => {
if (window.location.hash !== `#modal-${modalId}` && isOpen) { if (window.location.hash !== `#modal-${modalId}` && isOpen) {
@ -224,11 +227,16 @@ export const SelectModalButton = <T extends 'grid' | 'list', D extends { name?:
onSelected={item => { onSelected={item => {
setOpen(false); setOpen(false);
onSelected(item); onSelected(item);
router.back(); if (document.referrer.includes(location.origin) || historyPushed.current || reloaded) {
router.back();
} else {
router.replace('', { scroll: false });
}
}} /> }} />
<Button {...(props as object)} onClick={() => { <Button {...(props as object)} onClick={() => {
setOpen(true); setOpen(true);
router.push(`#modal-${modalId}`,{ scroll: false }); router.push(`#modal-${modalId}`, { scroll: false });
historyPushed.current = true;
}} /> }} />
</>); </>);
}; };