Skip to content

Commit

Permalink
feat(workspaces): folders (#6856)
Browse files Browse the repository at this point in the history
* feat(form-workspaces): add workspace api endpoints (#4182)

* feat: add get workspaces endpoint skeleton

* feat: add create workspace API endpoint

* feat: add update workspace title API endpoint

* feat: add delete workspace API endpoint

* feat: add get workspace forms API endpoint

* feat: add delete workspace forms API endpoint

* feat: add move workspace forms API endpoint

* feat: add auth and logging for workspace routes

* feat: update workspace service to take in necessasry parameters

* feat: add request body for delete workspace api

* feat(form-workspaces-be/1): add workspace db model (#4200)

* feat: add workspace database model

* feat: add workspace db model tests

* refactor: update variable naming in workspace tests

* feat: update workspace formId validation to test for existent admin

* feat: add WorkspaceDto

* fix: update workspace formIds validation rules

* fix: removed unused database fields renaming

* refactor: simplify expression for workspace formIds validation

* feat: add timestamps for workspace db model

* fix: add more workspace model tests and rename test descriptions

* fix: reduce maximum title length of workspace to 50 chars

* feat(form-workspaces-be/2): add get workspaces functionality (#4247)

* fix: removed unused database fields renaming

* feat: add timestamps for workspace db model

* feat: add get workspaces functionality

* feat: add get workspaces tests

* feat: update workspace mapRouteError to include more MongoErrors

* refactor: remove unused MissingUserError in workspace service

* fix: include _id in Workspace type

* feat: add test to check workspace ordering in GET API

* refactor: rename mock constant in model tests

* refactor: remove unused virtual count in workspace model

* feat: add controller test for database conflict error

* feat(form-workspaces-be/3): add create workspace logic  (#4251)

* feat: add create workspace functionality

* feat: add tests for create workspace functionality

* fix: remove return type for create workspace method in model

* feat: add database conflict error test for workspace controller

* fix: failing tests

* fix: update joi validation for workspace title to 50 chars

* feat(form-workspaces-be/4): add update workspace title logic (#4252)

* feat: add update workspace title functionality

* feat: add update workspace title functionality tests

* refactor: workspace model to simplify function call

* feat: add workspace admin permissions check for udpate workspace title

* fix: return appropriate error when unable to update workspace title

* refactor: use object arguments for updateWorkspaceTitle

* fix: broken test cases

* feat: add route test for update title when user has no permissions

* refactor: remove admin field from updateWorkspaceTitle

* fix: update workspace validation implementation

* refactor: workspace utils error messaging

* feat(form-workspaces/1): add form workspaces side menu (#4041)

* refactor: move Workspace contents to a WorkspaceContent component

* feat: add side menu view to WorkspacePage

* feat: add mobile view for workspace page

* feat: use msw for GET workspaces instead of hardcoding in WorkspacePage

* refactor: WorkspacePage to combine mobile and desktop views

* fix: update size and page validation in useWorkspaceForms

* fix: remove react rollout banner since workspaces is released after rollout

* feat: update workspace page to include AdminNavBar

* feat(form-workspaces/2): add form workspaces modals (#4051)

* feat: add create workspace modal view for desktop and mobile

* refactor: move create workspace modal to CreateOrRenameWorkspaceModal.tsx

* feat: add storybook for create workspace modal

* feat: add edit workspace icon button

* chore: cleanup unused formCount prop in WorkspaceHeader

* feat: add rename workspace modal

* feat: add storybook for rename workspace modal

* feat: add delete workspace modal

* feat: add delete workspace modal storybook

* refactor: split CreateOrRenameWorkspaceModal to Create and Rename modals

* feat: add RenameWorkspaceModal storybook

* fix: add isFullWidth for mobile viewport modal buttons

* refactor: cleanup unused minLength validator in workspaceValidation

* feat(form-workspaces-be/5): add delete workspace logic (#4254)

* feat: add delete workspace functionality

* feat: add delete workspace functionality tests

* refactor: use object function argument for deleteWorkspace

* fix: move deleteWorkspace transaction logic to WorkspaceService

* feat: update deleteWorkspace to return boolean instead of void

* refactor: deleteWorkspace transaction to use mongoose withTransaction

* fix: use form document method to archive forms instead of static method

This resolves the issue of failing transaction due to 'nearest' readPreference for FormSchema.
Setting readPreference for static queries throws an error.

* fix: tests for deleteWorkspace functionality

* refactor: remove admin field from deleteWorkspace

* fix: delete workspace transaction to update model instead of documents

Fixed the readPreference issue in multi-document transactions by setting
readPreference in updateMany function on FormModel to be 'primary'

* refactor: workspace service test to reduce db queries

* fix: remove unused session parameter from form model archive method

* feat(form-workspaces-be/7): add move forms from multiple source workspaces functionality (#6418)

* feat: add delete workspace functionality

* feat: add delete workspace functionality tests

* refactor: use object function argument for deleteWorkspace

* fix: move deleteWorkspace transaction logic to WorkspaceService

* feat: update deleteWorkspace to return boolean instead of void

* refactor: deleteWorkspace transaction to use mongoose withTransaction

* fix: use form document method to archive forms instead of static method

This resolves the issue of failing transaction due to 'nearest' readPreference for FormSchema.
Setting readPreference for static queries throws an error.

* fix: tests for deleteWorkspace functionality

* refactor: remove admin field from deleteWorkspace

* fix: delete workspace transaction to update model instead of documents

Fixed the readPreference issue in multi-document transactions by setting
readPreference in updateMany function on FormModel to be 'primary'

* refactor: workspace service test to reduce db queries

* fix: remove unused session parameter from form model archive method

* fix: add new flag to deleteWorkspace findOneAndDelete

* feat: add move forms to another workspace functionality

* test: happy path remove and add formIds to workspace

* test: fix delete and add move form happy path workspace routes

* test: happy path test for workspace service move form

* test: add error test for workspace routes

---------

Co-authored-by: Hans Tirtaputra <[email protected]>

* test: modernise test cases

* feat(workspaces): Integrating BE with FE (#6560)

* feat: add api services for workspace

* feat: update Workspace page with default workspace

* feat: update workspace context

* feat: add workspace mutations

* chore: trim workspace content props

* feat: add functionality for rename and delete workspace

* feat: add default workspace to menu

* feat: implement create workspace

* feat: update workspace header

* feat: implement row action and move form to workspace

* feat: remove workspace mocks from browser

* feat: remove form from workspaces after archive

* feat: reset rename modal useform value

* fix: do not mutate if title is same

* fix: move to selected workspace even form exists

* fix: do not switch to workspace after moving

* fix: mobile mode for workspace

* chore: clean unused attributes

* chore: improve spacing for desktop workspace header

* chore: improve spacing for mobile view too

* fix: align workspace with develop

* fix: useQuery keys

* feat: delete workspace message

* fix: BE tests

* fix: delete workspace copy

* chore: abstract out use context form workspace modals

* fix: mock workspace for storybook

* fix: mobile workspace page grid and menu padding

* feat: display checkbox on form's current workspace

* feat: use callback and memo for workspace row actions

* feat: mobile drawer actions

* fix: move emptyworkspace to WorkspaceContent

* fix(workspaces): remove form from workspace upon collaborator removal (#6592)

* fix: remove form from workspace upon collaborator removal

* fix: typing of default workspace

* fix: return promise and catch error

* fix(workspaces): workspace design review (#6632)

* fix: UI updates from design review

* feat: change text style of default workspace

* feat: bold deleted workspace name

* feat: drawer for mobile workspace edit

* fix: separate form count from the workspace title

* feat: update workspace content and removed unused WorkspacePageContent

* feat: update empty workspace component with folder variation

* feat: display emptyworkspace based on whether default

* chore: copy fixes

* chore: copy for aria-label

* feat(workspaces): create and duplicate form into active workspace (#6755)

* feat: create and move form to workspace transaction

* feat: inject workspace Id to create form handler

* fix: promise return of create form transaction

* fix: invalidate workspace query cache when creating form

* feat: allow for optional workspaceId param in duplicate form

* feat: update dupe form wizard to duplicate to active workspace

* fix: do not send workspaceId if is default workspace

* test: fix admin controller tests

* test: add workspaceId test to admin controller

* chore: abstract processing of form into workspace for testing

* test: form into workspace processing

* chore: use duplicate dto for duplicate controller

* chore: use duplicate dto in FE mutations

* docs: improve explanation of workspaceId logic

* chore: use CreateFormBodyDto type for validator

* feat(workspaces): remove form from workspace (#6754)

* feat: route to remove workspace from forms

* feat: FE mutation to remove form from workspaces

* feat: add remove form from workspace to row actions

* chore: remove deprecated workspace form routes

* fix: design review

* fix: show workspace header for empty workspaces

* fix: span the workspace content

* test: workspace route test for removing from

* feat: use a post request for removing forms for extensibility

* test: update tests

* fix: update remove form workspace service users

* test: fix admin form controller tests

* refactor: abstract out empty workspace components

* chore: update meta action name and messages

* refactor: de-DRY workspace click

* chore: remove unused method and imports

* fix: revert mobile margins

* fix: prevent overflow of folder name in mobile

* feat(workspaces): feature announcement  (#6831)

* chore: update copy of workspace validation messages

* chore: remove bolding from workspace titel

* feat: folders feature announcement

* fix: conditional rendering of create workspace modal

* fix: change border to grid level

* fix: make content bg always neutral 100

* fix: apply column length to both md and lg

* fix: create form modal stories

* fix: additional provider for create default modal

* fix(workspaces): QA issues (#6836)

* fix: empty grey scrollbars

* fix: alignment of text in empty folder

* fix: reduce length of workspace title to 25

* fix: add min width to button to prevent text overflow

* fix: conditionally rename modal

* fix: reset move workspace state on close

* fix: use constants for min and max title length

* fix: use rem over px

* fix(workspaces): use svg for announcement and what's new (#6839)

* feat: use svg

* feat: use svg instead of animation json

* ref: add type to AnnouncementsFeatureList

* fix: use correct import file path

* fix: change announcement date to 2023-10-26

* fix: delete unused json

* fix: typing error of arg of archiveForms

* fix: update release date to 31 oct 2023

---------

Co-authored-by: Hans Sebastian Tirtaputra <[email protected]>
Co-authored-by: Foo Chi Fa <[email protected]>
Co-authored-by: foochifa <[email protected]>
  • Loading branch information
4 people authored Oct 30, 2023
1 parent 85a8900 commit 8fc9ceb
Show file tree
Hide file tree
Showing 75 changed files with 5,204 additions and 418 deletions.
2 changes: 1 addition & 1 deletion frontend/src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const LOCAL_STORAGE_EVENT = 'local-storage'
* Key to store whether a user has seen the rollout announcements before.
*/
export const ROLLOUT_ANNOUNCEMENT_KEY_PREFIX =
'has-seen-rollout-announcement-20230531b-'
'has-seen-rollout-announcement-20231026-'

/**
* Key to store whether the admin has seen the feature tour in localStorage.
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/features/admin-form/common/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ export const useMutateCollaborators = () => {

// Remove all related queries from cache.
queryClient.removeQueries(adminFormKeys.id(formId))
queryClient.invalidateQueries(workspaceKeys.all)
queryClient.invalidateQueries(workspaceKeys.dashboard)
queryClient.invalidateQueries(workspaceKeys.workspaces)

navigate(DASHBOARD_ROUTE)
},
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/features/admin-form/template/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const useCommonHooks = () => {

const handleSuccess = useCallback(
(data: Pick<FormDto, '_id'>) => {
queryClient.invalidateQueries(workspaceKeys.all)
queryClient.invalidateQueries(workspaceKeys.dashboard)
queryClient.invalidateQueries(workspaceKeys.workspaces)
navigate(`${ADMINFORM_ROUTE}/${data._id}`)
},
[navigate, queryClient],
Expand Down
16 changes: 0 additions & 16 deletions frontend/src/features/rollout-announcement/Announcements.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import { ModalCloseButton } from '~components/Modal'

import { ProgressIndicator } from '../../components/ProgressIndicator/ProgressIndicator'

import { NEW_FEATURES } from './components/AnnouncementsFeatureList'
import { NewFeatureContent } from './components/NewFeatureContent'
import { NEW_FEATURES } from './Announcements'

interface RolloutAnnouncementModalProps {
isOpen: boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { GUIDE_PAYMENTS_ENTRY } from '~constants/links'

import { FeatureUpdateImage } from '~features/whats-new/FeatureUpdateList'

import foldersDashboard from '../../whats-new/assets/folders_dashboard.svg'
import PaymentsAnnouncementGraphic from '../assets/payments.json'

export interface NewFeature {
title: string
description: string
learnMoreLink?: string
image: FeatureUpdateImage
}
// When updating this, remember to update the ROLLOUT_ANNOUNCEMENT_KEY_PREFIX with the new date
// so admins will see new announcements.
export const NEW_FEATURES: NewFeature[] = [
{
// Announcement date: 2023-10-31
title: 'Introducing Folders!',
description:
'Say hello to a new way of managing your forms! Create folders and organise your forms to find them easily later.',
image: {
url: foldersDashboard,
alt: 'Dashboard page with folders',
},
},
{
// Announcement date: 2023-05-31
title: 'Collect payments on your form',
description:
'Respondents can now pay for fees and services directly on your form! We integrate with Stripe to provide reliable payments and hassle-free reconciliations. Payment methods we support include debit / credit cards and PayNow.',
learnMoreLink: GUIDE_PAYMENTS_ENTRY,
image: {
animationData: PaymentsAnnouncementGraphic,
alt: 'Collect payments on your form',
},
},
]
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
import { ModalBody, ModalHeader, Text } from '@chakra-ui/react'
import { AnimationConfigWithData } from 'lottie-web'
import { Image, ModalBody, ModalHeader, Text } from '@chakra-ui/react'

import Link from '~components/Link'
import { LottieAnimation } from '~templates/LottieAnimation'

import { NewFeature } from './AnnouncementsFeatureList'
import { NewFeatureTag } from './NewFeatureTag'

interface NewFeatureContentProps {
title: string
description: string
learnMoreLink: string
animationData: AnimationConfigWithData['animationData']
}

export const NewFeatureContent = (props: {
content: NewFeatureContentProps
content: NewFeature
}): JSX.Element => {
const { title, description, animationData, learnMoreLink } = props.content
const { title, description, image, learnMoreLink } = props.content

return (
<>
<LottieAnimation
bg="primary.100"
pt="4.5rem"
height={{ base: '30vh', md: 'initial' }}
animationData={animationData}
preserveAspectRatio="xMidYMax slice"
/>
{image?.animationData ? (
<LottieAnimation
bg="primary.100"
pt="4.5rem"
height={{ base: '30vh', md: 'initial' }}
animationData={image.animationData}
preserveAspectRatio="xMidYMax slice"
/>
) : (
<Image width="100%" src={image.url} alt={image.alt} />
)}
<ModalHeader>
<NewFeatureTag />
<Text mt="0.625rem">{title}</Text>
</ModalHeader>
<ModalBody whiteSpace="pre-wrap">
<Text textStyle="body-1" color="secondary.500">
{description}{' '}
<Link isExternal href={learnMoreLink}>
Learn more
</Link>
{!!learnMoreLink && (
<Link isExternal href={learnMoreLink}>
Learn more
</Link>
)}
</Text>
</ModalBody>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
import { ModalCloseButton } from '~components/Modal'

import { useUserMutations } from '~features/user/mutations'
import { useWorkspace } from '~features/workspace/queries'
import { useDashboard } from '~features/workspace/queries'

import { useUser } from '../queries'

Expand Down Expand Up @@ -61,7 +61,7 @@ const useModalState = ({
reset: UseFormReset<TransferOwnershipInputs>
trigger: UseFormTrigger<TransferOwnershipInputs>
}): UseModalStateReturn => {
const { refetch } = useWorkspace()
const { refetch } = useDashboard()

const [page, setPage] = useState(0)
const [email, setEmail] = useState('')
Expand Down
25 changes: 18 additions & 7 deletions frontend/src/features/whats-new/FeatureUpdateList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { JsonObject, RequireExactlyOne } from 'type-fest'

import { GUIDE_PAYMENTS_ENTRY } from '~constants/links'

import Animation1 from './assets/1-payments.json'
import Animation2 from './assets/2-search-and-filter.json'
import Animation3 from './assets/3-dnd.json'
import Animation2 from './assets/2-payments.json'
import Animation3 from './assets/3-search-and-filter.json'
import Animation4 from './assets/4-dnd.json'
import foldersDashboard from './assets/folders_dashboard.svg'

// image can either be a static image (using url) or an animation (using animationData)
export type FeatureUpdateImage = RequireExactlyOne<
{
alt: string
Expand All @@ -30,14 +32,23 @@ export interface FeatureUpdateList {
// New features should be added at the top of the list.
export const FEATURE_UPDATE_LIST: FeatureUpdateList = {
// Update version whenever a new feature is added.
version: 3,
version: 4,
features: [
{
title: 'Introducing Folders!',
date: new Date('31 Oct 2023 GMT+8'),
description: `Say hello to a new way of managing your forms! Create folders and organise your forms to find them easily later.`,
image: {
url: foldersDashboard,
alt: 'Introducing Folders!',
},
},
{
title: 'Collect payments on your form',
date: new Date('31 May 2023 GMT+8'),
description: `Respondents can now pay for fees and services directly on your form! We integrate with Stripe to provide reliable payments and hassle-free reconciliations. Payment methods we support include debit / credit cards and PayNow. [Learn more](${GUIDE_PAYMENTS_ENTRY})`,
image: {
animationData: Animation1,
animationData: Animation2,
alt: 'Collect payments on your form',
},
},
Expand All @@ -49,7 +60,7 @@ export const FEATURE_UPDATE_LIST: FeatureUpdateList = {
* Applying filters to narrow down results
`,
image: {
animationData: Animation2,
animationData: Animation3,
alt: 'Search and filter your forms',
},
},
Expand All @@ -64,7 +75,7 @@ export const FEATURE_UPDATE_LIST: FeatureUpdateList = {
Notice anything wrong? Let us know by using the feedback button at the bottom-right of the screen.
`,
image: {
animationData: Animation3,
animationData: Animation4,
alt: 'The new FormSG experience',
},
},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/features/whats-new/WhatsNewContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const WhatsNewContent = ({
if (image.animationData) {
return (
<LottieAnimation
bg="primary.100"
title={image.alt}
aria-label={image.alt}
animationData={image.animationData}
Expand Down
128 changes: 128 additions & 0 deletions frontend/src/features/whats-new/assets/folders_dashboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions frontend/src/features/workspace/WorkspaceContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useMemo } from 'react'
import { Box, Container, Grid, useDisclosure } from '@chakra-ui/react'

import { GUIDE_PAYMENTS_ENTRY } from '~constants/links'
import { ROLLOUT_ANNOUNCEMENT_KEY_PREFIX } from '~constants/localStorage'
import { useLocalStorage } from '~hooks/useLocalStorage'
import InlineMessage from '~components/InlineMessage'

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

import CreateFormModal from './components/CreateFormModal'
import {
EmptyDefaultWorkspace,
EmptyNewWorkspace,
} from './components/EmptyWorkspace'
import { WorkspaceFormRows } from './components/WorkspaceFormRow'
import { WorkspaceHeader } from './components/WorkspaceHeader'
import { useWorkspaceContext } from './WorkspaceContext'

export const WorkspaceContent = (): JSX.Element => {
const { isLoading, totalFormsCount, isDefaultWorkspace } =
useWorkspaceContext()
const createFormModalDisclosure = useDisclosure()
const { user, isLoading: isUserLoading } = useUser()

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

const isAnnouncementModalOpen = useMemo(
() => !isUserLoading && hasSeenAnnouncement === false,
[isUserLoading, hasSeenAnnouncement],
)

const dashboardMessage = `Introducing payments! Citizens can now pay for fees and services directly on your form. [Learn more](${GUIDE_PAYMENTS_ENTRY})`

return (
<>
<CreateFormModal
isOpen={createFormModalDisclosure.isOpen}
onClose={createFormModalDisclosure.onClose}
/>
{totalFormsCount === 0 && isDefaultWorkspace ? (
<EmptyDefaultWorkspace
handleOpenCreateFormModal={createFormModalDisclosure.onOpen}
isLoading={isLoading}
/>
) : (
<Grid
bg="neutral.100"
templateColumns="1fr"
templateRows="auto 1fr auto"
minH="100vh"
templateAreas=" 'header' 'main'"
overflowY="auto"
>
<Container
gridArea="header"
maxW="100%"
borderBottom="1px solid var(--chakra-colors-neutral-300)"
px={{ base: '2rem', md: '4rem' }}
py="1rem"
>
{isDefaultWorkspace && (
<InlineMessage
useMarkdown
mb="2rem"
mx="-2rem"
justifyContent="center"
>
{dashboardMessage}
</InlineMessage>
)}
<WorkspaceHeader
handleOpenCreateFormModal={createFormModalDisclosure.onOpen}
/>
</Container>
{totalFormsCount === 0 && !isDefaultWorkspace ? (
<EmptyNewWorkspace isLoading={isLoading} />
) : (
<Box gridArea="main">
<RolloutAnnouncementModal
onClose={() => setHasSeenAnnouncement(true)}
isOpen={isAnnouncementModalOpen}
/>
<WorkspaceFormRows />
</Box>
)}

<Container pt={{ base: '1rem', md: '1.5rem' }} />
</Grid>
)}
</>
)
}
10 changes: 8 additions & 2 deletions frontend/src/features/workspace/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext, useContext } from 'react'
import { createContext, Dispatch, SetStateAction, useContext } from 'react'

import { AdminDashboardFormMetaDto } from '~shared/types'
import { AdminDashboardFormMetaDto, FormId } from '~shared/types'
import { Workspace } from '~shared/types/workspace'

import { FilterOption } from './types'

Expand All @@ -14,6 +15,11 @@ export interface WorkspaceContextProps {
activeFilter: FilterOption
setActiveFilter: (filterOption: FilterOption) => void
hasActiveSearchOrFilter: boolean
activeWorkspace: Workspace
workspaces?: Workspace[]
setCurrentWorkspace: Dispatch<SetStateAction<string>>
getFormWorkspace: (formId: FormId) => Workspace | undefined
isDefaultWorkspace: boolean
}

export const WorkspaceContext = createContext<
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/features/workspace/WorkspacePage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '~shared/types/form/form'

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

import { DASHBOARD_ROUTE } from '~constants/routes'
import {
Expand Down Expand Up @@ -80,6 +81,7 @@ export default {
return res(ctx.json(THIRTY_FORMS))
},
),
getWorkspaces(),
getUser({
delay: 0,
mockUser: {
Expand Down
Loading

0 comments on commit 8fc9ceb

Please sign in to comment.