Prefill workspace invitation email, fixes #7174 --------- Co-authored-by: ad-elias <elias@autodiligence.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
This commit is contained in:
@ -10,7 +10,7 @@ import { useAuth } from '@/auth/hooks/useAuth';
|
|||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { billingState } from '@/client-config/states/billingState';
|
import { billingState } from '@/client-config/states/billingState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||||
|
|
||||||
import { email, mocks, password, results, token } from '../__mocks__/useAuth';
|
import { email, mocks, password, results, token } from '../__mocks__/useAuth';
|
||||||
@ -78,7 +78,9 @@ describe('useAuth', () => {
|
|||||||
const icons = useRecoilValue(iconsState);
|
const icons = useRecoilValue(iconsState);
|
||||||
const authProviders = useRecoilValue(authProvidersState);
|
const authProviders = useRecoilValue(authProvidersState);
|
||||||
const billing = useRecoilValue(billingState);
|
const billing = useRecoilValue(billingState);
|
||||||
const isSignInPrefilled = useRecoilValue(isSignInPrefilledState);
|
const isDeveloperDefaultSignInPrefilled = useRecoilValue(
|
||||||
|
isDeveloperDefaultSignInPrefilledState,
|
||||||
|
);
|
||||||
const supportChat = useRecoilValue(supportChatState);
|
const supportChat = useRecoilValue(supportChatState);
|
||||||
const isDebugMode = useRecoilValue(isDebugModeState);
|
const isDebugMode = useRecoilValue(isDebugModeState);
|
||||||
return {
|
return {
|
||||||
@ -88,7 +90,7 @@ describe('useAuth', () => {
|
|||||||
icons,
|
icons,
|
||||||
authProviders,
|
authProviders,
|
||||||
billing,
|
billing,
|
||||||
isSignInPrefilled,
|
isDeveloperDefaultSignInPrefilled,
|
||||||
supportChat,
|
supportChat,
|
||||||
isDebugMode,
|
isDebugMode,
|
||||||
},
|
},
|
||||||
@ -119,7 +121,7 @@ describe('useAuth', () => {
|
|||||||
sso: false,
|
sso: false,
|
||||||
});
|
});
|
||||||
expect(state.billing).toBeNull();
|
expect(state.billing).toBeNull();
|
||||||
expect(state.isSignInPrefilled).toBe(false);
|
expect(state.isDeveloperDefaultSignInPrefilled).toBe(false);
|
||||||
expect(state.supportChat).toEqual({
|
expect(state.supportChat).toEqual({
|
||||||
supportDriver: 'none',
|
supportDriver: 'none',
|
||||||
supportFrontChatId: null,
|
supportFrontChatId: null,
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { billingState } from '@/client-config/states/billingState';
|
|||||||
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
||||||
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
|
||||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||||
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
@ -32,6 +31,7 @@ import {
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
||||||
|
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||||
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
|
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
|
||||||
@ -78,8 +78,8 @@ export const useAuth = () => {
|
|||||||
.getLoadable(authProvidersState)
|
.getLoadable(authProvidersState)
|
||||||
.getValue();
|
.getValue();
|
||||||
const billing = snapshot.getLoadable(billingState).getValue();
|
const billing = snapshot.getLoadable(billingState).getValue();
|
||||||
const isSignInPrefilled = snapshot
|
const isDeveloperDefaultSignInPrefilled = snapshot
|
||||||
.getLoadable(isSignInPrefilledState)
|
.getLoadable(isDeveloperDefaultSignInPrefilledState)
|
||||||
.getValue();
|
.getValue();
|
||||||
const supportChat = snapshot.getLoadable(supportChatState).getValue();
|
const supportChat = snapshot.getLoadable(supportChatState).getValue();
|
||||||
const isDebugMode = snapshot.getLoadable(isDebugModeState).getValue();
|
const isDebugMode = snapshot.getLoadable(isDebugModeState).getValue();
|
||||||
@ -96,7 +96,10 @@ export const useAuth = () => {
|
|||||||
set(iconsState, iconsValue);
|
set(iconsState, iconsValue);
|
||||||
set(authProvidersState, authProvidersValue);
|
set(authProvidersState, authProvidersValue);
|
||||||
set(billingState, billing);
|
set(billingState, billing);
|
||||||
set(isSignInPrefilledState, isSignInPrefilled);
|
set(
|
||||||
|
isDeveloperDefaultSignInPrefilledState,
|
||||||
|
isDeveloperDefaultSignInPrefilled,
|
||||||
|
);
|
||||||
set(supportChatState, supportChat);
|
set(supportChatState, supportChat);
|
||||||
set(isDebugModeState, isDebugMode);
|
set(isDebugModeState, isDebugMode);
|
||||||
set(captchaProviderState, captchaProvider);
|
set(captchaProviderState, captchaProvider);
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const validationSchema = z
|
export const validationSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -20,7 +22,12 @@ export const validationSchema = z
|
|||||||
|
|
||||||
export type Form = z.infer<typeof validationSchema>;
|
export type Form = z.infer<typeof validationSchema>;
|
||||||
export const useSignInUpForm = () => {
|
export const useSignInUpForm = () => {
|
||||||
const isSignInPrefilled = useRecoilValue(isSignInPrefilledState);
|
const isDeveloperDefaultSignInPrefilled = useRecoilValue(
|
||||||
|
isDeveloperDefaultSignInPrefilledState,
|
||||||
|
);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const invitationPrefilledEmail = searchParams.get('email');
|
||||||
|
|
||||||
const form = useForm<Form>({
|
const form = useForm<Form>({
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -33,10 +40,12 @@ export const useSignInUpForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSignInPrefilled === true) {
|
if (isDefined(invitationPrefilledEmail)) {
|
||||||
|
form.setValue('email', invitationPrefilledEmail);
|
||||||
|
} else if (isDeveloperDefaultSignInPrefilled === true) {
|
||||||
form.setValue('email', 'tim@apple.dev');
|
form.setValue('email', 'tim@apple.dev');
|
||||||
form.setValue('password', 'Applecar2025');
|
form.setValue('password', 'Applecar2025');
|
||||||
}
|
}
|
||||||
}, [form, isSignInPrefilled]);
|
}, [form, isDeveloperDefaultSignInPrefilled, invitationPrefilledEmail]);
|
||||||
return { form: form };
|
return { form: form };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionId
|
|||||||
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
||||||
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
|
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||||
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
|
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
|
||||||
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
|
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
|
||||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||||
@ -20,7 +20,9 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
const setIsDebugMode = useSetRecoilState(isDebugModeState);
|
const setIsDebugMode = useSetRecoilState(isDebugModeState);
|
||||||
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
|
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
|
||||||
|
|
||||||
const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState);
|
const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState(
|
||||||
|
isDeveloperDefaultSignInPrefilledState,
|
||||||
|
);
|
||||||
const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState);
|
const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState);
|
||||||
|
|
||||||
const setBilling = useSetRecoilState(billingState);
|
const setBilling = useSetRecoilState(billingState);
|
||||||
@ -76,7 +78,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
});
|
});
|
||||||
setIsDebugMode(data?.clientConfig.debugMode);
|
setIsDebugMode(data?.clientConfig.debugMode);
|
||||||
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
|
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
|
||||||
setIsSignInPrefilled(data?.clientConfig.signInPrefilled);
|
setIsDeveloperDefaultSignInPrefilled(data?.clientConfig.signInPrefilled);
|
||||||
setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
|
setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
|
||||||
|
|
||||||
setBilling(data?.clientConfig.billing);
|
setBilling(data?.clientConfig.billing);
|
||||||
@ -99,7 +101,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
data,
|
data,
|
||||||
setAuthProviders,
|
setAuthProviders,
|
||||||
setIsDebugMode,
|
setIsDebugMode,
|
||||||
setIsSignInPrefilled,
|
setIsDeveloperDefaultSignInPrefilled,
|
||||||
setIsSignUpDisabled,
|
setIsSignUpDisabled,
|
||||||
setSupportChat,
|
setSupportChat,
|
||||||
setBilling,
|
setBilling,
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const isDeveloperDefaultSignInPrefilledState = createState<boolean>({
|
||||||
|
key: 'isDeveloperDefaultSignInPrefilledState',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const isSignInPrefilledState = createState<boolean>({
|
|
||||||
key: 'isSignInPrefilledState',
|
|
||||||
defaultValue: false,
|
|
||||||
});
|
|
||||||
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { Button, IconSend } from 'twenty-ui';
|
import { Button, IconSend } from 'twenty-ui';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@ -105,12 +104,6 @@ export const WorkspaceInviteTeam = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (e.key === Key.Enter) {
|
|
||||||
submit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { isSubmitSuccessful, errors } = formState;
|
const { isSubmitSuccessful, errors } = formState;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -133,7 +126,6 @@ export const WorkspaceInviteTeam = () => {
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -229,6 +229,7 @@ export class WorkspaceInvitationService {
|
|||||||
|
|
||||||
if (invitation.value.isPersonalInvitation) {
|
if (invitation.value.isPersonalInvitation) {
|
||||||
link.searchParams.set('inviteToken', invitation.value.appToken.value);
|
link.searchParams.set('inviteToken', invitation.value.appToken.value);
|
||||||
|
link.searchParams.set('email', invitation.value.email);
|
||||||
}
|
}
|
||||||
const emailData = {
|
const emailData = {
|
||||||
link: link.toString(),
|
link: link.toString(),
|
||||||
|
|||||||
Reference in New Issue
Block a user