User Profile
The User Profile module provides comprehensive functionality for displaying and managing user profile information, including personal details, preferences, and workspace associations.
Overview
The User Profile page offers:
- Complete profile information display
- Profile editing capabilities
- Avatar/profile picture management
- Multi-language support
- Workspace associations display
- Address management
Profile Components
Profile Information
The profile system manages the following user information:
interface UserProfile {
id: string;
name: string;
email: string;
phoneNumber?: string;
profilePicture?: string;
preferredUsername?: string;
birthday?: string;
gender?: string;
language?: string;
address?: Address;
workspaceMembers?: WorkspaceMember[];
}
interface Address {
street?: string;
city?: string;
state?: string;
postalCode?: string;
country?: string;
}
Getting User Profile
Fetch Current User Profile
Use the GraphQL query to retrieve the current user's profile:
import { gql, useQuery } from '@apollo/client';
const GET_PROFILE = gql`
query GetProfile {
getProfile {
id
name
email
phoneNumber
profilePicture
preferredUsername
birthday
gender
language
address {
street
city
state
postalCode
country
}
workspaceMembers {
workspace {
id
name
type
}
}
}
}
`;
// In your component
const { data, loading, error } = useQuery(GET_PROFILE);
const userProfile = data?.getProfile;
Updating Profile Information
Edit Profile Mutation
Update user profile with the following mutation:
const UPDATE_PROFILE = gql`
mutation UpdateProfile($input: editUserInput!) {
editProfile(input: $input) {
id
name
email
phoneNumber
profilePicture
preferredUsername
birthday
gender
language
address {
street
city
state
postalCode
country
}
updatedAt
}
}
`;
// Usage
const [updateProfile] = useMutation(UPDATE_PROFILE);
await updateProfile({
variables: {
input: {
id: userId,
name: 'John Doe',
email: '[email protected]',
phoneNumber: '+1234567890',
preferredUsername: 'johndoe',
birthday: '1990-01-01',
gender: 'male',
language: 'en',
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
postalCode: '10001',
country: 'United States'
}
}
}
});
Profile Picture Management
Uploading Profile Pictures
The profile system supports image upload with validation:
// Upload image to API
const uploadImageToAPI = async (file: File): Promise<string> => {
const formData = new FormData();
formData.append('file', file);
const apiUrl = import.meta.env.VITE_WORKSPACE_API_URL || 'http://localhost:3602';
const response = await fetch(`${apiUrl}/upload/image`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error('Failed to upload image');
}
const result = await response.json();
return result.url;
};
// File validation
const handleImageUpload = (file: File) => {
// Validate file type
if (!file.type.startsWith('image/')) {
throw new Error('Please select an image file');
}
// Validate file size (max 5MB)
if (file.size > 5 * 1024 * 1024) {
throw new Error('Image must be less than 5MB');
}
// Process upload
return uploadImageToAPI(file);
};
Multi-Language Support
Supported Languages
The profile system supports 15+ languages with native names and regional flags:
const languages = [
{ code: 'en', name: 'English', nativeName: 'English', flag: '🇺🇸' },
{ code: 'es', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' },
{ code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷' },
{ code: 'de', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪' },
{ code: 'it', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹' },
{ code: 'pt', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹' },
{ code: 'ru', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' },
{ code: 'zh', name: 'Chinese', nativeName: '中文', flag: '🇨🇳' },
{ code: 'ja', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' },
{ code: 'ko', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' },
{ code: 'ar', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦', direction: 'rtl' },
{ code: 'he', name: 'Hebrew', nativeName: 'עברית', flag: '🇮🇱', direction: 'rtl' },
{ code: 'hi', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳' },
{ code: 'th', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭' },
{ code: 'vi', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳' }
];
Language Change Implementation
import { useTranslation } from 'react-i18next';
const { i18n } = useTranslation();
// Change language
const changeLanguage = async (languageCode: string) => {
await i18n.changeLanguage(languageCode);
// Update user profile
await updateProfile({
variables: {
input: {
id: userId,
language: languageCode
}
}
});
};
Address Management
Country Selection
The system includes a comprehensive list of 200+ countries for address management:
const countries = [
'United States', 'Canada', 'United Kingdom', 'Germany', 'France',
'Italy', 'Spain', 'Netherlands', 'Belgium', 'Switzerland',
// ... 200+ countries
].sort();
Address Validation
interface AddressInput {
street?: string;
city?: string;
state?: string;
postalCode?: string;
country?: string;
}
const validateAddress = (address: AddressInput): boolean => {
// Basic validation rules
if (address.postalCode && !/^[\w\s-]+$/.test(address.postalCode)) {
return false;
}
// Country-specific validation can be added
if (address.country === 'United States' && address.postalCode) {
return /^\d{5}(-\d{4})?$/.test(address.postalCode);
}
return true;
};
Timezone Configuration
Available Timezones
const timezones = [
{ value: 'UTC', label: 'UTC' },
{ value: 'America/New_York', label: 'Eastern Time (ET)' },
{ value: 'America/Chicago', label: 'Central Time (CT)' },
{ value: 'America/Denver', label: 'Mountain Time (MT)' },
{ value: 'America/Los_Angeles', label: 'Pacific Time (PT)' },
{ value: 'Europe/London', label: 'Greenwich Mean Time (GMT)' },
{ value: 'Europe/Paris', label: 'Central European Time (CET)' },
{ value: 'Asia/Tokyo', label: 'Japan Standard Time (JST)' },
{ value: 'Asia/Kolkata', label: 'India Standard Time (IST)' }
];
Workspace Associations
Displaying User Workspaces
Users can be members of multiple workspaces. Display them with appropriate badges:
interface WorkspaceMember {
workspace: {
id: string;
name: string;
type: string;
};
}
// Display workspaces
const renderWorkspaces = (members: WorkspaceMember[]) => {
return members.map(member => (
<Badge key={member.workspace.id} variant="secondary">
{member.workspace.name}
</Badge>
));
};
UI Components
Profile Form Components
The profile page uses these UI primitives:
import { Avatar, AvatarImage, AvatarFallback } from '@sdk/modules/ui-primitives/components/core/avatar';
import { Button } from '@sdk/modules/ui-primitives/components/core/button';
import { Card, CardContent, CardHeader, CardTitle } from '@sdk/modules/ui-primitives/components/core/card';
import { Input, Label, Select } from '@sdk/modules/ui-primitives/components/form';
import { Alert, Badge } from '@sdk/modules/ui-primitives/components/feedback';
import { useToast } from '@sdk/hooks/use-toast';
Edit Mode Toggle
Implement edit mode with state management:
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState(initialProfileData);
const handleEditToggle = () => {
if (isEditing) {
// Save changes
handleSaveProfile();
} else {
// Enter edit mode
setIsEditing(true);
}
};
const handleCancel = () => {
setIsEditing(false);
// Reset form to original data
setFormData(originalProfileData);
};
Error Handling
Profile Update Errors
Handle various error scenarios:
const [updateProfile] = useMutation(UPDATE_PROFILE, {
onCompleted: () => {
toast({
title: 'Profile Updated',
description: 'Your profile has been successfully updated.',
});
setIsEditing(false);
refetch();
},
onError: (error) => {
toast({
title: 'Update Failed',
description: error.message,
variant: 'destructive',
});
}
});
Loading States
Implement proper loading states:
if (loading) {
return (
<div className="animate-pulse space-y-4">
<div className="h-32 bg-muted rounded-lg"></div>
<div className="h-96 bg-muted rounded-lg"></div>
</div>
);
}
if (error) {
return (
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>
Unable to load profile. Please try again later.
</AlertDescription>
</Alert>
);
}
Best Practices
Security Considerations
- Input Validation: Always validate user input before submission
- File Size Limits: Enforce maximum file sizes for profile pictures (5MB recommended)
- Secure Upload: Use authenticated endpoints for image uploads
- Sanitization: Sanitize all text inputs to prevent XSS attacks
Performance Optimization
- Image Optimization: Compress images before upload
- Lazy Loading: Load profile sections on demand
- Caching: Cache profile data to reduce API calls
- Debouncing: Debounce form inputs during editing
Accessibility
- Form Labels: Use proper labels for all form inputs
- ARIA Attributes: Include appropriate ARIA attributes
- Keyboard Navigation: Ensure all actions are keyboard accessible
- Screen Reader Support: Test with screen readers
Complete Example
Here's a minimal implementation of a user profile component:
import React, { useState, useEffect } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';
import { Card, CardContent, CardHeader, CardTitle } from '@sdk/components';
import { Button, Input, Label } from '@sdk/components';
import { useToast } from '@sdk/hooks';
const UserProfile: React.FC = () => {
const { toast } = useToast();
const [isEditing, setIsEditing] = useState(false);
const { data, loading, error, refetch } = useQuery(GET_PROFILE);
const [updateProfile] = useMutation(UPDATE_PROFILE);
const [formData, setFormData] = useState({
name: '',
email: '',
phoneNumber: '',
});
useEffect(() => {
if (data?.getProfile) {
setFormData({
name: data.getProfile.name || '',
email: data.getProfile.email || '',
phoneNumber: data.getProfile.phoneNumber || '',
});
}
}, [data]);
const handleSave = async () => {
try {
await updateProfile({
variables: {
input: {
id: data.getProfile.id,
...formData
}
}
});
toast({
title: 'Success',
description: 'Profile updated successfully',
});
setIsEditing(false);
refetch();
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update profile',
variant: 'destructive',
});
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error loading profile</div>;
return (
<Card>
<CardHeader>
<CardTitle>User Profile</CardTitle>
<Button onClick={() => setIsEditing(!isEditing)}>
{isEditing ? 'Cancel' : 'Edit'}
</Button>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<Label>Name</Label>
<Input
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
disabled={!isEditing}
/>
</div>
<div>
<Label>Email</Label>
<Input
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
disabled={!isEditing}
/>
</div>
<div>
<Label>Phone</Label>
<Input
value={formData.phoneNumber}
onChange={(e) => setFormData({...formData, phoneNumber: e.target.value})}
disabled={!isEditing}
/>
</div>
{isEditing && (
<Button onClick={handleSave}>Save Changes</Button>
)}
</div>
</CardContent>
</Card>
);
};
export default UserProfile;
Related Documentation
- User Management - Complete user management operations
- Authentication - Authentication and session management
- RBAC - Role-based access control for profiles
- API Reference - Complete GraphQL API documentation