import Button from "@/components/Low/Button"; import Checkbox from "@/components/Low/Checkbox"; import {PERMISSIONS} from "@/constants/userPermissions"; import useUsers from "@/hooks/useUsers"; import {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 {useFilePicker} from "use-file-picker"; import readXlsxFile from "read-excel-file"; import Modal from "@/components/Modal"; import {BsFileEarmarkEaselFill, BsQuestionCircleFill} from "react-icons/bs"; import {checkAccess, getTypesOfUser} from "@/utils/permissions"; import {PermissionType} from "@/interfaces/permissions"; import usePermissions from "@/hooks/usePermissions"; 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[]}; } = { 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"], }, }; interface Props { user: User; users: User[]; permissions: PermissionType[]; onFinish: () => void; } export default function BatchCodeGenerator({user, users, permissions, onFinish}: Props) { const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]); const [isLoading, setIsLoading] = useState(false); const [expiryDate, setExpiryDate] = useState( user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, ); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [type, setType] = useState("student"); const [showHelp, setShowHelp] = useState(false); const {openFilePicker, filesContent, clear} = useFilePicker({ accept: ".xlsx", multiple: false, readAs: "ArrayBuffer", }); useEffect(() => { if (!isExpiryDateEnabled) setExpiryDate(null); }, [isExpiryDateEnabled]); useEffect(() => { if (filesContent.length > 0) { const file = filesContent[0]; readXlsxFile(file.content).then((rows) => { try { const information = uniqBy( rows .map((row) => { const [firstName, lastName, country, passport_id, email, phone] = row as string[]; return EMAIL_REGEX.test(email.toString().trim()) ? { 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) { toast.error( "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!", ); return clear(); } setInfos(information); } catch { toast.error( "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!", ); return clear(); } }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [filesContent]); const generateAndInvite = async () => { const newUsers = infos.filter((x) => !users.map((u) => u.email).includes(x.email)); const existingUsers = infos .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 existingUsersSentence = existingUsers.length > 0 ? `invite ${existingUsers.length} registered student(s)` : undefined; if ( !confirm( `You are about to ${[newUsersSentence, existingUsersSentence].filter((x) => !!x).join(" and ")}, are you sure you want to continue?`, ) ) return; setIsLoading(true); Promise.all(existingUsers.map(async (u) => 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); setInfos([]); }; const generateCode = (type: Type, informations: typeof infos) => { const uid = new ShortUniqueId(); const codes = informations.map(() => uid.randomUUID(6)); setIsLoading(true); axios .post<{ok: boolean; valid?: number; reason?: string}>("/api/code", { type, codes, infos: informations, expiryDate, }) .then(({data, status}) => { if (data.ok) { toast.success( `Successfully generated${data.valid ? ` ${data.valid}/${informations.length}` : ""} ${capitalize( type, )} codes and they have been notified by e-mail!`, {toastId: "success"}, ); onFinish(); return; } if (status === 403) { toast.error(data.reason, {toastId: "forbidden"}); } }) .catch(({response: {status, data}}) => { if (status === 403) { toast.error(data.reason, {toastId: "forbidden"}); return; } toast.error(`Something went wrong, please try again later!`, { toastId: "error", }); }) .finally(() => { setIsLoading(false); return clear(); }); }; return ( <> setShowHelp(false)} title="Excel File Format">
Please upload an Excel file with the following format:
First Name Last Name Country Passport/National ID E-mail Phone Number
Notes:
  • - All incorrect e-mails will be ignored;
  • - All already registered e-mails will be ignored;
  • - You may have a header row with the format above, however, it is not necessary;
  • - All of the e-mails in the file will receive an e-mail to join EnCoach with the role selected below.
setShowHelp(true)}>
{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)} /> )} )} {user && ( )} {checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"], permissions, "createCodes") && ( )}
); }