Skip to content

Commit 6736701

Browse files
committed
feat: style delete modal
1 parent d5d00cd commit 6736701

9 files changed

Lines changed: 105 additions & 25 deletions

File tree

src/generic/TypeXToConfirmModal.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React, { useEffect } from 'react';
2-
import { Button, Form, ModalDialog } from '@openedx/paragon';
2+
import { Button, Card, Form, Icon, ModalDialog } from '@openedx/paragon';
3+
import { WarningFilled } from '@openedx/paragon/icons';
4+
import { useIntl } from '@edx/frontend-platform/i18n';
5+
import messages from './messages';
36

47
interface TypeXToConfirmModalProps {
58
label: string;
6-
bodyText: string;
9+
bodyText: string | React.ReactNode;
710
confirmLabel: string;
811
cancelLabel: string;
912
X: string;
@@ -28,6 +31,7 @@ const TypeXToConfirmModal: React.FC<TypeXToConfirmModalProps> = ({
2831
setContext,
2932
}) => {
3033
const [confirmedByTyping, setConfirmedByTyping] = React.useState(false);
34+
const intl = useIntl();
3135

3236
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
3337
if (!confirmedByTyping) { return; }
@@ -77,21 +81,39 @@ const TypeXToConfirmModal: React.FC<TypeXToConfirmModalProps> = ({
7781
<ModalDialog.Title>{label}</ModalDialog.Title>
7882
</ModalDialog.Header>
7983
<ModalDialog.Body>
80-
<div>{bodyText}</div>
81-
<div>
84+
<Card className="bg-warning-100">
85+
<Card.Section>
86+
<div className="d-flex align-items-start mb-2">
87+
<Icon src={WarningFilled} className="text-warning-500 mr-2" />
88+
<div className="small">{bodyText}</div>
89+
</div>
90+
</Card.Section>
91+
</Card>
92+
<div className="mt-3">
8293
<div>
83-
Type <span className="text-primary-500 font-weight-bold">{X}</span> to confirm
94+
{(() => {
95+
const messageText = intl.formatMessage(messages.typeToConfirmInstruction, { X });
96+
const parts = messageText.split(X);
97+
return (
98+
<>
99+
{parts[0]}
100+
<strong>{X}</strong>
101+
{parts[1]}
102+
</>
103+
);
104+
})()}
84105
</div>
85106
<Form.Control
86107
onKeyDown={handleKeyDown}
87108
onChange={handleChange}
109+
className="mt-4"
88110
/>
89111
</div>
90112
<ModalDialog.Footer>
91113
<Button variant="tertiary" onClick={handleCancel}>
92114
{cancelLabel}
93115
</Button>
94-
<Button onClick={handleConfirm} disabled={!confirmedByTyping}>
116+
<Button onClick={handleConfirm} disabled={!confirmedByTyping} variant="danger">
95117
{confirmLabel}
96118
</Button>
97119
</ModalDialog.Footer>

src/generic/messages.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineMessages } from '@edx/frontend-platform/i18n';
2+
3+
const messages = defineMessages({
4+
typeToConfirmInstruction: {
5+
id: 'course-authoring.generic.type-to-confirm-instruction',
6+
defaultMessage: 'Type {X} to confirm',
7+
},
8+
});
9+
10+
export default messages;

src/taxonomy/data/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export const apiUrls = {
9999
tagsPlanImport: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/import/plan/`),
100100
createTag: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/`),
101101
updateTag: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/`),
102+
deleteTag: (taxonomyId: number) => makeUrl(`${taxonomyId}/tags/`),
102103
} satisfies Record<string, (...args: any[]) => string>;
103104

104105
/**

src/taxonomy/data/apiHooks.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,23 @@ export const useUpdateTag = (taxonomyId: number) => {
269269
},
270270
});
271271
};
272+
273+
export const useDeleteTag = (taxonomyId: number) => {
274+
const queryClient = useQueryClient();
275+
276+
return useMutation({
277+
mutationFn: async ({ value, withSubtags }: { value: string; withSubtags: boolean; }) => {
278+
const body = { tags: [value], with_subtags: withSubtags };
279+
await getAuthenticatedHttpClient().delete(apiUrls.deleteTag(taxonomyId), {
280+
data: body,
281+
});
282+
},
283+
onSuccess: () => {
284+
queryClient.invalidateQueries({
285+
queryKey: taxonomyQueryKeys.taxonomyTagList(taxonomyId),
286+
});
287+
// In the metadata, 'tagsCount' (and possibly other fields) will have changed:
288+
queryClient.invalidateQueries({ queryKey: taxonomyQueryKeys.taxonomyMetadata(taxonomyId) });
289+
},
290+
});
291+
};

src/taxonomy/tag-list/DeleteModal.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ const DeleteModal = () => {
1414
setConfirmDeleteDialogOpen,
1515
confirmDeleteDialogContext,
1616
setConfirmDeleteDialogContext,
17-
handleDeleteTag,
17+
handleDeleteRow,
1818
} = useContext(TreeTableContext);
1919
const intl = useIntl();
2020

2121
const handleConfirm = (row: Row<TreeRowData>) => {
22-
handleDeleteTag(row);
22+
handleDeleteRow(row);
2323
setConfirmDeleteDialogOpen(false);
2424
setConfirmDeleteDialogContext(null);
2525
};
@@ -33,15 +33,29 @@ const DeleteModal = () => {
3333
const count = useMemo(() => rowData ? getTagWithDescendantsCount(rowData) : 0, [confirmDeleteDialogContext]);
3434

3535
const hasSubtags = count > 1;
36-
const bodyText = hasSubtags ? intl.formatMessage(messages.deleteTagWithSubtagsConfirmation, { count }) : intl.formatMessage(messages.deleteTagConfirmation);
37-
const typeToDeleteText = hasSubtags ? intl.formatMessage(messages.typeToConfirmDeleteTagWithSubtags) : intl.formatMessage(messages.typeToConfirmDeleteOneTag);
36+
// const bodyText = hasSubtags ? intl.formatMessage(messages.deleteTagWithSubtagsConfirmation, { count }) : intl.formatMessage(messages.deleteTagConfirmation, { count });
37+
const typeToDeleteText = hasSubtags ? intl.formatMessage(messages.typeToConfirmDeleteTagWithSubtags, { count }) : intl.formatMessage(messages.typeToConfirmDeleteOneTag);
38+
const messageText = hasSubtags ? intl.formatMessage(messages.deleteTagWithSubtagsConfirmation, { count }) : intl.formatMessage(messages.deleteTagConfirmation, { count });
39+
const parts = messageText.split(String(count));
40+
const bodyText = (
41+
<>
42+
<div>
43+
{parts[0]}
44+
<strong>{count}</strong>
45+
{parts[1]}
46+
</div>
47+
<div>
48+
<strong>{intl.formatMessage(messages.deleteTagConfirmationEmphasizedPart)}</strong>
49+
</div>
50+
</>
51+
);
3852

3953
return (
4054
<TypeXToConfirmModal
4155
label={intl.formatMessage(messages.confirmDeleteTitle, { tagName: rowData?.value })}
4256
X={typeToDeleteText}
4357
bodyText={bodyText}
44-
confirmLabel={intl.formatMessage(messages.deleteLabel)}
58+
confirmLabel={intl.formatMessage(count > 1 ? messages.deleteLabelPlural : messages.deleteLabelSingular)}
4559
cancelLabel={intl.formatMessage(messages.cancelLabel)}
4660
isOpen={confirmDeleteDialogOpen}
4761
context={confirmDeleteDialogContext}

src/taxonomy/tag-list/TagListTable.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, {
44
useEffect,
55
} from 'react';
66
import type { PaginationState, Row } from '@tanstack/react-table';
7-
import { useTagListData, useCreateTag, useUpdateTag } from '@src/taxonomy/data/apiHooks';
7+
import { useTagListData, useCreateTag, useUpdateTag, useDeleteTag } from '@src/taxonomy/data/apiHooks';
88
import { TableView, TreeTableContext } from '@src/taxonomy/tree-table';
99
import type {
1010
RowId,
@@ -86,6 +86,7 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
8686
});
8787
const createTagMutation = useCreateTag(taxonomyId);
8888
const updateTagMutation = useUpdateTag(taxonomyId);
89+
const deleteTagMutation = useDeleteTag(taxonomyId);
8990
const pageCount = tagList?.numPages ?? -1;
9091
const canAddTag = tagList?.canAddTag !== false;
9192

@@ -108,6 +109,7 @@ const TagListTable = ({ taxonomyId, maxDepth }: TagListTableProps) => {
108109
setActiveActionMenuRowId,
109110
setConfirmDeleteDialogOpen,
110111
setConfirmDeleteDialogContext,
112+
deleteTagMutation,
111113
},
112114
);
113115

src/taxonomy/tag-list/hooks.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AxiosError } from 'axios';
33
import { useIntl } from '@edx/frontend-platform/i18n';
44

55
import globalMessages from '@src/messages';
6-
import { useCreateTag, useUpdateTag } from '@src/taxonomy/data/apiHooks';
6+
import { useCreateTag, useDeleteTag, useUpdateTag } from '@src/taxonomy/data/apiHooks';
77
import type { RowId, TreeRowData } from '@src/taxonomy/tree-table/types';
88
import { TagTree } from './tagTree';
99
import { TagListTableError } from './errors';
@@ -56,6 +56,7 @@ interface UseEditActionsParams {
5656
setActiveActionMenuRowId: React.Dispatch<React.SetStateAction<RowId | null>>;
5757
setConfirmDeleteDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
5858
setConfirmDeleteDialogContext: React.Dispatch<React.SetStateAction<Row<TreeRowData> | null>>;
59+
deleteTagMutation: ReturnType<typeof useDeleteTag>;
5960
}
6061

6162
const getInlineValidationMessage = (value: string, intl: ReturnType<typeof useIntl>): string => {
@@ -131,6 +132,7 @@ const useEditActions = ({
131132
exitDraftWithoutSave,
132133
setEditingRowId,
133134
updateTagMutation,
135+
deleteTagMutation,
134136
setConfirmDeleteDialogOpen,
135137
setConfirmDeleteDialogContext,
136138
}: UseEditActionsParams) => {
@@ -287,18 +289,18 @@ const useEditActions = ({
287289
setConfirmDeleteDialogContext(row);
288290
};
289291

290-
const someDeleteTagAPICall = async (value: string) => {
291-
// Placeholder for actual delete API call
292-
return new Promise((resolve) => setTimeout(resolve, 1000));
293-
};
294-
295292
const handleDeleteTag = async (row: Row<TreeRowData>) => {
296293
const rowData = getTagListRowData(row);
297294
const count = getTagWithDescendantsCount(rowData);
295+
// If the tag in the frontend state does not have subtags,
296+
// don't allow the backend to delete subtags.
297+
// That prevents problems in case of stale frontend state.
298+
const shouldDeleteSubtags = count > 1;
298299
try {
299-
// In view mode, the table reloads on change, reflecting the deletion without needing to manually update the table state
300+
// In view mode, the table reloads on change, reflecting the deletion
301+
// without needing to manually update the table state
300302
enterViewMode();
301-
await someDeleteTagAPICall(rowData.value); // Replace with actual delete API call
303+
await deleteTagMutation.mutateAsync({ value: rowData.value, withSubtags: shouldDeleteSubtags });
302304
setToast({
303305
show: true,
304306
message: intl.formatMessage(messages.tagsDeleteSuccessMessage, { count }),

src/taxonomy/tag-list/messages.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,31 @@ const messages = defineMessages({
103103
},
104104
deleteTagConfirmation: {
105105
id: 'course-authoring.tag-list.delete-tag-confirmation',
106-
defaultMessage: 'Warning! You are about to delete a tag. If the tag is applied to course content it will be removed.',
106+
defaultMessage: 'Warning! You are about to delete {count} tag(s).',
107107
},
108-
deleteLabel: {
108+
deleteLabelPlural: {
109109
id: 'course-authoring.tag-list.delete-label',
110-
defaultMessage: 'Delete',
110+
defaultMessage: 'Delete Tags',
111+
},
112+
deleteLabelSingular: {
113+
id: 'course-authoring.tag-list.delete-label-singular',
114+
defaultMessage: 'Delete Tag',
111115
},
112116
cancelLabel: {
113117
id: 'course-authoring.tag-list.cancel-label',
114118
defaultMessage: 'Cancel',
115119
},
116120
typeToConfirmDeleteTagWithSubtags: {
117121
id: 'course-authoring.tag-list.delete-tag-with-subtags-type-to-confirm',
118-
defaultMessage: 'DELETE WITH SUB-TAGS',
122+
defaultMessage: 'DELETE ALL {count} TAGS',
119123
},
120124
deleteTagWithSubtagsConfirmation: {
121125
id: 'course-authoring.tag-list.delete-tag-with-subtags-confirmation',
122-
defaultMessage: 'Warning! You are about to delete a tag containing sub-tags. If you proceed, {count} sub-tags that you did not directly select will also be deleted. Any tags applied to course content will be removed.',
126+
defaultMessage: 'Warning! You are about to delete a tag containing sub-tags. If you proceed, {count} tags will be deleted.',
127+
},
128+
deleteTagConfirmationEmphasizedPart: {
129+
id: 'course-authoring.tag-list.delete-tag-confirmation-bold-part',
130+
defaultMessage: 'Any tags applied to course content will be removed across all assigned organizations.',
123131
},
124132
});
125133

src/taxonomy/tree-table/TableView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { ArrowDropUpDown } from '@openedx/paragon/icons';
1919
import { useIntl } from '@edx/frontend-platform/i18n';
2020
import TableBody from './TableBody';
21+
// @ts-ignore
2122
import './TableView.scss';
2223
import messages from './messages';
2324
import SaveErrorAlert from './SaveErrorAlert';

0 commit comments

Comments
 (0)