feat: multi-workspace (frontend) (#4232)
* select workspace component * generateJWT mutation * workspaces state and hooks * requested changes * mutation fix * requested changes * user workpsace delete call * migration to drop and createt user workspace * revert select props * add DropdownMenu * seperate multi-workspace dropdown as component * Signup button displayed accurately * update seed data for multi-workspace * lint fix * lint fix * css fix * lint fix * state fix * isDefined check * refactor * add default workspace constants for logo and name * update migration * lint fix * isInviteMode check on sign-in/up * removeWorkspaceMember mutation * import fixes * prop name fix * backfill migration * handle edge cases * refactor * remove migration query * delete user on no-workspace found condition * emit workspaceMember.deleted * Fix event class and unrelated fix linked to a previously missing dependency * Edit migration (I did it in prod manually) * Revert changes * Fix tests * Fix conflicts --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GENERATE_JWT = gql`
|
||||
mutation GenerateJWT($workspaceId: String!) {
|
||||
generateJWT(workspaceId: $workspaceId) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
|
||||
import { workspacesState } from '@/auth/states/workspaces';
|
||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
@ -40,6 +41,7 @@ export const useAuth = () => {
|
||||
|
||||
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
||||
const setIsVerifyPendingState = useSetRecoilState(isVerifyPendingState);
|
||||
const setWorkspaces = useSetRecoilState(workspacesState);
|
||||
|
||||
const [challenge] = useChallengeMutation();
|
||||
const [signUp] = useSignUpMutation();
|
||||
@ -101,6 +103,15 @@ export const useAuth = () => {
|
||||
}
|
||||
const workspace = user.defaultWorkspace ?? null;
|
||||
setCurrentWorkspace(workspace);
|
||||
if (isDefined(verifyResult.data?.verify.user.workspaces)) {
|
||||
const validWorkspaces = verifyResult.data?.verify.user.workspaces
|
||||
.filter(
|
||||
({ workspace }) => workspace !== null && workspace !== undefined,
|
||||
)
|
||||
.map((validWorkspace) => validWorkspace.workspace!);
|
||||
|
||||
setWorkspaces(validWorkspaces);
|
||||
}
|
||||
return {
|
||||
user,
|
||||
workspaceMember,
|
||||
@ -114,6 +125,7 @@ export const useAuth = () => {
|
||||
setCurrentUser,
|
||||
setCurrentWorkspaceMember,
|
||||
setCurrentWorkspace,
|
||||
setWorkspaces,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@ export const SignInUpForm = () => {
|
||||
const { form } = useSignInUpForm();
|
||||
|
||||
const {
|
||||
isInviteMode,
|
||||
signInUpStep,
|
||||
signInUpMode,
|
||||
continueWithCredentials,
|
||||
@ -89,14 +90,14 @@ export const SignInUpForm = () => {
|
||||
}, [signInUpMode, signInUpStep]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (signInUpMode === SignInUpMode.Invite) {
|
||||
if (isInviteMode) {
|
||||
return `Join ${workspace?.displayName ?? ''} team`;
|
||||
}
|
||||
|
||||
return signInUpMode === SignInUpMode.SignIn
|
||||
? 'Sign in to Twenty'
|
||||
: 'Sign up to Twenty';
|
||||
}, [signInUpMode, workspace?.displayName]);
|
||||
}, [signInUpMode, workspace?.displayName, isInviteMode]);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ import { useAuth } from '../../hooks/useAuth';
|
||||
export enum SignInUpMode {
|
||||
SignIn = 'sign-in',
|
||||
SignUp = 'sign-up',
|
||||
Invite = 'invite',
|
||||
}
|
||||
|
||||
export enum SignInUpStep {
|
||||
@ -33,15 +32,13 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
|
||||
const { navigateAfterSignInUp } = useNavigateAfterSignInUp();
|
||||
|
||||
const [isInviteMode] = useState(() => isMatchingLocation(AppPath.Invite));
|
||||
|
||||
const [signInUpStep, setSignInUpStep] = useState<SignInUpStep>(
|
||||
SignInUpStep.Init,
|
||||
);
|
||||
|
||||
const [signInUpMode, setSignInUpMode] = useState<SignInUpMode>(() => {
|
||||
if (isMatchingLocation(AppPath.Invite)) {
|
||||
return SignInUpMode.Invite;
|
||||
}
|
||||
|
||||
return isMatchingLocation(AppPath.SignIn)
|
||||
? SignInUpMode.SignIn
|
||||
: SignInUpMode.SignUp;
|
||||
@ -72,24 +69,14 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data?.checkUserExists.exists) {
|
||||
isMatchingLocation(AppPath.Invite)
|
||||
? setSignInUpMode(SignInUpMode.Invite)
|
||||
: setSignInUpMode(SignInUpMode.SignIn);
|
||||
setSignInUpMode(SignInUpMode.SignIn);
|
||||
} else {
|
||||
isMatchingLocation(AppPath.Invite)
|
||||
? setSignInUpMode(SignInUpMode.Invite)
|
||||
: setSignInUpMode(SignInUpMode.SignUp);
|
||||
setSignInUpMode(SignInUpMode.SignUp);
|
||||
}
|
||||
setSignInUpStep(SignInUpStep.Password);
|
||||
},
|
||||
});
|
||||
}, [
|
||||
isMatchingLocation,
|
||||
setSignInUpStep,
|
||||
checkUserExistsQuery,
|
||||
form,
|
||||
setSignInUpMode,
|
||||
]);
|
||||
}, [setSignInUpStep, checkUserExistsQuery, form, setSignInUpMode]);
|
||||
|
||||
const submitCredentials: SubmitHandler<Form> = useCallback(
|
||||
async (data) => {
|
||||
@ -102,7 +89,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
workspace: currentWorkspace,
|
||||
workspaceMember: currentWorkspaceMember,
|
||||
} =
|
||||
signInUpMode === SignInUpMode.SignIn
|
||||
signInUpMode === SignInUpMode.SignIn && !isInviteMode
|
||||
? await signInWithCredentials(
|
||||
data.email.toLowerCase().trim(),
|
||||
data.password,
|
||||
@ -122,6 +109,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
},
|
||||
[
|
||||
signInUpMode,
|
||||
isInviteMode,
|
||||
signInWithCredentials,
|
||||
signUpWithCredentials,
|
||||
workspaceInviteHash,
|
||||
@ -156,6 +144,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
);
|
||||
|
||||
return {
|
||||
isInviteMode,
|
||||
signInUpStep,
|
||||
signInUpMode,
|
||||
continueWithCredentials,
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { createState } from '@/ui/utilities/state/utils/createState';
|
||||
import { Workspace } from '~/generated/graphql';
|
||||
|
||||
export type Workspaces = Pick<Workspace, 'id' | 'logo' | 'displayName'>;
|
||||
|
||||
export const workspacesState = createState<Workspaces[] | null>({
|
||||
key: 'workspacesState',
|
||||
defaultValue: [],
|
||||
});
|
||||
Reference in New Issue
Block a user