Skip to content

Commit

Permalink
feat: collaborators (#1007)
Browse files Browse the repository at this point in the history
* style(colors): add danger for icon

* feat(api-service): add collaborator API service methods

* feat(hooks): add collaborator hooks

* feat: add CollaboratorModal

* feat: add Dashboard layout

 This Dashboard layout contains the CollaboratorModal

* feat(storybook): add stories for CollaboratorModal

* fix: import order

* chore: remove duplicate collaboratorData

* chore: only import types

* chore: replace button with loadingbutton

* chore: remove unused import

* fix: also disable button if field is empty

* fix: wrong redirect path

* feat: make link open in new tab

* feat: handle enter key for input

* temp create special route for collaborators

* ref(collaborators): refactor to remove shared props from context (#1076)

* refactor(collaborators): refactors collaborators to remove context

* fix(removecollaborator): renamed variable for clarity

* chore(cøllaboratorhooks): remove extra imports

* chore(collaboratormodal): rename variable for clarity

* fix(mainsubmodal): add rudimentary validation

* refactor(collaboratormodal): shift constant to own file

* chore(collaboratormodal): misc fixes

* chore(loadingbutton): add code block

* refactor(ack submodal): remove moadl body

* refactor(removecollaboratorsubmodal): pass user and onDeleteComplete as props

* refactor(collaboratormodal): refactor to manage state between deletion/mainmodal

* refactor(mainsubmodal): shift unnecessary state downwards into the main modal

* chore(utils): tweak apiDataBuilder to be slightly more powerful

* chore(collab modal stories): tweak stories to work with api

* chore(collaboratormodal): rename subfolder to components

* chore(constants): removed unused stuff

* refactor(collaborators types): shift collaborator types to types folder

* chore(collaboratorservice): add types to methods

* chore(collaboratorhooks): add types to hooks

* chore(mocks): update mocks to fit new typings

* chore(mainsubmodal): update to fit new types

* chore(collaborator hooks): shift into own files for ease of discovery

* chore(types): shift collaborator to error and rename

* chore(dashboard): edit dashboard for testing

* chore(collaboratorhooks): update error import

* refactor(mainsubmodal): add loading state and fix reset

* refactor(ack submodal): add isloading prop

* refactor(collaboartor): add loading stae; update import

* chore(usedeletecollaboratorhook): update erro type

* fix(collaboratormodal): prevent useres being stuck on delete

* fix(uselistcollaboratorshook): transform data from be into shape

* fix(mainsubmodal): disable button if field empty

* chore(mainsubmodal): convert units to rem

* fix(mainsubmodal): clean up state on modal close

* chore(mainsubmodal): remove unused variables

* chore(mainsubmodal): remove multiple calls to function

* chore(collaboratormodal): fix stories

* fix(mainsubmodal): fixed text sizing and add placeholder

* fix(collaborator): fixed story typing for constants

* chore(mainsubmodal): update text styling

Co-authored-by: seaerchin <[email protected]>
  • Loading branch information
prestonlimlianjie and seaerchin authored Oct 21, 2022
1 parent 10eb8ea commit 7e51399
Show file tree
Hide file tree
Showing 24 changed files with 962 additions and 13 deletions.
123 changes: 123 additions & 0 deletions src/components/CollaboratorModal/CollaboratorModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Button, useDisclosure } from "@chakra-ui/react"
import { ComponentMeta, ComponentStory } from "@storybook/react"
import { CollaboratorModal } from "components/CollaboratorModal/index"
import { MemoryRouter, Route } from "react-router-dom"

import { MOCK_COLLABORATORS, MOCK_USER } from "mocks/constants"
import { handlers } from "mocks/handlers"
import {
buildCollaboratorData,
buildCollaboratorRoleData,
buildContributor,
buildLoginData,
buildRemoveContributor,
} from "mocks/utils"

const collaboratorModalMeta = {
title: "Components/CollaboratorModal",
component: CollaboratorModal,
parameters: {
// Set delay so mock API requests will get resolved and the UI will render properly
chromatic: { delay: 500 },
msw: {
handlers,
},
},
decorators: [
(Story) => {
return (
<MemoryRouter initialEntries={["/sites/:siteName/dashboard"]}>
<Route path="/sites/:siteName/dashboard">
<Story />
</Route>
</MemoryRouter>
)
},
],
} as ComponentMeta<typeof CollaboratorModal>

// TODO!: add stories for the submodals
// the sub modals won't show up on chromatic when changes are made at present.
const Template: ComponentStory<typeof CollaboratorModal> = () => {
const props = useDisclosure({ defaultIsOpen: true })
return (
<>
<Button onClick={props.onOpen}>Open collaborators</Button>
<CollaboratorModal {...props} />
</>
)
}

export const AdminMain = Template.bind({})
AdminMain.parameters = {
msw: {
handlers: [
...handlers,
buildRemoveContributor(null),
buildLoginData(MOCK_USER),
buildCollaboratorData({
collaborators: [
// Email override so that the modal can display the "(You)" text depending on
// the LoggedInUser
{ ...MOCK_COLLABORATORS.ADMIN_2, email: MOCK_USER.email },
MOCK_COLLABORATORS.ADMIN_1,
MOCK_COLLABORATORS.CONTRIBUTOR_1,
MOCK_COLLABORATORS.CONTRIBUTOR_2,
],
}),
buildCollaboratorRoleData({ role: "ADMIN" }),
buildContributor(),
],
},
}

export const ContributorMain = Template.bind({})
ContributorMain.parameters = {
msw: {
handlers: [
...handlers,
buildLoginData(MOCK_USER),
buildCollaboratorData({
collaborators: [
MOCK_COLLABORATORS.ADMIN_2,
MOCK_COLLABORATORS.ADMIN_1,
// Email override so that the modal can display the "(You)" text depending on
// the LoggedInUser
{
...MOCK_COLLABORATORS.CONTRIBUTOR_1,
email: MOCK_USER.email,
// Setting lastLoggedIn as now since that must be true
// because the user is seeing this modal
lastLoggedIn: new Date().toString(),
},
MOCK_COLLABORATORS.CONTRIBUTOR_2,
],
}),
buildCollaboratorRoleData({ role: "CONTRIBUTOR" }),
],
},
}

export const AdminAddContributor = Template.bind({})
AdminAddContributor.parameters = {
msw: {
handlers: [
...handlers,
buildLoginData(MOCK_USER),
buildRemoveContributor(null),
buildCollaboratorData({
collaborators: [
// Email override so that the modal can display the "(You)" text depending on
// the LoggedInUser
{ ...MOCK_COLLABORATORS.ADMIN_2, email: MOCK_USER.email },
MOCK_COLLABORATORS.ADMIN_1,
MOCK_COLLABORATORS.CONTRIBUTOR_1,
MOCK_COLLABORATORS.CONTRIBUTOR_2,
],
}),
buildCollaboratorRoleData({ role: "ADMIN" }),
buildContributor(true),
],
},
}
export default collaboratorModalMeta
51 changes: 51 additions & 0 deletions src/components/CollaboratorModal/CollaboratorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ModalProps } from "@chakra-ui/react"
import {
MainSubmodal,
RemoveCollaboratorSubmodal,
} from "components/CollaboratorModal/components"
import { useState } from "react"

