feat: Add user profile photo upload with base64 encoding
Added complete photo upload support for user profiles: - Added photoUrl field to User entity and all auth responses - Created migration V008 for photo_url column in users table - Updated UpdateProfileDto to include photoUrl validation - Modified auth.service.ts to handle photoUrl in all user responses (register, login, getUserById, updateProfile, refreshAccessToken, loginWithExternalAuth) - Updated AuthResponse interface to include photoUrl - Updated frontend User and UserProfile interfaces to include photoUrl - Updated AppShell to display user photoUrl in header avatar - Fixed ChildDialog to remove invalid props from PhotoUpload component The photo upload uses base64 encoding (max 5MB) for simplicity and works across all environments. Future enhancement can migrate to CDN/object storage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,7 @@ export class AuthService {
|
||||
id: savedUser.id,
|
||||
email: savedUser.email,
|
||||
name: savedUser.name,
|
||||
photoUrl: savedUser.photoUrl,
|
||||
locale: savedUser.locale,
|
||||
emailVerified: savedUser.emailVerified,
|
||||
preferences: savedUser.preferences,
|
||||
@@ -234,6 +235,7 @@ export class AuthService {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
photoUrl: user.photoUrl,
|
||||
locale: user.locale,
|
||||
emailVerified: user.emailVerified,
|
||||
preferences: user.preferences,
|
||||
@@ -299,6 +301,7 @@ export class AuthService {
|
||||
id: refreshToken.user.id,
|
||||
email: refreshToken.user.email,
|
||||
name: refreshToken.user.name,
|
||||
photoUrl: refreshToken.user.photoUrl,
|
||||
locale: refreshToken.user.locale,
|
||||
emailVerified: refreshToken.user.emailVerified,
|
||||
preferences: refreshToken.user.preferences,
|
||||
@@ -361,6 +364,7 @@ export class AuthService {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
photoUrl: user.photoUrl,
|
||||
role: 'user',
|
||||
locale: user.locale,
|
||||
timezone: user.timezone,
|
||||
@@ -375,6 +379,7 @@ export class AuthService {
|
||||
userId: string,
|
||||
updateData: {
|
||||
name?: string;
|
||||
photoUrl?: string;
|
||||
timezone?: string;
|
||||
preferences?: {
|
||||
notifications?: boolean;
|
||||
@@ -407,6 +412,12 @@ export class AuthService {
|
||||
this.logger.log(`Updated user.name to: "${user.name}"`);
|
||||
}
|
||||
|
||||
// Update photo URL if provided
|
||||
if (updateData.photoUrl !== undefined) {
|
||||
user.photoUrl = updateData.photoUrl;
|
||||
this.logger.log(`Updated user.photoUrl (length: ${updateData.photoUrl?.length || 0})`);
|
||||
}
|
||||
|
||||
// Update timezone if provided
|
||||
if (updateData.timezone !== undefined) {
|
||||
user.timezone = updateData.timezone;
|
||||
@@ -431,6 +442,7 @@ export class AuthService {
|
||||
id: updatedUser.id,
|
||||
email: updatedUser.email,
|
||||
name: updatedUser.name,
|
||||
photoUrl: updatedUser.photoUrl,
|
||||
role: 'user',
|
||||
locale: updatedUser.locale,
|
||||
timezone: updatedUser.timezone,
|
||||
@@ -556,6 +568,7 @@ export class AuthService {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
photoUrl: user.photoUrl,
|
||||
locale: user.locale,
|
||||
emailVerified: user.emailVerified,
|
||||
preferences: user.preferences,
|
||||
|
||||
@@ -25,6 +25,10 @@ export class UpdateProfileDto {
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
photoUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
locale?: string;
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface AuthResponse {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
photoUrl?: string;
|
||||
locale: string;
|
||||
emailVerified: boolean;
|
||||
families?: Array<{ id: string; familyId: string; role: string }>;
|
||||
|
||||
@@ -153,8 +153,6 @@ export function ChildDialog({ open, onClose, onSubmit, child, isLoading = false
|
||||
onChange={(url) => setFormData({ ...formData, photoUrl: url })}
|
||||
disabled={isLoading}
|
||||
size={80}
|
||||
childId={child?.id}
|
||||
type="profile"
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
@@ -116,6 +116,7 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={user?.photoUrl}
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface UserProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
photoUrl?: string;
|
||||
role: string;
|
||||
locale: string;
|
||||
emailVerified: boolean;
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
photoUrl?: string;
|
||||
role: string;
|
||||
families?: Array<{
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user