fix(homepage): fix baby profile photo upload failing due to CORS

Direct PUT to R2 presigned URL is cross-origin — browser blocks it.
Route the upload through the existing PUT /api/upload server proxy instead,
same pattern used for memories. Also return `key` from children POST so
the proxy call has the R2 object key without needing the presigned URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-24 13:37:36 +05:30
parent fa5e27bfd9
commit afae041208
2 changed files with 14 additions and 7 deletions

View file

@ -63,7 +63,7 @@ export async function POST(
{ expiresIn: 3600 } { expiresIn: 3600 }
); );
return NextResponse.json({ uploadUrl, publicUrl: `${baseUrl}/${r2Key}` }); return NextResponse.json({ uploadUrl, key: r2Key, publicUrl: `${baseUrl}/${r2Key}` });
} }
// PATCH — update child profile fields (imageUrl, and extensible for name/etc later) // PATCH — update child profile fields (imageUrl, and extensible for name/etc later)

View file

@ -188,17 +188,23 @@ export default function HomePage() {
if (!file || !childId) return; if (!file || !childId) return;
setUploadingPhoto(true); setUploadingPhoto(true);
try { try {
// 1. Get presigned R2 URL // 1. Get R2 key + public URL from server
const presignRes = await fetch(`/api/children/${childId}`, { const initRes = await fetch(`/api/children/${childId}`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contentType: file.type, filename: file.name }), body: JSON.stringify({ contentType: file.type, filename: file.name }),
}); });
if (!presignRes.ok) throw new Error("Failed to get upload URL"); if (!initRes.ok) throw new Error("Failed to get upload URL");
const { uploadUrl, publicUrl } = await presignRes.json(); const { key, publicUrl } = await initRes.json();
// 2. Upload directly to R2 // 2. Upload via server proxy — avoids CORS on direct R2 PUT
await fetch(uploadUrl, { method: "PUT", body: file, headers: { "Content-Type": file.type } }); const putParams = new URLSearchParams({ key, contentType: file.type });
const putRes = await fetch(`/api/upload?${putParams}`, {
method: "PUT",
body: file,
headers: { "Content-Type": file.type },
});
if (!putRes.ok) throw new Error("Upload failed");
// 3. Save URL to DB // 3. Save URL to DB
await fetch(`/api/children/${childId}`, { await fetch(`/api/children/${childId}`, {
@ -211,6 +217,7 @@ export default function HomePage() {
updateChildImage(childId, publicUrl); updateChildImage(childId, publicUrl);
} catch (err) { } catch (err) {
console.error("Photo upload failed:", err); console.error("Photo upload failed:", err);
alert("Photo upload failed. Please try again.");
} }
setUploadingPhoto(false); setUploadingPhoto(false);
if (photoInputRef.current) photoInputRef.current.value = ""; if (photoInputRef.current) photoInputRef.current.value = "";