V2 onboarding (#2543)

* fix cannot query avatarUrl

* create workspace working

* fix bugs related to refetch queries

* onboarding working

* updated dependency array

* improve error handling

* update types, remove as any, remove console logs

* small fix
This commit is contained in:
bosiraphael 2023-11-16 17:09:10 +01:00 committed by GitHub
parent b1b6bbe7d3
commit 0ae9373532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 127 additions and 120 deletions

View File

@ -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,

View File

@ -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<CurrentWorkspaceMember | null>({
export const currentWorkspaceMemberState = atom<WorkspaceMember | null>({
key: 'currentWorkspaceMemberState',
default: null,
});

View File

@ -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) {

View File

@ -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<T> = NonNullable<
{
[K in keyof T]: K extends '__typename'
? never
: T[K] extends string | undefined | null
? K
: never;
}[keyof T]
>;
type ExtractEntityTypeFromQueryResponse<T> = T extends {
searchResults: Array<infer U>;
}
? U
: never;
type SearchFilter = { fieldNames: string[]; filter: string | number };
export type OrderBy =

View File

@ -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);

View File

@ -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 (

View File

@ -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',

View File

@ -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,
});
},
});
};

View File

@ -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 = ({
<StyledContainer>
<StyledCardContainer>
<ColorSchemeCard
onClick={() => onChange(ColorScheme.Light)}
onClick={() => onChange('Light')}
variant="light"
selected={value === ColorScheme.Light}
selected={value === 'Light'}
/>
<StyledLabel>Light</StyledLabel>
</StyledCardContainer>
<StyledCardContainer>
<ColorSchemeCard
onClick={() => onChange(ColorScheme.Dark)}
onClick={() => onChange('Dark')}
variant="dark"
selected={value === ColorScheme.Dark}
selected={value === 'Dark'}
/>
<StyledLabel>Dark</StyledLabel>
</StyledCardContainer>
<StyledCardContainer>
<ColorSchemeCard
onClick={() => onChange(ColorScheme.System)}
onClick={() => onChange('System')}
variant="system"
selected={value === ColorScheme.System}
selected={value === 'System'}
/>
<StyledLabel>System settings</StyledLabel>
</StyledCardContainer>

View File

@ -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 =

View File

@ -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<User, 'email' | 'supportUserHash'>,
currentWorkspaceMember: Pick<
CurrentWorkspaceMember,
'firstName' | 'lastName'
>,
currentWorkspaceMember: Pick<WorkspaceMember, 'firstName' | 'lastName'>,
) => {
const url = 'https://chat-assets.frontapp.com/v1/chat.bundle.js';
const script = document.querySelector(`script[src="${url}"]`);

View File

@ -82,8 +82,6 @@ type RecordTableProps = {
export const RecordTable = ({ updateEntityMutation }: RecordTableProps) => {
const tableBodyRef = useRef<HTMLDivElement>(null);
console.log('record table');
const {
leaveTableFocus,
setRowSelectedState,

View File

@ -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) => {

View File

@ -18,10 +18,6 @@ export type UserPickerProps = {
initialSearchFilter?: string | null;
};
type UserForSelect = EntityForSelect & {
entityType: Entity.WorkspaceMember;
};
export const UserPicker = ({
userId,
onSubmit,

View File

@ -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;
};

View File

@ -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<Form> = 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(

View File

@ -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) {

View File

@ -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,