Confirm deletions

This commit is contained in:
Gregory Schier
2023-03-30 17:09:11 -07:00
parent 7c2611a5a7
commit f3fbd070dd
8 changed files with 95 additions and 28 deletions

View File

@@ -1,6 +1,8 @@
import type { HTMLAttributes, ReactElement } from 'react'; import type { HTMLAttributes, ReactElement } from 'react';
import { useConfirm } from '../hooks/useConfirm';
import { useDeleteRequest } from '../hooks/useDeleteRequest'; import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDuplicateRequest } from '../hooks/useDuplicateRequest'; import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
import { useRequest } from '../hooks/useRequest';
import { Dropdown } from './core/Dropdown'; import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon'; import { Icon } from './core/Icon';
@@ -9,9 +11,11 @@ interface Props {
children: ReactElement<HTMLAttributes<HTMLButtonElement>>; children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
} }
export function RequestSettingsDropdown({ requestId, children }: Props) { export function RequestActionsDropdown({ requestId, children }: Props) {
const request = useRequest(requestId ?? null);
const deleteRequest = useDeleteRequest(requestId ?? null); const deleteRequest = useDeleteRequest(requestId ?? null);
const duplicateRequest = useDuplicateRequest({ id: requestId, navigateAfter: true }); const duplicateRequest = useDuplicateRequest({ id: requestId, navigateAfter: true });
const confirm = useConfirm();
return ( return (
<Dropdown <Dropdown
@@ -23,7 +27,17 @@ export function RequestSettingsDropdown({ requestId, children }: Props) {
}, },
{ {
label: 'Delete', label: 'Delete',
onSelect: deleteRequest.mutate, onSelect: async () => {
const confirmed = await confirm({
title: 'Delete Request',
description: `Are you sure you want to delete "${request?.name}"?`,
confirmButtonColor: 'danger',
confirmButtonText: 'Delete',
});
if (confirmed) {
deleteRequest.mutate();
}
},
leftSlot: <Icon icon="trash" />, leftSlot: <Icon icon="trash" />,
}, },
]} ]}

View File

@@ -13,7 +13,7 @@ import { Button } from './core/Button';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { HStack, VStack } from './core/Stacks'; import { HStack, VStack } from './core/Stacks';
import { DropMarker } from './DropMarker'; import { DropMarker } from './DropMarker';
import { RequestSettingsDropdown } from './RequestSettingsDropdown'; import { RequestActionsDropdown } from './RequestActionsDropdown';
import { ToggleThemeButton } from './ToggleThemeButton'; import { ToggleThemeButton } from './ToggleThemeButton';
interface Props { interface Props {
@@ -218,7 +218,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
</span> </span>
)} )}
</Button> </Button>
<RequestSettingsDropdown requestId={requestId}> <RequestActionsDropdown requestId={requestId}>
<IconButton <IconButton
color="custom" color="custom"
size="sm" size="sm"
@@ -229,7 +229,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
'group-hover/item:opacity-100 focus-visible:opacity-100', 'group-hover/item:opacity-100 focus-visible:opacity-100',
)} )}
/> />
</RequestSettingsDropdown> </RequestActionsDropdown>
</div> </div>
</li> </li>
); );

View File

