Skip to content

Commit fb8b9cb

Browse files
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>
1 parent 499067a commit fb8b9cb

20 files changed

Lines changed: 641 additions & 282 deletions

File tree

packages/twenty-front/src/generated-admin/graphql.ts

Lines changed: 16 additions & 13 deletions
Large diffs are not rendered by default.

packages/twenty-front/src/generated-metadata/graphql.ts

Lines changed: 8 additions & 8 deletions
Large diffs are not rendered by default.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useApplicationAvatarColors } from '@/applications/hooks/useApplicationAvatarColors';
2+
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display';
3+
4+
type ApplicationDisplayData = {
5+
id?: string | null;
6+
name?: string | null;
7+
universalIdentifier?: string | null;
8+
logoUrl?: string | null;
9+
applicationRegistration?: {
10+
logoUrl?: string | null;
11+
} | null;
12+
};
13+
14+
type ApplicationDisplayProps = {
15+
application: ApplicationDisplayData;
16+
};
17+
18+
export const ApplicationDisplay = ({
19+
application,
20+
}: ApplicationDisplayProps) => {
21+
const colors = useApplicationAvatarColors(application);
22+
const name = application.name ?? '';
23+
const logoUrl =
24+
application.logoUrl ?? application.applicationRegistration?.logoUrl;
25+
26+
return (
27+
<>
28+
<Avatar
29+
type="app"
30+
size="md"
31+
avatarUrl={logoUrl ?? undefined}
32+
placeholder={name}
33+
placeholderColorSeed={application.universalIdentifier ?? name}
34+
color={colors?.color}
35+
backgroundColor={colors?.backgroundColor}
36+
borderColor={colors?.borderColor}
37+
/>
38+
<OverflowingTextWithTooltip text={name} />
39+
</>
40+
);
41+
};

packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay';
12
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
23
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
34
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
@@ -13,12 +14,7 @@ import { t } from '@lingui/core/macro';
1314
import { type ReactNode, useContext, useState } from 'react';
1415
import { assertUnreachable, getSettingsPath } from 'twenty-shared/utils';
1516
import { SettingsPath } from 'twenty-shared/types';
16-
import {
17-
H2Title,
18-
IconChevronRight,
19-
IconPinned,
20-
OverflowingTextWithTooltip,
21-
} from 'twenty-ui/display';
17+
import { H2Title, IconChevronRight, IconPinned } from 'twenty-ui/display';
2218
import { SearchInput } from 'twenty-ui/input';
2319
import { Section } from 'twenty-ui/layout';
2420
import { MenuItemToggle } from 'twenty-ui/navigation';
@@ -41,7 +37,6 @@ export const SettingsAdminApps = () => {
4137
const apolloAdminClient = useApolloAdminClient();
4238
const [searchQuery, setSearchQuery] = useState('');
4339
const [showPreInstalledOnly, setShowPreInstalledOnly] = useState(false);
44-
const { theme } = useContext(ThemeContext);
4540

4641
const { data } = useQuery(FindAllApplicationRegistrationsDocument, {
4742
client: apolloAdminClient,
@@ -136,41 +131,62 @@ export const SettingsAdminApps = () => {
136131
</TableRow>
137132
<TableBody>
138133
{filtered.map((registration) => (
139-
<TableRow
134+
<SettingsAdminAppsTableRow
140135
key={registration.id}
141-
to={getSettingsPath(
142-
SettingsPath.AdminPanelApplicationRegistrationDetail,
143-
{ applicationRegistrationId: registration.id },
144-
)}
145-
gridAutoColumns={TABLE_GRID}
146-
mobileGridAutoColumns={TABLE_GRID_MOBILE}
147-
isClickable
148-
>
149-
<TableCell
150-
color={themeCssVariables.font.color.primary}
151-
overflow="hidden"
152-
textOverflow="ellipsis"
153-
whiteSpace="nowrap"
154-
>
155-
<OverflowingTextWithTooltip text={registration.name} />
156-
</TableCell>
157-
<TableCell overflow="hidden" align="right">
158-
{getFormattedSource(registration)}
159-
</TableCell>
160-
<TableCell align="right">
161-
{registration.isListed ? t`Yes` : t`No`}
162-
</TableCell>
163-
<TableCell align="right">
164-
<IconChevronRight
165-
size={theme.icon.size.md}
166-
color={theme.font.color.tertiary}
167-
/>
168-
</TableCell>
169-
</TableRow>
136+
registration={registration}
137+
getFormattedSource={getFormattedSource}
138+
/>
170139
))}
171140
</TableBody>
172141
</Table>
173142
</StyledTableContainer>
174143
</Section>
175144
);
176145
};
146+
147+
type SettingsAdminAppsTableRowProps = {
148+
registration: ApplicationRegistrationFragmentFragment;
149+
getFormattedSource: (
150+
registration: ApplicationRegistrationFragmentFragment,
151+
) => string;
152+
};
153+
154+
const SettingsAdminAppsTableRow = ({
155+
registration,
156+
getFormattedSource,
157+
}: SettingsAdminAppsTableRowProps) => {
158+
const { theme } = useContext(ThemeContext);
159+
160+
return (
161+
<TableRow
162+
to={getSettingsPath(
163+
SettingsPath.AdminPanelApplicationRegistrationDetail,
164+
{ applicationRegistrationId: registration.id },
165+
)}
166+
gridAutoColumns={TABLE_GRID}
167+
mobileGridAutoColumns={TABLE_GRID_MOBILE}
168+
isClickable
169+
>
170+
<TableCell
171+
color={themeCssVariables.font.color.primary}
172+
gap={themeCssVariables.spacing[2]}
173+
minWidth="0"
174+
overflow="hidden"
175+
>
176+
<ApplicationDisplay application={registration} />
177+
</TableCell>
178+
<TableCell overflow="hidden" align="right">
179+
{getFormattedSource(registration)}
180+
</TableCell>
181+
<TableCell align="right">
182+
{registration.isListed ? t`Yes` : t`No`}
183+
</TableCell>
184+
<TableCell align="right">
185+
<IconChevronRight
186+
size={theme.icon.size.md}
187+
color={theme.font.color.tertiary}
188+
/>
189+
</TableCell>
190+
</TableRow>
191+
);
192+
};

0 commit comments

Comments
 (0)