Merge branch 'develop' into feature/exam-generation

This commit is contained in:
Tiago Ribeiro
2023-11-21 19:45:30 +00:00
16 changed files with 314 additions and 115 deletions

View File

@@ -44,6 +44,7 @@
"random-words": "^2.0.0", "random-words": "^2.0.0",
"react": "18.2.0", "react": "18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-currency-input-field": "^3.6.12",
"react-datepicker": "^4.18.0", "react-datepicker": "^4.18.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-firebase-hooks": "^5.1.1", "react-firebase-hooks": "^5.1.1",

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}
/> />
)} )}
@@ -117,7 +117,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} />
)} )}
{userType === "developer" && ( {userType === "developer" && (
<Nav disabled={disableNavigation} Icon={BsCloudFill} label="Generation" path={path} keyPath="/generation" isMinimized={true} /> <Nav disabled={disableNavigation} Icon={BsCloudFill} label="Generation" path={path} keyPath="/generation" isMinimized={true} />

View File

@@ -6,7 +6,7 @@ import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import moment from "moment"; import moment from "moment";
import {Divider} from "primereact/divider"; import {Divider} from "primereact/divider";
import {useState} from "react"; import {useEffect, useState} from "react";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import {BsFileEarmarkText, BsPencil, BsStar} from "react-icons/bs"; import {BsFileEarmarkText, BsPencil, BsStar} from "react-icons/bs";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
@@ -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);
@@ -40,15 +41,46 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}:
const [referralAgent, setReferralAgent] = useState(user.corporateInformation?.referralAgent); const [referralAgent, setReferralAgent] = useState(user.corporateInformation?.referralAgent);
const [type, setType] = useState(user.type); const [type, setType] = useState(user.type);
const [status, setStatus] = useState(user.status); const [status, setStatus] = useState(user.status);
const [companyName, setCompanyName] = useState(user.corporateInformation?.companyInformation.name);
const [userAmount, setUserAmount] = useState(user.corporateInformation?.companyInformation.userAmount);
const [referralAgentLabel, setReferralAgentLabel] = useState<string>();
const {stats} = useStats(user.id); const {stats} = useStats(user.id);
const {users} = useUsers(); const {users} = useUsers();
useEffect(() => {
if (users && users.length > 0) {
if (!referralAgent) {
setReferralAgentLabel("No manager");
return;
}
const agent = users.find((x) => x.id === referralAgent);
setReferralAgentLabel(`${agent?.name} - ${agent?.email}`);
}
}, [users, referralAgent]);
const updateUser = () => { const updateUser = () => {
if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return; if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return;
// TODO: Add the corporate information when it is changed as well
axios axios
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {...user, subscriptionExpirationDate: expiryDate, type, status}) .post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
...user,
subscriptionExpirationDate: expiryDate,
type,
status,
corporateInformation:
type === "corporate"
? {
referralAgent,
companyInformation: {
companyName,
userAmount,
},
}
: undefined,
})
.then(() => { .then(() => {
toast.success("User updated successfully!"); toast.success("User updated successfully!");
onClose(true); onClose(true);
@@ -255,12 +287,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>
@@ -269,26 +300,27 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}:
{user.type === "corporate" && ( {user.type === "corporate" && (
<> <>
<Divider className="w-full" /> <Divider className="w-full" />
<div className="flex flex-col md:flex-row gap-8 w-full"> <div className="grid grid-cols-2 gap-8 w-full">
<Input <Input
label="Company Name" label="Company Name"
type="text" type="text"
name="companyName" name="companyName"
onChange={() => null} onChange={setCompanyName}
placeholder="Enter company name" placeholder="Enter company name"
defaultValue={user.corporateInformation?.companyInformation.name} defaultValue={companyName}
/> />
<Input <Input
label="Amount of Users" label="Amount of Users"
type="number" type="number"
name="userAmount" name="userAmount"
onChange={() => null} onChange={(e) => setUserAmount(e ? parseInt(e) : undefined)}
placeholder="Enter amount of users" placeholder="Enter amount of users"
defaultValue={user.corporateInformation?.companyInformation.userAmount} defaultValue={userAmount}
/> />
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Country Agent</label> <label className="font-normal text-base text-mti-gray-dim">Country Manager</label>
{referralAgentLabel && (
<Select <Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none" className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={[ options={[
@@ -297,7 +329,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}:
]} ]}
defaultValue={{ defaultValue={{
value: referralAgent, value: referralAgent,
label: referralAgent ? users.find((u) => u.id === referralAgent)?.name || "" : "No agent", label: referralAgentLabel,
}} }}
onChange={(value) => setReferralAgent(value?.value)} onChange={(value) => setReferralAgent(value?.value)}
styles={{ styles={{
@@ -317,6 +349,11 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}:
}), }),
}} }}
/> />
)}
</div>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Pricing</label>
</div> </div>
</div> </div>
</> </>

