Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
17 KiB
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:
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
-
Base Components:
EntityAvatar: The core component that handles image display, fallbacks, and loading statesEntityImageUpload: Handles image upload, preview, and deletion for all entity types
-
Entity-Specific Components:
UserAvatar: Specialized for user avatarsContactAvatar: Specialized for contact avatarsClientAvatar: Specialized for client logos
-
Service Layer:
EntityImageService: Provides methods for uploading and deleting entity images
-
Utilities:
avatarUtils: Helper functions for retrieving image URLs and other common operations
Data Flow
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
import { EntityAvatar } from '@alga-psa/ui';
<EntityAvatar
entityId="123"
entityName="John Doe"
imageUrl="https://example.com/avatar.jpg"
size="md"
/>
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
import { UserAvatar } from '@alga-psa/ui';
<UserAvatar
userId="user-123"
userName="John Doe"
avatarUrl={userAvatarUrl}
size="md"
/>
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
import { ContactAvatar } from '@alga-psa/ui';
<ContactAvatar
contactId="contact-123"
contactName="Jane Smith"
avatarUrl={contactAvatarUrl}
size="md"
/>
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
import { ClientAvatar } from '@alga-psa/ui';
<ClientAvatar
clientId="client-123"
clientName="Acme Inc."
logoUrl={clientLogoUrl}
size="md"
/>
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<LinkDocumentAsAvatarResult> |
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
import { EntityImageUpload } from '@alga-psa/ui';
import { uploadUserAvatar, deleteUserAvatar } from '@alga-psa/users/actions';
<EntityImageUpload
entityType="user"
entityId={userId}
entityName={userName}
imageUrl={avatarUrl}
onImageChange={(newUrl) => 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.
async function uploadEntityImage(
entityType: EntityType,
entityId: string,
file: File,
userId: string,
tenant: string,
contextName?: string
): Promise<UploadResult>
Parameters:
entityType: Type of entity ('user', 'contact', 'client', or 'tenant')entityId: Entity's unique identifierfile: File object to uploaduserId: ID of the user performing the uploadtenant: Tenant contextcontextName: Optional context name override
Returns:
- Promise resolving to an object with:
success: Boolean indicating successmessage: Optional messageimageUrl: Optional new image URL
deleteEntityImage
Deletes an image associated with an entity.
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 identifieruserId: ID of the user performing the deletiontenant: Tenant context
Returns:
- Promise resolving to an object with:
success: Boolean indicating successmessage: Optional message
Utilities
avatarUtils
Utility functions for working with entity images.
Functions
getEntityImageUrl
Retrieves the image URL for an entity.
async function getEntityImageUrl(
entityType: EntityType,
entityId: string,
tenant: string
): Promise<string | null>
Parameters:
entityType: Type of entity ('user', 'contact', 'client', or 'tenant')entityId: Entity's unique identifiertenant: Tenant context
Returns:
- Promise resolving to the image URL or null if not found
Convenience Functions
// Get a user's avatar URL
async function getUserAvatarUrl(userId: string, tenant: string): Promise<string | null>
// Get a contact's avatar URL
async function getContactAvatarUrl(contactId: string, tenant: string): Promise<string | null>
// Get a client's logo URL
async function getClientLogoUrl(clientId: string, tenant: string): Promise<string | null>
Batch Functions
For efficient retrieval of multiple avatar URLs in a single query:
// Get logo URLs for multiple clients
async function getClientLogoUrlsBatch(clientIds: string[], tenant: string): Promise<Map<string, string | null>>
// Get avatar URLs for multiple users
async function getUserAvatarUrlsBatch(userIds: string[], tenant: string): Promise<Map<string, string | null>>
// Get avatar URLs for multiple contacts
async function getContactAvatarUrlsBatch(contactIds: string[], tenant: string): Promise<Map<string, string | null>>
// Generic batch function for any entity type
async function getEntityImageUrlsBatch(entityType: EntityType, entityIds: string[], tenant: string): Promise<Map<string, string | null>>
Integration Examples
User Profile Page
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<string | null>(null);
useEffect(() => {
const fetchAvatarUrl = async () => {
const url = await getUserAvatarUrl(userId, tenant);
setAvatarUrl(url);
};
fetchAvatarUrl();
}, [userId, tenant]);
return (
<div className="profile-page">
<h1>User Profile</h1>
<div className="avatar-section">
<EntityImageUpload
entityType="user"
entityId={userId}
entityName={userName}
imageUrl={avatarUrl}
onImageChange={(newUrl) => setAvatarUrl(newUrl)}
uploadAction={uploadUserAvatar}
deleteAction={deleteUserAvatar}
size="lg"
/>
</div>
{/* Rest of profile content */}
</div>
);
};
Client Details Page
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<string | null>(null);
useEffect(() => {
const fetchLogoUrl = async () => {
const url = await getClientLogoUrl(clientId, tenant);
setLogoUrl(url);
};
fetchLogoUrl();
}, [clientId, tenant]);
return (
<div className="client-details">
<div className="client-header">
<EntityImageUpload
entityType="client"
entityId={clientId}
entityName={clientName}
imageUrl={logoUrl}
onImageChange={(newUrl) => setLogoUrl(newUrl)}
uploadAction={uploadClientLogo}
deleteAction={deleteClientLogo}
size="xl"
/>
<h1>{clientName}</h1>
</div>
{/* Rest of client details */}
</div>
);
};
Ticket Conversation with User Avatars
import { UserAvatar, ContactAvatar } from '@alga-psa/ui';
const TicketComment = ({ comment, author, isContact }) => {
return (
<div className="comment">
<div className="comment-avatar">
{isContact ? (
<ContactAvatar
contactId={author.id}
contactName={author.name}
avatarUrl={author.avatarUrl}
size="md"
/>
) : (
<UserAvatar
userId={author.id}
userName={author.name}
avatarUrl={author.avatarUrl}
size="md"
/>
)}
</div>
<div className="comment-content">
<div className="comment-header">
<span className="author-name">{author.name}</span>
<span className="comment-time">{comment.timestamp}</span>
</div>
<div className="comment-body">
{comment.content}
</div>
</div>
</div>
);
};
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
-
Image Optimization:
- The system automatically processes uploaded images to optimize them for display.
- Images are stored efficiently in the storage system.
-
Loading States:
- The avatar components include built-in loading states to provide visual feedback during image loading.
- Use the
keyprop with a timestamp when updating images to force re-rendering.
-
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
-
Upload Errors:
- The
EntityImageUploadcomponent handles upload errors and displays appropriate toast messages. - Server-side validation ensures only valid images are accepted.
- The
-
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
onImageChangecallback is properly updating state in parent components. - Add a timestamp query parameter to the image URL to bypass browser caching.
- Use the
keyprop 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:
- Storage System: For storing and retrieving image files.
- Document System: For managing document metadata and associations.
- User Management: For user avatars and permissions.
- Client Management: For client logos.
- Contact Management: For contact avatars.
Future Enhancements
Potential improvements to consider for the Avatar System:
- Image Cropping: Add support for cropping images during upload.
- Multiple Image Sizes: Generate and store multiple sizes of each image for different display contexts.
- Animated Avatars: Support for animated GIFs or short video avatars.
- Default Avatars: Provide a selection of default avatars instead of just initials.
- Group Avatars: Support for displaying multiple avatars in a group (e.g., for teams).