fix: use presigned S3 URLs for avatar images instead of /uploads/ paths
Avatar images were rendered via /uploads/ which doesn't exist since the S3 migration. Now the server enriches profile responses with avatarImageUrl (presigned S3 URL) and the frontend uses it directly. Also fixed the public profile page at /users/:id. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@ export function ProfileSection() {
|
||||
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [bio, setBio] = useState("");
|
||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
||||
const [avatarFilename, setAvatarFilename] = useState<string | null>(null);
|
||||
const [avatarDisplayUrl, setAvatarDisplayUrl] = useState<string | null>(null);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [message, setMessage] = useState<{
|
||||
type: "success" | "error";
|
||||
@@ -24,7 +25,8 @@ export function ProfileSection() {
|
||||
if (profile && !dirty) {
|
||||
setDisplayName(profile.displayName ?? "");
|
||||
setBio(profile.bio ?? "");
|
||||
setAvatarUrl(profile.avatarUrl ?? null);
|
||||
setAvatarFilename(profile.avatarUrl ?? null);
|
||||
setAvatarDisplayUrl(profile.avatarImageUrl ?? null);
|
||||
}
|
||||
}, [profile, dirty]);
|
||||
|
||||
@@ -34,7 +36,7 @@ export function ProfileSection() {
|
||||
try {
|
||||
await updateProfile.mutateAsync({
|
||||
displayName: displayName.trim() || undefined,
|
||||
avatarUrl,
|
||||
avatarUrl: avatarFilename,
|
||||
bio: bio.trim() || undefined,
|
||||
});
|
||||
setDirty(false);
|
||||
@@ -63,13 +65,16 @@ export function ProfileSection() {
|
||||
return;
|
||||
}
|
||||
|
||||
const localPreview = URL.createObjectURL(file);
|
||||
setAvatarDisplayUrl(localPreview);
|
||||
setUploading(true);
|
||||
setMessage(null);
|
||||
try {
|
||||
const result = await apiUpload<{ filename: string }>("/api/images", file);
|
||||
setAvatarUrl(result.filename);
|
||||
setAvatarFilename(result.filename);
|
||||
setDirty(true);
|
||||
} catch {
|
||||
setAvatarDisplayUrl(null);
|
||||
setMessage({ type: "error", text: "Avatar upload failed." });
|
||||
} finally {
|
||||
setUploading(false);
|
||||
@@ -92,9 +97,9 @@ export function ProfileSection() {
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="relative w-16 h-16 rounded-full overflow-hidden cursor-pointer group shrink-0"
|
||||
>
|
||||
{avatarUrl ? (
|
||||
{avatarDisplayUrl ? (
|
||||
<img
|
||||
src={`/uploads/${avatarUrl}`}
|
||||
src={avatarDisplayUrl}
|
||||
alt="Avatar"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
@@ -145,11 +150,12 @@ export function ProfileSection() {
|
||||
>
|
||||
{uploading ? "Uploading..." : "Change avatar"}
|
||||
</button>
|
||||
{avatarUrl && (
|
||||
{avatarFilename && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setAvatarUrl(null);
|
||||
setAvatarFilename(null);
|
||||
setAvatarDisplayUrl(null);
|
||||
setDirty(true);
|
||||
}}
|
||||
className="block text-xs text-red-500 hover:text-red-700 mt-0.5"
|
||||
|
||||
Reference in New Issue
Block a user