diff --git a/.circleci/config.yml b/.circleci/config.yml index db8617ea2..fbb4b55d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -233,6 +233,7 @@ workflows: - feat/v6 - pm-2074_1 - feat/ai-workflows + - delete_user - deployQa: context: org-global diff --git a/src/apps/admin/src/lib/components/DialogDeleteUser/DialogDeleteUser.module.scss b/src/apps/admin/src/lib/components/DialogDeleteUser/DialogDeleteUser.module.scss new file mode 100644 index 000000000..c9bec7f6e --- /dev/null +++ b/src/apps/admin/src/lib/components/DialogDeleteUser/DialogDeleteUser.module.scss @@ -0,0 +1,18 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: flex; + flex-direction: column; + gap: $sp-4; +} + +.description { + white-space: pre-line; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: $sp-3; + margin-top: $sp-4; +} diff --git a/src/apps/admin/src/lib/components/DialogDeleteUser/DialogDeleteUser.tsx b/src/apps/admin/src/lib/components/DialogDeleteUser/DialogDeleteUser.tsx new file mode 100644 index 000000000..93bac5fb0 --- /dev/null +++ b/src/apps/admin/src/lib/components/DialogDeleteUser/DialogDeleteUser.tsx @@ -0,0 +1,107 @@ +import { ChangeEvent, FC, useCallback, useEffect, useState } from 'react' +import classNames from 'classnames' + +import { BaseModal, Button, InputText } from '~/libs/ui' + +import { UserInfo } from '../../models' + +import styles from './DialogDeleteUser.module.scss' + +interface Props { + className?: string + open: boolean + setOpen: (isOpen: boolean) => void + userInfo: UserInfo + isLoading?: boolean + onDelete: (ticketUrl: string) => void +} + +export const DialogDeleteUser: FC = (props: Props) => { + const [ticketUrl, setTicketUrl] = useState('') + const [error, setError] = useState('') + + useEffect(() => { + if (props.open) { + setTicketUrl('') + setError('') + } + }, [props.open]) + + const handleClose = useCallback(() => { + if (!props.isLoading) { + props.setOpen(false) + } + }, [props.isLoading, props.setOpen]) + + const handleConfirm = useCallback(() => { + if (!ticketUrl.trim()) { + setError('Delete ticket URL is required') + return + } + + setError('') + props.onDelete(ticketUrl.trim()) + }, [props, ticketUrl]) + + const handleTicketUrlChange = useCallback( + (event: ChangeEvent) => { + if (error) { + setError('') + } + + setTicketUrl(event.target.value) + }, + [error], + ) + + const description + = `Are you sure you want to DELETE user ${props.userInfo.handle} with email address ${props.userInfo.email}. ` + + 'If you are sure, please enter the associated delete request ticket URL below' + + return ( + +
+

{description}

+ + +
+ + +
+
+
+ ) +} + +export default DialogDeleteUser diff --git a/src/apps/admin/src/lib/components/DialogDeleteUser/index.ts b/src/apps/admin/src/lib/components/DialogDeleteUser/index.ts new file mode 100644 index 000000000..d1271c6ba --- /dev/null +++ b/src/apps/admin/src/lib/components/DialogDeleteUser/index.ts @@ -0,0 +1 @@ +export { DialogDeleteUser } from './DialogDeleteUser' diff --git a/src/apps/admin/src/lib/components/UsersTable/UsersTable.module.scss b/src/apps/admin/src/lib/components/UsersTable/UsersTable.module.scss index d81f26842..0fd56a9bb 100644 --- a/src/apps/admin/src/lib/components/UsersTable/UsersTable.module.scss +++ b/src/apps/admin/src/lib/components/UsersTable/UsersTable.module.scss @@ -57,7 +57,7 @@ } .blockColumnAction { - width: 240px; + width: 320px; @include ltelg { width: 60px; diff --git a/src/apps/admin/src/lib/components/UsersTable/UsersTable.tsx b/src/apps/admin/src/lib/components/UsersTable/UsersTable.tsx index 8b481359f..6059722de 100644 --- a/src/apps/admin/src/lib/components/UsersTable/UsersTable.tsx +++ b/src/apps/admin/src/lib/components/UsersTable/UsersTable.tsx @@ -26,6 +26,7 @@ import { DialogEditUserSSOLogin } from '../DialogEditUserSSOLogin' import { DialogEditUserTerms } from '../DialogEditUserTerms' import { DialogEditUserStatus } from '../DialogEditUserStatus' import { DialogUserStatusHistory } from '../DialogUserStatusHistory' +import { DialogDeleteUser } from '../DialogDeleteUser' import { DropdownMenuButton } from '../common/DropdownMenuButton' import { useTableFilterLocal, useTableFilterLocalProps } from '../../hooks' import { TABLE_DATE_FORMAT } from '../../../config/index.config' @@ -43,12 +44,18 @@ interface Props { totalPages: number onPageChange: (page: number) => void updatingStatus: { [key: string]: boolean } + deletingUsers: { [key: string]: boolean } doUpdateStatus: ( userInfo: UserInfo, newStatus: string, comment: string, onSuccess?: () => void, ) => void + doDeleteUser: ( + userInfo: UserInfo, + ticketUrl: string, + onSuccess?: () => void, + ) => void } export const UsersTable: FC = props => { @@ -100,6 +107,9 @@ export const UsersTable: FC = props => { const [showDialogStatusHistory, setShowDialogStatusHistory] = useState< UserInfo | undefined >() + const [showDialogDeleteUser, setShowDialogDeleteUser] = useState< + UserInfo | undefined + >() const { width: screenWidth }: WindowSize = useWindowSize() const updatingStatusBool = useMemo( @@ -294,6 +304,8 @@ export const UsersTable: FC = props => { columnId: 'Action', label: 'Action', renderer: (data: UserInfo) => { + const isDeleting = props.deletingUsers?.[data.id] === true + function onSelectOption(item: string): void { if (item === 'Primary Email') { setShowDialogEditUserEmail(data) @@ -321,6 +333,8 @@ export const UsersTable: FC = props => { data, message: confirmation, }) + } else if (item === 'Delete') { + setShowDialogDeleteUser(data) } } @@ -335,8 +349,8 @@ export const UsersTable: FC = props => { 'Terms', 'SSO Logins', ...(data.active - ? ['Deactivate'] - : ['Activate']), + ? ['Deactivate', 'Delete'] + : ['Activate', 'Delete']), ]} onSelectOption={onSelectOption} > @@ -374,6 +388,7 @@ export const UsersTable: FC = props => { onClick={function onClick() { onSelectOption('Deactivate') }} + disabled={isDeleting} /> ) : (