179
src/dashboards/Agent.tsx Normal file
View File

@@ -0,0 +1,179 @@
/* eslint-disable @next/next/no-img-element */
import Modal from "@/components/Modal";
import useStats from "@/hooks/useStats";
import useUsers from "@/hooks/useUsers";
import {Group, Stat, User} from "@/interfaces/user";
import UserList from "@/pages/(admin)/Lists/UserList";
import {dateSorter} from "@/utils";
import moment from "moment";
import {useEffect, useState} from "react";
import {
BsArrowLeft,
BsClipboard2Data,
BsClipboard2DataFill,
BsClock,
BsGlobeCentralSouthAsia,
BsPaperclip,
BsPerson,
BsPersonAdd,
BsPersonFill,
BsPersonFillGear,
BsPersonGear,
BsPersonLinesFill,
} from "react-icons/bs";
import UserCard from "@/components/UserCard";
import useGroups from "@/hooks/useGroups";
import {calculateAverageLevel, calculateBandScore} from "@/utils/score";
import {MODULE_ARRAY} from "@/utils/moduleUtils";
import {Module} from "@/interfaces";
import {groupByExam} from "@/utils/stats";
import IconCard from "./IconCard";
import GroupList from "@/pages/(admin)/Lists/GroupList";
interface Props {
user: User;
}
export default function AgentDashboard({user}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const {stats} = useStats();
const {users, reload} = useUsers();
const {groups} = useGroups(user.id);
useEffect(() => {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
const corporateFilter = (user: User) => user.type === "corporate";
const referredCorporateFilter = (x: User) =>
x.type === "corporate" && !!x.corporateInformation && x.corporateInformation.referralAgent === user.id;
const UserDisplay = (displayUser: User) => (
<div
onClick={() => setSelectedUser(displayUser)}
className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
const ReferredCorporateList = () => {
const filter = (x: User) => x.type === "corporate" && !!x.corporateInformation && x.corporateInformation.referralAgent === user.id;
return (
<>
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Referred Corporate ({users.filter(filter).length})</h2>
</div>
<UserList user={user} filter={filter} />
</>
);
};
const CorporateList = () => {
const filter = (x: User) => x.type === "corporate";
return (
<>
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Referred Corporate ({users.filter(filter).length})</h2>
</div>
<UserList user={user} filter={filter} />
</>
);
};
const DefaultDashboard = () => (
<>
<section className="flex flex-wrap gap-2 items-center -lg:justify-center lg:gap-4 text-center">
<IconCard
onClick={() => setPage("referredCorporate")}
Icon={BsPersonFill}
label="Referred Corporate"
value={users.filter(referredCorporateFilter).length}
color="purple"
/>
<IconCard
onClick={() => setPage("corporate")}
Icon={BsPersonFill}
label="Corporate"
value={users.filter(corporateFilter).length}
color="purple"
/>
</section>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest Referred Corporate</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users
.filter(referredCorporateFilter)
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => (
<UserDisplay key={x.id} {...x} />
))}
</div>
</div>
<div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest corporate</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users
.filter(corporateFilter)
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => (
<UserDisplay key={x.id} {...x} />
))}
</div>
</div>
</section>
</>
);
return (
<>
<Modal isOpen={showModal} onClose={() => setSelectedUser(undefined)}>
<>
{selectedUser && (
<div className="w-full flex flex-col gap-8">
<UserCard
loggedInUser={user}
onClose={(shouldReload) => {
setSelectedUser(undefined);
if (shouldReload) reload();
}}
onViewStudents={
selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => setPage("students") : undefined
}
onViewTeachers={selectedUser.type === "corporate" ? () => setPage("teachers") : undefined}
user={selectedUser}
/>
</div>
)}
</>
</Modal>
{page === "referredCorporate" && <ReferredCorporateList />}
{page === "corporate" && <CorporateList />}
{page === "" && <DefaultDashboard />}
</>
);
}

