- Replace avatar image from API with gradient avatar from twake-mui (#457)

- Display 2-letter initials (e.g., "JD" for "John Doe") instead of single letter
- Add getInitials helper function in avatarUtils for reusability
- Update Avatar in PeopleSearch, CalendarSearch, and Menubar components
- Remove avatarUrl prop usage across all Avatar components
This commit is contained in:
lenhanphung
2026-01-13 20:44:42 +07:00
committed by GitHub
parent 00266d2701
commit 8b3afc1737
9 changed files with 206 additions and 33 deletions
+4 -4
View File
@@ -100,7 +100,7 @@ describe("Calendar App Component Display Tests", () => {
/>, />,
preloadedState preloadedState
); );
expect(screen.getByText("t")).toBeInTheDocument(); expect(screen.getByText("T")).toBeInTheDocument();
}); });
// Edge cases for avatar display logic // Edge cases for avatar display logic
@@ -131,7 +131,7 @@ describe("Calendar App Component Display Tests", () => {
/>, />,
preloadedState preloadedState
); );
expect(screen.getByText("t")).toBeInTheDocument(); expect(screen.getByText("T")).toBeInTheDocument();
}); });
it("handles user with only name", () => { it("handles user with only name", () => {
@@ -161,7 +161,7 @@ describe("Calendar App Component Display Tests", () => {
/>, />,
preloadedState preloadedState
); );
expect(screen.getByText("t")).toBeInTheDocument(); expect(screen.getByText("T")).toBeInTheDocument();
}); });
it("handles user with both name and family_name", () => { it("handles user with both name and family_name", () => {
@@ -221,7 +221,7 @@ describe("Calendar App Component Display Tests", () => {
/>, />,
preloadedState preloadedState
); );
expect(screen.getByText("t")).toBeInTheDocument(); expect(screen.getByText("T")).toBeInTheDocument();
}); });
it("handles user with empty email", () => { it("handles user with empty email", () => {
@@ -2032,6 +2032,25 @@ describe("Helper functions", () => {
expect(typeof result.color).toBe("string"); expect(typeof result.color).toBe("string");
}); });
it("stringAvatar returns 2-letter initials for full name", () => {
const result = stringAvatar("John Doe");
expect(result.children).toBe("JD");
expect(result.color).toBeDefined();
expect(typeof result.color).toBe("string");
});
it("stringAvatar handles single word names", () => {
const result = stringAvatar("Alice");
expect(result.children).toBe("A");
expect(result.color).toBeDefined();
});
it("stringAvatar handles email addresses", () => {
const result = stringAvatar("test@example.com");
expect(result.children).toBe("T");
expect(result.color).toBeDefined();
});
it("InfoRow renders text and link if url is valid", () => { it("InfoRow renders text and link if url is valid", () => {
renderWithProviders( renderWithProviders(
<InfoRow <InfoRow
+113
View File
@@ -0,0 +1,113 @@
import { getInitials, stringToGradient } from "../../src/utils/avatarUtils";
jest.mock("@linagora/twake-mui", () => ({
nameToColor: jest.fn((name: string) => {
if (!name) return undefined;
const colors = [
"sunrise",
"downy",
"sugarCoral",
"pinkBonnet",
"blueMana",
"nightBlue",
"snowPea",
"pluviophile",
"cornflower",
"paleGreen",
"moonBlue",
];
const hash = Array.from(name.toUpperCase())
.map((letter) => letter.charCodeAt(0))
.reduce((sum, number) => sum + number, 0);
return colors[hash % colors.length];
}),
}));
describe("avatarUtils", () => {
describe("getInitials", () => {
it("returns 2-letter initials for full name", () => {
expect(getInitials("John Doe")).toBe("JD");
expect(getInitials("Alice Smith")).toBe("AS");
expect(getInitials("Nguyễn Văn")).toBe("NV");
});
it("returns single letter for single word", () => {
expect(getInitials("Alice")).toBe("A");
expect(getInitials("John")).toBe("J");
});
it("returns uppercase initials", () => {
expect(getInitials("john doe")).toBe("JD");
expect(getInitials("ALICE SMITH")).toBe("AS");
expect(getInitials("a")).toBe("A");
});
it("handles multiple spaces between words", () => {
expect(getInitials("John Doe")).toBe("JD");
expect(getInitials("Alice Smith Brown")).toBe("AS");
});
it("handles email addresses", () => {
expect(getInitials("john.doe@email.com")).toBe("J");
expect(getInitials("test@example.com")).toBe("T");
});
it("handles empty string", () => {
expect(getInitials("")).toBe("");
});
it("handles whitespace-only string", () => {
expect(getInitials(" ")).toBe("");
});
it("handles single character", () => {
expect(getInitials("A")).toBe("A");
expect(getInitials("a")).toBe("A");
});
it("handles names with special characters", () => {
expect(getInitials("Jean-Pierre")).toBe("J");
expect(getInitials("Mary Jane Watson")).toBe("MJ");
});
it("handles unicode characters", () => {
expect(getInitials("Nguyễn Văn")).toBe("NV");
expect(getInitials("José María")).toBe("JM");
});
it("takes first two words for names with more than two words", () => {
expect(getInitials("John Michael Smith")).toBe("JM");
expect(getInitials("Alice Bob Charlie")).toBe("AB");
});
});
describe("stringToGradient", () => {
it("returns color for valid string", () => {
const result = stringToGradient("John Doe");
expect(result).toBeDefined();
expect(typeof result).toBe("string");
});
it("returns same color for same input", () => {
const result1 = stringToGradient("John Doe");
const result2 = stringToGradient("John Doe");
expect(result1).toBe(result2);
});
it("returns undefined for empty string", () => {
expect(stringToGradient("")).toBeUndefined();
});
it("returns color for single character", () => {
const result = stringToGradient("A");
expect(result).toBeDefined();
expect(typeof result).toBe("string");
});
it("returns color for email address", () => {
const result = stringToGradient("test@example.com");
expect(result).toBeDefined();
expect(typeof result).toBe("string");
});
});
});
+41 -3
View File
@@ -138,6 +138,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@@ -2070,6 +2071,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@@ -2093,6 +2095,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -2195,6 +2198,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@@ -2238,6 +2242,7 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@@ -2510,6 +2515,7 @@
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz",
"integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"preact": "~10.12.1" "preact": "~10.12.1"
} }
@@ -3136,6 +3142,7 @@
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://npm.pkg.github.com/download/@linagora/twake-mui/1.1.3/c8ed9f2e04beaf55c1cfd1e014d2f43081c975ae", "resolved": "https://npm.pkg.github.com/download/@linagora/twake-mui/1.1.3/c8ed9f2e04beaf55c1cfd1e014d2f43081c975ae",
"integrity": "sha512-lLylOmnMOpXRlkFCsKq2Jaf1bfaobQsHz1fvxRIR0MIs8EAy2gyKc/5D22Q/Mqo3aN2zEum9qrd1D030lm8NJg==", "integrity": "sha512-lLylOmnMOpXRlkFCsKq2Jaf1bfaobQsHz1fvxRIR0MIs8EAy2gyKc/5D22Q/Mqo3aN2zEum9qrd1D030lm8NJg==",
"license": "MIT",
"dependencies": { "dependencies": {
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
@@ -3251,6 +3258,7 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz",
"integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==", "integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.6", "@mui/core-downloads-tracker": "^7.3.6",
@@ -3361,6 +3369,7 @@
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.6.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.6.tgz",
"integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==", "integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/private-theming": "^7.3.6", "@mui/private-theming": "^7.3.6",
@@ -3719,6 +3728,7 @@
"integrity": "sha512-LvoOF53PL6zXgdzEhgnnP51S4FseDFH1bHrobK4EK6zZX/tN8qgf5tdlmN7h4OkMv/Qs1oUfvj0QcLWSstnnvA==", "integrity": "sha512-LvoOF53PL6zXgdzEhgnnP51S4FseDFH1bHrobK4EK6zZX/tN8qgf5tdlmN7h4OkMv/Qs1oUfvj0QcLWSstnnvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@rspack/core": "1.6.8", "@rspack/core": "1.6.8",
"@rspack/lite-tapable": "~1.1.0", "@rspack/lite-tapable": "~1.1.0",
@@ -3946,6 +3956,7 @@
"integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==", "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@module-federation/runtime-tools": "0.21.6", "@module-federation/runtime-tools": "0.21.6",
"@rspack/binding": "1.6.8", "@rspack/binding": "1.6.8",
@@ -4205,6 +4216,7 @@
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.21.3", "@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0", "@svgr/babel-preset": "8.1.0",
@@ -4289,6 +4301,7 @@
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
@@ -4299,6 +4312,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.10.4", "@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
@@ -4594,6 +4608,7 @@
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -4615,6 +4630,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -4626,6 +4642,7 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
} }
@@ -4682,6 +4699,7 @@
"integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/scope-manager": "8.51.0",
@@ -4711,6 +4729,7 @@
"integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.51.0",
@@ -5217,6 +5236,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -5976,6 +5996,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -6687,6 +6708,7 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.21.0" "@babel/runtime": "^7.21.0"
}, },
@@ -6702,7 +6724,8 @@
"version": "1.11.19", "version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
@@ -7301,6 +7324,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -8655,6 +8679,7 @@
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.7.6" "@babel/runtime": "^7.7.6"
} }
@@ -9629,6 +9654,7 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jest/core": "30.2.0", "@jest/core": "30.2.0",
"@jest/types": "30.2.0", "@jest/types": "30.2.0",
@@ -10392,6 +10418,7 @@
"integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.19.6", "@babel/core": "^7.19.6",
"@svgr/babel-preset": "^6.5.1", "@svgr/babel-preset": "^6.5.1",
@@ -10850,6 +10877,7 @@
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"cssstyle": "^4.2.1", "cssstyle": "^4.2.1",
"data-urls": "^5.0.0", "data-urls": "^5.0.0",
@@ -11286,6 +11314,7 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "*" "node": "*"
} }
@@ -11295,6 +11324,7 @@
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"moment": "^2.29.4" "moment": "^2.29.4"
}, },
@@ -11330,7 +11360,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@@ -12279,6 +12308,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -12301,6 +12331,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -12320,6 +12351,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/use-sync-external-store": "^0.0.6", "@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0" "use-sync-external-store": "^1.4.0"
@@ -12344,6 +12376,7 @@
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -12444,7 +12477,8 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/redux-first-history": { "node_modules/redux-first-history": {
"version": "5.2.0", "version": "5.2.0",
@@ -13671,6 +13705,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -13851,6 +13886,7 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@cspotcode/source-map-support": "^0.8.0", "@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7", "@tsconfig/node10": "^1.0.7",
@@ -14067,6 +14103,7 @@
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -14875,6 +14912,7 @@
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
+2 -1
View File
@@ -8,6 +8,7 @@ import {
ListItemText, ListItemText,
TextField, TextField,
} from "@linagora/twake-mui"; } from "@linagora/twake-mui";
import { stringAvatar } from "../Event/utils/eventUtils";
import { import {
type ReactNode, type ReactNode,
useCallback, useCallback,
@@ -287,7 +288,7 @@ export function PeopleSearch({
return ( return (
<ListItem key={key + option?.email} {...otherProps} disableGutters> <ListItem key={key + option?.email} {...otherProps} disableGutters>
<ListItemAvatar> <ListItemAvatar>
<Avatar src={option.avatarUrl} alt={option.displayName} /> <Avatar {...stringAvatar(option.displayName || option.email)} />
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={option.displayName} primary={option.displayName}
+2 -2
View File
@@ -9,6 +9,7 @@ import {
useTheme, useTheme,
} from "@linagora/twake-mui"; } from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { stringAvatar } from "../Event/utils/eventUtils";
import { useState } from "react"; import { useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { getCalendars } from "../../features/Calendars/CalendarApi"; import { getCalendars } from "../../features/Calendars/CalendarApi";
@@ -50,8 +51,7 @@ function CalendarItem({
> >
<Box display="flex" alignItems="center" gap={2}> <Box display="flex" alignItems="center" gap={2}>
<Avatar <Avatar
src={cal.owner.avatarUrl} {...stringAvatar(cal.owner.displayName || cal.owner.email)}
alt={cal.owner.email}
style={{ style={{
border: `2px solid ${cal.cal["apple:color"] || defaultColors[0].light}`, border: `2px solid ${cal.cal["apple:color"] || defaultColors[0].light}`,
boxShadow: cal.cal["apple:color"] boxShadow: cal.cal["apple:color"]
+2 -2
View File
@@ -1,7 +1,7 @@
import CancelIcon from "@mui/icons-material/Cancel"; import CancelIcon from "@mui/icons-material/Cancel";
import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { Avatar, Badge, Box, Typography } from "@linagora/twake-mui"; import { Avatar, Badge, Box, Typography } from "@linagora/twake-mui";
import { stringToGradient } from "../../../utils/avatarUtils"; import { stringToGradient, getInitials } from "../../../utils/avatarUtils";
import { ThunkDispatch } from "@reduxjs/toolkit"; import { ThunkDispatch } from "@reduxjs/toolkit";
import { emptyEventsCal } from "../../../features/Calendars/CalendarSlice"; import { emptyEventsCal } from "../../../features/Calendars/CalendarSlice";
import { getCalendarsListAsync } from "../../../features/Calendars/services/getCalendarsListAsync"; import { getCalendarsListAsync } from "../../../features/Calendars/services/getCalendarsListAsync";
@@ -103,7 +103,7 @@ export function stringToColor(string: string) {
export function stringAvatar(name: string) { export function stringAvatar(name: string) {
return { return {
color: stringToGradient(name), color: stringToGradient(name),
children: name[0], children: getInitials(name),
}; };
} }
+5 -21
View File
@@ -8,7 +8,7 @@ import SettingsIcon from "@mui/icons-material/Settings";
import LogoutIcon from "@mui/icons-material/Logout"; import LogoutIcon from "@mui/icons-material/Logout";
import "./Menubar.styl"; import "./Menubar.styl";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { stringToGradient } from "../../utils/avatarUtils"; import { stringToGradient, getInitials } from "../../utils/avatarUtils";
import { import {
Avatar, Avatar,
IconButton, IconButton,
@@ -262,19 +262,11 @@ export function Menubar({
<div className="menu-items"> <div className="menu-items">
<IconButton onClick={handleUserMenuClick}> <IconButton onClick={handleUserMenuClick}>
<Avatar <Avatar
color={stringToGradient( color={stringToGradient(getUserDisplayName(user))}
user && user.family_name
? user.family_name
: user && user.email
? user.email
: ""
)}
size="m" size="m"
aria-label={t("menubar.userProfile")} aria-label={t("menubar.userProfile")}
> >
{user?.name && user?.family_name {getInitials(getUserDisplayName(user))}
? `${user.name[0]}${user.family_name[0]}`
: (user?.email?.[0] ?? "")}
</Avatar> </Avatar>
</IconButton> </IconButton>
</div> </div>
@@ -338,19 +330,11 @@ export function Menubar({
}} }}
> >
<Avatar <Avatar
color={stringToGradient( color={stringToGradient(getUserDisplayName(user))}
user && user.family_name
? user.family_name
: user && user.email
? user.email
: ""
)}
size="m" size="m"
sx={{ marginBottom: "8px" }} sx={{ marginBottom: "8px" }}
> >
{user?.name && user?.family_name {getInitials(getUserDisplayName(user))}
? `${user.name[0]}${user.family_name[0]}`
: (user?.email?.[0] ?? "")}
</Avatar> </Avatar>
<Typography variant="body1" sx={{ fontWeight: 500 }}> <Typography variant="body1" sx={{ fontWeight: 500 }}>
{getUserDisplayName(user)} {getUserDisplayName(user)}
+18
View File
@@ -9,3 +9,21 @@ export function stringToGradient(str: string): string | undefined {
if (!str) return undefined; if (!str) return undefined;
return nameToColor(str); return nameToColor(str);
} }
/**
* Get initials from a name string (max 2 characters)
* @param name - Name string like "John Doe", "Alice", or "john.doe@email.com"
* @returns Initials like "JD", "A", or "J"
*/
export function getInitials(name: string): string {
if (!name) return "";
const trimmed = name.trim();
const words = trimmed.split(/\s+/).filter(Boolean);
if (words.length >= 2) {
return `${words[0][0]}${words[1][0]}`.toUpperCase();
}
return trimmed[0]?.toUpperCase() ?? "";
}