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

View File

@@ -1,10 +1,19 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
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 { sessionOptions } from "@/lib/session";
import { Group } from "@/interfaces/user";
import { updateExpiryDateOnGroup } from "@/utils/groups.be";
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 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);
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 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 });
res.status(200).json({ ok: true });

View File

@@ -1,11 +1,20 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
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 { sessionOptions } from "@/lib/session";
import { Group } from "@/interfaces/user";
import { v4 } from "uuid";
import { updateExpiryDateOnGroup } from "@/utils/groups.be";
const db = getFirestore(app);
@@ -22,13 +31,22 @@ async function handler(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 = [
...(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) => ({
id: doc.id,
...doc.data(),
@@ -40,6 +58,16 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
async function post(req: NextApiRequest, res: NextApiResponse) {
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 });
}

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";
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 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");
};
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 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;
};