fix: correct avatar upload and DiceBear URL version
- Add api.upload() to client.ts that passes FormData without setting Content-Type, letting the browser set multipart/form-data with the correct boundary (was causing 422 on the upload endpoint) - Use api.upload() instead of api.post() for avatar file upload - Update DiceBear URLs from v6 to 9.x in both frontend and backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ class AvatarService:
|
|||||||
"""Service for generating and managing user avatars."""
|
"""Service for generating and managing user avatars."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.base_url = "https://api.dicebear.com/v6"
|
self.base_url = "https://api.dicebear.com/9.x"
|
||||||
|
|
||||||
async def generate_avatar_url(self, seed: str, style: str = "identicon") -> str:
|
async def generate_avatar_url(self, seed: str, style: str = "identicon") -> str:
|
||||||
"""Generate a DiceBear avatar URL for the given seed.
|
"""Generate a DiceBear avatar URL for the given seed.
|
||||||
|
|||||||
@@ -14,13 +14,16 @@ export function clearToken(): void {
|
|||||||
|
|
||||||
async function request<T>(
|
async function request<T>(
|
||||||
path: string,
|
path: string,
|
||||||
options: RequestInit = {}
|
options: RequestInit = {},
|
||||||
|
isFormData = false
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
|
||||||
...(options.headers as Record<string, string>),
|
...(options.headers as Record<string, string>),
|
||||||
};
|
};
|
||||||
|
if (!isFormData) {
|
||||||
|
headers["Content-Type"] = "application/json";
|
||||||
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
headers["Authorization"] = `Bearer ${token}`;
|
headers["Authorization"] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
@@ -42,6 +45,8 @@ export const api = {
|
|||||||
get: <T>(path: string) => request<T>(path),
|
get: <T>(path: string) => request<T>(path),
|
||||||
post: <T>(path: string, body: unknown) =>
|
post: <T>(path: string, body: unknown) =>
|
||||||
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
|
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
|
||||||
|
upload: <T>(path: string, formData: FormData) =>
|
||||||
|
request<T>(path, { method: "POST", body: formData }, true),
|
||||||
patch: <T>(path: string, body: unknown) =>
|
patch: <T>(path: string, body: unknown) =>
|
||||||
request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
|
request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
|
||||||
delete: (path: string) => request<void>(path, { method: "DELETE" }),
|
delete: (path: string) => request<void>(path, { method: "DELETE" }),
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
|||||||
|
|
||||||
console.log("Uploading file to /auth/me/avatar");
|
console.log("Uploading file to /auth/me/avatar");
|
||||||
console.log("Final file size:", processedFile.size);
|
console.log("Final file size:", processedFile.size);
|
||||||
const response = await api.post<MemberRead>('/auth/me/avatar', formData);
|
const response = await api.upload<MemberRead>('/auth/me/avatar', formData);
|
||||||
console.log("Upload response:", response);
|
console.log("Upload response:", response);
|
||||||
|
|
||||||
setAvatarUrl(response.avatar_url || '');
|
setAvatarUrl(response.avatar_url || '');
|
||||||
@@ -263,7 +263,7 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
|||||||
try {
|
try {
|
||||||
// Generate a new random avatar using user ID as seed for consistency
|
// Generate a new random avatar using user ID as seed for consistency
|
||||||
const seed = Math.random().toString(36).substring(2, 15);
|
const seed = Math.random().toString(36).substring(2, 15);
|
||||||
const newAvatarUrl = `https://api.dicebear.com/v6/identicon/svg?seed=${seed}&backgroundType=gradientLinear&size=128`;
|
const newAvatarUrl = `https://api.dicebear.com/9.x/identicon/svg?seed=${seed}&backgroundType=gradientLinear&size=128`;
|
||||||
|
|
||||||
console.log("Generated avatar URL:", newAvatarUrl);
|
console.log("Generated avatar URL:", newAvatarUrl);
|
||||||
console.log("Calling updateSettings with:", { avatar_url: newAvatarUrl });
|
console.log("Calling updateSettings with:", { avatar_url: newAvatarUrl });
|
||||||
@@ -300,7 +300,7 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
|||||||
onError={() => {
|
onError={() => {
|
||||||
console.error("Failed to load avatar:", avatarUrl);
|
console.error("Failed to load avatar:", avatarUrl);
|
||||||
// Set to default avatar on error
|
// Set to default avatar on error
|
||||||
setAvatarUrl(`https://api.dicebear.com/v6/identicon/svg?seed=${me.id}&backgroundType=gradientLinear&size=128`);
|
setAvatarUrl(`https://api.dicebear.com/9.x/identicon/svg?seed=${me.id}&backgroundType=gradientLinear&size=128`);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user