Files
twenty/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx
T
martmull 948ed964bb Add isConfigured to application registration in App admin panel (#20326)
## After
Added "Configured" column in admin panel apps tab
<img width="1171" height="611" alt="image"
src="https://github.com/user-attachments/assets/e6850b39-5789-4ad2-b3df-192a28cb03e2"
/>

Add a banner to ask to configure the application 
<img width="1218" height="483" alt="image"
src="https://github.com/user-attachments/assets/1491363c-3479-41ca-97b7-c7dc9e07ff16"
/>
2026-05-07 09:59:01 +02:00

208 lines
6.9 KiB
TypeScript

import { AppChip } from '@/applications/components/AppChip';
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
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 { useQuery } from '@apollo/client/react';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { type ReactNode, useContext, useState } from 'react';
import { assertUnreachable, getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import { H2Title, IconChevronRight, IconPinned } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { Tag } from 'twenty-ui/components';
import {
type ApplicationRegistrationFragmentFragment,
ApplicationRegistrationSourceType,
FindAllApplicationRegistrationsDocument,
} from '~/generated-admin/graphql';
const StyledTableContainer = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
margin-top: ${themeCssVariables.spacing[3]};
`;
const TABLE_GRID = '1fr 100px 100px 100px 40px';
const TABLE_GRID_MOBILE = '3fr 3fr 1fr 1fr 40px';
export const SettingsAdminApps = () => {
const apolloAdminClient = useApolloAdminClient();
const [searchQuery, setSearchQuery] = useState('');
const [showPreInstalledOnly, setShowPreInstalledOnly] = useState(false);
const { data } = useQuery(FindAllApplicationRegistrationsDocument, {
client: apolloAdminClient,
});
const registrations = data?.findAllApplicationRegistrations ?? [];
const query = searchQuery.trim().toLowerCase();
const filtered = registrations
.filter((registration) => {
if (showPreInstalledOnly && !registration.isPreInstalled) {
return false;
}
if (query.length === 0) {
return true;
}
return (
registration.name.toLowerCase().includes(query) ||
(registration.sourcePackage ?? '').toLowerCase().includes(query) ||
registration.universalIdentifier.toLowerCase().includes(query)
);
})
.toSorted((a, b) => Number(a.isConfigured) - Number(b.isConfigured));
const getFormattedSource = (
registration: ApplicationRegistrationFragmentFragment,
) => {
switch (registration.sourceType) {
case ApplicationRegistrationSourceType.TARBALL: {
return 'Tarball';
}
case ApplicationRegistrationSourceType.NPM: {
return 'NPM';
}
case ApplicationRegistrationSourceType.OAUTH_ONLY: {
return 'OAuth';
}
case ApplicationRegistrationSourceType.LOCAL: {
return 'Local';
}
default:
return assertUnreachable(registration.sourceType);
}
};
return (
<Section>
<H2Title
title={t`All App Registrations`}
description={t`All application registrations across the platform, including orphaned marketplace apps`}
/>
<SearchInput
placeholder={t`Search registrations...`}
value={searchQuery}
onChange={setSearchQuery}
filterDropdown={(filterButton: ReactNode) => (
<Dropdown
dropdownId="settings-admin-apps-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={filterButton}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconPinned}
onToggleChange={() =>
setShowPreInstalledOnly(!showPreInstalledOnly)
}
toggled={showPreInstalledOnly}
text={t`Pre-installed only`}
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
)}
/>
<StyledTableContainer>
<Table>
<TableRow
gridAutoColumns={TABLE_GRID}
mobileGridAutoColumns={TABLE_GRID_MOBILE}
>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader align="right">{t`Source`}</TableHeader>
<TableHeader align="right">{t`Listed`}</TableHeader>
<TableHeader align="right">{t`Configured`}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
<TableBody>
{filtered.map((registration) => (
<SettingsAdminAppsTableRow
key={registration.id}
registration={registration}
getFormattedSource={getFormattedSource}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
</Section>
);
};
type SettingsAdminAppsTableRowProps = {
registration: ApplicationRegistrationFragmentFragment;
getFormattedSource: (
registration: ApplicationRegistrationFragmentFragment,
) => string;
};
const SettingsAdminAppsTableRow = ({
registration,
getFormattedSource,
}: SettingsAdminAppsTableRowProps) => {
const { theme } = useContext(ThemeContext);
return (
<TableRow
to={getSettingsPath(
SettingsPath.AdminPanelApplicationRegistrationDetail,
{ applicationRegistrationId: registration.id },
)}
gridAutoColumns={TABLE_GRID}
mobileGridAutoColumns={TABLE_GRID_MOBILE}
isClickable
>
<TableCell
color={themeCssVariables.font.color.primary}
gap={themeCssVariables.spacing[2]}
minWidth="0"
overflow="hidden"
>
<AppChip
size="md"
fallbackApplicationData={{
logo: registration.logoUrl,
name: registration.name,
}}
/>
</TableCell>
<TableCell overflow="hidden" align="right">
{getFormattedSource(registration)}
</TableCell>
<TableCell align="right">
{registration.isListed ? t`Yes` : t`No`}
</TableCell>
<TableCell align="right">
<Tag
color={registration.isConfigured ? 'green' : 'red'}
text={registration.isConfigured ? t`Yes` : t`No`}
/>
</TableCell>
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
</TableCell>
</TableRow>
);
};