Skip to content

Commit

Permalink
feat(envited.ascs.digital): Add extra confirmation on delete (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroenbranje authored Mar 7, 2024
2 parents 74bddf1 + 34a9137 commit 08d74dc
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 28 deletions.
28 changes: 28 additions & 0 deletions apps/design-system/src/components/Atoms/Dialog/Dialog.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Meta, Story } from '@storybook/react'
import React from 'react'

import { Dialog } from './Dialog'

export default {
component: Dialog,
title: 'Components/Dialog',
} as Meta

const Template: Story = ({ isOpen, setShow, heading, description }) => (
<Dialog
isOpen={isOpen}
setShow={setShow}
heading={heading}
description={description}
action={<button>CLICK</button>}
/>
)

export const DialogStory = Template.bind({})

DialogStory.args = {
isOpen: true,
setShow: () => {},
heading: 'Heading',
description: 'Description',
}
88 changes: 88 additions & 0 deletions apps/design-system/src/components/Atoms/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Dialog as ReactDialog, Transition } from '@headlessui/react'
import { ExclamationTriangleIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { FC, Fragment, JSXElementConstructor, ReactElement } from 'react'

interface DialogProps {
isOpen: boolean
setShow: (x: boolean) => void
heading: string | ReactElement<any, string | JSXElementConstructor<any>>
description: string | ReactElement<any, string | JSXElementConstructor<any>>
action: ReactElement
}

export const Dialog: FC<DialogProps> = ({ isOpen, setShow, heading, description, action }) => {
return (
<Transition.Root show={isOpen} as={Fragment}>
<ReactDialog as="div" className="relative z-50" onClose={setShow}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-800 bg-opacity-75 dark:bg-opacity-75 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-50 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<ReactDialog.Panel className="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-900 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md bg-white dark:bg-gray-900 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-900 focus:ring-offset-0"
onClick={() => {
setShow(false)
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-600 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600 dark:text-red-100" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<ReactDialog.Title
as="h3"
className="text-base font-semibold leading-6 text-gray-900 dark:text-white"
>
{heading}
</ReactDialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">{description}</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
{action}
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => {
setShow(false)
}}
>
Cancel
</button>
</div>
</ReactDialog.Panel>
</Transition.Child>
</div>
</div>
</ReactDialog>
</Transition.Root>
)
}
25 changes: 25 additions & 0 deletions apps/design-system/src/components/Atoms/Dialog/Dialog.ui.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { render, screen } from '@testing-library/react'

import { Dialog } from './Dialog'

describe('atoms/Dialog', () => {
describe('render', () => {
it('should render Dialog with content', () => {
// when ... rendering component
const setShowStub = jest.fn()
render(
<Dialog
heading="HEADING"
description="DESCRIPTION"
isOpen={true}
setShow={setShowStub}
action={<button>BUTTON</button>}
/>,
)
const DialogElement = screen.getByText('HEADING')

// then ... should render as expected
expect(DialogElement).toBeInTheDocument()
})
})
})
1 change: 1 addition & 0 deletions apps/design-system/src/components/Atoms/Dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Dialog } from './Dialog'
1 change: 1 addition & 0 deletions apps/design-system/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { Button } from './components/Atoms/Button'
export { Card } from './components/Atoms/Card'
export { ContactPerson } from './components/Atoms/ContactPerson'
export { Date } from './components/Atoms/Date'
export { Dialog } from './components/Atoms/Dialog'
export { Checkbox, Checkboxes, DragAndDropField, FileField, TextField, TextareaField } from './components/Atoms/Form'
export { Grid, GridRow } from './components/Atoms/Grid'
export { Heading } from './components/Atoms/Heading'
Expand Down
2 changes: 1 addition & 1 deletion apps/envited.ascs.digital/modules/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const Header: FC<HeaderProps> = () => {
return (
<header className="py-10">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<nav className="relative z-50 flex justify-between">
<nav className="relative z-40 flex justify-between">
<div className="flex items-center md:gap-x-12">
<Link href="/" aria-label="Home">
<Image src="/envited-logo.png" alt="ENVITED" priority height={40} width={170} />
Expand Down
27 changes: 2 additions & 25 deletions apps/envited.ascs.digital/modules/Users/Users.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
'use client'

import { Card, Heading, Table, TableBody, TableCell, TableHeader, TableRow } from '@envited-marketplace/design-system'
import { TrashIcon } from '@heroicons/react/24/outline'
import { map } from 'ramda'
import React, { FC } from 'react'

import { useTranslation } from '../../common/i18n'
import { useNotification } from '../../common/notifications'
import { User } from '../../common/types/types'
import { deleteUser } from './Users.actions'
import { UserDialogConfirm } from './UsersDialogConfirm'

interface UsersProps {
users: User[]
}
export const Users: FC<UsersProps> = ({ users }) => {
const { t } = useTranslation('Users')
const { error, success } = useNotification()

const deleteUserWithId = (id: string) => async () => {
try {
await deleteUser(id)
success('User is deactivated')
} catch (e) {
error('Something went wrong')
}
}

return (
<Card>
Expand Down Expand Up @@ -64,18 +52,7 @@ export const Users: FC<UsersProps> = ({ users }) => {
{t(isActive ? '[Label] active' : '[Label] inactive')}
</div>
</TableCell>
<TableCell extraClasses="pl-3">
{isActive ? (
<form action={deleteUserWithId(id)}>
<button className="text-white bg-red-500 hover:bg-red-600 p-1.5 rounded-lg">
<span className="sr-only">{t('[Button] deactivate')}</span>
<TrashIcon className="w-3" />
</button>
</form>
) : (
<></>
)}
</TableCell>
<TableCell extraClasses="pl-3">{isActive ? <UserDialogConfirm id={id} /> : <></>}</TableCell>
</TableRow>
))(users)}
</TableBody>
Expand Down
51 changes: 51 additions & 0 deletions apps/envited.ascs.digital/modules/Users/UsersDialogConfirm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use client'

import { Dialog } from '@envited-marketplace/design-system'
import { TrashIcon } from '@heroicons/react/24/outline'
import { FC, useState } from 'react'

import { useTranslation } from '../../common/i18n'
import { useNotification } from '../../common/notifications'
import { deleteUser } from './Users.actions'

interface DialogConfirmProps {
id: string
}

export const UserDialogConfirm: FC<DialogConfirmProps> = ({ id }) => {
const { t } = useTranslation('Users')
const { error, success } = useNotification()
const [showDialog, setShowDialog] = useState(true)

const deleteUserWithId = (id: string) => async () => {
try {
await deleteUser(id)
success(t('[Notification] success'))
setShowDialog(false)
} catch (e) {
error(t('[Notification] error'))
}
}

return (
<>
<button className="text-white bg-red-500 hover:bg-red-600 p-1.5 rounded-lg" onClick={() => setShowDialog(true)}>
<span className="sr-only">{t('[Button] deactivate')}</span>
<TrashIcon className="w-3" />
</button>
<Dialog
heading={t('[Heading] deactivate account')}
description={t('[Description] deactivate account')}
isOpen={showDialog}
setShow={setShowDialog}
action={
<form action={deleteUserWithId(id)}>
<button className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto">
{t('[Button] deactivate')}
</button>
</form>
}
/>
</>
)
}
6 changes: 5 additions & 1 deletion apps/envited.ascs.digital/modules/Users/locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"[Heading] email": "Email",
"[Heading] did": "DID",
"[Heading] status": "Status",
"[Heading] deactivate account": "Deactivate account",
"[Description] deactivate account": "Are you sure you want to deactivate this account? All of your data will be permanently removed from our servers forever. This action cannot be undone.",
"[Button] deactivate": "Deactivate",
"[Label] active": "Active",
"[Label] inactive": "Inactive"
"[Label] inactive": "Inactive",
"[Notification] success": "User is deactivated",
"[Notification] error": "Something went wrong"
}
6 changes: 5 additions & 1 deletion apps/envited.ascs.digital/modules/Users/locales/en_GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"[Heading] email": "Email",
"[Heading] did": "DID",
"[Heading] status": "Status",
"[Heading] deactivate account": "Deactivate account",
"[Description] deactivate account": "Are you sure you want to deactivate this account? All of your data will be permanently removed from our servers forever. This action cannot be undone.",
"[Button] deactivate": "Deactivate",
"[Label] active": "Active",
"[Label] inactive": "Inactive"
"[Label] inactive": "Inactive",
"[Notification] success": "User is deactivated",
"[Notification] error": "Something went wrong"
}
45 changes: 45 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"devDependencies": {
"@babel/core": "^7.14.5",
"@babel/preset-react": "^7.14.5",
"@headlessui/react": "^1.7.18",
"@heroicons/react": "^2.1.1",
"@nx/cypress": "17.0.1",
"@nx/esbuild": "18.0.4",
Expand Down

0 comments on commit 08d74dc

Please sign in to comment.