feat(): enable custom domain usage (#9911)
# Content - Introduce the `workspaceUrls` property. It contains two sub-properties: `customUrl, subdomainUrl`. These endpoints are used to access the workspace. Even if the `workspaceUrls` is invalid for multiple reasons, the `subdomainUrl` remains valid. - Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL computation on the frontend part. - Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
This commit is contained in:
@ -5,7 +5,10 @@ export const IMPERSONATE = gql`
|
||||
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
||||
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
||||
workspace {
|
||||
subdomain
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
id
|
||||
}
|
||||
loginToken {
|
||||
|
||||
@ -22,7 +22,10 @@ export const SIGN_UP = gql`
|
||||
}
|
||||
workspace {
|
||||
id
|
||||
subdomain
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,10 @@ export const CHECK_USER_EXISTS = gql`
|
||||
availableWorkspaces {
|
||||
id
|
||||
displayName
|
||||
subdomain
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
logo
|
||||
sso {
|
||||
type
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_PUBLIC_WORKSPACE_DATA_BY_SUBDOMAIN = gql`
|
||||
query GetPublicWorkspaceDataBySubdomain {
|
||||
getPublicWorkspaceDataBySubdomain {
|
||||
export const GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN = gql`
|
||||
query GetPublicWorkspaceDataByDomain {
|
||||
getPublicWorkspaceDataByDomain {
|
||||
id
|
||||
logo
|
||||
displayName
|
||||
subdomain
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
authProviders {
|
||||
sso {
|
||||
id
|
||||
@ -53,7 +53,7 @@ import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type
|
||||
import { captchaState } from '@/client-config/states/captchaState';
|
||||
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
@ -62,6 +62,7 @@ import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/state
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
export const useAuth = () => {
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
@ -96,8 +97,7 @@ export const useAuth = () => {
|
||||
useGetLoginTokenFromEmailVerificationTokenMutation();
|
||||
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
||||
|
||||
const { isOnAWorkspaceSubdomain } =
|
||||
useIsCurrentLocationOnAWorkspaceSubdomain();
|
||||
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||
|
||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||
|
||||
@ -289,10 +289,10 @@ export const useAuth = () => {
|
||||
|
||||
setCurrentWorkspace(workspace);
|
||||
|
||||
if (isDefined(workspace) && isOnAWorkspaceSubdomain) {
|
||||
if (isDefined(workspace) && isOnAWorkspace) {
|
||||
setLastAuthenticateWorkspaceDomain({
|
||||
workspaceId: workspace.id,
|
||||
subdomain: workspace.subdomain,
|
||||
workspaceUrl: getWorkspaceUrl(workspace.workspaceUrls),
|
||||
});
|
||||
}
|
||||
|
||||
@ -315,7 +315,7 @@ export const useAuth = () => {
|
||||
};
|
||||
}, [
|
||||
getCurrentUser,
|
||||
isOnAWorkspaceSubdomain,
|
||||
isOnAWorkspace,
|
||||
setCurrentUser,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
@ -413,7 +413,8 @@ export const useAuth = () => {
|
||||
|
||||
if (isMultiWorkspaceEnabled) {
|
||||
return redirectToWorkspaceDomain(
|
||||
signUpResult.data.signUp.workspace.subdomain,
|
||||
getWorkspaceUrl(signUpResult.data.signUp.workspace.workspaceUrls),
|
||||
|
||||
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
||||
{
|
||||
...(!isEmailVerificationRequired && {
|
||||
|
||||
@ -27,6 +27,7 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirect
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
const StyledContentContainer = styled(motion.div)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||
@ -92,9 +93,13 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
if (response.__typename === 'UserExists') {
|
||||
if (response.availableWorkspaces.length >= 1) {
|
||||
const workspace = response.availableWorkspaces[0];
|
||||
return redirectToWorkspaceDomain(workspace.subdomain, pathname, {
|
||||
email: form.getValues('email'),
|
||||
});
|
||||
return redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(workspace.workspaceUrls),
|
||||
pathname,
|
||||
{
|
||||
email: form.getValues('email'),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (response.__typename === 'UserNotExists') {
|
||||
|
||||
@ -3,6 +3,7 @@ import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||
@ -52,9 +53,11 @@ const apolloMocks = [
|
||||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
describe('useSSO', () => {
|
||||
|
||||
@ -13,14 +13,16 @@ export const useSSO = () => {
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const { redirect } = useRedirect();
|
||||
|
||||
const redirectToSSOLoginPage = async (identityProviderId: string) => {
|
||||
let authorizationUrlForSSOResult;
|
||||
try {
|
||||
authorizationUrlForSSOResult = await apolloClient.mutate({
|
||||
mutation: GET_AUTHORIZATION_URL,
|
||||
variables: {
|
||||
input: { identityProviderId, workspaceInviteHash },
|
||||
input: {
|
||||
identityProviderId,
|
||||
workspaceInviteHash,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
|
||||
@ -21,6 +21,7 @@ export type CurrentWorkspace = Pick<
|
||||
| 'hasValidEnterpriseKey'
|
||||
| 'subdomain'
|
||||
| 'hostname'
|
||||
| 'workspaceUrls'
|
||||
| 'metadataVersion'
|
||||
>;
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { Workspace } from '~/generated/graphql';
|
||||
|
||||
export type Workspaces = Pick<
|
||||
Workspace,
|
||||
'id' | 'logo' | 'displayName' | 'subdomain'
|
||||
'id' | 'logo' | 'displayName' | 'workspaceUrls'
|
||||
>[];
|
||||
|
||||
export const workspacesState = createState<Workspaces>({
|
||||
|
||||
@ -1,20 +1,12 @@
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useBuildWorkspaceUrl = () => {
|
||||
const domainConfiguration = useRecoilValue(domainConfigurationState);
|
||||
|
||||
const buildWorkspaceUrl = (
|
||||
subdomain: string,
|
||||
endpoint: string,
|
||||
pathname?: string,
|
||||
searchParams?: Record<string, string>,
|
||||
searchParams?: Record<string, string | boolean>,
|
||||
) => {
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
if (subdomain.length !== 0) {
|
||||
url.hostname = `${subdomain}.${domainConfiguration.frontDomain}`;
|
||||
}
|
||||
const url = new URL(endpoint);
|
||||
|
||||
if (isDefined(pathname)) {
|
||||
url.pathname = pathname;
|
||||
@ -22,7 +14,7 @@ export const useBuildWorkspaceUrl = () => {
|
||||
|
||||
if (isDefined(searchParams)) {
|
||||
Object.entries(searchParams).forEach(([key, value]) =>
|
||||
url.searchParams.set(key, value),
|
||||
url.searchParams.set(key, value.toString()),
|
||||
);
|
||||
}
|
||||
return url.toString();
|
||||
|
||||
@ -5,9 +5,9 @@ import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectTo
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { useGetPublicWorkspaceDataBySubdomainQuery } from '~/generated/graphql';
|
||||
import { useGetPublicWorkspaceDataByDomainQuery } from '~/generated/graphql';
|
||||
|
||||
export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
export const useGetPublicWorkspaceDataByDomain = () => {
|
||||
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||
const setWorkspaceAuthProviders = useSetRecoilState(
|
||||
@ -19,15 +19,15 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
workspacePublicDataState,
|
||||
);
|
||||
|
||||
const { loading, data, error } = useGetPublicWorkspaceDataBySubdomainQuery({
|
||||
const { loading, data, error } = useGetPublicWorkspaceDataByDomainQuery({
|
||||
skip:
|
||||
(isMultiWorkspaceEnabled && isDefaultDomain) ||
|
||||
isDefined(workspacePublicData),
|
||||
onCompleted: (data) => {
|
||||
setWorkspaceAuthProviders(
|
||||
data.getPublicWorkspaceDataBySubdomain.authProviders,
|
||||
data.getPublicWorkspaceDataByDomain.authProviders,
|
||||
);
|
||||
setWorkspacePublicDataState(data.getPublicWorkspaceDataBySubdomain);
|
||||
setWorkspacePublicDataState(data.getPublicWorkspaceDataByDomain);
|
||||
},
|
||||
onError: (error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -38,7 +38,7 @@ export const useGetPublicWorkspaceDataBySubdomain = () => {
|
||||
|
||||
return {
|
||||
loading,
|
||||
data: data?.getPublicWorkspaceDataBySubdomain,
|
||||
data: data?.getPublicWorkspaceDataByDomain,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@ -4,7 +4,7 @@ import { domainConfigurationState } from '@/domain-manager/states/domainConfigur
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useIsCurrentLocationOnAWorkspaceSubdomain = () => {
|
||||
export const useIsCurrentLocationOnAWorkspace = () => {
|
||||
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
|
||||
|
||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||
@ -18,10 +18,10 @@ export const useIsCurrentLocationOnAWorkspaceSubdomain = () => {
|
||||
throw new Error('frontDomain and defaultSubdomain are required');
|
||||
}
|
||||
|
||||
const isOnAWorkspaceSubdomain =
|
||||
const isOnAWorkspace =
|
||||
isMultiWorkspaceEnabled && window.location.hostname !== defaultDomain;
|
||||
|
||||
return {
|
||||
isOnAWorkspaceSubdomain,
|
||||
isOnAWorkspace,
|
||||
};
|
||||
};
|
||||
@ -8,7 +8,7 @@ export const useLastAuthenticatedWorkspaceDomain = () => {
|
||||
lastAuthenticatedWorkspaceDomainState,
|
||||
);
|
||||
const setLastAuthenticateWorkspaceDomainWithCookieAttributes = (
|
||||
params: { workspaceId: string; subdomain: string } | null,
|
||||
params: { workspaceId: string; workspaceUrl: string } | null,
|
||||
) => {
|
||||
setLastAuthenticatedWorkspaceDomain({
|
||||
...(params ? params : {}),
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { useIsCurrentLocationOnAWorkspaceSubdomain } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspaceSubdomain';
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
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,11 @@
|
||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||
|
||||
export const useReadWorkspaceUrlFromCurrentLocation = () => {
|
||||
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||
|
||||
return {
|
||||
currentLocationHostname: isOnAWorkspace
|
||||
? window.location.hostname
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
@ -9,12 +9,12 @@ export const useRedirectToWorkspaceDomain = () => {
|
||||
const { redirect } = useRedirect();
|
||||
|
||||
const redirectToWorkspaceDomain = (
|
||||
subdomain: string,
|
||||
baseUrl: string,
|
||||
pathname?: string,
|
||||
searchParams?: Record<string, string>,
|
||||
searchParams?: Record<string, string | boolean>,
|
||||
) => {
|
||||
if (!isMultiWorkspaceEnabled) return;
|
||||
redirect(buildWorkspaceUrl(subdomain, pathname, searchParams));
|
||||
redirect(buildWorkspaceUrl(baseUrl, pathname, searchParams));
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -3,7 +3,7 @@ import { cookieStorageEffect } from '~/utils/recoil-effects';
|
||||
|
||||
export const lastAuthenticatedWorkspaceDomainState = createState<
|
||||
| {
|
||||
subdomain: string;
|
||||
workspaceUrl: string;
|
||||
workspaceId: string;
|
||||
cookieAttributes?: Cookies.CookieAttributes;
|
||||
}
|
||||
|
||||
@ -159,6 +159,10 @@ export const queries = {
|
||||
subdomain
|
||||
hasValidEnterpriseKey
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
featureFlags {
|
||||
id
|
||||
key
|
||||
@ -183,6 +187,11 @@ export const queries = {
|
||||
logo
|
||||
displayName
|
||||
subdomain
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
userVars
|
||||
@ -309,6 +318,10 @@ export const responseData = {
|
||||
isPasswordAuthEnabled: true,
|
||||
subdomain: 'test',
|
||||
hostname: null,
|
||||
workspaceUrls: {
|
||||
customUrl: undefined,
|
||||
subdomainUrl: 'https://test.twenty.com/',
|
||||
},
|
||||
featureFlags: [],
|
||||
metadataVersion: 1,
|
||||
currentBillingSubscription: null,
|
||||
|
||||
@ -27,6 +27,10 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
isGoogleAuthEnabled: true,
|
||||
isMicrosoftAuthEnabled: false,
|
||||
isPasswordAuthEnabled: true,
|
||||
workspaceUrls: {
|
||||
subdomainUrl: 'https://twenty.twenty.com',
|
||||
customUrl: 'https://my-custom-domain.com',
|
||||
},
|
||||
currentBillingSubscription: {
|
||||
id: '1',
|
||||
interval: SubscriptionInterval.Month,
|
||||
|
||||
@ -8,6 +8,7 @@ import { useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { useImpersonateMutation } from '~/generated/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
export const useImpersonate = () => {
|
||||
const [currentUser] = useRecoilState(currentUserState);
|
||||
@ -55,9 +56,13 @@ export const useImpersonate = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
return redirectToWorkspaceDomain(workspace.subdomain, AppPath.Verify, {
|
||||
loginToken: loginToken.token,
|
||||
});
|
||||
return redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(workspace.workspaceUrls),
|
||||
AppPath.Verify,
|
||||
{
|
||||
loginToken: loginToken.token,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
setError('Failed to impersonate user. Please try again.');
|
||||
setIsLoading(false);
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
UndecoratedLink,
|
||||
} from 'twenty-ui';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
const StyledContainer = styled.div<{ isNavigationDrawerExpanded: boolean }>`
|
||||
align-items: center;
|
||||
@ -67,7 +68,7 @@ export const MultiWorkspaceDropdownButton = ({
|
||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||
|
||||
const handleChange = async (workspace: Workspaces[0]) => {
|
||||
redirectToWorkspaceDomain(workspace.subdomain);
|
||||
redirectToWorkspaceDomain(getWorkspaceUrl(workspace.workspaceUrls));
|
||||
};
|
||||
const [isNavigationDrawerExpanded] = useRecoilState(
|
||||
isNavigationDrawerExpandedState,
|
||||
@ -104,7 +105,7 @@ export const MultiWorkspaceDropdownButton = ({
|
||||
{workspaces.map((workspace) => (
|
||||
<UndecoratedLink
|
||||
key={workspace.id}
|
||||
to={buildWorkspaceUrl(workspace.subdomain)}
|
||||
to={buildWorkspaceUrl(getWorkspaceUrl(workspace.workspaceUrls))}
|
||||
onClick={(event) => {
|
||||
event?.preventDefault();
|
||||
handleChange(workspace);
|
||||
|
||||
@ -38,6 +38,10 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
subdomain
|
||||
hasValidEnterpriseKey
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
featureFlags {
|
||||
id
|
||||
key
|
||||
@ -62,6 +66,11 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
logo
|
||||
displayName
|
||||
subdomain
|
||||
hostname
|
||||
workspaceUrls {
|
||||
subdomainUrl
|
||||
customUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
userVars
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useReadWorkspaceSubdomainFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceSubdomainFromCurrentLocation';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
|
||||
import { useReadWorkspaceUrlFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceUrlFromCurrentLocation';
|
||||
|
||||
import { useGetPublicWorkspaceDataBySubdomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataBySubdomain';
|
||||
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
|
||||
import { useGetPublicWorkspaceDataByDomain } from '@/domain-manager/hooks/useGetPublicWorkspaceDataByDomain';
|
||||
import { WorkspaceUrls } from '~/generated/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
export const WorkspaceProviderEffect = () => {
|
||||
const { data: getPublicWorkspaceData } =
|
||||
useGetPublicWorkspaceDataBySubdomain();
|
||||
const { data: getPublicWorkspaceData } = useGetPublicWorkspaceDataByDomain();
|
||||
|
||||
const lastAuthenticatedWorkspaceDomain = useRecoilValue(
|
||||
lastAuthenticatedWorkspaceDomainState,
|
||||
@ -20,23 +21,38 @@ export const WorkspaceProviderEffect = () => {
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
|
||||
|
||||
const { workspaceSubdomain } = useReadWorkspaceSubdomainFromCurrentLocation();
|
||||
const { currentLocationHostname } = useReadWorkspaceUrlFromCurrentLocation();
|
||||
|
||||
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
|
||||
|
||||
const getHostnamesFromWorkspaceUrls = (workspaceUrls: WorkspaceUrls) => {
|
||||
return {
|
||||
customUrlHostname: workspaceUrls.customUrl
|
||||
? new URL(workspaceUrls.customUrl).hostname
|
||||
: undefined,
|
||||
subdomainUrlHostname: new URL(workspaceUrls.subdomainUrl).hostname,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const hostnames = getPublicWorkspaceData
|
||||
? getHostnamesFromWorkspaceUrls(getPublicWorkspaceData?.workspaceUrls)
|
||||
: null;
|
||||
if (
|
||||
isMultiWorkspaceEnabled &&
|
||||
isDefined(getPublicWorkspaceData?.subdomain) &&
|
||||
getPublicWorkspaceData.subdomain !== workspaceSubdomain
|
||||
isDefined(getPublicWorkspaceData) &&
|
||||
currentLocationHostname !== hostnames?.customUrlHostname &&
|
||||
currentLocationHostname !== hostnames?.subdomainUrlHostname
|
||||
) {
|
||||
redirectToWorkspaceDomain(getPublicWorkspaceData.subdomain);
|
||||
redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(getPublicWorkspaceData.workspaceUrls),
|
||||
);
|
||||
}
|
||||
}, [
|
||||
workspaceSubdomain,
|
||||
isMultiWorkspaceEnabled,
|
||||
redirectToWorkspaceDomain,
|
||||
getPublicWorkspaceData,
|
||||
currentLocationHostname,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -44,10 +60,10 @@ export const WorkspaceProviderEffect = () => {
|
||||
isMultiWorkspaceEnabled &&
|
||||
isDefaultDomain &&
|
||||
isDefined(lastAuthenticatedWorkspaceDomain) &&
|
||||
'subdomain' in lastAuthenticatedWorkspaceDomain &&
|
||||
isDefined(lastAuthenticatedWorkspaceDomain?.subdomain)
|
||||
'workspaceUrl' in lastAuthenticatedWorkspaceDomain &&
|
||||
isDefined(lastAuthenticatedWorkspaceDomain?.workspaceUrl)
|
||||
) {
|
||||
redirectToWorkspaceDomain(lastAuthenticatedWorkspaceDomain.subdomain);
|
||||
redirectToWorkspaceDomain(lastAuthenticatedWorkspaceDomain.workspaceUrl);
|
||||
}
|
||||
}, [
|
||||
isMultiWorkspaceEnabled,
|
||||
|
||||
@ -4,7 +4,6 @@ export const ACTIVATE_WORKSPACE = gql`
|
||||
mutation ActivateWorkspace($input: ActivateWorkspaceInput!) {
|
||||
activateWorkspace(data: $input) {
|
||||
id
|
||||
subdomain
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -7,7 +7,6 @@ export const GET_WORKSPACE_FROM_INVITE_HASH = gql`
|
||||
displayName
|
||||
logo
|
||||
allowImpersonation
|
||||
subdomain
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user