From be50dd0d3cce651c18362d479959f868343384dc Mon Sep 17 00:00:00 2001 From: tunile943 <105695780+tunile943@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:55:51 -0400 Subject: [PATCH 01/17] Implement image uploading with Firebase (#233) * Create upload image function. Needs to wrap it around a router. * Add file extension (.jpg, .jpeg, .png) to uploaded file. * Cleanup code and fix bugs * Revert to previous image URL if no image selected during Event Edit. * Changing from fetch image URL from DB to FormEvents. * default image when editing images * Format * Fix create event button not working * Revert dropzone * Revert dropzone --------- Co-authored-by: Akinfolami Akin-Alamu Co-authored-by: Jason Zheng --- frontend/src/components/atoms/Dropzone.tsx | 12 +++++- .../src/components/organisms/EventForm.tsx | 37 ++++++++++++++-- .../src/components/organisms/ViewEvents.tsx | 2 +- frontend/src/pages/events/[eventid]/edit.tsx | 4 +- frontend/src/utils/helpers.ts | 42 ++++++++++++++++++- 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/atoms/Dropzone.tsx b/frontend/src/components/atoms/Dropzone.tsx index b3d8e217..5119f936 100644 --- a/frontend/src/components/atoms/Dropzone.tsx +++ b/frontend/src/components/atoms/Dropzone.tsx @@ -2,15 +2,23 @@ import React, { useState } from "react"; interface DropzoneProps { setError: React.Dispatch>; + selectedFile: File | null; + setSelectedFile: React.Dispatch>; label?: string; + [key: string]: any; } /** * Dropzone component that allows uploading files. Requires a setState to be * passed in to handle file upload errors. */ -const Dropzone = ({ setError, label }: DropzoneProps) => { - const [selectedFile, setSelectedFile] = useState(null); +const Dropzone = ({ + setError, + selectedFile, + setSelectedFile, + label, + ...props +}: DropzoneProps) => { const allowedFileTypes = ["image/jpg", "image/jpeg", "image/png"]; const maxFileSize = 50 * 1024 * 1024; // 50 MB diff --git a/frontend/src/components/organisms/EventForm.tsx b/frontend/src/components/organisms/EventForm.tsx index dbdb0a75..14e9ae30 100644 --- a/frontend/src/components/organisms/EventForm.tsx +++ b/frontend/src/components/organisms/EventForm.tsx @@ -17,7 +17,11 @@ import { Typography } from "@mui/material"; import { useAuth } from "@/utils/AuthContext"; import router from "next/router"; import Snackbar from "../atoms/Snackbar"; -import { convertToISO, fetchUserIdFromDatabase } from "@/utils/helpers"; +import { + convertToISO, + fetchUserIdFromDatabase, + uploadImageToFirebase, +} from "@/utils/helpers"; import { api } from "@/utils/api"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import Dropzone from "../atoms/Dropzone"; @@ -39,7 +43,7 @@ type FormValues = { location: string; volunteerSignUpCap: string; eventDescription: string; - eventImage: string; + imageURL: string; rsvpLinkImage: string; startDate: Date; startTime: Date; @@ -65,6 +69,9 @@ const EventForm = ({ setValue("eventDescription", value); }; + /** Dropzone file */ + const [selectedFile, setSelectedFile] = useState(null); + /** Dropzone errors */ const [dropzoneError, setDropzoneError] = useState(""); @@ -100,7 +107,7 @@ const EventForm = ({ location: eventDetails.location, volunteerSignUpCap: eventDetails.volunteerSignUpCap, eventDescription: eventDetails.eventDescription, - eventImage: eventDetails.eventImage, + imageURL: eventDetails.imageURL, rsvpLinkImage: eventDetails.rsvpLinkImage, startDate: eventDetails.startDate, startTime: eventDetails.startTime, @@ -127,6 +134,7 @@ const EventForm = ({ location, volunteerSignUpCap, eventDescription, + imageURL, startDate, startTime, endTime, @@ -134,12 +142,18 @@ const EventForm = ({ const userid = await fetchUserIdFromDatabase(user?.email as string); const startDateTime = convertToISO(startTime, startDate); const endDateTime = convertToISO(endTime, startDate); + let newImageURL = null; + if (selectedFile) { + newImageURL = await uploadImageToFirebase(userid, selectedFile); + } + const { response } = await api.post("/events", { userID: `${userid}`, event: { name: `${eventName}`, location: status === 0 ? "VIRTUAL" : `${location}`, description: `${eventDescription}`, + imageURL: newImageURL, startDate: startDateTime, endDate: endDateTime, capacity: +volunteerSignUpCap, @@ -165,16 +179,24 @@ const EventForm = ({ location, volunteerSignUpCap, eventDescription, + imageURL, startDate, startTime, endTime, } = data; + const userid = await fetchUserIdFromDatabase(user?.email as string); const startDateTime = convertToISO(startTime, startDate); const endDateTime = convertToISO(endTime, startDate); + let newImageURL = imageURL; + if (selectedFile) { + newImageURL = await uploadImageToFirebase(userid, selectedFile); // Update URL if there is any. + } + const { response } = await api.put(`/events/${eventId}`, { name: `${eventName}`, location: status === 0 ? "VIRTUAL" : `${location}`, description: `${eventDescription}`, + imageURL: newImageURL, startDate: startDateTime, endDate: endDateTime, capacity: +volunteerSignUpCap, @@ -372,7 +394,14 @@ const EventForm = ({ })} /> - + + {/* { endDate: event["endDate"], role: "Supervisor", hours: eventHours(event["startDate"], event["endDate"]), - img_src: event["imageURL"], + imageURL: event["imageURL"], }; } ) || []; diff --git a/frontend/src/pages/events/[eventid]/edit.tsx b/frontend/src/pages/events/[eventid]/edit.tsx index 6059e3cc..6b469c8c 100644 --- a/frontend/src/pages/events/[eventid]/edit.tsx +++ b/frontend/src/pages/events/[eventid]/edit.tsx @@ -13,7 +13,7 @@ type eventData = { location: string; volunteerSignUpCap: string; eventDescription: string; - eventImage: string; + imageURL: string; rsvpLinkImage: string; startDate: Date; endDate: Date; @@ -41,7 +41,7 @@ const EditEvent = () => { location: data?.location, volunteerSignUpCap: data?.capacity, eventDescription: data?.description, - eventImage: data?.eventImage || "", + imageURL: data?.imageURL || "", rsvpLinkImage: data?.rsvpLinkImage || "", startDate: data?.startDate, endDate: data?.endDate, diff --git a/frontend/src/utils/helpers.ts b/frontend/src/utils/helpers.ts index a0c193e6..82056a2f 100644 --- a/frontend/src/utils/helpers.ts +++ b/frontend/src/utils/helpers.ts @@ -1,6 +1,12 @@ import dayjs from "dayjs"; import { api } from "./api"; import { isToday, isTomorrow, isPast, parseISO, format } from "date-fns"; +import { + getStorage, + ref, + uploadBytesResumable, + getDownloadURL, +} from "firebase/storage"; /** * This functions performs a search in the DB based on the email of the user that @@ -73,7 +79,7 @@ export const convertToISO = (inputTime: Date, inputDate: Date) => { const date = format(new Date(inputDate), "yyyy-MM-dd"); const time = format(new Date(inputTime), "HH:mm:ss"); - return dayjs(`${date} ${time}`).toJSON(); + return dayjs(`${date} ${time}`).toJSON(); }; /** @@ -169,3 +175,37 @@ export const displayDateInfo = (date: Date) => { export const formatRoleOrStatus = (str: string) => { return str[0] + str.substring(1).toLowerCase(); }; + +/** + * Upload image to firebase + * @param userID - The userID of who uploads the image + * @param image - The image file to upload + * @returns The string URL to the event. + */ +export const uploadImageToFirebase = async ( + userID: string | null, + image: File +) => { + if (!userID) { + console.error("No userID provided"); + return ""; + } + + const fileExtension = image.name.slice(image.name.lastIndexOf(".")); + const currentTime = Date.now(); + const refString = `${userID}_${currentTime}${fileExtension}`; + const storage = getStorage(); + const imageRef = ref(storage, refString); + const uploadTask = uploadBytesResumable(imageRef, image); + + // Retrieve the download URL + try { + // Wait for the upload to complete and retrieve the download URL + await uploadTask; // Ensures the upload completes before proceeding + const downloadURL = await getDownloadURL(uploadTask.snapshot.ref); + return downloadURL; + } catch (error) { + console.error("Upload failed:", error); + return ""; + } +}; From 21158dde01ce703df9da964ebcd9296d24f28ed5 Mon Sep 17 00:00:00 2001 From: Jason Zheng <48730262+jasozh@users.noreply.github.com> Date: Thu, 18 Apr 2024 17:17:55 -0400 Subject: [PATCH 02/17] Fix button text not aligned (#240) --- frontend/src/components/organisms/EventCardNew.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/organisms/EventCardNew.tsx b/frontend/src/components/organisms/EventCardNew.tsx index 586bca7d..e8eda813 100644 --- a/frontend/src/components/organisms/EventCardNew.tsx +++ b/frontend/src/components/organisms/EventCardNew.tsx @@ -94,7 +94,7 @@ const EventCardNew = ({ event }: EventCardNewProps) => {
{/* Card left content */} -
+
From f6cf961576faa9563560e4a7000e3ebdea781ac1 Mon Sep 17 00:00:00 2001 From: Jason Zheng <48730262+jasozh@users.noreply.github.com> Date: Thu, 18 Apr 2024 17:29:38 -0400 Subject: [PATCH 03/17] Fix image not showing in edit event (#241) --- frontend/src/components/atoms/Dropzone.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/atoms/Dropzone.tsx b/frontend/src/components/atoms/Dropzone.tsx index 5119f936..08cefa2e 100644 --- a/frontend/src/components/atoms/Dropzone.tsx +++ b/frontend/src/components/atoms/Dropzone.tsx @@ -51,7 +51,20 @@ const Dropzone = ({