Added the ability to create an agent using the CodeGenerator

This commit is contained in:
Tiago Ribeiro
2023-11-21 13:24:07 +00:00
parent 51e7c535df
commit 8072cefbe6
9 changed files with 55 additions and 77 deletions

View File

@@ -105,12 +105,12 @@ export default function MobileMenu({isOpen, onClose, path, user}: Props) {
</Link> </Link>
{user.type !== "student" && ( {user.type !== "student" && (
<Link <Link
href="/manage" href="/settings"
className={clsx( className={clsx(
"transition ease-in-out duration-300 w-fit", "transition ease-in-out duration-300 w-fit",
path === "/manage" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ", path === "/settings" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
)}> )}>
Management Settings
</Link> </Link>
)} )}
<Link <Link

View File

@@ -1,5 +1,6 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import {User} from "@/interfaces/user"; import {User} from "@/interfaces/user";
import {USER_TYPE_LABELS} from "@/resources/user";
import {calculateAverageLevel} from "@/utils/score"; import {calculateAverageLevel} from "@/utils/score";
import {capitalize} from "lodash"; import {capitalize} from "lodash";
import {ReactElement} from "react"; import {ReactElement} from "react";
@@ -28,7 +29,7 @@ export default function ProfileSummary({user, items}: Props) {
<div className="flex -md:flex-col justify-between w-full gap-8"> <div className="flex -md:flex-col justify-between w-full gap-8">
<div className="flex flex-col gap-2 py-2"> <div className="flex flex-col gap-2 py-2">
<h1 className="font-bold text-2xl md:text-4xl">{user.name}</h1> <h1 className="font-bold text-2xl md:text-4xl">{user.name}</h1>
<h6 className="font-normal text-base text-mti-gray-taupe">{capitalize(user.type)}</h6> <h6 className="font-normal text-base text-mti-gray-taupe">{USER_TYPE_LABELS[user.type]}</h6>
</div> </div>
<ProgressBar <ProgressBar
label={`Level ${calculateAverageLevel(user.levels).toFixed(1)}`} label={`Level ${calculateAverageLevel(user.levels).toFixed(1)}`}

View File

@@ -93,9 +93,9 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
<Nav <Nav
disabled={disableNavigation} disabled={disableNavigation}
Icon={BsShieldFill} Icon={BsShieldFill}
label="Management" label="Settings"
path={path} path={path}
keyPath="/manage" keyPath="/settings"
isMinimized={isMinimized} isMinimized={isMinimized}
/> />
)} )}
@@ -107,7 +107,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={true} /> <Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={true} />
<Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={true} /> <Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={true} />
{userType !== "student" && ( {userType !== "student" && (
<Nav disabled={disableNavigation} Icon={BsShieldFill} label="Management" path={path} keyPath="/manage" isMinimized={true} /> <Nav disabled={disableNavigation} Icon={BsShieldFill} label="Settings" path={path} keyPath="/settings" isMinimized={true} />
)} )}
</div> </div>

View File

@@ -17,6 +17,7 @@ import Input from "./Low/Input";
import ProfileSummary from "./ProfileSummary"; import ProfileSummary from "./ProfileSummary";
import Select from "react-select"; import Select from "react-select";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {USER_TYPE_LABELS} from "@/resources/user";
const expirationDateColor = (date: Date) => { const expirationDateColor = (date: Date) => {
const momentDate = moment(date); const momentDate = moment(date);
@@ -255,12 +256,11 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}:
defaultValue={user.type} defaultValue={user.type}
onChange={(e) => setType(e.target.value as typeof user.type)} onChange={(e) => setType(e.target.value as typeof user.type)}
className="p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer bg-white"> className="p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer bg-white">
<option value="student">Student</option> {Object.keys(USER_TYPE_LABELS).map((type) => (
<option value="teacher">Teacher</option> <option key={type} value={type}>
<option value="corporate">Corporate</option> {USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]}
<option value="agent">Country Agent</option> </option>
<option value="admin">Admin</option> ))}
<option value="developer">Developer</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,7 @@ 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 axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import {capitalize} from "lodash"; import {capitalize} from "lodash";
@@ -17,6 +18,7 @@ export default function BatchCodeGenerator({user}: {user: User}) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [expiryDate, setExpiryDate] = useState<Date | null>(null); const [expiryDate, setExpiryDate] = useState<Date | null>(null);
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student");
const {openFilePicker, filesContent} = useFilePicker({ const {openFilePicker, filesContent} = useFilePicker({
accept: ".txt", accept: ".txt",
@@ -108,37 +110,18 @@ export default function BatchCodeGenerator({user}: {user: User}) {
)} )}
<label className="font-normal text-base text-mti-gray-dim">Select the type of user they should be</label> <label className="font-normal text-base text-mti-gray-dim">Select the type of user they should be</label>
{user && ( {user && (
<div className="grid -md:grid-cols-2 md:grid-cols-1 xl:grid-cols-2 gap-4 place-items-center"> <select
<Button defaultValue="student"
className="w-44 2xl:w-48" onChange={(e) => setType(e.target.value as typeof user.type)}
variant="outline" 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">
onClick={() => generateCode("student")} {Object.keys(USER_TYPE_LABELS).map((type) => (
disabled={emails.length === 0 || isLoading || !PERMISSIONS.generateCode.student.includes(user.type)}> <option key={type} value={type}>
Student {USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]}
</Button> </option>
<Button ))}
className="w-44 2xl:w-48" </select>
variant="outline"
onClick={() => generateCode("teacher")}
disabled={emails.length === 0 || isLoading || !PERMISSIONS.generateCode.teacher.includes(user.type)}>
Teacher
</Button>
<Button
className="w-44 2xl:w-48"
variant="outline"
onClick={() => generateCode("corporate")}
disabled={emails.length === 0 || isLoading || !PERMISSIONS.generateCode.corporate.includes(user.type)}>
Corporate
</Button>
<Button
className="w-44 2xl:w-48"
variant="outline"
onClick={() => generateCode("admin")}
disabled={emails.length === 0 || isLoading || !PERMISSIONS.generateCode.admin.includes(user.type)}>
Admin
</Button>
</div>
)} )}
<Button onClick={() => generateCode(type)}>Generate & Send</Button>
</div> </div>
); );
} }

