mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 12:02:10 +03:00
review(front): refacto url-manager (#8861)
Replace https://github.com/twentyhq/twenty/pull/8855
This commit is contained in:
parent
7ab00a4c82
commit
081ecbcfaf
@ -7,5 +7,6 @@ GENERATE_SOURCEMAP=false
|
|||||||
# VITE_DISABLE_TYPESCRIPT_CHECKER=true
|
# VITE_DISABLE_TYPESCRIPT_CHECKER=true
|
||||||
# VITE_DISABLE_ESLINT_CHECKER=true
|
# VITE_DISABLE_ESLINT_CHECKER=true
|
||||||
# VITE_ENABLE_SSL=false
|
# VITE_ENABLE_SSL=false
|
||||||
|
# VITE_HOST=localhost.com
|
||||||
# SSL_KEY_PATH="./certs/your-cert.key"
|
# SSL_KEY_PATH="./certs/your-cert.key"
|
||||||
# SSL_CERT_PATH="./certs/your-cert.crt"
|
# SSL_CERT_PATH="./certs/your-cert.crt"
|
@ -14,6 +14,7 @@ import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/i
|
|||||||
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';
|
||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
|
||||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||||
<MockedProvider mocks={mocks} addTypename={false}>
|
<MockedProvider mocks={mocks} addTypename={false}>
|
||||||
@ -83,6 +84,9 @@ describe('useAuth', () => {
|
|||||||
);
|
);
|
||||||
const supportChat = useRecoilValue(supportChatState);
|
const supportChat = useRecoilValue(supportChatState);
|
||||||
const isDebugMode = useRecoilValue(isDebugModeState);
|
const isDebugMode = useRecoilValue(isDebugModeState);
|
||||||
|
const isMultiWorkspaceEnabled = useRecoilValue(
|
||||||
|
isMultiWorkspaceEnabledState,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...useAuth(),
|
...useAuth(),
|
||||||
client,
|
client,
|
||||||
@ -93,6 +97,7 @@ describe('useAuth', () => {
|
|||||||
isDeveloperDefaultSignInPrefilled,
|
isDeveloperDefaultSignInPrefilled,
|
||||||
supportChat,
|
supportChat,
|
||||||
isDebugMode,
|
isDebugMode,
|
||||||
|
isMultiWorkspaceEnabled,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
snapshot_UNSTABLE,
|
snapshot_UNSTABLE,
|
||||||
useGotoRecoilSnapshot,
|
useGotoRecoilSnapshot,
|
||||||
useRecoilCallback,
|
useRecoilCallback,
|
||||||
useRecoilValue,
|
|
||||||
useSetRecoilState,
|
useSetRecoilState,
|
||||||
} from 'recoil';
|
} from 'recoil';
|
||||||
import { iconsState } from 'twenty-ui';
|
import { iconsState } from 'twenty-ui';
|
||||||
@ -42,18 +41,15 @@ import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDa
|
|||||||
import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat';
|
import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat';
|
||||||
import { currentUserState } from '../states/currentUserState';
|
import { currentUserState } from '../states/currentUserState';
|
||||||
import { tokenPairState } from '../states/tokenPairState';
|
import { tokenPairState } from '../states/tokenPairState';
|
||||||
import { lastAuthenticateWorkspaceState } from '@/auth/states/lastAuthenticateWorkspaceState';
|
|
||||||
|
|
||||||
import { urlManagerState } from '@/url-manager/states/url-manager.state';
|
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
||||||
|
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||||
const setCurrentUser = useSetRecoilState(currentUserState);
|
const setCurrentUser = useSetRecoilState(currentUserState);
|
||||||
const urlManager = useRecoilValue(urlManagerState);
|
|
||||||
const setLastAuthenticateWorkspaceState = useSetRecoilState(
|
|
||||||
lastAuthenticateWorkspaceState,
|
|
||||||
);
|
|
||||||
const setCurrentWorkspaceMember = useSetRecoilState(
|
const setCurrentWorkspaceMember = useSetRecoilState(
|
||||||
currentWorkspaceMemberState,
|
currentWorkspaceMemberState,
|
||||||
);
|
);
|
||||||
@ -68,7 +64,12 @@ export const useAuth = () => {
|
|||||||
const [challenge] = useChallengeMutation();
|
const [challenge] = useChallengeMutation();
|
||||||
const [signUp] = useSignUpMutation();
|
const [signUp] = useSignUpMutation();
|
||||||
const [verify] = useVerifyMutation();
|
const [verify] = useVerifyMutation();
|
||||||
const { isTwentyWorkspaceSubdomain, getWorkspaceSubdomain } = useUrlManager();
|
const { isOnAWorkspaceSubdomain } =
|
||||||
|
useIsCurrentLocationOnAWorkspaceSubdomain();
|
||||||
|
const { workspaceSubdomain } = useReadWorkspaceSubdomainFromCurrentLocation();
|
||||||
|
|
||||||
|
const { setLastAuthenticateWorkspaceDomain } =
|
||||||
|
useLastAuthenticatedWorkspaceDomain();
|
||||||
const [checkUserExistsQuery, { data: checkUserExistsData }] =
|
const [checkUserExistsQuery, { data: checkUserExistsData }] =
|
||||||
useCheckUserExistsLazyQuery();
|
useCheckUserExistsLazyQuery();
|
||||||
|
|
||||||
@ -101,6 +102,9 @@ export const useAuth = () => {
|
|||||||
const isCurrentUserLoaded = snapshot
|
const isCurrentUserLoaded = snapshot
|
||||||
.getLoadable(isCurrentUserLoadedState)
|
.getLoadable(isCurrentUserLoadedState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
const isMultiWorkspaceEnabled = snapshot
|
||||||
|
.getLoadable(isMultiWorkspaceEnabledState)
|
||||||
|
.getValue();
|
||||||
const initialSnapshot = emptySnapshot.map(({ set }) => {
|
const initialSnapshot = emptySnapshot.map(({ set }) => {
|
||||||
set(iconsState, iconsValue);
|
set(iconsState, iconsValue);
|
||||||
set(authProvidersState, authProvidersValue);
|
set(authProvidersState, authProvidersValue);
|
||||||
@ -114,6 +118,7 @@ export const useAuth = () => {
|
|||||||
set(captchaProviderState, captchaProvider);
|
set(captchaProviderState, captchaProvider);
|
||||||
set(clientConfigApiStatusState, clientConfigApiStatus);
|
set(clientConfigApiStatusState, clientConfigApiStatus);
|
||||||
set(isCurrentUserLoadedState, isCurrentUserLoaded);
|
set(isCurrentUserLoadedState, isCurrentUserLoaded);
|
||||||
|
set(isMultiWorkspaceEnabledState, isMultiWorkspaceEnabled);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
goToRecoilSnapshot(initialSnapshot);
|
goToRecoilSnapshot(initialSnapshot);
|
||||||
@ -212,13 +217,11 @@ export const useAuth = () => {
|
|||||||
const workspace = user.defaultWorkspace ?? null;
|
const workspace = user.defaultWorkspace ?? null;
|
||||||
|
|
||||||
setCurrentWorkspace(workspace);
|
setCurrentWorkspace(workspace);
|
||||||
if (isDefined(workspace) && isTwentyWorkspaceSubdomain) {
|
|
||||||
setLastAuthenticateWorkspaceState({
|
if (isDefined(workspace) && isOnAWorkspaceSubdomain) {
|
||||||
id: workspace.id,
|
setLastAuthenticateWorkspaceDomain({
|
||||||
|
workspaceId: workspace.id,
|
||||||
subdomain: workspace.subdomain,
|
subdomain: workspace.subdomain,
|
||||||
cookieAttributes: {
|
|
||||||
domain: `.${urlManager.frontDomain}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,12 +248,11 @@ export const useAuth = () => {
|
|||||||
setTokenPair,
|
setTokenPair,
|
||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
setCurrentWorkspace,
|
setCurrentWorkspace,
|
||||||
isTwentyWorkspaceSubdomain,
|
isOnAWorkspaceSubdomain,
|
||||||
setCurrentWorkspaceMembers,
|
setCurrentWorkspaceMembers,
|
||||||
setCurrentWorkspaceMember,
|
setCurrentWorkspaceMember,
|
||||||
setDateTimeFormat,
|
setDateTimeFormat,
|
||||||
setLastAuthenticateWorkspaceState,
|
setLastAuthenticateWorkspaceDomain,
|
||||||
urlManager.frontDomain,
|
|
||||||
setWorkspaces,
|
setWorkspaces,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -340,15 +342,13 @@ export const useAuth = () => {
|
|||||||
params.workspacePersonalInviteToken,
|
params.workspacePersonalInviteToken,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const subdomain = getWorkspaceSubdomain;
|
if (isDefined(workspaceSubdomain)) {
|
||||||
|
url.searchParams.set('workspaceSubdomain', workspaceSubdomain);
|
||||||
if (isDefined(subdomain)) {
|
|
||||||
url.searchParams.set('workspaceSubdomain', subdomain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
},
|
},
|
||||||
[getWorkspaceSubdomain],
|
[workspaceSubdomain],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleGoogleLogin = useCallback(
|
const handleGoogleLogin = useCallback(
|
||||||
|
@ -30,8 +30,8 @@ import { useAuth } from '@/auth/hooks/useAuth';
|
|||||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||||
import { signInUpModeState } from '@/auth/states/signInUpModeState';
|
import { signInUpModeState } from '@/auth/states/signInUpModeState';
|
||||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
|
||||||
import { SignInUpMode } from '@/auth/types/signInUpMode';
|
import { SignInUpMode } from '@/auth/types/signInUpMode';
|
||||||
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
|
||||||
const StyledContentContainer = styled(motion.div)`
|
const StyledContentContainer = styled(motion.div)`
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||||
@ -53,8 +53,7 @@ export const SignInUpGlobalScopeForm = () => {
|
|||||||
const { signInWithMicrosoft } = useSignInWithMicrosoft();
|
const { signInWithMicrosoft } = useSignInWithMicrosoft();
|
||||||
const { checkUserExists } = useAuth();
|
const { checkUserExists } = useAuth();
|
||||||
const { readCaptchaToken } = useReadCaptchaToken();
|
const { readCaptchaToken } = useReadCaptchaToken();
|
||||||
const { redirectToWorkspace } = useUrlManager();
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
|
|
||||||
const setSignInUpStep = useSetRecoilState(signInUpStepState);
|
const setSignInUpStep = useSetRecoilState(signInUpStepState);
|
||||||
const [signInUpMode, setSignInUpMode] = useRecoilState(signInUpModeState);
|
const [signInUpMode, setSignInUpMode] = useRecoilState(signInUpModeState);
|
||||||
|
|
||||||
@ -97,7 +96,7 @@ export const SignInUpGlobalScopeForm = () => {
|
|||||||
isDefined(data?.checkUserExists.availableWorkspaces) &&
|
isDefined(data?.checkUserExists.availableWorkspaces) &&
|
||||||
data.checkUserExists.availableWorkspaces.length >= 1
|
data.checkUserExists.availableWorkspaces.length >= 1
|
||||||
) {
|
) {
|
||||||
return redirectToWorkspace(
|
return redirectToWorkspaceDomain(
|
||||||
data?.checkUserExists.availableWorkspaces[0].subdomain,
|
data?.checkUserExists.availableWorkspaces[0].subdomain,
|
||||||
pathname,
|
pathname,
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
import { act, renderHook } from '@testing-library/react';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { useEmailPasswordResetLinkMutation } from '~/generated/graphql';
|
||||||
|
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
|
|
||||||
|
// Mocks
|
||||||
|
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||||
|
jest.mock('~/generated/graphql');
|
||||||
|
|
||||||
|
describe('useHandleResetPassword', () => {
|
||||||
|
const enqueueSnackBarMock = jest.fn();
|
||||||
|
const emailPasswordResetLinkMock = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(useSnackBar as jest.Mock).mockReturnValue({
|
||||||
|
enqueueSnackBar: enqueueSnackBarMock,
|
||||||
|
});
|
||||||
|
(useEmailPasswordResetLinkMutation as jest.Mock).mockReturnValue([
|
||||||
|
emailPasswordResetLinkMock,
|
||||||
|
]);
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error message if email is invalid', async () => {
|
||||||
|
const { result } = renderHook(() => useHandleResetPassword());
|
||||||
|
await act(() => result.current.handleResetPassword('')());
|
||||||
|
|
||||||
|
expect(enqueueSnackBarMock).toHaveBeenCalledWith('Invalid email', {
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show success message if password reset link is sent', async () => {
|
||||||
|
emailPasswordResetLinkMock.mockResolvedValue({
|
||||||
|
data: { emailPasswordResetLink: { success: true } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useHandleResetPassword());
|
||||||
|
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||||
|
|
||||||
|
expect(enqueueSnackBarMock).toHaveBeenCalledWith(
|
||||||
|
'Password reset link has been sent to the email',
|
||||||
|
{ variant: SnackBarVariant.Success },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error message if sending reset link fails', async () => {
|
||||||
|
emailPasswordResetLinkMock.mockResolvedValue({
|
||||||
|
data: { emailPasswordResetLink: { success: false } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useHandleResetPassword());
|
||||||
|
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||||
|
|
||||||
|
expect(enqueueSnackBarMock).toHaveBeenCalledWith('There was some issue', {
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error message in case of request error', async () => {
|
||||||
|
const errorMessage = 'Network Error';
|
||||||
|
emailPasswordResetLinkMock.mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useHandleResetPassword());
|
||||||
|
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||||
|
|
||||||
|
expect(enqueueSnackBarMock).toHaveBeenCalledWith(errorMessage, {
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,73 @@
|
|||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { useGetAuthorizationUrlMutation } from '~/generated/graphql';
|
||||||
|
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||||
|
jest.mock('~/generated/graphql');
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
const mockEnqueueSnackBar = jest.fn();
|
||||||
|
const mockGetAuthorizationUrlMutation = jest.fn();
|
||||||
|
|
||||||
|
// Mock return values
|
||||||
|
(useSnackBar as jest.Mock).mockReturnValue({
|
||||||
|
enqueueSnackBar: mockEnqueueSnackBar,
|
||||||
|
});
|
||||||
|
(useGetAuthorizationUrlMutation as jest.Mock).mockReturnValue([
|
||||||
|
mockGetAuthorizationUrlMutation,
|
||||||
|
]);
|
||||||
|
|
||||||
|
describe('useSSO', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getAuthorizationUrlForSSO with correct parameters', async () => {
|
||||||
|
const { result } = renderHook(() => useSSO());
|
||||||
|
const identityProviderId = 'test-id';
|
||||||
|
|
||||||
|
mockGetAuthorizationUrlMutation.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
getAuthorizationUrl: {
|
||||||
|
authorizationURL: 'http://example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await result.current.getAuthorizationUrlForSSO({ identityProviderId });
|
||||||
|
|
||||||
|
expect(mockGetAuthorizationUrlMutation).toHaveBeenCalledWith({
|
||||||
|
variables: { input: { identityProviderId } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enqueue error snackbar when URL retrieval fails', async () => {
|
||||||
|
const { result } = renderHook(() => useSSO());
|
||||||
|
const identityProviderId = 'test-id';
|
||||||
|
|
||||||
|
mockGetAuthorizationUrlMutation.mockResolvedValueOnce({
|
||||||
|
errors: [{ message: 'Error message' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await result.current.redirectToSSOLoginPage(identityProviderId);
|
||||||
|
|
||||||
|
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Error message', {
|
||||||
|
variant: 'error',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enqueue default error snackbar when error message is not provided', async () => {
|
||||||
|
const { result } = renderHook(() => useSSO());
|
||||||
|
const identityProviderId = 'test-id';
|
||||||
|
|
||||||
|
mockGetAuthorizationUrlMutation.mockResolvedValueOnce({ errors: [{}] });
|
||||||
|
|
||||||
|
await result.current.redirectToSSOLoginPage(identityProviderId);
|
||||||
|
|
||||||
|
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Unknown error', {
|
||||||
|
variant: 'error',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,55 @@
|
|||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
|
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
useParams: jest.fn(),
|
||||||
|
useSearchParams: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@/auth/hooks/useAuth', () => ({
|
||||||
|
useAuth: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useSignInWithGoogle', () => {
|
||||||
|
it('should call signInWithGoogle with correct params', () => {
|
||||||
|
const signInWithGoogleMock = jest.fn();
|
||||||
|
const mockUseParams = { workspaceInviteHash: 'testHash' };
|
||||||
|
const mockSearchParams = new URLSearchParams('inviteToken=testToken');
|
||||||
|
|
||||||
|
(useParams as jest.Mock).mockReturnValue(mockUseParams);
|
||||||
|
(useSearchParams as jest.Mock).mockReturnValue([mockSearchParams]);
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
signInWithGoogle: signInWithGoogleMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useSignInWithGoogle());
|
||||||
|
result.current.signInWithGoogle();
|
||||||
|
|
||||||
|
expect(signInWithGoogleMock).toHaveBeenCalledWith({
|
||||||
|
workspaceInviteHash: 'testHash',
|
||||||
|
workspacePersonalInviteToken: 'testToken',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call signInWithGoogle with undefined invite token if not present', () => {
|
||||||
|
const signInWithGoogleMock = jest.fn();
|
||||||
|
const mockUseParams = { workspaceInviteHash: 'testHash' };
|
||||||
|
const mockSearchParams = new URLSearchParams();
|
||||||
|
|
||||||
|
(useParams as jest.Mock).mockReturnValue(mockUseParams);
|
||||||
|
(useSearchParams as jest.Mock).mockReturnValue([mockSearchParams]);
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
signInWithGoogle: signInWithGoogleMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useSignInWithGoogle());
|
||||||
|
result.current.signInWithGoogle();
|
||||||
|
|
||||||
|
expect(signInWithGoogleMock).toHaveBeenCalledWith({
|
||||||
|
workspaceInviteHash: 'testHash',
|
||||||
|
workspacePersonalInviteToken: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,60 @@
|
|||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
|
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
useParams: jest.fn(),
|
||||||
|
useSearchParams: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@/auth/hooks/useAuth', () => ({
|
||||||
|
useAuth: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useSignInWithMicrosoft', () => {
|
||||||
|
it('should call signInWithMicrosoft with the correct parameters', () => {
|
||||||
|
const workspaceInviteHashMock = 'testHash';
|
||||||
|
const inviteTokenMock = 'testToken';
|
||||||
|
const signInWithMicrosoftMock = jest.fn();
|
||||||
|
|
||||||
|
(useParams as jest.Mock).mockReturnValue({
|
||||||
|
workspaceInviteHash: workspaceInviteHashMock,
|
||||||
|
});
|
||||||
|
(useSearchParams as jest.Mock).mockReturnValue([
|
||||||
|
new URLSearchParams(`inviteToken=${inviteTokenMock}`),
|
||||||
|
]);
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
signInWithMicrosoft: signInWithMicrosoftMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useSignInWithMicrosoft());
|
||||||
|
result.current.signInWithMicrosoft();
|
||||||
|
|
||||||
|
expect(signInWithMicrosoftMock).toHaveBeenCalledWith({
|
||||||
|
workspaceInviteHash: workspaceInviteHashMock,
|
||||||
|
workspacePersonalInviteToken: inviteTokenMock,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing inviteToken gracefully', () => {
|
||||||
|
const workspaceInviteHashMock = 'testHash';
|
||||||
|
const signInWithMicrosoftMock = jest.fn();
|
||||||
|
|
||||||
|
(useParams as jest.Mock).mockReturnValue({
|
||||||
|
workspaceInviteHash: workspaceInviteHashMock,
|
||||||
|
});
|
||||||
|
(useSearchParams as jest.Mock).mockReturnValue([new URLSearchParams('')]);
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
signInWithMicrosoft: signInWithMicrosoftMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useSignInWithMicrosoft());
|
||||||
|
result.current.signInWithMicrosoft();
|
||||||
|
|
||||||
|
expect(signInWithMicrosoftMock).toHaveBeenCalledWith({
|
||||||
|
workspaceInviteHash: workspaceInviteHashMock,
|
||||||
|
workspacePersonalInviteToken: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,18 +0,0 @@
|
|||||||
import { cookieStorageEffect } from '~/utils/recoil-effects';
|
|
||||||
import { Workspace } from '~/generated/graphql';
|
|
||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const lastAuthenticateWorkspaceState = createState<
|
|
||||||
| (Pick<Workspace, 'id' | 'subdomain'> & {
|
|
||||||
cookieAttributes?: Cookies.CookieAttributes;
|
|
||||||
})
|
|
||||||
| null
|
|
||||||
>({
|
|
||||||
key: 'lastAuthenticateWorkspaceState',
|
|
||||||
defaultValue: null,
|
|
||||||
effects: [
|
|
||||||
cookieStorageEffect('lastAuthenticateWorkspace', {
|
|
||||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
@ -13,13 +13,13 @@ import { useEffect } from 'react';
|
|||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
import { useGetClientConfigQuery } from '~/generated/graphql';
|
import { useGetClientConfigQuery } from '~/generated/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { urlManagerState } from '@/url-manager/states/url-manager.state';
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
import { isSSOEnabledState } from '@/client-config/states/isSSOEnabledState';
|
import { isSSOEnabledState } from '@/client-config/states/isSSOEnabledState';
|
||||||
|
|
||||||
export const ClientConfigProviderEffect = () => {
|
export const ClientConfigProviderEffect = () => {
|
||||||
const setIsDebugMode = useSetRecoilState(isDebugModeState);
|
const setIsDebugMode = useSetRecoilState(isDebugModeState);
|
||||||
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
|
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
|
||||||
const setUrlManager = useSetRecoilState(urlManagerState);
|
const setDomainConfiguration = useSetRecoilState(domainConfigurationState);
|
||||||
|
|
||||||
const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState(
|
const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState(
|
||||||
isDeveloperDefaultSignInPrefilledState,
|
isDeveloperDefaultSignInPrefilledState,
|
||||||
@ -77,7 +77,6 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
|
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
|
||||||
setIsDeveloperDefaultSignInPrefilled(data?.clientConfig.signInPrefilled);
|
setIsDeveloperDefaultSignInPrefilled(data?.clientConfig.signInPrefilled);
|
||||||
setIsMultiWorkspaceEnabled(data?.clientConfig.isMultiWorkspaceEnabled);
|
setIsMultiWorkspaceEnabled(data?.clientConfig.isMultiWorkspaceEnabled);
|
||||||
|
|
||||||
setBilling(data?.clientConfig.billing);
|
setBilling(data?.clientConfig.billing);
|
||||||
setSupportChat(data?.clientConfig.support);
|
setSupportChat(data?.clientConfig.support);
|
||||||
|
|
||||||
@ -95,7 +94,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
setChromeExtensionId(data?.clientConfig?.chromeExtensionId);
|
setChromeExtensionId(data?.clientConfig?.chromeExtensionId);
|
||||||
setApiConfig(data?.clientConfig?.api);
|
setApiConfig(data?.clientConfig?.api);
|
||||||
setIsSSOEnabledState(data?.clientConfig?.isSSOEnabled);
|
setIsSSOEnabledState(data?.clientConfig?.isSSOEnabled);
|
||||||
setUrlManager({
|
setDomainConfiguration({
|
||||||
defaultSubdomain: data?.clientConfig?.defaultSubdomain,
|
defaultSubdomain: data?.clientConfig?.defaultSubdomain,
|
||||||
frontDomain: data?.clientConfig?.frontDomain,
|
frontDomain: data?.clientConfig?.frontDomain,
|
||||||
});
|
});
|
||||||
@ -114,7 +113,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
setApiConfig,
|
setApiConfig,
|
||||||
setIsAnalyticsEnabled,
|
setIsAnalyticsEnabled,
|
||||||
error,
|
error,
|
||||||
setUrlManager,
|
setDomainConfiguration,
|
||||||
setIsSSOEnabledState,
|
setIsSSOEnabledState,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
export const useBuildWorkspaceUrl = () => {
|
||||||
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
|
|
||||||
|
const buildWorkspaceUrl = (
|
||||||
|
subdomain?: string,
|
||||||
|
pathname?: string,
|
||||||
|
searchParams?: Record<string, string>,
|
||||||
|
) => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
|
||||||
|
if (isDefined(subdomain) && subdomain.length !== 0) {
|
||||||
|
url.hostname = `${subdomain}.${domainConfiguration.frontDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(pathname)) {
|
||||||
|
url.pathname = pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(searchParams)) {
|
||||||
|
Object.entries(searchParams).forEach(([key, value]) =>
|
||||||
|
url.searchParams.set(key, value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildWorkspaceUrl,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,42 @@
|
|||||||
|
import { useGetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
|
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||||
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||||
|
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||||
|
|
||||||
|
export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||||
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||||
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
const setAuthProviders = useSetRecoilState(authProvidersState);
|
||||||
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
||||||
|
const setWorkspacePublicDataState = useSetRecoilState(
|
||||||
|
workspacePublicDataState,
|
||||||
|
);
|
||||||
|
const { setLastAuthenticateWorkspaceDomain } =
|
||||||
|
useLastAuthenticatedWorkspaceDomain();
|
||||||
|
|
||||||
|
const { loading } = useGetPublicWorkspaceDataBySubdomainQuery({
|
||||||
|
skip:
|
||||||
|
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
||||||
|
isDefined(workspacePublicData),
|
||||||
|
onCompleted: (data) => {
|
||||||
|
setAuthProviders(data.getPublicWorkspaceDataBySubdomain.authProviders);
|
||||||
|
setWorkspacePublicDataState(data.getPublicWorkspaceDataBySubdomain);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
setLastAuthenticateWorkspaceDomain(null);
|
||||||
|
redirectToDefaultDomain();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
|
import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration';
|
||||||
|
|
||||||
|
export const useIsCurrentLocationOnAWorkspaceSubdomain = () => {
|
||||||
|
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
||||||
|
|
||||||
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isMultiWorkspaceEnabled &&
|
||||||
|
(!isDefined(domainConfiguration.frontDomain) ||
|
||||||
|
!isDefined(domainConfiguration.defaultSubdomain))
|
||||||
|
) {
|
||||||
|
throw new Error('frontDomain and defaultSubdomain are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOnAWorkspaceSubdomain =
|
||||||
|
isMultiWorkspaceEnabled && window.location.hostname !== defaultDomain;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOnAWorkspaceSubdomain,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration';
|
||||||
|
|
||||||
|
export const useIsCurrentLocationOnDefaultDomain = () => {
|
||||||
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
||||||
|
const isDefaultDomain = isMultiWorkspaceEnabled
|
||||||
|
? window.location.hostname === defaultDomain
|
||||||
|
: true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDefaultDomain,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
|
||||||
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
|
|
||||||
|
export const useLastAuthenticatedWorkspaceDomain = () => {
|
||||||
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
|
const setLastAuthenticatedWorkspaceDomain = useSetRecoilState(
|
||||||
|
lastAuthenticatedWorkspaceDomainState,
|
||||||
|
);
|
||||||
|
const setLastAuthenticateWorkspaceDomainWithCookieAttributes = (
|
||||||
|
params: { workspaceId: string; subdomain: string } | null,
|
||||||
|
) => {
|
||||||
|
setLastAuthenticatedWorkspaceDomain(
|
||||||
|
params
|
||||||
|
? {
|
||||||
|
...params,
|
||||||
|
cookieAttributes: {
|
||||||
|
domain: `.${domainConfiguration.frontDomain}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
setLastAuthenticateWorkspaceDomain:
|
||||||
|
setLastAuthenticateWorkspaceDomainWithCookieAttributes,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
export const useReadDefaultDomainFromConfiguration = () => {
|
||||||
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
|
||||||
|
const defaultDomain = isMultiWorkspaceEnabled
|
||||||
|
? `${domainConfiguration.defaultSubdomain}.${domainConfiguration.frontDomain}`
|
||||||
|
: domainConfiguration.frontDomain;
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultDomain,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
||||||
|
|
||||||
|
export const useReadWorkspaceSubdomainFromCurrentLocation = () => {
|
||||||
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
|
const { isOnAWorkspaceSubdomain } =
|
||||||
|
useIsCurrentLocationOnAWorkspaceSubdomain();
|
||||||
|
if (!isDefined(domainConfiguration.frontDomain)) {
|
||||||
|
throw new Error('frontDomain is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceSubdomain = isOnAWorkspaceSubdomain
|
||||||
|
? window.location.hostname.replace(
|
||||||
|
`.${domainConfiguration.frontDomain}`,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
workspaceSubdomain,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration';
|
||||||
|
|
||||||
|
export const useRedirectToDefaultDomain = () => {
|
||||||
|
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
||||||
|
const redirectToDefaultDomain = () => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
if (url.hostname !== defaultDomain) {
|
||||||
|
url.hostname = defaultDomain;
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirectToDefaultDomain,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||||
|
|
||||||
|
export const useRedirectToWorkspaceDomain = () => {
|
||||||
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||||
|
|
||||||
|
const redirectToWorkspaceDomain = (
|
||||||
|
subdomain: string,
|
||||||
|
pathname?: string,
|
||||||
|
searchParams?: Record<string, string>,
|
||||||
|
) => {
|
||||||
|
if (!isMultiWorkspaceEnabled) return;
|
||||||
|
window.location.href = buildWorkspaceUrl(subdomain, pathname, searchParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirectToWorkspaceDomain,
|
||||||
|
};
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import { createState } from 'twenty-ui';
|
import { createState } from 'twenty-ui';
|
||||||
import { ClientConfig } from '~/generated/graphql';
|
import { ClientConfig } from '~/generated/graphql';
|
||||||
|
|
||||||
export const urlManagerState = createState<
|
export const domainConfigurationState = createState<
|
||||||
Pick<ClientConfig, 'frontDomain' | 'defaultSubdomain'>
|
Pick<ClientConfig, 'frontDomain' | 'defaultSubdomain'>
|
||||||
>({
|
>({
|
||||||
key: 'urlManager',
|
key: 'domainConfiguration',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
frontDomain: '',
|
frontDomain: '',
|
||||||
defaultSubdomain: undefined,
|
defaultSubdomain: undefined,
|
@ -0,0 +1,16 @@
|
|||||||
|
import { cookieStorageEffect } from '~/utils/recoil-effects';
|
||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const lastAuthenticatedWorkspaceDomainState = createState<{
|
||||||
|
subdomain: string;
|
||||||
|
workspaceId: string;
|
||||||
|
cookieAttributes?: Cookies.CookieAttributes;
|
||||||
|
} | null>({
|
||||||
|
key: 'lastAuthenticateWorkspaceDomain',
|
||||||
|
defaultValue: null,
|
||||||
|
effects: [
|
||||||
|
cookieStorageEffect('lastAuthenticateWorkspaceDomain', {
|
||||||
|
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
@ -61,7 +61,8 @@ export const SettingsSecurityOptionsList = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
currentWorkspace[key] === true &&
|
currentWorkspace[key] === true &&
|
||||||
allAuthProvidersEnabled.filter((isAuthEnable) => isAuthEnable).length <= 1
|
allAuthProvidersEnabled.filter((isAuthEnabled) => isAuthEnabled).length <=
|
||||||
|
1
|
||||||
) {
|
) {
|
||||||
return enqueueSnackBar(
|
return enqueueSnackBar(
|
||||||
'At least one authentication method must be enabled',
|
'At least one authentication method must be enabled',
|
||||||
|
@ -13,15 +13,13 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { IconChevronDown, MenuItemSelectAvatar } from 'twenty-ui';
|
import {
|
||||||
|
IconChevronDown,
|
||||||
|
MenuItemSelectAvatar,
|
||||||
|
UndecoratedLink,
|
||||||
|
} from 'twenty-ui';
|
||||||
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
|
||||||
import { Link } from 'react-router-dom';
|
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
|
||||||
text-decoration: none;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledLogo = styled.div<{ logo: string }>`
|
const StyledLogo = styled.div<{ logo: string }>`
|
||||||
background: url(${({ logo }) => logo});
|
background: url(${({ logo }) => logo});
|
||||||
@ -79,7 +77,7 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
const { switchWorkspace } = useWorkspaceSwitching();
|
const { switchWorkspace } = useWorkspaceSwitching();
|
||||||
const { buildWorkspaceUrl } = useUrlManager();
|
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||||
|
|
||||||
const { closeDropdown } = useDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
|
const { closeDropdown } = useDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
|
||||||
|
|
||||||
@ -122,9 +120,13 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{workspaces.map((workspace) => (
|
{workspaces.map((workspace) => (
|
||||||
<StyledLink
|
<UndecoratedLink
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
to={buildWorkspaceUrl(workspace.subdomain)}
|
to={buildWorkspaceUrl(workspace.subdomain)}
|
||||||
|
onClick={(event) => {
|
||||||
|
event?.preventDefault();
|
||||||
|
handleChange(workspace.id);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItemSelectAvatar
|
<MenuItemSelectAvatar
|
||||||
text={workspace.displayName ?? ''}
|
text={workspace.displayName ?? ''}
|
||||||
@ -136,12 +138,8 @@ export const MultiWorkspaceDropdownButton = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
selected={currentWorkspace?.id === workspace.id}
|
selected={currentWorkspace?.id === workspace.id}
|
||||||
onClick={(event) => {
|
|
||||||
event?.preventDefault();
|
|
||||||
handleChange(workspace.id);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</StyledLink>
|
</UndecoratedLink>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,16 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSwitchWorkspaceMutation } from '~/generated/graphql';
|
import { useSwitchWorkspaceMutation } from '~/generated/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||||
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
|
||||||
export const useWorkspaceSwitching = () => {
|
export const useWorkspaceSwitching = () => {
|
||||||
const [switchWorkspaceMutation] = useSwitchWorkspaceMutation();
|
const [switchWorkspaceMutation] = useSwitchWorkspaceMutation();
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const { redirectToHome, redirectToWorkspace } = useUrlManager();
|
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
||||||
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
|
|
||||||
const switchWorkspace = async (workspaceId: string) => {
|
const switchWorkspace = async (workspaceId: string) => {
|
||||||
if (currentWorkspace?.id === workspaceId) return;
|
if (currentWorkspace?.id === workspaceId) return;
|
||||||
@ -35,10 +37,10 @@ export const useWorkspaceSwitching = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isDefined(errors) || !isDefined(data?.switchWorkspace.subdomain)) {
|
if (isDefined(errors) || !isDefined(data?.switchWorkspace.subdomain)) {
|
||||||
return redirectToHome();
|
return redirectToDefaultDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectToWorkspace(data.switchWorkspace.subdomain);
|
redirectToWorkspaceDomain(data.switchWorkspace.subdomain);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { switchWorkspace };
|
return { switchWorkspace };
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
import { useMemo, useCallback } from 'react';
|
|
||||||
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
|
||||||
import { urlManagerState } from '@/url-manager/states/url-manager.state';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
|
||||||
|
|
||||||
export const useUrlManager = () => {
|
|
||||||
const urlManager = useRecoilValue(urlManagerState);
|
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
|
||||||
|
|
||||||
const homePageDomain = useMemo(() => {
|
|
||||||
return isMultiWorkspaceEnabled
|
|
||||||
? `${urlManager.defaultSubdomain}.${urlManager.frontDomain}`
|
|
||||||
: urlManager.frontDomain;
|
|
||||||
}, [
|
|
||||||
isMultiWorkspaceEnabled,
|
|
||||||
urlManager.defaultSubdomain,
|
|
||||||
urlManager.frontDomain,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isTwentyHomePage = useMemo(() => {
|
|
||||||
if (!isMultiWorkspaceEnabled) return true;
|
|
||||||
return window.location.hostname === homePageDomain;
|
|
||||||
}, [homePageDomain, isMultiWorkspaceEnabled]);
|
|
||||||
|
|
||||||
const isTwentyWorkspaceSubdomain = useMemo(() => {
|
|
||||||
if (!isMultiWorkspaceEnabled) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isDefined(urlManager.frontDomain) ||
|
|
||||||
!isDefined(urlManager.defaultSubdomain)
|
|
||||||
) {
|
|
||||||
throw new Error('frontDomain and defaultSubdomain are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.location.hostname !== homePageDomain;
|
|
||||||
}, [
|
|
||||||
homePageDomain,
|
|
||||||
isMultiWorkspaceEnabled,
|
|
||||||
urlManager.defaultSubdomain,
|
|
||||||
urlManager.frontDomain,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getWorkspaceSubdomain = useMemo(() => {
|
|
||||||
if (!isDefined(urlManager.frontDomain)) {
|
|
||||||
throw new Error('frontDomain is not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
return isTwentyWorkspaceSubdomain
|
|
||||||
? window.location.hostname.replace(`.${urlManager.frontDomain}`, '')
|
|
||||||
: null;
|
|
||||||
}, [isTwentyWorkspaceSubdomain, urlManager.frontDomain]);
|
|
||||||
|
|
||||||
const buildWorkspaceUrl = useCallback(
|
|
||||||
(
|
|
||||||
subdomain?: string,
|
|
||||||
onPage?: string,
|
|
||||||
searchParams?: Record<string, string>,
|
|
||||||
) => {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
|
|
||||||
if (isDefined(subdomain) && subdomain.length !== 0) {
|
|
||||||
url.hostname = `${subdomain}.${urlManager.frontDomain}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefined(onPage)) {
|
|
||||||
url.pathname = onPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefined(searchParams)) {
|
|
||||||
Object.entries(searchParams).forEach(([key, value]) =>
|
|
||||||
url.searchParams.set(key, value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return url.toString();
|
|
||||||
},
|
|
||||||
[urlManager.frontDomain],
|
|
||||||
);
|
|
||||||
|
|
||||||
const redirectToWorkspace = useCallback(
|
|
||||||
(
|
|
||||||
subdomain: string,
|
|
||||||
onPage?: string,
|
|
||||||
searchParams?: Record<string, string>,
|
|
||||||
) => {
|
|
||||||
if (!isMultiWorkspaceEnabled) return;
|
|
||||||
window.location.href = buildWorkspaceUrl(subdomain, onPage, searchParams);
|
|
||||||
},
|
|
||||||
[buildWorkspaceUrl, isMultiWorkspaceEnabled],
|
|
||||||
);
|
|
||||||
|
|
||||||
const redirectToHome = useCallback(() => {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
if (url.hostname !== homePageDomain) {
|
|
||||||
url.hostname = homePageDomain;
|
|
||||||
window.location.href = url.toString();
|
|
||||||
}
|
|
||||||
}, [homePageDomain]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
redirectToHome,
|
|
||||||
redirectToWorkspace,
|
|
||||||
homePageDomain,
|
|
||||||
isTwentyHomePage,
|
|
||||||
buildWorkspaceUrl,
|
|
||||||
isTwentyWorkspaceSubdomain,
|
|
||||||
getWorkspaceSubdomain,
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,79 +1,52 @@
|
|||||||
import { useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useGetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
|
||||||
|
|
||||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { lastAuthenticateWorkspaceState } from '@/auth/states/lastAuthenticateWorkspaceState';
|
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
|
||||||
|
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
||||||
|
|
||||||
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||||
export const WorkspaceProviderEffect = () => {
|
export const WorkspaceProviderEffect = () => {
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
|
||||||
const setAuthProviders = useSetRecoilState(authProvidersState);
|
const lastAuthenticatedWorkspaceDomain = useRecoilValue(
|
||||||
const setWorkspacePublicDataState = useSetRecoilState(
|
lastAuthenticatedWorkspaceDomainState,
|
||||||
workspacePublicDataState,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const [lastAuthenticateWorkspace, setLastAuthenticateWorkspace] =
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
useRecoilState(lastAuthenticateWorkspaceState);
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||||
|
|
||||||
const {
|
const { workspaceSubdomain } = useReadWorkspaceSubdomainFromCurrentLocation();
|
||||||
redirectToHome,
|
|
||||||
getWorkspaceSubdomain,
|
|
||||||
redirectToWorkspace,
|
|
||||||
isTwentyHomePage,
|
|
||||||
} = useUrlManager();
|
|
||||||
|
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
|
||||||
useGetPublicWorkspaceDataBySubdomainQuery({
|
|
||||||
skip:
|
|
||||||
(isMultiWorkspaceEnabled && isTwentyHomePage) ||
|
|
||||||
isDefined(workspacePublicData),
|
|
||||||
onCompleted: (data) => {
|
|
||||||
setAuthProviders(data.getPublicWorkspaceDataBySubdomain.authProviders);
|
|
||||||
setWorkspacePublicDataState(data.getPublicWorkspaceDataBySubdomain);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(error);
|
|
||||||
setLastAuthenticateWorkspace(null);
|
|
||||||
redirectToHome();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (isMultiWorkspaceEnabled && isDefined(workspacePublicData?.subdomain)) {
|
||||||
isMultiWorkspaceEnabled &&
|
redirectToWorkspaceDomain(workspacePublicData.subdomain);
|
||||||
isDefined(workspacePublicData?.subdomain) &&
|
|
||||||
workspacePublicData.subdomain !== getWorkspaceSubdomain
|
|
||||||
) {
|
|
||||||
redirectToWorkspace(workspacePublicData.subdomain);
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
getWorkspaceSubdomain,
|
workspaceSubdomain,
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
redirectToWorkspace,
|
redirectToWorkspaceDomain,
|
||||||
workspacePublicData,
|
workspacePublicData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
isMultiWorkspaceEnabled &&
|
isMultiWorkspaceEnabled &&
|
||||||
isDefined(lastAuthenticateWorkspace?.subdomain) &&
|
isDefined(lastAuthenticatedWorkspaceDomain?.subdomain) &&
|
||||||
isTwentyHomePage
|
isDefaultDomain
|
||||||
) {
|
) {
|
||||||
redirectToWorkspace(lastAuthenticateWorkspace.subdomain);
|
redirectToWorkspaceDomain(lastAuthenticatedWorkspaceDomain.subdomain);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
isTwentyHomePage,
|
isDefaultDomain,
|
||||||
lastAuthenticateWorkspace,
|
lastAuthenticatedWorkspaceDomain,
|
||||||
redirectToWorkspace,
|
redirectToWorkspaceDomain,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
|
@ -14,27 +14,33 @@ import { SignInUpWorkspaceScopeForm } from '@/auth/sign-in-up/components/SignInU
|
|||||||
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
||||||
import { SignInUpSSOIdentityProviderSelection } from '@/auth/sign-in-up/components/SignInUpSSOIdentityProviderSelection';
|
import { SignInUpSSOIdentityProviderSelection } from '@/auth/sign-in-up/components/SignInUpSSOIdentityProviderSelection';
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect';
|
import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeFormEffect';
|
||||||
|
import { useGetPublicWorkspaceDataBySubdomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataBySubdomain';
|
||||||
|
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
||||||
|
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||||
|
|
||||||
export const SignInUp = () => {
|
export const SignInUp = () => {
|
||||||
const { form } = useSignInUpForm();
|
const { form } = useSignInUpForm();
|
||||||
const { signInUpStep } = useSignInUp(form);
|
const { signInUpStep } = useSignInUp(form);
|
||||||
const { isTwentyHomePage, isTwentyWorkspaceSubdomain } = useUrlManager();
|
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||||
|
const { isOnAWorkspaceSubdomain } =
|
||||||
|
useIsCurrentLocationOnAWorkspaceSubdomain();
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
const { loading } = useGetPublicWorkspaceDataBySubdomain();
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
|
|
||||||
const signInUpForm = useMemo(() => {
|
const signInUpForm = useMemo(() => {
|
||||||
if (isTwentyHomePage && isMultiWorkspaceEnabled) {
|
if (loading) return null;
|
||||||
|
|
||||||
|
if (isDefaultDomain && isMultiWorkspaceEnabled) {
|
||||||
return <SignInUpGlobalScopeForm />;
|
return <SignInUpGlobalScopeForm />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!isMultiWorkspaceEnabled ||
|
(!isMultiWorkspaceEnabled ||
|
||||||
(isMultiWorkspaceEnabled && isTwentyWorkspaceSubdomain)) &&
|
(isMultiWorkspaceEnabled && isOnAWorkspaceSubdomain)) &&
|
||||||
signInUpStep === SignInUpStep.SSOIdentityProviderSelection
|
signInUpStep === SignInUpStep.SSOIdentityProviderSelection
|
||||||
) {
|
) {
|
||||||
return <SignInUpSSOIdentityProviderSelection />;
|
return <SignInUpSSOIdentityProviderSelection />;
|
||||||
@ -42,7 +48,7 @@ export const SignInUp = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(workspacePublicData) &&
|
isDefined(workspacePublicData) &&
|
||||||
(!isMultiWorkspaceEnabled || isTwentyWorkspaceSubdomain)
|
(!isMultiWorkspaceEnabled || isOnAWorkspaceSubdomain)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -54,9 +60,10 @@ export const SignInUp = () => {
|
|||||||
|
|
||||||
return <SignInUpGlobalScopeForm />;
|
return <SignInUpGlobalScopeForm />;
|
||||||
}, [
|
}, [
|
||||||
isTwentyHomePage,
|
isDefaultDomain,
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
isTwentyWorkspaceSubdomain,
|
isOnAWorkspaceSubdomain,
|
||||||
|
loading,
|
||||||
signInUpStep,
|
signInUpStep,
|
||||||
workspacePublicData,
|
workspacePublicData,
|
||||||
]);
|
]);
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
|
||||||
const StyledContentContainer = styled.div`
|
const StyledContentContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -51,7 +51,7 @@ export const CreateWorkspace = () => {
|
|||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const onboardingStatus = useOnboardingStatus();
|
const onboardingStatus = useOnboardingStatus();
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
const { redirectToWorkspace } = useUrlManager();
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
|
|
||||||
const [activateWorkspace] = useActivateWorkspaceMutation();
|
const [activateWorkspace] = useActivateWorkspaceMutation();
|
||||||
const apolloMetadataClient = useApolloMetadataClient();
|
const apolloMetadataClient = useApolloMetadataClient();
|
||||||
@ -84,7 +84,7 @@ export const CreateWorkspace = () => {
|
|||||||
setIsCurrentUserLoaded(false);
|
setIsCurrentUserLoaded(false);
|
||||||
|
|
||||||
if (isDefined(result.data) && isMultiWorkspaceEnabled) {
|
if (isDefined(result.data) && isMultiWorkspaceEnabled) {
|
||||||
return redirectToWorkspace(
|
return redirectToWorkspaceDomain(
|
||||||
result.data.activateWorkspace.workspace.subdomain,
|
result.data.activateWorkspace.workspace.subdomain,
|
||||||
AppPath.Verify,
|
AppPath.Verify,
|
||||||
{
|
{
|
||||||
@ -111,7 +111,7 @@ export const CreateWorkspace = () => {
|
|||||||
setIsCurrentUserLoaded,
|
setIsCurrentUserLoaded,
|
||||||
isMultiWorkspaceEnabled,
|
isMultiWorkspaceEnabled,
|
||||||
apolloMetadataClient,
|
apolloMetadataClient,
|
||||||
redirectToWorkspace,
|
redirectToWorkspaceDomain,
|
||||||
enqueueSnackBar,
|
enqueueSnackBar,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { GithubVersionLink, H2Title, Section, IconWorld } from 'twenty-ui';
|
import {
|
||||||
import { Link } from 'react-router-dom';
|
GithubVersionLink,
|
||||||
|
H2Title,
|
||||||
|
Section,
|
||||||
|
IconWorld,
|
||||||
|
UndecoratedLink,
|
||||||
|
} from 'twenty-ui';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
|
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
@ -16,10 +19,6 @@ import packageJson from '../../../package.json';
|
|||||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
|
||||||
text-decoration: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SettingsWorkspace = () => {
|
export const SettingsWorkspace = () => {
|
||||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||||
return (
|
return (
|
||||||
@ -48,9 +47,9 @@ export const SettingsWorkspace = () => {
|
|||||||
title="Domain"
|
title="Domain"
|
||||||
description="Edit your subdomain name or set a custom domain."
|
description="Edit your subdomain name or set a custom domain."
|
||||||
/>
|
/>
|
||||||
<StyledLink to={getSettingsPagePath(SettingsPath.Domain)}>
|
<UndecoratedLink to={getSettingsPagePath(SettingsPath.Domain)}>
|
||||||
<SettingsCard title="Customize Domain" Icon={<IconWorld />} />
|
<SettingsCard title="Customize Domain" Icon={<IconWorld />} />
|
||||||
</StyledLink>
|
</UndecoratedLink>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
|
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
|
||||||
import { useUrlManager } from '@/url-manager/hooks/useUrlManager';
|
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||||
import { urlManagerState } from '@/url-manager/states/url-manager.state';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||||
|
|
||||||
const validationSchema = z
|
const validationSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -39,17 +39,17 @@ const StyledDomain = styled.h2`
|
|||||||
color: ${({ theme }) => theme.font.color.secondary};
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
margin-left: 8px;
|
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsDomain = () => {
|
export const SettingsDomain = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const urlManager = useRecoilValue(urlManagerState);
|
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||||
const { buildWorkspaceUrl } = useUrlManager();
|
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||||
|
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
currentWorkspaceState,
|
currentWorkspaceState,
|
||||||
@ -142,8 +142,8 @@ export const SettingsDomain = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{isDefined(urlManager) && isDefined(urlManager.frontDomain) && (
|
{isDefined(domainConfiguration.frontDomain) && (
|
||||||
<StyledDomain>.{urlManager.frontDomain}</StyledDomain>
|
<StyledDomain>.{domainConfiguration.frontDomain}</StyledDomain>
|
||||||
)}
|
)}
|
||||||
</StyledDomainFromWrapper>
|
</StyledDomainFromWrapper>
|
||||||
)}
|
)}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AtomEffect } from 'recoil';
|
import { AtomEffect } from 'recoil';
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { cookieStorage } from '~/utils/cookie-storage';
|
import { cookieStorage } from '~/utils/cookie-storage';
|
||||||
|
|
||||||
@ -20,6 +21,20 @@ export const localStorageEffect =
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const customCookieAttributeZodSchema = z.object({
|
||||||
|
cookieAttributes: z.object({
|
||||||
|
expires: z.union([z.number(), z.instanceof(Date)]).optional(),
|
||||||
|
path: z.string().optional(),
|
||||||
|
domain: z.string().optional(),
|
||||||
|
secure: z.boolean().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isCustomCookiesAttributesValue = (
|
||||||
|
value: unknown,
|
||||||
|
): value is { cookieAttributes: Cookies.CookieAttributes } =>
|
||||||
|
customCookieAttributeZodSchema.safeParse(value).success;
|
||||||
|
|
||||||
export const cookieStorageEffect =
|
export const cookieStorageEffect =
|
||||||
<T>(
|
<T>(
|
||||||
key: string,
|
key: string,
|
||||||
@ -52,9 +67,7 @@ export const cookieStorageEffect =
|
|||||||
|
|
||||||
const cookieAttributes = {
|
const cookieAttributes = {
|
||||||
...defaultAttributes,
|
...defaultAttributes,
|
||||||
...(typeof newValue === 'object' &&
|
...(isCustomCookiesAttributesValue(newValue)
|
||||||
'cookieAttributes' in newValue &&
|
|
||||||
typeof newValue.cookieAttributes === 'object'
|
|
||||||
? newValue.cookieAttributes
|
? newValue.cookieAttributes
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,9 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
VITE_BUILD_SOURCEMAP,
|
VITE_BUILD_SOURCEMAP,
|
||||||
VITE_DISABLE_TYPESCRIPT_CHECKER,
|
VITE_DISABLE_TYPESCRIPT_CHECKER,
|
||||||
VITE_DISABLE_ESLINT_CHECKER,
|
VITE_DISABLE_ESLINT_CHECKER,
|
||||||
VITE_ENABLE_SSL,
|
VITE_HOST,
|
||||||
|
SSL_CERT_PATH,
|
||||||
|
SSL_KEY_PATH,
|
||||||
REACT_APP_PORT,
|
REACT_APP_PORT,
|
||||||
} = env;
|
} = env;
|
||||||
|
|
||||||
@ -64,27 +66,24 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VITE_ENABLE_SSL && (!env.SSL_KEY_PATH || !env.SSL_CERT_PATH)) {
|
|
||||||
throw new Error(
|
|
||||||
'to use https SSL_KEY_PATH and SSL_CERT_PATH must be both defined',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root: __dirname,
|
root: __dirname,
|
||||||
cacheDir: '../../node_modules/.vite/packages/twenty-front',
|
cacheDir: '../../node_modules/.vite/packages/twenty-front',
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
port: port,
|
port: port,
|
||||||
protocol: VITE_ENABLE_SSL ? 'https' : 'http',
|
...(VITE_HOST ? { host: VITE_HOST } : {}),
|
||||||
...(VITE_ENABLE_SSL
|
...(SSL_KEY_PATH && SSL_CERT_PATH
|
||||||
? {
|
? {
|
||||||
|
protocol: 'https',
|
||||||
https: {
|
https: {
|
||||||
key: fs.readFileSync(env.SSL_KEY_PATH),
|
key: fs.readFileSync(env.SSL_KEY_PATH),
|
||||||
cert: fs.readFileSync(env.SSL_CERT_PATH),
|
cert: fs.readFileSync(env.SSL_CERT_PATH),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {
|
||||||
|
protocol: 'http',
|
||||||
|
}),
|
||||||
fs: {
|
fs: {
|
||||||
allow: [
|
allow: [
|
||||||
searchForWorkspaceRoot(process.cwd()),
|
searchForWorkspaceRoot(process.cwd()),
|
||||||
|
Loading…
Reference in New Issue
Block a user