Skip to content

Commit 23dfbf3

Browse files
committed
♻️(frontend) add user avatar to thread comments
We extracted the UserAvatar component from the doc-share feature and integrated it into the users feature. It will be used in the thread comments feature as well.
1 parent dea792e commit 23dfbf3

File tree

10 files changed

+147
-73
lines changed

10 files changed

+147
-73
lines changed

src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ test.describe('Doc Comments', () => {
4444
await thread.locator('[data-test="addreaction"]').first().click();
4545
await thread.getByRole('button', { name: '👍' }).click();
4646

47+
await expect(
48+
thread.getByRole('img', { name: 'E2E Chromium' }).first(),
49+
).toBeVisible();
4750
await expect(thread.getByText('This is a comment').first()).toBeVisible();
4851
await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible();
4952
await expect(thread.locator('.bn-comment-reaction')).toHaveText('👍1');
@@ -81,6 +84,9 @@ test.describe('Doc Comments', () => {
8184
await otherThread.locator('[data-test="save"]').click();
8285

8386
// We check that the second user can see the comment he just made
87+
await expect(
88+
otherThread.getByRole('img', { name: `E2E ${otherBrowserName}` }).first(),
89+
).toBeVisible();
8490
await expect(
8591
otherThread.getByText('This is a comment from the other user').first(),
8692
).toBeVisible();

src/frontend/apps/impress/cunningham.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ const dsfrTheme = {
9898
},
9999
font: {
100100
families: {
101-
base: 'Marianne',
102-
accent: 'Marianne',
101+
base: 'Marianne, Inter, Roboto Flex Variable, sans-serif',
102+
accent: 'Marianne, Inter, Roboto Flex Variable, sans-serif',
103103
},
104104
},
105105
},

src/frontend/apps/impress/src/cunningham/cunningham-tokens.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,10 @@
556556
--c--theme--logo--widthHeader: 110px;
557557
--c--theme--logo--widthFooter: 220px;
558558
--c--theme--logo--alt: gouvernement logo;
559-
--c--theme--font--families--base: marianne;
560-
--c--theme--font--families--accent: marianne;
559+
--c--theme--font--families--base:
560+
marianne, inter, roboto flex variable, sans-serif;
561+
--c--theme--font--families--accent:
562+
marianne, inter, roboto flex variable, sans-serif;
561563
--c--components--la-gaufre: true;
562564
--c--components--home-proconnect: true;
563565
--c--components--favicon--ico: /assets/favicon-dsfr.ico;

src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,12 @@ export const tokens = {
436436
widthFooter: '220px',
437437
alt: 'Gouvernement Logo',
438438
},
439-
font: { families: { base: 'Marianne', accent: 'Marianne' } },
439+
font: {
440+
families: {
441+
base: 'Marianne, Inter, Roboto Flex Variable, sans-serif',
442+
accent: 'Marianne, Inter, Roboto Flex Variable, sans-serif',
443+
},
444+
},
440445
},
441446
components: {
442447
'la-gaufre': true,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react';
2+
3+
import { Box, BoxType } from '@/components';
4+
5+
type AvatarSvgProps = {
6+
initials: string;
7+
background: string;
8+
fontFamily?: string;
9+
} & BoxType;
10+
11+
export const AvatarSvg: React.FC<AvatarSvgProps> = ({
12+
initials,
13+
background,
14+
fontFamily,
15+
...props
16+
}) => (
17+
<Box
18+
as="svg"
19+
xmlns="http://www.w3.org/2000/svg"
20+
width="24"
21+
height="24"
22+
viewBox="0 0 24 24"
23+
{...props}
24+
>
25+
<rect
26+
x="0.5"
27+
y="0.5"
28+
width="23"
29+
height="23"
30+
rx="11.5"
31+
ry="11.5"
32+
fill={background}
33+
stroke="rgba(255,255,255,0.5)"
34+
strokeWidth="1"
35+
/>
36+
<text
37+
x="50%"
38+
y="50%"
39+
dy="0.35em"
40+
textAnchor="middle"
41+
fontSize="10"
42+
fontWeight="600"
43+
fill="rgba(255,255,255,0.9)"
44+
fontFamily={fontFamily || 'Arial'}
45+
>
46+
{initials}
47+
</text>
48+
</Box>
49+
);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { renderToStaticMarkup } from 'react-dom/server';
2+
3+
import { tokens } from '@/cunningham';
4+
5+
import { AvatarSvg } from './AvatarSvg';
6+
7+
const colors = tokens.themes.default.theme.colors;
8+
9+
const avatarsColors = [
10+
colors['blue-500'],
11+
colors['brown-500'],
12+
colors['cyan-500'],
13+
colors['gold-500'],
14+
colors['green-500'],
15+
colors['olive-500'],
16+
colors['orange-500'],
17+
colors['pink-500'],
18+
colors['purple-500'],
19+
colors['yellow-500'],
20+
];
21+
22+
const getColorFromName = (name: string) => {
23+
let hash = 0;
24+
for (let i = 0; i < name.length; i++) {
25+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
26+
}
27+
return avatarsColors[Math.abs(hash) % avatarsColors.length];
28+
};
29+
30+
const getInitialFromName = (name: string) => {
31+
const splitName = name?.split(' ');
32+
return (splitName[0]?.charAt(0) || '?') + (splitName?.[1]?.charAt(0) || '');
33+
};
34+
35+
type UserAvatarProps = {
36+
fullName?: string;
37+
background?: string;
38+
};
39+
40+
export const UserAvatar = ({ fullName, background }: UserAvatarProps) => {
41+
const name = fullName?.trim() || '?';
42+
43+
return (
44+
<AvatarSvg
45+
className="--docs--user-avatar"
46+
initials={getInitialFromName(name).toUpperCase()}
47+
background={background || getColorFromName(name)}
48+
/>
49+
);
50+
};
51+
52+
export const avatarUrlFromName = (
53+
fullName?: string,
54+
fontFamily?: string,
55+
): string => {
56+
const name = fullName?.trim() || '?';
57+
const initials = getInitialFromName(name).toUpperCase();
58+
const background = getColorFromName(name);
59+
60+
const svgMarkup = renderToStaticMarkup(
61+
<AvatarSvg
62+
className="--docs--user-avatar"
63+
initials={initials}
64+
background={background}
65+
fontFamily={fontFamily}
66+
/>,
67+
);
68+
69+
return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svgMarkup)}`;
70+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './Auth';
22
export * from './ButtonLogin';
3+
export * from './UserAvatar';

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import { css } from 'styled-components';
1717
import * as Y from 'yjs';
1818

1919
import { Box, TextErrors } from '@/components';
20+
import { useCunninghamTheme } from '@/cunningham';
2021
import { Doc, useIsCollaborativeEditable } from '@/docs/doc-management';
21-
import { useAuth } from '@/features/auth';
22+
import { avatarUrlFromName, useAuth } from '@/features/auth';
2223

2324
import {
2425
useHeadings,
@@ -81,6 +82,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
8182
const { user } = useAuth();
8283
const { setEditor } = useEditorStore();
8384
const { t } = useTranslation();
85+
const { themeTokens } = useCunninghamTheme();
8486

8587
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
8688
const isConnectedToCollabServer = provider.isSynced;
@@ -166,7 +168,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
166168
return {
167169
id: encodedURIUserId,
168170
username: fullName || t('Anonymous'),
169-
avatarUrl: 'https://i.pravatar.cc/300',
171+
avatarUrl: avatarUrlFromName(
172+
fullName,
173+
themeTokens?.font?.families?.base,
174+
),
170175
};
171176
}),
172177
);

src/frontend/apps/impress/src/features/docs/doc-share/components/SearchUserRow.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import {
44
QuickSearchItemContentProps,
55
} from '@/components/quick-search';
66
import { useCunninghamTheme } from '@/cunningham';
7-
import { User } from '@/features/auth';
8-
9-
import { UserAvatar } from './UserAvatar';
7+
import { User, UserAvatar } from '@/features/auth';
108

119
type Props = {
1210
user: User;
@@ -36,7 +34,7 @@ export const SearchUserRow = ({
3634
className="--docs--search-user-row"
3735
>
3836
<UserAvatar
39-
user={user}
37+
fullName={user.full_name || user.email}
4038
background={
4139
isInvitation ? colorsTokens['greyscale-400'] : undefined
4240
}

src/frontend/apps/impress/src/features/docs/doc-share/components/UserAvatar.tsx

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)