@@ -2,9 +2,8 @@ import { memo, useCallback } from 'react';
import { useCreateRequest } from '../hooks/useCreateRequest'; import { useCreateRequest } from '../hooks/useCreateRequest';
import { useSidebarDisplay } from '../hooks/useSidebarDisplay'; import { useSidebarDisplay } from '../hooks/useSidebarDisplay';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';
export const SidebarDisplayToggle = memo(function SidebarDisplayToggle() { export const SidebarActions = memo(function SidebarDisplayToggle() {
const sidebarDisplay = useSidebarDisplay(); const sidebarDisplay = useSidebarDisplay();
const createRequest = useCreateRequest({ navigateAfter: true }); const createRequest = useCreateRequest({ navigateAfter: true });
const handleCreateRequest = useCallback(() => { const handleCreateRequest = useCallback(() => {
@@ -12,7 +11,7 @@ export const SidebarDisplayToggle = memo(function SidebarDisplayToggle() {
}, []); }, []);
return ( return (
<HStack space={1}> <>
<IconButton <IconButton
onClick={sidebarDisplay.toggle} onClick={sidebarDisplay.toggle}
className="pointer-events-auto" className="pointer-events-auto"
@@ -27,6 +26,6 @@ export const SidebarDisplayToggle = memo(function SidebarDisplayToggle() {
title="Show sidebar" title="Show sidebar"
icon="plusCircle" icon="plusCircle"
/> />
</HStack> </>
); );
}); });

View File

@@ -11,11 +11,12 @@ import { useWindowSize } from 'react-use';
import { useSidebarDisplay } from '../hooks/useSidebarDisplay'; import { useSidebarDisplay } from '../hooks/useSidebarDisplay';
import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants'; import { WINDOW_FLOATING_SIDEBAR_WIDTH } from '../lib/constants';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { HStack } from './core/Stacks';
import { Overlay } from './Overlay'; import { Overlay } from './Overlay';
import { RequestResponse } from './RequestResponse'; import { RequestResponse } from './RequestResponse';
import { ResizeHandle } from './ResizeHandle'; import { ResizeHandle } from './ResizeHandle';
import { Sidebar } from './Sidebar'; import { Sidebar } from './Sidebar';
import { SidebarDisplayToggle } from './SidebarDisplayToggle'; import { SidebarActions } from './SidebarActions';
import { WorkspaceHeader } from './WorkspaceHeader'; import { WorkspaceHeader } from './WorkspaceHeader';
const side = { gridArea: 'side' }; const side = { gridArea: 'side' };
@@ -127,7 +128,9 @@ export default function Workspace() {
)} )}
> >
<HeaderSize className="border-transparent"> <HeaderSize className="border-transparent">
<SidebarDisplayToggle /> <HStack space={0.5}>
<SidebarActions />
</HStack>
</HeaderSize> </HeaderSize>
<Sidebar /> <Sidebar />
</motion.div> </motion.div>

View File

@@ -1,6 +1,7 @@
import classnames from 'classnames'; import classnames from 'classnames';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useConfirm } from '../hooks/useConfirm';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace'; import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
import { useRoutes } from '../hooks/useRoutes'; import { useRoutes } from '../hooks/useRoutes';
@@ -21,6 +22,7 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
const createWorkspace = useCreateWorkspace({ navigateAfter: true }); const createWorkspace = useCreateWorkspace({ navigateAfter: true });
const deleteWorkspace = useDeleteWorkspace(activeWorkspaceId); const deleteWorkspace = useDeleteWorkspace(activeWorkspaceId);
const routes = useRoutes(); const routes = useRoutes();
const confirm = useConfirm();
const items: DropdownItem[] = useMemo(() => { const items: DropdownItem[] = useMemo(() => {
const workspaceItems = workspaces.map((w) => ({ const workspaceItems = workspaces.map((w) => ({
@@ -46,7 +48,17 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
{ {
label: 'Delete Workspace', label: 'Delete Workspace',
leftSlot: <Icon icon="trash" />, leftSlot: <Icon icon="trash" />,
onSelect: () => deleteWorkspace.mutate(), onSelect: async () => {
const confirmed = await confirm({
title: 'Delete Workspace',
description: `Are you sure you want to delete "${activeWorkspace?.name}"?`,
confirmButtonColor: 'danger',
confirmButtonText: 'Delete',
});
if (confirmed) {
deleteWorkspace.mutate();
}
},
}, },
]; ];
}, [workspaces, activeWorkspaceId]); }, [workspaces, activeWorkspaceId]);

View File

@@ -3,8 +3,8 @@ import { memo } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveRequest } from '../hooks/useActiveRequest';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks'; import { HStack } from './core/Stacks';
import { RequestSettingsDropdown } from './RequestSettingsDropdown'; import { RequestActionsDropdown } from './RequestActionsDropdown';
import { SidebarDisplayToggle } from './SidebarDisplayToggle'; import { SidebarActions } from './SidebarActions';
import { WorkspaceDropdown } from './WorkspaceDropdown'; import { WorkspaceDropdown } from './WorkspaceDropdown';
interface Props { interface Props {
@@ -19,8 +19,8 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
alignItems="center" alignItems="center"
className={classnames(className, 'w-full h-full')} className={classnames(className, 'w-full h-full')}
> >
<HStack className="flex-1 pointer-events-none" alignItems="center"> <HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
<SidebarDisplayToggle /> <SidebarActions />
<WorkspaceDropdown className="pointer-events-auto" /> <WorkspaceDropdown className="pointer-events-auto" />
</HStack> </HStack>
<div className="flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none"> <div className="flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none">
@@ -29,14 +29,14 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
<div className="flex-1 flex justify-end -mr-2 pointer-events-none"> <div className="flex-1 flex justify-end -mr-2 pointer-events-none">
<IconButton size="sm" title="" icon="magnifyingGlass" /> <IconButton size="sm" title="" icon="magnifyingGlass" />
{activeRequest && ( {activeRequest && (
<RequestSettingsDropdown requestId={activeRequest?.id}> <RequestActionsDropdown requestId={activeRequest?.id}>
<IconButton <IconButton
size="sm" size="sm"
title="Request Options" title="Request Options"
icon="gear" icon="gear"
className="pointer-events-auto" className="pointer-events-auto"
/> />
</RequestSettingsDropdown> </RequestActionsDropdown>
)} )}
</div> </div>
</HStack> </HStack>

View File

@@ -1,21 +1,40 @@
import type { ButtonProps } from '../components/core/Button';
import { Button } from '../components/core/Button'; import { Button } from '../components/core/Button';
import { HStack } from '../components/core/Stacks'; import { HStack } from '../components/core/Stacks';
interface Props { interface Props {
hide: () => void; hide: () => void;
onResult: (result: boolean) => void;
confirmButtonColor?: ButtonProps['color'];
confirmButtonText?: string;
} }
export function Confirm({ hide }: Props) { export function Confirm({
hide,
onResult,
confirmButtonColor = 'primary',
confirmButtonText = 'Confirm',
}: Props) {
const focusRef = (el: HTMLButtonElement | null) => { const focusRef = (el: HTMLButtonElement | null) => {
el?.focus(); el?.focus();
}; };
const handleHide = () => {
onResult(false);
hide();
};
const handleSuccess = () => {
onResult(true);
hide();
};
return ( return (
<HStack space={2} justifyContent="end"> <HStack space={2} justifyContent="end">
<Button className="focus" color="gray" onClick={hide}> <Button className="focus" color="gray" onClick={handleHide}>
Cancel Cancel
</Button> </Button>
<Button className="focus" ref={focusRef} color="primary"> <Button className="focus" ref={focusRef} color={confirmButtonColor} onClick={handleSuccess}>
Confirm {confirmButtonText}
</Button> </Button>
</HStack> </HStack>
); );

View File

@@ -1,14 +1,34 @@
import type { ButtonProps } from '../components/core/Button';
import { useDialog } from '../components/DialogContext'; import { useDialog } from '../components/DialogContext';
import { Confirm } from './Confirm'; import { Confirm } from './Confirm';
export function useConfirm() { export function useConfirm() {
const dialog = useDialog(); const dialog = useDialog();
return ({ title, description }: { title: string; description?: string }) => { return ({
dialog.show({ title,
title, description,
description, confirmButtonColor,
hideX: true, confirmButtonText,
render: Confirm, }: {
title: string;
description?: string;
confirmButtonColor?: ButtonProps['color'];
confirmButtonText?: string;
}) => {
return new Promise((resolve: (r: boolean) => void) => {
dialog.show({
title,
description,
hideX: true,
render: ({ hide }) => (
<Confirm
hide={hide}
onResult={resolve}
confirmButtonColor={confirmButtonColor}
confirmButtonText={confirmButtonText}
/>
),
});
}); });
}; };
} }