diff --git a/src/hooks/useDiscussionParams.ts b/src/hooks/useDiscussionParams.ts new file mode 100644 index 0000000000..5360847cb8 --- /dev/null +++ b/src/hooks/useDiscussionParams.ts @@ -0,0 +1,72 @@ +import { useHistory, useLocation } from 'react-router-dom'; + +const discussionModeKeys = ['view', 'start', 'reply'] as const; + +export type DiscussionMode = (typeof discussionModeKeys)[number]; + +/** + * Handle Discussion (side panel modal) state with the url query params + * `discussionMode` and `discussionId`. + */ +export default function useDiscussionParams() { + const history = useHistory(); + const location = useLocation(); + + return { + getDiscussionParams(): { + /** Undefined implies a closed modal */ + discussionMode: DiscussionMode | undefined; + discussionId: string | undefined; + } { + const q = new URLSearchParams(location.search); + + const discussionMode = q.get('discussionMode') as DiscussionMode | null; + + // Silent ignore on invalid `discussionModeKeys` + if ( + discussionMode === null || + !discussionModeKeys.includes(discussionMode) + ) { + return { + discussionMode: undefined, + discussionId: undefined + }; + } + + // Check reply mode for valid `discussionId` + // Silent fail if `discussionId` is invalid + if (discussionMode === 'reply') { + const discussionId = q.get('discussionId'); + + if (discussionId === null) + return { + discussionMode: undefined, + discussionId: undefined + }; + + return { discussionMode, discussionId }; + } + + return { discussionMode, discussionId: undefined }; + }, + + /** Push a new url query to update the Discussion subviews state. `false` implies closing the modal */ + pushDiscussionQuery( + query: + | { discussionMode: Extract } + | { + discussionMode: Extract; + discussionId: string; + } + | false + ) { + if (query === false) { + history.push(`${location.pathname}`); + return; + } + + const querystring = new URLSearchParams(query); + history.push(`${location.pathname}?${querystring}`); + } + }; +} diff --git a/src/views/DiscussionBoard/Discussion.test.tsx b/src/views/DiscussionBoard/Discussion.test.tsx index 325dc89fbb..5441a22c03 100644 --- a/src/views/DiscussionBoard/Discussion.test.tsx +++ b/src/views/DiscussionBoard/Discussion.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { @@ -22,13 +23,15 @@ describe('Discussion component', () => { const [discussion] = discussions; render( - - - + + + + + ); expect( @@ -105,13 +108,15 @@ describe('Discussion component', () => { }; render( - - - + + + + + ); // Toggle view more replies diff --git a/src/views/DiscussionBoard/Discussion.tsx b/src/views/DiscussionBoard/Discussion.tsx index f0369504bb..9d3e269709 100644 --- a/src/views/DiscussionBoard/Discussion.tsx +++ b/src/views/DiscussionBoard/Discussion.tsx @@ -37,9 +37,7 @@ const Discussion = ({ return (

{t('general.discussion')}

- - {replies.length > 0 && ( <>
@@ -74,7 +72,6 @@ const Discussion = ({ )} )} -

{t('general.reply')}

{ ); it('renders the component', () => { - render(); + render( + + + + ); expect( screen.getByRole('heading', { @@ -41,7 +46,11 @@ describe('ViewDiscussions component', () => { }); it('renders alerts for no discussion posts', () => { - render(); + render( + + + + ); expect(screen.getByText(noNewDiscussionsText)).toBeInTheDocument(); @@ -61,7 +70,11 @@ describe('ViewDiscussions component', () => { discussion => discussion.replies.length === 0 ); - render(); + render( + + + + ); /* Discussions with replies */ diff --git a/src/views/DiscussionBoard/ViewDiscussions.tsx b/src/views/DiscussionBoard/ViewDiscussions.tsx index 14e4f92845..60eb065421 100644 --- a/src/views/DiscussionBoard/ViewDiscussions.tsx +++ b/src/views/DiscussionBoard/ViewDiscussions.tsx @@ -5,6 +5,7 @@ import { SystemIntakeGRBReviewDiscussionFragment } from 'gql/gen/graphql'; import Alert from 'components/shared/Alert'; import IconButton from 'components/shared/IconButton'; +import useDiscussionParams from 'hooks/useDiscussionParams'; import DiscussionPost from './components/DiscussionPost'; import DiscussionsList from './components/DiscussionsList'; @@ -22,6 +23,8 @@ type ViewDiscussionsProps = { const ViewDiscussions = ({ grbDiscussions }: ViewDiscussionsProps) => { const { t } = useTranslation('discussions'); + const { pushDiscussionQuery } = useDiscussionParams(); + const discussionsWithoutReplies: SystemIntakeGRBReviewDiscussionFragment[] = grbDiscussions.filter(discussion => discussion.replies.length === 0); @@ -36,18 +39,17 @@ const ViewDiscussions = ({ grbDiscussions }: ViewDiscussionsProps) => {

{t('governanceReviewBoard.internal.description')}

-

{t('general.label')}

null} + onClick={() => { + pushDiscussionQuery({ discussionMode: 'start' }); + }} icon={} unstyled > {t('general.startNewDiscussion')} - { {discussionsWithoutReplies.map((discussion, index) => (
  • { {discussionsWithReplies.map((discussion, index) => (
  • { if ('systemIntakeID' in mutationProps) { mutateDiscussion({ @@ -83,9 +86,10 @@ const DiscussionForm = ({ message: t('general.alerts.startDiscussionError'), type: 'error' }); + }) + .finally(() => { + pushDiscussionQuery({ discussionMode: 'view' }); }); - - // TODO: Go back to discussion board view } }); @@ -159,7 +163,11 @@ const DiscussionForm = ({ -