feat: restore profile settings API integration

Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-02-23 11:35:54 +00:00
parent 7c616d412d
commit 549eb714c1
3 changed files with 396 additions and 63 deletions

View File

@@ -0,0 +1,90 @@
import { ApiResponse } from '@goodgo/types';
import { apiClient } from './client';
/**
* EN: Raw upload response from storage service.
* VI: Response upload thô từ storage service.
*/
interface UploadFileResult {
success: boolean;
fileId?: string;
objectKey?: string;
error?: string;
}
/**
* EN: CDN URL response payload.
* VI: Payload response URL CDN.
*/
interface CdnUrlResult {
url: string;
isCDN: boolean;
description: string;
}
/**
* EN: Normalized avatar upload response.
* VI: Response upload avatar đã chuẩn hóa.
*/
export interface UploadAvatarResult {
fileId: string;
objectKey?: string;
url: string;
}
/**
* EN: Storage API for avatar upload flow.
* VI: Storage API cho luồng upload avatar.
*/
export const storageApi = {
/**
* EN: Upload avatar as a public file and resolve CDN/fallback URL.
* VI: Upload avatar ở chế độ public và lấy URL CDN/fallback.
*/
uploadAvatar: async (file: File): Promise<ApiResponse<UploadAvatarResult>> => {
const formData = new FormData();
formData.append('file', file);
const uploadResponse = await apiClient.post<UploadFileResult>(
'/files/upload?accessLevel=public',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
const uploadedFileId = uploadResponse.data?.fileId;
if (!uploadResponse.success || !uploadResponse.data?.success || !uploadedFileId) {
throw new Error(
uploadResponse.data?.error ||
uploadResponse.error?.message ||
'Failed to upload avatar'
);
}
let resolvedUrl = '';
try {
const cdnUrlResponse = await apiClient.get<CdnUrlResult>(
`/files/${uploadedFileId}/cdn-url`
);
resolvedUrl = cdnUrlResponse.data?.url ?? '';
} catch {
// EN: Fallback keeps profile update possible even if CDN URL fetch fails.
// VI: Fallback vẫn cho phép cập nhật profile nếu lấy CDN URL thất bại.
resolvedUrl = '';
}
return {
success: true,
data: {
fileId: uploadedFileId,
objectKey: uploadResponse.data.objectKey,
url: resolvedUrl,
},
timestamp: new Date().toISOString(),
};
},
};

View File

@@ -9,16 +9,21 @@ import { apiClient } from './client';
export interface UserProfile {
id: string;
userId: string;
firstName?: string;
lastName?: string;
phone?: string;
phoneVerified?: boolean;
bio?: string;
avatarUrl?: string;
customFields?: Record<string, unknown>;
preferences?: Record<string, unknown>;
metadata?: Record<string, unknown>;
timezone?: string;
locale?: string;
phoneNumber?: {
countryCode: string;
nationalNumber: string;
};
attributes: Array<{
key: string;
value: string;
valueType: string;
}>;
createdAt: string;
updatedAt: string;
updatedAt?: string;
}
/**
@@ -26,13 +31,19 @@ export interface UserProfile {
* VI: DTO cập nhật profile người dùng
*/
export interface UpdateUserProfileDto {
firstName?: string;
lastName?: string;
phone?: string;
avatarUrl?: string;
customFields?: Record<string, unknown>;
preferences?: Record<string, unknown>;
metadata?: Record<string, unknown>;
bio?: string;
timezone?: string;
locale?: string;
avatarUrl?: string | null;
}
/**
* EN: Set profile attribute DTO.
* VI: DTO đặt thuộc tính profile.
*/
export interface SetProfileAttributeDto {
value: string;
valueType?: 'String' | 'Number' | 'Boolean' | 'Date' | 'Json';
}
/**
@@ -47,7 +58,7 @@ export const userApi = {
* @param userId - User ID / ID người dùng
*/
getProfile: async (userId: string): Promise<ApiResponse<UserProfile>> => {
return apiClient.get(`/identity/users/${userId}/profile`);
return apiClient.get(`/users/${userId}/profile`);
},
/**
@@ -61,7 +72,25 @@ export const userApi = {
userId: string,
data: UpdateUserProfileDto
): Promise<ApiResponse<UserProfile>> => {
return apiClient.put(`/identity/users/${userId}/profile`, data);
return apiClient.put(`/users/${userId}/profile`, data);
},
/**
* EN: Set custom profile attribute.
* VI: Đặt thuộc tính tùy chỉnh cho profile.
*/
setProfileAttribute: async (
userId: string,
key: string,
payload: SetProfileAttributeDto
): Promise<ApiResponse<{ key: string; value: string; valueType: string }>> => {
return apiClient.put(
`/users/${userId}/profile/attributes/${encodeURIComponent(key)}`,
{
value: payload.value,
valueType: payload.valueType ?? 'String',
}
);
},
/**
@@ -75,9 +104,7 @@ export const userApi = {
userId: string,
avatarUrl: string
): Promise<ApiResponse<UserProfile>> => {
return apiClient.post(`/identity/users/${userId}/profile/avatar`, {
avatarUrl,
});
return apiClient.put(`/users/${userId}/profile`, { avatarUrl });
},
/**
@@ -87,6 +114,6 @@ export const userApi = {
* @param userId - User ID / ID người dùng
*/
deleteAvatar: async (userId: string): Promise<ApiResponse> => {
return apiClient.delete(`/identity/users/${userId}/profile/avatar`);
return apiClient.put(`/users/${userId}/profile`, { avatarUrl: null });
},
};