Added code role validation

This commit is contained in:
Joao Ramos
2024-07-25 09:43:11 +01:00
parent f6b4d6ad52
commit 923319051c
2 changed files with 517 additions and 329 deletions

View File

@@ -1,249 +1,366 @@
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import Checkbox from "@/components/Low/Checkbox"; import Checkbox from "@/components/Low/Checkbox";
import {PERMISSIONS} from "@/constants/userPermissions"; import { PERMISSIONS } from "@/constants/userPermissions";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {Type, User} from "@/interfaces/user"; import { Type, User } from "@/interfaces/user";
import {USER_TYPE_LABELS} from "@/resources/user"; import { USER_TYPE_LABELS } from "@/resources/user";
import axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import {capitalize, uniqBy} from "lodash"; import { capitalize, uniqBy } from "lodash";
import moment from "moment"; import moment from "moment";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import {toast} from "react-toastify"; import { toast } from "react-toastify";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import {useFilePicker} from "use-file-picker"; import { useFilePicker } from "use-file-picker";
import readXlsxFile from "read-excel-file"; import readXlsxFile from "read-excel-file";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import {BsFileEarmarkEaselFill, BsQuestionCircleFill} from "react-icons/bs"; import { BsFileEarmarkEaselFill, BsQuestionCircleFill } from "react-icons/bs";
import { checkAccess } from "@/utils/permissions";
import { PermissionType } from "@/interfaces/permissions";
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]+)*$/); const USER_TYPE_PERMISSIONS: {
[key in Type]: { perm: PermissionType | undefined; list: Type[] };
const USER_TYPE_PERMISSIONS: {[key in Type]: Type[]} = { } = {
student: [], student: {
teacher: [], perm: "createCodeStudent",
agent: [], list: [],
corporate: ["student", "teacher"], },
mastercorporate: ["student", "teacher", "corporate"], teacher: {
admin: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], perm: "createCodeTeacher",
developer: ["student", "teacher", "agent", "corporate", "admin", "developer", "mastercorporate"], list: [],
},
agent: {
perm: "createCodeCountryManager",
list: [],
},
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 BatchCodeGenerator({user}: {user: User}) { export default function BatchCodeGenerator({ user }: { user: User }) {
const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]); const [infos, setInfos] = useState<
const [isLoading, setIsLoading] = useState(false); { email: string; name: string; passport_id: string }[]
const [expiryDate, setExpiryDate] = useState<Date | null>( >([]);
user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, const [isLoading, setIsLoading] = useState(false);
); const [expiryDate, setExpiryDate] = useState<Date | null>(
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); user?.subscriptionExpirationDate
const [type, setType] = useState<Type>("student"); ? moment(user.subscriptionExpirationDate).toDate()
const [showHelp, setShowHelp] = useState(false); : null
);
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student");
const [showHelp, setShowHelp] = useState(false);
const {users} = useUsers(); const { users } = useUsers();
const {openFilePicker, filesContent, clear} = useFilePicker({ const { openFilePicker, filesContent, clear } = useFilePicker({
accept: ".xlsx", accept: ".xlsx",
multiple: false, multiple: false,
readAs: "ArrayBuffer", readAs: "ArrayBuffer",
}); });
useEffect(() => { useEffect(() => {
if (!isExpiryDateEnabled) setExpiryDate(null); if (!isExpiryDateEnabled) setExpiryDate(null);
}, [isExpiryDateEnabled]); }, [isExpiryDateEnabled]);
useEffect(() => { useEffect(() => {
if (filesContent.length > 0) { if (filesContent.length > 0) {
const file = filesContent[0]; const file = filesContent[0];
readXlsxFile(file.content).then((rows) => { readXlsxFile(file.content).then((rows) => {
try { try {
const information = uniqBy( const information = uniqBy(
rows rows
.map((row) => { .map((row) => {
const [firstName, lastName, country, passport_id, email, ...phone] = row as string[]; const [
return EMAIL_REGEX.test(email.toString().trim()) firstName,
? { lastName,
email: email.toString().trim().toLowerCase(), country,
name: `${firstName ?? ""} ${lastName ?? ""}`.trim(), passport_id,
passport_id: passport_id?.toString().trim() || undefined, email,
} ...phone
: undefined; ] = row as string[];
}) return EMAIL_REGEX.test(email.toString().trim())
.filter((x) => !!x) as typeof infos, ? {
(x) => x.email, email: email.toString().trim().toLowerCase(),
); name: `${firstName ?? ""} ${lastName ?? ""}`.trim(),
passport_id: passport_id?.toString().trim() || undefined,
}
: undefined;
})
.filter((x) => !!x) as typeof infos,
(x) => x.email
);
if (information.length === 0) { if (information.length === 0) {
toast.error( toast.error(
"Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!", "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!"
); );
return clear(); return clear();
} }
setInfos(information); setInfos(information);
} catch { } catch {
toast.error( toast.error(
"Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!", "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!"
); );
return clear(); return clear();
} }
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [filesContent]); }, [filesContent]);
const generateAndInvite = async () => { const generateAndInvite = async () => {
const newUsers = infos.filter((x) => !users.map((u) => u.email).includes(x.email)); const newUsers = infos.filter(
const existingUsers = infos (x) => !users.map((u) => u.email).includes(x.email)
.filter((x) => users.map((u) => u.email).includes(x.email)) );
.map((i) => users.find((u) => u.email === i.email)) const existingUsers = infos
.filter((x) => !!x && x.type === "student") as User[]; .filter((x) => users.map((u) => u.email).includes(x.email))
.map((i) => users.find((u) => u.email === i.email))
.filter((x) => !!x && x.type === "student") as User[];
const newUsersSentence = newUsers.length > 0 ? `generate ${newUsers.length} code(s)` : undefined; const newUsersSentence =
const existingUsersSentence = existingUsers.length > 0 ? `invite ${existingUsers.length} registered student(s)` : undefined; newUsers.length > 0 ? `generate ${newUsers.length} code(s)` : undefined;
if ( const existingUsersSentence =
!confirm( existingUsers.length > 0
`You are about to ${[newUsersSentence, existingUsersSentence].filter((x) => !!x).join(" and ")}, are you sure you want to continue?`, ? `invite ${existingUsers.length} registered student(s)`
) : undefined;
) if (
return; !confirm(
`You are about to ${[newUsersSentence, existingUsersSentence]
.filter((x) => !!x)
.join(" and ")}, are you sure you want to continue?`
)
)
return;
setIsLoading(true); setIsLoading(true);
Promise.all(existingUsers.map(async (u) => await axios.post(`/api/invites`, {to: u.id, from: user.id}))) Promise.all(
.then(() => toast.success(`Successfully invited ${existingUsers.length} registered student(s)!`)) existingUsers.map(
.finally(() => { async (u) =>
if (newUsers.length === 0) setIsLoading(false); await axios.post(`/api/invites`, { to: u.id, from: user.id })
}); )
)
.then(() =>
toast.success(
`Successfully invited ${existingUsers.length} registered student(s)!`
)
)
.finally(() => {
if (newUsers.length === 0) setIsLoading(false);
});
if (newUsers.length > 0) generateCode(type, newUsers); if (newUsers.length > 0) generateCode(type, newUsers);
setInfos([]); setInfos([]);
}; };
const generateCode = (type: Type, informations: typeof infos) => { const generateCode = (type: Type, informations: typeof infos) => {
const uid = new ShortUniqueId(); const uid = new ShortUniqueId();
const codes = informations.map(() => uid.randomUUID(6)); const codes = informations.map(() => uid.randomUUID(6));
setIsLoading(true); setIsLoading(true);
axios axios
.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,
expiryDate, expiryDate,
}) })
.then(({data, status}) => { .then(({ data, status }) => {
if (data.ok) { if (data.ok) {
toast.success( toast.success(
`Successfully generated${data.valid ? ` ${data.valid}/${informations.length}` : ""} ${capitalize( `Successfully generated${
type, data.valid ? ` ${data.valid}/${informations.length}` : ""
)} codes and they have been notified by e-mail!`, } ${capitalize(type)} codes and they have been notified by e-mail!`,
{toastId: "success"}, { toastId: "success" }
); );
return; return;
} }
if (status === 403) { if (status === 403) {
toast.error(data.reason, {toastId: "forbidden"}); toast.error(data.reason, { toastId: "forbidden" });
} }
}) })
.catch(({response: {status, data}}) => { .catch(({ response: { status, data } }) => {
if (status === 403) { if (status === 403) {
toast.error(data.reason, {toastId: "forbidden"}); toast.error(data.reason, { toastId: "forbidden" });
return; return;
} }
toast.error(`Something went wrong, please try again later!`, { toast.error(`Something went wrong, please try again later!`, {
toastId: "error", toastId: "error",
}); });
}) })
.finally(() => { .finally(() => {
setIsLoading(false); setIsLoading(false);
return clear(); return clear();
}); });
}; };
return ( return (
<> <>
<Modal isOpen={showHelp} onClose={() => setShowHelp(false)} title="Excel File Format"> <Modal
<div className="mt-4 flex flex-col gap-2"> isOpen={showHelp}
<span>Please upload an Excel file with the following format:</span> onClose={() => setShowHelp(false)}
<table className="w-full"> title="Excel File Format"
<thead> >
<tr> <div className="mt-4 flex flex-col gap-2">
<th className="border border-neutral-200 px-2 py-1">First Name</th> <span>Please upload an Excel file with the following format:</span>
<th className="border border-neutral-200 px-2 py-1">Last Name</th> <table className="w-full">
<th className="border border-neutral-200 px-2 py-1">Country</th> <thead>
<th className="border border-neutral-200 px-2 py-1">Passport/National ID</th> <tr>
<th className="border border-neutral-200 px-2 py-1">E-mail</th> <th className="border border-neutral-200 px-2 py-1">
<th className="border border-neutral-200 px-2 py-1">Phone Number</th> First Name
</tr> </th>
</thead> <th className="border border-neutral-200 px-2 py-1">
</table> Last Name
<span className="mt-4"> </th>
<b>Notes:</b> <th className="border border-neutral-200 px-2 py-1">Country</th>
<ul> <th className="border border-neutral-200 px-2 py-1">
<li>- All incorrect e-mails will be ignored;</li> Passport/National ID
<li>- All already registered e-mails will be ignored;</li> </th>
<li>- You may have a header row with the format above, however, it is not necessary;</li> <th className="border border-neutral-200 px-2 py-1">E-mail</th>
<li>- All of the e-mails in the file will receive an e-mail to join EnCoach with the role selected below.</li> <th className="border border-neutral-200 px-2 py-1">
</ul> Phone Number
</span> </th>
</div> </tr>
</Modal> </thead>
<div className="border-mti-gray-platinum flex flex-col gap-4 rounded-xl border p-4"> </table>
<div className="flex items-end justify-between"> <span className="mt-4">
<label className="text-mti-gray-dim text-base font-normal">Choose an Excel file</label> <b>Notes:</b>
<div className="tooltip cursor-pointer" data-tip="Excel File Format" onClick={() => setShowHelp(true)}> <ul>
<BsQuestionCircleFill /> <li>- All incorrect e-mails will be ignored;</li>
</div> <li>- All already registered e-mails will be ignored;</li>
</div> <li>
<Button onClick={openFilePicker} isLoading={isLoading} disabled={isLoading}> - You may have a header row with the format above, however, it
{filesContent.length > 0 ? filesContent[0].name : "Choose a file"} is not necessary;
</Button> </li>
{user && (["developer","admin","corporate", "mastercorporate"].includes(user.type)) && ( <li>
<> - All of the e-mails in the file will receive an e-mail to join
<div className="-md:flex-row -md:items-center flex justify-between gap-2 md:flex-col 2xl:flex-row 2xl:items-center"> EnCoach with the role selected below.
<label className="text-mti-gray-dim text-base font-normal">Expiry Date</label> </li>
<Checkbox isChecked={isExpiryDateEnabled} onChange={setIsExpiryDateEnabled} disabled={!!user.subscriptionExpirationDate}> </ul>
Enabled </span>
</Checkbox> </div>
</div> </Modal>
{isExpiryDateEnabled && ( <div className="border-mti-gray-platinum flex flex-col gap-4 rounded-xl border p-4">
<ReactDatePicker <div className="flex items-end justify-between">
className={clsx( <label className="text-mti-gray-dim text-base font-normal">
"flex min-h-[70px] w-full cursor-pointer justify-center rounded-full border p-6 text-sm font-normal focus:outline-none", Choose an Excel file
"hover:border-mti-purple tooltip", </label>
"transition duration-300 ease-in-out", <div
)} className="tooltip cursor-pointer"
filterDate={(date) => data-tip="Excel File Format"
moment(date).isAfter(new Date()) && onClick={() => setShowHelp(true)}
(user.subscriptionExpirationDate ? moment(date).isBefore(user.subscriptionExpirationDate) : true) >
} <BsQuestionCircleFill />
dateFormat="dd/MM/yyyy" </div>
selected={expiryDate} </div>
onChange={(date) => setExpiryDate(date)} <Button
/> onClick={openFilePicker}
)} isLoading={isLoading}
</> disabled={isLoading}
)} >
<label className="text-mti-gray-dim text-base font-normal">Select the type of user they should be</label> {filesContent.length > 0 ? filesContent[0].name : "Choose a file"}
{user && ( </Button>
<select {user &&
defaultValue="student" checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && (
onChange={(e) => setType(e.target.value as typeof user.type)} <>
className="flex min-h-[70px] w-full min-w-[350px] cursor-pointer justify-center rounded-full border bg-white p-6 text-sm font-normal focus:outline-none"> <div className="-md:flex-row -md:items-center flex justify-between gap-2 md:flex-col 2xl:flex-row 2xl:items-center">
{Object.keys(USER_TYPE_LABELS) <label className="text-mti-gray-dim text-base font-normal">
.filter((x) => USER_TYPE_PERMISSIONS[user.type].includes(x as Type)) Expiry Date
.map((type) => ( </label>
<option key={type} value={type}> <Checkbox
{USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]} isChecked={isExpiryDateEnabled}
</option> onChange={setIsExpiryDateEnabled}
))} disabled={!!user.subscriptionExpirationDate}
</select> >
)} Enabled
<Button onClick={generateAndInvite} disabled={infos.length === 0 || (isExpiryDateEnabled ? !expiryDate : false)}> </Checkbox>
Generate & Send </div>
</Button> {isExpiryDateEnabled && (
</div> <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)}
/>
)}
</>
)}
<label className="text-mti-gray-dim text-base font-normal">
Select the type of user they should be
</label>
{user && (
<select
defaultValue="student"
onChange={(e) => setType(e.target.value as typeof user.type)}
className="flex min-h-[70px] w-full min-w-[350px] cursor-pointer justify-center rounded-full border bg-white p-6 text-sm font-normal focus:outline-none"
>
{Object.keys(USER_TYPE_LABELS)
.filter((x) => {
const { list, perm } = USER_TYPE_PERMISSIONS[x as Type];
return checkAccess(user, list, perm);
})
.map((type) => (
<option key={type} value={type}>
{USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]}
</option>
))}
</select>
)}
<Button
onClick={generateAndInvite}
disabled={
infos.length === 0 || (isExpiryDateEnabled ? !expiryDate : false)
}
>
Generate & Send
</Button>
</div>
</>
);
} }