View File

@@ -26,9 +26,7 @@ export interface CorporateInformation {
value: number; value: number;
currency: string; currency: string;
}; };
monthlyDuration: number;
referralAgent?: string; referralAgent?: string;
allowedUserAmount?: number;
} }
export interface CompanyInformation { export interface CompanyInformation {

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>();
@@ -159,7 +160,7 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us
onClick={() => updateAccountType(row.original, "corporate")} onClick={() => updateAccountType(row.original, "corporate")}
className="text-sm !py-2 !px-4" className="text-sm !py-2 !px-4"
disabled={row.original.type === "corporate" || !PERMISSIONS.generateCode["corporate"].includes(user.type)}> disabled={row.original.type === "corporate" || !PERMISSIONS.generateCode["corporate"].includes(user.type)}>
Admin Corporate
</Button> </Button>
<Button <Button
onClick={() => updateAccountType(row.original, "admin")} onClick={() => updateAccountType(row.original, "admin")}
@@ -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

@@ -68,8 +68,6 @@ export default function RegisterCorporate({isLoading, setIsLoading, mutateUser,
userAmount: companyUsers, userAmount: companyUsers,
}, },
referralAgent, referralAgent,
allowedUserAmount: companyUsers,
monthlyDuration: subscriptionDuration,
}, },
}) })
.then((response) => { .then((response) => {

View File

@@ -67,6 +67,7 @@ async function registerIndividual(req: NextApiRequest, res: NextApiResponse) {
type: codeData.type, type: codeData.type,
subscriptionExpirationDate: codeData.expiryDate, subscriptionExpirationDate: codeData.expiryDate,
registrationDate: new Date(), registrationDate: new Date(),
status: code ? "active" : "paymentDue",
}; };
await setDoc(doc(db, "users", userId), user); await setDoc(doc(db, "users", userId), user);

View File

@@ -26,6 +26,7 @@ import StudentDashboard from "@/dashboards/Student";
import AdminDashboard from "@/dashboards/Admin"; import AdminDashboard from "@/dashboards/Admin";
import CorporateDashboard from "@/dashboards/Corporate"; import CorporateDashboard from "@/dashboards/Corporate";
import TeacherDashboard from "@/dashboards/Teacher"; import TeacherDashboard from "@/dashboards/Teacher";
import AgentDashboard from "@/dashboards/Agent";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -166,6 +167,7 @@ export default function Home() {
{user.type === "student" && <StudentDashboard user={user} />} {user.type === "student" && <StudentDashboard user={user} />}
{user.type === "teacher" && <TeacherDashboard user={user} />} {user.type === "teacher" && <TeacherDashboard user={user} />}
{user.type === "corporate" && <CorporateDashboard user={user} />} {user.type === "corporate" && <CorporateDashboard user={user} />}
{user.type === "agent" && <AgentDashboard user={user} />}
{user.type === "admin" && <AdminDashboard user={user} />} {user.type === "admin" && <AdminDashboard user={user} />}
{user.type === "developer" && <AdminDashboard user={user} />} {user.type === "developer" && <AdminDashboard user={user} />}
</Layout> </Layout>

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."

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",
};

View File

@@ -4431,6 +4431,11 @@ react-chartjs-2@^5.2.0:
resolved "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz" resolved "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz"
integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA== integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==
react-currency-input-field@^3.6.12:
version "3.6.12"
resolved "https://registry.yarnpkg.com/react-currency-input-field/-/react-currency-input-field-3.6.12.tgz#6c59bec50b9a769459c971f94f9a67b7bf9046f7"
integrity sha512-92mVEo1u7tF8Lz5JeaEHpQY/p6ulmnfSk9r3dVMyykQNLoScvgQ7GczvV3uGDr81xkTF3czj7CTJ9Ekqq4+pIA==
react-datepicker@^4.18.0: react-datepicker@^4.18.0:
version "4.18.0" version "4.18.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.18.0.tgz#d66301acc47833d31fa6f46f98781b084106da0e" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.18.0.tgz#d66301acc47833d31fa6f46f98781b084106da0e"