feat: implement user profiles and settings

Add comprehensive user profile system allowing users to showcase
their activity and customize their profiles.

Database Changes:
- Added profile fields to User model: slug, displayName, bio, profilePublic
- Added index on slug field for efficient lookups

API Changes:
- Added GET /users/me endpoint to fetch current user
- Added PUT /users/me endpoint to update user settings
- Added GET /users/profile/:identifier endpoint for public profiles
- Updated UserService with profile methods and statistics
- Modified AuthService to include profile fields in user responses

Frontend Changes:
- Created ProfileComponent to display user profiles with stats
- Created SettingsComponent for profile customization
- Added profile and settings routes
- Updated header dropdown menu with profile links
- Enhanced UserService with profile methods
- Added updateUser method to AuthService

Features:
- Custom profile slugs for clean URLs
- Display names separate from usernames
- User bios (up to 500 characters)
- Public/private profile toggle
- Activity statistics (suggestions, likes, comments, acceptance rate)
- Badge display (Staff, Mod, VIP, Discord Member)
- Beautiful witch-themed styling

Closes #45
This commit is contained in:
2026-02-19 17:27:35 -08:00
committed by Naomi Carrigan
parent 7579f1ec97
commit 34c7ca8ba2
11 changed files with 989 additions and 11 deletions
@@ -9,6 +9,35 @@ import { Observable } from 'rxjs';
import { ApiService } from './api.service';
import { User } from '@library/shared-types';
export interface UserProfileResponse {
id: string;
username: string;
displayName?: string;
avatar?: string;
bio?: string;
slug?: string;
badges: {
isStaff: boolean;
isMod: boolean;
isVip: boolean;
inDiscord: boolean;
};
stats: {
suggestionsCount: number;
suggestionsAcceptedCount: number;
likesCount: number;
commentsCount: number;
};
createdAt: Date;
}
export interface UpdateUserSettingsRequest {
slug?: string;
displayName?: string;
bio?: string;
profilePublic?: boolean;
}
@Injectable({
providedIn: 'root'
})
@@ -26,4 +55,16 @@ export class UserService {
unbanUser(userId: string): Observable<User> {
return this.api.post<User>(`/users/${userId}/unban`, {});
}
getMe(): Observable<User> {
return this.api.get<User>('/users/me');
}
updateSettings(settings: UpdateUserSettingsRequest): Observable<User> {
return this.api.put<User>('/users/me', settings);
}
getProfile(identifier: string): Observable<UserProfileResponse> {
return this.api.get<UserProfileResponse>(`/users/profile/${identifier}`);
}
}