Commit ca2aec8d authored by Niko Mlynarcik's avatar Niko Mlynarcik Committed by Adam Štěpánek
Browse files

Prompt user before leaving edit or create pages

parent 2a602d0f
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
@@ -18,13 +18,15 @@ import {
} from '@chakra-ui/react';
import i18next, { t } from 'i18next';
import { FiChevronDown, FiMenu } from 'react-icons/fi';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useApi, useAuth } from '../../../hooks/Caffeine';
import { ColorModeToggle } from '../../utils/ColorModeToggle';
import { KafeAvatar } from '../../utils/KafeAvatar';
import { LanguageToggle } from '../../utils/LanguageToggle';
import { Logo } from '../Logo';
import { MessageButton } from '../MessageButton';
import { useAuthLinkFunction } from '../../../hooks/useAuthLink';
import { ConfirmExitLink } from '../../utils/ConfirmExitLink';

export const NAVBAR_HEIGHT = 20;

@@ -36,7 +38,10 @@ interface INavbarProps extends FlexProps {
export function Navbar({ onOpen, forceReload, signedIn, ...rest }: INavbarProps) {
    const { toggleColorMode } = useColorMode();
    const { user, setUser } = useAuth();
    const location = useLocation();
    const navigate = useNavigate();
    const authLink = useAuthLinkFunction();

    const api = useApi();

    const toggleLanguage = () => {
@@ -90,6 +95,18 @@ export function Navbar({ onOpen, forceReload, signedIn, ...rest }: INavbarProps)
                />
            )}

            {signedIn && <Spacer display={{ base: 'flex', md: 'none' }} />}

            <Flex h="20" alignItems="center" ml={2} mr={8} justifyContent="space-between" key="heading">
                <ConfirmExitLink
                    alertCondition={location.pathname.endsWith("edit") || location.pathname.endsWith("create")}
                    destPath={authLink("/")}
                    handleConfirm={() => {}}
                >
                    <Logo />
                </ConfirmExitLink>
            </Flex>

            <Spacer />

            <HStack spacing={1}>
+47 −26
Original line number Diff line number Diff line
import { BoxProps, CloseButton, Flex, IconButton, Spacer, useColorModeValue, VStack } from '@chakra-ui/react';
import { 
    BoxProps, 
    CloseButton, 
    Flex, 
    IconButton, 
    Spacer, 
    useColorModeValue, 
    VStack, 
} from '@chakra-ui/react';
import { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { IoReader, IoReaderOutline, IoSettingsOutline } from 'react-icons/io5';
import { Link, useMatches } from 'react-router-dom';
import { useLocation, useMatches } from 'react-router-dom';
import { useAuth, useOrganizations } from '../../../hooks/Caffeine';
import { useAuthLink } from '../../../hooks/useAuthLink';
import { useAuthLinkFunction } from '../../../hooks/useAuthLink';
import { useColorScheme } from '../../../hooks/useColorScheme';
import { AppRoute, authRoutes } from '../../../routes';
import { Permission } from '../../../schemas/generic';
@@ -13,6 +21,7 @@ import { OrganizationAvatar } from '../../utils/OrganizationAvatar/OrganizationA
import { Footer } from '../Footer';
import { ReportButton } from '../ReportButton';
import { NavItem } from './NavItem';
import { ConfirmExitLink } from '../../utils/ConfirmExitLink';

interface ISidebarProps extends BoxProps {
    onClose: () => void;
@@ -29,6 +38,8 @@ export function Sidebar({ onClose, forceReload, ...rest }: ISidebarProps) {
    const { user } = useAuth();

    const routeValues = authRoutes(i18next.t, user, useOrganizations().currentOrganization);
    const location = useLocation();
    const authLink = useAuthLinkFunction();

    const match = matches[matches.length - 1].id
        .split('-')
@@ -55,7 +66,11 @@ export function Sidebar({ onClose, forceReload, ...rest }: ISidebarProps) {

            return (
                <Fragment key={i}>
                    <Link to={useAuthLink(fullPath)}>
                    <ConfirmExitLink 
                        alertCondition={location.pathname.endsWith("edit") || location.pathname.endsWith("create")}
                        destPath={authLink(fullPath)}
                        handleConfirm={() => {}}
                    >
                        <NavItem
                                key={i}
                                icon={
@@ -71,7 +86,7 @@ export function Sidebar({ onClose, forceReload, ...rest }: ISidebarProps) {
                            >
                                {route.title}
                        </NavItem>
                    </Link>
                    </ConfirmExitLink>
                    {selected && children && children.length > 0 && (
                        <Flex
                            direction="column"
@@ -128,24 +143,29 @@ export function Sidebar({ onClose, forceReload, ...rest }: ISidebarProps) {
                h="full"
            >
                {organizations.map((org, key) => (
                    <Link
                        to={useAuthLink(undefined, org.id)}
                    <ConfirmExitLink 
                        key={key}
                        onClick={() => {
                        alertCondition={location.pathname.endsWith("edit") || location.pathname.endsWith("create")}
                        destPath={authLink(`/`, org.id)} 
                        handleConfirm={() => {
                            localStorage.setItem(LS_LATEST_ORG_KEY, org.id);
                            forceReload();
                        }}
                            forceReload();}
                        }
                    >
                        <OrganizationAvatar organization={org} size="sm" cursor="pointer" />
                    </Link>
                    </ConfirmExitLink>
                ))}
                <Spacer />
                {(['read', 'append', 'inspect', 'write', 'all'] as Permission[]).some((perm) =>
                    user?.permissions['system']?.includes(perm),
                ) && (
                    <Link to={useAuthLink('/system')}>
                    <ConfirmExitLink
                        alertCondition={location.pathname.endsWith("edit") || location.pathname.endsWith("create")}
                        destPath={authLink('/system')}  
                        handleConfirm={() => {}}
                    >
                        <IconButton aria-label="Settings" icon={<IoSettingsOutline />} borderRadius="full" />
                    </Link>
                    </ConfirmExitLink>
                )}
            </VStack>
            <Flex
@@ -174,3 +194,4 @@ export function Sidebar({ onClose, forceReload, ...rest }: ISidebarProps) {
        </Flex>
    );
}
+44 −0
Original line number Diff line number Diff line
import { Link, useNavigate } from 'react-router-dom';
import { LeavePageAlert } from './LeavePageAlert';
import { useDisclosure } from '@chakra-ui/react';

interface ILeavePageAlertProps {
    alertCondition: boolean;
    destPath: string;
    handleConfirm: () => void;
    children: React.ReactNode;
}

export function ConfirmExitLink({ alertCondition, destPath, handleConfirm, children }: ILeavePageAlertProps) {
    const {
        isOpen,
        onOpen,
        onClose
    } = useDisclosure()
    
    const navigate = useNavigate();
    
    return (
        <Link to={destPath}
            onClick={(e) => {
                if (alertCondition) {
                    e.preventDefault();
                    onOpen();
                }
                else {
                    handleConfirm();
                }
            }}
        >
        <LeavePageAlert
            isOpen={isOpen}
            onClose={onClose}
            handleConfirm={() => {
                navigate(destPath);
                handleConfirm();
            }}
        />
        {children}
        </Link>
    )
}
 No newline at end of file
+52 −0
Original line number Diff line number Diff line
import { 
    BoxProps, 
    AlertDialog, 
    AlertDialogBody, 
    AlertDialogContent, 
    AlertDialogFooter, 
    AlertDialogHeader, 
    AlertDialogOverlay, 
    Button, 
} from '@chakra-ui/react';
import { createRef } from 'react';
import { t } from 'i18next';

interface ILeavePageAlertProps extends BoxProps {
    isOpen: boolean;
    onClose: () => void;
    handleConfirm: () => void;
}

export function LeavePageAlert({ isOpen, onClose, handleConfirm }: ILeavePageAlertProps) {
    const cancelRef = createRef<HTMLButtonElement>();
    
    return (
        <AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
            <AlertDialogOverlay>
                <AlertDialogContent>
                    <AlertDialogHeader fontSize="lg" fontWeight="bold">
                        {t('error.leavePageTitle').toString()}
                    </AlertDialogHeader>

                    <AlertDialogBody>{t('error.leavePageWarn').toString()}</AlertDialogBody>

                    <AlertDialogFooter>
                        <Button ref={cancelRef} onClick={onClose}>
                            {t('generic.cancel').toString()}
                        </Button>
                        <Button
                            colorScheme="red"
                            onClick={() => {
                                handleConfirm();
                                onClose();
                            }}
                            ml={3}
                        >
                            {t('generic.ok').toString()}
                        </Button>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialogOverlay>
        </AlertDialog>
    )
}
 No newline at end of file
+4 −1
Original line number Diff line number Diff line
@@ -138,10 +138,13 @@
    "projectDoesNotExist": "Projekt s daným ID neexistuje",
    "subtitle": "Omlouváme se za potíže. Pokud máte pocit, že se to nemělo stát (což pravděpodobně nemělo), pošlete nám prosím tento report",
    "title": "Jejky, tak to nešlo podle plánu",
    "tokenInvalid": "Token není platný"
    "tokenInvalid": "Token není platný",
    "leavePageTitle": "Neuložené změny",
    "leavePageWarn": "Můžete mít neuložené změny, opravdu chcete stránku opustit?"
  },
  "generic": {
    "cancel": "Zrušit",
    "ok": "OK",
    "edit": "Upravit",
    "empty": "Kde nic, tu nic...",
    "for": "pro",
Loading