Merge branch 'develop' into feature/ticket-system
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
getFirestore,
|
||||||
import {sessionOptions} from "@/lib/session";
|
collection,
|
||||||
import {Group} from "@/interfaces/user";
|
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);
|
const db = getFirestore(app);
|
||||||
|
|
||||||
@@ -20,16 +29,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
async function get(req: NextApiRequest, res: NextApiResponse) {
|
async function get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
res.status(401).json({ok: false});
|
res.status(401).json({ ok: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {id} = req.query as {id: string};
|
const { id } = req.query as { id: string };
|
||||||
|
|
||||||
const snapshot = await getDoc(doc(db, "groups", id));
|
const snapshot = await getDoc(doc(db, "groups", id));
|
||||||
|
|
||||||
if (snapshot.exists()) {
|
if (snapshot.exists()) {
|
||||||
res.status(200).json({...snapshot.data(), id: snapshot.id});
|
res.status(200).json({ ...snapshot.data(), id: snapshot.id });
|
||||||
} else {
|
} else {
|
||||||
res.status(404).json(undefined);
|
res.status(404).json(undefined);
|
||||||
}
|
}
|
||||||
@@ -37,44 +46,63 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
async function del(req: NextApiRequest, res: NextApiResponse) {
|
async function del(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
res.status(401).json({ok: false});
|
res.status(401).json({ ok: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {id} = req.query as {id: string};
|
const { id } = req.query as { id: string };
|
||||||
|
|
||||||
const snapshot = await getDoc(doc(db, "groups", id));
|
const snapshot = await getDoc(doc(db, "groups", id));
|
||||||
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 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(403).json({ok: false});
|
res.status(403).json({ ok: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function patch(req: NextApiRequest, res: NextApiResponse) {
|
async function patch(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
res.status(401).json({ok: false});
|
res.status(401).json({ ok: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {id} = req.query as {id: string};
|
const { id } = req.query as { id: string };
|
||||||
|
|
||||||
const snapshot = await getDoc(doc(db, "groups", id));
|
const snapshot = await getDoc(doc(db, "groups", id));
|
||||||
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 (
|
||||||
await setDoc(snapshot.ref, req.body, {merge: true});
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
res.status(200).json({ok: true});
|
await setDoc(snapshot.ref, req.body, { merge: true });
|
||||||
|
|
||||||
|
res.status(200).json({ ok: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(403).json({ok: false});
|
res.status(403).json({ ok: false });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
getFirestore,
|
||||||
import {sessionOptions} from "@/lib/session";
|
collection,
|
||||||
import {Group} from "@/interfaces/user";
|
getDocs,
|
||||||
import {v4} from "uuid";
|
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);
|
const db = getFirestore(app);
|
||||||
|
|
||||||
@@ -13,7 +22,7 @@ export default withIronSessionApiRoute(handler, sessionOptions);
|
|||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
res.status(401).json({ok: false});
|
res.status(401).json({ ok: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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(
|
||||||
res.status(200).json({ok: true});
|
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
53
src/utils/groups.be.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -1,18 +1,24 @@
|
|||||||
import {CorporateUser, Group, User} from "@/interfaces/user";
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user