From c301c920cca55954c7965cc51d294122854ace5f Mon Sep 17 00:00:00 2001 From: rozita-hasani Date: Sun, 9 Mar 2025 17:41:23 +0100 Subject: [PATCH] refactor: update UserAvatar component --- .../sidebar/SidebarAccountDropdown.tsx | 2 +- .../calendar/components/CalendarLeaveItem.tsx | 6 +- .../leave/components/LeaveConflicts.tsx | 4 +- .../components/LeaveStatusUpdateDialog.tsx | 4 +- src/modules/leave/pages/LeavesPage.tsx | 2 +- src/modules/user/components/UserAvatar.tsx | 42 ++--- .../user/components/UserDefaultAvatar.tsx | 143 ++++++++++++++++++ .../user/components/UserDetailsCard.tsx | 2 +- src/modules/user/components/UserList.tsx | 6 +- src/modules/user/pages/ProfilePage.tsx | 2 +- 10 files changed, 179 insertions(+), 34 deletions(-) create mode 100644 src/modules/user/components/UserDefaultAvatar.tsx diff --git a/src/components/sidebar/SidebarAccountDropdown.tsx b/src/components/sidebar/SidebarAccountDropdown.tsx index 47b0375..1635f86 100644 --- a/src/components/sidebar/SidebarAccountDropdown.tsx +++ b/src/components/sidebar/SidebarAccountDropdown.tsx @@ -46,7 +46,7 @@ export default function SidebarAccountDropdown({isActive, onClick}: AccountDropd
- +

{user?.firstName} {user?.lastName}

diff --git a/src/modules/calendar/components/CalendarLeaveItem.tsx b/src/modules/calendar/components/CalendarLeaveItem.tsx index 28b8cd9..e3681a7 100644 --- a/src/modules/calendar/components/CalendarLeaveItem.tsx +++ b/src/modules/calendar/components/CalendarLeaveItem.tsx @@ -27,9 +27,9 @@ export default function CalendarLeaveItem({leave}: CalendarLeaveItemProps) { >
{leave.user.firstName} diff --git a/src/modules/leave/components/LeaveConflicts.tsx b/src/modules/leave/components/LeaveConflicts.tsx index 8fac40b..e1ae1be 100644 --- a/src/modules/leave/components/LeaveConflicts.tsx +++ b/src/modules/leave/components/LeaveConflicts.tsx @@ -32,8 +32,8 @@ export default function LeaveConflicts({conflicts}: LeaveConflictProps) { >
{leave.user.firstName} {leave.user.lastName}
diff --git a/src/modules/leave/components/LeaveStatusUpdateDialog.tsx b/src/modules/leave/components/LeaveStatusUpdateDialog.tsx index 4608713..775b832 100644 --- a/src/modules/leave/components/LeaveStatusUpdateDialog.tsx +++ b/src/modules/leave/components/LeaveStatusUpdateDialog.tsx @@ -48,8 +48,8 @@ export default function LeaveStatusUpdateDialog({ >
diff --git a/src/modules/leave/pages/LeavesPage.tsx b/src/modules/leave/pages/LeavesPage.tsx index be34e32..84f3b1e 100644 --- a/src/modules/leave/pages/LeavesPage.tsx +++ b/src/modules/leave/pages/LeavesPage.tsx @@ -206,7 +206,7 @@ function LeaveRow({leave, handleRowClick}: LeaveRowProps) { return ( - + viewEmployeeProfile(leave.user.id)} diff --git a/src/modules/user/components/UserAvatar.tsx b/src/modules/user/components/UserAvatar.tsx index 3fd2633..8ebbf19 100644 --- a/src/modules/user/components/UserAvatar.tsx +++ b/src/modules/user/components/UserAvatar.tsx @@ -1,35 +1,41 @@ import React, {useContext} from "react"; -import {AssetResponse} from "@/core/types/user.ts"; +import {UserResponse} from "@/core/types/user.ts"; import {UserContext} from "@/contexts/UserContext.tsx"; -import {User} from "lucide-react"; +import UserDefaultAvatar from "@/modules/user/components/UserDefaultAvatar.tsx"; type AvatarProps = { - avatar: AssetResponse | null; - avatarSize: number; + size: number; className?: string; + user: UserResponse; }; -export default function UserAvatar({avatar, avatarSize, className}: AvatarProps) { - const { accessToken } = useContext(UserContext); - const avatarSrc = avatar ? `${avatar.url}?token=${accessToken}` : null; +export default function UserAvatar({size, className, user}: AvatarProps) { + const {accessToken} = useContext(UserContext); const commonStyles = { - height: `${avatarSize}px`, - width: `${avatarSize}px`, + height: `${size}px`, + width: `${size}px`, objectFit: 'cover' as const, transition: 'all 0.2s ease-in-out' }; + if(!user) { + return ( +
+ +
+ ); + } - if (!avatarSrc) { + if (!user.avatar) { return (
- +
); } @@ -37,15 +43,11 @@ export default function UserAvatar({avatar, avatarSize, className}: AvatarProps) return (
User Avatar { - e.currentTarget.onerror = null; - e.currentTarget.src = 'fallback-avatar-url.jpg'; // Add your fallback image URL - }} />
); diff --git a/src/modules/user/components/UserDefaultAvatar.tsx b/src/modules/user/components/UserDefaultAvatar.tsx new file mode 100644 index 0000000..d313f0a --- /dev/null +++ b/src/modules/user/components/UserDefaultAvatar.tsx @@ -0,0 +1,143 @@ +import React, { useMemo } from 'react'; + +interface ColorPalette { + backgrounds: string[]; + texts: string[]; +} + +interface UserDefaultAvatarProps { + /** Full name to generate initials from */ + name: string; + /** Size of the avatar in pixels (both width and height) */ + size?: number; + /** Optional CSS class name for additional styling */ + className?: string; + /** Font family for the initials */ + fontFamily?: string; + /** Custom color palette for backgrounds and text */ + colorPalette?: ColorPalette; +} + +/** + * UserDefaultAvatar - A React component that generates avatar circles with initials + * similar to the example in the image. + */ +export default function UserDefaultAvatar({ + name, + size = 48, + className = '', + fontFamily = 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"', + colorPalette = { + backgrounds: [ + '#E8F2FF', // Light Blue + '#FFF1E8', // Light Orange/Peach + '#E8FFE9', // Light Green + '#F5E8FF', // Light Purple + '#FFE8E8', // Light Pink/Red + '#F2F4F7', // Light Grey + '#E8FBFF', // Light Cyan + '#FFF8E8', // Light Yellow + '#FFE8F7', // Light Pink + '#E8FFFD', // Light Mint + '#F7E8FF', // Light Lavender + '#FFE8EC', // Light Coral + '#E8FFF1', // Light Seafoam + '#FFF3E8', // Light Apricot + '#E8EEFF', // Light Periwinkle + '#FFFFE8' // Light Cream + ], + texts: [ + '#2E5AAC', // Dark Blue + '#AC6B2E', // Dark Orange + '#2EAC4A', // Dark Green + '#8A2EAC', // Dark Purple + '#AC2E2E', // Dark Red + '#4A5468', // Dark Grey + '#2E9DAC', // Dark Cyan + '#AC962E', // Dark Yellow + '#AC2E8A', // Dark Pink + '#2EACA3', // Dark Mint + '#892EAC', // Dark Lavender + '#AC2E4A', // Dark Coral + '#2EAC77', // Dark Seafoam + '#AC5F2E', // Dark Apricot + '#2E41AC', // Dark Periwinkle + '#ACAC2E' // Dark Olive + ] + } + }: UserDefaultAvatarProps) { + // Generate initials from the name + const initials = useMemo((): string => { + if (!name || typeof name !== 'string') return '??'; + + const parts = name.trim().split(/\s+/); + + if (parts.length <= 1) { + // If there's only one name, use the first two characters if possible + return name.length > 1 + ? name.substring(0, 2).toUpperCase() + : name.substring(0, 1).toUpperCase(); + } + + // Get first char of first name and first char of last name + const firstInitial = parts[0].charAt(0); + const lastInitial = parts[parts.length - 1].charAt(0); + + return (firstInitial + lastInitial).toUpperCase(); + }, [name]); + + // Get color index based on name + const colorIndex = useMemo((): number => { + if (!name) return 0; + + // Simple hash function to ensure consistent colors for the same name + let hash = 0; + for (let i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash) + name.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return Math.abs(hash) % colorPalette.backgrounds.length; + }, [name, colorPalette.backgrounds.length]); + + // Get background and text colors + const backgroundColor = colorPalette.backgrounds[colorIndex]; + const textColor = colorPalette.texts[colorIndex]; + + // Calculate dimensions + const strokeWidth = 1; + const radius = (size / 2) - (strokeWidth / 2); + const fontSize = size * 0.35; // 35% of circle size + + return ( + + + + {initials} + + + ); +}; \ No newline at end of file diff --git a/src/modules/user/components/UserDetailsCard.tsx b/src/modules/user/components/UserDetailsCard.tsx index 6e01f89..d9238fd 100644 --- a/src/modules/user/components/UserDetailsCard.tsx +++ b/src/modules/user/components/UserDetailsCard.tsx @@ -19,7 +19,7 @@ export default function UserDetailsCard({employeeDetails, leavePolicy}: Employee {employeeDetails ? (
- +

diff --git a/src/modules/user/components/UserList.tsx b/src/modules/user/components/UserList.tsx index 5ab0ac2..21cb481 100644 --- a/src/modules/user/components/UserList.tsx +++ b/src/modules/user/components/UserList.tsx @@ -34,9 +34,9 @@ export function UserList({ className="flex items-center space-x-2 cursor-pointer hover:text-primary transition-colors" >
- +