diff --git a/src/app/admin/interests/page.js b/src/app/admin/interests/page.js new file mode 100644 index 000000000..9835c572d --- /dev/null +++ b/src/app/admin/interests/page.js @@ -0,0 +1,18 @@ +import ProtectedPage from "@/components/dynamic/ProtectedPage"; +import Interests from "@/components/dynamic/admin/dashboards/Interests"; + +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/app/api/interests/route.js b/src/app/api/interests/route.js new file mode 100644 index 000000000..9e231475c --- /dev/null +++ b/src/app/api/interests/route.js @@ -0,0 +1,114 @@ +import { NextResponse } from "next/server"; +import { db } from "../../../../firebase"; +import { + collection, + getDocs, + doc, + updateDoc, + Timestamp, + query, + where, +} from "firebase/firestore"; +import { authenticate } from "@/utils/auth"; +import { AUTH } from "@/data/dynamic/admin/Feedback"; + +export async function POST() { + const res = NextResponse; + const { auth, uid } = await authenticate(AUTH.POST); + + if (auth !== 200) { + return res.json( + { message: `Authentication Error: ${message}` }, + { status: auth } + ); + } + + try { + await updateDoc(doc(db, "users", uid), { + "roles.interests": 0, + timestamp: Timestamp.now(), + }); + return res.json({ message: "OK" }, { status: 200 }); + } catch (err) { + return res.json( + { message: `Internal Server Error: ${err}` }, + { status: 500 } + ); + } +} + +export async function GET() { + const res = NextResponse; + const { auth, message } = await authenticate(AUTH.GET); + + if (auth !== 200) { + return res.json( + { message: `Authentication Error: ${message}` }, + { status: auth } + ); + } + + const output = []; + + try { + const snapshot = await getDocs( + query(collection(db, "users"), where("roles.interests", "in", [-1, 0, 1])) + ); + snapshot.forEach((doc) => { + const { name, email, timestamp, roles } = doc.data(); + output.push({ + uid: doc.id, + name, + email, + status: roles.interests, + timestamp: timestamp, + }); + }); + + const sorted = output.sort((a, b) => + a.timestamp.seconds < b.timestamp.seconds ? 1 : -1 + ); + + return res.json({ message: "OK", items: sorted }, { status: 200 }); + } catch (err) { + return res.json( + { message: `Internal Server Error: ${err}` }, + { status: 500 } + ); + } +} + +export async function PUT(req) { + const res = NextResponse; + const { auth, message } = await authenticate(AUTH.PUT); + + if (auth !== 200) { + return res.json( + { message: `Authentication Error: ${message}` }, + { status: auth } + ); + } + + const { objects, attribute, status } = await req.json(); + + try { + objects.forEach(async (object) => { + if (attribute === "role") { + await updateDoc(doc(db, "users", object.uid), { + "roles.interests": deleteField(), + }); + } else if (attribute === "status") { + await updateDoc(doc(db, "users", object.uid), { + "roles.interests": status, + }); + } + }); + + return res.json({ message: "OK" }, { status: 200 }); + } catch (err) { + return res.json( + { message: `Internal Server Error: ${err}` }, + { status: 500 } + ); + } +} diff --git a/src/app/form/interest/page.js b/src/app/form/interest/page.js new file mode 100644 index 000000000..6d90bc9b0 --- /dev/null +++ b/src/app/form/interest/page.js @@ -0,0 +1,12 @@ +import Interest from "@/components/dynamic/form/Interest"; +import ProtectedPage from "@/components/dynamic/ProtectedPage"; + +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/components/dynamic/admin/Table.jsx b/src/components/dynamic/admin/Table.jsx index b7f61a66f..72e04532a 100644 --- a/src/components/dynamic/admin/Table.jsx +++ b/src/components/dynamic/admin/Table.jsx @@ -53,6 +53,7 @@ const Table = ({ }) ); }; + return !objects ? ( ) : ( diff --git a/src/components/dynamic/admin/dashboards/Interests.jsx b/src/components/dynamic/admin/dashboards/Interests.jsx new file mode 100644 index 000000000..ae1152370 --- /dev/null +++ b/src/components/dynamic/admin/dashboards/Interests.jsx @@ -0,0 +1,23 @@ +"use client"; +import { + FILTERS, + HEADERS, + STATUSES, + TAGS, +} from "@/data/dynamic/admin/Interest"; +import Dashboard from "../Dashboard"; + +const Feedback = () => { + return ( + + ); +}; +export default Feedback; diff --git a/src/components/dynamic/form/Interest.jsx b/src/components/dynamic/form/Interest.jsx new file mode 100644 index 000000000..5a4933fa8 --- /dev/null +++ b/src/components/dynamic/form/Interest.jsx @@ -0,0 +1,44 @@ +"use client"; + +import { useState } from "react"; +import Form from "@/components/dynamic/form/Form.jsx"; +import axios from "axios"; +import toast from "react-hot-toast"; +import { FIELDS, ATTRIBUTES } from "@/data/dynamic/form/Interest"; +import { useSession } from "next-auth/react"; +import { STATUSES } from "@/data/dynamic/admin/Interest"; + +const Interest = () => { + const { data: session } = useSession(); + const [interest, setInterest] = useState({ + ...ATTRIBUTES, + name: session.user.name, + email: session.user.email, + roles: session.user.roles, + form: "interests", + }); + + const onSubmit = (setLoading, setState) => { + axios + .post("/api/interests", interest) + .then(() => toast(`✅ Submitted successfully!`)) + .catch(() => toast(`❌ Internal Server Error`)) + .finally(() => { + setLoading(false); + setState(2); + }); + }; + + return ( +
+ ); +}; + +export default Interest; diff --git a/src/data/dynamic/Navigation.js b/src/data/dynamic/Navigation.js index e04d6c801..2ab1880c9 100644 --- a/src/data/dynamic/Navigation.js +++ b/src/data/dynamic/Navigation.js @@ -13,6 +13,7 @@ import { FaLock, FaCalendarDay, FaMedal, + FaExclamation, } from "react-icons/fa"; import { AiOutlineQrcode } from "react-icons/ai"; import { signOut } from "next-auth/react"; @@ -61,6 +62,11 @@ export const TABS = { link: "/admin/committees", icon: , }, + { + name: "interests", + link: "/admin/interest", + icon: , + }, { name: "feedback", link: "/admin/feedback", diff --git a/src/data/dynamic/admin/Interest.js b/src/data/dynamic/admin/Interest.js new file mode 100644 index 000000000..cc7537afa --- /dev/null +++ b/src/data/dynamic/admin/Interest.js @@ -0,0 +1,51 @@ +export const FILTERS = { + unnotified: { + state: true, + value: 0, + }, + notified: { + state: true, + value: 1, + }, +}; + +export const TAGS = [ + { + text: "notified", + value: 1, + }, + { + text: "unnotified", + value: 0, + }, +]; + +export const HEADERS = [ + { text: "name", size: "w-5/12", icon: true, sort: "off" }, + { text: "email", size: "w-5/12", icon: true, sort: "off" }, + { + text: "status", + size: "w-2/12", + icon: true, + sort: "off", + hasTag: true, + }, +]; + +export const AUTH = { + POST: {}, + GET: { + admins: 1, + }, + PUT: { + admins: 1, + }, + DELETE: { + admins: 1, + }, +}; + +export const STATUSES = { + 1: "notified", + 0: "unnotified", +}; diff --git a/src/data/dynamic/form/Interest.js b/src/data/dynamic/form/Interest.js new file mode 100644 index 000000000..8ec691a17 --- /dev/null +++ b/src/data/dynamic/form/Interest.js @@ -0,0 +1,35 @@ +export const FIELDS = { + description: { + input: "description", + width: 12, + texts: [ + "Welcome to HACKATHON_NAME. Thank you for taking interestin in HACKAHTON_NAME, we appreciate your efforts to help support HACKATHON_NAME. HACKATHON_NAME is a DESCRIPTION hackathon spanning HACKATHON_LENGTH on HACKATHON_DATE.", + "This is simply an interest form, once applications are released, you will be notified immediately and will be required to register again. We hope to see you there!", + ], + }, + name: { + input: "input", + name: "name", + type: "text", + title: "Name", + maxLength: 50, + width: 12, + editable: false, + required: true, + }, + email: { + input: "input", + name: "email", + type: "email", + title: "Email Address", + maxLength: 50, + width: 12, + editable: false, + required: true, + }, +}; + +export const ATTRIBUTES = { + name: "", + email: "", +};