Merge branch 'develop' into feature/ticket-system

This commit is contained in:
Tiago Ribeiro
2024-01-29 21:03:47 +00:00
5 changed files with 216 additions and 86 deletions

View File

@@ -39,6 +39,8 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
const [participants, setParticipants] = useState<string[]>( const [participants, setParticipants] = useState<string[]>(
group?.participants || [], group?.participants || [],
); );
const [isLoading, setIsLoading] = useState(false);
const { openFilePicker, filesContent, clear } = useFilePicker({ const { openFilePicker, filesContent, clear } = useFilePicker({
accept: ".xlsx", accept: ".xlsx",
multiple: false, multiple: false,
@@ -47,6 +49,8 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
useEffect(() => { useEffect(() => {
if (filesContent.length > 0) { if (filesContent.length > 0) {
setIsLoading(true);
const file = filesContent[0]; const file = filesContent[0];
readXlsxFile(file.content).then((rows) => { readXlsxFile(file.content).then((rows) => {
const emails = uniq( const emails = uniq(
@@ -64,6 +68,7 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
if (emails.length === 0) { if (emails.length === 0) {
toast.error("Please upload an Excel file containing e-mails!"); toast.error("Please upload an Excel file containing e-mails!");
clear(); clear();
setIsLoading(false);
return; return;
} }
@@ -86,16 +91,20 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
: "Added all students found in the file you've provided!", : "Added all students found in the file you've provided!",
{ toastId: "upload-success" }, { toastId: "upload-success" },
); );
setIsLoading(false);
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [filesContent, user.type, users]); }, [filesContent, user.type, users]);
const submit = () => { const submit = () => {
setIsLoading(true);
if (name !== group?.name && (name === "Students" || name === "Teachers")) { if (name !== group?.name && (name === "Students" || name === "Teachers")) {
toast.error( toast.error(
"That group name is reserved and cannot be used, please enter another one.", "That group name is reserved and cannot be used, please enter another one.",
); );
setIsLoading(false);
return; return;
} }
@@ -113,7 +122,10 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
toast.error("Something went wrong, please try again later!"); toast.error("Something went wrong, please try again later!");
return false; return false;
}) })
.finally(onClose); .finally(() => {
setIsLoading(false);
onClose();
});
}; };
return ( return (
@@ -178,6 +190,7 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
<Button <Button
className="w-full max-w-[300px]" className="w-full max-w-[300px]"
onClick={openFilePicker} onClick={openFilePicker}
isLoading={isLoading}
variant="outline" variant="outline"
> >
{filesContent.length === 0 {filesContent.length === 0
@@ -193,6 +206,7 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
variant="outline" variant="outline"
color="red" color="red"
className="w-full max-w-[200px]" className="w-full max-w-[200px]"
isLoading={isLoading}
onClick={onClose} onClick={onClose}
> >
Cancel Cancel
@@ -200,6 +214,7 @@ const CreatePanel = ({ user, users, group, onClose }: CreateDialogProps) => {
<Button <Button
className="w-full max-w-[200px]" className="w-full max-w-[200px]"
onClick={submit} onClick={submit}
isLoading={isLoading}
disabled={!name} disabled={!name}
> >
Submit Submit

View File

@@ -1,10 +1,19 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { app } from "@/firebase"; import { app } from "@/firebase";
import {getFirestore, collection, getDocs, getDoc, doc, deleteDoc, setDoc} from "firebase/firestore"; import {
getFirestore,
collection,
getDocs,
getDoc,
doc,
deleteDoc,
setDoc,
} from "firebase/firestore";
import { withIronSessionApiRoute } from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import { Group } from "@/interfaces/user"; import { Group } from "@/interfaces/user";
import { updateExpiryDateOnGroup } from "@/utils/groups.be";
const db = getFirestore(app); const db = getFirestore(app);
@@ -47,7 +56,11 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
const group = { ...snapshot.data(), id: snapshot.id } as Group; const group = { ...snapshot.data(), id: snapshot.id } as Group;
const user = req.session.user; const user = req.session.user;
if (user.type === "admin" || user.type === "developer" || user.id === group.admin) { if (
user.type === "admin" ||
user.type === "developer" ||
user.id === group.admin
) {
await deleteDoc(snapshot.ref); await deleteDoc(snapshot.ref);
res.status(200).json({ ok: true }); res.status(200).json({ ok: true });
@@ -69,7 +82,22 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
const group = { ...snapshot.data(), id: snapshot.id } as Group; const group = { ...snapshot.data(), id: snapshot.id } as Group;
const user = req.session.user; const user = req.session.user;
if (user.type === "admin" || user.type === "developer" || user.id === group.admin) { if (
user.type === "admin" ||
user.type === "developer" ||
user.id === group.admin
) {
if ("participants" in req.body) {
const newParticipants = (req.body.participants as string[]).filter(
(x) => !group.participants.includes(x),
);
await Promise.all(
newParticipants.map(
async (p) => await updateExpiryDateOnGroup(p, group.admin),
),
);
}
await setDoc(snapshot.ref, req.body, { merge: true }); await setDoc(snapshot.ref, req.body, { merge: true });
res.status(200).json({ ok: true }); res.status(200).json({ ok: true });

View File

@@ -1,11 +1,20 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { app } from "@/firebase"; import { app } from "@/firebase";
import {getFirestore, collection, getDocs, setDoc, doc, query, where} from "firebase/firestore"; import {
getFirestore,
collection,
getDocs,
setDoc,
doc,
query,
where,
} from "firebase/firestore";
import { withIronSessionApiRoute } from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import { Group } from "@/interfaces/user"; import { Group } from "@/interfaces/user";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { updateExpiryDateOnGroup } from "@/utils/groups.be";
const db = getFirestore(app); const db = getFirestore(app);
@@ -22,13 +31,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
} }
async function get(req: NextApiRequest, res: NextApiResponse) { async function get(req: NextApiRequest, res: NextApiResponse) {
const {admin, participant} = req.query as {admin: string; participant: string}; const { admin, participant } = req.query as {
admin: string;
participant: string;
};
const queryConstraints = [ const queryConstraints = [
...(admin ? [where("admin", "==", admin)] : []), ...(admin ? [where("admin", "==", admin)] : []),
...(participant ? [where("participants", "array-contains", participant)] : []), ...(participant
? [where("participants", "array-contains", participant)]
: []),
]; ];
const snapshot = await getDocs(queryConstraints.length > 0 ? query(collection(db, "groups"), ...queryConstraints) : collection(db, "groups")); const snapshot = await getDocs(
queryConstraints.length > 0
? query(collection(db, "groups"), ...queryConstraints)
: collection(db, "groups"),
);
const groups = snapshot.docs.map((doc) => ({ const groups = snapshot.docs.map((doc) => ({
id: doc.id, id: doc.id,
...doc.data(), ...doc.data(),
@@ -40,6 +58,16 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
async function post(req: NextApiRequest, res: NextApiResponse) { async function post(req: NextApiRequest, res: NextApiResponse) {
const body = req.body as Group; const body = req.body as Group;
await setDoc(doc(db, "groups", v4()), {name: body.name, admin: body.admin, participants: body.participants}); await Promise.all(
body.participants.map(
async (p) => await updateExpiryDateOnGroup(p, body.admin),
),
);
await setDoc(doc(db, "groups", v4()), {
name: body.name,
admin: body.admin,
participants: body.participants,
});
res.status(200).json({ ok: true }); res.status(200).json({ ok: true });
} }

53
src/utils/groups.be.ts Normal file
View File

@@ -0,0 +1,53 @@
import { app } from "@/firebase";
import { CorporateUser, StudentUser, TeacherUser } from "@/interfaces/user";
import { doc, getDoc, getFirestore, setDoc } from "firebase/firestore";
import moment from "moment";
const db = getFirestore(app);
export const updateExpiryDateOnGroup = async (
participantID: string,
corporateID: string,
) => {
const corporateRef = await getDoc(doc(db, "users", corporateID));
const participantRef = await getDoc(doc(db, "users", participantID));
if (!corporateRef.exists() || !participantRef.exists()) return;
const corporate = {
...corporateRef.data(),
id: corporateRef.id,
} as CorporateUser;
const participant = { ...participantRef.data(), id: participantRef.id } as
| StudentUser
| TeacherUser;
if (
corporate.type !== "corporate" ||
(participant.type !== "student" && participant.type !== "teacher")
)
return;
if (
!corporate.subscriptionExpirationDate ||
!participant.subscriptionExpirationDate
) {
return await setDoc(
doc(db, "users", participant.id),
{ subscriptionExpirationDate: null },
{ merge: true },
);
}
const corporateDate = moment(corporate.subscriptionExpirationDate);
const participantDate = moment(participant.subscriptionExpirationDate);
if (corporateDate.isAfter(participantDate))
return await setDoc(
doc(db, "users", participant.id),
{ subscriptionExpirationDate: corporateDate.toISOString() },
{ merge: true },
);
return;
};

View File

@@ -2,17 +2,23 @@ import {CorporateUser, Group, User} from "@/interfaces/user";
import axios from "axios"; import axios from "axios";
export const isUserFromCorporate = async (userID: string) => { export const isUserFromCorporate = async (userID: string) => {
const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`)).data; const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`))
.data;
const users = (await axios.get<User[]>("/api/users/list")).data; const users = (await axios.get<User[]>("/api/users/list")).data;
const adminTypes = groups.map((g) => users.find((u) => u.id === g.admin)?.type); const adminTypes = groups.map(
(g) => users.find((u) => u.id === g.admin)?.type,
);
return adminTypes.includes("corporate"); return adminTypes.includes("corporate");
}; };
export const getUserCorporate = async (userID: string) => { export const getUserCorporate = async (userID: string) => {
const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`)).data; const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`))
.data;
const users = (await axios.get<User[]>("/api/users/list")).data; const users = (await axios.get<User[]>("/api/users/list")).data;
const admins = groups.map((g) => users.find((u) => u.id === g.admin)).filter((x) => x?.type === "corporate"); const admins = groups
.map((g) => users.find((u) => u.id === g.admin))
.filter((x) => x?.type === "corporate");
return admins.length > 0 ? (admins[0] as CorporateUser) : undefined; return admins.length > 0 ? (admins[0] as CorporateUser) : undefined;
}; };