From d926d3a80bfcd4b63333967fb1d632a8f2324f21 Mon Sep 17 00:00:00 2001 From: ahong75 Date: Thu, 12 Sep 2024 16:22:21 -0400 Subject: [PATCH 1/2] Added image upload component and backend call --- web/components/BabyBook/ImageUpload.tsx | 57 ++++++++ web/package-lock.json | 180 ++++++++++++++++++++++++ web/pages/api/save-image.ts | 38 +++++ web/pages/upload-test.tsx | 14 ++ 4 files changed, 289 insertions(+) create mode 100644 web/components/BabyBook/ImageUpload.tsx create mode 100644 web/pages/api/save-image.ts create mode 100644 web/pages/upload-test.tsx diff --git a/web/components/BabyBook/ImageUpload.tsx b/web/components/BabyBook/ImageUpload.tsx new file mode 100644 index 0000000..125f58b --- /dev/null +++ b/web/components/BabyBook/ImageUpload.tsx @@ -0,0 +1,57 @@ +import React, { useContext } from "react"; +import { storage } from "../../db/firebase"; // import firebase storage +import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; + +export const ImageUpload = ({}) => { + const saveFile = async (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + try { + const file = files[0]; + + const extension = file.name.split(".").pop(); + const storageRef = ref(storage, `images/${Date.now()}.${extension}`); + + const uploadTask = uploadBytesResumable(storageRef, file); + + uploadTask.on( + "state_changed", + (snapshot) => { + const progress = + (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + }, + (error) => { + console.error("Upload failed:", error); + }, + async () => { + const downloadURL = await getDownloadURL(uploadTask.snapshot.ref); + // TODO: Incorporate baby and caregiver context + // https://github.com/GTBitsOfGood/motherhood-beyond-bars/blob/_original/mobile/screens/babybook/SelectPicture.tsx#L28 + // TODO: Incorporate caption + const metadata = { + imageURL: downloadURL, + caregiverID: "test-caregiverId", + caption: "test-caption", + babyID: "test-babyId", + }; + const response = await fetch("/api/save-image", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(metadata), + }); + if (!response.ok) { + const result = await response.json(); + alert(result.error || "Error uploading image metadata"); + } + } + ); + } catch (error) { + console.error("Upload failed:", error); + alert("An error occurred while uploading the image."); + } + }; + return ; +}; diff --git a/web/package-lock.json b/web/package-lock.json index 4202634..22577a1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1232,6 +1232,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@next/swc-android-arm-eabi": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz", + "integrity": "sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-android-arm64": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz", + "integrity": "sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-darwin-arm64": { "version": "12.3.4", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz", @@ -1247,6 +1277,156 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz", + "integrity": "sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-freebsd-x64": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz", + "integrity": "sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz", + "integrity": "sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz", + "integrity": "sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz", + "integrity": "sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz", + "integrity": "sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz", + "integrity": "sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz", + "integrity": "sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz", + "integrity": "sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "12.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz", + "integrity": "sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", diff --git a/web/pages/api/save-image.ts b/web/pages/api/save-image.ts new file mode 100644 index 0000000..ffcbdc7 --- /dev/null +++ b/web/pages/api/save-image.ts @@ -0,0 +1,38 @@ +// pages/api/saveImage.js +import { NextApiRequest, NextApiResponse } from "next"; +import { db } from "../../db/firebase"; // Firestore database +import { doc, setDoc, Timestamp } from "firebase/firestore"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === "POST") { + // Extracting the necessary data from the request body + const { imageURL, caregiverID, caption, babyID } = req.body; + + try { + // Create a reference to the document in the 'babies' collection and save metadata + const docRef = doc( + db, + "babies", + babyID, + "book", + `${caregiverID}_${Date.now()}` + ); + await setDoc(docRef, { + imageUrl: imageURL, + caption: caption, + date: Timestamp.now(), + caregiverId: caregiverID, + }); + + res.status(200).json({ message: "Image metadata saved successfully" }); + } catch (error) { + console.error("Error saving image metadata:", error); + res.status(500).json({ error: "Error saving image metadata" }); + } + } else { + res.status(405).json({ message: "Method not allowed" }); + } +} diff --git a/web/pages/upload-test.tsx b/web/pages/upload-test.tsx new file mode 100644 index 0000000..76899df --- /dev/null +++ b/web/pages/upload-test.tsx @@ -0,0 +1,14 @@ +// pages/upload-test.js + +import { ImageUpload } from "@components/BabyBook/ImageUpload"; + +const UploadTestPage = () => { + return ( +
+

Test Image Upload

