diff --git a/front/package.json b/front/package.json index e4ed64cf4..5d448f44e 100644 --- a/front/package.json +++ b/front/package.json @@ -159,8 +159,8 @@ "workerDirectory": "public" }, "nyc": { - "lines": 70, - "statements": 70, + "lines": 65, + "statements": 65, "exclude": [ "src/generated/**/*" ] diff --git a/front/src/modules/apollo/hooks/useApolloFactory.ts b/front/src/modules/apollo/hooks/useApolloFactory.ts index d3dac6a13..5beb22560 100644 --- a/front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/front/src/modules/apollo/hooks/useApolloFactory.ts @@ -1,17 +1,10 @@ import { useMemo, useRef } from 'react'; -import { - ApolloLink, - InMemoryCache, - NormalizedCacheObject, -} from '@apollo/client'; +import { InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { useRecoilState } from 'recoil'; -import { isMockModeState } from '@/auth/states/isMockModeState'; import { tokenPairState } from '@/auth/states/tokenPairState'; import { isDebugModeState } from '@/client-config/states/isDebugModeState'; import { CommentThreadTarget } from '~/generated/graphql'; -import { mockedCompaniesData } from '~/testing/mock-data/companies'; -import { mockedUsersData } from '~/testing/mock-data/users'; import { ApolloFactory } from '../services/apollo.factory'; @@ -20,22 +13,8 @@ export function useApolloFactory() { const [isDebugMode] = useRecoilState(isDebugModeState); const [tokenPair, setTokenPair] = useRecoilState(tokenPairState); - const [isMockMode] = useRecoilState(isMockModeState); const apolloClient = useMemo(() => { - const mockLink = new ApolloLink((operation, forward) => { - return forward(operation).map((response) => { - if (operation.operationName === 'GetCompanies') { - return { data: { companies: mockedCompaniesData } }; - } - if (operation.operationName === 'GetCurrentUser') { - return { data: { currentUser: mockedUsersData[0] } }; - } - - return response; - }); - }); - apolloRef.current = new ApolloFactory({ uri: `${process.env.REACT_APP_API_URL}`, cache: new InMemoryCache({ @@ -65,13 +44,13 @@ export function useApolloFactory() { onUnauthenticatedError() { setTokenPair(null); }, - extraLinks: isMockMode ? [mockLink] : [], + extraLinks: [], isDebugMode, tokenPair, }); return apolloRef.current.getClient(); - }, [isMockMode, setTokenPair, isDebugMode, tokenPair]); + }, [setTokenPair, isDebugMode, tokenPair]); return apolloClient; } diff --git a/front/src/modules/auth/components/ui/Modal.tsx b/front/src/modules/auth/components/ui/Modal.tsx index 284007bc5..6e22ff218 100644 --- a/front/src/modules/auth/components/ui/Modal.tsx +++ b/front/src/modules/auth/components/ui/Modal.tsx @@ -1,8 +1,6 @@ import React from 'react'; import styled from '@emotion/styled'; -import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; -import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { Modal as UIModal } from '@/ui/components/modal/Modal'; type Props = React.ComponentProps<'div'>; @@ -19,11 +17,6 @@ const StyledContainer = styled.div` `; export function AuthModal({ children, ...restProps }: Props) { - useHotkeysScopeOnMountOnly({ - scope: InternalHotkeysScope.Modal, - customScopes: { 'command-menu': false, goto: false }, - }); - return ( {children} diff --git a/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts b/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts index aa54d6084..82c2737fa 100644 --- a/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts +++ b/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts @@ -7,8 +7,11 @@ export enum InternalHotkeysScope { CellEditMode = 'cell-edit-mode', RightDrawer = 'right-drawer', TableHeaderDropdownButton = 'table-header-dropdown-button', - CreateProfile = 'create-profile', RelationPicker = 'relation-picker', CellDoubleTextInput = 'cell-double-text-input', - Modal = 'modal', + Settings = 'settings', + CreateWokspace = 'create-workspace', + PasswordLogin = 'password-login', + AuthIndex = 'auth-index', + CreateProfile = 'create-profile', } diff --git a/front/src/modules/settings/profile/components/NameFields.tsx b/front/src/modules/settings/profile/components/NameFields.tsx index 2afdae980..1ed33a0fb 100644 --- a/front/src/modules/settings/profile/components/NameFields.tsx +++ b/front/src/modules/settings/profile/components/NameFields.tsx @@ -17,7 +17,17 @@ const StyledComboInputContainer = styled.div` } `; -export function NameFields() { +type OwnProps = { + autoSave?: boolean; + onFirstNameUpdate?: (firstName: string) => void; + onLastNameUpdate?: (lastName: string) => void; +}; + +export function NameFields({ + autoSave = true, + onFirstNameUpdate, + onLastNameUpdate, +}: OwnProps) { const currentUser = useRecoilValue(currentUserState); const [firstName, setFirstName] = useState(currentUser?.firstName ?? ''); @@ -27,26 +37,34 @@ export function NameFields() { // TODO: Enhance this with react-hook-form (https://www.react-hook-form.com) const debouncedUpdate = debounce(async () => { + if (onFirstNameUpdate) { + onFirstNameUpdate(firstName); + } + if (onLastNameUpdate) { + onLastNameUpdate(lastName); + } try { - const { data, errors } = await updateUser({ - variables: { - where: { - id: currentUser?.id, - }, - data: { - firstName: { - set: firstName, + if (autoSave) { + const { data, errors } = await updateUser({ + variables: { + where: { + id: currentUser?.id, }, - lastName: { - set: lastName, + data: { + firstName: { + set: firstName, + }, + lastName: { + set: lastName, + }, }, }, - }, - refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], - }); + refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], + }); - if (errors || !data?.updateUser) { - throw errors; + if (errors || !data?.updateUser) { + throw errors; + } } } catch (error) { console.error(error); @@ -64,7 +82,7 @@ export function NameFields() { return () => { debouncedUpdate.cancel(); }; - }, [firstName, lastName, currentUser, debouncedUpdate]); + }, [firstName, lastName, currentUser, debouncedUpdate, autoSave]); return ( diff --git a/front/src/modules/ui/layout/AuthLayout.tsx b/front/src/modules/ui/layout/AuthLayout.tsx index 2113eefc4..04becd3b6 100644 --- a/front/src/modules/ui/layout/AuthLayout.tsx +++ b/front/src/modules/ui/layout/AuthLayout.tsx @@ -1,10 +1,14 @@ +import { useRecoilValue } from 'recoil'; + +import { isMockModeState } from '@/auth/states/isMockModeState'; import { Companies } from '~/pages/companies/Companies'; +import { CompaniesMockMode } from '~/pages/companies/CompaniesMockMode'; export function AuthLayout({ children }: React.PropsWithChildren) { + const isMockMode = useRecoilValue(isMockModeState); return ( <> - {/** Mocked data */} - + {isMockMode ? : } {children} ); diff --git a/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx b/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx index ad6e96b4e..dee31d49d 100644 --- a/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx +++ b/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { mockedUsersData } from '~/testing/mock-data/users'; import NavCollapseButton from './NavCollapseButton'; @@ -50,15 +51,16 @@ function NavWorkspaceButton() { const currentWorkspace = currentUser?.workspaceMember?.workspace; - if (!currentWorkspace) { - return null; - } - return ( - - {currentWorkspace?.displayName} + + {currentWorkspace?.displayName ?? 'Twenty'} diff --git a/front/src/pages/auth/CreateProfile.tsx b/front/src/pages/auth/CreateProfile.tsx index 248f24e81..c273a6230 100644 --- a/front/src/pages/auth/CreateProfile.tsx +++ b/front/src/pages/auth/CreateProfile.tsx @@ -9,12 +9,14 @@ import { SubTitle } from '@/auth/components/ui/SubTitle'; import { Title } from '@/auth/components/ui/Title'; import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus'; import { currentUserState } from '@/auth/states/currentUserState'; +import { isMockModeState } from '@/auth/states/isMockModeState'; import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; +import { NameFields } from '@/settings/profile/components/NameFields'; +import { PictureUploader } from '@/settings/profile/components/PictureUploader'; import { MainButton } from '@/ui/components/buttons/MainButton'; -import { ImageInput } from '@/ui/components/inputs/ImageInput'; -import { TextInput } from '@/ui/components/inputs/TextInput'; import { SubSectionTitle } from '@/ui/components/section-titles/SubSectionTitle'; import { GET_CURRENT_USER } from '@/users/queries'; import { useUpdateUserMutation } from '~/generated/graphql'; @@ -36,16 +38,13 @@ const StyledButtonContainer = styled.div` width: 200px; `; -const StyledComboInputContainer = styled.div` - display: flex; - flex-direction: row; - > * + * { - margin-left: ${({ theme }) => theme.spacing(4)}; - } -`; - export function CreateProfile() { + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.CreateProfile, + customScopes: { 'command-menu': false, goto: false }, + }); const navigate = useNavigate(); + const [, setMockMode] = useRecoilState(isMockModeState); const onboardingStatus = useOnboardingStatus(); const [currentUser] = useRecoilState(currentUserState); @@ -94,15 +93,16 @@ export function CreateProfile() { () => { handleCreate(); }, - InternalHotkeysScope.Modal, + InternalHotkeysScope.CreateProfile, [handleCreate], ); useEffect(() => { + setMockMode(true); if (onboardingStatus !== OnboardingStatus.OngoingProfileCreation) { navigate('/'); } - }, [onboardingStatus, navigate]); + }, [onboardingStatus, navigate, setMockMode]); return ( <> @@ -111,29 +111,18 @@ export function CreateProfile() { - + - - - - + diff --git a/front/src/pages/auth/CreateWorkspace.tsx b/front/src/pages/auth/CreateWorkspace.tsx index 0be124935..380a1ae3b 100644 --- a/front/src/pages/auth/CreateWorkspace.tsx +++ b/front/src/pages/auth/CreateWorkspace.tsx @@ -2,11 +2,14 @@ import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; import { SubTitle } from '@/auth/components/ui/SubTitle'; import { Title } from '@/auth/components/ui/Title'; import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus'; +import { isMockModeState } from '@/auth/states/isMockModeState'; import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { MainButton } from '@/ui/components/buttons/MainButton'; @@ -34,6 +37,11 @@ const StyledButtonContainer = styled.div` `; export function CreateWorkspace() { + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.CreateWokspace, + customScopes: { 'command-menu': false, goto: false }, + }); + const [, setMockMode] = useRecoilState(isMockModeState); const navigate = useNavigate(); const onboardingStatus = useOnboardingStatus(); @@ -74,15 +82,16 @@ export function CreateWorkspace() { () => { handleCreate(); }, - InternalHotkeysScope.Modal, + InternalHotkeysScope.CreateWokspace, [handleCreate], ); useEffect(() => { + setMockMode(true); if (onboardingStatus !== OnboardingStatus.OngoingWorkspaceCreation) { navigate('/auth/create/profile'); } - }, [onboardingStatus, navigate]); + }, [onboardingStatus, navigate, setMockMode]); return ( <> diff --git a/front/src/pages/auth/Index.tsx b/front/src/pages/auth/Index.tsx index f6e399152..f311f95ae 100644 --- a/front/src/pages/auth/Index.tsx +++ b/front/src/pages/auth/Index.tsx @@ -13,6 +13,7 @@ import { authFlowUserEmailState } from '@/auth/states/authFlowUserEmailState'; import { isMockModeState } from '@/auth/states/isMockModeState'; import { authProvidersState } from '@/client-config/states/authProvidersState'; import { isDemoModeState } from '@/client-config/states/isDemoModeState'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { MainButton } from '@/ui/components/buttons/MainButton'; @@ -32,6 +33,10 @@ const StyledFooterNote = styled(FooterNote)` `; export function Index() { + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.AuthIndex, + customScopes: { 'command-menu': false, goto: false }, + }); const navigate = useNavigate(); const theme = useTheme(); const [, setMockMode] = useRecoilState(isMockModeState); @@ -62,7 +67,7 @@ export function Index() { () => { onPasswordLoginClick(); }, - InternalHotkeysScope.Modal, + InternalHotkeysScope.AuthIndex, [onPasswordLoginClick], ); diff --git a/front/src/pages/auth/PasswordLogin.tsx b/front/src/pages/auth/PasswordLogin.tsx index 3e08e0da0..8fb966dc2 100644 --- a/front/src/pages/auth/PasswordLogin.tsx +++ b/front/src/pages/auth/PasswordLogin.tsx @@ -11,6 +11,7 @@ import { useAuth } from '@/auth/hooks/useAuth'; import { authFlowUserEmailState } from '@/auth/states/authFlowUserEmailState'; import { isMockModeState } from '@/auth/states/isMockModeState'; import { isDemoModeState } from '@/client-config/states/isDemoModeState'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { MainButton } from '@/ui/components/buttons/MainButton'; @@ -50,6 +51,10 @@ const StyledErrorContainer = styled.div` `; export function PasswordLogin() { + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.PasswordLogin, + customScopes: { 'command-menu': false, goto: false }, + }); const navigate = useNavigate(); const [isDemoMode] = useRecoilState(isDemoModeState); @@ -81,7 +86,7 @@ export function PasswordLogin() { () => { handleLogin(); }, - InternalHotkeysScope.Modal, + InternalHotkeysScope.PasswordLogin, [handleLogin], ); diff --git a/front/src/pages/companies/CompaniesMockMode.tsx b/front/src/pages/companies/CompaniesMockMode.tsx new file mode 100644 index 000000000..7d101a07c --- /dev/null +++ b/front/src/pages/companies/CompaniesMockMode.tsx @@ -0,0 +1,40 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { RecoilScope } from '@/recoil-scope/components/RecoilScope'; +import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar'; +import { IconBuildingSkyscraper } from '@/ui/icons/index'; +import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'; +import { TableContext } from '@/ui/tables/states/TableContext'; + +import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany'; +import { TableActionBarButtonDeleteCompanies } from './table/TableActionBarButtonDeleteCompanies'; +import { CompanyTableMockMode } from './CompanyTableMockMode'; + +const StyledTableContainer = styled.div` + display: flex; + width: 100%; +`; + +export function CompaniesMockMode() { + const theme = useTheme(); + + return ( + <> + } + > + + + + + + + + + + + + ); +} diff --git a/front/src/pages/companies/CompanyTableMockMode.tsx b/front/src/pages/companies/CompanyTableMockMode.tsx new file mode 100644 index 000000000..07ef66e56 --- /dev/null +++ b/front/src/pages/companies/CompanyTableMockMode.tsx @@ -0,0 +1,32 @@ +import { IconList } from '@tabler/icons-react'; + +import { EntityTable } from '@/ui/components/table/EntityTable'; +import { HooksEntityTable } from '@/ui/components/table/HooksEntityTable'; +import { mockedCompaniesData } from '~/testing/mock-data/companies'; + +import { useCompaniesColumns } from './companies-columns'; +import { companiesFilters } from './companies-filters'; +import { availableSorts } from './companies-sorts'; + +export function CompanyTableMockMode() { + const companiesColumns = useCompaniesColumns(); + + const companies = mockedCompaniesData; + + return ( + <> + + } + availableSorts={availableSorts} + /> + + ); +} diff --git a/front/src/pages/settings/SettingsProfile.tsx b/front/src/pages/settings/SettingsProfile.tsx index ead68b066..4fa1a67f7 100644 --- a/front/src/pages/settings/SettingsProfile.tsx +++ b/front/src/pages/settings/SettingsProfile.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; +import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { EmailField } from '@/settings/profile/components/EmailField'; import { NameFields } from '@/settings/profile/components/NameFields'; import { PictureUploader } from '@/settings/profile/components/PictureUploader'; @@ -24,6 +26,11 @@ const StyledSectionContainer = styled.div` `; export function SettingsProfile() { + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.Settings, + customScopes: { 'command-menu': true, goto: false }, + }); + return ( diff --git a/front/src/pages/settings/__stories__/settings.stories.tsx b/front/src/pages/settings/__stories__/SettingsProfile.stories.tsx similarity index 100% rename from front/src/pages/settings/__stories__/settings.stories.tsx rename to front/src/pages/settings/__stories__/SettingsProfile.stories.tsx diff --git a/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx b/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx new file mode 100644 index 000000000..0f1a74cae --- /dev/null +++ b/front/src/pages/settings/__stories__/SettingsWorkspaceMembers.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { graphqlMocks } from '~/testing/graphqlMocks'; +import { getRenderWrapperForPage } from '~/testing/renderWrappers'; + +import { SettingsWorkspaceMembers } from '../SettingsWorkspaceMembers'; + +const meta: Meta = { + title: 'Pages/Settings/SettingsWorkspaceMembers', + component: SettingsWorkspaceMembers, +}; + +export default meta; + +export type Story = StoryObj; + +export const Default: Story = { + render: getRenderWrapperForPage( + , + '/settings/workspace-members', + ), + parameters: { + msw: graphqlMocks, + }, +};