Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(form-v2): instantiate emergency contact modal in AdminNavBar #4381

Merged
merged 10 commits into from
Aug 2, 2022
2 changes: 2 additions & 0 deletions frontend/src/app/AdminNavBar/AdminNavBar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getTabletViewParameters,
LoggedInDecorator,
StoryRouter,
ViewedEmergencyContactDecorator,
} from '~utils/storybook'

import { AdminNavBar, AdminNavBarProps } from './AdminNavBar'
Expand All @@ -21,6 +22,7 @@ export default {
decorators: [
StoryRouter({ initialEntries: ['/12345'], path: '/:formId' }),
LoggedInDecorator,
ViewedEmergencyContactDecorator,
],
} as Meta

Expand Down
52 changes: 47 additions & 5 deletions frontend/src/app/AdminNavBar/AdminNavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useEffect, useMemo } from 'react'
import { BiCommentDetail } from 'react-icons/bi'
import { Link as ReactLink } from 'react-router-dom'
import {
Expand All @@ -12,7 +12,12 @@ import {

import { BxsHelpCircle } from '~assets/icons/BxsHelpCircle'
import { ReactComponent as BrandMarkSvg } from '~assets/svgs/brand/brand-mark-colour.svg'
import {
EMERGENCY_CONTACT_KEY_PREFIX,
ROLLOUT_ANNOUNCEMENT_KEY_PREFIX,
} from '~constants/localStorage'
import { useIsMobile } from '~hooks/useIsMobile'
import { useLocalStorage } from '~hooks/useLocalStorage'
import { logout } from '~services/AuthService'
import IconButton from '~components/IconButton'
import Link from '~components/Link'
Expand Down Expand Up @@ -80,11 +85,49 @@ export interface AdminNavBarProps {
export const AdminNavBar = ({ isMenuOpen }: AdminNavBarProps): JSX.Element => {
const { user } = useUser()

const ROLLOUT_ANNOUNCEMENT_KEY = useMemo(
() => ROLLOUT_ANNOUNCEMENT_KEY_PREFIX + user?._id,
[user],
)
const [hasSeenAnnouncement] = useLocalStorage<boolean>(
ROLLOUT_ANNOUNCEMENT_KEY,
)

// Only want to show the emergency contact modal if user id exists but user has no emergency contact
const emergencyContactKey = useMemo(
() =>
user && user._id && !user.contact
? EMERGENCY_CONTACT_KEY_PREFIX + user._id
: null,
[user],
)

const [hasSeenContactModal, setHasSeenContactModal] =
useLocalStorage<boolean>(emergencyContactKey)

const {
isOpen: isContactModalOpen,
onClose: onContactModalClose,
onOpen: onContactModalOpen,
} = useDisclosure()
} = useDisclosure({
onClose: () => {
setHasSeenContactModal(true)
},
})

// Emergency contact modal appears after the rollout announcement modal
useEffect(() => {
if (!hasSeenContactModal && user && !user?.contact && hasSeenAnnouncement) {
onContactModalOpen()
}
}, [hasSeenContactModal, onContactModalOpen, user, hasSeenAnnouncement])

const handleLogout = () => {
logout()
if (emergencyContactKey) {
localStorage.removeItem(emergencyContactKey)
}
}

return (
<>
Expand All @@ -105,21 +148,20 @@ export const AdminNavBar = ({ isMenuOpen }: AdminNavBarProps): JSX.Element => {
defaultIsOpen={isMenuOpen}
menuListProps={{ maxWidth: '19rem' }}
>
{/* TODO: Replace with billing route when available */}
<Menu.Item as={ReactLink} to="/billing">
Billing
</Menu.Item>
<Menu.Item onClick={onContactModalOpen}>
Emergency contact
</Menu.Item>
<AvatarMenuDivider />
<Menu.Item onClick={logout}>Sign out</Menu.Item>
<Menu.Item onClick={handleLogout}>Sign out</Menu.Item>
</AvatarMenu>
</HStack>
</AdminNavBar.Container>
<EmergencyContactModal
isOpen={isContactModalOpen}
onClose={onContactModalClose}
isOpen={isContactModalOpen}
/>
</>
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ export const FEATURE_TOUR_KEY_PREFIX = 'has-seen-feature-tour-'
/**
* Key to store whether a user has seen the emergency contact number modal in localStorage.
*/
export const EMERGENCY_CONTACT_KEY_PREFIX = 'has-emergency-contact'
export const EMERGENCY_CONTACT_KEY_PREFIX = 'has-seen-emergency-contact'
13 changes: 12 additions & 1 deletion frontend/src/features/workspace/WorkspacePage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {
FormStatus,
} from '~shared/types/form/form'

import { getUser, MOCK_USER } from '~/mocks/msw/handlers/user'

import { ROOT_ROUTE } from '~constants/routes'
import {
getMobileViewParameters,
LoggedInDecorator,
mockDateDecorator,
StoryRouter,
ViewedEmergencyContactDecorator,
Expand Down Expand Up @@ -57,12 +60,13 @@ export default {
component: WorkspacePage,
decorators: [
ViewedRolloutDecorator,
ViewedEmergencyContactDecorator,
StoryRouter({
initialEntries: [ROOT_ROUTE],
path: ROOT_ROUTE,
}),
mockDateDecorator,
LoggedInDecorator,
ViewedEmergencyContactDecorator,
],
parameters: {
layout: 'fullscreen',
Expand All @@ -76,6 +80,13 @@ export default {
return res(ctx.json(THIRTY_FORMS))
},
),
getUser({
delay: 0,
mockUser: {
...MOCK_USER,
email: '[email protected]',
},
}),
],
},
} as Meta
Expand Down
31 changes: 4 additions & 27 deletions frontend/src/features/workspace/WorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import { useSearchParams } from 'react-router-dom'
import { Box, Container, Grid, useDisclosure } from '@chakra-ui/react'
import { chunk } from 'lodash'

import {
EMERGENCY_CONTACT_KEY_PREFIX,
ROLLOUT_ANNOUNCEMENT_KEY_PREFIX,
} from '~constants/localStorage'
import { AdminNavBar } from '~/app/AdminNavBar/AdminNavBar'

import { ROLLOUT_ANNOUNCEMENT_KEY_PREFIX } from '~constants/localStorage'
import { useLocalStorage } from '~hooks/useLocalStorage'
import Pagination from '~components/Pagination'

import { RolloutAnnouncementModal } from '~features/rollout-announcement/RolloutAnnouncementModal'
import { EmergencyContactModal } from '~features/user/emergency-contact/EmergencyContactModal'
import { useUser } from '~features/user/queries'

// TODO #4279: Remove after React rollout is complete
Expand Down Expand Up @@ -128,26 +126,9 @@ export const WorkspacePage = (): JSX.Element => {
[isUserLoading, hasSeenAnnouncement],
)

const emergencyContactKey = useMemo(
() => (user?._id ? EMERGENCY_CONTACT_KEY_PREFIX + user._id : null),
[user],
)

const [hasSeenEmergencyContact, setHasSeenEmergencyContact] =
useLocalStorage<boolean>(emergencyContactKey)

const isEmergencyContactModalOpen = useMemo(
() =>
!isUserLoading &&
// Open emergency contact modal after the rollout announcement modal
Boolean(hasSeenAnnouncement) &&
!hasSeenEmergencyContact &&
!user?.contact,
[isUserLoading, hasSeenAnnouncement, hasSeenEmergencyContact, user],
)

return (
<>
<AdminNavBar />
<CreateFormModal
isOpen={createFormModalDisclosure.isOpen}
onClose={createFormModalDisclosure.onClose}
Expand Down Expand Up @@ -187,10 +168,6 @@ export const WorkspacePage = (): JSX.Element => {
onClose={() => setHasSeenAnnouncement(true)}
isOpen={isAnnouncementModalOpen}
/>
<EmergencyContactModal
onClose={() => setHasSeenEmergencyContact(true)}
isOpen={isEmergencyContactModalOpen}
/>
<WorkspaceFormRows rows={paginatedData} isLoading={isLoading} />
</Box>
<Container
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/services/AuthService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { UserDto } from '~shared/types/user'

import { LOCAL_STORAGE_EVENT, LOGGED_IN_KEY } from '~constants/localStorage'

import { ApiService } from './ApiService'

const AUTH_ENDPOINT = '/auth'
Expand Down Expand Up @@ -32,5 +34,9 @@ export const verifyLoginOtp = async (params: {
}

export const logout = async (): Promise<void> => {
// Remove logged in state from localStorage
localStorage.removeItem(LOGGED_IN_KEY)
// Event to let useLocalStorage know that key is being deleted.
window.dispatchEvent(new Event(LOCAL_STORAGE_EVENT))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/justANote:
Not from your PR, but the working of LOCAL_STORAGE_EVENT and useLocalStorage seems a bit funny and inefficient 🤔. Every component which use useLocalStorage (and specifies the key it cares about) take an action on all key changes. Storage events by right contain information about what changed, so listeners can take informed decisions.

Not a blocker to merge, maybe I'll have a chat with KarRui later.

return ApiService.get(`${AUTH_ENDPOINT}/logout`)
}