From 19d16c9ceff1e506c248c9defa7a1eeea9994d0c Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Wed, 24 Jul 2024 18:52:02 +0100 Subject: [PATCH 1/4] Added new permission system --- src/components/High/Layout.tsx | 3 +- src/components/Sidebar.tsx | 548 ++++++++++++++++--------- src/interfaces/permissions.ts | 49 +++ src/interfaces/user.ts | 2 + src/pages/api/permissions/[id].ts | 30 ++ src/pages/api/permissions/bootstrap.ts | 43 ++ src/pages/api/permissions/index.ts | 36 ++ src/pages/api/user.ts | 196 +++++---- src/pages/index.tsx | 1 - src/pages/permissions/[id].tsx | 190 +++++++++ src/pages/permissions/index.tsx | 94 +++++ src/utils/permissions.be.ts | 83 ++++ src/utils/permissions.ts | 45 ++ src/utils/users.be.ts | 14 + 14 files changed, 1071 insertions(+), 263 deletions(-) create mode 100644 src/interfaces/permissions.ts create mode 100644 src/pages/api/permissions/[id].ts create mode 100644 src/pages/api/permissions/bootstrap.ts create mode 100644 src/pages/api/permissions/index.ts create mode 100644 src/pages/permissions/[id].tsx create mode 100644 src/pages/permissions/index.tsx create mode 100644 src/utils/permissions.be.ts create mode 100644 src/utils/permissions.ts create mode 100644 src/utils/users.be.ts diff --git a/src/components/High/Layout.tsx b/src/components/High/Layout.tsx index 79eb2d2a..38a971db 100644 --- a/src/components/High/Layout.tsx +++ b/src/components/High/Layout.tsx @@ -33,8 +33,7 @@ export default function Layout({user, children, className, navDisabled = false, focusMode={focusMode} onFocusLayerMouseEnter={onFocusLayerMouseEnter} className="-md:hidden" - userType={user.type} - userId={user.id} + user={user} />
void; - className?: string; - userType?: Type; - userId?: string; + path: string; + navDisabled?: boolean; + focusMode?: boolean; + onFocusLayerMouseEnter?: () => void; + className?: string; + user: User; } interface NavProps { - Icon: IconType; - label: string; - path: string; - keyPath: string; - disabled?: boolean; - isMinimized?: boolean; - badge?: number; + Icon: IconType; + label: string; + path: string; + keyPath: string; + disabled?: boolean; + isMinimized?: boolean; + badge?: number; } -const Nav = ({Icon, label, path, keyPath, disabled = false, isMinimized = false, badge}: NavProps) => { - return ( - - - {!isMinimized && {label}} - {!!badge && badge > 0 && ( -
- {badge} -
- )} - - ); +const Nav = ({ + Icon, + label, + path, + keyPath, + disabled = false, + isMinimized = false, + badge, +}: NavProps) => { + return ( + + + {!isMinimized && {label}} + {!!badge && badge > 0 && ( +
+ {badge} +
+ )} + + ); }; -export default function Sidebar({path, navDisabled = false, focusMode = false, userType, onFocusLayerMouseEnter, className, userId}: Props) { - const router = useRouter(); +export default function Sidebar({ + path, + navDisabled = false, + focusMode = false, + user, + onFocusLayerMouseEnter, + className, +}: Props) { + const router = useRouter(); - const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]); + const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [ + state.isSidebarMinimized, + state.toggleSidebarMinimized, + ]); - const {totalAssignedTickets} = useTicketsListener(userId); + const { totalAssignedTickets } = useTicketsListener(user.id); - const logout = async () => { - axios.post("/api/logout").finally(() => { - setTimeout(() => router.reload(), 500); - }); - }; + const logout = async () => { + axios.post("/api/logout").finally(() => { + setTimeout(() => router.reload(), 500); + }); + }; - const disableNavigation = preventNavigation(navDisabled, focusMode); + const disableNavigation = preventNavigation(navDisabled, focusMode); - return ( -
-
-
-
-
+ return ( +
+
+
+
+
-
-
- {isMinimized ? : } - {!isMinimized && Minimize} -
-
{} : logout} - className={clsx( - "hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out", - isMinimized ? "w-fit" : "w-full min-w-[250px] px-8", - )}> - - {!isMinimized && Log Out} -
-
- {focusMode && } -
- ); +
+
+ {isMinimized ? ( + + ) : ( + + )} + {!isMinimized && ( + Minimize + )} +
+
{} : logout} + className={clsx( + "hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out", + isMinimized ? "w-fit" : "w-full min-w-[250px] px-8" + )} + > + + {!isMinimized && ( + Log Out + )} +
+
+ {focusMode && ( + + )} +
+ ); } diff --git a/src/interfaces/permissions.ts b/src/interfaces/permissions.ts new file mode 100644 index 00000000..2e448af5 --- /dev/null +++ b/src/interfaces/permissions.ts @@ -0,0 +1,49 @@ +export const markets = ["au", "br", "de"] as const; + +export const permissions = [ + // generate codes are basicly invites + "createCodeStudent", + "createCodeTeacher", + "createCodeCorporate", + "createCodeCountryManager", + "createCodeAdmin", + // exams + "createReadingExam", + "createListeningExam", + "createWritingExam", + "createSpeakingExam", + "createLevelExam", + // view pages + "viewExams", + "viewExercises", + "viewRecords", + "viewStats", + "viewTickets", + "viewPaymentRecords", + // view data + "viewStudent", + "viewTeacher", + "viewCorporate", + "viewCountryManager", + "viewAdmin", + // edit data + "editStudent", + "editTeacher", + "editCorporate", + "editCountryManager", + "editAdmin", + // delete data + "deleteStudent", + "deleteTeacher", + "deleteCorporate", + "deleteCountryManager", + "deleteAdmin", +] as const; + +export type PermissionType = (typeof permissions)[keyof typeof permissions]; + +export interface Permission { + id: string; + type: PermissionType; + users: string[]; +} diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index 542b36bf..ddda1ccb 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -1,5 +1,6 @@ import { Module } from "."; import { InstructorGender } from "./exam"; +import { PermissionType } from "./permissions"; export type User = | StudentUser @@ -26,6 +27,7 @@ export interface BasicUser { subscriptionExpirationDate?: null | Date; registrationDate?: Date; status: UserStatus; + permissions: PermissionType[], } export interface StudentUser extends BasicUser { diff --git a/src/pages/api/permissions/[id].ts b/src/pages/api/permissions/[id].ts new file mode 100644 index 00000000..7c379719 --- /dev/null +++ b/src/pages/api/permissions/[id].ts @@ -0,0 +1,30 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; +import { app } from "@/firebase"; +import { getFirestore, doc, setDoc } from "firebase/firestore"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; + +const db = getFirestore(app); + +export default withIronSessionApiRoute(handler, sessionOptions); + +async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === "PATCH") return patch(req, res); +} + +async function patch(req: NextApiRequest, res: NextApiResponse) { + if (!req.session.user) { + res.status(401).json({ ok: false }); + return; + } + const { id } = req.query as { id: string }; + const { users } = req.body; + try { + await setDoc(doc(db, "permissions", id), { users }, { merge: true }); + return res.status(200).json({ ok: true }); + } catch (err) { + console.error(err); + return res.status(500).json({ ok: false }); + } +} diff --git a/src/pages/api/permissions/bootstrap.ts b/src/pages/api/permissions/bootstrap.ts new file mode 100644 index 00000000..cd36b52a --- /dev/null +++ b/src/pages/api/permissions/bootstrap.ts @@ -0,0 +1,43 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; +import { app } from "@/firebase"; +import { + getFirestore, + collection, + getDocs, + query, + where, + doc, + setDoc, + addDoc, + getDoc, + deleteDoc, +} from "firebase/firestore"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { Permission } from "@/interfaces/permissions"; +import { bootstrap } from "@/utils/permissions.be"; + +const db = getFirestore(app); + +export default withIronSessionApiRoute(handler, sessionOptions); + +async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === "GET") return get(req, res); +} + +async function get(req: NextApiRequest, res: NextApiResponse) { + if (!req.session.user) { + res.status(401).json({ ok: false }); + return; + } + + console.log("Boostrap"); + try { + await bootstrap(); + return res.status(200).json({ ok: true }); + } catch (err) { + console.error("Failed to update permissions", err); + return res.status(500).json({ ok: false }); + } +} diff --git a/src/pages/api/permissions/index.ts b/src/pages/api/permissions/index.ts new file mode 100644 index 00000000..3f9fbfac --- /dev/null +++ b/src/pages/api/permissions/index.ts @@ -0,0 +1,36 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; +import { app } from "@/firebase"; +import { + getFirestore, + collection, + getDocs, + query, + where, + doc, + setDoc, + addDoc, + getDoc, + deleteDoc, +} from "firebase/firestore"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { Permission } from "@/interfaces/permissions"; +import { getPermissions, getPermissionDocs } from "@/utils/permissions.be"; + +const db = getFirestore(app); + +export default withIronSessionApiRoute(handler, sessionOptions); + +async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === "GET") return get(req, res); +} + +async function get(req: NextApiRequest, res: NextApiResponse) { + if (!req.session.user) { + res.status(401).json({ ok: false }); + return; + } + const docs = await getPermissionDocs(); + res.status(200).json(docs); +} diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts index 921c0222..88925234 100644 --- a/src/pages/api/user.ts +++ b/src/pages/api/user.ts @@ -1,11 +1,22 @@ -import {PERMISSIONS} from "@/constants/userPermissions"; -import {app, adminApp} from "@/firebase"; -import {Group, User} from "@/interfaces/user"; -import {sessionOptions} from "@/lib/session"; -import {collection, deleteDoc, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore"; -import {getAuth} from "firebase-admin/auth"; -import {withIronSessionApiRoute} from "iron-session/next"; -import {NextApiRequest, NextApiResponse} from "next"; +import { PERMISSIONS } from "@/constants/userPermissions"; +import { app, adminApp } from "@/firebase"; +import { Group, User } from "@/interfaces/user"; +import { sessionOptions } from "@/lib/session"; +import { + collection, + deleteDoc, + doc, + getDoc, + getDocs, + getFirestore, + query, + setDoc, + where, +} from "firebase/firestore"; +import { getAuth } from "firebase-admin/auth"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { NextApiRequest, NextApiResponse } from "next"; +import { getPermissions, getPermissionDocs } from "@/utils/permissions.be"; const db = getFirestore(app); const auth = getAuth(adminApp); @@ -13,89 +24,132 @@ const auth = getAuth(adminApp); export default withIronSessionApiRoute(user, sessionOptions); async function user(req: NextApiRequest, res: NextApiResponse) { - if (req.method === "GET") return get(req, res); - if (req.method === "DELETE") return del(req, res); + if (req.method === "GET") return get(req, res); + if (req.method === "DELETE") return del(req, res); - res.status(404).json(undefined); + res.status(404).json(undefined); } async function del(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ok: false}); - return; - } + if (!req.session.user) { + res.status(401).json({ ok: false }); + return; + } - const {id} = req.query as {id: string}; + const { id } = req.query as { id: string }; - const docUser = await getDoc(doc(db, "users", req.session.user.id)); - if (!docUser.exists()) { - res.status(401).json({ok: false}); - return; - } + const docUser = await getDoc(doc(db, "users", req.session.user.id)); + if (!docUser.exists()) { + res.status(401).json({ ok: false }); + return; + } - const user = docUser.data() as User; + const user = docUser.data() as User; - const docTargetUser = await getDoc(doc(db, "users", id)); - if (!docTargetUser.exists()) { - res.status(404).json({ok: false}); - return; - } + const docTargetUser = await getDoc(doc(db, "users", id)); + if (!docTargetUser.exists()) { + res.status(404).json({ ok: false }); + return; + } - const targetUser = {...docTargetUser.data(), id: docTargetUser.id} as User; + const targetUser = { ...docTargetUser.data(), id: docTargetUser.id } as User; - if (user.type === "corporate" && (targetUser.type === "student" || targetUser.type === "teacher")) { - res.json({ok: true}); + if ( + user.type === "corporate" && + (targetUser.type === "student" || targetUser.type === "teacher") + ) { + res.json({ ok: true }); - const userParticipantGroup = await getDocs(query(collection(db, "groups"), where("participants", "array-contains", id))); - await Promise.all([ - ...userParticipantGroup.docs - .filter((x) => (x.data() as Group).admin === user.id) - .map(async (x) => await setDoc(x.ref, {participants: x.data().participants.filter((y: string) => y !== id)}, {merge: true})), - ]); + const userParticipantGroup = await getDocs( + query( + collection(db, "groups"), + where("participants", "array-contains", id) + ) + ); + await Promise.all([ + ...userParticipantGroup.docs + .filter((x) => (x.data() as Group).admin === user.id) + .map( + async (x) => + await setDoc( + x.ref, + { + participants: x + .data() + .participants.filter((y: string) => y !== id), + }, + { merge: true } + ) + ), + ]); - return; - } + return; + } - const permission = PERMISSIONS.deleteUser[targetUser.type]; - if (!permission.includes(user.type)) { - res.status(403).json({ok: false}); - return; - } + const permission = PERMISSIONS.deleteUser[targetUser.type]; + if (!permission.includes(user.type)) { + res.status(403).json({ ok: false }); + return; + } - res.json({ok: true}); + res.json({ ok: true }); - await auth.deleteUser(id); - await deleteDoc(doc(db, "users", id)); - const userCodeDocs = await getDocs(query(collection(db, "codes"), where("userId", "==", id))); - const userParticipantGroup = await getDocs(query(collection(db, "groups"), where("participants", "array-contains", id))); - const userGroupAdminDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id))); - const userStatsDocs = await getDocs(query(collection(db, "stats"), where("user", "==", id))); + await auth.deleteUser(id); + await deleteDoc(doc(db, "users", id)); + const userCodeDocs = await getDocs( + query(collection(db, "codes"), where("userId", "==", id)) + ); + const userParticipantGroup = await getDocs( + query(collection(db, "groups"), where("participants", "array-contains", id)) + ); + const userGroupAdminDocs = await getDocs( + query(collection(db, "groups"), where("admin", "==", id)) + ); + const userStatsDocs = await getDocs( + query(collection(db, "stats"), where("user", "==", id)) + ); - await Promise.all([ - ...userCodeDocs.docs.map(async (x) => await deleteDoc(x.ref)), - ...userGroupAdminDocs.docs.map(async (x) => await deleteDoc(x.ref)), - ...userStatsDocs.docs.map(async (x) => await deleteDoc(x.ref)), - ...userParticipantGroup.docs.map( - async (x) => await setDoc(x.ref, {participants: x.data().participants.filter((y: string) => y !== id)}, {merge: true}), - ), - ]); + await Promise.all([ + ...userCodeDocs.docs.map(async (x) => await deleteDoc(x.ref)), + ...userGroupAdminDocs.docs.map(async (x) => await deleteDoc(x.ref)), + ...userStatsDocs.docs.map(async (x) => await deleteDoc(x.ref)), + ...userParticipantGroup.docs.map( + async (x) => + await setDoc( + x.ref, + { + participants: x.data().participants.filter((y: string) => y !== id), + }, + { merge: true } + ) + ), + ]); } async function get(req: NextApiRequest, res: NextApiResponse) { - if (req.session.user) { - const docUser = await getDoc(doc(db, "users", req.session.user.id)); - if (!docUser.exists()) { - res.status(401).json(undefined); - return; - } + if (req.session.user) { + const docUser = await getDoc(doc(db, "users", req.session.user.id)); + if (!docUser.exists()) { + res.status(401).json(undefined); + return; + } - const user = docUser.data() as User; + const user = docUser.data() as User; + + const permissionDocs = await getPermissionDocs(); - req.session.user = {...user, id: req.session.user.id}; - await req.session.save(); + const userWithPermissions = { + ...user, + permissions: getPermissions(req.session.user.id, permissionDocs), + }; + req.session.user = { + ...userWithPermissions, + id: req.session.user.id, + }; + await req.session.save(); - res.json({...user, id: req.session.user.id}); - } else { - res.status(401).json(undefined); - } + res.json({ ...userWithPermissions, id: req.session.user.id }); + } else { + res.status(401).json(undefined); + } } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 601d4838..0ef4fea7 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -8,7 +8,6 @@ import {useEffect, useState} from "react"; import useStats from "@/hooks/useStats"; import {averageScore, groupBySession, totalExams} from "@/utils/stats"; import useUser from "@/hooks/useUser"; -import Sidebar from "@/components/Sidebar"; import Diagnostic from "@/components/Diagnostic"; import {ToastContainer} from "react-toastify"; import {capitalize} from "lodash"; diff --git a/src/pages/permissions/[id].tsx b/src/pages/permissions/[id].tsx new file mode 100644 index 00000000..093c946b --- /dev/null +++ b/src/pages/permissions/[id].tsx @@ -0,0 +1,190 @@ +/* eslint-disable @next/next/no-img-element */ +import Head from "next/head"; +import { useState } from "react"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { Permission, PermissionType } from "@/interfaces/permissions"; +import { getPermissionDoc } from "@/utils/permissions.be"; +import { User } from "@/interfaces/user"; +import Layout from "@/components/High/Layout"; +import { getUsers } from "@/utils/users.be"; +import { BsTrash } from "react-icons/bs"; +import Select from "@/components/Low/Select"; +import Button from "@/components/Low/Button"; +import axios from "axios"; +import { toast, ToastContainer } from "react-toastify"; + +interface BasicUser { + id: string; + name: string; +} + +interface PermissionWithBasicUsers { + id: string; + type: PermissionType; + users: BasicUser[]; +} + +export const getServerSideProps = withIronSessionSsr(async (context) => { + const { req, params } = context; + const user = req.session.user; + + if (!user || !user.isVerified) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + if (shouldRedirectHome(user)) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + + if (!params?.id) { + return { + redirect: { + destination: "/permissions", + permanent: false, + }, + }; + } + + // Fetch data from external API + const permission: Permission = await getPermissionDoc(params.id as string); + + const allUserData: User[] = await getUsers(); + const users = allUserData.map((u) => ({ + id: u.id, + name: u.name, + })) as BasicUser[]; + + // const res = await fetch("api/permissions"); + // const permissions: Permission[] = await res.json(); + // Pass data to the page via props + const usersData: BasicUser[] = permission.users.reduce( + (acc: BasicUser[], userId) => { + const user = users.find((u) => u.id === userId) as BasicUser; + if (user) { + acc.push(user); + } + return acc; + }, + [] + ); + + return { + props: { + // permissions: permissions.map((p) => ({ id: p.id, type: p.type })), + permission: { + ...permission, + id: params.id, + users: usersData, + }, + user: req.session.user, + users, + }, + }; +}, sessionOptions); + +interface Props { + permission: PermissionWithBasicUsers; + user: User; + users: BasicUser[]; +} + +export default function Page(props: Props) { + console.log("Props", props); + + const { permission, user, users } = props; + + const [selectedUsers, setSelectedUsers] = useState(() => + permission.users.map((u) => u.id) + ); + + const onChange = (value: any) => { + console.log("value", value); + setSelectedUsers((prev) => { + if (value?.value) { + return [...prev, value?.value]; + } + return prev; + }); + }; + const removeUser = (id: string) => { + setSelectedUsers((prev) => prev.filter((u) => u !== id)); + }; + + const update = async () => { + console.log("update", selectedUsers); + try { + await axios.patch(`/api/permissions/${permission.id}`, { + users: selectedUsers, + }); + toast.success("Permission updated"); + } catch (err) { + toast.error("Failed to update permission"); + } + }; + + return ( + <> + + EnCoach + + + + + + +

+ Permission: {permission.type as string} +

+
+ - - -
- - - )} - {user.type === "corporate" && ( - <> -
- - setUserAmount(e ? parseInt(e) : undefined)} - placeholder="Enter number of users" - defaultValue={userAmount} - disabled={disabled} - /> - setMonthlyDuration(e ? parseInt(e) : undefined)} - placeholder="Enter monthly duration" - defaultValue={monthlyDuration} - disabled={disabled} - /> -
- -
- setPaymentValue(e ? parseInt(e) : undefined)} - type="number" - defaultValue={paymentValue || 0} - className="col-span-3" - disabled={disabled} - /> - u.type === "agent") - .map((x) => ({ - value: x.id, - label: `${x.name} - ${x.email}`, - })), - ]} - defaultValue={{ - value: referralAgent, - label: referralAgentLabel, - }} - menuPortalTarget={document?.body} - onChange={(value) => setReferralAgent(value?.value)} - styles={{ - menuPortal: (base) => ({...base, zIndex: 9999}), - control: (styles) => ({ - ...styles, - paddingLeft: "4px", - border: "none", - outline: "none", - ":focus": { - outline: "none", - }, - }), - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - // editing country manager should only be available for dev/admin - isDisabled={!["developer", "admin"].includes(loggedInUser.type) || disabledFields.countryManager} - /> - )} -
-
- {referralAgent !== "" && loggedInUser.type !== "corporate" ? ( - <> - - setCommission(e ? parseInt(e) : undefined)} - type="number" - defaultValue={commissionValue || 0} - className="col-span-3" - disabled={disabled || loggedInUser.type === "agent"} - /> - - ) : ( -
- )} -
-
- - - )} -
-
- null} - placeholder="Enter your name" - defaultValue={user.name} - disabled - /> - null} - placeholder="Enter email address" - defaultValue={user.email} - disabled - /> -
+ {user.type === "agent" && ( + <> +
+ + + +
+ + + )} + {user.type === "corporate" && ( + <> +
+ + setUserAmount(e ? parseInt(e) : undefined)} + placeholder="Enter number of users" + defaultValue={userAmount} + disabled={disabled} + /> + setMonthlyDuration(e ? parseInt(e) : undefined)} + placeholder="Enter monthly duration" + defaultValue={monthlyDuration} + disabled={disabled} + /> +
+ +
+ setPaymentValue(e ? parseInt(e) : undefined)} + type="number" + defaultValue={paymentValue || 0} + className="col-span-3" + disabled={disabled} + /> + u.type === "agent") + .map((x) => ({ + value: x.id, + label: `${x.name} - ${x.email}`, + })), + ]} + defaultValue={{ + value: referralAgent, + label: referralAgentLabel, + }} + menuPortalTarget={document?.body} + onChange={(value) => setReferralAgent(value?.value)} + styles={{ + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused + ? "#D5D9F0" + : state.isSelected + ? "#7872BF" + : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + // editing country manager should only be available for dev/admin + isDisabled={ + checkAccess( + loggedInUser, + getTypesOfUser(["developer", "admin"]) + ) || disabledFields.countryManager + } + /> + )} +
+
+ {referralAgent !== "" && loggedInUser.type !== "corporate" ? ( + <> + + setCommission(e ? parseInt(e) : undefined)} + type="number" + defaultValue={commissionValue || 0} + className="col-span-3" + disabled={disabled || loggedInUser.type === "agent"} + /> + + ) : ( +
+ )} +
+
+ + + )} +
+
+ null} + placeholder="Enter your name" + defaultValue={user.name} + disabled + /> + null} + placeholder="Enter email address" + defaultValue={user.email} + disabled + /> +
-
-
- - -
- null} - placeholder="Enter phone number" - defaultValue={user.demographicInformation?.phone} - disabled - /> -
+
+
+ + +
+ null} + placeholder="Enter phone number" + defaultValue={user.demographicInformation?.phone} + disabled + /> +
- {user.type === "student" && ( - null} - placeholder="Enter National ID or Passport number" - value={user.type === "student" ? user.demographicInformation?.passport_id : undefined} - disabled - required - /> - )} + {user.type === "student" && ( + null} + placeholder="Enter National ID or Passport number" + value={ + user.type === "student" + ? user.demographicInformation?.passport_id + : undefined + } + disabled + required + /> + )} -
- {user.type !== "corporate" && user.type !== 'mastercorporate' && ( -
- - - {EMPLOYMENT_STATUS.map(({status, label}) => ( - - {({checked}) => ( - - {label} - - )} - - ))} - -
- )} - {user.type === "corporate" && ( - - )} -
-
- - - - {({checked}) => ( - - Male - - )} - - - {({checked}) => ( - - Female - - )} - - - {({checked}) => ( - - Other - - )} - - -
-
-
- - setExpiryDate(checked ? user.subscriptionExpirationDate || new Date() : null)} - disabled={disabled}> - Enabled - -
- {!expiryDate && ( -
- {!expiryDate && "Unlimited"} - {expiryDate && moment(expiryDate).format("DD/MM/YYYY")} -
- )} - {expiryDate && ( - - moment(date).isAfter(new Date()) && - (loggedInUser.subscriptionExpirationDate - ? moment(date).isBefore(moment(loggedInUser.subscriptionExpirationDate)) - : true) - } - dateFormat="dd/MM/yyyy" - selected={moment(expiryDate).toDate()} - onChange={(date) => setExpiryDate(date)} - disabled={disabled} - /> - )} -
-
-
- {(loggedInUser.type === "developer" || loggedInUser.type === "admin") && ( - <> - -
-
- - o.value === type)} - onChange={(value) => setType(value?.value as typeof user.type)} - styles={{ - control: (styles) => ({ - ...styles, - paddingLeft: "4px", - border: "none", - outline: "none", - ":focus": { - outline: "none", - }, - }), - menuPortal: (base) => ({...base, zIndex: 9999}), - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - isDisabled={disabled} - /> -
-
- - )} -
+
+ {user.type !== "corporate" && user.type !== "mastercorporate" && ( +
+ + + {EMPLOYMENT_STATUS.map(({ status, label }) => ( + + {({ checked }) => ( + + {label} + + )} + + ))} + +
+ )} + {user.type === "corporate" && ( + + )} +
+
+ + + + {({ checked }) => ( + + Male + + )} + + + {({ checked }) => ( + + Female + + )} + + + {({ checked }) => ( + + Other + + )} + + +
+
+
+ + + setExpiryDate( + checked + ? user.subscriptionExpirationDate || new Date() + : null + ) + } + disabled={disabled} + > + Enabled + +
+ {!expiryDate && ( +
+ {!expiryDate && "Unlimited"} + {expiryDate && moment(expiryDate).format("DD/MM/YYYY")} +
+ )} + {expiryDate && ( + + moment(date).isAfter(new Date()) && + (loggedInUser.subscriptionExpirationDate + ? moment(date).isBefore( + moment(loggedInUser.subscriptionExpirationDate) + ) + : true) + } + dateFormat="dd/MM/yyyy" + selected={moment(expiryDate).toDate()} + onChange={(date) => setExpiryDate(date)} + disabled={disabled} + /> + )} +
+
+
+ {checkAccess(loggedInUser, ["developer", "admin"]) && ( + <> + +
+
+ + o.value === type)} + onChange={(value) => + setType(value?.value as typeof user.type) + } + styles={{ + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused + ? "#D5D9F0" + : state.isSelected + ? "#7872BF" + : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + isDisabled={disabled} + /> +
+
+ + )} +
-
-
- {onViewCorporate && ["student", "teacher"].includes(user.type) && ( - - )} - {onViewStudents && ["corporate", "teacher"].includes(user.type) && ( - - )} - {onViewTeachers && ["student", "corporate"].includes(user.type) && ( - - )} -
-
- - -
-
- - ); +
+
+ {onViewCorporate && ["student", "teacher"].includes(user.type) && ( + + )} + {onViewStudents && ["corporate", "teacher"].includes(user.type) && ( + + )} + {onViewTeachers && ["student", "corporate"].includes(user.type) && ( + + )} +
+
+ + +
+
+ + ); }; export default UserCard; diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx index b52f6ef6..c2a0de83 100644 --- a/src/pages/generation.tsx +++ b/src/pages/generation.tsx @@ -1,19 +1,19 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; import useUser from "@/hooks/useUser"; -import {toast, ToastContainer} from "react-toastify"; +import { toast, ToastContainer } from "react-toastify"; import Layout from "@/components/High/Layout"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; -import {useState} from "react"; -import {Module} from "@/interfaces"; -import {RadioGroup, Tab} from "@headlessui/react"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { useState } from "react"; +import { Module } from "@/interfaces"; +import { RadioGroup, Tab } from "@headlessui/react"; import clsx from "clsx"; -import {MODULE_ARRAY} from "@/utils/moduleUtils"; -import {capitalize} from "lodash"; +import { MODULE_ARRAY } from "@/utils/moduleUtils"; +import { capitalize } from "lodash"; import Button from "@/components/Low/Button"; -import {Exercise, ReadingPart} from "@/interfaces/exam"; +import { Exercise, ReadingPart } from "@/interfaces/exam"; import Input from "@/components/Low/Input"; import axios from "axios"; import ReadingGeneration from "./(generation)/ReadingGeneration"; @@ -21,101 +21,109 @@ import ListeningGeneration from "./(generation)/ListeningGeneration"; import WritingGeneration from "./(generation)/WritingGeneration"; import LevelGeneration from "./(generation)/LevelGeneration"; import SpeakingGeneration from "./(generation)/SpeakingGeneration"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { - const user = req.session.user; +export const getServerSideProps = withIronSessionSsr(({ req, res }) => { + const user = req.session.user; - if (!user || !user.isVerified) { - return { - redirect: { - destination: "/login", - permanent: false, - } - }; - } + if (!user || !user.isVerified) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } - if (shouldRedirectHome(user) || user.type !== "developer") { - return { - redirect: { - destination: "/", - permanent: false, - } - }; - } + if ( + shouldRedirectHome(user) || + checkAccess(user, getTypesOfUser(["developer"])) + ) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } - return { - props: {user: req.session.user}, - }; + return { + props: { user: req.session.user }, + }; }, sessionOptions); export default function Generation() { - const [module, setModule] = useState("reading"); + const [module, setModule] = useState("reading"); - const {user} = useUser({redirectTo: "/login"}); + const { user } = useUser({ redirectTo: "/login" }); - return ( - <> - - Exam Generation | EnCoach - - - - - - {user && ( - -

Exam Generation

-
- - - {[...MODULE_ARRAY].map((x) => ( - - {({checked}) => ( - - {capitalize(x)} - - )} - - ))} - -
- {module === "reading" && } - {module === "listening" && } - {module === "writing" && } - {module === "speaking" && } - {module === "level" && } -
- )} - - ); + return ( + <> + + Exam Generation | EnCoach + + + + + + {user && ( + +

Exam Generation

+
+ + + {[...MODULE_ARRAY].map((x) => ( + + {({ checked }) => ( + + {capitalize(x)} + + )} + + ))} + +
+ {module === "reading" && } + {module === "listening" && } + {module === "writing" && } + {module === "speaking" && } + {module === "level" && } +
+ )} + + ); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 0ef4fea7..9a824ded 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,25 +1,33 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; import Navbar from "@/components/Navbar"; -import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone} from "react-icons/bs"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {useEffect, useState} from "react"; +import { + BsFileEarmarkText, + BsPencil, + BsStar, + BsBook, + BsHeadphones, + BsPen, + BsMegaphone, +} from "react-icons/bs"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { useEffect, useState } from "react"; import useStats from "@/hooks/useStats"; -import {averageScore, groupBySession, totalExams} from "@/utils/stats"; +import { averageScore, groupBySession, totalExams } from "@/utils/stats"; import useUser from "@/hooks/useUser"; import Diagnostic from "@/components/Diagnostic"; -import {ToastContainer} from "react-toastify"; -import {capitalize} from "lodash"; -import {Module} from "@/interfaces"; +import { ToastContainer } from "react-toastify"; +import { capitalize } from "lodash"; +import { Module } from "@/interfaces"; import ProgressBar from "@/components/Low/ProgressBar"; import Layout from "@/components/High/Layout"; -import {calculateAverageLevel} from "@/utils/score"; +import { calculateAverageLevel } from "@/utils/score"; import axios from "axios"; import DemographicInformationInput from "@/components/DemographicInformationInput"; import moment from "moment"; import Link from "next/link"; -import {MODULE_ARRAY} from "@/utils/moduleUtils"; +import { MODULE_ARRAY } from "@/utils/moduleUtils"; import ProfileSummary from "@/components/ProfileSummary"; import StudentDashboard from "@/dashboards/Student"; import AdminDashboard from "@/dashboards/Admin"; @@ -28,171 +36,209 @@ import TeacherDashboard from "@/dashboards/Teacher"; import AgentDashboard from "@/dashboards/Agent"; import MasterCorporateDashboard from "@/dashboards/MasterCorporate"; import PaymentDue from "./(status)/PaymentDue"; -import {useRouter} from "next/router"; -import {PayPalScriptProvider} from "@paypal/react-paypal-js"; -import {CorporateUser, MasterCorporateUser, Type, userTypes} from "@/interfaces/user"; +import { useRouter } from "next/router"; +import { PayPalScriptProvider } from "@paypal/react-paypal-js"; +import { + CorporateUser, + MasterCorporateUser, + Type, + userTypes, +} from "@/interfaces/user"; import Select from "react-select"; -import {USER_TYPE_LABELS} from "@/resources/user"; +import { USER_TYPE_LABELS } from "@/resources/user"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { - const user = req.session.user; +export const getServerSideProps = withIronSessionSsr(({ req, res }) => { + const user = req.session.user; - const envVariables: {[key: string]: string} = {}; - Object.keys(process.env) - .filter((x) => x.startsWith("NEXT_PUBLIC")) - .forEach((x: string) => { - envVariables[x] = process.env[x]!; - }); + const envVariables: { [key: string]: string } = {}; + Object.keys(process.env) + .filter((x) => x.startsWith("NEXT_PUBLIC")) + .forEach((x: string) => { + envVariables[x] = process.env[x]!; + }); - if (!user || !user.isVerified) { - return { - redirect: { - destination: "/login", - permanent: false, - }, - }; - } + if (!user || !user.isVerified) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } - return { - props: {user: req.session.user, envVariables}, - }; + return { + props: { user: req.session.user, envVariables }, + }; }, sessionOptions); interface Props { - user: any; - envVariables: {[key: string]: string}; + user: any; + envVariables: { [key: string]: string }; } export default function Home(props: Props) { - const {envVariables} = props; - const [showDiagnostics, setShowDiagnostics] = useState(false); - const [showDemographicInput, setShowDemographicInput] = useState(false); - const [selectedScreen, setSelectedScreen] = useState("admin"); + const { envVariables } = props; + const [showDiagnostics, setShowDiagnostics] = useState(false); + const [showDemographicInput, setShowDemographicInput] = useState(false); + const [selectedScreen, setSelectedScreen] = useState("admin"); - const {user, mutateUser} = useUser({redirectTo: "/login"}); - const router = useRouter(); + const { user, mutateUser } = useUser({ redirectTo: "/login" }); + const router = useRouter(); - useEffect(() => { - if (user) { - setShowDemographicInput( - !user.demographicInformation || - !user.demographicInformation.country || - !user.demographicInformation.gender || - !user.demographicInformation.phone, - ); - setShowDiagnostics(user.isFirstLogin && user.type === "student"); - } - }, [user]); + useEffect(() => { + if (user) { + setShowDemographicInput( + !user.demographicInformation || + !user.demographicInformation.country || + !user.demographicInformation.gender || + !user.demographicInformation.phone + ); + setShowDiagnostics(user.isFirstLogin && user.type === "student"); + } + }, [user]); - const checkIfUserExpired = () => { - const expirationDate = user!.subscriptionExpirationDate; + const checkIfUserExpired = () => { + const expirationDate = user!.subscriptionExpirationDate; - if (expirationDate === null || expirationDate === undefined) return false; - if (moment(expirationDate).isAfter(moment(new Date()))) return false; + if (expirationDate === null || expirationDate === undefined) return false; + if (moment(expirationDate).isAfter(moment(new Date()))) return false; - return true; - }; + return true; + }; - if (user && (user.status === "paymentDue" || user.status === "disabled" || checkIfUserExpired())) { - return ( - <> - - EnCoach - - - - - {user.status === "disabled" && ( - -
- Your account has been disabled! - Please contact an administrator if you believe this to be a mistake. -
-
- )} - {(user.status === "paymentDue" || checkIfUserExpired()) && } - - ); - } + if ( + user && + (user.status === "paymentDue" || + user.status === "disabled" || + checkIfUserExpired()) + ) { + return ( + <> + + EnCoach + + + + + {user.status === "disabled" && ( + +
+ + Your account has been disabled! + + + Please contact an administrator if you believe this to be a + mistake. + +
+
+ )} + {(user.status === "paymentDue" || checkIfUserExpired()) && ( + + )} + + ); + } - if (user && showDemographicInput) { - return ( - <> - - EnCoach - - - - - - - - - ); - } + if (user && showDemographicInput) { + return ( + <> + + EnCoach + + + + + + + + + ); + } - if (user && showDiagnostics) { - return ( - <> - - EnCoach - - - - - - setShowDiagnostics(false)} /> - - - ); - } + if (user && showDiagnostics) { + return ( + <> + + EnCoach + + + + + + setShowDiagnostics(false)} /> + + + ); + } - return ( - <> - - EnCoach - - - - - - {user && ( - - {user.type === "student" && } - {user.type === "teacher" && } - {user.type === "corporate" && } - {user.type === "mastercorporate" && } - {user.type === "agent" && } - {user.type === "admin" && } - {user.type === "developer" && ( - <> - ({ + value: u, + label: USER_TYPE_LABELS[u], + }))} + value={{ + value: selectedScreen, + label: USER_TYPE_LABELS[selectedScreen], + }} + onChange={(value) => + value + ? setSelectedScreen(value.value) + : setSelectedScreen("admin") + } + /> - {selectedScreen === "student" && } - {selectedScreen === "teacher" && } - {selectedScreen === "corporate" && } - {selectedScreen === "mastercorporate" && } - {selectedScreen === "agent" && } - {selectedScreen === "admin" && } - - )} - - )} - - ); + {selectedScreen === "student" && } + {selectedScreen === "teacher" && } + {selectedScreen === "corporate" && ( + + )} + {selectedScreen === "mastercorporate" && ( + + )} + {selectedScreen === "agent" && } + {selectedScreen === "admin" && } + + )} + + )} + + ); } diff --git a/src/pages/payment-record.tsx b/src/pages/payment-record.tsx index cfcc493e..aa5ba11c 100644 --- a/src/pages/payment-record.tsx +++ b/src/pages/payment-record.tsx @@ -1,20 +1,28 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; import useUser from "@/hooks/useUser"; -import {toast, ToastContainer} from "react-toastify"; +import { toast, ToastContainer } from "react-toastify"; import Layout from "@/components/High/Layout"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; import usePayments from "@/hooks/usePayments"; import usePaypalPayments from "@/hooks/usePaypalPayments"; -import {Payment, PaypalPayment} from "@/interfaces/paypal"; -import {CellContext, createColumnHelper, flexRender, getCoreRowModel, HeaderGroup, Table, useReactTable} from "@tanstack/react-table"; -import {CURRENCIES} from "@/resources/paypal"; -import {BsTrash} from "react-icons/bs"; +import { Payment, PaypalPayment } from "@/interfaces/paypal"; +import { + CellContext, + createColumnHelper, + flexRender, + getCoreRowModel, + HeaderGroup, + Table, + useReactTable, +} from "@tanstack/react-table"; +import { CURRENCIES } from "@/resources/paypal"; +import { BsTrash } from "react-icons/bs"; import axios from "axios"; -import {useEffect, useState, useMemo} from "react"; -import {AgentUser, CorporateUser, User} from "@/interfaces/user"; +import { useEffect, useState, useMemo } from "react"; +import { AgentUser, CorporateUser, User } from "@/interfaces/user"; import UserCard from "@/components/UserCard"; import Modal from "@/components/Modal"; import clsx from "clsx"; @@ -26,1192 +34,1448 @@ import Input from "@/components/Low/Input"; import ReactDatePicker from "react-datepicker"; import moment from "moment"; import PaymentAssetManager from "@/components/PaymentAssetManager"; -import {toFixedNumber} from "@/utils/number"; -import {CSVLink} from "react-csv"; -import {Tab} from "@headlessui/react"; -import {useListSearch} from "@/hooks/useListSearch"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { - const user = req.session.user; +import { toFixedNumber } from "@/utils/number"; +import { CSVLink } from "react-csv"; +import { Tab } from "@headlessui/react"; +import { useListSearch } from "@/hooks/useListSearch"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; - if (!user || !user.isVerified) { - return { - redirect: { - destination: "/login", - permanent: false, - }, - }; - } +export const getServerSideProps = withIronSessionSsr(({ req, res }) => { + const user = req.session.user; - if (shouldRedirectHome(user) || !["admin", "developer", "agent", "corporate", "mastercorporate"].includes(user.type)) { - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } + if (!user || !user.isVerified) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } - return { - props: {user: req.session.user}, - }; + if ( + shouldRedirectHome(user) || + checkAccess( + user, + getTypesOfUser([ + "admin", + "developer", + "agent", + "corporate", + "mastercorporate", + ]) + ) + ) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + + return { + props: { user: req.session.user }, + }; }, sessionOptions); const columnHelper = createColumnHelper(); const paypalColumnHelper = createColumnHelper(); -const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () => void; reload: () => void; showComission: boolean}) => { - const [corporate, setCorporate] = useState(); - const [date, setDate] = useState(new Date()); +const PaymentCreator = ({ + onClose, + reload, + showComission = false, +}: { + onClose: () => void; + reload: () => void; + showComission: boolean; +}) => { + const [corporate, setCorporate] = useState(); + const [date, setDate] = useState(new Date()); - const {users} = useUsers(); + const { users } = useUsers(); - const price = corporate?.corporateInformation?.payment?.value || 0; - const commission = corporate?.corporateInformation?.payment?.commission || 0; - const currency = corporate?.corporateInformation?.payment?.currency || "EUR"; + const price = corporate?.corporateInformation?.payment?.value || 0; + const commission = corporate?.corporateInformation?.payment?.commission || 0; + const currency = corporate?.corporateInformation?.payment?.currency || "EUR"; - const referralAgent = useMemo(() => { - if (corporate?.corporateInformation?.referralAgent) { - return users.find((u) => u.id === corporate.corporateInformation.referralAgent); - } + const referralAgent = useMemo(() => { + if (corporate?.corporateInformation?.referralAgent) { + return users.find( + (u) => u.id === corporate.corporateInformation.referralAgent + ); + } - return undefined; - }, [corporate?.corporateInformation?.referralAgent, users]); + return undefined; + }, [corporate?.corporateInformation?.referralAgent, users]); - const submit = () => { - axios - .post(`/api/payments`, { - corporate: corporate?.id, - agent: referralAgent?.id, - agentCommission: commission, - agentValue: toFixedNumber((commission! / 100) * price!, 2), - currency, - value: price, - isPaid: false, - date: date.toISOString(), - }) - .then(() => { - toast.success("New payment has been created successfully!"); - reload(); - onClose(); - }) - .catch(() => { - toast.error("Something went wrong, please try again later!"); - }); - }; + const submit = () => { + axios + .post(`/api/payments`, { + corporate: corporate?.id, + agent: referralAgent?.id, + agentCommission: commission, + agentValue: toFixedNumber((commission! / 100) * price!, 2), + currency, + value: price, + isPaid: false, + date: date.toISOString(), + }) + .then(() => { + toast.success("New payment has been created successfully!"); + reload(); + onClose(); + }) + .catch(() => { + toast.error("Something went wrong, please try again later!"); + }); + }; - return ( -
-

New Payment

-
-
- - u.type === "corporate") as CorporateUser[] + ).map((user) => ({ + value: user.id, + meta: user, + label: `${ + user.corporateInformation?.companyInformation?.name || user.name + } - ${user.email}`, + }))} + defaultValue={{ value: "undefined", label: "Select an account" }} + onChange={(value) => + setCorporate((value as any)?.meta ?? undefined) + } + menuPortalTarget={document?.body} + styles={{ + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused + ? "#D5D9F0" + : state.isSelected + ? "#7872BF" + : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> +
-
- -
- {}} type="number" value={price} defaultValue={0} className="col-span-3" disabled /> - {}} type="number" defaultValue={0} value={commission} disabled /> -
-
- - c.currency === currency)?.label}`} - onChange={() => null} - type="text" - defaultValue={0} - disabled - /> -
-
- )} +
+ +
+ {}} + type="number" + value={price} + defaultValue={0} + className="col-span-3" + disabled + /> + {}} + type="number" + defaultValue={0} + value={commission} + disabled + /> +
+
+ + c.currency === currency)?.label + }`} + onChange={() => null} + type="text" + defaultValue={0} + disabled + /> +
+
+ )} -
-
- - setDate(date ?? new Date())} - /> -
+
+
+ + setDate(date ?? new Date())} + /> +
-
- - null} - type="text" - defaultValue={"No country manager"} - disabled - /> -
-
-
- - -
-
-
- ); +
+ + null} + type="text" + defaultValue={"No country manager"} + disabled + /> +
+
+
+ + +
+
+
+ ); }; const IS_PAID_OPTIONS = [ - { - value: null, - label: "All", - }, - { - value: false, - label: "Unpaid", - }, - { - value: true, - label: "Paid", - }, + { + value: null, + label: "All", + }, + { + value: false, + label: "Unpaid", + }, + { + value: true, + label: "Paid", + }, ]; const IS_FILE_SUBMITTED_OPTIONS = [ - { - value: null, - label: "All", - }, - { - value: false, - label: "Submitted", - }, - { - value: true, - label: "Not Submitted", - }, + { + value: null, + label: "All", + }, + { + value: false, + label: "Submitted", + }, + { + value: true, + label: "Not Submitted", + }, ]; -const CSV_PAYMENTS_WHITELISTED_KEYS = ["corporateId", "corporate", "date", "amount", "agent", "agentCommission", "agentValue", "isPaid"]; +const CSV_PAYMENTS_WHITELISTED_KEYS = [ + "corporateId", + "corporate", + "date", + "amount", + "agent", + "agentCommission", + "agentValue", + "isPaid", +]; -const CSV_PAYPAL_WHITELISTED_KEYS = ["orderId", "status", "name", "email", "value", "createdAt", "subscriptionExpirationDate"]; +const CSV_PAYPAL_WHITELISTED_KEYS = [ + "orderId", + "status", + "name", + "email", + "value", + "createdAt", + "subscriptionExpirationDate", +]; interface SimpleCSVColumn { - key: string; - label: string; - index: number; + key: string; + label: string; + index: number; } interface PaypalPaymentWithUserData extends PaypalPayment { - name: string; - email: string; + name: string; + email: string; } const paypalFilterRows = [["email"], ["name"]]; export default function PaymentRecord() { - const [selectedCorporateUser, setSelectedCorporateUser] = useState(); - const [selectedAgentUser, setSelectedAgentUser] = useState(); - const [isCreatingPayment, setIsCreatingPayment] = useState(false); - const [filters, setFilters] = useState<{filter: (p: Payment) => boolean; id: string}[]>([]); - const [displayPayments, setDisplayPayments] = useState([]); + const [selectedCorporateUser, setSelectedCorporateUser] = useState(); + const [selectedAgentUser, setSelectedAgentUser] = useState(); + const [isCreatingPayment, setIsCreatingPayment] = useState(false); + const [filters, setFilters] = useState< + { filter: (p: Payment) => boolean; id: string }[] + >([]); + const [displayPayments, setDisplayPayments] = useState([]); - const [corporate, setCorporate] = useState(); - const [agent, setAgent] = useState(); + const [corporate, setCorporate] = useState(); + const [agent, setAgent] = useState(); - const {user} = useUser({redirectTo: "/login"}); - const {users, reload: reloadUsers} = useUsers(); - const {payments: originalPayments, reload: reloadPayment} = usePayments(); - const {payments: paypalPayments, reload: reloadPaypalPayment} = usePaypalPayments(); - const [startDate, setStartDate] = useState(moment("01/01/2023").toDate()); - const [endDate, setEndDate] = useState(moment().endOf("day").toDate()); - const [paid, setPaid] = useState(IS_PAID_OPTIONS[0].value); - const [commissionTransfer, setCommissionTransfer] = useState(IS_FILE_SUBMITTED_OPTIONS[0].value); - const [corporateTransfer, setCorporateTransfer] = useState(IS_FILE_SUBMITTED_OPTIONS[0].value); - const reload = () => { - reloadUsers(); - reloadPayment(); - }; + const { user } = useUser({ redirectTo: "/login" }); + const { users, reload: reloadUsers } = useUsers(); + const { payments: originalPayments, reload: reloadPayment } = usePayments(); + const { payments: paypalPayments, reload: reloadPaypalPayment } = + usePaypalPayments(); + const [startDate, setStartDate] = useState( + moment("01/01/2023").toDate() + ); + const [endDate, setEndDate] = useState( + moment().endOf("day").toDate() + ); + const [paid, setPaid] = useState(IS_PAID_OPTIONS[0].value); + const [commissionTransfer, setCommissionTransfer] = useState( + IS_FILE_SUBMITTED_OPTIONS[0].value + ); + const [corporateTransfer, setCorporateTransfer] = useState( + IS_FILE_SUBMITTED_OPTIONS[0].value + ); + const reload = () => { + reloadUsers(); + reloadPayment(); + }; - const payments = useMemo(() => { - return originalPayments.filter((p: Payment) => { - const date = moment(p.date); - return date.isAfter(startDate) && date.isBefore(endDate); - }); - }, [originalPayments, startDate, endDate]); + const payments = useMemo(() => { + return originalPayments.filter((p: Payment) => { + const date = moment(p.date); + return date.isAfter(startDate) && date.isBefore(endDate); + }); + }, [originalPayments, startDate, endDate]); - const [selectedIndex, setSelectedIndex] = useState(0); + const [selectedIndex, setSelectedIndex] = useState(0); - useEffect(() => { - setDisplayPayments( - filters - .map((f) => f.filter) - .reduce((d, f) => d.filter(f), payments) - .sort((a, b) => moment(b.date).diff(moment(a.date))), - ); - }, [payments, filters]); + useEffect(() => { + setDisplayPayments( + filters + .map((f) => f.filter) + .reduce((d, f) => d.filter(f), payments) + .sort((a, b) => moment(b.date).diff(moment(a.date))) + ); + }, [payments, filters]); - useEffect(() => { - if (user && user.type === "agent") { - setAgent(user); - } - }, [user]); + useEffect(() => { + if (user && user.type === "agent") { + setAgent(user); + } + }, [user]); - useEffect(() => { - setFilters((prev) => [ - ...prev.filter((x) => x.id !== "agent-filter"), - ...(!agent - ? [] - : [ - { - id: "agent-filter", - filter: (p: Payment) => p.agent === agent.id, - }, - ]), - ]); - }, [agent]); + useEffect(() => { + setFilters((prev) => [ + ...prev.filter((x) => x.id !== "agent-filter"), + ...(!agent + ? [] + : [ + { + id: "agent-filter", + filter: (p: Payment) => p.agent === agent.id, + }, + ]), + ]); + }, [agent]); - useEffect(() => { - setFilters((prev) => [ - ...prev.filter((x) => x.id !== "corporate-filter"), - ...(!corporate - ? [] - : [ - { - id: "corporate-filter", - filter: (p: Payment) => p.corporate === corporate.id, - }, - ]), - ]); - }, [corporate]); + useEffect(() => { + setFilters((prev) => [ + ...prev.filter((x) => x.id !== "corporate-filter"), + ...(!corporate + ? [] + : [ + { + id: "corporate-filter", + filter: (p: Payment) => p.corporate === corporate.id, + }, + ]), + ]); + }, [corporate]); - useEffect(() => { - setFilters((prev) => [ - ...prev.filter((x) => x.id !== "paid"), - ...(typeof paid !== "boolean" ? [] : [{id: "paid", filter: (p: Payment) => p.isPaid === paid}]), - ]); - }, [paid]); + useEffect(() => { + setFilters((prev) => [ + ...prev.filter((x) => x.id !== "paid"), + ...(typeof paid !== "boolean" + ? [] + : [{ id: "paid", filter: (p: Payment) => p.isPaid === paid }]), + ]); + }, [paid]); - useEffect(() => { - setFilters((prev) => [ - ...prev.filter((x) => x.id !== "commissionTransfer"), - ...(typeof commissionTransfer !== "boolean" - ? [] - : [ - { - id: "commissionTransfer", - filter: (p: Payment) => !p.commissionTransfer === commissionTransfer, - }, - ]), - ]); - }, [commissionTransfer]); + useEffect(() => { + setFilters((prev) => [ + ...prev.filter((x) => x.id !== "commissionTransfer"), + ...(typeof commissionTransfer !== "boolean" + ? [] + : [ + { + id: "commissionTransfer", + filter: (p: Payment) => + !p.commissionTransfer === commissionTransfer, + }, + ]), + ]); + }, [commissionTransfer]); - useEffect(() => { - setFilters((prev) => [ - ...prev.filter((x) => x.id !== "corporateTransfer"), - ...(typeof corporateTransfer !== "boolean" - ? [] - : [ - { - id: "corporateTransfer", - filter: (p: Payment) => !p.corporateTransfer === corporateTransfer, - }, - ]), - ]); - }, [corporateTransfer]); + useEffect(() => { + setFilters((prev) => [ + ...prev.filter((x) => x.id !== "corporateTransfer"), + ...(typeof corporateTransfer !== "boolean" + ? [] + : [ + { + id: "corporateTransfer", + filter: (p: Payment) => + !p.corporateTransfer === corporateTransfer, + }, + ]), + ]); + }, [corporateTransfer]); - useEffect(() => { - if (user && user.type === "corporate") return setCorporate(user); - if (user && user.type === "agent") return setAgent(user); - }, [user]); + useEffect(() => { + if (user && user.type === "corporate") return setCorporate(user); + if (user && user.type === "agent") return setAgent(user); + }, [user]); - const updatePayment = (payment: Payment, key: string, value: any) => { - axios - .patch(`api/payments/${payment.id}`, {...payment, [key]: value}) - .then(() => toast.success("Updated the payment")) - .finally(reload); - }; + const updatePayment = (payment: Payment, key: string, value: any) => { + axios + .patch(`api/payments/${payment.id}`, { ...payment, [key]: value }) + .then(() => toast.success("Updated the payment")) + .finally(reload); + }; - const deletePayment = (id: string) => { - if (!confirm(`Are you sure you want to delete this payment?`)) return; + const deletePayment = (id: string) => { + if (!confirm(`Are you sure you want to delete this payment?`)) return; - axios - .delete(`/api/payments/${id}`) - .then(() => toast.success(`Deleted the "${id}" payment`)) - .catch((reason) => { - if (reason.response.status === 404) { - toast.error("Exam not found!"); - return; - } + axios + .delete(`/api/payments/${id}`) + .then(() => toast.success(`Deleted the "${id}" payment`)) + .catch((reason) => { + if (reason.response.status === 404) { + toast.error("Exam not found!"); + return; + } - if (reason.response.status === 403) { - toast.error("You do not have permission to delete an approved payment record!"); - return; - } + if (reason.response.status === 403) { + toast.error( + "You do not have permission to delete an approved payment record!" + ); + return; + } - toast.error("Something went wrong, please try again later."); - }) - .finally(reload); - }; + toast.error("Something went wrong, please try again later."); + }) + .finally(reload); + }; - const getFileAssetsColumns = () => { - if (user) { - const containerClassName = "flex gap-2 text-mti-purple-light hover:text-mti-purple-dark ease-in-out duration-300 cursor-pointer"; - switch (user.type) { - case "corporate": - return [ - columnHelper.accessor("corporateTransfer", { - header: "Corporate transfer", - id: "corporateTransfer", - cell: (info) => ( -
- -
- ), - }), - ]; - case "agent": - return [ - columnHelper.accessor("commissionTransfer", { - header: "Commission transfer", - id: "commissionTransfer", - cell: (info) => ( -
- -
- ), - }), - ]; - case "admin": - return [ - columnHelper.accessor("corporateTransfer", { - header: "Corporate transfer", - id: "corporateTransfer", - cell: (info) => ( -
- -
- ), - }), - columnHelper.accessor("commissionTransfer", { - header: "Commission transfer", - id: "commissionTransfer", - cell: (info) => ( -
- -
- ), - }), - ]; - case "developer": - return [ - columnHelper.accessor("corporateTransfer", { - header: "Corporate transfer", - id: "corporateTransfer", - cell: (info) => ( -
- -
- ), - }), - columnHelper.accessor("commissionTransfer", { - header: "Commission transfer", - id: "commissionTransfer", - cell: (info) => ( -
- -
- ), - }), - ]; - default: - return []; - } - } + const getFileAssetsColumns = () => { + if (user) { + const containerClassName = + "flex gap-2 text-mti-purple-light hover:text-mti-purple-dark ease-in-out duration-300 cursor-pointer"; + switch (user.type) { + case "corporate": + return [ + columnHelper.accessor("corporateTransfer", { + header: "Corporate transfer", + id: "corporateTransfer", + cell: (info) => ( +
+ +
+ ), + }), + ]; + case "agent": + return [ + columnHelper.accessor("commissionTransfer", { + header: "Commission transfer", + id: "commissionTransfer", + cell: (info) => ( +
+ +
+ ), + }), + ]; + case "admin": + return [ + columnHelper.accessor("corporateTransfer", { + header: "Corporate transfer", + id: "corporateTransfer", + cell: (info) => ( +
+ +
+ ), + }), + columnHelper.accessor("commissionTransfer", { + header: "Commission transfer", + id: "commissionTransfer", + cell: (info) => ( +
+ +
+ ), + }), + ]; + case "developer": + return [ + columnHelper.accessor("corporateTransfer", { + header: "Corporate transfer", + id: "corporateTransfer", + cell: (info) => ( +
+ +
+ ), + }), + columnHelper.accessor("commissionTransfer", { + header: "Commission transfer", + id: "commissionTransfer", + cell: (info) => ( +
+ +
+ ), + }), + ]; + default: + return []; + } + } - return []; - }; + return []; + }; - const columHelperValue = (key: string, info: any) => { - switch (key) { - case "agentCommission": { - const value = info.getValue(); - return {value: `${value}%`}; - } - case "agent": { - const user = users.find((x) => x.id === info.row.original.agent) as AgentUser; - return { - value: user?.name, - user, - }; - } - case "agentValue": - case "amount": { - const value = info.getValue(); - const numberValue = toFixedNumber(value, 2); - return {value: numberValue}; - } - case "date": { - const value = info.getValue(); - return {value: moment(value).format("DD/MM/YYYY")}; - } - case "corporate": { - const specificValue = info.row.original.corporate; - const user = users.find((x) => x.id === specificValue) as CorporateUser; - return { - user, - value: user?.corporateInformation.companyInformation.name || user?.name, - }; - } - case "currency": { - return { - value: info.row.original.currency, - }; - } - case "isPaid": - case "corporateId": - default: { - const value = info.getValue(); - return {value}; - } - } - }; + const columHelperValue = (key: string, info: any) => { + switch (key) { + case "agentCommission": { + const value = info.getValue(); + return { value: `${value}%` }; + } + case "agent": { + const user = users.find( + (x) => x.id === info.row.original.agent + ) as AgentUser; + return { + value: user?.name, + user, + }; + } + case "agentValue": + case "amount": { + const value = info.getValue(); + const numberValue = toFixedNumber(value, 2); + return { value: numberValue }; + } + case "date": { + const value = info.getValue(); + return { value: moment(value).format("DD/MM/YYYY") }; + } + case "corporate": { + const specificValue = info.row.original.corporate; + const user = users.find((x) => x.id === specificValue) as CorporateUser; + return { + user, + value: + user?.corporateInformation.companyInformation.name || user?.name, + }; + } + case "currency": { + return { + value: info.row.original.currency, + }; + } + case "isPaid": + case "corporateId": + default: { + const value = info.getValue(); + return { value }; + } + } + }; - const hiddenToCorporateColumns = () => { - if (user && user.type !== "corporate") - return [ - columnHelper.accessor("agent", { - header: "Country Manager", - id: "agent", - cell: (info) => { - const {user, value} = columHelperValue(info.column.id, info); - return ( -
setSelectedAgentUser(user)}> - {value} -
- ); - }, - }), - columnHelper.accessor("agentCommission", { - header: "Commission", - id: "agentCommission", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return <>{value}; - }, - }), - columnHelper.accessor("agentValue", { - header: "Commission Value", - id: "agentValue", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - const finalValue = `${value} ${info.row.original.currency}`; - return {finalValue}; - }, - }), - ]; - return []; - }; + const hiddenToCorporateColumns = () => { + if (user && user.type !== "corporate") + return [ + columnHelper.accessor("agent", { + header: "Country Manager", + id: "agent", + cell: (info) => { + const { user, value } = columHelperValue(info.column.id, info); + return ( +
setSelectedAgentUser(user)} + > + {value} +
+ ); + }, + }), + columnHelper.accessor("agentCommission", { + header: "Commission", + id: "agentCommission", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return <>{value}; + }, + }), + columnHelper.accessor("agentValue", { + header: "Commission Value", + id: "agentValue", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + const finalValue = `${value} ${info.row.original.currency}`; + return {finalValue}; + }, + }), + ]; + return []; + }; - const defaultColumns = [ - columnHelper.accessor("corporate", { - header: "Corporate ID", - id: "corporateId", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return value; - }, - }), - columnHelper.accessor("corporate", { - header: "Corporate", - id: "corporate", - cell: (info) => { - const {user, value} = columHelperValue(info.column.id, info); - return ( -
setSelectedCorporateUser(user)}> - {value} -
- ); - }, - }), - columnHelper.accessor("date", { - header: "Date", - id: "date", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {value}; - }, - }), - columnHelper.accessor("value", { - header: "Amount", - id: "amount", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - const currency = CURRENCIES.find((x) => x.currency === info.row.original.currency)?.label; - const finalValue = `${value} ${currency}`; - return {finalValue}; - }, - }), - ...hiddenToCorporateColumns(), - columnHelper.accessor("isPaid", { - header: "Paid", - id: "isPaid", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); + const defaultColumns = [ + columnHelper.accessor("corporate", { + header: "Corporate ID", + id: "corporateId", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return value; + }, + }), + columnHelper.accessor("corporate", { + header: "Corporate", + id: "corporate", + cell: (info) => { + const { user, value } = columHelperValue(info.column.id, info); + return ( +
setSelectedCorporateUser(user)} + > + {value} +
+ ); + }, + }), + columnHelper.accessor("date", { + header: "Date", + id: "date", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {value}; + }, + }), + columnHelper.accessor("value", { + header: "Amount", + id: "amount", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + const currency = CURRENCIES.find( + (x) => x.currency === info.row.original.currency + )?.label; + const finalValue = `${value} ${currency}`; + return {finalValue}; + }, + }), + ...hiddenToCorporateColumns(), + columnHelper.accessor("isPaid", { + header: "Paid", + id: "isPaid", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); - return ( - { - if (user?.type === agent || user?.type === "corporate" || value) return null; - if (!info.row.original.commissionTransfer || !info.row.original.corporateTransfer) - return alert("All files need to be uploaded to consider it paid!"); - if (!confirm(`Are you sure you want to consider this payment paid?`)) return null; + return ( + { + if (user?.type === agent || user?.type === "corporate" || value) + return null; + if ( + !info.row.original.commissionTransfer || + !info.row.original.corporateTransfer + ) + return alert( + "All files need to be uploaded to consider it paid!" + ); + if ( + !confirm(`Are you sure you want to consider this payment paid?`) + ) + return null; - return updatePayment(info.row.original, "isPaid", e); - }}> - - - ); - }, - }), - ...getFileAssetsColumns(), - { - header: "", - id: "actions", - cell: ({row}: {row: {original: Payment}}) => { - return ( -
- {user?.type !== "agent" && ( -
deletePayment(row.original.id)}> - -
- )} -
- ); - }, - }, - ]; + return updatePayment(info.row.original, "isPaid", e); + }} + > + +
+ ); + }, + }), + ...getFileAssetsColumns(), + { + header: "", + id: "actions", + cell: ({ row }: { row: { original: Payment } }) => { + return ( +
+ {user?.type !== "agent" && ( +
deletePayment(row.original.id)} + > + +
+ )} +
+ ); + }, + }, + ]; - const table = useReactTable({ - data: displayPayments, - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - }); + const table = useReactTable({ + data: displayPayments, + columns: defaultColumns, + getCoreRowModel: getCoreRowModel(), + }); - const updatedPaypalPayments = useMemo( - () => - paypalPayments.map((p) => { - const user = users.find((x) => x.id === p.userId) as User; - return {...p, name: user?.name, email: user?.email}; - }), - [paypalPayments, users], - ); + const updatedPaypalPayments = useMemo( + () => + paypalPayments.map((p) => { + const user = users.find((x) => x.id === p.userId) as User; + return { ...p, name: user?.name, email: user?.email }; + }), + [paypalPayments, users] + ); - const paypalColumns = [ - paypalColumnHelper.accessor("orderId", { - header: "Order ID", - id: "orderId", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {value}; - }, - }), - paypalColumnHelper.accessor("status", { - header: "Status", - id: "status", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {value}; - }, - }), - paypalColumnHelper.accessor("name", { - header: "User Name", - id: "name", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {value}; - }, - }), - paypalColumnHelper.accessor("email", { - header: "Email", - id: "email", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {value}; - }, - }), - paypalColumnHelper.accessor("value", { - header: "Amount", - id: "value", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - const finalValue = `${value} ${info.row.original.currency}`; - return {finalValue}; - }, - }), - paypalColumnHelper.accessor("createdAt", { - header: "Date", - id: "createdAt", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {moment(value).format("DD/MM/YYYY")}; - }, - }), - paypalColumnHelper.accessor("subscriptionExpirationDate", { - header: "Expiration Date", - id: "subscriptionExpirationDate", - cell: (info) => { - const {value} = columHelperValue(info.column.id, info); - return {moment(value).format("DD/MM/YYYY")}; - }, - }), - ]; + const paypalColumns = [ + paypalColumnHelper.accessor("orderId", { + header: "Order ID", + id: "orderId", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {value}; + }, + }), + paypalColumnHelper.accessor("status", { + header: "Status", + id: "status", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {value}; + }, + }), + paypalColumnHelper.accessor("name", { + header: "User Name", + id: "name", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {value}; + }, + }), + paypalColumnHelper.accessor("email", { + header: "Email", + id: "email", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {value}; + }, + }), + paypalColumnHelper.accessor("value", { + header: "Amount", + id: "value", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + const finalValue = `${value} ${info.row.original.currency}`; + return {finalValue}; + }, + }), + paypalColumnHelper.accessor("createdAt", { + header: "Date", + id: "createdAt", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {moment(value).format("DD/MM/YYYY")}; + }, + }), + paypalColumnHelper.accessor("subscriptionExpirationDate", { + header: "Expiration Date", + id: "subscriptionExpirationDate", + cell: (info) => { + const { value } = columHelperValue(info.column.id, info); + return {moment(value).format("DD/MM/YYYY")}; + }, + }), + ]; - const {rows: filteredRows, renderSearch} = useListSearch(paypalFilterRows, updatedPaypalPayments); + const { rows: filteredRows, renderSearch } = useListSearch( + paypalFilterRows, + updatedPaypalPayments + ); - const paypalTable = useReactTable({ - data: filteredRows.sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt), "second")), - columns: paypalColumns, - getCoreRowModel: getCoreRowModel(), - }); + const paypalTable = useReactTable({ + data: filteredRows.sort((a, b) => + moment(b.createdAt).diff(moment(a.createdAt), "second") + ), + columns: paypalColumns, + getCoreRowModel: getCoreRowModel(), + }); - const getUserModal = () => { - if (user) { - if (selectedCorporateUser) { - return ( - setSelectedCorporateUser(undefined)}> - <> - {selectedCorporateUser && ( -
- { - setSelectedCorporateUser(undefined); - if (shouldReload) reload(); - }} - user={selectedCorporateUser} - disabled - disabledFields={{countryManager: true}} - /> -
- )} - -
- ); - } + const getUserModal = () => { + if (user) { + if (selectedCorporateUser) { + return ( + setSelectedCorporateUser(undefined)} + > + <> + {selectedCorporateUser && ( +
+ { + setSelectedCorporateUser(undefined); + if (shouldReload) reload(); + }} + user={selectedCorporateUser} + disabled + disabledFields={{ countryManager: true }} + /> +
+ )} + +
+ ); + } - if (selectedAgentUser) { - return ( - setSelectedAgentUser(undefined)}> - <> - {selectedAgentUser && ( -
- { - setSelectedAgentUser(undefined); - if (shouldReload) reload(); - }} - user={selectedAgentUser} - /> -
- )} - -
- ); - } - } + if (selectedAgentUser) { + return ( + setSelectedAgentUser(undefined)} + > + <> + {selectedAgentUser && ( +
+ { + setSelectedAgentUser(undefined); + if (shouldReload) reload(); + }} + user={selectedAgentUser} + /> +
+ )} + +
+ ); + } + } - return null; - }; + return null; + }; - const getCSVData = () => { - const tables = [table, paypalTable]; - const whitelists = [CSV_PAYMENTS_WHITELISTED_KEYS, CSV_PAYPAL_WHITELISTED_KEYS]; - const currentTable = tables[selectedIndex]; - const whitelist = whitelists[selectedIndex]; - const columns = (currentTable.getHeaderGroups() as any[]).reduce((accm: any[], group: any) => { - const whitelistedColumns = group.headers.filter((header: any) => whitelist.includes(header.id)); + const getCSVData = () => { + const tables = [table, paypalTable]; + const whitelists = [ + CSV_PAYMENTS_WHITELISTED_KEYS, + CSV_PAYPAL_WHITELISTED_KEYS, + ]; + const currentTable = tables[selectedIndex]; + const whitelist = whitelists[selectedIndex]; + const columns = (currentTable.getHeaderGroups() as any[]).reduce( + (accm: any[], group: any) => { + const whitelistedColumns = group.headers.filter((header: any) => + whitelist.includes(header.id) + ); - const data = whitelistedColumns.map((data: any) => ({ - key: data.column.columnDef.id, - label: data.column.columnDef.header, - })) as SimpleCSVColumn[]; + const data = whitelistedColumns.map((data: any) => ({ + key: data.column.columnDef.id, + label: data.column.columnDef.header, + })) as SimpleCSVColumn[]; - return [...accm, ...data]; - }, []); + return [...accm, ...data]; + }, + [] + ); - const {rows} = currentTable.getRowModel(); + const { rows } = currentTable.getRowModel(); - const finalColumns = [ - ...columns, - { - key: "currency", - label: "Currency", - }, - ]; + const finalColumns = [ + ...columns, + { + key: "currency", + label: "Currency", + }, + ]; - return { - columns: finalColumns, - rows: rows.map((row) => { - return finalColumns.reduce((accm, {key}) => { - const {value} = columHelperValue(key, { - row, - getValue: () => row.getValue(key), - }); - return { - ...accm, - [key]: value, - }; - }, {}); - }), - }; - }; + return { + columns: finalColumns, + rows: rows.map((row) => { + return finalColumns.reduce((accm, { key }) => { + const { value } = columHelperValue(key, { + row, + getValue: () => row.getValue(key), + }); + return { + ...accm, + [key]: value, + }; + }, {}); + }), + }; + }; - const {rows: csvRows, columns: csvColumns} = getCSVData(); + const { rows: csvRows, columns: csvColumns } = getCSVData(); - const renderTable = (table: Table) => ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - -
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- ); + const renderTable = (table: Table) => ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ ); - return ( - <> - - Payment Record | EnCoach - - - - - - {user && ( - - {getUserModal()} - setIsCreatingPayment(false)}> - setIsCreatingPayment(false)} - reload={reload} - showComission={user.type === "developer" || user.type === "admin"} - /> - + return ( + <> + + Payment Record | EnCoach + + + + + + {user && ( + + {getUserModal()} + setIsCreatingPayment(false)} + > + setIsCreatingPayment(false)} + reload={reload} + showComission={checkAccess(user, ["developer", "admin"])} + /> + -
-

Payment Record

-
- {(["developer", "admin", "agent", "corporate", "mastercorporate"].includes(user.type)) && ( - - )} - {(user.type === "developer" || user.type === "admin") && ( - - )} -
-
- - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark", - ) - }> - Payments - - {["admin", "developer"].includes(user.type) && ( - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark", - ) - }> - Paymob - - )} - - - -
-
- - u.type === "agent") as AgentUser[]).map((user) => ({ - value: user.id, - meta: user, - label: `${user.name} - ${user.email}`, - }))} - value={ - agent - ? { - value: agent?.id, - label: `${agent.name} - ${agent.email}`, - } - : undefined - } - onChange={(value) => setAgent(value !== null ? (value as any).meta : undefined)} - menuPortalTarget={document?.body} - styles={{ - menuPortal: (base) => ({...base, zIndex: 9999}), - control: (styles) => ({ - ...styles, - paddingLeft: "4px", - border: "none", - outline: "none", - ":focus": { - outline: "none", - }, - }), - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - /> -
- )} -
- - e.value === commissionTransfer)} - onChange={(value) => { - if (value) return setCommissionTransfer(value.value); - setCommissionTransfer(null); - }} - menuPortalTarget={document?.body} - styles={{ - menuPortal: (base) => ({...base, zIndex: 9999}), - control: (styles) => ({ - ...styles, - paddingLeft: "4px", - border: "none", - outline: "none", - ":focus": { - outline: "none", - }, - }), - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - /> -
- )} -
- - u.type === "corporate" + ) as CorporateUser[] + ).map((user) => ({ + value: user.id, + meta: user, + label: `${ + user.corporateInformation?.companyInformation?.name || + user.name + } - ${user.email}`, + }))} + defaultValue={ + user.type === "corporate" + ? { + value: user.id, + meta: user, + label: `${ + user.corporateInformation?.companyInformation + ?.name || user.name + } - ${user.email}`, + } + : undefined + } + isDisabled={user.type === "corporate"} + onChange={(value) => + setCorporate((value as any)?.meta ?? undefined) + } + menuPortalTarget={document?.body} + styles={{ + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused + ? "#D5D9F0" + : state.isSelected + ? "#7872BF" + : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> +
+ {user.type !== "corporate" && ( +
+ + e.value === paid)} + onChange={(value) => { + if (value) return setPaid(value.value); + setPaid(null); + }} + menuPortalTarget={document?.body} + styles={{ + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused + ? "#D5D9F0" + : state.isSelected + ? "#7872BF" + : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> +
+
+ + + moment(date).isSameOrBefore(moment(new Date())) + } + onChange={([initialDate, finalDate]: [Date, Date]) => { + setStartDate( + initialDate ?? moment("01/01/2023").toDate() + ); + if (finalDate) { + // basicly selecting a final day works as if I'm selecting the first + // minute of that day. this way it covers the whole day + setEndDate(moment(finalDate).endOf("day").toDate()); + return; + } + setEndDate(null); + }} + /> +
+ {user.type !== "corporate" && ( +
+ + e.value === corporateTransfer + )} + onChange={(value) => { + if (value) return setCorporateTransfer(value.value); + setCorporateTransfer(null); + }} + menuPortalTarget={document?.body} + styles={{ + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused + ? "#D5D9F0" + : state.isSelected + ? "#7872BF" + : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> +
+
+ {renderTable(table as Table)} +
+ + {renderSearch()} + {renderTable(paypalTable as Table)} + +
+
+
+ )} + + ); } diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index 1dcce483..5e1fcae9 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -1,655 +1,818 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {ChangeEvent, Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState} from "react"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { + ChangeEvent, + Dispatch, + ReactNode, + SetStateAction, + useEffect, + useRef, + useState, +} from "react"; import useUser from "@/hooks/useUser"; -import {toast, ToastContainer} from "react-toastify"; +import { toast, ToastContainer } from "react-toastify"; import Layout from "@/components/High/Layout"; import Input from "@/components/Low/Input"; import Button from "@/components/Low/Button"; import Link from "next/link"; import axios from "axios"; -import {ErrorMessage} from "@/constants/errors"; +import { ErrorMessage } from "@/constants/errors"; import clsx from "clsx"; -import {CorporateUser, EmploymentStatus, EMPLOYMENT_STATUS, Gender, User} from "@/interfaces/user"; +import { + CorporateUser, + EmploymentStatus, + EMPLOYMENT_STATUS, + Gender, + User, + DemographicInformation, +} from "@/interfaces/user"; import CountrySelect from "@/components/Low/CountrySelect"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; import moment from "moment"; -import {BsCamera, BsQuestionCircleFill} from "react-icons/bs"; -import {USER_TYPE_LABELS} from "@/resources/user"; +import { BsCamera, BsQuestionCircleFill } from "react-icons/bs"; +import { USER_TYPE_LABELS } from "@/resources/user"; import useGroups from "@/hooks/useGroups"; import useUsers from "@/hooks/useUsers"; -import {convertBase64} from "@/utils"; -import {Divider} from "primereact/divider"; +import { convertBase64 } from "@/utils"; +import { Divider } from "primereact/divider"; import GenderInput from "@/components/High/GenderInput"; import EmploymentStatusInput from "@/components/High/EmploymentStatusInput"; import TimezoneSelect from "@/components/Low/TImezoneSelect"; import Modal from "@/components/Modal"; -import {Module} from "@/interfaces"; +import { Module } from "@/interfaces"; import ModuleLevelSelector from "@/components/Medium/ModuleLevelSelector"; import Select from "@/components/Low/Select"; -import {InstructorGender} from "@/interfaces/exam"; -import {capitalize} from "lodash"; +import { InstructorGender } from "@/interfaces/exam"; +import { capitalize } from "lodash"; import TopicModal from "@/components/Medium/TopicModal"; -import {v4} from "uuid"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { - const user = req.session.user; +import { v4 } from "uuid"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; - if (!user || !user.isVerified) { - return { - redirect: { - destination: "/login", - permanent: false, - }, - }; - } +export const getServerSideProps = withIronSessionSsr(({ req, res }) => { + const user = req.session.user; - if (shouldRedirectHome(user)) { - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } + if (!user || !user.isVerified) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } - return { - props: {user: req.session.user}, - }; + if (shouldRedirectHome(user)) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + + return { + props: { user: req.session.user }, + }; }, sessionOptions); interface Props { - user: User; - mutateUser: Function; + user: User; + mutateUser: Function; } -const DoubleColumnRow = ({children}: {children: ReactNode}) =>
{children}
; +const DoubleColumnRow = ({ children }: { children: ReactNode }) => ( +
{children}
+); -function UserProfile({user, mutateUser}: Props) { - const [bio, setBio] = useState(user.bio || ""); - const [name, setName] = useState(user.name || ""); - const [email, setEmail] = useState(user.email || ""); - const [password, setPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [profilePicture, setProfilePicture] = useState(user.profilePicture); +function UserProfile({ user, mutateUser }: Props) { + const [bio, setBio] = useState(user.bio || ""); + const [name, setName] = useState(user.name || ""); + const [email, setEmail] = useState(user.email || ""); + const [password, setPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [profilePicture, setProfilePicture] = useState(user.profilePicture); - const [desiredLevels, setDesiredLevels] = useState<{[key in Module]: number} | undefined>( - ["developer", "student"].includes(user.type) ? user.desiredLevels : undefined, - ); - const [focus, setFocus] = useState<"academic" | "general">(user.focus); + const [desiredLevels, setDesiredLevels] = useState< + { [key in Module]: number } | undefined + >( + checkAccess(user, ["developer", "student"]) ? user.desiredLevels : undefined + ); + const [focus, setFocus] = useState<"academic" | "general">(user.focus); - const [country, setCountry] = useState(user.demographicInformation?.country || ""); - const [phone, setPhone] = useState(user.demographicInformation?.phone || ""); - const [gender, setGender] = useState(user.demographicInformation?.gender || undefined); - const [employment, setEmployment] = useState( - user.type === "corporate" || user.type === "mastercorporate" ? undefined : user.demographicInformation?.employment, - ); - const [passport_id, setPassportID] = useState(user.type === "student" ? user.demographicInformation?.passport_id : undefined); + const [country, setCountry] = useState( + user.demographicInformation?.country || "" + ); + const [phone, setPhone] = useState( + user.demographicInformation?.phone || "" + ); + const [gender, setGender] = useState( + user.demographicInformation?.gender || undefined + ); + const [employment, setEmployment] = useState( + checkAccess(user, ["corporate", "mastercorporate"]) + ? undefined + : (user.demographicInformation as DemographicInformation)?.employment + ); + const [passport_id, setPassportID] = useState( + checkAccess(user, ["student"]) + ? (user.demographicInformation as DemographicInformation)?.passport_id + : undefined + ); - const [preferredGender, setPreferredGender] = useState( - user.type === "student" || user.type === "developer" ? user.preferredGender || "varied" : undefined, - ); - const [preferredTopics, setPreferredTopics] = useState( - user.type === "student" || user.type === "developer" ? user.preferredTopics : undefined, - ); + const [preferredGender, setPreferredGender] = useState< + InstructorGender | undefined + >( + user.type === "student" || user.type === "developer" + ? user.preferredGender || "varied" + : undefined + ); + const [preferredTopics, setPreferredTopics] = useState( + user.type === "student" || user.type === "developer" + ? user.preferredTopics + : undefined + ); - const [position, setPosition] = useState(user.type === "corporate" ? user.demographicInformation?.position : undefined); - const [corporateInformation, setCorporateInformation] = useState(user.type === "corporate" ? user.corporateInformation : undefined); - const [companyName, setCompanyName] = useState(user.type === "agent" ? user.agentInformation?.companyName : undefined); - const [commercialRegistration, setCommercialRegistration] = useState( - user.type === "agent" ? user.agentInformation?.commercialRegistration : undefined, - ); - const [arabName, setArabName] = useState(user.type === "agent" ? user.agentInformation?.companyArabName : undefined); + const [position, setPosition] = useState( + user.type === "corporate" + ? user.demographicInformation?.position + : undefined + ); + const [corporateInformation, setCorporateInformation] = useState( + user.type === "corporate" ? user.corporateInformation : undefined + ); + const [companyName, setCompanyName] = useState( + user.type === "agent" ? user.agentInformation?.companyName : undefined + ); + const [commercialRegistration, setCommercialRegistration] = useState< + string | undefined + >( + user.type === "agent" + ? user.agentInformation?.commercialRegistration + : undefined + ); + const [arabName, setArabName] = useState( + user.type === "agent" ? user.agentInformation?.companyArabName : undefined + ); - const [timezone, setTimezone] = useState(user.demographicInformation?.timezone || moment.tz.guess()); + const [timezone, setTimezone] = useState( + user.demographicInformation?.timezone || moment.tz.guess() + ); - const [isPreferredTopicsOpen, setIsPreferredTopicsOpen] = useState(false); + const [isPreferredTopicsOpen, setIsPreferredTopicsOpen] = useState(false); - const {groups} = useGroups(); - const {users} = useUsers(); + const { groups } = useGroups(); + const { users } = useUsers(); - const profilePictureInput = useRef(null); - const expirationDateColor = (date: Date) => { - const momentDate = moment(date); - const today = moment(new Date()); + const profilePictureInput = useRef(null); + const expirationDateColor = (date: Date) => { + const momentDate = moment(date); + const today = moment(new Date()); - if (today.add(1, "days").isAfter(momentDate)) return "!bg-mti-red-ultralight border-mti-red-light"; - if (today.add(3, "days").isAfter(momentDate)) return "!bg-mti-rose-ultralight border-mti-rose-light"; - if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light"; - }; + if (today.add(1, "days").isAfter(momentDate)) + return "!bg-mti-red-ultralight border-mti-red-light"; + if (today.add(3, "days").isAfter(momentDate)) + return "!bg-mti-rose-ultralight border-mti-rose-light"; + if (today.add(7, "days").isAfter(momentDate)) + return "!bg-mti-orange-ultralight border-mti-orange-light"; + }; - const uploadProfilePicture = async (event: ChangeEvent) => { - if (event.target.files && event.target.files[0]) { - const picture = event.target.files[0]; - const base64 = await convertBase64(picture); - setProfilePicture(base64 as string); - } - }; + const uploadProfilePicture = async (event: ChangeEvent) => { + if (event.target.files && event.target.files[0]) { + const picture = event.target.files[0]; + const base64 = await convertBase64(picture); + setProfilePicture(base64 as string); + } + }; - const updateUser = async () => { - setIsLoading(true); - if (email !== user?.email && !password) { - toast.error("To update your e-mail you need to input your password!"); - setIsLoading(false); - return; - } + const updateUser = async () => { + setIsLoading(true); + if (email !== user?.email && !password) { + toast.error("To update your e-mail you need to input your password!"); + setIsLoading(false); + return; + } - if (newPassword && !password) { - toast.error("To update your password you need to input your current one!"); - setIsLoading(false); - return; - } + if (newPassword && !password) { + toast.error( + "To update your password you need to input your current one!" + ); + setIsLoading(false); + return; + } - if (email !== user?.email) { - const userAdmins = groups.filter((x) => x.participants.includes(user.id)).map((x) => x.admin); - const message = - users.filter((x) => userAdmins.includes(x.id) && x.type === "corporate").length > 0 - ? "If you change your e-mail address, you will lose all benefits from your university/institute. Are you sure you want to continue?" - : "Are you sure you want to update your e-mail address?"; + if (email !== user?.email) { + const userAdmins = groups + .filter((x) => x.participants.includes(user.id)) + .map((x) => x.admin); + const message = + users.filter((x) => userAdmins.includes(x.id) && x.type === "corporate") + .length > 0 + ? "If you change your e-mail address, you will lose all benefits from your university/institute. Are you sure you want to continue?" + : "Are you sure you want to update your e-mail address?"; - if (!confirm(message)) { - setIsLoading(false); - return; - } - } + if (!confirm(message)) { + setIsLoading(false); + return; + } + } - axios - .post("/api/users/update", { - bio, - name, - email, - password, - newPassword, - profilePicture, - desiredLevels, - preferredGender, - preferredTopics, - focus, - demographicInformation: { - phone, - country, - employment: user?.type === "corporate" ? undefined : employment, - position: user?.type === "corporate" ? position : undefined, - gender, - passport_id, - timezone, - }, - ...(user.type === "corporate" ? {corporateInformation} : {}), - ...(user.type === "agent" - ? { - agentInformation: { - companyName, - commercialRegistration, - arabName, - }, - } - : {}), - }) - .then((response) => { - if (response.status === 200) { - toast.success("Your profile has been updated!"); - mutateUser((response.data as {user: User}).user); - setIsLoading(false); - return; - } - }) - .catch((error) => { - console.log(error); - toast.error((error.response.data as ErrorMessage).message); - }) - .finally(() => { - setIsLoading(false); - }); - }; + axios + .post("/api/users/update", { + bio, + name, + email, + password, + newPassword, + profilePicture, + desiredLevels, + preferredGender, + preferredTopics, + focus, + demographicInformation: { + phone, + country, + employment: user?.type === "corporate" ? undefined : employment, + position: user?.type === "corporate" ? position : undefined, + gender, + passport_id, + timezone, + }, + ...(user.type === "corporate" ? { corporateInformation } : {}), + ...(user.type === "agent" + ? { + agentInformation: { + companyName, + commercialRegistration, + arabName, + }, + } + : {}), + }) + .then((response) => { + if (response.status === 200) { + toast.success("Your profile has been updated!"); + mutateUser((response.data as { user: User }).user); + setIsLoading(false); + return; + } + }) + .catch((error) => { + console.log(error); + toast.error((error.response.data as ErrorMessage).message); + }) + .finally(() => { + setIsLoading(false); + }); + }; - const ExpirationDate = () => ( -
- - - {!user.subscriptionExpirationDate && "Unlimited"} - {user.subscriptionExpirationDate && moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} - -
- ); + const ExpirationDate = () => ( +
+ + + {!user.subscriptionExpirationDate && "Unlimited"} + {user.subscriptionExpirationDate && + moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} + +
+ ); - const TimezoneInput = () => ( -
- - -
- ); + const TimezoneInput = () => ( +
+ + +
+ ); - const manualDownloadLink = ["student", "teacher", "corporate"].includes(user.type) ? `/manuals/${user.type}.pdf` : ""; + const manualDownloadLink = ["student", "teacher", "corporate"].includes( + user.type + ) + ? `/manuals/${user.type}.pdf` + : ""; - return ( - -
-

Edit Profile

-
-
-

Edit Profile

-
e.preventDefault()}> - - {user.type !== "corporate" ? ( - setName(e)} - placeholder="Enter your name" - defaultValue={name} - required - /> - ) : ( - - setCorporateInformation((prev) => ({ - ...prev!, - companyInformation: { - ...prev!.companyInformation, - name: e, - }, - })) - } - placeholder="Enter your company's name" - defaultValue={corporateInformation?.companyInformation.name} - required - /> - )} + return ( + +
+

Edit Profile

+
+
+

Edit Profile

+ e.preventDefault()} + > + + {user.type !== "corporate" ? ( + setName(e)} + placeholder="Enter your name" + defaultValue={name} + required + /> + ) : ( + + setCorporateInformation((prev) => ({ + ...prev!, + companyInformation: { + ...prev!.companyInformation, + name: e, + }, + })) + } + placeholder="Enter your company's name" + defaultValue={corporateInformation?.companyInformation.name} + required + /> + )} - {user.type === "agent" && ( - setArabName(e)} - placeholder="Enter your arab name" - defaultValue={arabName} - required - /> - )} + {user.type === "agent" && ( + setArabName(e)} + placeholder="Enter your arab name" + defaultValue={arabName} + required + /> + )} - setEmail(e)} - placeholder="Enter email address" - defaultValue={email} - required - /> - - - setPassword(e)} - placeholder="Enter your password" - required - /> - setNewPassword(e)} - placeholder="Enter your new password (optional)" - /> - - {user.type === "agent" && ( -
- null} - placeholder="Enter your company's name" - defaultValue={companyName} - disabled - /> - null} - placeholder="Enter commercial registration" - defaultValue={commercialRegistration} - disabled - /> -
- )} + setEmail(e)} + placeholder="Enter email address" + defaultValue={email} + required + /> + + + setPassword(e)} + placeholder="Enter your password" + required + /> + setNewPassword(e)} + placeholder="Enter your new password (optional)" + /> + + {user.type === "agent" && ( +
+ null} + placeholder="Enter your company's name" + defaultValue={companyName} + disabled + /> + null} + placeholder="Enter commercial registration" + defaultValue={commercialRegistration} + disabled + /> +
+ )} - -
- - -
- setPhone(e)} - placeholder="Enter phone number" - defaultValue={phone} - required - /> -
+ +
+ + +
+ setPhone(e)} + placeholder="Enter phone number" + defaultValue={phone} + required + /> +
- {user.type === "student" ? ( - - setPassportID(e)} - placeholder="Enter National ID or Passport number" - value={passport_id} - required - /> - - - ) : ( - - )} + {user.type === "student" ? ( + + setPassportID(e)} + placeholder="Enter National ID or Passport number" + value={passport_id} + required + /> + + + ) : ( + + )} - + - {desiredLevels && ["developer", "student"].includes(user.type) && ( - <> -
- - >} - /> -
-
- -
- - -
-
- - )} + {desiredLevels && + ["developer", "student"].includes(user.type) && ( + <> +
+ + + > + } + /> +
+
+ +
+ + +
+
+ + )} - {preferredGender && ["developer", "student"].includes(user.type) && ( - <> - - -
- - + value + ? setPreferredGender( + value.value as InstructorGender + ) + : null + } + options={[ + { value: "male", label: "Male" }, + { value: "female", label: "Female" }, + { value: "varied", label: "Varied" }, + ]} + /> +
+
+ + +
+
- setIsPreferredTopicsOpen(false)} - selectTopics={setPreferredTopics} - initialTopics={preferredTopics || []} - /> + setIsPreferredTopicsOpen(false)} + selectTopics={setPreferredTopics} + initialTopics={preferredTopics || []} + /> - - - )} + + + )} - {user.type === "corporate" && ( - <> - - null} - label="Number of users" - defaultValue={user.corporateInformation.companyInformation.userAmount} - disabled - required - /> - null} - label="Pricing" - defaultValue={`${user.corporateInformation.payment?.value} ${user.corporateInformation.payment?.currency}`} - disabled - required - /> - - - - )} + {user.type === "corporate" && ( + <> + + null} + label="Number of users" + defaultValue={ + user.corporateInformation.companyInformation.userAmount + } + disabled + required + /> + null} + label="Pricing" + defaultValue={`${user.corporateInformation.payment?.value} ${user.corporateInformation.payment?.currency}`} + disabled + required + /> + + + + )} - {user.type === "corporate" && ( - <> - - - setName(e)} - placeholder="Enter your name" - defaultValue={name} - required - /> - - - - )} + {user.type === "corporate" && ( + <> + + + setName(e)} + placeholder="Enter your name" + defaultValue={name} + required + /> + + + + )} - {user.type === "corporate" && user.corporateInformation.referralAgent && ( - <> - - - null} - defaultValue={users.find((x) => x.id === user.corporateInformation.referralAgent)?.name} - type="text" - label="Country Manager's Name" - placeholder="Not available" - required - disabled - /> - null} - defaultValue={users.find((x) => x.id === user.corporateInformation.referralAgent)?.email} - type="text" - label="Country Manager's E-mail" - placeholder="Not available" - required - disabled - /> - - -
- - x.id === user.corporateInformation.referralAgent)?.demographicInformation - ?.country - } - onChange={() => null} - disabled - /> -
+ {user.type === "corporate" && + user.corporateInformation.referralAgent && ( + <> + + + null} + defaultValue={ + users.find( + (x) => + x.id === user.corporateInformation.referralAgent + )?.name + } + type="text" + label="Country Manager's Name" + placeholder="Not available" + required + disabled + /> + null} + defaultValue={ + users.find( + (x) => + x.id === user.corporateInformation.referralAgent + )?.email + } + type="text" + label="Country Manager's E-mail" + placeholder="Not available" + required + disabled + /> + + +
+ + + x.id === user.corporateInformation.referralAgent + )?.demographicInformation?.country + } + onChange={() => null} + disabled + /> +
- null} - placeholder="Not available" - defaultValue={ - users.find((x) => x.id === user.corporateInformation.referralAgent)?.demographicInformation?.phone - } - disabled - required - /> -
- - )} + null} + placeholder="Not available" + defaultValue={ + users.find( + (x) => + x.id === user.corporateInformation.referralAgent + )?.demographicInformation?.phone + } + disabled + required + /> +
+ + )} - {user.type !== "corporate" && ( - - + {user.type !== "corporate" && ( + + -
- - -
-
- )} - -
-
-
(profilePictureInput.current as any)?.click()}> -
-
- -
- {user.name} -
- - (profilePictureInput.current as any)?.click()} - className="cursor-pointer text-mti-purple-light text-sm"> - Change picture - -
{USER_TYPE_LABELS[user.type]}
-
- {user.type === "agent" && ( -
- {user.demographicInformation?.country.toLowerCase() -
- )} - {manualDownloadLink && ( - - - - )} -
-
-
- Bio -