+ +
+ ); +}; + +export default UploadTestPage; From 9f090c3595620e15d1ba1a9000aafd714421fd61 Mon Sep 17 00:00:00 2001 From: Parker Abramson Date: Tue, 24 Sep 2024 18:00:46 -0400 Subject: [PATCH 2/2] Clean and combine code --- web/components/BabyBook/ImageUpload.tsx | 57 ---------------------- web/db/actions/caregiver/Photo.ts | 64 +++++++++++++++++++++++++ web/pages/api/save-image.ts | 38 --------------- web/pages/upload-test.tsx | 14 ------ 4 files changed, 64 insertions(+), 109 deletions(-) delete mode 100644 web/components/BabyBook/ImageUpload.tsx create mode 100644 web/db/actions/caregiver/Photo.ts delete mode 100644 web/pages/api/save-image.ts delete mode 100644 web/pages/upload-test.tsx diff --git a/web/components/BabyBook/ImageUpload.tsx b/web/components/BabyBook/ImageUpload.tsx deleted file mode 100644 index 125f58b..0000000 --- a/web/components/BabyBook/ImageUpload.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useContext } from "react"; -import { storage } from "../../db/firebase"; // import firebase storage -import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; - -export const ImageUpload = ({}) => { - const saveFile = async (e: React.ChangeEvent) => { - const files = e.target.files; - if (!files || files.length === 0) return; - - try { - const file = files[0]; - - const extension = file.name.split(".").pop(); - const storageRef = ref(storage, `images/${Date.now()}.${extension}`); - - const uploadTask = uploadBytesResumable(storageRef, file); - - uploadTask.on( - "state_changed", - (snapshot) => { - const progress = - (snapshot.bytesTransferred / snapshot.totalBytes) * 100; - }, - (error) => { - console.error("Upload failed:", error); - }, - async () => { - const downloadURL = await getDownloadURL(uploadTask.snapshot.ref); - // TODO: Incorporate baby and caregiver context - // https://github.com/GTBitsOfGood/motherhood-beyond-bars/blob/_original/mobile/screens/babybook/SelectPicture.tsx#L28 - // TODO: Incorporate caption - const metadata = { - imageURL: downloadURL, - caregiverID: "test-caregiverId", - caption: "test-caption", - babyID: "test-babyId", - }; - const response = await fetch("/api/save-image", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(metadata), - }); - if (!response.ok) { - const result = await response.json(); - alert(result.error || "Error uploading image metadata"); - } - } - ); - } catch (error) { - console.error("Upload failed:", error); - alert("An error occurred while uploading the image."); - } - }; - return ; -}; diff --git a/web/db/actions/caregiver/Photo.ts b/web/db/actions/caregiver/Photo.ts new file mode 100644 index 0000000..92530d8 --- /dev/null +++ b/web/db/actions/caregiver/Photo.ts @@ -0,0 +1,64 @@ +import React from "react"; +import { db, storage } from "../../firebase"; // import firebase storage +import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; +import { doc, setDoc, Timestamp } from "firebase/firestore"; + +export async function uploadPhoto(e: React.ChangeEvent) { + const files = e.target.files; + if (!files || files.length === 0) { + return { success: false, error: "File attempted to be uploaded was empty" }; + } + + try { + const file = files[0]; + + const extension = file.name.split(".").pop(); + const storageRef = ref(storage, `images/${Date.now()}.${extension}`); + + const uploadTask = uploadBytesResumable(storageRef, file); + + uploadTask.on( + "state_changed", + (snapshot) => { + const progress = + (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + }, + (error) => { + return { success: false, error: `Upload failed: ${error}` }; + }, + async () => { + const downloadURL = await getDownloadURL(uploadTask.snapshot.ref); + // TODO: Incorporate baby and caregiver context + // https://github.com/GTBitsOfGood/motherhood-beyond-bars/blob/_original/mobile/screens/babybook/SelectPicture.tsx#L28 + // TODO: Incorporate caption + const metadata = { + imageURL: downloadURL, + caregiverID: "test-caregiverId", + caption: "test-caption", + babyID: "test-babyId", + }; + + const { imageURL, caregiverID, caption, babyID } = metadata; + + const docRef = doc( + db, + "babies", + babyID, + "book", + `${caregiverID}_${Date.now()}` + ); + + await setDoc(docRef, { + imageUrl: imageURL, + caption: caption, + date: Timestamp.now(), + caregiverId: caregiverID, + }); + + return { success: true, data: { downloadURL: downloadURL } }; + } + ); + } catch (error) { + return { success: false, error: `Upload failed: ${error}` }; + } +} diff --git a/web/pages/api/save-image.ts b/web/pages/api/save-image.ts deleted file mode 100644 index ffcbdc7..0000000 --- a/web/pages/api/save-image.ts +++ /dev/null @@ -1,38 +0,0 @@ -// pages/api/saveImage.js -import { NextApiRequest, NextApiResponse } from "next"; -import { db } from "../../db/firebase"; // Firestore database -import { doc, setDoc, Timestamp } from "firebase/firestore"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method === "POST") { - // Extracting the necessary data from the request body - const { imageURL, caregiverID, caption, babyID } = req.body; - - try { - // Create a reference to the document in the 'babies' collection and save metadata - const docRef = doc( - db, - "babies", - babyID, - "book", - `${caregiverID}_${Date.now()}` - ); - await setDoc(docRef, { - imageUrl: imageURL, - caption: caption, - date: Timestamp.now(), - caregiverId: caregiverID, - }); - - res.status(200).json({ message: "Image metadata saved successfully" }); - } catch (error) { - console.error("Error saving image metadata:", error); - res.status(500).json({ error: "Error saving image metadata" }); - } - } else { - res.status(405).json({ message: "Method not allowed" }); - } -} diff --git a/web/pages/upload-test.tsx b/web/pages/upload-test.tsx deleted file mode 100644 index 76899df..0000000 --- a/web/pages/upload-test.tsx +++ /dev/null @@ -1,14 +0,0 @@ -// pages/upload-test.js - -import { ImageUpload } from "@components/BabyBook/ImageUpload"; - -const UploadTestPage = () => { - return ( -
-

Test Image Upload

- -
- ); -}; - -export default UploadTestPage;