View File

@@ -2,6 +2,7 @@ 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 axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import {capitalize} from "lodash"; import {capitalize} from "lodash";
@@ -15,6 +16,7 @@ export default function CodeGenerator({user}: {user: User}) {
const [generatedCode, setGeneratedCode] = useState<string>(); const [generatedCode, setGeneratedCode] = useState<string>();
const [expiryDate, setExpiryDate] = useState<Date | null>(null); const [expiryDate, setExpiryDate] = useState<Date | null>(null);
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student");
useEffect(() => { useEffect(() => {
if (user && (user.type === "corporate" || user.type === "teacher")) { if (user && (user.type === "corporate" || user.type === "teacher")) {
@@ -57,36 +59,16 @@ export default function CodeGenerator({user}: {user: User}) {
<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 && ( {user && (
<div className="grid -md:grid-cols-2 md:grid-cols-1 place-items-center 2xl:grid-cols-2 gap-4"> <select
<Button defaultValue="student"
className="w-44 md:w-48" onChange={(e) => setType(e.target.value as typeof user.type)}
variant="outline" 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">
onClick={() => generateCode("student")} {Object.keys(USER_TYPE_LABELS).map((type) => (
disabled={!PERMISSIONS.generateCode.student.includes(user.type) || (isExpiryDateEnabled && expiryDate === null)}> <option key={type} value={type}>
Student {USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS]}
</Button> </option>
<Button ))}
className="w-44 md:w-48" </select>
variant="outline"
onClick={() => generateCode("teacher")}
disabled={!PERMISSIONS.generateCode.teacher.includes(user.type) || (isExpiryDateEnabled && expiryDate === null)}>
Teacher
</Button>
<Button
className="w-44 md:w-48"
variant="outline"
onClick={() => generateCode("corporate")}
disabled={!PERMISSIONS.generateCode.corporate.includes(user.type) || (isExpiryDateEnabled && expiryDate === null)}>
Corporate
</Button>
<Button
className="w-44 md:w-48"
variant="outline"
onClick={() => generateCode("admin")}
disabled={!PERMISSIONS.generateCode.admin.includes(user.type) || (isExpiryDateEnabled && expiryDate === null)}>
Admin
</Button>
</div>
)} )}
{user && (user.type === "developer" || user.type === "admin") && ( {user && (user.type === "developer" || user.type === "admin") && (
<> <>
@@ -111,6 +93,7 @@ export default function CodeGenerator({user}: {user: User}) {
)} )}
</> </>
)} )}
<Button onClick={() => generateCode(type)}>Generate</Button>
<label className="font-normal text-base text-mti-gray-dim">Generated Code:</label> <label className="font-normal text-base text-mti-gray-dim">Generated Code:</label>
<div <div
className={clsx( className={clsx(

View File

@@ -16,6 +16,7 @@ import {countries, TCountries} from "countries-list";
import countryCodes from "country-codes-list"; import countryCodes from "country-codes-list";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import UserCard from "@/components/UserCard"; import UserCard from "@/components/UserCard";
import {USER_TYPE_LABELS} from "@/resources/user";
const columnHelper = createColumnHelper<User>(); const columnHelper = createColumnHelper<User>();
@@ -316,7 +317,7 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us
<SorterArrow name="type" /> <SorterArrow name="type" />
</button> </button>
) as any, ) as any,
cell: (info) => capitalize(info.getValue()), cell: (info) => USER_TYPE_LABELS[info.getValue()],
}), }),
columnHelper.accessor("subscriptionExpirationDate", { columnHelper.accessor("subscriptionExpirationDate", {
header: ( header: (

View File

@@ -50,7 +50,7 @@ export default function Admin() {
return ( return (
<> <>
<Head> <Head>
<title>Management Panel | EnCoach</title> <title>Settings Panel | EnCoach</title>
<meta <meta
name="description" name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
@@ -61,8 +61,8 @@ export default function Admin() {
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<Layout user={user} className="gap-6"> <Layout user={user} className="gap-6">
<section className="w-full flex -md:flex-col -xl:gap-2 gap-8 justify-between"> <section className="w-full flex -lg:flex-col -xl:gap-2 gap-8 justify-between">
{user.email === "tiago.ribeiro@ecrop.dev" ? <ExamGenerator /> : <ExamLoader />} <ExamLoader />
<CodeGenerator user={user} /> <CodeGenerator user={user} />
<BatchCodeGenerator user={user} /> <BatchCodeGenerator user={user} />
</section> </section>

10
src/resources/user.ts Normal file
View File

@@ -0,0 +1,10 @@
import {Type} from "@/interfaces/user";
export const USER_TYPE_LABELS: {[key in Type]: string} = {
student: "Student",
teacher: "Teacher",
corporate: "Corporate",
agent: "Country Manager",
admin: "Admin",
developer: "Developer",
};