import { useLoginContext } from "contexts/LoginContext"

import useRedirectHook from "hooks/useRedirectHook"

import { Collaborator } from "types/collaborators"

// eslint-disable-next-line import/prefer-default-export
export const CollaboratorModal = (
props: Omit<ModalProps, "children">
): JSX.Element => {
const [deleteCollaboratorTarget, setDeleteCollaboratorTarget] = useState<
Collaborator | undefined
>(undefined)
const { onCloseComplete } = props
const [showDelete, setShowDelete] = useState(false)
const { email } = useLoginContext()
const isUserDeletingThemselves = email === deleteCollaboratorTarget?.email
const { setRedirectToPage } = useRedirectHook()

return showDelete && deleteCollaboratorTarget ? (
<RemoveCollaboratorSubmodal
{...props}
userToDelete={deleteCollaboratorTarget}
onCloseComplete={() => {
setShowDelete(false)
onCloseComplete?.()
}}
onDeleteComplete={() => {
setShowDelete(false)
if (isUserDeletingThemselves) {
setRedirectToPage(`/sites`)
}
}}
/>
) : (
<MainSubmodal
{...props}
onDelete={(user) => {
setShowDelete(true)
setDeleteCollaboratorTarget(user)
}}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
Text,
UnorderedList,
ListItem,
Stack,
useModalContext,
} from "@chakra-ui/react"
import { Button, Link, Checkbox } from "@opengovsg/design-system-react"
import { useFormContext } from "react-hook-form"

import { TEXT_FONT_SIZE } from "../constants"

const TERMS_OF_USE_LINK = "https://v2.isomer.gov.sg" // TODO: Update this when we get it

export const AcknowledgementSubmodalContent = ({
isLoading,
}: {
isLoading: boolean
}): JSX.Element => {
const { watch, register, getValues } = useFormContext()
const isAcknowledged = watch("isAcknowledged")
const newCollaboratorEmail = getValues("newCollaboratorEmail")

const { onClose } = useModalContext()

return (
<>
<Text fontSize={TEXT_FONT_SIZE}>
<Text as="span">You are adding</Text>
<Text color="primary.500" as="span">
{" "}
{newCollaboratorEmail}{" "}
</Text>
<Text as="span">as a collaborator to your site.</Text>
</Text>
<br />
<Text fontSize={TEXT_FONT_SIZE}>
<Text as="span">This user</Text>
<Text as="b"> will be able to </Text>
<Text as="span">
{" "}
edit site content and publish it with the approval of a Site Admin,
but{" "}
</Text>
<Text as="b"> will not be able to </Text>
<Text as="span"> add/remove collaborators, or approve changes.</Text>
</Text>
<br />
<Text fontSize={TEXT_FONT_SIZE}>
<Text as="span">
Site Admins and their respective agencies or healthcare institutions
are responsible for:
</Text>
</Text>
<Text fontSize={TEXT_FONT_SIZE}>
<UnorderedList ml="24px">
<ListItem>
The users they authorise to edit their sites in any manner or
capacity
</ListItem>
<ListItem>All the content published on the sites</ListItem>
</UnorderedList>
</Text>
<br />
<Text fontSize={TEXT_FONT_SIZE}>
<Text as="span">
GovTech will not be held liable for content published by any user that
has been granted access/allowed to retain access by a Site Admin.
</Text>
</Text>
<br />
<Checkbox {...register("isAcknowledged")}>
<Text color="text.body" fontSize="16px">
<Text as="span">I agree to Isomer&lsquo;s</Text>{" "}
<Link href={TERMS_OF_USE_LINK} target="_blank">
Terms of Use
</Link>
</Text>
</Checkbox>
<Stack spacing={4} direction="row" justify="right">
<Button variant="clear" color="secondary" onClick={onClose}>
Cancel
</Button>
<Button
isDisabled={!isAcknowledged}
type="submit"
isLoading={isLoading}
>
Continue
</Button>
</Stack>
</>
)
}
Loading

0 comments on commit 7e51399

Please sign in to comment.