feat: onboarding & profile edition (#507)

* feat: wip onboarding

* fix: generate graphql front

* wip: onboarding

* feat: login/register and edit profile

* fix: unused import

* fix: test

* Use DEBUG_MODE instead of STAGE and mute typescript depth exceed errors

* Fix seeds

* Fix onboarding when coming from google

* Fix

* Fix lint

* Fix ci

* Fix tests

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2023-07-07 02:05:15 +02:00
committed by GitHub
parent 0b7a023f3d
commit 1144bd13ed
141 changed files with 2660 additions and 962 deletions

View File

@ -1,56 +0,0 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
import { useIsLogged } from '../hooks/useIsLogged';
const EmptyContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
justify-content: center;
width: 100%;
`;
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const FadeInStyle = styled.div`
animation: ${fadeIn} 1s forwards;
opacity: 0;
`;
export function RequireAuth({
children,
}: {
children: JSX.Element;
}): JSX.Element {
const navigate = useNavigate();
const isLogged = useIsLogged();
useEffect(() => {
if (!isLogged) {
navigate('/auth');
}
}, [isLogged, navigate]);
if (!isLogged) {
return (
<EmptyContainer>
<FadeInStyle>
Please hold on a moment, we're directing you to our login page...
</FadeInStyle>
</EmptyContainer>
);
}
return children;
}

View File

@ -0,0 +1,83 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
import { useOnboardingStatus } from '../hooks/useOnboardingStatus';
import { OnboardingStatus } from '../utils/getOnboardingStatus';
const EmptyContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
justify-content: center;
width: 100%;
`;
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const FadeInStyle = styled.div`
animation: ${fadeIn} 1s forwards;
opacity: 0;
`;
export function RequireOnboarded({
children,
}: {
children: JSX.Element;
}): JSX.Element {
const navigate = useNavigate();
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
captureHotkeyTypeInFocusState,
);
const onboardingStatus = useOnboardingStatus();
useEffect(() => {
if (onboardingStatus === OnboardingStatus.OngoingUserCreation) {
navigate('/auth');
} else if (onboardingStatus === OnboardingStatus.OngoingWorkspaceCreation) {
navigate('/auth/create/workspace');
} else if (onboardingStatus === OnboardingStatus.OngoingProfileCreation) {
navigate('/auth/create/profile');
}
}, [onboardingStatus, navigate]);
useEffect(() => {
if (onboardingStatus === OnboardingStatus.Completed) {
setCaptureHotkeyTypeInFocus(false);
}
}, [setCaptureHotkeyTypeInFocus, onboardingStatus]);
if (onboardingStatus !== OnboardingStatus.Completed) {
return (
<EmptyContainer>
<FadeInStyle>
{onboardingStatus === OnboardingStatus.OngoingUserCreation && (
<div>
Please hold on a moment, we're directing you to our login page...
</div>
)}
{onboardingStatus !== OnboardingStatus.OngoingUserCreation && (
<div>
Please hold on a moment, we're directing you to our onboarding
flow...
</div>
)}
</FadeInStyle>
</EmptyContainer>
);
}
return children;
}

View File

@ -3,7 +3,8 @@ import { useNavigate } from 'react-router-dom';
import { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
import { useIsLogged } from '../hooks/useIsLogged';
import { useOnboardingStatus } from '../hooks/useOnboardingStatus';
import { OnboardingStatus } from '../utils/getOnboardingStatus';
const EmptyContainer = styled.div`
align-items: center;
@ -27,22 +28,22 @@ const FadeInStyle = styled.div`
opacity: 0;
`;
export function RequireNotAuth({
export function RequireOnboarding({
children,
}: {
children: JSX.Element;
}): JSX.Element {
const navigate = useNavigate();
const isLogged = useIsLogged();
const onboardingStatus = useOnboardingStatus();
useEffect(() => {
if (isLogged) {
if (onboardingStatus === OnboardingStatus.Completed) {
navigate('/');
}
}, [isLogged, navigate]);
}, [navigate, onboardingStatus]);
if (isLogged) {
if (onboardingStatus === OnboardingStatus.Completed) {
return (
<EmptyContainer>
<FadeInStyle>

View File

@ -1,13 +0,0 @@
import jwt from 'jwt-decode';
import { AuthTokenPair, useGetCurrentUserQuery } from '~/generated/graphql';
export function useFetchCurrentUser(tokenPair: AuthTokenPair | null) {
const userId = tokenPair?.accessToken.token
? jwt<{ sub: string }>(tokenPair.accessToken.token).sub
: null;
const { data } = useGetCurrentUserQuery({
variables: { uuid: userId },
});
return data?.users?.[0];
}

View File

@ -0,0 +1,20 @@
import { useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { useIsLogged } from '../hooks/useIsLogged';
import { currentUserState } from '../states/currentUserState';
import {
getOnboardingStatus,
OnboardingStatus,
} from '../utils/getOnboardingStatus';
export function useOnboardingStatus(): OnboardingStatus {
const [currentUser] = useRecoilState(currentUserState);
const isLoggedIn = useIsLogged();
const onboardingStatus = useMemo(
() => getOnboardingStatus(isLoggedIn, currentUser),
[currentUser, isLoggedIn],
);
return onboardingStatus;
}

View File

@ -1,14 +1,8 @@
import { atom } from 'recoil';
import { User, Workspace, WorkspaceMember } from '~/generated/graphql';
import { GetCurrentUserQuery } from '~/generated/graphql';
type CurrentUser = Pick<User, 'id' | 'email' | 'displayName'> & {
workspaceMember?:
| (Pick<WorkspaceMember, 'id'> & {
workspace: Pick<Workspace, 'id' | 'displayName' | 'logo'>;
})
| null;
};
export type CurrentUser = GetCurrentUserQuery['currentUser'];
export const currentUserState = atom<CurrentUser | null>({
key: 'currentUserState',

View File

@ -0,0 +1,25 @@
import { CurrentUser } from '../states/currentUserState';
export enum OnboardingStatus {
OngoingUserCreation = 'ongoing_user_creation',
OngoingWorkspaceCreation = 'ongoing_workspace_creation',
OngoingProfileCreation = 'ongoing_profile_creation',
Completed = 'completed',
}
export function getOnboardingStatus(
isLoggedIn: boolean,
currentUser: CurrentUser | null,
) {
if (!isLoggedIn || !currentUser) {
return OnboardingStatus.OngoingUserCreation;
}
if (!currentUser.workspaceMember?.workspace.displayName) {
return OnboardingStatus.OngoingWorkspaceCreation;
}
if (!currentUser.firstName || !currentUser.lastName) {
return OnboardingStatus.OngoingProfileCreation;
}
return OnboardingStatus.Completed;
}