Skip to content

Commit 1882e58

Browse files
committed
feat: deletion func done
1 parent 5bd7b96 commit 1882e58

File tree

9 files changed

+234
-4
lines changed

9 files changed

+234
-4
lines changed

public/locales/en/collection.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"editedAlert": "You have successfully updated your collection!",
5252
"editCollection": {
5353
"edit": "Edit",
54-
"generalInfo": "General Information"
54+
"generalInfo": "General Information",
55+
"deleteCollection": "Delete Collection"
5556
},
5657
"featuredItems": {
5758
"title": "Featured Items",
@@ -60,5 +61,12 @@
6061
"nextLabel": "Go to next slide",
6162
"dotLabel": "Go to slide"
6263
}
63-
}
64+
},
65+
"deleteCollectionModal": {
66+
"title": "Delete Collection",
67+
"message": "Are you sure you want to delete your collection? You cannot undelete this collection.",
68+
"delete": "Delete"
69+
},
70+
"collectionDeletedSuccess": "Your collection has been deleted.",
71+
"defaultCollectionDeleteError": "Something went wrong deleting the collection. Try again later."
6472
}

public/locales/en/shared.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"more": "More...",
99
"less": "Less...",
1010
"share": "Share",
11+
"delete": "Delete",
1112
"saveChanges": "Save Changes",
1213
"dragHandleLabel": "press space to select and keys to drag",
1314
"pageNumberNotFound": {

src/sections/collection/Collection.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function Collection({
5252
const canUserEditCollection = Boolean(collectionUserPermissions?.canEditCollection)
5353
const canUserAddDataset = Boolean(collectionUserPermissions?.canAddDataset)
5454
const canUserPublishCollection = Boolean(collectionUserPermissions?.canPublishCollection)
55+
const canUserDeleteCollection = Boolean(collectionUserPermissions?.canDeleteCollection)
5556

5657
const showAddDataActions = canUserAddCollection || canUserAddDataset
5758
const showPublishButton = !collection?.isReleased && canUserPublishCollection
@@ -107,7 +108,13 @@ export function Collection({
107108
collectionId={collection.id}
108109
/>
109110
)}
110-
{showEditButton && <EditCollectionDropdown collection={collection} />}
111+
{showEditButton && (
112+
<EditCollectionDropdown
113+
collection={collection}
114+
canUserDeleteCollection={canUserDeleteCollection}
115+
collectionRepository={collectionRepository}
116+
/>
117+
)}
111118
</ButtonGroup>
112119
)}
113120
</div>

src/sections/collection/edit-collection-dropdown/EditCollectionDropdown.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ import {
1111
import { PencilFill } from 'react-bootstrap-icons'
1212
import { Collection } from '@/collection/domain/models/Collection'
1313
import { RouteWithParams } from '@/sections/Route.enum'
14+
import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository'
15+
import { CollectionHelper } from '../CollectionHelper'
16+
import { DeleteCollectionButton } from './delete-collection-button/DeleteCollectionButton'
1417
import styles from './EditCollectionDropdown.module.scss'
1518

1619
interface EditCollectionDropdownProps {
1720
collection: Collection
21+
canUserDeleteCollection: boolean
22+
collectionRepository: CollectionRepository
1823
}
1924

20-
export const EditCollectionDropdown = ({ collection }: EditCollectionDropdownProps) => {
25+
export const EditCollectionDropdown = ({
26+
collection,
27+
collectionRepository,
28+
canUserDeleteCollection
29+
}: EditCollectionDropdownProps) => {
2130
const { t } = useTranslation('collection')
2231
const navigate = useNavigate()
2332

@@ -29,6 +38,10 @@ export const EditCollectionDropdown = ({ collection }: EditCollectionDropdownPro
2938
navigate(RouteWithParams.EDIT_COLLECTION_FEATURED_ITEMS(collection.id))
3039
}
3140

41+
// TODO:ME - We need another check, if collection has data
42+
const canCollectionBeDeleted =
43+
canUserDeleteCollection && !CollectionHelper.isRootCollection(collection.hierarchy)
44+
3245
return (
3346
<DropdownButton
3447
id="edit-collection-dropdown"
@@ -55,6 +68,17 @@ export const EditCollectionDropdown = ({ collection }: EditCollectionDropdownPro
5568
<DropdownButtonItem onClick={onClickEditFeaturedItems}>
5669
{t('featuredItems.title')}
5770
</DropdownButtonItem>
71+
72+
{canCollectionBeDeleted && (
73+
<>
74+
<DropdownSeparator />
75+
<DeleteCollectionButton
76+
collectionId={collection.id}
77+
parentCollection={CollectionHelper.getParentCollection(collection.hierarchy)}
78+
collectionRepository={collectionRepository}
79+
/>
80+
</>
81+
)}
5882
</DropdownButton>
5983
)
6084
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useState } from 'react'
2+
import { toast } from 'react-toastify'
3+
import { useTranslation } from 'react-i18next'
4+
import { useNavigate } from 'react-router-dom'
5+
import { DropdownButtonItem } from '@iqss/dataverse-design-system'
6+
import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository'
7+
import { ConfirmDeleteCollectionModal } from './confirm-delete-collection-modal/ConfirmDeleteCollectionModal'
8+
import { useDeleteCollection } from './useDeleteCollection'
9+
import { UpwardHierarchyNode } from '@/shared/hierarchy/domain/models/UpwardHierarchyNode'
10+
import { RouteWithParams } from '@/sections/Route.enum'
11+
12+
interface DeleteCollectionButtonProps {
13+
collectionId: string
14+
parentCollection: UpwardHierarchyNode | undefined
15+
collectionRepository: CollectionRepository
16+
}
17+
18+
export const DeleteCollectionButton = ({
19+
collectionId,
20+
parentCollection,
21+
collectionRepository
22+
}: DeleteCollectionButtonProps) => {
23+
const [showConfirmationModal, setShowConfirmationModal] = useState(false)
24+
const navigate = useNavigate()
25+
const { t } = useTranslation('collection')
26+
27+
const { handleDeleteCollection, isDeletingCollection, errorDeletingCollection } =
28+
useDeleteCollection({
29+
collectionRepository,
30+
onSuccessfulDelete: closeModalAndNavigateToParentCollection
31+
})
32+
33+
const handleOpenModal = () => setShowConfirmationModal(true)
34+
const handleCloseModal = () => setShowConfirmationModal(false)
35+
36+
function closeModalAndNavigateToParentCollection() {
37+
handleCloseModal()
38+
39+
toast.success(t('collectionDeletedSuccess'))
40+
navigate(RouteWithParams.COLLECTIONS(parentCollection?.id))
41+
}
42+
43+
return (
44+
<>
45+
<DropdownButtonItem onClick={handleOpenModal}>
46+
{t('editCollection.deleteCollection')}
47+
</DropdownButtonItem>
48+
<ConfirmDeleteCollectionModal
49+
show={showConfirmationModal}
50+
handleClose={handleCloseModal}
51+
handleDelete={() => handleDeleteCollection(collectionId)}
52+
isDeletingCollection={isDeletingCollection}
53+
errorDeletingCollection={errorDeletingCollection}
54+
/>
55+
</>
56+
)
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@import 'node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module';
2+
3+
.message {
4+
color: $dv-warning-color;
5+
6+
&.error {
7+
color: $dv-danger-color;
8+
}
9+
10+
svg {
11+
min-width: fit-content;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useTranslation } from 'react-i18next'
2+
import { Button, Modal, Spinner, Stack } from '@iqss/dataverse-design-system'
3+
import { ExclamationCircleFill, ExclamationTriangle } from 'react-bootstrap-icons'
4+
import styles from './ConfirmDeleteCollectionModal.module.scss'
5+
6+
interface ConfirmDeleteCollectionModalProps {
7+
show: boolean
8+
handleClose: () => void
9+
handleDelete: () => void
10+
isDeletingCollection: boolean
11+
errorDeletingCollection: string | null
12+
}
13+
14+
export const ConfirmDeleteCollectionModal = ({
15+
show,
16+
handleClose,
17+
handleDelete,
18+
isDeletingCollection,
19+
errorDeletingCollection
20+
}: ConfirmDeleteCollectionModalProps) => {
21+
const { t: tShared } = useTranslation('shared')
22+
const { t } = useTranslation('collection')
23+
24+
return (
25+
<Modal show={show} onHide={isDeletingCollection ? () => {} : handleClose} centered size="lg">
26+
<Modal.Header>
27+
<Modal.Title>{t('deleteCollectionModal.title')}</Modal.Title>
28+
</Modal.Header>
29+
<Modal.Body>
30+
<Stack gap={2}>
31+
<Stack direction="horizontal" gap={2} className={styles.message}>
32+
<ExclamationTriangle /> <span>{t('deleteCollectionModal.message')}</span>
33+
</Stack>
34+
{errorDeletingCollection && (
35+
<Stack direction="horizontal" gap={2} className={`${styles.message} ${styles.error}`}>
36+
<ExclamationCircleFill /> <span>{errorDeletingCollection}</span>
37+
</Stack>
38+
)}
39+
</Stack>
40+
</Modal.Body>
41+
<Modal.Footer>
42+
<Button
43+
variant="secondary"
44+
onClick={handleClose}
45+
type="button"
46+
disabled={isDeletingCollection}>
47+
{tShared('cancel')}
48+
</Button>
49+
<Button
50+
variant="danger"
51+
onClick={handleDelete}
52+
type="button"
53+
disabled={isDeletingCollection}>
54+
<Stack direction="horizontal" gap={1}>
55+
{tShared('delete')}
56+
{isDeletingCollection && <Spinner variant="light" animation="border" size="sm" />}
57+
</Stack>
58+
</Button>
59+
</Modal.Footer>
60+
</Modal>
61+
)
62+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useState } from 'react'
2+
import { useTranslation } from 'react-i18next'
3+
import { WriteError } from '@iqss/dataverse-client-javascript'
4+
import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteErrorHandler'
5+
import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository'
6+
import { deleteCollection } from '@/collection/domain/useCases/deleteCollection'
7+
import { Utils } from '@/shared/helpers/Utils'
8+
9+
interface UseDeleteCollection {
10+
collectionRepository: CollectionRepository
11+
onSuccessfulDelete?: () => void
12+
}
13+
14+
interface UseDeleteCollectionReturn {
15+
isDeletingCollection: boolean
16+
errorDeletingCollection: string | null
17+
handleDeleteCollection: (collectionIdOrAlias: number | string) => Promise<void>
18+
}
19+
20+
export const useDeleteCollection = ({
21+
collectionRepository,
22+
onSuccessfulDelete
23+
}: UseDeleteCollection): UseDeleteCollectionReturn => {
24+
const { t } = useTranslation('collection')
25+
const [isDeletingCollection, setIsDeletingCollection] = useState<boolean>(false)
26+
const [errorDeletingCollection, setErrorDeletingCollection] = useState<string | null>(null)
27+
28+
const handleDeleteCollection = async (collectionIdOrAlias: number | string) => {
29+
setIsDeletingCollection(true)
30+
31+
try {
32+
await deleteCollection(collectionRepository, collectionIdOrAlias)
33+
34+
// Wait 3 seconds before calling onSuccessfulDelete (which is redirecting to parent collection) otherwise Search API will still return the deleted collection item
35+
await Utils.sleep(3000)
36+
37+
onSuccessfulDelete?.()
38+
} catch (err: WriteError | unknown) {
39+
if (err instanceof WriteError) {
40+
const error = new JSDataverseWriteErrorHandler(err)
41+
const formattedError = error.getReasonWithoutStatusCode() ?? error.getErrorMessage()
42+
setErrorDeletingCollection(formattedError)
43+
} else {
44+
setErrorDeletingCollection(t('defaultCollectionDeleteError'))
45+
}
46+
} finally {
47+
setIsDeletingCollection(false)
48+
}
49+
}
50+
51+
return {
52+
isDeletingCollection,
53+
errorDeletingCollection,
54+
handleDeleteCollection
55+
}
56+
}

src/shared/helpers/Utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export class Utils {
1212
timeoutId = setTimeout(() => fn(...args), delay)
1313
}
1414
}
15+
16+
static sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))
1517
}

0 commit comments

Comments
 (0)