diff --git a/app/(default)/api/leads/upload/route.ts b/app/(default)/api/leads/upload/route.ts index 576c9fc..ab5af70 100644 --- a/app/(default)/api/leads/upload/route.ts +++ b/app/(default)/api/leads/upload/route.ts @@ -1,60 +1,33 @@ -import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage"; -import { NextResponse } from "next/server"; -import { db, storage} from "@/Firebase"; +import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; +import { NextResponse } from "next/server"; +import { storage } from "@/Firebase"; export async function POST(request: Request) { - try { - const storage = getStorage(); - const selectedLead = await request.json(); - const imageRef = ref(storage, `images/${selectedLead.name}`); - let imageUrl; + try { + const formData = await request.formData(); - try { - const imageBlob = await fetch(selectedLead.imageUrl).then((r) => r.blob()); - console.log("Image blob fetched:", imageBlob); - try { - await uploadBytes(imageRef, imageBlob); - console.log("Image uploaded to storage"); - try { - imageUrl = await getDownloadURL(imageRef); - console.log("Image URL obtained:", imageUrl); - } catch (error: any) { - console.error("Error getting image URL:", error); - return NextResponse.json( - { error: "An error occurred", details: error.message }, - { status: 500 } - ); - } - } catch (error: any) { - console.error("Error uploading image:", error); - return NextResponse.json( - { error: "An error occurred", details: error.message }, - { status: 500 } - ); - } - } catch (error: any) { - console.error("Error fetching image blob:", error); - return NextResponse.json( - { error: "An error occurred", details: error.message }, - { status: 500 } - ); - } - - return NextResponse.json({ imageUrl: imageUrl }, { status: 201 }); - } catch (error) { - if (error instanceof Error) { - console.error("Error details:", error.message); - return NextResponse.json( - { error: "An error occurred", details: error.message }, - { status: 500 } - ); - } else { - console.error("Unknown error:", error); - return NextResponse.json( - { error: "An unknown error occurred" }, - { status: 500 } - ); - } + const Image: File | null = formData.get('file') as File; + if (!Image) { + return NextResponse.json( + { message: "Bad Request", details: "No file uploaded" }, + { status: 400 } + ); } -} \ No newline at end of file + + const name: string = formData.get('name') as string; + const imageRef = ref(storage, `images/${name}`); + await uploadBytes(imageRef, Image); + const imageUrl = await getDownloadURL(imageRef); + + return NextResponse.json({ + imageUrl: imageUrl + }); + } catch (error:any) { + console.error("Error uploading file:", error); + return NextResponse.json( + { message: "Internal Server Error", details: error.message }, + { status: 500 } + ); + } +} diff --git a/app/(default)/api/membersData/route.ts b/app/(default)/api/membersData/route.ts index b40a8d6..5cc45ac 100644 --- a/app/(default)/api/membersData/route.ts +++ b/app/(default)/api/membersData/route.ts @@ -1,317 +1,202 @@ -import { NextResponse } from 'next/server'; -import { db, storage } from '@/Firebase'; +import { NextResponse } from "next/server"; +import { db, storage } from "@/Firebase"; import { - collection, addDoc, getDocs, DocumentData, - DocumentSnapshot, - doc, - updateDoc, - deleteDoc, - getDoc -} from 'firebase/firestore'; -import { ref, uploadBytes, getDownloadURL, deleteObject } from 'firebase/storage'; - + collection, + addDoc, + getDocs, + DocumentData, + DocumentSnapshot, + doc, + updateDoc, + deleteDoc, + getDoc, +} from "firebase/firestore"; +import { + ref, + uploadBytes, + getDownloadURL, + deleteObject, +} from "firebase/storage"; +// GET handler to retrieve all members export async function GET() { - try { - - const querySnapshot = await getDocs(collection(db, "pbMembers")); - - const membersRaw = querySnapshot.docs.map( - (doc: DocumentSnapshot) => ({ - id: doc.id, - ...doc.data(), - }) - ); - - const members = membersRaw.map((member: any) => { - return { - id: member.id, - name: member.name, - role: member.role, - company: member.company || '', - year: member.year, - linkedInUrl: member.linkedInUrl || '', - imageUrl: member.imageUrl || '' - }; - }); - - return NextResponse.json(members); - } catch (error) { - if (error instanceof Error) { - console.error("Error fetching members:", error.message); - return NextResponse.json( - { - error: "An error occurred while fetching members", - details: error.message, - }, - { status: 500 } - ); - } else { - console.error("Unknown error:", error); - return NextResponse.json( - { error: "An unknown error occurred while fetching members" }, - { status: 500 } - ); - } + try { + // Fetch all member documents from the Firestore collection + const querySnapshot = await getDocs(collection(db, "pbMembers")); + + // Map the documents to extract data and include document ID + const membersRaw = querySnapshot.docs.map( + (doc: DocumentSnapshot) => ({ + id: doc.id, + ...doc.data(), + }) + ); + + // Format the members' data for the response + const members = membersRaw.map((member: any) => ({ + id: member.id, + name: member.name, + role: member.role, + company: member.company || "", + year: member.year, + linkedInUrl: member.linkedInUrl || "", + imageUrl: member.imageUrl || "", + })); + + // Return the formatted member data as JSON + return NextResponse.json(members); + } catch (error) { + // Catch any error during the fetching process + if (error instanceof Error) { + console.error("Error fetching members:", error.message); + return NextResponse.json( + { + error: "An error occurred while fetching members", + details: error.message, + }, + { status: 500 } + ); + } else { + console.error("Unknown error:", error); + return NextResponse.json( + { error: "An unknown error occurred while fetching members" }, + { status: 500 } + ); } + } } +// POST handler to add a new member export async function POST(request: Request) { - try { - const data = await request.json(); - - const { name, year, role, company, imageUrl, linkedInUrl } = data; - - if (!name) { - return NextResponse.json( - { message: 'Missing Name.', error: true }, - { status: 400 } - ); - } - - let downloadURL = ''; - if (imageUrl) { - const response = await fetch(imageUrl); - if (!response.ok) { - return NextResponse.json( - { message: 'Failed to fetch image from the URL.', error: true }, - { status: 400 } - ); - } - const contentType = response.headers.get('content-type'); - - if (!contentType || !contentType.startsWith('image/')) { - return NextResponse.json( - { message: 'The provided URL is not a valid image.', error: true }, - { status: 400 } - ); - } - - const blob = await response.blob(); - - const extension = contentType.split('/')[1]; - - const imageRef = ref(storage, `members/${name}.${extension}`); - await uploadBytes(imageRef, blob, { contentType }); - - downloadURL = await getDownloadURL(imageRef); - } - - const docRef = await addDoc(collection(db, 'pbMembers'), { - name, - year, - role, - company, - linkedInUrl, - imageUrl: downloadURL, - }); - - return NextResponse.json( - { message: 'Member added successfully', id: docRef.id }, - { status: 201 } - ); - } catch (error) { - console.error('Error adding member:', error); - return NextResponse.json( - { message: `Failed to add member: ${error}`, error: true }, - { status: 500 } - ); + try { + // Parse the request body to extract member data + const data = await request.json(); + + const { name, year, role, company, imageUrl, linkedInUrl } = data; + + // Check for missing required fields + if (!name) { + return NextResponse.json( + { message: "Missing Name.", error: true }, + { status: 400 } + ); } -} + // Add the new member document to the Firestore collection + const docRef = await addDoc(collection(db, "pbMembers"), { + name, + year, + role, + company, + linkedInUrl, + imageUrl, + }); + + // Return success response with the new member's document ID + return NextResponse.json( + { message: "Member added successfully", id: docRef.id }, + { status: 201 } + ); + } catch (error) { + // Catch and log any errors during member creation + console.error("Error adding member:", error); + return NextResponse.json( + { message: `Failed to add member: ${error}`, error: true }, + { status: 500 } + ); + } +} +// PUT handler to update an existing member export async function PUT(request: Request) { - try { - const data = await request.json(); - const { id, name, year, role, company, imageUrl, linkedInUrl } = data; - - if (!id || !name) { - return NextResponse.json( - { message: 'Missing required fields or member ID', error: true }, - { status: 400 } - ); - } - - const memberRef = doc(db, "pbMembers", id); - const memberSnapshot = await getDoc(memberRef); - if (!memberSnapshot.exists()) { - return NextResponse.json( - { message: 'Member not found', error: true }, - { status: 404 } - ); - } - - const memberData = memberSnapshot.data(); - let downloadURL = memberData.imageUrl; - - if (imageUrl && imageUrl !== memberData.imageUrl) { - const response = await fetch(imageUrl); - if (!response.ok) { - return NextResponse.json( - { message: 'Failed to fetch image from the URL.', error: true }, - { status: 400 } - ); - } - - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.startsWith('image/')) { - return NextResponse.json( - { message: 'The provided URL is not a valid image.', error: true }, - { status: 400 } - ); - } - - const blob = await response.blob(); - const extension = contentType.split('/')[1]; - - // Use a timestamp to ensure a unique filename - const timestamp = Date.now(); - const imageRef = ref(storage, `members/${name}_${timestamp}.${extension}`); - - await uploadBytes(imageRef, blob, { contentType }); - downloadURL = await getDownloadURL(imageRef); - - if (memberData.imageUrl) { - const oldImageRef = ref(storage, memberData.imageUrl); - await deleteObject(oldImageRef).catch(error => { - console.error('Error deleting old image:', error); - }); - } - } - - const updatedData = { - name, - year, - role, - company, - linkedInUrl, - imageUrl: downloadURL, - }; - - await updateDoc(memberRef, updatedData); - - return NextResponse.json( - { message: 'Member updated successfully', data: updatedData }, - { status: 200 } - ); - } catch (error) { - console.error('Error updating member:', error); - return NextResponse.json( - { message: `Failed to update member: ${error}`, error: true }, - { status: 500 } - ); + try { + // Parse the request body to get the updated member data + const data = await request.json(); + const { id, name, imageUrl } = data; + + // Check for missing required fields + if (!id || !name) { + return NextResponse.json( + { message: "Missing required fields or member ID", error: true }, + { status: 400 } + ); } -} + // Update member data in Firestore + const updatedData = { ...data, imageUrl: imageUrl }; + await updateDoc(doc(db, "pbMembers", id), updatedData); + + // Return success response with the updated member data + return NextResponse.json( + { message: "Member updated successfully", data: updatedData }, + { status: 200 } + ); + } catch (error) { + // Catch and log any errors during member update + console.error("Error updating member:", error); + return NextResponse.json( + { message: `Failed to update member: ${error}`, error: true }, + { status: 500 } + ); + } +} +// DELETE handler to delete a member and their image export async function DELETE(request: Request) { - try { - const { id } = await request.json(); - - if (!id) { - return NextResponse.json( - { message: 'Missing member ID', error: true }, - { status: 400 } - ); - } - - // Reference to the member document - const memberRef = doc(db, 'pbMembers', id); - const memberSnapshot = await getDoc(memberRef); - - if (!memberSnapshot.exists()) { - return NextResponse.json( - { message: 'Member not found', error: true }, - { status: 404 } - ); - } + try { + // Parse the request body to get the member ID + const { id } = await request.json(); + + // Check if the member ID is provided + if (!id) { + return NextResponse.json( + { message: "Missing member ID", error: true }, + { status: 400 } + ); + } - const memberData = memberSnapshot.data(); - const imageUrl = memberData.imageUrl; + // Fetch the member document from Firestore + const memberRef = doc(db, "pbMembers", id); + const memberSnapshot = await getDoc(memberRef); - // Delete the image from Firebase Storage if it exists - if (imageUrl) { - const imageRef = ref(storage, imageUrl); - await deleteObject(imageRef).catch((error) => { - console.error('Error deleting image from Firebase Storage:', error); - return NextResponse.json( - { message: 'Failed to delete image from storage', error: true }, - { status: 500 } - ); - }); - } + // If the member does not exist, return a 404 response + if (!memberSnapshot.exists()) { + return NextResponse.json( + { message: "Member not found", error: true }, + { status: 404 } + ); + } - // Delete the member document from Firestore - await deleteDoc(memberRef); + // Extract member data and image URL + const memberData = memberSnapshot.data(); + const imageUrl = memberData.imageUrl; + // If an image URL exists, delete the image from Firebase Storage + if (imageUrl) { + const imageRef = ref(storage, imageUrl); + await deleteObject(imageRef).catch((error) => { + console.error("Error deleting image from Firebase Storage:", error); return NextResponse.json( - { message: 'Member and associated image deleted successfully' }, - { status: 200 } - ); - } catch (error) { - console.error('Error deleting member:', error); - return NextResponse.json( - { message: 'Failed to delete member', error }, - { status: 500 } + { message: "Failed to delete image from storage", error: true }, + { status: 500 } ); + }); } -} - - -// // Posting a array of members - -// export async function POST(request: Request) { -// try { -// const data = await request.json(); - -// if (!Array.isArray(data)) { -// return NextResponse.json({ error: 'Invalid data format. Expected an array of objects.' }, { status: 400 }); -// } - -// for (const item of data) { -// const { name, year, role, company, imageUrl, linkedInUrl } = item; - -// if (!name) { -// return NextResponse.json({ error: 'Missing Name.' }, { status: 400 }); -// } - -// let downloadURL = ''; -// if (imageUrl) { -// const response = await fetch(imageUrl); - -// if (!response.ok) { -// return NextResponse.json({ error: 'Failed to fetch image from the URL.' }, { status: 400 }); -// } - -// const contentType = response.headers.get('content-type'); - -// if (!contentType || contentType.includes('text/html')) { -// return NextResponse.json({ error: 'Provided link is not a direct image link. Please ensure the Google Drive file is public.' }, { status: 400 }); -// } - -// const blob = await response.blob(); -// const extension = contentType.split('/')[1]; - -// const imageRef = ref(storage, `members/${name}.${extension}`); -// await uploadBytes(imageRef, blob, { contentType }); - -// downloadURL = await getDownloadURL(imageRef); // Assign value to downloadURL -// } - -// await addDoc(collection(db, 'pbMembers'), { -// name, -// year, -// role, -// company, -// linkedInUrl, -// imageUrl: downloadURL || '', -// }); -// } - -// return NextResponse.json({ message: 'Data added successfully' }, { status: 200 }); -// } catch (error) { -// console.error('Error uploading data:', error); -// return NextResponse.json({ error: `Failed to add data: ${error}` }, { status: 500 }); -// } -// } \ No newline at end of file + // Delete the member document from Firestore + await deleteDoc(memberRef); + + // Return success response after deletion + return NextResponse.json( + { message: "Member and associated image deleted successfully" }, + { status: 200 } + ); + } catch (error) { + // Catch and log any errors during the deletion process + console.error("Error deleting member:", error); + return NextResponse.json( + { message: "Failed to delete member", error }, + { status: 500 } + ); + } +} diff --git a/app/(default)/api/membersData/upload/route.ts b/app/(default)/api/membersData/upload/route.ts new file mode 100644 index 0000000..576d11d --- /dev/null +++ b/app/(default)/api/membersData/upload/route.ts @@ -0,0 +1,49 @@ +import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; +import { NextResponse } from "next/server"; +import { storage } from "@/Firebase"; + +// The POST function to handle file uploads to Firebase Storage +export async function POST(request: Request) { + try { + // Parse the form data from the incoming request + const formData = await request.formData(); + + // Retrieve the uploaded file from the form data + const Image: File | null = formData.get('file') as File; + + // If no file is uploaded, return a bad request response + if (!Image) { + return NextResponse.json( + { message: "Bad Request", details: "No file uploaded" }, + { status: 400 } + ); + } + + // Retrieve the name of the image from the form data + const name: string = formData.get('name') as string; + + // Create a reference in Firebase Storage with the image name + const imageRef = ref(storage, `members/${name}`); + + // Upload the image to Firebase Storage + await uploadBytes(imageRef, Image); + + // Get the download URL of the uploaded image + const imageUrl = await getDownloadURL(imageRef); + + // Return the image URL as the response + return NextResponse.json({ + imageUrl: imageUrl + }); + + } catch (e) { + // Log any error that occurs during the process + console.error("Error during file upload:", e); + + // Return a server error response if something goes wrong + return NextResponse.json( + { message: "Internal Server Error", details: e instanceof Error ? e.message : 'Unknown error' }, + { status: 500 } + ); + } +} diff --git a/app/(default)/api/registration/pbctf/route.ts b/app/(default)/api/registration/pbctf/route.ts index f13446c..7e5675f 100644 --- a/app/(default)/api/registration/pbctf/route.ts +++ b/app/(default)/api/registration/pbctf/route.ts @@ -12,12 +12,88 @@ import { } from "firebase/firestore"; import { NextResponse } from "next/server"; -// Add a new registration +//Check if USN exists +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const usn = searchParams.get("usn"); + if (!usn) { + return NextResponse.json({ error: "usn is required" }, { status: 400 }); + } + const q = query( + collection(db, "pbctf_registrations"), + where("participant1.usn", "==", usn) + ); + const querySnapshot = await getDocs(q); + if (!querySnapshot.empty) { + return NextResponse.json( + { message: "usn not registered", isUnique: true }, + { status: 200 } + ); + } + const q2 = query( + collection(db, "pbctf_registrations"), + where("participant2.usn", "==", usn) + ); + const querySnapshot2 = await getDocs(q2); + + if (!querySnapshot2.empty) { + return NextResponse.json( + { message: "usn not unique", isUnique: true }, + { status: 200 } + ); + } else { + return NextResponse.json( + { message: "usn already exists", isUnique: false }, + { status: 403 } + ); + } + } catch (error) { + if (error instanceof Error) { + console.error("Error details:", error.message); + return NextResponse.json( + { error: "An error occurred", details: error.message }, + { status: 500 } + ); + } else { + console.error("Unknown error:", error); + return NextResponse.json( + { error: "An unknown error occurred" }, + { status: 500 } + ); + } + } +} + export async function POST(request: Request) { + try { + const { searchParams } = new URL(request.url); // Extract query parameters + const action = searchParams.get("action"); // Determine the action from query params + + if (action === "validateRecaptcha") { + return validateRecaptcha(request); + } else if (action === "addRegistration") { + return addRegistration(request); + } else { + return NextResponse.json( + { error: "Invalid action specified" }, + { status: 400 } + ); + } + } catch (error) { + console.error("Error processing request:", error); + return NextResponse.json( + { error: "An error occurred", details: error }, + { status: 500 } + ); + } +} + +// Add a new registration +async function validateRecaptcha(request: Request) { const formData = await request.json(); const { recaptcha_token } = formData; - const recaptchaToken = recaptcha_token; const details = { @@ -27,7 +103,6 @@ export async function POST(request: Request) { }, }; - if (!recaptchaToken) { return NextResponse.json( { @@ -61,3 +136,27 @@ export async function POST(request: Request) { // Return a response return NextResponse.json({ message: "Recaptcha validated!" }); } + +async function addRegistration(request: Request) { + try { + const data = await request.json(); + if (!data || !data.participant1 || !data.participationType) { + return NextResponse.json( + { + error: + "Invalid data. Participant1 and participationType are required.", + }, + { status: 400 } + ); + } + await addDoc(collection(db, "pbctf_registrations"), data); + + return NextResponse.json({ message: "Registration successful!" }); + } catch (error) { + console.error("Error adding registration:", error); + return NextResponse.json( + { error: "Failed to add registration.", details: error }, + { status: 500 } + ); + } +} diff --git a/components/Members.tsx b/components/Members.tsx index 17f2359..6c2aea2 100644 --- a/components/Members.tsx +++ b/components/Members.tsx @@ -5,11 +5,11 @@ import ClipLoader from "react-spinners/ClipLoader"; import { FaRegBell, FaEllipsisV } from "react-icons/fa"; import { onAuthStateChanged } from "firebase/auth"; import { auth } from "@/Firebase"; -import { storage } from "@/Firebase"; -import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; import Image from "next/image"; import Card from "./ui/Card"; import CollapsibleSection from "./ui/CollapsibleSection"; +import { useStoreMember } from "@/lib/zustand/store"; + interface Member { id?: string; @@ -45,7 +45,10 @@ export default function Members() { year: "", linkedInUrl: "", imageUrl: "", - }); + }) + + const { image, setImage } = useStoreMember(); + const [menuVisible, setMenuVisible] = useState<{ [key: string]: boolean }>( {} ); @@ -57,45 +60,34 @@ export default function Members() { const [isAdmin, setIsAdmin] = useState(false); - useEffect(() => { - onAuthStateChanged(auth, async (user) => { - if (user) { - const uid = user.uid; - try { - const resp = await fetch(`/api/admin?uid=${uid}`); - const data = await resp.json(); - if (data.isAdmin) { - setIsAdmin(true); - } - } catch (error) { - console.log("Error getting document:", error); - } + useEffect(() => { + onAuthStateChanged(auth, async (user) => { + if (user) { + const uid = user.uid; + try { + const resp = await fetch(`/api/admin?uid=${uid}`); + const data = await resp.json(); + if (data.isAdmin) { + setIsAdmin(true); } - }); - },[isAdmin]); + } catch (error) { + console.log("Error getting document:", error); + } + } + }); + }),[isAdmin]; const handleFileChange = async (e: React.ChangeEvent) => { const files = e.target.files; - if (files && files.length > 0) { const file = files[0]; - - const contentType = file.type; - const extension = contentType.split("/")[1]; - - const imageRef = ref(storage, `members/${newMember.name}.${extension}`); - - try { - const snapshot = await uploadBytes(imageRef, file, { contentType }); - - const downloadURL = await getDownloadURL(snapshot.ref); - - setNewMember((prev) => ({ ...prev, imageUrl: downloadURL })); - } catch (error) { - console.error("Error uploading file:", error); - } + setImage(file); + setNewMember((newMember) => ({ + ...newMember, + imageUrl: URL.createObjectURL(files[0]), + })); } else { - console.warn("No file selected."); + console.error("No file selected or invalid file input"); } }; @@ -106,30 +98,92 @@ export default function Members() { ); return; } + try { const method = isEditing ? "PUT" : "POST"; - const memberData = isEditing + let imageUrl = newMember.imageUrl; + + // Handling image URL upload if it's a blob + if (newMember.imageUrl && newMember.imageUrl.startsWith("blob")) { + console.log("Image file detected, uploading to Firebase Storage..."); + const formData = new FormData(); + formData.append("file", image as Blob); + formData.append("name", newMember.name); + try { + const response = await fetch("/api/membersData/upload", { + method: "POST", + body: formData, + }); + + const data = await response.json(); + if (!response.ok) + throw new Error(data.message || "Image upload failed."); + imageUrl = data.imageUrl; + console.log("Image uploaded successfully, URL:", imageUrl); + } catch (error) { + console.error("Error uploading image:", error); + alert("Failed to upload image. Please try again."); + return; + } + } + + // Updating member data with the new image URL + let memberData = isEditing ? { ...newMember, id: editMemberId } : newMember; - const response = await fetch("/api/membersData", { - method: method, - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(memberData), - }); + memberData = { ...newMember, imageUrl }; - const result = await response.json(); + if (newMember.id) { + // Update member in Firestore + try { + const response = await fetch(`/api/membersData`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(memberData), + }); - if (!response.ok) { - console.error("Network response was not ok.", result); - throw new Error(result.message || "Network response was not ok."); + const result = await response.json(); + + if (!response.ok) { + console.error("Network response was not ok.", result); + throw new Error(result.message || "Network response was not ok."); + } + + alert("Member updated successfully"); + } catch (error) { + console.error("Error updating member:", error); + alert("Failed to update member. Please try again."); + return; + } + } else { + // Add new member to Firestore + try { + const response = await fetch("/api/membersData", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(memberData), + }); + + if (!response.ok) { + const result = await response.json(); + console.error("Failed to add member:", result); + throw new Error(result.message || "Failed to add member."); + } + + alert("Member added successfully"); + } catch (error) { + console.error("Error adding member:", error); + alert("Failed to add member. Please try again."); + return; + } } - alert( - isEditing ? "Member updated successfully" : "Member added successfully" - ); + // Reset the form and state setShowForm(false); setIsEditing(false); setEditMemberId(null); @@ -144,7 +198,9 @@ export default function Members() { await fetchData(); } catch (error) { console.error("Error adding/updating member:", error); - alert("An error occurred. Check the console for details."); + alert( + "An unexpected error occurred. Please check the console for details." + ); } }; @@ -219,6 +275,7 @@ export default function Members() { } }); + const uniqueMembers = Array.from(uniqueMembersMap.values()).sort((a, b) => a.name.localeCompare(b.name) ); @@ -256,6 +313,7 @@ export default function Members() { heading={heading} content={
+ {/* {heading === "First Year" && (

diff --git a/components/forms/pbctfForm.tsx b/components/forms/pbctfForm.tsx index 6a9f541..ef771d5 100644 --- a/components/forms/pbctfForm.tsx +++ b/components/forms/pbctfForm.tsx @@ -3,23 +3,13 @@ import "../../app/css/additional-styles/utility-patterns.css"; import "../../app/css/additional-styles/theme.css"; import { useEffect, useState } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; -import { collection, addDoc, query, where, getDocs } from "firebase/firestore"; -import { db } from "@/Firebase"; import { branches } from "@/lib/constants/dropdownOptions"; import { Press_Start_2P } from "next/font/google"; import toast from "react-hot-toast"; - -// NOTE TO ANYONE WHO IS WANDERING HERE IN HOPES OF PBCTF 4.0 -// THIS FORM DIRECTLY CALLS THE DB FROM THE CLIENT SIDE -// THIS IS NOT A GOOD PRACTICE -// PLEASE DO NOT USE THIS AS A REFERENCE -// KINDLY CONTACT SKY SINGH TO FIX THIS -// WHENVEVER THE NEXT CTF IS // PARAGATI RAJ ARE YOU READING THIS // I MISS YOU - const pressStart2P = Press_Start_2P({ weight: "400", subsets: ["latin"], @@ -40,7 +30,6 @@ type FormData = { participant2?: ParticipantData; }; - const PBCTFForm: React.FC = () => { const [isSuccess, setSuccess] = useState(false); const [participationType, setParticipationType] = useState<"solo" | "duo">( @@ -82,9 +71,8 @@ const PBCTFForm: React.FC = () => { const Rtoken = await grecaptcha.enterprise.execute( process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ); - setToken(Rtoken); + setToken(Rtoken); }); - }; useEffect(() => { @@ -107,30 +95,21 @@ const PBCTFForm: React.FC = () => { const watchUsn1 = watch("participant1.usn"); const watchUsn2 = watch("participant2.usn"); - - const checkUsnUniqueness = async (usn: string): Promise => { - const q = query( - collection(db, "pbctf_registrations"), - where("participant1.usn", "==", usn) - ); - const querySnapshot = await getDocs(q); - - if (!querySnapshot.empty) { + if (!usn) { + console.log("USN is required"); + return false; + } + try { + const resp = await fetch(`/api/registration/pbctf?usn=${usn}`); + const data = await resp.json(); + return Boolean(data.isUnique); + } catch (error) { + console.log("Error getting document:", error); return false; } - - const q2 = query( - collection(db, "pbctf_registrations"), - where("participant2.usn", "==", usn) - ); - const querySnapshot2 = await getDocs(q2); - - return querySnapshot2.empty; }; - - const onSubmit: SubmitHandler = async (data) => { if (isSubmitting) return; setIsSubmitting(true); @@ -139,19 +118,22 @@ const PBCTFForm: React.FC = () => { try { const recaptcha_token = token; if (recaptcha_token) { - const response = await fetch("/api/registration/pbctf", { - method: "POST", - body: JSON.stringify({ recaptcha_token }), - }); + const response1 = await fetch( + "/api/registration/pbctf?action=validateRecaptcha", + { + method: "POST", + body: JSON.stringify({ recaptcha_token }), + } + ); - const res = await response.json(); + const res = await response1.json(); - if (!response.ok || res.error) { + if (!response1.ok || res.error) { toast.error(res.message); return; } - + console.log(data); // Check if USNs are the same for duo participation if ( data.participationType === "duo" && @@ -182,9 +164,20 @@ const PBCTFForm: React.FC = () => { return; } } - // If all checks pass, submit the form - await addDoc(collection(db, "pbctf_registrations"), data); + const response2 = await fetch( + "/api/registration/pbctf?action=addRegistration", + { + method: "POST", + body: JSON.stringify(data), + } + ); + + const result = await response2.json(); + if (!response2.ok) { + toast.error(result.error || "Failed to submit registration."); + return; + } setSuccess(true); } } catch (error) { @@ -241,8 +234,6 @@ const PBCTFForm: React.FC = () => { } const renderParticipantFields = (participantNumber: 1 | 2) => ( - -

Participant {participantNumber}

@@ -316,27 +307,29 @@ const PBCTFForm: React.FC = () => {
@@ -390,60 +383,59 @@ const PBCTFForm: React.FC = () => { return ( <> - -
-

- {headingText} -

-
-
- - -
+
+

+ {headingText} +

+ +
+ + +
- {renderParticipantFields(1)} - {participationType === "duo" && renderParticipantFields(2)} + {renderParticipantFields(1)} + {participationType === "duo" && renderParticipantFields(2)} - {usnError && ( -

{usnError}

- )} + {usnError && ( +

{usnError}

+ )} -
- -
- -
+
+ +
+ +
); }; diff --git a/components/leadspage.tsx b/components/leadspage.tsx index d38b315..1e3ff7b 100644 --- a/components/leadspage.tsx +++ b/components/leadspage.tsx @@ -1,9 +1,8 @@ "use client"; import React, { useState, useEffect } from "react"; import { onAuthStateChanged } from "firebase/auth"; -import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage"; - import { auth } from "@/Firebase"; +import { useStore } from "@/lib/zustand/store"; interface Lead { id: string; @@ -16,12 +15,12 @@ interface Lead { const Leads: React.FC = () => { const [loading, setLoading] = useState(true); - const [showForm, setShowForm] = useState(false); const [isAdminLoggedIn, setIsAdminLoggedIn] = useState(false); const [currentLeads, setCurrentLeads] = useState([]); const [alumniLeads, setAlumniLeads] = useState([]); const [selectedLead, setSelectedLead] = useState(null); // For editing leads + const { image } = useStore(); // Fetch leads from Firestore const fetchLeads = async () => { @@ -69,24 +68,40 @@ const Leads: React.FC = () => { } try { - const storage = getStorage(); let imageUrl = selectedLead.imageUrl; - // If an image is being uploaded, handle that if (selectedLead.imageUrl && selectedLead.imageUrl.startsWith("blob")) { - console.log("Image file detected, uploading to Firebase Storage..."); - const imageRef = ref(storage, `images/${selectedLead.name}`); - - // Fetch the blob file for upload - const imageBlob = await fetch(selectedLead.imageUrl).then((r) => - r.blob() + console.log( + "Image file detected, preparing for upload to Firebase Storage..." ); - await uploadBytes(imageRef, imageBlob); - imageUrl = await getDownloadURL(imageRef); + + // Create a new FormData object + const formData = new FormData(); + if (image) { + formData.append("file", image); + formData.append("name", selectedLead.name); + } + + const response = await fetch("/api/leads/upload", { + method: "POST", + body: formData, // FormData automatically sets the correct headers + }); + + let data; + try { + data = await response.json(); + } catch (error) { + console.error("Failed to parse JSON response:", error); + throw new Error("Unexpected response from the server"); + } + imageUrl = data.imageUrl; + if (!response.ok) { + console.error("Error uploading file:", data); + throw new Error(data.message || "Error uploading file"); + } console.log("Image uploaded successfully, URL:", imageUrl); } - // Create the lead data with the updated (or original) image URL const leadData = { ...selectedLead, imageUrl: imageUrl, // Ensure the Firebase URL is used @@ -344,6 +359,7 @@ const LeadForm: React.FC = ({ imageUrl: "", } ); + const { setImage } = useStore(); const handleChange = ( e: React.ChangeEvent< @@ -359,10 +375,13 @@ const LeadForm: React.FC = ({ const handleFileChange = (e: React.ChangeEvent) => { const files = e.target.files; + if (files && files.length > 0) { + const file = files[0]; + setImage(file); setLead((prevLead) => ({ ...prevLead, - imageUrl: URL.createObjectURL(files[0]), + imageUrl: URL.createObjectURL(file), })); } else { console.error("No file selected or invalid file input"); diff --git a/lib/zustand/store.ts b/lib/zustand/store.ts new file mode 100644 index 0000000..e2568d0 --- /dev/null +++ b/lib/zustand/store.ts @@ -0,0 +1,17 @@ +// store/useStore.ts +import {create} from 'zustand'; + +interface SharedState { + image: string | File | null| Blob; // Store image as base64 string or Blob URL + setImage: (image: string | File|Blob) => void; // Function to update the image +} + +export const useStore = create((set) => ({ + image: null, + setImage: (image) => set({ image }), // Set the image in state +})); + +export const useStoreMember = create((set) => ({ + image: null, + setImage: (image) => set({ image }), // Set the image in state +})); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f88eb96..f604331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,8 @@ "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "typescript": "^5.3.3", - "uuid": "^11.0.3" + "uuid": "^11.0.3", + "zustand": "^5.0.2" }, "devDependencies": { "@tailwindcss/forms": "^0.5.7", @@ -7515,6 +7516,24 @@ "loose-envify": "^1.1.0" } }, + "node_modules/@react-three/fiber/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@react-types/accordion": { "version": "3.0.0-alpha.21", "resolved": "https://registry.npmjs.org/@react-types/accordion/-/accordion-3.0.0-alpha.21.tgz", @@ -18516,19 +18535,31 @@ } }, "node_modules/zustand": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", - "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", - "peer": true, + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.2.tgz", + "integrity": "sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==", + "license": "MIT", "engines": { - "node": ">=12.7.0" + "node": ">=12.20.0" }, "peerDependencies": { - "react": ">=16.8" + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" }, "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, "react": { "optional": true + }, + "use-sync-external-store": { + "optional": true } } } diff --git a/package.json b/package.json index 5d272f3..661bbd6 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "typescript": "^5.3.3", - "uuid": "^11.0.3" + "uuid": "^11.0.3", + "zustand": "^5.0.2" }, "devDependencies": { "@tailwindcss/forms": "^0.5.7",