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 { useConfirm } from '../hooks/useConfirm';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
import { useRequest } from '../hooks/useRequest';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
@@ -9,9 +11,11 @@ interface Props {
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 duplicateRequest = useDuplicateRequest({ id: requestId, navigateAfter: true });
const confirm = useConfirm();
return (
<Dropdown
@@ -23,7 +27,17 @@ export function RequestSettingsDropdown({ requestId, children }: Props) {
},
{
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" />,
},
]}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import classnames from 'classnames';
import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useConfirm } from '../hooks/useConfirm';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
import { useRoutes } from '../hooks/useRoutes';
@@ -21,6 +22,7 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
const deleteWorkspace = useDeleteWorkspace(activeWorkspaceId);
const routes = useRoutes();
const confirm = useConfirm();
const items: DropdownItem[] = useMemo(() => {
const workspaceItems = workspaces.map((w) => ({
@@ -46,7 +48,17 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
{
label: 'Delete Workspace',
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]);

View File

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

View File

@@ -1,21 +1,40 @@
import type { ButtonProps } from '../components/core/Button';
import { Button } from '../components/core/Button';
import { HStack } from '../components/core/Stacks';
interface Props {
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) => {
el?.focus();
};
const handleHide = () => {
onResult(false);
hide();
};
const handleSuccess = () => {
onResult(true);
hide();
};
return (
<HStack space={2} justifyContent="end">
<Button className="focus" color="gray" onClick={hide}>
<Button className="focus" color="gray" onClick={handleHide}>
Cancel
</Button>
<Button className="focus" ref={focusRef} color="primary">
Confirm
<Button className="focus" ref={focusRef} color={confirmButtonColor} onClick={handleSuccess}>
{confirmButtonText}
</Button>
</HStack>
);

View File

@@ -1,14 +1,34 @@
import type { ButtonProps } from '../components/core/Button';
import { useDialog } from '../components/DialogContext';
import { Confirm } from './Confirm';
export function useConfirm() {
const dialog = useDialog();
return ({ title, description }: { title: string; description?: string }) => {
dialog.show({
title,
description,
hideX: true,
render: Confirm,
return ({
title,
description,
confirmButtonColor,
confirmButtonText,
}: {
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}
/>
),
});
});
};
}