Admin panel refactor (#10119)

addressing > 
There are two patterns to avoid:
Creating functions that return JSX like renderThing() -> this was taken
already addressed in https://github.com/twentyhq/twenty/pull/10011
Making a hook that "stores" all the logic of a component - > this PR is
addressing this particular pattern
In essence, handlers should remain in the component and be connected to
their events.
And everything in a handler can be abstracted into its dedicated hook.
For example:
const { myReactiveState } =
useRecoilValue(myReactiveStateComponentState);
const { removeThingFromOtherThing } = useRemoveThingFromOtherThing();

const handleClick = () => {
  if (isDefined(myReactiveState)) {
    removeThingFromOtherThing();
  }
}

Broadly speaking, this is how you can split large components into
several sub-hooks.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
nitin
2025-02-11 17:10:28 +00:00
committed by GitHub
co-authored by Lucas Bordeau
parent 83bf2d1739
commit 252922b522
11 changed files with 266 additions and 296 deletions
@@ -36,6 +36,8 @@ const StyledShowMoreButton = styled(Button)<{ isSelected?: boolean }>`
`} `}
`; `;
const StyledEnvVariablesDescription = styled.div``;
export const SettingsAdminEnvVariables = () => { export const SettingsAdminEnvVariables = () => {
const { data: environmentVariables } = useGetEnvironmentVariablesGroupedQuery( const { data: environmentVariables } = useGetEnvironmentVariablesGroupedQuery(
{ {
@@ -65,59 +67,68 @@ export const SettingsAdminEnvVariables = () => {
); );
return ( return (
<Section> <>
{visibleGroups.map((group) => ( <StyledEnvVariablesDescription>
<StyledGroupContainer key={group.name}> These are only the server values. Ensure your worker environment has the
<H1Title title={group.name} fontColor={H1TitleFontColor.Primary} /> same variables and values, this is required for asynchronous tasks like
{group.description !== '' && ( email sync.
<StyledGroupDescription>{group.description}</StyledGroupDescription> </StyledEnvVariablesDescription>
)} <Section>
{group.variables.length > 0 && ( {visibleGroups.map((group) => (
<StyledGroupVariablesContainer> <StyledGroupContainer key={group.name}>
<SettingsAdminEnvVariablesTable variables={group.variables} /> <H1Title title={group.name} fontColor={H1TitleFontColor.Primary} />
</StyledGroupVariablesContainer> {group.description !== '' && (
)} <StyledGroupDescription>
</StyledGroupContainer> {group.description}
))} </StyledGroupDescription>
)}
{group.variables.length > 0 && (
<StyledGroupVariablesContainer>
<SettingsAdminEnvVariablesTable variables={group.variables} />
</StyledGroupVariablesContainer>
)}
</StyledGroupContainer>
))}
{hiddenGroups.length > 0 && ( {hiddenGroups.length > 0 && (
<> <>
<StyledButtonsRow> <StyledButtonsRow>
{hiddenGroups.map((group) => ( {hiddenGroups.map((group) => (
<StyledShowMoreButton <StyledShowMoreButton
key={group.name} key={group.name}
onClick={() => toggleGroupVisibility(group.name)} onClick={() => toggleGroupVisibility(group.name)}
title={group.name} title={group.name}
variant="secondary" variant="secondary"
isSelected={selectedGroup === group.name} isSelected={selectedGroup === group.name}
> >
{group.name} variables {group.name} variables
</StyledShowMoreButton> </StyledShowMoreButton>
))} ))}
</StyledButtonsRow> </StyledButtonsRow>
{selectedGroupData && ( {selectedGroupData && (
<StyledGroupContainer> <StyledGroupContainer>
<H1Title <H1Title
title={selectedGroupData.name} title={selectedGroupData.name}
fontColor={H1TitleFontColor.Primary} fontColor={H1TitleFontColor.Primary}
/> />
{selectedGroupData.description !== '' && ( {selectedGroupData.description !== '' && (
<StyledGroupDescription> <StyledGroupDescription>
{selectedGroupData.description} {selectedGroupData.description}
</StyledGroupDescription> </StyledGroupDescription>
)} )}
{selectedGroupData.variables.length > 0 && ( {selectedGroupData.variables.length > 0 && (
<StyledGroupVariablesContainer> <StyledGroupVariablesContainer>
<SettingsAdminEnvVariablesTable <SettingsAdminEnvVariablesTable
variables={selectedGroupData.variables} variables={selectedGroupData.variables}
/> />
</StyledGroupVariablesContainer> </StyledGroupVariablesContainer>
)} )}
</StyledGroupContainer> </StyledGroupContainer>
)} )}
</> </>
)} )}
</Section> </Section>
</>
); );
}; };
@@ -1,10 +1,9 @@
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState'; import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent'; import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent';
import { SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminUserLookupWorkspaceTabsId'; import { SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminUserLookupWorkspaceTabsId';
import { useImpersonate } from '@/settings/admin-panel/hooks/useImpersonate';
import { useUserLookup } from '@/settings/admin-panel/hooks/useUserLookup';
import { adminPanelErrorState } from '@/settings/admin-panel/states/adminPanelErrorState';
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState'; import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { TabList } from '@/ui/layout/tab/components/TabList'; import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
@@ -12,7 +11,7 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useState } from 'react'; import { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { getImageAbsoluteURI, isDefined } from 'twenty-shared'; import { getImageAbsoluteURI, isDefined } from 'twenty-shared';
import { import {
Button, Button,
@@ -23,6 +22,7 @@ import {
Section, Section,
} from 'twenty-ui'; } from 'twenty-ui';
import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useUserLookupAdminPanelMutation } from '~/generated/graphql';
const StyledLinkContainer = styled.div` const StyledLinkContainer = styled.div`
margin-right: ${({ theme }) => theme.spacing(2)}; margin-right: ${({ theme }) => theme.spacing(2)};
@@ -35,11 +35,6 @@ const StyledContainer = styled.div`
flex-direction: row; flex-direction: row;
`; `;
const StyledErrorSection = styled.div`
color: ${({ theme }) => theme.font.color.danger};
margin-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledUserInfo = styled.div` const StyledUserInfo = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(5)}; margin-bottom: ${({ theme }) => theme.spacing(5)};
`; `;
@@ -60,40 +55,48 @@ const StyledContentContainer = styled.div`
export const SettingsAdminGeneral = () => { export const SettingsAdminGeneral = () => {
const [userIdentifier, setUserIdentifier] = useState(''); const [userIdentifier, setUserIdentifier] = useState('');
const [userId, setUserId] = useState(''); const { enqueueSnackBar } = useSnackBar();
const { error: impersonateError } = useImpersonate();
const { activeTabId, setActiveTabId } = useTabList( const { activeTabId, setActiveTabId } = useTabList(
SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID, SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID,
); );
const userLookupResult = useRecoilValue(userLookupResultState); const [userLookupResult, setUserLookupResult] = useRecoilState(
const adminPanelError = useRecoilValue(adminPanelErrorState); userLookupResultState,
);
const [isUserLookupLoading, setIsUserLookupLoading] = useState(false);
const { handleUserLookup, isLoading } = useUserLookup(); const [userLookup] = useUserLookupAdminPanelMutation();
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState); const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
const handleSearch = async () => { const handleSearch = async () => {
setActiveTabId(''); setActiveTabId('');
setIsUserLookupLoading(true);
setUserLookupResult(null);
const result = await handleUserLookup(userIdentifier); const response = await userLookup({
variables: { userIdentifier },
onCompleted: (data) => {
setIsUserLookupLoading(false);
if (isDefined(data?.userLookupAdminPanel)) {
setUserLookupResult(data.userLookupAdminPanel);
}
},
onError: (error) => {
setIsUserLookupLoading(false);
enqueueSnackBar(error.message, {
variant: SnackBarVariant.Error,
});
},
});
if (isDefined(result?.user?.id) && !adminPanelError) { const result = response.data?.userLookupAdminPanel;
setUserId(result.user.id.trim());
}
if ( if (isDefined(result?.workspaces) && result.workspaces.length > 0) {
isDefined(result?.workspaces) &&
result.workspaces.length > 0 &&
!adminPanelError
) {
setActiveTabId(result.workspaces[0].id); setActiveTabId(result.workspaces[0].id);
} }
}; };
const shouldShowUserData = userLookupResult && !adminPanelError;
const activeWorkspace = userLookupResult?.workspaces.find( const activeWorkspace = userLookupResult?.workspaces.find(
(workspace) => workspace.id === activeTabId, (workspace) => workspace.id === activeTabId,
); );
@@ -139,7 +142,7 @@ export const SettingsAdminGeneral = () => {
onInputEnter={handleSearch} onInputEnter={handleSearch}
placeholder="Enter user ID or email address" placeholder="Enter user ID or email address"
fullWidth fullWidth
disabled={isLoading} disabled={isUserLookupLoading}
/> />
</StyledLinkContainer> </StyledLinkContainer>
<Button <Button
@@ -148,18 +151,12 @@ export const SettingsAdminGeneral = () => {
accent="blue" accent="blue"
title="Search" title="Search"
onClick={handleSearch} onClick={handleSearch}
disabled={!userIdentifier.trim() || isLoading} disabled={!userIdentifier.trim() || isUserLookupLoading}
/> />
</StyledContainer> </StyledContainer>
{(adminPanelError || impersonateError) && (
<StyledErrorSection>
{adminPanelError ?? impersonateError}
</StyledErrorSection>
)}
</Section> </Section>
{shouldShowUserData && ( {isDefined(userLookupResult) && (
<Section> <Section>
<StyledUserInfo> <StyledUserInfo>
<H1Title title="User Info" fontColor={H1TitleFontColor.Primary} /> <H1Title title="User Info" fontColor={H1TitleFontColor.Primary} />
@@ -180,10 +177,7 @@ export const SettingsAdminGeneral = () => {
/> />
</StyledTabListContainer> </StyledTabListContainer>
<StyledContentContainer> <StyledContentContainer>
<SettingsAdminWorkspaceContent <SettingsAdminWorkspaceContent activeWorkspace={activeWorkspace} />
activeWorkspace={activeWorkspace}
userId={userId}
/>
</StyledContentContainer> </StyledContentContainer>
</Section> </Section>
)} )}
@@ -1,19 +1,30 @@
import { Button, H2Title, IconUser, Toggle } from 'twenty-ui'; import { Button, H2Title, IconUser, Toggle } from 'twenty-ui';
import { currentUserState } from '@/auth/states/currentUserState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState'; import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { useFeatureFlag } from '@/settings/admin-panel/hooks/useFeatureFlag'; import { useFeatureFlagState } from '@/settings/admin-panel/hooks/useFeatureFlagState';
import { useImpersonate } from '@/settings/admin-panel/hooks/useImpersonate'; import { useImpersonationAuth } from '@/settings/admin-panel/hooks/useImpersonationAuth';
import { useImpersonationRedirect } from '@/settings/admin-panel/hooks/useImpersonationRedirect';
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo'; import { WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Table } from '@/ui/layout/table/components/Table'; import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow'; import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
import {
FeatureFlagKey,
useImpersonateMutation,
useUpdateWorkspaceFeatureFlagMutation,
} from '~/generated/graphql';
type SettingsAdminWorkspaceContentProps = { type SettingsAdminWorkspaceContentProps = {
activeWorkspace: WorkspaceInfo | undefined; activeWorkspace: WorkspaceInfo | undefined;
userId: string;
}; };
const StyledTable = styled(Table)` const StyledTable = styled(Table)`
@@ -22,17 +33,85 @@ const StyledTable = styled(Table)`
export const SettingsAdminWorkspaceContent = ({ export const SettingsAdminWorkspaceContent = ({
activeWorkspace, activeWorkspace,
userId,
}: SettingsAdminWorkspaceContentProps) => { }: SettingsAdminWorkspaceContentProps) => {
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState); const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
const [isImpersonateLoading, setIsImpersonationLoading] = useState(false);
const { enqueueSnackBar } = useSnackBar();
const [currentUser] = useRecoilState(currentUserState);
const { const { executeImpersonationAuth } = useImpersonationAuth();
handleImpersonate, const { executeImpersonationRedirect } = useImpersonationRedirect();
isLoading: isImpersonateLoading, const [updateFeatureFlag] = useUpdateWorkspaceFeatureFlagMutation();
canImpersonate, const [impersonate] = useImpersonateMutation();
} = useImpersonate();
const { handleFeatureFlagUpdate } = useFeatureFlag(); const { updateFeatureFlagState } = useFeatureFlagState();
const [userLookupResult, setUserLookupResult] = useRecoilState(
userLookupResultState,
);
const handleImpersonate = async (workspaceId: string) => {
if (!userLookupResult?.user.id) {
enqueueSnackBar('Please search for a user first', {
variant: SnackBarVariant.Error,
});
return;
}
setIsImpersonationLoading(true);
await impersonate({
variables: { userId: userLookupResult.user.id, workspaceId },
onCompleted: async (data) => {
const { loginToken, workspace } = data.impersonate;
const isCurrentWorkspace = workspace.id === activeWorkspace?.id;
setUserLookupResult(null);
if (isCurrentWorkspace) {
await executeImpersonationAuth(loginToken.token);
return;
}
return executeImpersonationRedirect(
workspace.workspaceUrls,
loginToken.token,
);
},
onError: (error) => {
enqueueSnackBar(`Failed to impersonate user. ${error.message}`, {
variant: SnackBarVariant.Error,
});
},
}).finally(() => {
setIsImpersonationLoading(false);
});
};
const handleFeatureFlagUpdate = async (
workspaceId: string,
featureFlag: FeatureFlagKey,
value: boolean,
) => {
const previousValue = userLookupResult?.workspaces
.find((workspace) => workspace.id === workspaceId)
?.featureFlags.find((flag) => flag.key === featureFlag)?.value;
updateFeatureFlagState(workspaceId, featureFlag, value);
await updateFeatureFlag({
variables: {
workspaceId,
featureFlag,
value,
},
onError: (error) => {
if (isDefined(previousValue)) {
updateFeatureFlagState(workspaceId, featureFlag, previousValue);
}
enqueueSnackBar(`Failed to update feature flag. ${error.message}`, {
variant: SnackBarVariant.Error,
});
},
});
};
if (!activeWorkspace) return null; if (!activeWorkspace) return null;
@@ -45,13 +124,13 @@ export const SettingsAdminWorkspaceContent = ({
}`} }`}
description={'Total Users'} description={'Total Users'}
/> />
{canImpersonate && ( {currentUser?.canImpersonate && (
<Button <Button
Icon={IconUser} Icon={IconUser}
variant="primary" variant="primary"
accent="blue" accent="blue"
title={'Impersonate'} title={'Impersonate'}
onClick={() => handleImpersonate(userId, activeWorkspace.id)} onClick={() => handleImpersonate(activeWorkspace.id)}
disabled={ disabled={
isImpersonateLoading || activeWorkspace.allowImpersonation === false isImpersonateLoading || activeWorkspace.allowImpersonation === false
} }
@@ -1,63 +0,0 @@
import { adminPanelErrorState } from '@/settings/admin-panel/states/adminPanelErrorState';
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import {
FeatureFlagKey,
useUpdateWorkspaceFeatureFlagMutation,
} from '~/generated/graphql';
export const useFeatureFlag = () => {
const [userLookupResult, setUserLookupResult] = useRecoilState(
userLookupResultState,
);
const setError = useSetRecoilState(adminPanelErrorState);
const [updateFeatureFlag] = useUpdateWorkspaceFeatureFlagMutation();
const handleFeatureFlagUpdate = async (
workspaceId: string,
featureFlag: FeatureFlagKey,
value: boolean,
) => {
setError(null);
const previousState = userLookupResult;
if (isDefined(userLookupResult)) {
setUserLookupResult({
...userLookupResult,
workspaces: userLookupResult.workspaces.map((workspace) =>
workspace.id === workspaceId
? {
...workspace,
featureFlags: workspace.featureFlags.map((flag) =>
flag.key === featureFlag ? { ...flag, value } : flag,
),
}
: workspace,
),
});
}
const response = await updateFeatureFlag({
variables: {
workspaceId,
featureFlag,
value,
},
onError: (error) => {
if (isDefined(previousState)) {
setUserLookupResult(previousState);
}
setError(error.message);
},
});
return !!response.data;
};
return {
handleFeatureFlagUpdate,
};
};
@@ -0,0 +1,36 @@
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { FeatureFlagKey } from '~/generated/graphql';
export const useFeatureFlagState = () => {
const [userLookupResult, setUserLookupResult] = useRecoilState(
userLookupResultState,
);
const updateFeatureFlagState = (
workspaceId: string,
featureFlag: FeatureFlagKey,
value: boolean,
) => {
if (!isDefined(userLookupResult)) return;
setUserLookupResult({
...userLookupResult,
workspaces: userLookupResult.workspaces.map((workspace) =>
workspace.id === workspaceId
? {
...workspace,
featureFlags: workspace.featureFlags.map((flag) =>
flag.key === featureFlag ? { ...flag, value } : flag,
),
}
: workspace,
),
});
};
return {
updateFeatureFlagState,
};
};
@@ -1,78 +0,0 @@
import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
import { AppPath } from '@/types/AppPath';
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);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const setIsAppWaitingForFreshObjectMetadata = useSetRecoilState(
isAppWaitingForFreshObjectMetadataState,
);
const { getAuthTokensFromLoginToken } = useAuth();
const [impersonate] = useImpersonateMutation();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleImpersonate = async (userId: string, workspaceId: string) => {
if (!userId.trim()) {
setError('Please enter a user ID');
return;
}
setIsLoading(true);
setError(null);
try {
const impersonateResult = await impersonate({
variables: { userId, workspaceId },
});
if (isDefined(impersonateResult.errors)) {
throw impersonateResult.errors;
}
if (!impersonateResult.data?.impersonate) {
throw new Error('No impersonate result');
}
const { loginToken, workspace } = impersonateResult.data.impersonate;
if (workspace.id === currentWorkspace?.id) {
setIsAppWaitingForFreshObjectMetadata(true);
await getAuthTokensFromLoginToken(loginToken.token);
setIsAppWaitingForFreshObjectMetadata(false);
return;
}
return redirectToWorkspaceDomain(
getWorkspaceUrl(workspace.workspaceUrls),
AppPath.Verify,
{
loginToken: loginToken.token,
},
);
} catch (error) {
setError('Failed to impersonate user. Please try again.');
setIsLoading(false);
}
};
return {
handleImpersonate,
isLoading,
error,
canImpersonate: currentUser?.canImpersonate,
};
};
@@ -0,0 +1,18 @@
import { useAuth } from '@/auth/hooks/useAuth';
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
import { useSetRecoilState } from 'recoil';
export const useImpersonationAuth = () => {
const { getAuthTokensFromLoginToken } = useAuth();
const setIsAppWaitingForFreshObjectMetadata = useSetRecoilState(
isAppWaitingForFreshObjectMetadataState,
);
const executeImpersonationAuth = async (loginToken: string) => {
setIsAppWaitingForFreshObjectMetadata(true);
await getAuthTokensFromLoginToken(loginToken);
setIsAppWaitingForFreshObjectMetadata(false);
};
return { executeImpersonationAuth };
};
@@ -0,0 +1,21 @@
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { AppPath } from '@/types/AppPath';
import { WorkspaceUrls } from '~/generated/graphql';
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
export const useImpersonationRedirect = () => {
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const executeImpersonationRedirect = (
workspaceUrls: WorkspaceUrls,
loginToken: string,
) => {
return redirectToWorkspaceDomain(
getWorkspaceUrl(workspaceUrls),
AppPath.Verify,
{ loginToken },
);
};
return { executeImpersonationRedirect };
};
@@ -1,42 +0,0 @@
import { adminPanelErrorState } from '@/settings/admin-panel/states/adminPanelErrorState';
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
import { useState } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useUserLookupAdminPanelMutation } from '~/generated/graphql';
export const useUserLookup = () => {
const setUserLookupResult = useSetRecoilState(userLookupResultState);
const setError = useSetRecoilState(adminPanelErrorState);
const [isLoading, setIsLoading] = useState(false);
const [userLookup] = useUserLookupAdminPanelMutation({
onCompleted: (data) => {
setIsLoading(false);
if (isDefined(data?.userLookupAdminPanel)) {
setUserLookupResult(data.userLookupAdminPanel);
}
},
onError: (error) => {
setIsLoading(false);
setError(error.message);
},
});
const handleUserLookup = async (userIdentifier: string) => {
setError(null);
setIsLoading(true);
setUserLookupResult(null);
const response = await userLookup({
variables: { userIdentifier },
});
return response.data?.userLookupAdminPanel;
};
return {
handleUserLookup,
isLoading,
};
};
@@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const adminPanelErrorState = atom<string | null>({
key: 'adminPanelErrorState',
default: null,
});
@@ -19,7 +19,7 @@ export const ENVIRONMENT_VARIABLES_GROUP_METADATA: Record<
position: 200, position: 200,
description: description:
'We use this to limit the number of requests to the server. This is useful to prevent abuse.', 'We use this to limit the number of requests to the server. This is useful to prevent abuse.',
isHiddenOnLoad: false, isHiddenOnLoad: true,
}, },
[EnvironmentVariablesGroup.StorageConfig]: { [EnvironmentVariablesGroup.StorageConfig]: {
position: 300, position: 300,
@@ -46,13 +46,13 @@ export const ENVIRONMENT_VARIABLES_GROUP_METADATA: Record<
[EnvironmentVariablesGroup.Logging]: { [EnvironmentVariablesGroup.Logging]: {
position: 700, position: 700,
description: '', description: '',
isHiddenOnLoad: false, isHiddenOnLoad: true,
}, },
[EnvironmentVariablesGroup.ExceptionHandler]: { [EnvironmentVariablesGroup.ExceptionHandler]: {
position: 800, position: 800,
description: description:
'By default, exceptions are sent to the logs. This should be enough for most self-hosting use-cases. For our cloud app we use Sentry.', 'By default, exceptions are sent to the logs. This should be enough for most self-hosting use-cases. For our cloud app we use Sentry.',
isHiddenOnLoad: false, isHiddenOnLoad: true,
}, },
[EnvironmentVariablesGroup.Other]: { [EnvironmentVariablesGroup.Other]: {
position: 900, position: 900,