View File

@@ -1,126 +1,197 @@
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import Checkbox from "@/components/Low/Checkbox"; import Checkbox from "@/components/Low/Checkbox";
import {PERMISSIONS} from "@/constants/userPermissions"; import { PERMISSIONS } from "@/constants/userPermissions";
import {Type, User} from "@/interfaces/user"; import { Type, User } from "@/interfaces/user";
import {USER_TYPE_LABELS} from "@/resources/user"; import { USER_TYPE_LABELS } from "@/resources/user";
import axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import {capitalize} from "lodash"; import { capitalize } from "lodash";
import moment from "moment"; import moment from "moment";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import {toast} from "react-toastify"; import { toast } from "react-toastify";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { checkAccess } from "@/utils/permissions";
import { PermissionType } from "@/interfaces/permissions";
const USER_TYPE_PERMISSIONS: {[key in Type]: Type[]} = { const USER_TYPE_PERMISSIONS: {
student: [], [key in Type]: { perm: PermissionType | undefined; list: Type[] };
teacher: [], } = {
agent: [], student: {
corporate: ["student", "teacher"], perm: "createCodeStudent",
mastercorporate: ["student", "teacher", "corporate"], list: [],
admin: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], },
developer: ["student", "teacher", "agent", "corporate", "admin", "developer","mastercorporate"], teacher: {
perm: "createCodeTeacher",
list: [],
},
agent: {
perm: "createCodeCountryManager",
list: [],
},
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 CodeGenerator({user}: {user: User}) { export default function CodeGenerator({ user }: { user: User }) {
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()
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); : null
const [type, setType] = useState<Type>("student"); );
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student");
useEffect(() => { useEffect(() => {
if (!isExpiryDateEnabled) setExpiryDate(null); if (!isExpiryDateEnabled) setExpiryDate(null);
}, [isExpiryDateEnabled]); }, [isExpiryDateEnabled]);
const generateCode = (type: Type) => { const generateCode = (type: Type) => {
const uid = new ShortUniqueId(); const uid = new ShortUniqueId();
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 })
.then(({data, status}) => { .then(({ data, status }) => {
if (data.ok) { if (data.ok) {
toast.success(`Successfully generated a ${capitalize(type)} code!`, {toastId: "success"}); toast.success(`Successfully generated a ${capitalize(type)} code!`, {
setGeneratedCode(code); toastId: "success",
return; });
} setGeneratedCode(code);
return;
}
if (status === 403) { if (status === 403) {
toast.error(data.reason, {toastId: "forbidden"}); toast.error(data.reason, { toastId: "forbidden" });
} }
}) })
.catch(({response: {status, data}}) => { .catch(({ response: { status, data } }) => {
if (status === 403) { if (status === 403) {
toast.error(data.reason, {toastId: "forbidden"}); toast.error(data.reason, { toastId: "forbidden" });
return; return;
} }
toast.error(`Something went wrong, please try again later!`, {toastId: "error"}); toast.error(`Something went wrong, please try again later!`, {
}); toastId: "error",
}; });
});
};
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 && ( User Code Generator
<select </label>
defaultValue="student" {user && (
onChange={(e) => setType(e.target.value as typeof user.type)} <select
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"> defaultValue="student"
{Object.keys(USER_TYPE_LABELS) onChange={(e) => setType(e.target.value as typeof user.type)}
.filter((x) => USER_TYPE_PERMISSIONS[user.type].includes(x 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"
.map((type) => ( >
<option key={type} value={type}> {Object.keys(USER_TYPE_LABELS)
{USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]} .filter((x) => {
</option> const { list, perm } = USER_TYPE_PERMISSIONS[x as Type];
))} return checkAccess(user, list, perm);
</select> })
)} .map((type) => (
{user && (user.type === "developer" || user.type === "admin" || user.type === "corporate") && ( <option key={type} value={type}>
<> {USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]}
<div className="-md:flex-row -md:items-center flex justify-between gap-2 md:flex-col 2xl:flex-row 2xl:items-center"> </option>
<label className="text-mti-gray-dim text-base font-normal">Expiry Date</label> ))}
<Checkbox isChecked={isExpiryDateEnabled} onChange={setIsExpiryDateEnabled} disabled={!!user.subscriptionExpirationDate}> </select>
Enabled )}
</Checkbox> {user &&
</div> checkAccess(user, ["developer", "admin", "corporate"]) && (
{isExpiryDateEnabled && ( <>
<ReactDatePicker <div className="-md:flex-row -md:items-center flex justify-between gap-2 md:flex-col 2xl:flex-row 2xl:items-center">
className={clsx( <label className="text-mti-gray-dim text-base font-normal">
"flex min-h-[70px] w-full cursor-pointer justify-center rounded-full border p-6 text-sm font-normal focus:outline-none", Expiry Date
"hover:border-mti-purple tooltip", </label>
"transition duration-300 ease-in-out", <Checkbox
)} isChecked={isExpiryDateEnabled}
filterDate={(date) => onChange={setIsExpiryDateEnabled}
moment(date).isAfter(new Date()) && disabled={!!user.subscriptionExpirationDate}
(user.subscriptionExpirationDate ? moment(date).isBefore(user.subscriptionExpirationDate) : true) >
} Enabled
dateFormat="dd/MM/yyyy" </Checkbox>
selected={expiryDate} </div>
onChange={(date) => setExpiryDate(date)} {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",
<Button onClick={() => generateCode(type)} disabled={isExpiryDateEnabled ? !expiryDate : false}> "transition duration-300 ease-in-out"
Generate )}
</Button> filterDate={(date) =>
<label className="font-normal text-base text-mti-gray-dim">Generated Code:</label> moment(date).isAfter(new Date()) &&
<div (user.subscriptionExpirationDate
className={clsx( ? moment(date).isBefore(user.subscriptionExpirationDate)
"p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", : true)
"hover:border-mti-purple tooltip", }
"transition duration-300 ease-in-out", dateFormat="dd/MM/yyyy"
)} selected={expiryDate}
data-tip="Click to copy" onChange={(date) => setExpiryDate(date)}
onClick={() => { />
if (generatedCode) navigator.clipboard.writeText(generatedCode); )}
}}> </>
{generatedCode} )}
</div> <Button
{generatedCode && <span className="text-sm text-mti-gray-dim font-light">Give this code to the user to complete their registration</span>} onClick={() => generateCode(type)}
</div> disabled={isExpiryDateEnabled ? !expiryDate : false}
); >
Generate
</Button>
<label className="font-normal text-base text-mti-gray-dim">
Generated Code:
</label>
<div
className={clsx(
"p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
"hover:border-mti-purple tooltip",
"transition duration-300 ease-in-out"
)}
data-tip="Click to copy"
onClick={() => {
if (generatedCode) navigator.clipboard.writeText(generatedCode);
}}
>
{generatedCode}
</div>
{generatedCode && (
<span className="text-sm text-mti-gray-dim font-light">
Give this code to the user to complete their registration
</span>
)}
</div>
);
} }