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"; 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]: Type[] } = { student: [], teacher: [], agent: [], corporate: ["student", "teacher"], admin: ["student", "teacher", "agent", "corporate", "admin"], developer: ["student", "teacher", "agent", "corporate", "admin", "developer"], }; export default function BatchCodeGenerator({ user }: { user: User }) { const [infos, setInfos] = useState< { email: string; name: string; passport_id: string }[] >([]); const [isLoading, setIsLoading] = useState(false); const [expiryDate, setExpiryDate] = useState(null); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [type, setType] = useState("student"); const [showHelp, setShowHelp] = useState(false); const { users } = useUsers(); const { openFilePicker, filesContent, clear } = useFilePicker({ accept: ".xlsx", multiple: false, readAs: "ArrayBuffer", }); useEffect(() => { if (user && (user.type === "corporate" || user.type === "teacher")) { setExpiryDate(user.subscriptionExpirationDate || null); } }, [user]); 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(), 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" }, ); 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 && (user.type === "developer" || user.type === "admin") && ( <>
Enabled
{isExpiryDateEnabled && ( moment(date).isAfter(new Date())} dateFormat="dd/MM/yyyy" selected={expiryDate} onChange={(date) => setExpiryDate(date)} /> )} )} {user && ( )}
); }