Add admin avatars and app logos (#20001)

## Summary
- Add user avatars and workspace logos to the admin general table and
workspace member detail view
- Show app icons in the admin app registrations table and reuse the
shared application display component
- Expose the needed avatar and logo fields through admin GraphQL queries
and backend lookup/statistics services
- Keep workspace fallback behavior consistent when no logo is set and
clean up a few local table styling duplicates

## Testing
- `./node_modules/.bin/tsc -p packages/twenty-front/tsconfig.json
--noEmit --pretty false`
- Manual UI verification of the admin general, workspace detail, and
apps tables

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas des Francs
2026-04-26 19:15:20 +02:00
committed by GitHub
parent 499067ae14
commit fb8b9cb86c
20 changed files with 643 additions and 284 deletions
@@ -1,3 +1,4 @@
import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay';
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';
@@ -13,12 +14,7 @@ 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,
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
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';
@@ -41,7 +37,6 @@ export const SettingsAdminApps = () => {
const apolloAdminClient = useApolloAdminClient();
const [searchQuery, setSearchQuery] = useState('');
const [showPreInstalledOnly, setShowPreInstalledOnly] = useState(false);
const { theme } = useContext(ThemeContext);
const { data } = useQuery(FindAllApplicationRegistrationsDocument, {
client: apolloAdminClient,
@@ -136,37 +131,11 @@ export const SettingsAdminApps = () => {
</TableRow>
<TableBody>
{filtered.map((registration) => (
<TableRow
<SettingsAdminAppsTableRow
key={registration.id}
to={getSettingsPath(
SettingsPath.AdminPanelApplicationRegistrationDetail,
{ applicationRegistrationId: registration.id },
)}
gridAutoColumns={TABLE_GRID}
mobileGridAutoColumns={TABLE_GRID_MOBILE}
isClickable
>
<TableCell
color={themeCssVariables.font.color.primary}
overflow="hidden"
textOverflow="ellipsis"
whiteSpace="nowrap"
>
<OverflowingTextWithTooltip text={registration.name} />
</TableCell>
<TableCell overflow="hidden" align="right">
{getFormattedSource(registration)}
</TableCell>
<TableCell align="right">
{registration.isListed ? t`Yes` : t`No`}
</TableCell>
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
</TableCell>
</TableRow>
registration={registration}
getFormattedSource={getFormattedSource}
/>
))}
</TableBody>
</Table>
@@ -174,3 +143,50 @@ export const SettingsAdminApps = () => {
</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"
>
<ApplicationDisplay application={registration} />
</TableCell>
<TableCell overflow="hidden" align="right">
{getFormattedSource(registration)}
</TableCell>
<TableCell align="right">
{registration.isListed ? t`Yes` : t`No`}
</TableCell>
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
</TableCell>
</TableRow>
);
};