Merged develop into feature/level-file-upload
This commit is contained in:
@@ -6,11 +6,12 @@ interface Props {
|
|||||||
label: string;
|
label: string;
|
||||||
value?: string | number;
|
value?: string | number;
|
||||||
color: "purple" | "rose" | "red" | "green";
|
color: "purple" | "rose" | "red" | "green";
|
||||||
|
className?: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
onClick?: () => void;
|
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} = {
|
const colorClasses: {[key in typeof color]: string} = {
|
||||||
purple: "text-mti-purple-light",
|
purple: "text-mti-purple-light",
|
||||||
red: "text-mti-red-light",
|
red: "text-mti-red-light",
|
||||||
@@ -24,6 +25,7 @@ export default function IconCard({Icon, label, value, color, tooltip, onClick}:
|
|||||||
className={clsx(
|
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",
|
"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",
|
tooltip && "tooltip tooltip-bottom",
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
data-tip={tooltip}>
|
data-tip={tooltip}>
|
||||||
<Icon className={clsx("text-6xl", colorClasses[color])} />
|
<Icon className={clsx("text-6xl", colorClasses[color])} />
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default function BatchCreateUser({user}: {user: User}) {
|
|||||||
const information = uniqBy(
|
const information = uniqBy(
|
||||||
rows
|
rows
|
||||||
.map((row) => {
|
.map((row) => {
|
||||||
const [firstName, lastName, country, passport_id, email, phone, group, studentID] = row as string[];
|
const [firstName, lastName, country, passport_id, email, phone, group, studentID, corporate] = row as string[];
|
||||||
const countryItem =
|
const countryItem =
|
||||||
countryCodes.findOne("countryCode" as any, country.toUpperCase()) ||
|
countryCodes.findOne("countryCode" as any, country.toUpperCase()) ||
|
||||||
countryCodes.all().find((x) => x.countryNameEn.toLowerCase() === country.toLowerCase());
|
countryCodes.all().find((x) => x.countryNameEn.toLowerCase() === country.toLowerCase());
|
||||||
@@ -116,6 +116,7 @@ export default function BatchCreateUser({user}: {user: User}) {
|
|||||||
type: type,
|
type: type,
|
||||||
passport_id: passport_id?.toString().trim() || undefined,
|
passport_id: passport_id?.toString().trim() || undefined,
|
||||||
groupName: group,
|
groupName: group,
|
||||||
|
corporate,
|
||||||
studentID,
|
studentID,
|
||||||
demographicInformation: {
|
demographicInformation: {
|
||||||
country: countryItem?.countryCode,
|
country: countryItem?.countryCode,
|
||||||
@@ -184,6 +185,7 @@ export default function BatchCreateUser({user}: {user: User}) {
|
|||||||
<th className="border border-neutral-200 px-2 py-1">Phone Number</th>
|
<th className="border border-neutral-200 px-2 py-1">Phone Number</th>
|
||||||
<th className="border border-neutral-200 px-2 py-1">Group Name</th>
|
<th className="border border-neutral-200 px-2 py-1">Group Name</th>
|
||||||
<th className="border border-neutral-200 px-2 py-1">Student ID</th>
|
<th className="border border-neutral-200 px-2 py-1">Student ID</th>
|
||||||
|
{user?.type !== "corporate" && <th className="border border-neutral-200 px-2 py-1">Corporate (e-mail)</th>}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -181,52 +181,6 @@ export default function UserList({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{checkAccess(user, updateUserPermission.list, permissions, updateUserPermission.perm) && (
|
|
||||||
<Popover className="relative">
|
|
||||||
<Popover.Button>
|
|
||||||
<div data-tip="Change Type" className="cursor-pointer tooltip">
|
|
||||||
<BsPerson className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
|
||||||
</div>
|
|
||||||
</Popover.Button>
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1">
|
|
||||||
<Popover.Panel className="absolute z-10 w-screen right-1/2 translate-x-1/3 max-w-sm">
|
|
||||||
<div className="bg-white p-4 rounded-lg grid grid-cols-2 gap-2 w-full drop-shadow-xl">
|
|
||||||
<Button
|
|
||||||
onClick={() => updateAccountType(row.original, "student")}
|
|
||||||
className="text-sm !py-2 !px-4"
|
|
||||||
disabled={row.original.type === "student" || !PERMISSIONS.generateCode["student"].includes(user.type)}>
|
|
||||||
Student
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => updateAccountType(row.original, "teacher")}
|
|
||||||
className="text-sm !py-2 !px-4"
|
|
||||||
disabled={row.original.type === "teacher" || !PERMISSIONS.generateCode["teacher"].includes(user.type)}>
|
|
||||||
Teacher
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => updateAccountType(row.original, "corporate")}
|
|
||||||
className="text-sm !py-2 !px-4"
|
|
||||||
disabled={row.original.type === "corporate" || !PERMISSIONS.generateCode["corporate"].includes(user.type)}>
|
|
||||||
Corporate
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => updateAccountType(row.original, "admin")}
|
|
||||||
className="text-sm !py-2 !px-4"
|
|
||||||
disabled={row.original.type === "admin" || !PERMISSIONS.generateCode["admin"].includes(user.type)}>
|
|
||||||
Admin
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
{!row.original.isVerified && checkAccess(user, updateUserPermission.list, permissions, updateUserPermission.perm) && (
|
{!row.original.isVerified && checkAccess(user, updateUserPermission.list, permissions, updateUserPermission.perm) && (
|
||||||
<div data-tip="Verify User" className="cursor-pointer tooltip" onClick={() => verifyAccount(row.original)}>
|
<div data-tip="Verify User" className="cursor-pointer tooltip" onClick={() => verifyAccount(row.original)}>
|
||||||
<BsCheck className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
<BsCheck className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||||
|
|||||||
266
src/pages/(admin)/UserCreator.tsx
Normal file
266
src/pages/(admin)/UserCreator.tsx
Normal file
@@ -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<string>();
|
||||||
|
const [email, setEmail] = useState<string>();
|
||||||
|
const [phone, setPhone] = useState<string>();
|
||||||
|
const [passportID, setPassportID] = useState<string>();
|
||||||
|
const [studentID, setStudentID] = useState<string>();
|
||||||
|
const [country, setCountry] = useState(user?.demographicInformation?.country);
|
||||||
|
const [group, setGroup] = useState<string | null>();
|
||||||
|
const [availableCorporates, setAvailableCorporates] = useState<User[]>([]);
|
||||||
|
const [selectedCorporate, setSelectedCorporate] = useState<string | null>();
|
||||||
|
const [password, setPassword] = useState<string>();
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState<string>();
|
||||||
|
const [expiryDate, setExpiryDate] = useState<Date | null>(
|
||||||
|
user?.subscriptionExpirationDate ? moment(user?.subscriptionExpirationDate).toDate() : null,
|
||||||
|
);
|
||||||
|
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [type, setType] = useState<Type>("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 (
|
||||||
|
<div className="flex flex-col gap-4 border p-4 border-mti-gray-platinum rounded-xl">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<Input required label="Name" value={name} onChange={setName} type="text" name="name" placeholder="Name" />
|
||||||
|
<Input label="E-mail" required value={email} onChange={setEmail} type="email" name="email" placeholder="E-mail" />
|
||||||
|
|
||||||
|
<Input type="password" name="password" label="Password" value={password} onChange={setPassword} placeholder="Password" required />
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
name="confirmPassword"
|
||||||
|
label="Confirm Password"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={setConfirmPassword}
|
||||||
|
placeholder="ConfirmPassword"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Country *</label>
|
||||||
|
<CountrySelect value={country} onChange={setCountry} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input type="tel" name="phone" label="Phone number" value={phone} onChange={setPhone} placeholder="Phone number" required />
|
||||||
|
|
||||||
|
{type === "student" && (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
name="passport_id"
|
||||||
|
label="Passport/National ID"
|
||||||
|
onChange={setPassportID}
|
||||||
|
value={passportID}
|
||||||
|
placeholder="National ID or Passport number"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input type="text" name="studentID" label="Student ID" onChange={setStudentID} value={studentID} placeholder="Student ID" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{["student", "teacher"].includes(type) && !["corporate", "teacher"].includes(user?.type) && (
|
||||||
|
<div className={clsx("flex flex-col gap-4")}>
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Corporate</label>
|
||||||
|
<Select
|
||||||
|
options={availableCorporates.map((u) => ({value: u.id, label: getUserName(u)}))}
|
||||||
|
isClearable
|
||||||
|
onChange={(e) => setSelectedCorporate(e?.value || undefined)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"flex flex-col gap-4",
|
||||||
|
(!["student", "teacher"].includes(type) || ["corporate", "teacher"].includes(user?.type)) && "col-span-2",
|
||||||
|
)}>
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Group</label>
|
||||||
|
<Select
|
||||||
|
options={groups
|
||||||
|
.filter((x) => (!selectedCorporate ? true : x.admin === selectedCorporate))
|
||||||
|
.map((g) => ({value: g.id, label: g.name}))}
|
||||||
|
onChange={(e) => setGroup(e?.value || undefined)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"flex flex-col gap-4",
|
||||||
|
!checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && "col-span-2",
|
||||||
|
)}>
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Type</label>
|
||||||
|
{user && (
|
||||||
|
<select
|
||||||
|
defaultValue="student"
|
||||||
|
value={type}
|
||||||
|
onChange={(e) => setType(e.target.value as Type)}
|
||||||
|
className="p-6 w-full min-w-[350px] min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer bg-white">
|
||||||
|
{Object.keys(USER_TYPE_LABELS)
|
||||||
|
.filter((x) => {
|
||||||
|
const {list, perm} = USER_TYPE_PERMISSIONS[x as Type];
|
||||||
|
return checkAccess(user, getTypesOfUser(list), permissions, perm);
|
||||||
|
})
|
||||||
|
.map((type) => (
|
||||||
|
<option key={type} value={type}>
|
||||||
|
{USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{user && checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && (
|
||||||
|
<>
|
||||||
|
<div className="-md:flex-row -md:items-center flex justify-between gap-2 md:flex-col 2xl:flex-row 2xl:items-center">
|
||||||
|
<label className="text-mti-gray-dim text-base font-normal">Expiry Date</label>
|
||||||
|
<Checkbox
|
||||||
|
isChecked={isExpiryDateEnabled}
|
||||||
|
onChange={setIsExpiryDateEnabled}
|
||||||
|
disabled={!!user?.subscriptionExpirationDate}>
|
||||||
|
Enabled
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
{isExpiryDateEnabled && (
|
||||||
|
<ReactDatePicker
|
||||||
|
className={clsx(
|
||||||
|
"flex min-h-[70px] w-full cursor-pointer justify-center rounded-full border p-6 text-sm font-normal focus:outline-none",
|
||||||
|
"hover:border-mti-purple tooltip",
|
||||||
|
"transition duration-300 ease-in-out",
|
||||||
|
)}
|
||||||
|
filterDate={(date) =>
|
||||||
|
moment(date).isAfter(new Date()) &&
|
||||||
|
(user?.subscriptionExpirationDate ? moment(date).isBefore(user?.subscriptionExpirationDate) : true)
|
||||||
|
}
|
||||||
|
dateFormat="dd/MM/yyyy"
|
||||||
|
selected={expiryDate}
|
||||||
|
onChange={(date) => setExpiryDate(date)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={createUser} isLoading={isLoading} disabled={(isExpiryDateEnabled ? !expiryDate : false) || isLoading}>
|
||||||
|
Create User
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import {getFirestore, setDoc, doc, query, collection, where, getDocs, getDoc, de
|
|||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {v4} from "uuid";
|
import {v4} from "uuid";
|
||||||
import {Group} from "@/interfaces/user";
|
import {CorporateUser, Group} from "@/interfaces/user";
|
||||||
import {createUserWithEmailAndPassword, getAuth} from "firebase/auth";
|
import {createUserWithEmailAndPassword, getAuth} from "firebase/auth";
|
||||||
|
|
||||||
const DEFAULT_DESIRED_LEVELS = {
|
const DEFAULT_DESIRED_LEVELS = {
|
||||||
@@ -37,19 +37,25 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (!maker) {
|
if (!maker) {
|
||||||
return res.status(401).json({ok: false, reason: "You must be logged in to make user!"});
|
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, corporate} = req.body as {
|
||||||
email: string;
|
email: string;
|
||||||
|
password?: string;
|
||||||
passport_id: string;
|
passport_id: string;
|
||||||
type: string;
|
type: string;
|
||||||
groupName: string;
|
groupName?: string;
|
||||||
|
groupID?: string;
|
||||||
|
corporate?: string;
|
||||||
expiryDate: null | Date;
|
expiryDate: null | Date;
|
||||||
};
|
};
|
||||||
// cleaning data
|
// cleaning data
|
||||||
delete req.body.passport_id;
|
delete req.body.passport_id;
|
||||||
delete req.body.groupName;
|
delete req.body.groupName;
|
||||||
|
delete req.body.groupID;
|
||||||
delete req.body.expiryDate;
|
delete req.body.expiryDate;
|
||||||
|
delete req.body.password;
|
||||||
|
delete req.body.corporate;
|
||||||
|
|
||||||
await createUserWithEmailAndPassword(auth, email.toLowerCase(), passport_id)
|
await createUserWithEmailAndPassword(auth, email.toLowerCase(), !!password ? password : passport_id)
|
||||||
.then(async (userCredentials) => {
|
.then(async (userCredentials) => {
|
||||||
const userId = userCredentials.user.uid;
|
const userId = userCredentials.user.uid;
|
||||||
|
|
||||||
@@ -66,6 +72,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
registrationDate: new Date(),
|
registrationDate: new Date(),
|
||||||
subscriptionExpirationDate: expiryDate || null,
|
subscriptionExpirationDate: expiryDate || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
await setDoc(doc(db, "users", userId), user);
|
await setDoc(doc(db, "users", userId), user);
|
||||||
if (type === "corporate") {
|
if (type === "corporate") {
|
||||||
const defaultTeachersGroup: Group = {
|
const defaultTeachersGroup: Group = {
|
||||||
@@ -97,6 +104,34 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
await setDoc(doc(db, "groups", defaultCorporateGroup.id), defaultCorporateGroup);
|
await setDoc(doc(db, "groups", defaultCorporateGroup.id), defaultCorporateGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!!corporate) {
|
||||||
|
const corporateQ = query(collection(db, "users"), where("email", "==", corporate));
|
||||||
|
const corporateSnapshot = await getDocs(corporateQ);
|
||||||
|
|
||||||
|
if (!corporateSnapshot.empty) {
|
||||||
|
const corporateUser = corporateSnapshot.docs[0].data() as CorporateUser;
|
||||||
|
|
||||||
|
const q = query(
|
||||||
|
collection(db, "groups"),
|
||||||
|
where("admin", "==", corporateUser.id),
|
||||||
|
where("name", "==", type === "student" ? "Students" : "Teachers"),
|
||||||
|
limit(1),
|
||||||
|
);
|
||||||
|
const snapshot = await getDocs(q);
|
||||||
|
|
||||||
|
if (!snapshot.empty) {
|
||||||
|
const doc = snapshot.docs[0];
|
||||||
|
const participants: string[] = doc.get("participants");
|
||||||
|
|
||||||
|
if (!participants.includes(userId)) {
|
||||||
|
updateDoc(doc.ref, {
|
||||||
|
participants: [...participants, userId],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof groupName === "string" && groupName.trim().length > 0) {
|
if (typeof groupName === "string" && groupName.trim().length > 0) {
|
||||||
const q = query(collection(db, "groups"), where("admin", "==", maker.id), where("name", "==", groupName.trim()), limit(1));
|
const q = query(collection(db, "groups"), where("admin", "==", maker.id), where("name", "==", groupName.trim()), limit(1));
|
||||||
const snapshot = await getDocs(q);
|
const snapshot = await getDocs(q);
|
||||||
@@ -123,6 +158,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}`);
|
console.log(`Returning - ${email}`);
|
||||||
return res.status(200).json({ok: true});
|
return res.status(200).json({ok: true});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ import ExamGenerator from "./(admin)/ExamGenerator";
|
|||||||
import BatchCreateUser from "./(admin)/BatchCreateUser";
|
import BatchCreateUser from "./(admin)/BatchCreateUser";
|
||||||
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
|
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
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}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -46,6 +51,8 @@ export default function Admin() {
|
|||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
const {permissions} = usePermissions(user?.id || "");
|
const {permissions} = usePermissions(user?.id || "");
|
||||||
|
|
||||||
|
const [modalOpen, setModalOpen] = useState<string>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -60,14 +67,52 @@ export default function Admin() {
|
|||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{user && (
|
{user && (
|
||||||
<Layout user={user} className="gap-6">
|
<Layout user={user} className="gap-6">
|
||||||
|
<Modal isOpen={modalOpen === "batchCreateUser"} onClose={() => setModalOpen(undefined)}>
|
||||||
|
<BatchCreateUser user={user} />
|
||||||
|
</Modal>
|
||||||
|
<Modal isOpen={modalOpen === "batchCreateCode"} onClose={() => setModalOpen(undefined)}>
|
||||||
|
<CodeGenerator user={user} />
|
||||||
|
</Modal>
|
||||||
|
<Modal isOpen={modalOpen === "createCode"} onClose={() => setModalOpen(undefined)}>
|
||||||
|
<BatchCodeGenerator user={user} />
|
||||||
|
</Modal>
|
||||||
|
<Modal isOpen={modalOpen === "createUser"} onClose={() => setModalOpen(undefined)}>
|
||||||
|
<UserCreator user={user} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<section className="w-full grid grid-cols-2 -md:grid-cols-1 gap-8">
|
<section className="w-full grid grid-cols-2 -md:grid-cols-1 gap-8">
|
||||||
<ExamLoader />
|
<ExamLoader />
|
||||||
{checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && (
|
{checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && (
|
||||||
<>
|
<div className="w-full grid grid-cols-2 gap-4">
|
||||||
<BatchCreateUser user={user} />
|
<IconCard
|
||||||
<CodeGenerator user={user} />
|
Icon={BsCode}
|
||||||
<BatchCodeGenerator user={user} />
|
label="Generate Single Code"
|
||||||
</>
|
color="purple"
|
||||||
|
className="w-full h-full"
|
||||||
|
onClick={() => setModalOpen("createCode")}
|
||||||
|
/>
|
||||||
|
<IconCard
|
||||||
|
Icon={BsCodeSquare}
|
||||||
|
label="Generate Codes in Batch"
|
||||||
|
color="purple"
|
||||||
|
className="w-full h-full"
|
||||||
|
onClick={() => setModalOpen("batchCreateCode")}
|
||||||
|
/>
|
||||||
|
<IconCard
|
||||||
|
Icon={BsPersonFill}
|
||||||
|
label="Create Single User"
|
||||||
|
color="purple"
|
||||||
|
className="w-full h-full"
|
||||||
|
onClick={() => setModalOpen("createUser")}
|
||||||
|
/>
|
||||||
|
<IconCard
|
||||||
|
Icon={BsPeopleFill}
|
||||||
|
label="Create Users in Batch"
|
||||||
|
color="purple"
|
||||||
|
className="w-full h-full"
|
||||||
|
onClick={() => setModalOpen("batchCreateUser")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
<section className="w-full">
|
<section className="w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user