diff --git a/front/src/modules/auth/hooks/useOnboardingStatus.ts b/front/src/modules/auth/hooks/useOnboardingStatus.ts index 1fe60e4624..2cee5104ef 100644 --- a/front/src/modules/auth/hooks/useOnboardingStatus.ts +++ b/front/src/modules/auth/hooks/useOnboardingStatus.ts @@ -14,10 +14,6 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => { const currentWorkspace = useRecoilValue(currentWorkspaceState); const isLoggedIn = useIsLogged(); - console.log( - getOnboardingStatus(isLoggedIn, currentWorkspaceMember, currentWorkspace), - ); - return getOnboardingStatus( isLoggedIn, currentWorkspaceMember, diff --git a/front/src/modules/auth/states/currentWorkspaceMemberState.ts b/front/src/modules/auth/states/currentWorkspaceMemberState.ts index 16db1f4a1b..219bfa5ffe 100644 --- a/front/src/modules/auth/states/currentWorkspaceMemberState.ts +++ b/front/src/modules/auth/states/currentWorkspaceMemberState.ts @@ -1,18 +1,8 @@ import { atom } from 'recoil'; -import { ColorScheme } from '~/generated-metadata/graphql'; +import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; -export type CurrentWorkspaceMember = { - id: string; - locale: string; - colorScheme: ColorScheme; - allowImpersonation: boolean; - firstName: string; - lastName: string; - avatarUrl: string; -}; - -export const currentWorkspaceMemberState = atom({ +export const currentWorkspaceMemberState = atom({ key: 'currentWorkspaceMemberState', default: null, }); diff --git a/front/src/modules/auth/utils/getOnboardingStatus.ts b/front/src/modules/auth/utils/getOnboardingStatus.ts index 87b39743de..ed70c21f23 100644 --- a/front/src/modules/auth/utils/getOnboardingStatus.ts +++ b/front/src/modules/auth/utils/getOnboardingStatus.ts @@ -1,5 +1,5 @@ -import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState'; +import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; export enum OnboardingStatus { OngoingUserCreation = 'ongoing_user_creation', @@ -10,7 +10,7 @@ export enum OnboardingStatus { export const getOnboardingStatus = ( isLoggedIn: boolean, - currentWorkspaceMember: CurrentWorkspaceMember | null, + currentWorkspaceMember: WorkspaceMember | null, currentWorkspace: CurrentWorkspace | null, ) => { if (!isLoggedIn) { diff --git a/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts b/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts index 89f8dd5f12..7ce4114184 100644 --- a/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts +++ b/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts @@ -6,22 +6,6 @@ import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/comp import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { isDefined } from '~/utils/isDefined'; -type SelectStringKeys = NonNullable< - { - [K in keyof T]: K extends '__typename' - ? never - : T[K] extends string | undefined | null - ? K - : never; - }[keyof T] ->; - -type ExtractEntityTypeFromQueryResponse = T extends { - searchResults: Array; -} - ? U - : never; - type SearchFilter = { fieldNames: string[]; filter: string | number }; export type OrderBy = diff --git a/front/src/modules/settings/profile/components/NameFields.tsx b/front/src/modules/settings/profile/components/NameFields.tsx index 837eeae1ed..c89137e8e1 100644 --- a/front/src/modules/settings/profile/components/NameFields.tsx +++ b/front/src/modules/settings/profile/components/NameFields.tsx @@ -54,21 +54,27 @@ export const NameFields = ({ onLastNameUpdate(lastName); } try { + if (!currentWorkspaceMember?.id) { + throw new Error('User is not logged in'); + } + if (autoSave) { if (!updateOneObject || objectNotFoundInMetadata) { - return; + throw new Error('Object not found in metadata'); } await updateOneObject({ - idToUpdate: currentWorkspaceMember?.id ?? '', + idToUpdate: currentWorkspaceMember?.id, input: { firstName, lastName, }, }); - setCurrentWorkspaceMember( - (current) => ({ ...current, firstName, lastName } as any), - ); + setCurrentWorkspaceMember({ + ...currentWorkspaceMember, + firstName, + lastName, + }); } } catch (error) { logError(error); diff --git a/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx b/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx index 5b1abed63a..589a5dfcd9 100644 --- a/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx +++ b/front/src/modules/settings/profile/components/ProfilePictureUploader.tsx @@ -1,11 +1,9 @@ import { useState } from 'react'; -import { getOperationName } from '@apollo/client/utilities'; import { useRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; import { ImageInput } from '@/ui/input/components/ImageInput'; -import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; import { useUploadProfilePictureMutation } from '~/generated/graphql'; @@ -35,6 +33,9 @@ export const ProfilePictureUploader = () => { setUploadController(controller); try { + if (!currentWorkspaceMember?.id) { + throw new Error('User is not logged in'); + } const result = await uploadPicture({ variables: { file, @@ -44,7 +45,6 @@ export const ProfilePictureUploader = () => { signal: controller.signal, }, }, - refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], }); setUploadController(null); @@ -53,21 +53,19 @@ export const ProfilePictureUploader = () => { const avatarUrl = result?.data?.uploadProfilePicture; if (!avatarUrl) { - return; + throw new Error('Avatar URL not found'); } if (!updateOneObject || objectNotFoundInMetadata) { - return; + throw new Error('Object not found in metadata'); } await updateOneObject({ - idToUpdate: currentWorkspaceMember?.id ?? '', + idToUpdate: currentWorkspaceMember?.id, input: { avatarUrl, }, }); - setCurrentWorkspaceMember( - (current) => ({ ...current, avatarUrl } as any), - ); + setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl }); return result; } catch (error) { @@ -84,18 +82,19 @@ export const ProfilePictureUploader = () => { const handleRemove = async () => { if (!updateOneObject || objectNotFoundInMetadata) { - return; + throw new Error('Object not found in metadata'); + } + if (!currentWorkspaceMember?.id) { + throw new Error('User is not logged in'); } await updateOneObject({ - idToUpdate: currentWorkspaceMember?.id ?? '', + idToUpdate: currentWorkspaceMember?.id, input: { avatarUrl: null, }, }); - setCurrentWorkspaceMember( - (current) => ({ ...current, avatarUrl: null } as any), - ); + setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl: null }); }; return ( diff --git a/front/src/modules/settings/profile/components/ToggleField.tsx b/front/src/modules/settings/profile/components/ToggleField.tsx index 478a4544bd..5141187b11 100644 --- a/front/src/modules/settings/profile/components/ToggleField.tsx +++ b/front/src/modules/settings/profile/components/ToggleField.tsx @@ -20,21 +20,21 @@ export const ToggleField = () => { const handleChange = async (value: boolean) => { try { if (!updateOneObject || objectNotFoundInMetadata) { - return; + throw new Error('Object not found in metadata'); + } + if (!currentWorkspaceMember?.id) { + throw new Error('User is not logged in'); } await updateOneObject({ - idToUpdate: currentWorkspaceMember?.id ?? '', + idToUpdate: currentWorkspaceMember?.id, input: { allowImpersonation: value, }, }); - setCurrentWorkspaceMember( - (current) => - ({ - ...current, - allowImpersonation: value, - } as any), - ); + setCurrentWorkspaceMember({ + ...currentWorkspaceMember, + allowImpersonation: value, + }); } catch (err: any) { enqueueSnackBar(err?.message, { variant: 'error', diff --git a/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx b/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx index 4caf951b7d..726b7a3513 100644 --- a/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx +++ b/front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx @@ -1,9 +1,7 @@ -import { getOperationName } from '@apollo/client/utilities'; -import { useRecoilValue } from 'recoil'; +import { useRecoilState } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { ImageInput } from '@/ui/input/components/ImageInput'; -import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; import { useRemoveWorkspaceLogoMutation, @@ -13,23 +11,41 @@ import { export const WorkspaceLogoUploader = () => { const [uploadLogo] = useUploadWorkspaceLogoMutation(); const [removeLogo] = useRemoveWorkspaceLogoMutation(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + currentWorkspaceState, + ); const onUpload = async (file: File) => { if (!file) { return; } + if (!currentWorkspace?.id) { + throw new Error('Workspace id not found'); + } await uploadLogo({ variables: { file, }, - refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], + onCompleted: (data) => { + setCurrentWorkspace({ + ...currentWorkspace, + logo: data.uploadWorkspaceLogo, + }); + }, }); }; const onRemove = async () => { + if (!currentWorkspace?.id) { + throw new Error('Workspace id not found'); + } await removeLogo({ - refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], + onCompleted: () => { + setCurrentWorkspace({ + ...currentWorkspace, + logo: null, + }); + }, }); }; diff --git a/front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx b/front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx index 4cbff96797..4a49887acb 100644 --- a/front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx +++ b/front/src/modules/ui/input/color-scheme/components/ColorSchemePicker.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from '@emotion/styled'; -import { ColorScheme } from '~/generated/graphql'; +import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; import { ColorSchemeCard } from './ColorSchemeCard'; @@ -37,25 +37,25 @@ export const ColorSchemePicker = ({ onChange(ColorScheme.Light)} + onClick={() => onChange('Light')} variant="light" - selected={value === ColorScheme.Light} + selected={value === 'Light'} /> Light onChange(ColorScheme.Dark)} + onClick={() => onChange('Dark')} variant="dark" - selected={value === ColorScheme.Dark} + selected={value === 'Dark'} /> Dark onChange(ColorScheme.System)} + onClick={() => onChange('System')} variant="system" - selected={value === ColorScheme.System} + selected={value === 'System'} /> System settings diff --git a/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx b/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx index cc9cb566f4..ded7f00ac2 100644 --- a/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx +++ b/front/src/modules/ui/navigation/navbar/components/NavWorkspaceButton.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI'; @@ -53,7 +52,6 @@ type NavWorkspaceButtonProps = { const NavWorkspaceButton = ({ showCollapseButton, }: NavWorkspaceButtonProps) => { - const currentUser = useRecoilValue(currentUserState); const currentWorkspace = useRecoilValue(currentWorkspaceState); const DEFAULT_LOGO = diff --git a/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx b/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx index c08605bf8b..f5612e5b84 100644 --- a/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx +++ b/front/src/modules/ui/navigation/navbar/components/SupportChat.tsx @@ -3,13 +3,11 @@ import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; -import { - CurrentWorkspaceMember, - currentWorkspaceMemberState, -} from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { supportChatState } from '@/client-config/states/supportChatState'; import { IconHelpCircle } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; +import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { User } from '~/generated/graphql'; const StyledButtonContainer = styled.div` @@ -42,10 +40,7 @@ const SupportChat = () => { ( chatId: string, currentUser: Pick, - currentWorkspaceMember: Pick< - CurrentWorkspaceMember, - 'firstName' | 'lastName' - >, + currentWorkspaceMember: Pick, ) => { const url = 'https://chat-assets.frontapp.com/v1/chat.bundle.js'; const script = document.querySelector(`script[src="${url}"]`); diff --git a/front/src/modules/ui/object/record-table/components/RecordTable.tsx b/front/src/modules/ui/object/record-table/components/RecordTable.tsx index 18ddcd10ca..43c675140e 100644 --- a/front/src/modules/ui/object/record-table/components/RecordTable.tsx +++ b/front/src/modules/ui/object/record-table/components/RecordTable.tsx @@ -82,8 +82,6 @@ type RecordTableProps = { export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => { const tableBodyRef = useRef(null); - console.log('record table'); - const { leaveTableFocus, setRowSelectedState, diff --git a/front/src/modules/ui/theme/hooks/useColorScheme.ts b/front/src/modules/ui/theme/hooks/useColorScheme.ts index 5529460a87..6c76f4938e 100644 --- a/front/src/modules/ui/theme/hooks/useColorScheme.ts +++ b/front/src/modules/ui/theme/hooks/useColorScheme.ts @@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; -import { ColorScheme } from '~/generated/graphql'; +import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; export const useColorScheme = () => { const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState); @@ -12,7 +12,7 @@ export const useColorScheme = () => { useUpdateOneObjectRecord({ objectNamePlural: 'workspaceMembersV2', }); - const colorScheme = currentWorkspaceMember?.colorScheme ?? ColorScheme.System; + const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System'; const setColorScheme = useCallback( async (value: ColorScheme) => { diff --git a/front/src/modules/users/components/UserPicker.tsx b/front/src/modules/users/components/UserPicker.tsx index b8e1b28044..4187ad2d27 100644 --- a/front/src/modules/users/components/UserPicker.tsx +++ b/front/src/modules/users/components/UserPicker.tsx @@ -18,10 +18,6 @@ export type UserPickerProps = { initialSearchFilter?: string | null; }; -type UserForSelect = EntityForSelect & { - entityType: Entity.WorkspaceMember; -}; - export const UserPicker = ({ userId, onSubmit, diff --git a/front/src/modules/workspace-member/types/WorkspaceMember.ts b/front/src/modules/workspace-member/types/WorkspaceMember.ts index 617443bff8..5ed523b57c 100644 --- a/front/src/modules/workspace-member/types/WorkspaceMember.ts +++ b/front/src/modules/workspace-member/types/WorkspaceMember.ts @@ -1,6 +1,11 @@ +export type ColorScheme = 'Dark' | 'Light' | 'System'; + export type WorkspaceMember = { id: string; firstName: string; lastName: string; - avatarUrl: string; + avatarUrl: string | null; + locale: string; + colorScheme: ColorScheme; + allowImpersonation: boolean; }; diff --git a/front/src/pages/auth/CreateProfile.tsx b/front/src/pages/auth/CreateProfile.tsx index ad1ef45dcf..3605938379 100644 --- a/front/src/pages/auth/CreateProfile.tsx +++ b/front/src/pages/auth/CreateProfile.tsx @@ -1,7 +1,6 @@ import { useCallback } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRecoilState } from 'recoil'; @@ -10,8 +9,8 @@ import { z } from 'zod'; import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; -import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { H2Title } from '@/ui/display/typography/components/H2Title'; @@ -19,8 +18,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; import { MainButton } from '@/ui/input/button/components/MainButton'; import { TextInput } from '@/ui/input/components/TextInput'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { useUpdateUserMutation } from '~/generated/graphql'; const StyledContentContainer = styled.div` width: 100%; @@ -57,10 +54,14 @@ export const CreateProfile = () => { const { enqueueSnackBar } = useSnackBar(); - const [currentUser] = useRecoilState(currentUserState); - const [currentWorkspaceMember] = useRecoilState(currentWorkspaceMemberState); + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + currentWorkspaceMemberState, + ); - const [updateUser] = useUpdateUserMutation(); + const { updateOneObject, objectNotFoundInMetadata } = + useUpdateOneObjectRecord({ + objectNameSingular: 'workspaceMemberV2', + }); // Form const { @@ -80,31 +81,37 @@ export const CreateProfile = () => { const onSubmit: SubmitHandler
= useCallback( async (data) => { try { - if (!currentUser?.id) { + if (!currentWorkspaceMember?.id) { throw new Error('User is not logged in'); } if (!data.firstName || !data.lastName) { throw new Error('First name or last name is missing'); } + if (!updateOneObject || objectNotFoundInMetadata) { + throw new Error('Object not found in metadata'); + } - const result = await updateUser({ - variables: { - where: { - id: currentUser?.id, - }, - data: { - firstName: data.firstName, - lastName: data.lastName, - }, + const result = await updateOneObject({ + idToUpdate: currentWorkspaceMember?.id, + input: { + firstName: data.firstName, + lastName: data.lastName, }, - refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], - awaitRefetchQueries: true, }); - if (result.errors || !result.data?.updateUser) { - throw result.errors; + if (result.errors || !result.data?.updateWorkspaceMemberV2) { + throw result.errors ?? new Error('Unknown error'); } + setCurrentWorkspaceMember( + (current) => + ({ + ...current, + firstName: data.firstName, + lastName: data.lastName, + } as any), + ); + navigate('/'); } catch (error: any) { enqueueSnackBar(error?.message, { @@ -112,7 +119,14 @@ export const CreateProfile = () => { }); } }, - [currentUser?.id, enqueueSnackBar, navigate, updateUser], + [ + currentWorkspaceMember?.id, + enqueueSnackBar, + navigate, + objectNotFoundInMetadata, + setCurrentWorkspaceMember, + updateOneObject, + ], ); useScopedHotkeys( diff --git a/front/src/pages/auth/CreateWorkspace.tsx b/front/src/pages/auth/CreateWorkspace.tsx index 78810b22f9..862e297edf 100644 --- a/front/src/pages/auth/CreateWorkspace.tsx +++ b/front/src/pages/auth/CreateWorkspace.tsx @@ -1,7 +1,6 @@ import { useCallback } from 'react'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -15,7 +14,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; import { MainButton } from '@/ui/input/button/components/MainButton'; import { TextInput } from '@/ui/input/components/TextInput'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { useUpdateWorkspaceMutation } from '~/generated/graphql'; const StyledContentContainer = styled.div` @@ -69,8 +67,6 @@ export const CreateWorkspace = () => { displayName: data.name, }, }, - refetchQueries: [getOperationName(GET_CURRENT_USER) ?? ''], - awaitRefetchQueries: true, }); if (result.errors || !result.data?.updateWorkspace) { diff --git a/server/src/tenant-manager/standard-objects/workspace-member.ts b/server/src/tenant-manager/standard-objects/workspace-member.ts index 4f5d650045..4d9d27bbe1 100644 --- a/server/src/tenant-manager/standard-objects/workspace-member.ts +++ b/server/src/tenant-manager/standard-objects/workspace-member.ts @@ -89,6 +89,20 @@ const workspaceMemberMetadata = { icon: 'IconLanguage', isNullable: false, }, + { + isCustom: false, + isActive: true, + type: FieldMetadataType.TEXT, + name: 'avatarUrl', + label: 'Avatar Url', + targetColumnMap: { + value: 'avatarUrl', + }, + description: 'Workspace member avatar', + icon: 'IconFileUpload', + isNullable: true, + isSystem: false, + }, // Relations { isCustom: false,