Skip to content

Commit

Permalink
Admin panel init (#8742)
Browse files Browse the repository at this point in the history
WIP
Related issues - 
#7090 
#8547 
Master issue - 
#4499

---------

Co-authored-by: Félix Malfait <[email protected]>
  • Loading branch information
ehconitin and FelixMalfait authored Nov 28, 2024
1 parent abe9185 commit e96ad9a
Show file tree
Hide file tree
Showing 38 changed files with 1,197 additions and 232 deletions.
176 changes: 176 additions & 0 deletions packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,12 @@ export type Mutation = {
updateOneServerlessFunction: ServerlessFunction;
updatePasswordViaResetToken: InvalidatePassword;
updateWorkspace: Workspace;
updateWorkspaceFeatureFlag: Scalars['Boolean'];
uploadFile: Scalars['String'];
uploadImage: Scalars['String'];
uploadProfilePicture: Scalars['String'];
uploadWorkspaceLogo: Scalars['String'];
userLookupAdminPanel: UserLookup;
verify: Verify;
};

Expand Down Expand Up @@ -679,6 +681,13 @@ export type MutationUpdateWorkspaceArgs = {
};


export type MutationUpdateWorkspaceFeatureFlagArgs = {
featureFlag: Scalars['String'];
value: Scalars['Boolean'];
workspaceId: Scalars['String'];
};


export type MutationUploadFileArgs = {
file: Scalars['Upload'];
fileFolder?: InputMaybe<FileFolder>;
Expand All @@ -701,6 +710,11 @@ export type MutationUploadWorkspaceLogoArgs = {
};


export type MutationUserLookupAdminPanelArgs = {
userIdentifier: Scalars['String'];
};


export type MutationVerifyArgs = {
loginToken: Scalars['String'];
};
Expand Down Expand Up @@ -1247,6 +1261,20 @@ export type UserExists = {
exists: Scalars['Boolean'];
};

export type UserInfo = {
__typename?: 'UserInfo';
email: Scalars['String'];
firstName?: Maybe<Scalars['String']>;
id: Scalars['String'];
lastName?: Maybe<Scalars['String']>;
};

export type UserLookup = {
__typename?: 'UserLookup';
user: UserInfo;
workspaces: Array<WorkspaceInfo>;
};

export type UserMappingOptionsUser = {
__typename?: 'UserMappingOptionsUser';
user?: Maybe<Scalars['String']>;
Expand Down Expand Up @@ -1285,6 +1313,7 @@ export type Workspace = {
__typename?: 'Workspace';
activationStatus: WorkspaceActivationStatus;
allowImpersonation: Scalars['Boolean'];
billingEntitlements?: Maybe<Array<BillingEntitlement>>;
billingSubscriptions?: Maybe<Array<BillingSubscription>>;
createdAt: Scalars['DateTime'];
currentBillingSubscription?: Maybe<BillingSubscription>;
Expand All @@ -1305,6 +1334,12 @@ export type Workspace = {
};


export type WorkspaceBillingEntitlementsArgs = {
filter?: BillingEntitlementFilter;
sorting?: Array<BillingEntitlementSort>;
};


export type WorkspaceBillingSubscriptionsArgs = {
filter?: BillingSubscriptionFilter;
sorting?: Array<BillingSubscriptionSort>;
Expand All @@ -1331,6 +1366,16 @@ export type WorkspaceEdge = {
node: Workspace;
};

export type WorkspaceInfo = {
__typename?: 'WorkspaceInfo';
featureFlags: Array<FeatureFlag>;
id: Scalars['String'];
logo?: Maybe<Scalars['String']>;
name: Scalars['String'];
totalUsers: Scalars['Float'];
users: Array<UserInfo>;
};

export type WorkspaceInvitation = {
__typename?: 'WorkspaceInvitation';
email: Scalars['String'];
Expand Down Expand Up @@ -1376,6 +1421,30 @@ export type WorkspaceNameAndId = {
id: Scalars['String'];
};

export type BillingEntitlement = {
__typename?: 'billingEntitlement';
id: Scalars['UUID'];
key: Scalars['String'];
value: Scalars['Boolean'];
workspaceId: Scalars['String'];
};

export type BillingEntitlementFilter = {
and?: InputMaybe<Array<BillingEntitlementFilter>>;
id?: InputMaybe<UuidFilterComparison>;
or?: InputMaybe<Array<BillingEntitlementFilter>>;
};

export type BillingEntitlementSort = {
direction: SortDirection;
field: BillingEntitlementSortFields;
nulls?: InputMaybe<SortNulls>;
};

export enum BillingEntitlementSortFields {
Id = 'id'
}

export type Field = {
__typename?: 'field';
createdAt: Scalars['DateTime'];
Expand Down Expand Up @@ -1787,6 +1856,22 @@ export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]

export type SkipSyncEmailOnboardingStepMutation = { __typename?: 'Mutation', skipSyncEmailOnboardingStep: { __typename?: 'OnboardingStepSuccess', success: boolean } };

export type UpdateWorkspaceFeatureFlagMutationVariables = Exact<{
workspaceId: Scalars['String'];
featureFlag: Scalars['String'];
value: Scalars['Boolean'];
}>;


export type UpdateWorkspaceFeatureFlagMutation = { __typename?: 'Mutation', updateWorkspaceFeatureFlag: boolean };

export type UserLookupAdminPanelMutationVariables = Exact<{
userIdentifier: Scalars['String'];
}>;


export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: string, value: boolean }> }> } };

export type CreateOidcIdentityProviderMutationVariables = Exact<{
input: SetupOidcSsoInput;
}>;
Expand Down Expand Up @@ -3178,6 +3263,97 @@ export function useSkipSyncEmailOnboardingStepMutation(baseOptions?: Apollo.Muta
export type SkipSyncEmailOnboardingStepMutationHookResult = ReturnType<typeof useSkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult<SkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>;
export const UpdateWorkspaceFeatureFlagDocument = gql`
mutation UpdateWorkspaceFeatureFlag($workspaceId: String!, $featureFlag: String!, $value: Boolean!) {
updateWorkspaceFeatureFlag(
workspaceId: $workspaceId
featureFlag: $featureFlag
value: $value
)
}
`;
export type UpdateWorkspaceFeatureFlagMutationFn = Apollo.MutationFunction<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>;

/**
* __useUpdateWorkspaceFeatureFlagMutation__
*
* To run a mutation, you first call `useUpdateWorkspaceFeatureFlagMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateWorkspaceFeatureFlagMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateWorkspaceFeatureFlagMutation, { data, loading, error }] = useUpdateWorkspaceFeatureFlagMutation({
* variables: {
* workspaceId: // value for 'workspaceId'
* featureFlag: // value for 'featureFlag'
* value: // value for 'value'
* },
* });
*/
export function useUpdateWorkspaceFeatureFlagMutation(baseOptions?: Apollo.MutationHookOptions<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>(UpdateWorkspaceFeatureFlagDocument, options);
}
export type UpdateWorkspaceFeatureFlagMutationHookResult = ReturnType<typeof useUpdateWorkspaceFeatureFlagMutation>;
export type UpdateWorkspaceFeatureFlagMutationResult = Apollo.MutationResult<UpdateWorkspaceFeatureFlagMutation>;
export type UpdateWorkspaceFeatureFlagMutationOptions = Apollo.BaseMutationOptions<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>;
export const UserLookupAdminPanelDocument = gql`
mutation UserLookupAdminPanel($userIdentifier: String!) {
userLookupAdminPanel(userIdentifier: $userIdentifier) {
user {
id
email
firstName
lastName
}
workspaces {
id
name
logo
totalUsers
users {
id
email
firstName
lastName
}
featureFlags {
key
value
}
}
}
}
`;
export type UserLookupAdminPanelMutationFn = Apollo.MutationFunction<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>;

/**
* __useUserLookupAdminPanelMutation__
*
* To run a mutation, you first call `useUserLookupAdminPanelMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUserLookupAdminPanelMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [userLookupAdminPanelMutation, { data, loading, error }] = useUserLookupAdminPanelMutation({
* variables: {
* userIdentifier: // value for 'userIdentifier'
* },
* });
*/
export function useUserLookupAdminPanelMutation(baseOptions?: Apollo.MutationHookOptions<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>(UserLookupAdminPanelDocument, options);
}
export type UserLookupAdminPanelMutationHookResult = ReturnType<typeof useUserLookupAdminPanelMutation>;
export type UserLookupAdminPanelMutationResult = Apollo.MutationResult<UserLookupAdminPanelMutation>;
export type UserLookupAdminPanelMutationOptions = Apollo.BaseMutationOptions<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>;
export const CreateOidcIdentityProviderDocument = gql`
mutation CreateOIDCIdentityProvider($input: SetupOIDCSsoInput!) {
createOIDCIdentityProvider(input: $input) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,17 +234,6 @@ const testCases = [
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.InviteTeam, res: AppPath.InviteTeam },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.Completed, res: undefined },

{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PlanRequired, res: AppPath.PlanRequired },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Unpaid, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.PastDue, onboardingStatus: OnboardingStatus.Completed, res: undefined },
{ loc: AppPath.Impersonate, isLoggedIn: false, subscriptionStatus: undefined, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.WorkspaceActivation, res: AppPath.CreateWorkspace },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.ProfileCreation, res: AppPath.CreateProfile },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.SyncEmail, res: AppPath.SyncEmails },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.InviteTeam, res: AppPath.InviteTeam },
{ loc: AppPath.Impersonate, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Active, onboardingStatus: OnboardingStatus.Completed, res: undefined },

{ loc: AppPath.Authorize, isLoggedIn: true, subscriptionStatus: undefined, onboardingStatus: OnboardingStatus.PlanRequired, res: AppPath.PlanRequired },
{ loc: AppPath.Authorize, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Canceled, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
{ loc: AppPath.Authorize, isLoggedIn: true, subscriptionStatus: SubscriptionStatus.Unpaid, onboardingStatus: OnboardingStatus.Completed, res: '/settings/billing' },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCreateAppRouter } from '@/app/hooks/useCreateAppRouter';
import { currentUserState } from '@/auth/states/currentUserState';
import { billingState } from '@/client-config/states/billingState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { RouterProvider } from 'react-router-dom';
Expand All @@ -16,13 +17,18 @@ export const AppRouter = () => {
const isBillingPageEnabled =
billing?.isBillingEnabled && !isFreeAccessEnabled;

const currentUser = useRecoilValue(currentUserState);

const isAdminPageEnabled = currentUser?.canImpersonate;

return (
<RouterProvider
router={useCreateAppRouter(
isBillingPageEnabled,
isCRMMigrationEnabled,
isServerlessFunctionSettingsEnabled,
isSSOEnabled,
isAdminPageEnabled,
)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,18 +242,34 @@ const SettingsSecuritySSOIdentifyProvider = lazy(() =>
),
);

const SettingsAdmin = lazy(() =>
import('~/pages/settings/admin-panel/SettingsAdmin').then((module) => ({
default: module.SettingsAdmin,
})),
);

const SettingsAdminFeatureFlags = lazy(() =>
import('~/pages/settings/admin-panel/SettingsAdminFeatureFlags').then(
(module) => ({
default: module.SettingsAdminFeatureFlags,
}),
),
);

type SettingsRoutesProps = {
isBillingEnabled?: boolean;
isCRMMigrationEnabled?: boolean;
isServerlessFunctionSettingsEnabled?: boolean;
isSSOEnabled?: boolean;
isAdminPageEnabled?: boolean;
};

export const SettingsRoutes = ({
isBillingEnabled,
isCRMMigrationEnabled,
isServerlessFunctionSettingsEnabled,
isSSOEnabled,
isAdminPageEnabled,
}: SettingsRoutesProps) => (
<Suspense fallback={<SettingsSkeletonLoader />}>
<Routes>
Expand Down Expand Up @@ -375,6 +391,15 @@ export const SettingsRoutes = ({
/>
</>
)}
{isAdminPageEnabled && (
<>
<Route path={SettingsPath.AdminPanel} element={<SettingsAdmin />} />
<Route
path={SettingsPath.FeatureFlags}
element={<SettingsAdminFeatureFlags />}
/>
</>
)}
</Routes>
</Suspense>
);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { Authorize } from '~/pages/auth/Authorize';
import { Invite } from '~/pages/auth/Invite';
import { PasswordReset } from '~/pages/auth/PasswordReset';
import { SignInUp } from '~/pages/auth/SignInUp';
import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect';
import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
Expand All @@ -30,6 +29,7 @@ export const useCreateAppRouter = (
isCRMMigrationEnabled?: boolean,
isServerlessFunctionSettingsEnabled?: boolean,
isSSOEnabled?: boolean,
isAdminPageEnabled?: boolean,
) =>
createBrowserRouter(
createRoutesFromElements(
Expand All @@ -54,7 +54,6 @@ export const useCreateAppRouter = (
element={<PaymentSuccess />}
/>
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
<Route path={AppPath.RecordShowPage} element={<RecordShowPage />} />
<Route
Expand All @@ -67,6 +66,7 @@ export const useCreateAppRouter = (
isServerlessFunctionSettingsEnabled
}
isSSOEnabled={isSSOEnabled}
isAdminPageEnabled={isAdminPageEnabled}
/>
}
/>
Expand Down
Loading

0 comments on commit e96ad9a

Please sign in to comment.