# Avatar System Documentation ## Overview The Avatar System is a comprehensive solution for managing and displaying user, contact, and client profile images throughout the application. It provides a consistent way to upload, store, retrieve, and display images with appropriate fallbacks when images are not available. The system follows a component-based architecture with a central base component (`EntityAvatar`) that is extended by entity-specific components (`UserAvatar`, `ContactAvatar`, `ClientAvatar`). This approach ensures consistent styling and behavior while allowing for entity-specific customizations. ## Architecture The Avatar System is built on a layered architecture: ```mermaid graph TD A[UI Components] --> B[Service Layer] B --> C[Storage Layer] A --> D[Utility Functions] D --> B subgraph "UI Components" A1[EntityAvatar] --> A2[UserAvatar] A1 --> A3[ContactAvatar] A1 --> A4[ClientAvatar] A5[EntityImageUpload] end subgraph "Service Layer" B1[EntityImageService] end subgraph "Utility Functions" D1[avatarUtils] end ``` ### Key Components 1. **Base Components**: - `EntityAvatar`: The core component that handles image display, fallbacks, and loading states - `EntityImageUpload`: Handles image upload, preview, and deletion for all entity types 2. **Entity-Specific Components**: - `UserAvatar`: Specialized for user avatars - `ContactAvatar`: Specialized for contact avatars - `ClientAvatar`: Specialized for client logos 3. **Service Layer**: - `EntityImageService`: Provides methods for uploading and deleting entity images 4. **Utilities**: - `avatarUtils`: Helper functions for retrieving image URLs and other common operations ### Data Flow ```mermaid sequenceDiagram participant User participant Component as UI Component participant Service as EntityImageService participant Storage as Storage Service participant DB as Database User->>Component: Upload image Component->>Service: uploadEntityImage() Service->>Storage: Upload file Storage-->>Service: Return file ID Service->>DB: Create document record Service->>DB: Create/update association Service-->>Component: Return image URL Component-->>User: Display updated image ``` ## Components ### EntityAvatar The base component that renders an avatar with fallback to initials when no image is available. #### Props | Prop | Type | Description | |------|------|-------------| | `entityId` | `string \| number` | Unique identifier for the entity | | `entityName` | `string` | Name of the entity (used for generating initials and colors) | | `imageUrl` | `string \| null` | URL to the entity's image | | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number` | Size of the avatar (predefined or custom pixel value) | | `className` | `string` | Optional additional CSS classes | | `getInitials` | `(name: string) => string` | Optional custom function to generate initials | | `altText` | `string` | Optional alt text for the image | #### Usage Example ```tsx import { EntityAvatar } from '@alga-psa/ui'; ``` ### UserAvatar Specialized component for displaying user avatars. #### Props | Prop | Type | Description | |------|------|-------------| | `userId` | `string` | User's unique identifier | | `userName` | `string` | User's name | | `avatarUrl` | `string \| null` | URL to the user's avatar | | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number` | Size of the avatar | | `className` | `string` | Optional additional CSS classes | #### Usage Example ```tsx import { UserAvatar } from '@alga-psa/ui'; ``` ### ContactAvatar Specialized component for displaying contact avatars. #### Props | Prop | Type | Description | |------|------|-------------| | `contactId` | `string` | Contact's unique identifier | | `contactName` | `string` | Contact's name | | `avatarUrl` | `string \| null` | URL to the contact's avatar | | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number` | Size of the avatar | | `className` | `string` | Optional additional CSS classes | #### Usage Example ```tsx import { ContactAvatar } from '@alga-psa/ui'; ``` ### ClientAvatar Specialized component for displaying client logos. **Location**: `packages/ui/src/components/ClientAvatar.tsx` #### Props | Prop | Type | Description | |------|------|-------------| | `clientId` | `string \| number` | Client's unique identifier | | `clientName` | `string` | Client's name | | `logoUrl` | `string \| null` | URL to the client's logo | | `size` | `EntityAvatarProps['size']` | Size of the avatar | | `className` | `string` | Optional additional CSS classes | #### Usage Example ```tsx import { ClientAvatar } from '@alga-psa/ui'; ``` ### EntityImageUpload Component for uploading, previewing, and deleting entity images. #### Props | Prop | Type | Description | |------|------|-------------| | `entityType` | `'user' \| 'contact' \| 'client' \| 'tenant'` | Type of entity | | `entityId` | `string` | Entity's unique identifier | | `entityName` | `string` | Entity's name | | `imageUrl` | `string \| null` | Current image URL | | `onImageChange` | `(newImageUrl: string \| null) => void` | Callback when image changes | | `uploadAction` | `(entityId: string, formData: FormData) => Promise<{...}>` | Function to handle upload | | `deleteAction` | `(entityId: string) => Promise<{...}>` | Function to handle deletion | | `linkDocumentAsAvatar` | `(args: { entityType: EntityType; entityId: string; documentId: string }) => Promise` | Links an existing document as the entity's avatar | | `renderDocumentSelector` | `(args: { isOpen: boolean; onClose: () => void; onSelectDocumentId: (documentId: string) => void; entityType: EntityType; entityId: string }) => React.ReactNode` | Custom render function for document selector modal | | `userType` | `string` | Optional user type context | | `userEntityId` | `string` | Optional user entity ID context | | `canModify` | `boolean` | Whether the current user can modify the avatar | | `className` | `string` | Optional additional CSS classes | | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | Size of the avatar preview | #### Usage Example ```tsx import { EntityImageUpload } from '@alga-psa/ui'; import { uploadUserAvatar, deleteUserAvatar } from '@alga-psa/users/actions'; setAvatarUrl(newUrl)} uploadAction={uploadUserAvatar} deleteAction={deleteUserAvatar} size="lg" /> ``` ## Services ### EntityImageService Service for managing entity images, handling upload and deletion operations. #### Methods ##### `uploadEntityImage` Uploads an image for an entity and associates it with the entity. ```typescript async function uploadEntityImage( entityType: EntityType, entityId: string, file: File, userId: string, tenant: string, contextName?: string ): Promise ``` **Parameters:** - `entityType`: Type of entity ('user', 'contact', 'client', or 'tenant') - `entityId`: Entity's unique identifier - `file`: File object to upload - `userId`: ID of the user performing the upload - `tenant`: Tenant context - `contextName`: Optional context name override **Returns:** - Promise resolving to an object with: - `success`: Boolean indicating success - `message`: Optional message - `imageUrl`: Optional new image URL ##### `deleteEntityImage` Deletes an image associated with an entity. ```typescript async function deleteEntityImage( entityType: EntityType, entityId: string, userId: string, tenant: string ): Promise<{ success: boolean; message?: string }> ``` **Parameters:** - `entityType`: Type of entity ('user', 'contact', 'client', or 'tenant') - `entityId`: Entity's unique identifier - `userId`: ID of the user performing the deletion - `tenant`: Tenant context **Returns:** - Promise resolving to an object with: - `success`: Boolean indicating success - `message`: Optional message ## Utilities ### avatarUtils Utility functions for working with entity images. #### Functions ##### `getEntityImageUrl` Retrieves the image URL for an entity. ```typescript async function getEntityImageUrl( entityType: EntityType, entityId: string, tenant: string ): Promise ``` **Parameters:** - `entityType`: Type of entity ('user', 'contact', 'client', or 'tenant') - `entityId`: Entity's unique identifier - `tenant`: Tenant context **Returns:** - Promise resolving to the image URL or null if not found ##### Convenience Functions ```typescript // Get a user's avatar URL async function getUserAvatarUrl(userId: string, tenant: string): Promise // Get a contact's avatar URL async function getContactAvatarUrl(contactId: string, tenant: string): Promise // Get a client's logo URL async function getClientLogoUrl(clientId: string, tenant: string): Promise ``` ##### Batch Functions For efficient retrieval of multiple avatar URLs in a single query: ```typescript // Get logo URLs for multiple clients async function getClientLogoUrlsBatch(clientIds: string[], tenant: string): Promise> // Get avatar URLs for multiple users async function getUserAvatarUrlsBatch(userIds: string[], tenant: string): Promise> // Get avatar URLs for multiple contacts async function getContactAvatarUrlsBatch(contactIds: string[], tenant: string): Promise> // Generic batch function for any entity type async function getEntityImageUrlsBatch(entityType: EntityType, entityIds: string[], tenant: string): Promise> ``` ## Integration Examples ### User Profile Page ```tsx import { useState, useEffect } from 'react'; import { UserAvatar, EntityImageUpload } from '@alga-psa/ui'; import { uploadUserAvatar, deleteUserAvatar } from '@alga-psa/users/actions'; import { getUserAvatarUrl } from 'server/src/lib/utils/avatarUtils'; const UserProfilePage = ({ userId, userName, tenant }) => { const [avatarUrl, setAvatarUrl] = useState(null); useEffect(() => { const fetchAvatarUrl = async () => { const url = await getUserAvatarUrl(userId, tenant); setAvatarUrl(url); }; fetchAvatarUrl(); }, [userId, tenant]); return (

