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:
@ -1,19 +1,30 @@
|
||||
import { Button, H2Title, IconUser, Toggle } from 'twenty-ui';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||
import { useFeatureFlag } from '@/settings/admin-panel/hooks/useFeatureFlag';
|
||||
import { useImpersonate } from '@/settings/admin-panel/hooks/useImpersonate';
|
||||
import { useFeatureFlagState } from '@/settings/admin-panel/hooks/useFeatureFlagState';
|
||||
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 { 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 { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
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 = {
|
||||
activeWorkspace: WorkspaceInfo | undefined;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
@ -22,17 +33,85 @@ const StyledTable = styled(Table)`
|
||||
|
||||
export const SettingsAdminWorkspaceContent = ({
|
||||
activeWorkspace,
|
||||
userId,
|
||||
}: SettingsAdminWorkspaceContentProps) => {
|
||||
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
|
||||
const [isImpersonateLoading, setIsImpersonationLoading] = useState(false);
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const [currentUser] = useRecoilState(currentUserState);
|
||||
|
||||
const {
|
||||
handleImpersonate,
|
||||
isLoading: isImpersonateLoading,
|
||||
canImpersonate,
|
||||
} = useImpersonate();
|
||||
const { executeImpersonationAuth } = useImpersonationAuth();
|
||||
const { executeImpersonationRedirect } = useImpersonationRedirect();
|
||||
const [updateFeatureFlag] = useUpdateWorkspaceFeatureFlagMutation();
|
||||
const [impersonate] = useImpersonateMutation();
|
||||
|
||||
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;
|
||||
|
||||
@ -45,13 +124,13 @@ export const SettingsAdminWorkspaceContent = ({
|
||||
}`}
|
||||
description={'Total Users'}
|
||||
/>
|
||||
{canImpersonate && (
|
||||
{currentUser?.canImpersonate && (
|
||||
<Button
|
||||
Icon={IconUser}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
title={'Impersonate'}
|
||||
onClick={() => handleImpersonate(userId, activeWorkspace.id)}
|
||||
onClick={() => handleImpersonate(activeWorkspace.id)}
|
||||
disabled={
|
||||
isImpersonateLoading || activeWorkspace.allowImpersonation === false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user