diff --git a/lib/src/compositions/issue/issue-header.tsx b/lib/src/compositions/issue/issue-header.tsx index 4f2f8f3a..9935466d 100644 --- a/lib/src/compositions/issue/issue-header.tsx +++ b/lib/src/compositions/issue/issue-header.tsx @@ -20,6 +20,9 @@ import { WrapComingSoonPopover } from '@elements/components/coming-soon-popover' import { useIdent, useWrapRequireAuth } from '@elements/store/hooks'; import { Status } from '@elements/logic/meta/initiative'; import { IssueTab } from '@elements/logic/issue'; +import { ContextMenu } from '@elements/components/context-menu'; +import { type ItemType } from '@elements/components/dropdown'; +import { TrashOutline } from '@elements/icons'; const Title = suspensify(() => { const issueId = useValue('current.issue/id'); @@ -163,6 +166,46 @@ const ActionBar = () => { ); }; +const IssueContextMenu = suspensify(() => { + const t = useTranslation(); + + const issueId = useValue('current.issue/id'); + const canDelete = useValue('issue/can-delete', { 'issue/id': issueId }); + const openModal = useDispatch('confirmation-modal/open'); + const deleteAction = useDispatch('issue/delete'); + + const onDeleteClick = useCallback(() => { + const onConfirm = async () => deleteAction({ 'issue/id': issueId }); + openModal({ + kind: 'danger', + confirmText: t('common/delete'), + titleText: t('issue.delete.modal/title'), + bodyText: t('issue.delete.modal/body'), + cancelText: t('common/cancel'), + onConfirm, + }); + }, [openModal, t, deleteAction, issueId]); + + const items = useMemo(() => { + let items = []; + if (canDelete) { + items.push({ + text: t('common/delete'), + onClick: onDeleteClick, + Icon: TrashOutline, + kind: 'danger', + key: 'delete', + type: 'button', + }); + } + + return items; + }, [t, canDelete, onDeleteClick]) as ItemType[]; + return items.length === 0 ? null : ( + + ); +}); + export const IssueHeader = suspensify(() => { const issueId = useValue('current.issue/id'); const updatedAt = useValue('issue/updated-at', { 'issue/id': issueId }); @@ -179,6 +222,10 @@ export const IssueHeader = suspensify(() => { +
+ {/*{isDraft ? null : }*/} + +
{/**/}
diff --git a/lib/src/logic/issue.ts b/lib/src/logic/issue.ts index 9d2b169e..b8440a37 100644 --- a/lib/src/logic/issue.ts +++ b/lib/src/logic/issue.ts @@ -58,6 +58,7 @@ export type Subs = { 'issue.locality/zoom': Sub<{ 'issue/id': string }, number>; 'issue/images': Sub<{ 'issue/id': string }, Image[]>; 'issue.image/can-delete': Sub<{ 'issue/id': string }, boolean>; + 'issue/can-delete': Sub<{ 'issue/id': string }, boolean>; }; export type Events = { @@ -87,6 +88,7 @@ export type Events = { 'navigated.issue/new': Evt<{ route: Match }>; 'issue.image/add': Evt<{ file: File; 'issue/id': string; caption: string }>; 'issue.image/delete': Evt<{ 'image/id': string; 'issue/id': string }>; + 'issue/delete': Evt<{ 'issue/id': string }>; }; export const issueSlice = () => ({ diff --git a/lib/src/translations/en.ts b/lib/src/translations/en.ts index feb4e7f0..b86d9a98 100644 --- a/lib/src/translations/en.ts +++ b/lib/src/translations/en.ts @@ -84,6 +84,8 @@ const issue = { 'issue.locality/update': 'Update issue locality', 'issue.locality/description': 'The locality around which the issue is centered.', 'issue/locality': 'Issue locality', + 'issue.delete.modal/title': 'Delete Issue', + 'issue.delete.modal/body': 'Are you sure you want to delete the issue? This cannot be undone.', 'issue.severity/label': ({ avgScore, userScore, votes }: any) => { const oneVote = votes === 1 || votes === '1'; const userScoreText = userScore diff --git a/storybook/src/stores/action/status.ts b/storybook/src/stores/action/status.ts index 4efef837..8b70f021 100644 --- a/storybook/src/stores/action/status.ts +++ b/storybook/src/stores/action/status.ts @@ -1,11 +1,13 @@ import { ActionStatus } from '@elements/logic/action'; +const actionId = 'action-1'; + export const store = { sub: { - 'action.status/modal': { visible: true, 'action/id': '' }, + 'action.status/modal': { visible: true, 'action/id': actionId }, 'action/status': ActionStatus.Active, 'action.status/can-update': false, - 'action/id': '', + 'action/id': actionId, }, evt: ['action.status.modal/close', 'action.status.modal/open', 'action.status/update'], }; diff --git a/storybook/src/stores/issue/header.ts b/storybook/src/stores/issue/header.ts index 4d06cd81..2bb8dbcb 100644 --- a/storybook/src/stores/issue/header.ts +++ b/storybook/src/stores/issue/header.ts @@ -1,4 +1,5 @@ import { store as votingStore } from '@story/stores/voting'; +import { store as confirmationModalStore } from '@story/stores/confirmation-modal'; import { lorem } from '@story/utils/string'; import { randomTimestamp } from '@story/utils/time'; import { store as textEditorStore } from '@story/stores/text-editor'; @@ -16,11 +17,13 @@ export const store = { 'issue.title/text': lorem.generateSentences(1), 'issue.tabs/active-tab': 'locations', 'issue/updated-at': randomTimestamp(), + 'issue/can-delete': false, }, evt: [ ...votingStore.evt, ...textEditorStore.evt, + ...confirmationModalStore.evt, 'issue.current.user/face', 'issue/follow', 'issue/unfollow', @@ -28,5 +31,6 @@ export const store = { 'issue/unsave', 'issue.severity/reset', 'issue.tabs/update', + 'issue/delete', ], };