User Profile

setAvatarUrl(newUrl)} uploadAction={uploadUserAvatar} deleteAction={deleteUserAvatar} size="lg" />
{/* Rest of profile content */}
); }; ``` ### Client Details Page ```tsx import { useState, useEffect } from 'react'; import { ClientAvatar, EntityImageUpload } from '@alga-psa/ui'; import { uploadClientLogo, deleteClientLogo } from '@alga-psa/clients/actions'; import { getClientLogoUrl } from 'server/src/lib/utils/avatarUtils'; const ClientDetailsPage = ({ clientId, clientName, tenant }) => { const [logoUrl, setLogoUrl] = useState(null); useEffect(() => { const fetchLogoUrl = async () => { const url = await getClientLogoUrl(clientId, tenant); setLogoUrl(url); }; fetchLogoUrl(); }, [clientId, tenant]); return (
setLogoUrl(newUrl)} uploadAction={uploadClientLogo} deleteAction={deleteClientLogo} size="xl" />

{clientName}

{/* Rest of client details */}
); }; ``` ### Ticket Conversation with User Avatars ```tsx import { UserAvatar, ContactAvatar } from '@alga-psa/ui'; const TicketComment = ({ comment, author, isContact }) => { return (
{isContact ? ( ) : ( )}
{author.name} {comment.timestamp}
{comment.content}
); }; ``` ## Best Practices ### When to Use Each Component - **EntityAvatar**: Use directly only when creating a new entity-specific avatar component. For most cases, use one of the specialized components. - **UserAvatar**: Use for displaying user avatars throughout the application. - **ContactAvatar**: Use for displaying contact avatars throughout the application. - **ClientAvatar**: Use for displaying client logos throughout the application. - **EntityImageUpload**: Use on profile/details pages where users should be able to upload or change images. ### Performance Considerations 1. **Image Optimization**: - The system automatically processes uploaded images to optimize them for display. - Images are stored efficiently in the storage system. 2. **Loading States**: - The avatar components include built-in loading states to provide visual feedback during image loading. - Use the `key` prop with a timestamp when updating images to force re-rendering. 3. **Caching**: - Image URLs include a timestamp query parameter to prevent browser caching issues. - Consider implementing a client-side cache for frequently accessed avatar URLs. ### Error Handling 1. **Upload Errors**: - The `EntityImageUpload` component handles upload errors and displays appropriate toast messages. - Server-side validation ensures only valid images are accepted. 2. **Display Fallbacks**: - All avatar components automatically fall back to displaying initials when no image is available. - The fallback colors are consistently generated based on the entity name. ## Troubleshooting ### Common Issues #### Images Not Updating Immediately **Symptoms**: After uploading a new image, the old image still appears in some places. **Solutions**: - Ensure the `onImageChange` callback is properly updating state in parent components. - Add a timestamp query parameter to the image URL to bypass browser caching. - Use the `key` prop with a timestamp to force React to re-render the component. #### Missing Avatars **Symptoms**: Avatars not appearing in certain parts of the application. **Solutions**: - Check if the component is correctly retrieving the avatar URL. - Verify that the entity ID and tenant are correctly passed to the utility functions. - Ensure the document association exists in the database. #### Upload Failures **Symptoms**: Image uploads fail with error messages. **Solutions**: - Check file size (must be under 2MB). - Ensure the file is a valid image format (PNG, JPG, GIF). - Verify that the user has permission to upload images for the entity. - Check server logs for detailed error information. ## Integration Points The Avatar System integrates with several other system components: 1. **Storage System**: For storing and retrieving image files. 2. **Document System**: For managing document metadata and associations. 3. **User Management**: For user avatars and permissions. 4. **Client Management**: For client logos. 5. **Contact Management**: For contact avatars. ## Future Enhancements Potential improvements to consider for the Avatar System: 1. **Image Cropping**: Add support for cropping images during upload. 2. **Multiple Image Sizes**: Generate and store multiple sizes of each image for different display contexts. 3. **Animated Avatars**: Support for animated GIFs or short video avatars. 4. **Default Avatars**: Provide a selection of default avatars instead of just initials. 5. **Group Avatars**: Support for displaying multiple avatars in a group (e.g., for teams).