ENCOA-263

This commit is contained in:
Tiago Ribeiro
2024-12-11 22:00:43 +00:00
parent ce35ba71f4
commit 1a7d35317b
10 changed files with 234 additions and 194 deletions

View File

@@ -19,7 +19,6 @@ import Select from "react-select";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import { USER_TYPE_LABELS } from "@/resources/user"; import { USER_TYPE_LABELS } from "@/resources/user";
import { CURRENCIES } from "@/resources/paypal"; import { CURRENCIES } from "@/resources/paypal";
import useCodes from "@/hooks/useCodes";
import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { PERMISSIONS } from "@/constants/userPermissions"; import { PERMISSIONS } from "@/constants/userPermissions";
import { PermissionType } from "@/interfaces/permissions"; import { PermissionType } from "@/interfaces/permissions";
@@ -119,7 +118,6 @@ const UserCard = ({
); );
const { data: stats } = useFilterRecordsByUser<Stat[]>(user.id); const { data: stats } = useFilterRecordsByUser<Stat[]>(user.id);
const { users } = useUsers(); const { users } = useUsers();
const { codes } = useCodes(user.id);
const { permissions } = usePermissions(loggedInUser.id); const { permissions } = usePermissions(loggedInUser.id);
useEffect(() => { useEffect(() => {

View File

@@ -2,7 +2,7 @@ import {Code, Group, User} from "@/interfaces/user";
import axios from "axios"; import axios from "axios";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
export default function useCodes(creator?: string) { export default function useCodes(entity?: string) {
const [codes, setCodes] = useState<Code[]>([]); const [codes, setCodes] = useState<Code[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false); const [isError, setIsError] = useState(false);
@@ -10,12 +10,12 @@ export default function useCodes(creator?: string) {
const getData = () => { const getData = () => {
setIsLoading(true); setIsLoading(true);
axios axios
.get<Code[]>(`/api/code${creator ? `?creator=${creator}` : ""}`) .get<Code[]>(`/api/code${entity ? `?entity=${entity}` : ""}`)
.then((response) => setCodes(response.data)) .then((response) => setCodes(response.data))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };
useEffect(getData, [creator]); useEffect(getData, [entity]);
return { codes, isLoading, isError, reload: getData }; return { codes, isLoading, isError, reload: getData };
} }

View File

@@ -19,6 +19,8 @@ import {BsFileEarmarkEaselFill, BsQuestionCircleFill} from "react-icons/bs";
import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { PermissionType } from "@/interfaces/permissions"; import { PermissionType } from "@/interfaces/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import { EntityWithRoles } from "@/interfaces/entity";
import Select from "@/components/Low/Select";
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/); const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/);
@@ -59,10 +61,11 @@ interface Props {
user: User; user: User;
users: User[]; users: User[];
permissions: PermissionType[]; permissions: PermissionType[];
entities: EntityWithRoles[]
onFinish: () => void; onFinish: () => void;
} }
export default function BatchCodeGenerator({user, users, permissions, onFinish}: Props) { export default function BatchCodeGenerator({ user, users, entities = [], permissions, onFinish }: Props) {
const [infos, setInfos] = useState<{ email: string; name: string; passport_id: string }[]>([]); const [infos, setInfos] = useState<{ email: string; name: string; passport_id: string }[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [expiryDate, setExpiryDate] = useState<Date | null>( const [expiryDate, setExpiryDate] = useState<Date | null>(
@@ -71,6 +74,7 @@ export default function BatchCodeGenerator({user, users, permissions, onFinish}:
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student"); const [type, setType] = useState<Type>("student");
const [showHelp, setShowHelp] = useState(false); const [showHelp, setShowHelp] = useState(false);
const [entity, setEntity] = useState((entities || [])[0]?.id || undefined)
const { openFilePicker, filesContent, clear } = useFilePicker({ const { openFilePicker, filesContent, clear } = useFilePicker({
accept: ".xlsx", accept: ".xlsx",
@@ -158,8 +162,9 @@ export default function BatchCodeGenerator({user, users, permissions, onFinish}:
.post<{ ok: boolean; valid?: number; reason?: string }>("/api/code", { .post<{ ok: boolean; valid?: number; reason?: string }>("/api/code", {
type, type,
codes, codes,
infos: informations, infos: informations.map((info, index) => ({ ...info, code: codes[index] })),
expiryDate, expiryDate,
entity
}) })
.then(({ data, status }) => { .then(({ data, status }) => {
if (data.ok) { if (data.ok) {
@@ -258,6 +263,15 @@ export default function BatchCodeGenerator({user, users, permissions, onFinish}:
)} )}
</> </>
)} )}
<div className={clsx("flex flex-col gap-4")}>
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
<Select
defaultValue={{ value: (entities || [])[0]?.id, label: (entities || [])[0]?.label }}
options={entities.map((e) => ({ value: e.id, label: e.label }))}
onChange={(e) => setEntity(e?.value || undefined)}
isClearable={checkAccess(user, ["admin", "developer"])}
/>
</div>
<label className="text-mti-gray-dim text-base font-normal">Select the type of user they should be</label> <label className="text-mti-gray-dim text-base font-normal">Select the type of user they should be</label>
{user && ( {user && (
<select <select

View File

@@ -14,6 +14,9 @@ import ShortUniqueId from "short-unique-id";
import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { PermissionType } from "@/interfaces/permissions"; import { PermissionType } from "@/interfaces/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import { EntityWithRoles } from "@/interfaces/entity";
import Select from "@/components/Low/Select";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
const USER_TYPE_PERMISSIONS: { const USER_TYPE_PERMISSIONS: {
[key in Type]: { perm: PermissionType | undefined; list: Type[] }; [key in Type]: { perm: PermissionType | undefined; list: Type[] };
@@ -51,16 +54,19 @@ const USER_TYPE_PERMISSIONS: {
interface Props { interface Props {
user: User; user: User;
permissions: PermissionType[]; permissions: PermissionType[];
entities: EntityWithRoles[]
onFinish: () => void; onFinish: () => void;
} }
export default function CodeGenerator({user, permissions, onFinish}: Props) { export default function CodeGenerator({ user, entities = [], permissions, onFinish }: Props) {
const [generatedCode, setGeneratedCode] = useState<string>(); const [generatedCode, setGeneratedCode] = useState<string>();
const [expiryDate, setExpiryDate] = useState<Date | null>( const [expiryDate, setExpiryDate] = useState<Date | null>(
user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null,
); );
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student"); const [type, setType] = useState<Type>("student");
const [entity, setEntity] = useState((entities || [])[0]?.id || undefined)
useEffect(() => { useEffect(() => {
if (!isExpiryDateEnabled) setExpiryDate(null); if (!isExpiryDateEnabled) setExpiryDate(null);
@@ -71,7 +77,7 @@ export default function CodeGenerator({user, permissions, onFinish}: Props) {
const code = uid.randomUUID(6); const code = uid.randomUUID(6);
axios axios
.post("/api/code", {type, codes: [code], expiryDate}) .post("/api/code", { type, codes: [code], expiryDate, entity })
.then(({ data, status }) => { .then(({ data, status }) => {
if (data.ok) { if (data.ok) {
toast.success(`Successfully generated a ${capitalize(type)} code!`, { toast.success(`Successfully generated a ${capitalize(type)} code!`, {
@@ -100,7 +106,18 @@ export default function CodeGenerator({user, permissions, onFinish}: Props) {
return ( return (
<div className="flex flex-col gap-4 border p-4 border-mti-gray-platinum rounded-xl"> <div className="flex flex-col gap-4 border p-4 border-mti-gray-platinum rounded-xl">
<label className="font-normal text-base text-mti-gray-dim">User Code Generator</label> <label className="font-normal text-base text-mti-gray-dim">User Code Generator</label>
{user && ( <div className={clsx("flex flex-col gap-4")}>
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
<Select
defaultValue={{ value: (entities || [])[0]?.id, label: (entities || [])[0]?.label }}
options={entities.map((e) => ({ value: e.id, label: e.label }))}
onChange={(e) => setEntity(e?.value || undefined)}
isClearable={checkAccess(user, ["admin", "developer"])}
/>
</div>
<div className={clsx("flex flex-col gap-4")}>
<label className="font-normal text-base text-mti-gray-dim">Type</label>
<select <select
defaultValue="student" defaultValue="student"
onChange={(e) => setType(e.target.value as typeof user.type)} onChange={(e) => setType(e.target.value as typeof user.type)}
@@ -116,8 +133,9 @@ export default function CodeGenerator({user, permissions, onFinish}: Props) {
</option> </option>
))} ))}
</select> </select>
)} </div>
{user && checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && (
{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"> <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> <label className="text-mti-gray-dim text-base font-normal">Expiry Date</label>

View File

@@ -16,6 +16,9 @@ import ReactDatePicker from "react-datepicker";
import clsx from "clsx"; import clsx from "clsx";
import { checkAccess } from "@/utils/permissions"; import { checkAccess } from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import { EntityWithRoles } from "@/interfaces/entity";
import { isAdmin } from "@/utils/users";
import { findBy } from "@/utils";
const columnHelper = createColumnHelper<Code>(); const columnHelper = createColumnHelper<Code>();
@@ -34,16 +37,15 @@ const CreatorCell = ({ id, users }: { id: string; users: User[] }) => {
); );
}; };
export default function CodeList({ user, canDeleteCodes }: { user: User, canDeleteCodes?: boolean }) { export default function CodeList({ user, entities, canDeleteCodes }
: { user: User, entities: EntityWithRoles[], canDeleteCodes?: boolean }) {
const [selectedCodes, setSelectedCodes] = useState<string[]>([]); const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
const [filteredCorporate, setFilteredCorporate] = useState<User | undefined>(user?.type === "corporate" ? user : undefined); const [filteredCorporate, setFilteredCorporate] = useState<User | undefined>(user?.type === "corporate" ? user : undefined);
const [filterAvailability, setFilterAvailability] = useState<"in-use" | "unused">(); const [filterAvailability, setFilterAvailability] = useState<"in-use" | "unused">();
const { permissions } = usePermissions(user?.id || "");
const { users } = useUsers(); const { users } = useUsers();
const { codes, reload } = useCodes(user?.type === "corporate" ? user?.id : undefined); const { codes, reload } = useCodes();
const [startDate, setStartDate] = useState<Date | null>(moment("01/01/2023").toDate()); const [startDate, setStartDate] = useState<Date | null>(moment("01/01/2023").toDate());
const [endDate, setEndDate] = useState<Date | null>(moment().endOf("day").toDate()); const [endDate, setEndDate] = useState<Date | null>(moment().endOf("day").toDate());
@@ -158,13 +160,17 @@ export default function CodeList({ user, canDeleteCodes }: { user: User, canDele
cell: (info) => (info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A"), cell: (info) => (info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A"),
}), }),
columnHelper.accessor("email", { columnHelper.accessor("email", {
header: "Invited E-mail", header: "E-mail",
cell: (info) => info.getValue() || "N/A", cell: (info) => info.getValue() || "N/A",
}), }),
columnHelper.accessor("creator", { columnHelper.accessor("creator", {
header: "Creator", header: "Creator",
cell: (info) => <CreatorCell id={info.getValue()} users={users} />, cell: (info) => <CreatorCell id={info.getValue()} users={users} />,
}), }),
columnHelper.accessor("entity", {
header: "Entity",
cell: (info) => findBy(entities, 'id', info.getValue())?.label || "N/A",
}),
columnHelper.accessor("userId", { columnHelper.accessor("userId", {
header: "Availability", header: "Availability",
cell: (info) => cell: (info) =>

View File

@@ -3,7 +3,6 @@ import Checkbox from "@/components/Low/Checkbox";
import Input from "@/components/Low/Input"; import Input from "@/components/Low/Input";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import useCodes from "@/hooks/useCodes";
import useDiscounts from "@/hooks/useDiscounts"; import useDiscounts from "@/hooks/useDiscounts";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";

View File

@@ -106,7 +106,7 @@ export default function Lists({ user, entities = [], permissions }: Props) {
)} )}
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && entitiesViewCodes.length > 0 && ( {checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && entitiesViewCodes.length > 0 && (
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide"> <TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
<CodeList user={user} canDeleteCodes={entitiesDeleteCodes.length > 0} /> <CodeList user={user} entities={entitiesViewCodes} canDeleteCodes={entitiesDeleteCodes.length > 0} />
</TabPanel> </TabPanel>
)} )}
{checkAccess(user, ["developer", "admin"]) && ( {checkAccess(user, ["developer", "admin"]) && (

View File

@@ -6,6 +6,12 @@ import { sessionOptions } from "@/lib/session";
import { Code, Group, Type } from "@/interfaces/user"; import { Code, Group, Type } from "@/interfaces/user";
import { PERMISSIONS } from "@/constants/userPermissions"; import { PERMISSIONS } from "@/constants/userPermissions";
import { prepareMailer, prepareMailOptions } from "@/email"; import { prepareMailer, prepareMailOptions } from "@/email";
import { isAdmin } from "@/utils/users";
import { requestUser } from "@/utils/api";
import { doesEntityAllow } from "@/utils/permissions";
import { getEntity, getEntityWithRoles } from "@/utils/entities.be";
import { findBy } from "@/utils";
import { EntityWithRoles } from "@/interfaces/entity";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -25,68 +31,28 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
return; return;
} }
const { creator } = req.query as { creator?: string }; const { entity } = req.query as { entity?: string };
const snapshot = await db.collection("codes").find(creator ? { creator: creator } : {}).toArray(); const snapshot = await db.collection("codes").find(entity ? { entity } : {}).toArray();
res.status(200).json(snapshot); res.status(200).json(snapshot);
} }
async function post(req: NextApiRequest, res: NextApiResponse) { const generateAndSendCode = async (
if (!req.session.user) { code: string,
res.status(401).json({ ok: false, reason: "You must be logged in to generate a code!" }); type: Type,
return; expiryDate: null | Date,
entity?: string,
info?: {
email: string; name: string; passport_id?: string
}) => {
if (!info) {
await db.collection("codes").insertOne({
code, type, expiryDate, entity
})
return true
} }
const { type, codes, infos, expiryDate } = req.body as { const previousCode = await db.collection("codes").findOne<Code>({ email: info.email, entity })
type: Type;
codes: string[];
infos?: { email: string; name: string; passport_id?: string }[];
expiryDate: null | Date;
};
const permission = PERMISSIONS.generateCode[type];
if (!permission.includes(req.session.user.type)) {
res.status(403).json({
ok: false,
reason: "Your account type does not have permissions to generate a code for that type of user!",
});
return;
}
const userCodes = await db.collection("codes").find<Code>({ creator: req.session.user.id }).toArray()
const creatorGroupsSnapshot = await db.collection("groups").find<Group>({ admin: req.session.user.id }).toArray()
const creatorGroups = creatorGroupsSnapshot.filter((x) => x.name === "Students" || x.name === "Teachers" || x.name === "Corporate");
const usersInGroups = creatorGroups.flatMap((x) => x.participants);
if (req.session.user.type === "corporate") {
const totalCodes = userCodes.filter((x) => !x.userId || !usersInGroups.includes(x.userId)).length + usersInGroups.length + codes.length;
const allowedCodes = 0;
if (totalCodes > allowedCodes) {
res.status(403).json({
ok: false,
reason: `You have or would have exceeded your amount of allowed codes, you currently are allowed to generate ${allowedCodes - userCodes.length
} codes.`,
});
return;
}
}
const codePromises = codes.map(async (code, index) => {
const codeRef = await db.collection("codes").findOne<Code>({ id: code });
let codeInformation = {
type,
code,
creator: req.session.user!.id,
creationDate: new Date().toISOString(),
expiryDate,
};
if (infos && infos.length > index) {
const { email, name, passport_id } = infos[index];
const previousCode = userCodes.find((x) => x.email === email) as Code;
const transport = prepareMailer(); const transport = prepareMailer();
const mailOptions = prepareMailOptions( const mailOptions = prepareMailOptions(
@@ -95,47 +61,68 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
code: previousCode ? previousCode.code : code, code: previousCode ? previousCode.code : code,
environment: process.env.ENVIRONMENT, environment: process.env.ENVIRONMENT,
}, },
[email.toLowerCase().trim()], [info.email.toLowerCase().trim()],
"EnCoach Registration", "EnCoach Registration",
"main", "main",
); );
try { try {
await transport.sendMail(mailOptions); await transport.sendMail(mailOptions);
if (!previousCode) {
if (!previousCode && codeRef) { await db.collection("codes").insertOne({
await db.collection("codes").updateOne( code, type, expiryDate, entity, name: info.name.trim(), email: info.email.trim().toLowerCase(),
{ id: codeRef.id }, ...(info.passport_id ? { passport_id: info.passport_id.trim() } : {})
{ })
$set: {
id: codeRef.id,
...codeInformation,
email: email.trim().toLowerCase(),
name: name.trim(),
...(passport_id ? { passport_id: passport_id.trim() } : {}),
}
},
{ upsert: true }
);
} }
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
} }
} else {
// upsert: true -> if it doesnt exist insert
await db.collection("codes").updateOne(
{ id: code },
{ $set: { id: code, ...codeInformation } },
{ upsert: true }
);
} }
});
Promise.all(codePromises).then((results) => { const countAvailableCodes = async (entity: EntityWithRoles) => {
res.status(200).json({ ok: true, valid: results.filter((x) => x).length }); const usedUp = await db.collection("codes").countDocuments({ entity: entity.id })
}); const total = entity.licenses
return total - usedUp
}
async function post(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false, reason: "You must be logged in to generate a code!" });
const { type, codes, infos, expiryDate, entity } = req.body as {
type: Type;
codes: string[];
infos?: { email: string; name: string; passport_id?: string, code: string }[];
expiryDate: null | Date;
entity?: string
};
if (!entity && !isAdmin(user))
return res.status(403).json({ ok: false, reason: "You must be an admin to generate a code without an entity!" });
const entityObj = entity ? await getEntityWithRoles(entity) : undefined
const isAllowed = entityObj ? doesEntityAllow(user, entityObj, 'create_code') : true
if (!isAllowed) return res.status(403).json({ ok: false, reason: "You do not have permissions to generate a code!" });
if (entityObj) {
const availableCodes = await countAvailableCodes(entityObj)
if (availableCodes < codes.length)
return res.status(400).json({
ok: false,
reason: `You only have ${availableCodes} codes available, while trying to create ${codes.length} codes`
})
}
const valid = []
for (const code of codes) {
const info = findBy(infos || [], 'code', code)
const isValid = await generateAndSendCode(code, type, expiryDate, entity, info)
valid.push(isValid)
}
return res.status(200).json({ ok: true, valid: valid.length });
} }
async function del(req: NextApiRequest, res: NextApiResponse) { async function del(req: NextApiRequest, res: NextApiResponse) {

View File

@@ -8,6 +8,8 @@ import {addUserToGroupOnCreation} from "@/utils/registration";
import moment from "moment"; import moment from "moment";
import { v4 } from "uuid"; import { v4 } from "uuid";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import { addUserToEntity, getEntityWithRoles } from "@/utils/entities.be";
import { findBy } from "@/utils";
const auth = getAuth(app); const auth = getAuth(app);
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -47,10 +49,9 @@ async function registerIndividual(req: NextApiRequest, res: NextApiResponse) {
const codeDoc = await db.collection("codes").findOne<Code>({ code }); const codeDoc = await db.collection("codes").findOne<Code>({ code });
if (code && code.length > 0 && !!codeDoc) { if (code && code.length > 0 && !codeDoc)
res.status(400).json({error: "Invalid Code!"}); return res.status(400).json({ error: "Invalid Code!" });
return;
}
createUserWithEmailAndPassword(auth, email.toLowerCase(), password) createUserWithEmailAndPassword(auth, email.toLowerCase(), password)
.then(async (userCredentials) => { .then(async (userCredentials) => {
@@ -81,7 +82,13 @@ async function registerIndividual(req: NextApiRequest, res: NextApiResponse) {
if (!!codeDoc) { if (!!codeDoc) {
await db.collection("codes").updateOne({ code: codeDoc.code }, { $set: { userId } }); await db.collection("codes").updateOne({ code: codeDoc.code }, { $set: { userId } });
if (codeDoc.creator) await addUserToGroupOnCreation(userId, codeDoc.type, codeDoc.creator); if (codeDoc.entity) {
const inviteEntity = await getEntityWithRoles(codeDoc.entity)
if (inviteEntity) {
const defaultRole = findBy(inviteEntity.roles, 'isDefault', true)!
await addUserToEntity(userId, codeDoc.entity, defaultRole.id)
}
}
} }
req.session.user = user; req.session.user = user;

View File

@@ -94,10 +94,21 @@ export default function Admin({ user, entities, permissions, allUsers, entitiesG
/> />
</Modal> </Modal>
<Modal isOpen={modalOpen === "batchCreateCode"} onClose={() => setModalOpen(undefined)}> <Modal isOpen={modalOpen === "batchCreateCode"} onClose={() => setModalOpen(undefined)}>
<BatchCodeGenerator user={user} users={allUsers} permissions={permissions} onFinish={() => setModalOpen(undefined)} /> <BatchCodeGenerator
entities={entitiesAllowCreateCodes}
user={user}
users={allUsers}
permissions={permissions}
onFinish={() => setModalOpen(undefined)}
/>
</Modal> </Modal>
<Modal isOpen={modalOpen === "createCode"} onClose={() => setModalOpen(undefined)}> <Modal isOpen={modalOpen === "createCode"} onClose={() => setModalOpen(undefined)}>
<CodeGenerator user={user} permissions={permissions} onFinish={() => setModalOpen(undefined)} /> <CodeGenerator
entities={entitiesAllowCreateCode}
user={user}
permissions={permissions}
onFinish={() => setModalOpen(undefined)}
/>
</Modal> </Modal>
<Modal isOpen={modalOpen === "createUser"} onClose={() => setModalOpen(undefined)}> <Modal isOpen={modalOpen === "createUser"} onClose={() => setModalOpen(undefined)}>
<UserCreator <UserCreator