Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve: UX and DX in delete and reset modals #382

Merged
merged 33 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e126691
feat: add DeleteOrResetModal component and integrate it into the dash…
pranavgoel29 Nov 22, 2024
25548a7
feat: refactor DeleteOrResetModal integration in PrivilegeTR and make…
pranavgoel29 Nov 22, 2024
16099f4
feat: enhance DeleteOrResetModal moving input state logic in the moda…
pranavgoel29 Nov 22, 2024
ff23910
fix: update placeholder text for role name input in delete confirmati…
pranavgoel29 Nov 22, 2024
12d9934
fix: react warning for props
pranavgoel29 Nov 22, 2024
eaf7af8
feat: add optional specialContent prop to DeleteOrResetModal and upda…
pranavgoel29 Nov 22, 2024
03067ef
feat: replace Modal with DeleteOrResetModal for privilege deletion co…
pranavgoel29 Nov 22, 2024
7f49871
Updated DeleteOrResetModal for user and user-role deletion confirmati…
pranavgoel29 Nov 22, 2024
9a44597
feat: add processContent prop to DeleteOrResetModal for additional pr…
pranavgoel29 Nov 22, 2024
f1397e4
Updated modal for reset password with common modal
pranavgoel29 Nov 22, 2024
a44cffb
feat: enhance DeleteOrResetModal to support 'simple' type and adjust …
pranavgoel29 Nov 22, 2024
426a356
Updated delete tile modal in dashboard with a simple delete or reset …
pranavgoel29 Nov 22, 2024
42f2e55
chore: minor change
pranavgoel29 Nov 22, 2024
83eda63
Updated delete stream modal to be consistent with other deletion modals
pranavgoel29 Nov 22, 2024
63cf098
removed loading from confirm button in DeleteOrResetModal and rearran…
pranavgoel29 Nov 22, 2024
a433c2f
Improved button disable logic in DeleteOrResetModal for better UX
pranavgoel29 Nov 22, 2024
f1d320c
Refactor RoleTR component: remove unused user input state, fixed rese…
pranavgoel29 Nov 22, 2024
f525f7c
Rearranged props according to the modal UI flow
pranavgoel29 Nov 22, 2024
57b75de
Removed unwanted userunput state and renamte useState value var
pranavgoel29 Nov 22, 2024
c72c125
fix: :pencil2: fixed selectedRole state value var naming
pranavgoel29 Nov 22, 2024
1174d10
updated variable names for clarity and consistency in DeleteOrResetMo…
pranavgoel29 Nov 23, 2024
edaf446
Revamped prop descriptions and code clarity in DeleteOrResetModal
pranavgoel29 Nov 23, 2024
546e391
refactor: simplify variable declarations and improve code readability…
pranavgoel29 Nov 23, 2024
015ba69
Removed unnecessary tempelate literal from placeholder prop
pranavgoel29 Nov 23, 2024
0ba6917
fix: remove unnecessary template literal from placeholder in DeleteSt…
pranavgoel29 Nov 23, 2024
cad22e8
Replace template literal with string for content in PrivilegeTR compo…
pranavgoel29 Nov 23, 2024
a5b77bb
updated placeholder text in home.spec.ts test file as per the updates
pranavgoel29 Nov 23, 2024
5709f02
Optimized confirmation and close modal functions using useCallback
pranavgoel29 Nov 23, 2024
7011ef3
Add comment to clarify rendering of action processing content in Dele…
pranavgoel29 Nov 23, 2024
92b388c
Refactor DeleteStreamModal to use DeleteOrResetModal component and st…
pranavgoel29 Nov 28, 2024
ccb9d13
Refactor header properties in PrivilegeTR component to use string lit…
pranavgoel29 Nov 28, 2024
7566323
moved DeleteOrResetModal styles to use CSS modules
pranavgoel29 Nov 28, 2024
33e540e
Renamed some props for clarity and be more descriptive
pranavgoel29 Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/Button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const IconButton: FC<IconButtonProps> = (props) => {
return (
<Tooltip label={tooltipLabel}>
<ActionIcon
data-testId={props.data_id}
data-testid={props.data_id}
size={props.size ? props.size : 'xl'}
className={`${classes.iconBtn} ${props.active && classes.iconBtnActive}`}
onClick={props.onClick && props.onClick}>
Expand All @@ -29,7 +29,7 @@ const IconButton: FC<IconButtonProps> = (props) => {
} else {
return (
<ActionIcon
data-testId={props.data_id}
data-testid={props.data_id}
size={props.size ? props.size : 'xl'}
className={`${classes.iconBtn} ${props.active && classes.iconBtnActive}`}
onClick={props.onClick && props.onClick}>
Expand Down
130 changes: 130 additions & 0 deletions src/components/Misc/DeleteOrResetModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Box, Button, Modal, Stack, Text, TextInput } from '@mantine/core';
import classes from './styles/DeleteOrResetModal.module.css';
import { ChangeEvent, useCallback, useState } from 'react';

type BaseProps = {
isOpen: boolean;
praveen5959 marked this conversation as resolved.
Show resolved Hide resolved
onClose: () => void;
header: string;
specialContent?: React.ReactNode;
content: string;
actionProcessingContent?: React.ReactNode;
isActionInProgress?: boolean;
onConfirm: () => void;
};

// Note: The `confirmationText` and `placeholder` props are required for 'delete' and 'reset' types, but not for 'simple' type.
type DeleteOrResetModalProps =
| (BaseProps & {
type: 'simple';
praveen5959 marked this conversation as resolved.
Show resolved Hide resolved
confirmationText?: never; // Will throw an error if `confirmationText` is passed
placeholder?: never;
})
| (BaseProps & {
type: 'delete' | 'reset';
confirmationText: string;
placeholder: string;
});

/**
* Confirmation modal for deleting or resetting an item.
* @param type - Specifies the type of modal ('simple', 'delete', or 'reset').
* @param isOpen - Controls whether the modal is visible.
* @param onClose - Callback to close the modal and reset the state.
* @param header - Header text displayed in the modal title.
* @param specialContent - Optional content for additional context or customization.
* @param content - Main descriptive content of the modal.
* @param placeholder - Input placeholder for confirmation text (applicable to 'delete' and 'reset').
* @param confirmationText - Text required to confirm the action (applicable to 'delete' and 'reset').
* @param actionProcessingContent - Optional content below text input for showing progress status or related information.
* @param isActionInProgress - Disables the confirm button when action is in progress.
* @param onConfirm - Callback function to be executed when the confirm button is clicked.
*/
const DeleteOrResetModal = ({
type,
isOpen,
onClose,
header,
specialContent,
content,
placeholder,
confirmationText,
actionProcessingContent,
isActionInProgress,
onConfirm,
}: DeleteOrResetModalProps) => {
const [confirmText, setConfirmText] = useState<string>('');

// Handler for the confirmation input field
const onChangeHandler = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setConfirmText(e.target.value);
}, []);

// Function to validate and trigger confirmation logic
const tryConfirm = useCallback(() => {
if (type === 'simple' || confirmationText === confirmText) {
setConfirmText('');
onConfirm();
}
}, [type, confirmationText, confirmText, onConfirm]);

// Function to close the modal and reset the confirmation text state.
const closeModal = useCallback(() => {
setConfirmText('');
onClose();
}, [onClose]);

return (
<Modal
withinPortal
opened={isOpen}
onClose={closeModal}
size="auto"
centered
styles={{
body: { padding: '0 1rem 1rem 1rem', width: 400 },
praveen5959 marked this conversation as resolved.
Show resolved Hide resolved
header: { padding: '1rem', paddingBottom: '0.4rem' },
}}
title={<Text style={{ fontSize: '0.9rem', fontWeight: 600 }}>{header}</Text>}>
<Stack>
<Stack gap={8}>
{specialContent}
<Text className={classes.warningText}>{content}</Text>

{/* Render confirmation field for 'delete' or 'reset' types */}
{type !== 'simple' && (
<>
<Text className={classes.confirmationText}>
Please type <span className={classes.confirmationTextHighlight}>{`"${confirmationText}"`}</span> to
confirm {type === 'delete' ? 'deletion' : 'reset'}.
</Text>
<TextInput value={confirmText} onChange={onChangeHandler} placeholder={placeholder} required />
</>
)}

{/* Renders the action processing content if provided */}
{actionProcessingContent}
</Stack>

{/* Action buttons */}
<Stack style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end' }}>
<Box>
<Button variant="outline" onClick={closeModal}>
Cancel
</Button>
</Box>
<Box>
{/* Disable the button if the confirmation text is not correct or the action is processing. */}
<Button
disabled={(type !== 'simple' && confirmationText !== confirmText) || isActionInProgress}
onClick={tryConfirm}>
{type === 'reset' ? 'Reset' : 'Delete'}
</Button>
</Box>
</Stack>
</Stack>
</Modal>
);
};

export default DeleteOrResetModal;
15 changes: 15 additions & 0 deletions src/components/Misc/styles/DeleteOrResetModal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.warningText {
margin-top: 0.4rem;
font-size: 0.7rem;
font-weight: 500;
color: var(--mantine-color-gray-8);
}

.confirmationText {
font-size: 0.7rem;
color: var(--mantine-color-gray-7);
}

.confirmationTextHighlight {
font-weight: 500;
}
123 changes: 35 additions & 88 deletions src/pages/AccessManagement/PrivilegeTR.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { FC, useEffect, useState } from 'react';
import { useGetLogStreamList } from '@/hooks/useGetLogStreamList';
import { useRole } from '@/hooks/useRole';
import styles from './styles/AccessManagement.module.css';
import DeleteOrResetModal from '@/components/Misc/DeleteOrResetModal';

interface PrivilegeTRProps {
roleName: string;
Expand All @@ -32,8 +33,6 @@ interface PrivilegeTRProps {
const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
const { roleName, defaultRole, deleteRoleMutation, getRoleIsLoading, getRoleIsError } = props;

const [UserInput, setUserInput] = useState<string>('');

// Delete Privilege Modal Constants : Starts
const [deletePrivilegeIndex, setDeletePrivilegeIndex] = useState<number>(0);
const [isDeletedPrivilegeOpen, { open: openDeletePrivilege, close: closeDeletePrivilege }] = useDisclosure();
Expand All @@ -46,7 +45,7 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
// Update Role Modal Constants : Starts
const [isUpdatedRoleOpen, { open: openUpdateRole, close: closeUpdateRole }] = useDisclosure();
const [selectedPrivilege, setSelectedPrivilege] = useState<string>('');
const [SelectedStream, setSelectedStream] = useState<string>('');
const [selectedStream, setSelectedStream] = useState<string>('');
const [streamSearchValue, setStreamSearchValue] = useState<string>('');
const [tagInput, setTagInput] = useState<string>('');
const { getLogStreamListData } = useGetLogStreamList();
Expand Down Expand Up @@ -104,7 +103,6 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {

const handleClosePrivilegeDelete = () => {
closeDeletePrivilege();
setUserInput('');
};

const handlePrivilegeDelete = () => {
Expand Down Expand Up @@ -140,7 +138,6 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {

const handleCloseDelete = () => {
closeDeleteRole();
setUserInput('');
};

const handleCloseUpdateRole = () => {
Expand All @@ -158,20 +155,20 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
});
}
if (selectedPrivilege === 'reader' || selectedPrivilege === 'writer' || selectedPrivilege === 'ingestor') {
if (getLogStreamListData?.data?.find((stream) => stream.name === SelectedStream)) {
if (getLogStreamListData?.data?.find((stream) => stream.name === selectedStream)) {
if (tagInput !== '' && tagInput !== undefined && selectedPrivilege === 'reader') {
getRoleData?.data?.push({
privilege: selectedPrivilege,
resource: {
stream: SelectedStream,
stream: selectedStream,
tag: tagInput,
},
});
} else {
getRoleData?.data?.push({
privilege: selectedPrivilege,
resource: {
stream: SelectedStream,
stream: selectedStream,
},
});
}
Expand All @@ -193,7 +190,7 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
getRoleData?.data?.find(
(role: any) =>
role.privilege === selectedPrivilege &&
role.resource?.stream === SelectedStream &&
role.resource?.stream === selectedStream &&
(tagInput
? role.resource?.tag === tagInput
: role.resource?.tag === null || role.resource?.tag === undefined),
Expand All @@ -202,9 +199,9 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
return true;
}
if (
getLogStreamListData?.data?.find((stream) => stream.name === SelectedStream) &&
SelectedStream !== '' &&
SelectedStream !== undefined
getLogStreamListData?.data?.find((stream) => stream.name === selectedStream) &&
selectedStream !== '' &&
selectedStream !== undefined
) {
return false;
}
Expand All @@ -213,15 +210,15 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
if (selectedPrivilege === 'writer' || selectedPrivilege === 'ingestor') {
if (
getRoleData?.data?.find(
(role: any) => role.privilege === selectedPrivilege && role.resource?.stream === SelectedStream,
(role: any) => role.privilege === selectedPrivilege && role.resource?.stream === selectedStream,
)
) {
return true;
}
if (
getLogStreamListData?.data?.find((stream) => stream.name === SelectedStream) &&
SelectedStream !== '' &&
SelectedStream !== undefined
getLogStreamListData?.data?.find((stream) => stream.name === selectedStream) &&
selectedStream !== '' &&
selectedStream !== undefined
) {
return false;
}
Expand Down Expand Up @@ -284,79 +281,29 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
</Box>
</td>
</tr>
<Modal
withinPortal
size="md"
opened={isDeletedRoleOpen}
onClose={handleCloseDelete}
title={'Delete Role'}
className={classes.modalStyle}
centered>
<TextInput
label="Are you sure you want to delete this Role?"
type="text"
onChange={(e) => {
setUserInput(e.target.value);
}}
placeholder={`Please enter the Role to confirm, i.e. ${roleName}`}
required
mb={20}
/>

<Group justify="right" mt={10}>
<Button
variant="filled"
color="gray"
className={classes.modalActionBtn}
disabled={UserInput === roleName ? false : true}
onClick={handleDelete}>
Delete
</Button>
<Button onClick={handleCloseDelete} variant="outline" color="gray" className={classes.modalCancelBtn}>
Cancel
</Button>
</Group>
</Modal>
<DeleteOrResetModal
type="delete"
isOpen={isDeletedRoleOpen}
onClose={handleCloseDelete}
header={'Delete Role'}
praveen5959 marked this conversation as resolved.
Show resolved Hide resolved
content="Are you sure you want to delete this Role?"
placeholder="Type the role name to confirm"
confirmationText={roleName}
onConfirm={handleDelete}
/>
{getRoleData?.data?.[deletePrivilegeIndex] ? (
<Modal
withinPortal
size="md"
opened={isDeletedPrivilegeOpen}
<DeleteOrResetModal
type="delete"
isOpen={isDeletedPrivilegeOpen}
onClose={handleClosePrivilegeDelete}
title={'Delete Privilege'}
centered
className={classes.modalStyle}>
<Stack>
<Text>{getBadge(getRoleData?.data[deletePrivilegeIndex], deletePrivilegeIndex, false)}</Text>
<TextInput
label="Are you sure you want to delete this role privilege?"
type="text"
onChange={(e) => {
setUserInput(e.target.value);
}}
placeholder={`Please enter the role to confirm, i.e. ${roleName}`}
required
/>
</Stack>

<Group justify="right" mt={10}>
<Button
variant="filled"
color="gray"
className={classes.modalActionBtn}
disabled={UserInput === roleName ? false : true}
onClick={handlePrivilegeDelete}>
Delete
</Button>
<Button
onClick={handleClosePrivilegeDelete}
variant="outline"
color="gray"
className={classes.modalCancelBtn}>
Cancel
</Button>
</Group>
</Modal>
header={'Delete Privilege'}
specialContent={<Text>{getBadge(getRoleData?.data[deletePrivilegeIndex], deletePrivilegeIndex, false)}</Text>}
content="Are you sure you want to delete this role privilege?"
placeholder="Type name of the role to confirm."
confirmationText={roleName}
onConfirm={handlePrivilegeDelete}
/>
) : (
''
)}
Expand Down Expand Up @@ -386,10 +333,10 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
setSelectedStream(value ?? '');
}}
nothingFoundMessage="No options"
value={SelectedStream}
value={selectedStream}
searchValue={streamSearchValue}
onSearchChange={(value) => setStreamSearchValue(value)}
onDropdownClose={() => setStreamSearchValue(SelectedStream)}
onDropdownClose={() => setStreamSearchValue(selectedStream)}
onDropdownOpen={() => setStreamSearchValue('')}
data={getLogStreamListData?.data?.map((stream) => ({ value: stream.name, label: stream.name })) ?? []}
searchable
Expand Down
Loading
Loading