376 lines
11 KiB
TypeScript
376 lines
11 KiB
TypeScript
import Button from "@/components/Low/Button";
|
|
import Checkbox from "@/components/Low/Checkbox";
|
|
import { Type, User } from "@/interfaces/user";
|
|
import { USER_TYPE_LABELS } from "@/resources/user";
|
|
import axios from "axios";
|
|
import clsx from "clsx";
|
|
import moment from "moment";
|
|
import { useEffect, useState } from "react";
|
|
import ReactDatePicker from "react-datepicker";
|
|
import { toast } from "react-toastify";
|
|
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
|
|
import { PermissionType } from "@/interfaces/permissions";
|
|
import Input from "@/components/Low/Input";
|
|
import CountrySelect from "@/components/Low/CountrySelect";
|
|
import Select from "@/components/Low/Select";
|
|
import { EntityWithRoles } from "@/interfaces/entity";
|
|
import useEntitiesGroups from "@/hooks/useEntitiesGroups";
|
|
|
|
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[];
|
|
entities: EntityWithRoles[];
|
|
permissions: PermissionType[];
|
|
onFinish: () => void;
|
|
}
|
|
|
|
export default function UserCreator({
|
|
user,
|
|
users,
|
|
entities = [],
|
|
permissions,
|
|
onFinish,
|
|
}: Props) {
|
|
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 [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 [position, setPosition] = useState<string>();
|
|
const [entity, setEntity] = useState((entities || [])[0]?.id || undefined);
|
|
|
|
const { groups } = useEntitiesGroups();
|
|
|
|
useEffect(() => {
|
|
if (!isExpiryDateEnabled) setExpiryDate(null);
|
|
}, [isExpiryDateEnabled]);
|
|
|
|
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 < 6)
|
|
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,
|
|
entity,
|
|
type,
|
|
studentID: type === "student" ? studentID : undefined,
|
|
expiryDate,
|
|
demographicInformation: {
|
|
passport_id: type === "student" ? passportID : undefined,
|
|
phone,
|
|
country,
|
|
position,
|
|
},
|
|
};
|
|
|
|
axios
|
|
.post("/api/make_user", body)
|
|
.then(() => {
|
|
toast.success("That user has been created!");
|
|
onFinish();
|
|
|
|
setName("");
|
|
setEmail("");
|
|
setPhone("");
|
|
setPassportID("");
|
|
setStudentID("");
|
|
setCountry(user?.demographicInformation?.country);
|
|
setGroup(null);
|
|
setEntity((entities || [])[0]?.id || undefined);
|
|
setExpiryDate(
|
|
user?.subscriptionExpirationDate
|
|
? moment(user?.subscriptionExpirationDate).toDate()
|
|
: null
|
|
);
|
|
setIsExpiryDateEnabled(true);
|
|
setType("student");
|
|
setPosition(undefined);
|
|
})
|
|
.catch((error) => {
|
|
const data = error?.response?.data;
|
|
if (!!data?.message) return toast.error(data.message);
|
|
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"
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
<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>
|
|
|
|
{["corporate", "mastercorporate"].includes(type) && (
|
|
<Input
|
|
type="text"
|
|
name="department"
|
|
label="Department"
|
|
onChange={setPosition}
|
|
value={position}
|
|
placeholder="Department"
|
|
/>
|
|
)}
|
|
|
|
<div className={clsx("flex flex-col gap-4")}>
|
|
<label className="font-normal text-base text-mti-gray-dim">
|
|
Classroom
|
|
</label>
|
|
<Select
|
|
options={groups
|
|
.filter((x) => x.entity?.id === entity)
|
|
.map((g) => ({ value: g.id, label: g.name }))}
|
|
onChange={(e) => setGroup(e?.value || undefined)}
|
|
isClearable
|
|
/>
|
|
</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).reduce<string[]>((acc, x) => {
|
|
const { list, perm } = USER_TYPE_PERMISSIONS[x as Type];
|
|
if (checkAccess(user, getTypesOfUser(list), permissions, perm))
|
|
acc.push(x);
|
|
return acc;
|
|
}, [])}
|
|
</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>
|
|
);
|
|
}
|