From 1bb540589498b00941946693e8641af45830fc0f Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Fri, 23 Aug 2024 12:02:35 +0100 Subject: [PATCH] ENCOA-88: Create individual accounts --- src/dashboards/IconCard.tsx | 4 +- src/pages/(admin)/UserCreator.tsx | 266 ++++++++++++++++++++++++++++++ src/pages/api/make_user.ts | 16 +- src/pages/settings.tsx | 55 +++++- 4 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 src/pages/(admin)/UserCreator.tsx diff --git a/src/dashboards/IconCard.tsx b/src/dashboards/IconCard.tsx index 5fd55486..b1ce3c7a 100644 --- a/src/dashboards/IconCard.tsx +++ b/src/dashboards/IconCard.tsx @@ -6,11 +6,12 @@ interface Props { label: string; value?: string | number; color: "purple" | "rose" | "red" | "green"; + className?: string; tooltip?: string; onClick?: () => void; } -export default function IconCard({Icon, label, value, color, tooltip, onClick}: Props) { +export default function IconCard({Icon, label, value, color, tooltip, className, onClick}: Props) { const colorClasses: {[key in typeof color]: string} = { purple: "text-mti-purple-light", red: "text-mti-red-light", @@ -24,6 +25,7 @@ export default function IconCard({Icon, label, value, color, tooltip, onClick}: className={clsx( "bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center text-center w-52 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300", tooltip && "tooltip tooltip-bottom", + className, )} data-tip={tooltip}> diff --git a/src/pages/(admin)/UserCreator.tsx b/src/pages/(admin)/UserCreator.tsx new file mode 100644 index 00000000..a0b955f5 --- /dev/null +++ b/src/pages/(admin)/UserCreator.tsx @@ -0,0 +1,266 @@ +import Button from "@/components/Low/Button"; +import Checkbox from "@/components/Low/Checkbox"; +import {PERMISSIONS} from "@/constants/userPermissions"; +import {CorporateUser, TeacherUser, Type, User} from "@/interfaces/user"; +import {USER_TYPE_LABELS} from "@/resources/user"; +import axios from "axios"; +import clsx from "clsx"; +import {capitalize, uniqBy} from "lodash"; +import moment from "moment"; +import {useEffect, useState} from "react"; +import ReactDatePicker from "react-datepicker"; +import {toast} from "react-toastify"; +import ShortUniqueId from "short-unique-id"; +import {checkAccess, getTypesOfUser} from "@/utils/permissions"; +import {PermissionType} from "@/interfaces/permissions"; +import usePermissions from "@/hooks/usePermissions"; +import Input from "@/components/Low/Input"; +import CountrySelect from "@/components/Low/CountrySelect"; +import useGroups from "@/hooks/useGroups"; +import useUsers from "@/hooks/useUsers"; +import {getUserName} from "@/utils/users"; +import Select from "@/components/Low/Select"; + +const USER_TYPE_PERMISSIONS: { + [key in Type]: {perm: PermissionType | undefined; list: Type[]}; +} = { + student: { + perm: "createCodeStudent", + list: [], + }, + teacher: { + perm: "createCodeTeacher", + list: [], + }, + agent: { + perm: "createCodeCountryManager", + list: ["student", "teacher", "corporate", "mastercorporate"], + }, + corporate: { + perm: "createCodeCorporate", + list: ["student", "teacher"], + }, + mastercorporate: { + perm: undefined, + list: ["student", "teacher", "corporate"], + }, + admin: { + perm: "createCodeAdmin", + list: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], + }, + developer: { + perm: undefined, + list: ["student", "teacher", "agent", "corporate", "admin", "developer", "mastercorporate"], + }, +}; + +export default function UserCreator({user}: {user: User}) { + const [name, setName] = useState(); + const [email, setEmail] = useState(); + const [phone, setPhone] = useState(); + const [passportID, setPassportID] = useState(); + const [studentID, setStudentID] = useState(); + const [country, setCountry] = useState(user?.demographicInformation?.country); + const [group, setGroup] = useState(); + const [availableCorporates, setAvailableCorporates] = useState([]); + const [selectedCorporate, setSelectedCorporate] = useState(); + const [password, setPassword] = useState(); + const [confirmPassword, setConfirmPassword] = useState(); + const [expiryDate, setExpiryDate] = useState( + user?.subscriptionExpirationDate ? moment(user?.subscriptionExpirationDate).toDate() : null, + ); + const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [type, setType] = useState("student"); + + const {permissions} = usePermissions(user?.id || ""); + const {groups} = useGroups({admin: ["developer", "admin"].includes(user?.type) ? undefined : user?.id, userType: user?.type}); + const {users} = useUsers(); + + useEffect(() => { + if (!isExpiryDateEnabled) setExpiryDate(null); + }, [isExpiryDateEnabled]); + + useEffect(() => { + setAvailableCorporates( + uniqBy( + users.filter((u) => u.type === "corporate" && groups.flatMap((g) => g.participants).includes(u.id)), + "id", + ), + ); + }, [users, groups]); + + const createUser = () => { + if (!name || name.trim().length === 0) return toast.error("Please enter a valid name!"); + if (!email || email.trim().length === 0) return toast.error("Please enter a valid e-mail address!"); + if (users.map((x) => x.email).includes(email.trim())) return toast.error("That e-mail is already in use!"); + if (!password || password.trim().length === 0) return toast.error("Please enter a valid password!"); + if (password !== confirmPassword) return toast.error("The passwords do not match!"); + + setIsLoading(true); + + const body = { + name, + email, + password, + groupID: group, + type, + studentID: type === "student" ? studentID : undefined, + expiryDate, + demographicInformation: { + passport_id: type === "student" ? passportID : undefined, + phone, + country, + }, + }; + + axios + .post("/api/make_user", body) + .then(() => { + toast.success("That user has been created!"); + + setName(""); + setEmail(""); + setPhone(""); + setPassportID(""); + setStudentID(""); + setCountry(user?.demographicInformation?.country); + setGroup(null); + setSelectedCorporate(null); + setExpiryDate(user?.subscriptionExpirationDate ? moment(user?.subscriptionExpirationDate).toDate() : null); + setIsExpiryDateEnabled(true); + setType("student"); + }) + .catch(() => toast.error("Something went wrong! Please try again later!")) + .finally(() => setIsLoading(false)); + }; + + return ( +
+
+ + + + + + +
+ + +
+ + + + {type === "student" && ( + <> + + + + )} + + {["student", "teacher"].includes(type) && !["corporate", "teacher"].includes(user?.type) && ( +
+ + (!selectedCorporate ? true : x.admin === selectedCorporate)) + .map((g) => ({value: g.id, label: g.name}))} + onChange={(e) => setGroup(e?.value || undefined)} + /> +
+ +
+ + {user && ( + + )} +
+ +
+ {user && checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && ( + <> +
+ + + Enabled + +
+ {isExpiryDateEnabled && ( + + moment(date).isAfter(new Date()) && + (user?.subscriptionExpirationDate ? moment(date).isBefore(user?.subscriptionExpirationDate) : true) + } + dateFormat="dd/MM/yyyy" + selected={expiryDate} + onChange={(date) => setExpiryDate(date)} + /> + )} + + )} +
+
+ + +
+ ); +} diff --git a/src/pages/api/make_user.ts b/src/pages/api/make_user.ts index bdcb14a2..479157bb 100644 --- a/src/pages/api/make_user.ts +++ b/src/pages/api/make_user.ts @@ -37,19 +37,23 @@ async function post(req: NextApiRequest, res: NextApiResponse) { if (!maker) { return res.status(401).json({ok: false, reason: "You must be logged in to make user!"}); } - const {email, passport_id, type, groupName, expiryDate} = req.body as { + const {email, passport_id, password, type, groupName, groupID, expiryDate} = req.body as { email: string; + password?: string; passport_id: string; type: string; - groupName: string; + groupName?: string; + groupID?: string; expiryDate: null | Date; }; // cleaning data delete req.body.passport_id; delete req.body.groupName; + delete req.body.groupID; delete req.body.expiryDate; + delete req.body.password; - await createUserWithEmailAndPassword(auth, email.toLowerCase(), passport_id) + await createUserWithEmailAndPassword(auth, email.toLowerCase(), !!password ? password : passport_id) .then(async (userCredentials) => { const userId = userCredentials.user.uid; @@ -66,6 +70,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { registrationDate: new Date(), subscriptionExpirationDate: expiryDate || null, }; + await setDoc(doc(db, "users", userId), user); if (type === "corporate") { const defaultTeachersGroup: Group = { @@ -123,6 +128,11 @@ async function post(req: NextApiRequest, res: NextApiResponse) { } } + if (!!groupID) { + const groupSnapshot = await getDoc(doc(db, "groups", groupID)); + await setDoc(groupSnapshot.ref, {participants: [...groupSnapshot.data()!.participants, userId]}, {merge: true}); + } + console.log(`Returning - ${email}`); return res.status(200).json({ok: true}); }) diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 8fc93230..9fdd3781 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -16,6 +16,11 @@ import ExamGenerator from "./(admin)/ExamGenerator"; import BatchCreateUser from "./(admin)/BatchCreateUser"; import {checkAccess, getTypesOfUser} from "@/utils/permissions"; import usePermissions from "@/hooks/usePermissions"; +import {useState} from "react"; +import Modal from "@/components/Modal"; +import IconCard from "@/dashboards/IconCard"; +import {BsCode, BsCodeSquare, BsPeopleFill, BsPersonFill} from "react-icons/bs"; +import UserCreator from "./(admin)/UserCreator"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -46,6 +51,8 @@ export default function Admin() { const {user} = useUser({redirectTo: "/login"}); const {permissions} = usePermissions(user?.id || ""); + const [modalOpen, setModalOpen] = useState(); + return ( <> @@ -60,14 +67,52 @@ export default function Admin() { {user && ( + setModalOpen(undefined)}> + + + setModalOpen(undefined)}> + + + setModalOpen(undefined)}> + + + setModalOpen(undefined)}> + + +
{checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && ( - <> - - - - +
+ setModalOpen("createCode")} + /> + setModalOpen("batchCreateCode")} + /> + setModalOpen("createUser")} + /> + setModalOpen("batchCreateUser")} + /> +
)}