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} />
["admin", "developer", "agent"].includes(x.type)) + .filter((x) => checkAccess(x, ["admin", "developer", "agent"])) .map((u) => ({ value: u.id, label: `${u.name} - ${u.email}`, })), ]} - disabled={user.type === "agent"} + disabled={checkAccess(user, ["agent"])} value={ assignedTo ? { diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index ac5712a6..df55e6d6 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -1,160 +1,215 @@ -import {User} from "@/interfaces/user"; -import {Dialog, Transition} from "@headlessui/react"; +import { User } from "@/interfaces/user"; +import { Dialog, Transition } from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; import Image from "next/image"; import Link from "next/link"; -import {useRouter} from "next/router"; -import {Fragment} from "react"; -import {BsXLg} from "react-icons/bs"; +import { useRouter } from "next/router"; +import { Fragment } from "react"; +import { BsXLg } from "react-icons/bs"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; interface Props { - isOpen: boolean; - onClose: () => void; - path: string; - user: User; - disableNavigation?: boolean; + isOpen: boolean; + onClose: () => void; + path: string; + user: User; + disableNavigation?: boolean; } -export default function MobileMenu({isOpen, onClose, path, user, disableNavigation}: Props) { - const router = useRouter(); +export default function MobileMenu({ + isOpen, + onClose, + path, + user, + disableNavigation, +}: Props) { + const router = useRouter(); - const logout = async () => { - axios.post("/api/logout").finally(() => { - setTimeout(() => router.reload(), 500); - }); - }; + const logout = async () => { + axios.post("/api/logout").finally(() => { + setTimeout(() => router.reload(), 500); + }); + }; - return ( - - - -
- + return ( + + + +
+ -
-
- - - - - EnCoach logo - -
- -
-
-
- - Dashboard - - {(user.type === "student" || user.type === "teacher" || user.type === "developer") && ( - <> - - Exams - - - Exercises - - - )} - - Stats - - - Record - - {["admin", "developer", "agent", "corporate", "mastercorporate"].includes(user.type) && ( - - Payment Record - - )} - {["admin", "developer", "corporate", "teacher", "mastercorporate"].includes(user.type) && ( - - Settings - - )} - {["admin", "developer", "agent"].includes(user.type) && ( - - Tickets - - )} - - Profile - +
+
+ + + + + EnCoach logo + +
+ +
+
+
+ + Dashboard + + {checkAccess(user, ["student", "teacher", "developer"]) && ( + <> + + Exams + + + Exercises + + + )} + + Stats + + + Record + + {checkAccess(user, [ + "admin", + "developer", + "agent", + "corporate", + "mastercorporate", + ]) && ( + + Payment Record + + )} + {checkAccess(user, [ + "admin", + "developer", + "corporate", + "teacher", + "mastercorporate", + ]) && ( + + Settings + + )} + {checkAccess(user, ["admin", "developer", "agent"]) && ( + + Tickets + + )} + + Profile + - - Logout - -
-
-
-
-
-
-
- ); + + Logout + +
+ +
+
+ + + + ); } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 16076f06..91d49c60 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,212 +1,382 @@ import clsx from "clsx"; -import {IconType} from "react-icons"; -import {MdSpaceDashboard} from "react-icons/md"; +import { IconType } from "react-icons"; +import { MdSpaceDashboard } from "react-icons/md"; import { - BsFileEarmarkText, - BsClockHistory, - BsPencil, - BsGraphUp, - BsChevronBarRight, - BsChevronBarLeft, - BsShieldFill, - BsCloudFill, - BsCurrencyDollar, - BsClipboardData, + BsFileEarmarkText, + BsClockHistory, + BsPencil, + BsGraphUp, + BsChevronBarRight, + BsChevronBarLeft, + BsShieldFill, + BsCloudFill, + BsCurrencyDollar, + BsClipboardData, + BsFileLock, } from "react-icons/bs"; -import {RiLogoutBoxFill} from "react-icons/ri"; -import {SlPencil} from "react-icons/sl"; -import {FaAward} from "react-icons/fa"; +import { RiLogoutBoxFill } from "react-icons/ri"; +import { SlPencil } from "react-icons/sl"; +import { FaAward } from "react-icons/fa"; import Link from "next/link"; -import {useRouter} from "next/router"; +import { useRouter } from "next/router"; import axios from "axios"; import FocusLayer from "@/components/FocusLayer"; -import {preventNavigation} from "@/utils/navigation.disabled"; -import {useEffect, useState} from "react"; +import { preventNavigation } from "@/utils/navigation.disabled"; +import { useEffect, useState } from "react"; import usePreferencesStore from "@/stores/preferencesStore"; -import {Type} from "@/interfaces/user"; +import { User } from "@/interfaces/user"; import useTicketsListener from "@/hooks/useTicketsListener"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; interface Props { - path: string; - navDisabled?: boolean; - focusMode?: boolean; - onFocusLayerMouseEnter?: () => 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/components/UserCard.tsx b/src/components/UserCard.tsx index 2b78b15a..49dad039 100644 --- a/src/components/UserCard.tsx +++ b/src/components/UserCard.tsx @@ -1,15 +1,27 @@ import useStats from "@/hooks/useStats"; -import {CorporateInformation, CorporateUser, EMPLOYMENT_STATUS, User} from "@/interfaces/user"; -import {groupBySession, averageScore} from "@/utils/stats"; -import {RadioGroup} from "@headlessui/react"; +import { + CorporateInformation, + CorporateUser, + EMPLOYMENT_STATUS, + User, + Type, +} from "@/interfaces/user"; +import { groupBySession, averageScore } from "@/utils/stats"; +import { RadioGroup } from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; import moment from "moment"; -import {Divider} from "primereact/divider"; -import {useEffect, useState} from "react"; +import { Divider } from "primereact/divider"; +import { useEffect, useState } from "react"; import ReactDatePicker from "react-datepicker"; -import {BsFileEarmarkText, BsPencil, BsPerson, BsPersonAdd, BsStar} from "react-icons/bs"; -import {toast} from "react-toastify"; +import { + BsFileEarmarkText, + BsPencil, + BsPerson, + BsPersonAdd, + BsStar, +} from "react-icons/bs"; +import { toast } from "react-toastify"; import Button from "./Low/Button"; import Checkbox from "./Low/Checkbox"; import CountrySelect from "./Low/CountrySelect"; @@ -17,638 +29,818 @@ import Input from "./Low/Input"; import ProfileSummary from "./ProfileSummary"; import Select from "react-select"; import useUsers from "@/hooks/useUsers"; -import {USER_TYPE_LABELS} from "@/resources/user"; -import {CURRENCIES} from "@/resources/paypal"; +import { USER_TYPE_LABELS } from "@/resources/user"; +import { CURRENCIES } from "@/resources/paypal"; import useCodes from "@/hooks/useCodes"; +import { checkAccess, getTypesOfUser } from "@/utils/permissions"; +import { PERMISSIONS } from "@/constants/userPermissions"; +import { PermissionType } from "@/interfaces/permissions"; const expirationDateColor = (date: Date) => { - const momentDate = moment(date); - const today = moment(new 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"; }; interface Props { - user: User; - loggedInUser: User; - onClose: (reload?: boolean) => void; - onViewStudents?: () => void; - onViewTeachers?: () => void; - onViewCorporate?: () => void; - disabled?: boolean; - disabledFields?: { - countryManager?: boolean; - }; + user: User; + loggedInUser: User; + onClose: (reload?: boolean) => void; + onViewStudents?: () => void; + onViewTeachers?: () => void; + onViewCorporate?: () => void; + disabled?: boolean; + disabledFields?: { + countryManager?: boolean; + }; } const USER_STATUS_OPTIONS = [ - { - value: "active", - label: "Active", - }, - { - value: "disabled", - label: "Disabled", - }, - { - value: "paymentDue", - label: "Payment Due", - }, + { + value: "active", + label: "Active", + }, + { + value: "disabled", + label: "Disabled", + }, + { + value: "paymentDue", + label: "Payment Due", + }, ]; const USER_TYPE_OPTIONS = Object.keys(USER_TYPE_LABELS).map((type) => ({ - value: type, - label: USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS], + value: type, + label: USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS], })); -const CURRENCIES_OPTIONS = CURRENCIES.map(({label, currency}) => ({ - value: currency, - label, +const CURRENCIES_OPTIONS = CURRENCIES.map(({ label, currency }) => ({ + value: currency, + label, })); -const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, onViewCorporate, disabled = false, disabledFields = {}}: Props) => { - const [expiryDate, setExpiryDate] = useState(user.subscriptionExpirationDate); - const [type, setType] = useState(user.type); - const [status, setStatus] = useState(user.status); - const [referralAgentLabel, setReferralAgentLabel] = useState(); - const [position, setPosition] = useState(user.type === "corporate" ? user.demographicInformation?.position : undefined); - const [passport_id, setPassportID] = useState(user.type === "student" ? user.demographicInformation?.passport_id : undefined); +const UserCard = ({ + user, + loggedInUser, + onClose, + onViewStudents, + onViewTeachers, + onViewCorporate, + disabled = false, + disabledFields = {}, +}: Props) => { + const [expiryDate, setExpiryDate] = useState( + user.subscriptionExpirationDate + ); + const [type, setType] = useState(user.type); + const [status, setStatus] = useState(user.status); + const [referralAgentLabel, setReferralAgentLabel] = useState(); + const [position, setPosition] = useState( + user.type === "corporate" + ? user.demographicInformation?.position + : undefined + ); + const [passport_id, setPassportID] = useState( + user.type === "student" + ? user.demographicInformation?.passport_id + : undefined + ); - const [referralAgent, setReferralAgent] = useState(user.type === "corporate" ? user.corporateInformation?.referralAgent : undefined); - const [companyName, setCompanyName] = useState( - user.type === "corporate" - ? user.corporateInformation?.companyInformation.name - : user.type === "agent" - ? user.agentInformation?.companyName - : undefined, - ); - const [arabName, setArabName] = useState(user.type === "agent" ? user.agentInformation?.companyArabName : undefined); - const [commercialRegistration, setCommercialRegistration] = useState( - user.type === "agent" ? user.agentInformation?.commercialRegistration : undefined, - ); - const [userAmount, setUserAmount] = useState(user.type === "corporate" ? user.corporateInformation?.companyInformation.userAmount : undefined); - const [paymentValue, setPaymentValue] = useState(user.type === "corporate" ? user.corporateInformation?.payment?.value : undefined); - const [paymentCurrency, setPaymentCurrency] = useState(user.type === "corporate" ? user.corporateInformation?.payment?.currency : "EUR"); - const [monthlyDuration, setMonthlyDuration] = useState(user.type === "corporate" ? user.corporateInformation?.monthlyDuration : undefined); - const [commissionValue, setCommission] = useState(user.type === "corporate" ? user.corporateInformation?.payment?.commission : undefined); - const {stats} = useStats(user.id); - const {users} = useUsers(); - const {codes} = useCodes(user.id); + const [referralAgent, setReferralAgent] = useState( + user.type === "corporate" + ? user.corporateInformation?.referralAgent + : undefined + ); + const [companyName, setCompanyName] = useState( + user.type === "corporate" + ? user.corporateInformation?.companyInformation.name + : user.type === "agent" + ? user.agentInformation?.companyName + : undefined + ); + const [arabName, setArabName] = useState( + user.type === "agent" ? user.agentInformation?.companyArabName : undefined + ); + const [commercialRegistration, setCommercialRegistration] = useState( + user.type === "agent" + ? user.agentInformation?.commercialRegistration + : undefined + ); + const [userAmount, setUserAmount] = useState( + user.type === "corporate" + ? user.corporateInformation?.companyInformation.userAmount + : undefined + ); + const [paymentValue, setPaymentValue] = useState( + user.type === "corporate" + ? user.corporateInformation?.payment?.value + : undefined + ); + const [paymentCurrency, setPaymentCurrency] = useState( + user.type === "corporate" + ? user.corporateInformation?.payment?.currency + : "EUR" + ); + const [monthlyDuration, setMonthlyDuration] = useState( + user.type === "corporate" + ? user.corporateInformation?.monthlyDuration + : undefined + ); + const [commissionValue, setCommission] = useState( + user.type === "corporate" + ? user.corporateInformation?.payment?.commission + : undefined + ); + const { stats } = useStats(user.id); + const { users } = useUsers(); + const { codes } = useCodes(user.id); - useEffect(() => { - if (users && users.length > 0) { - if (!referralAgent) { - setReferralAgentLabel("No manager"); - return; - } + useEffect(() => { + if (users && users.length > 0) { + if (!referralAgent) { + setReferralAgentLabel("No manager"); + return; + } - const agent = users.find((x) => x.id === referralAgent); - setReferralAgentLabel(`${agent?.name} - ${agent?.email}`); - } - }, [users, referralAgent]); + const agent = users.find((x) => x.id === referralAgent); + setReferralAgentLabel(`${agent?.name} - ${agent?.email}`); + } + }, [users, referralAgent]); - const updateUser = () => { - if (user.type === "corporate" && (!paymentValue || paymentValue < 0)) - return toast.error("Please set a price for the user's package before updating!"); - if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return; + const updateUser = () => { + if (user.type === "corporate" && (!paymentValue || paymentValue < 0)) + return toast.error( + "Please set a price for the user's package before updating!" + ); + if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) + return; - axios - .post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, { - ...user, - subscriptionExpirationDate: expiryDate, - type, - status, - agentInformation: - type === "agent" - ? { - companyName, - commercialRegistration, - arabName, - } - : undefined, - corporateInformation: - type === "corporate" - ? { - referralAgent, - monthlyDuration, - companyInformation: { - name: companyName, - userAmount, - }, - payment: { - value: paymentValue, - currency: paymentCurrency, - ...(referralAgent === "" ? {} : {commission: commissionValue}), - }, - } - : undefined, - }) - .then(() => { - toast.success("User updated successfully!"); - onClose(true); - }) - .catch(() => { - toast.error("Something went wrong!", {toastId: "update-error"}); - }); - }; + axios + .post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, { + ...user, + subscriptionExpirationDate: expiryDate, + type, + status, + agentInformation: + type === "agent" + ? { + companyName, + commercialRegistration, + arabName, + } + : undefined, + corporateInformation: + type === "corporate" + ? { + referralAgent, + monthlyDuration, + companyInformation: { + name: companyName, + userAmount, + }, + payment: { + value: paymentValue, + currency: paymentCurrency, + ...(referralAgent === "" + ? {} + : { commission: commissionValue }), + }, + } + : undefined, + }) + .then(() => { + toast.success("User updated successfully!"); + onClose(true); + }) + .catch(() => { + toast.error("Something went wrong!", { toastId: "update-error" }); + }); + }; - const generalProfileItems = [ - { - icon: , - value: Object.keys(groupBySession(stats)).length, - label: "Exams", - }, - { - icon: , - value: stats.length, - label: "Modules", - }, - { - icon: , - value: `${stats.length > 0 ? averageScore(stats) : 0}%`, - label: "Average Score", - }, - ]; + const generalProfileItems = [ + { + icon: ( + + ), + value: Object.keys(groupBySession(stats)).length, + label: "Exams", + }, + { + icon: , + value: stats.length, + label: "Modules", + }, + { + icon: , + value: `${stats.length > 0 ? averageScore(stats) : 0}%`, + label: "Average Score", + }, + ]; - const corporateProfileItems = - user.type === "corporate" - ? [ - { - icon: , - value: codes.length, - label: "Users Used", - }, - { - icon: , - value: user.corporateInformation.companyInformation.userAmount, - label: "Number of Users", - }, - ] - : []; + const corporateProfileItems = + user.type === "corporate" + ? [ + { + icon: ( + + ), + value: codes.length, + label: "Users Used", + }, + { + icon: ( + + ), + value: user.corporateInformation.companyInformation.userAmount, + label: "Number of Users", + }, + ] + : []; - return ( - <> - + const updateUserPermission = PERMISSIONS.updateUser[user.type] as { + list: Type[]; + perm: PermissionType; + }; + return ( + <> + - {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={!["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/constants/userPermissions.ts b/src/constants/userPermissions.ts index 0c167a09..af3e7543 100644 --- a/src/constants/userPermissions.ts +++ b/src/constants/userPermissions.ts @@ -12,25 +12,68 @@ export const PERMISSIONS = { developer: ["developer"], }, deleteUser: { - student: ["corporate", "developer", "admin", "mastercorporate"], - teacher: ["corporate", "developer", "admin", "mastercorporate"], - corporate: ["admin", "developer"], - mastercorporate: ["admin", "developer"], + student: { + perm: "deleteStudent", + list: ["corporate", "developer", "admin", "mastercorporate"], + }, + teacher: { + perm: "deleteTeacher", + list: ["corporate", "developer", "admin", "mastercorporate"], + }, + corporate: { + perm: "deleteCorporate", + list: ["admin", "developer"], + }, + mastercorporate: { + perm: undefined, + list: ["admin", "developer"], + }, - admin: ["developer", "admin"], - agent: ["developer", "admin"], - developer: ["developer"], + admin: { + perm: "deleteAdmin", + list: ["developer", "admin"], + }, + agent: { + perm: "deleteCountryManager", + list: ["developer", "admin"], + }, + developer: { + perm: undefined, + list: ["developer"], + }, }, updateUser: { - student: ["developer", "admin"], - teacher: ["developer", "admin"], + student: { + perm: "editStudent", + list: ["developer", "admin"], + }, + teacher: { + perm: "editTeacher", + list: ["developer", "admin"], + }, - corporate: ["admin", "developer"], - mastercorporate: ["admin", "developer"], + corporate: { + perm: "editCorporate", + list: ["admin", "developer"], + }, + mastercorporate: { + perm: undefined, + list: ["admin", "developer"], + }, - admin: ["developer", "admin"], - agent: ["developer", "admin"], - developer: ["developer"], + admin: { + perm: "editAdmin", + list: ["developer", "admin"], + }, + + agent: { + perm: "editCountryManager", + list: ["developer", "admin"], + }, + developer: { + perm: undefined, + list: ["developer"], + }, }, updateExpiryDate: { student: ["developer", "admin"], 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/(admin)/BatchCodeGenerator.tsx b/src/pages/(admin)/BatchCodeGenerator.tsx index 59fe79fc..7a0d5f52 100644 --- a/src/pages/(admin)/BatchCodeGenerator.tsx +++ b/src/pages/(admin)/BatchCodeGenerator.tsx @@ -1,249 +1,366 @@ import Button from "@/components/Low/Button"; import Checkbox from "@/components/Low/Checkbox"; -import {PERMISSIONS} from "@/constants/userPermissions"; +import { PERMISSIONS } from "@/constants/userPermissions"; import useUsers from "@/hooks/useUsers"; -import {Type, User} from "@/interfaces/user"; -import {USER_TYPE_LABELS} from "@/resources/user"; +import { Type, User } from "@/interfaces/user"; +import { USER_TYPE_LABELS } from "@/resources/user"; import axios from "axios"; import clsx from "clsx"; -import {capitalize, uniqBy} from "lodash"; +import { capitalize, uniqBy } from "lodash"; import moment from "moment"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import ReactDatePicker from "react-datepicker"; -import {toast} from "react-toastify"; +import { toast } from "react-toastify"; import ShortUniqueId from "short-unique-id"; -import {useFilePicker} from "use-file-picker"; +import { useFilePicker } from "use-file-picker"; import readXlsxFile from "read-excel-file"; import Modal from "@/components/Modal"; -import {BsFileEarmarkEaselFill, BsQuestionCircleFill} from "react-icons/bs"; +import { BsFileEarmarkEaselFill, BsQuestionCircleFill } from "react-icons/bs"; +import { checkAccess } from "@/utils/permissions"; +import { PermissionType } from "@/interfaces/permissions"; +const EMAIL_REGEX = new RegExp( + /^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/ +); -const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/); - -const USER_TYPE_PERMISSIONS: {[key in Type]: Type[]} = { - student: [], - teacher: [], - agent: [], - corporate: ["student", "teacher"], - mastercorporate: ["student", "teacher", "corporate"], - admin: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], - developer: ["student", "teacher", "agent", "corporate", "admin", "developer", "mastercorporate"], +const USER_TYPE_PERMISSIONS: { + [key in Type]: { perm: PermissionType | undefined; list: Type[] }; +} = { + student: { + perm: "createCodeStudent", + list: [], + }, + teacher: { + perm: "createCodeTeacher", + list: [], + }, + agent: { + perm: "createCodeCountryManager", + list: [], + }, + corporate: { + perm: "createCodeCorporate", + list: ["student", "teacher"], + }, + mastercorporate: { + perm: undefined, + list: ["student", "teacher", "corporate"], + }, + admin: { + perm: "createCodeAdmin", + list: [ + "student", + "teacher", + "agent", + "corporate", + "admin", + "mastercorporate", + ], + }, + developer: { + perm: undefined, + list: [ + "student", + "teacher", + "agent", + "corporate", + "admin", + "developer", + "mastercorporate", + ], + }, }; -export default function BatchCodeGenerator({user}: {user: User}) { - const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]); - const [isLoading, setIsLoading] = useState(false); - const [expiryDate, setExpiryDate] = useState( - user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, - ); - const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); - const [type, setType] = useState("student"); - const [showHelp, setShowHelp] = useState(false); +export default function BatchCodeGenerator({ user }: { user: User }) { + const [infos, setInfos] = useState< + { email: string; name: string; passport_id: string }[] + >([]); + const [isLoading, setIsLoading] = useState(false); + const [expiryDate, setExpiryDate] = useState( + user?.subscriptionExpirationDate + ? moment(user.subscriptionExpirationDate).toDate() + : null + ); + const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); + const [type, setType] = useState("student"); + const [showHelp, setShowHelp] = useState(false); - const {users} = useUsers(); + const { users } = useUsers(); - const {openFilePicker, filesContent, clear} = useFilePicker({ - accept: ".xlsx", - multiple: false, - readAs: "ArrayBuffer", - }); + const { openFilePicker, filesContent, clear } = useFilePicker({ + accept: ".xlsx", + multiple: false, + readAs: "ArrayBuffer", + }); - useEffect(() => { - if (!isExpiryDateEnabled) setExpiryDate(null); - }, [isExpiryDateEnabled]); + useEffect(() => { + if (!isExpiryDateEnabled) setExpiryDate(null); + }, [isExpiryDateEnabled]); - useEffect(() => { - if (filesContent.length > 0) { - const file = filesContent[0]; - readXlsxFile(file.content).then((rows) => { - try { - const information = uniqBy( - rows - .map((row) => { - const [firstName, lastName, country, passport_id, email, ...phone] = row as string[]; - return EMAIL_REGEX.test(email.toString().trim()) - ? { - email: email.toString().trim().toLowerCase(), - name: `${firstName ?? ""} ${lastName ?? ""}`.trim(), - passport_id: passport_id?.toString().trim() || undefined, - } - : undefined; - }) - .filter((x) => !!x) as typeof infos, - (x) => x.email, - ); + useEffect(() => { + if (filesContent.length > 0) { + const file = filesContent[0]; + readXlsxFile(file.content).then((rows) => { + try { + const information = uniqBy( + rows + .map((row) => { + const [ + firstName, + lastName, + country, + passport_id, + email, + ...phone + ] = row as string[]; + return EMAIL_REGEX.test(email.toString().trim()) + ? { + email: email.toString().trim().toLowerCase(), + name: `${firstName ?? ""} ${lastName ?? ""}`.trim(), + passport_id: passport_id?.toString().trim() || undefined, + } + : undefined; + }) + .filter((x) => !!x) as typeof infos, + (x) => x.email + ); - if (information.length === 0) { - toast.error( - "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!", - ); - return clear(); - } + if (information.length === 0) { + toast.error( + "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!" + ); + return clear(); + } - setInfos(information); - } catch { - toast.error( - "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!", - ); - return clear(); - } - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filesContent]); + setInfos(information); + } catch { + toast.error( + "Please upload an Excel file containing user information, one per line! All already registered e-mails have also been ignored!" + ); + return clear(); + } + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filesContent]); - const generateAndInvite = async () => { - const newUsers = infos.filter((x) => !users.map((u) => u.email).includes(x.email)); - const existingUsers = infos - .filter((x) => users.map((u) => u.email).includes(x.email)) - .map((i) => users.find((u) => u.email === i.email)) - .filter((x) => !!x && x.type === "student") as User[]; + const generateAndInvite = async () => { + const newUsers = infos.filter( + (x) => !users.map((u) => u.email).includes(x.email) + ); + const existingUsers = infos + .filter((x) => users.map((u) => u.email).includes(x.email)) + .map((i) => users.find((u) => u.email === i.email)) + .filter((x) => !!x && x.type === "student") as User[]; - const newUsersSentence = newUsers.length > 0 ? `generate ${newUsers.length} code(s)` : undefined; - const existingUsersSentence = existingUsers.length > 0 ? `invite ${existingUsers.length} registered student(s)` : undefined; - if ( - !confirm( - `You are about to ${[newUsersSentence, existingUsersSentence].filter((x) => !!x).join(" and ")}, are you sure you want to continue?`, - ) - ) - return; + const newUsersSentence = + newUsers.length > 0 ? `generate ${newUsers.length} code(s)` : undefined; + const existingUsersSentence = + existingUsers.length > 0 + ? `invite ${existingUsers.length} registered student(s)` + : undefined; + if ( + !confirm( + `You are about to ${[newUsersSentence, existingUsersSentence] + .filter((x) => !!x) + .join(" and ")}, are you sure you want to continue?` + ) + ) + return; - setIsLoading(true); - Promise.all(existingUsers.map(async (u) => await axios.post(`/api/invites`, {to: u.id, from: user.id}))) - .then(() => toast.success(`Successfully invited ${existingUsers.length} registered student(s)!`)) - .finally(() => { - if (newUsers.length === 0) setIsLoading(false); - }); + setIsLoading(true); + Promise.all( + existingUsers.map( + async (u) => + await axios.post(`/api/invites`, { to: u.id, from: user.id }) + ) + ) + .then(() => + toast.success( + `Successfully invited ${existingUsers.length} registered student(s)!` + ) + ) + .finally(() => { + if (newUsers.length === 0) setIsLoading(false); + }); - if (newUsers.length > 0) generateCode(type, newUsers); - setInfos([]); - }; + if (newUsers.length > 0) generateCode(type, newUsers); + setInfos([]); + }; - const generateCode = (type: Type, informations: typeof infos) => { - const uid = new ShortUniqueId(); - const codes = informations.map(() => uid.randomUUID(6)); + const generateCode = (type: Type, informations: typeof infos) => { + const uid = new ShortUniqueId(); + const codes = informations.map(() => uid.randomUUID(6)); - setIsLoading(true); - axios - .post<{ok: boolean; valid?: number; reason?: string}>("/api/code", { - type, - codes, - infos: informations, - expiryDate, - }) - .then(({data, status}) => { - if (data.ok) { - toast.success( - `Successfully generated${data.valid ? ` ${data.valid}/${informations.length}` : ""} ${capitalize( - type, - )} codes and they have been notified by e-mail!`, - {toastId: "success"}, - ); - return; - } + setIsLoading(true); + axios + .post<{ ok: boolean; valid?: number; reason?: string }>("/api/code", { + type, + codes, + infos: informations, + expiryDate, + }) + .then(({ data, status }) => { + if (data.ok) { + toast.success( + `Successfully generated${ + data.valid ? ` ${data.valid}/${informations.length}` : "" + } ${capitalize(type)} codes and they have been notified by e-mail!`, + { toastId: "success" } + ); + return; + } - if (status === 403) { - toast.error(data.reason, {toastId: "forbidden"}); - } - }) - .catch(({response: {status, data}}) => { - if (status === 403) { - toast.error(data.reason, {toastId: "forbidden"}); - return; - } + if (status === 403) { + toast.error(data.reason, { toastId: "forbidden" }); + } + }) + .catch(({ response: { status, data } }) => { + if (status === 403) { + toast.error(data.reason, { toastId: "forbidden" }); + return; + } - toast.error(`Something went wrong, please try again later!`, { - toastId: "error", - }); - }) - .finally(() => { - setIsLoading(false); - return clear(); - }); - }; + toast.error(`Something went wrong, please try again later!`, { + toastId: "error", + }); + }) + .finally(() => { + setIsLoading(false); + return clear(); + }); + }; - return ( - <> - setShowHelp(false)} title="Excel File Format"> -
- Please upload an Excel file with the following format: - - - - - - - - - - - -
First NameLast NameCountryPassport/National IDE-mailPhone Number
- - Notes: -
    -
  • - All incorrect e-mails will be ignored;
  • -
  • - All already registered e-mails will be ignored;
  • -
  • - You may have a header row with the format above, however, it is not necessary;
  • -
  • - All of the e-mails in the file will receive an e-mail to join EnCoach with the role selected below.
  • -
-
-
-
-
-
- -
setShowHelp(true)}> - -
-
- - {user && (["developer","admin","corporate", "mastercorporate"].includes(user.type)) && ( - <> -
- - - Enabled - -
- {isExpiryDateEnabled && ( - - moment(date).isAfter(new Date()) && - (user.subscriptionExpirationDate ? moment(date).isBefore(user.subscriptionExpirationDate) : true) - } - dateFormat="dd/MM/yyyy" - selected={expiryDate} - onChange={(date) => setExpiryDate(date)} - /> - )} - - )} - - {user && ( - - )} - -
- - ); + return ( + <> + setShowHelp(false)} + title="Excel File Format" + > +
+ Please upload an Excel file with the following format: + + + + + + + + + + + +
+ First Name + + Last Name + Country + Passport/National ID + E-mail + Phone Number +
+ + Notes: +
    +
  • - All incorrect e-mails will be ignored;
  • +
  • - All already registered e-mails will be ignored;
  • +
  • + - You may have a header row with the format above, however, it + is not necessary; +
  • +
  • + - All of the e-mails in the file will receive an e-mail to join + EnCoach with the role selected below. +
  • +
+
+
+
+
+
+ +
setShowHelp(true)} + > + +
+
+ + {user && + checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && ( + <> +
+ + + Enabled + +
+ {isExpiryDateEnabled && ( + + moment(date).isAfter(new Date()) && + (user.subscriptionExpirationDate + ? moment(date).isBefore(user.subscriptionExpirationDate) + : true) + } + dateFormat="dd/MM/yyyy" + selected={expiryDate} + onChange={(date) => setExpiryDate(date)} + /> + )} + + )} + + {user && ( + + )} + +
+ + ); } diff --git a/src/pages/(admin)/CodeGenerator.tsx b/src/pages/(admin)/CodeGenerator.tsx index 6a75574f..30c5007a 100644 --- a/src/pages/(admin)/CodeGenerator.tsx +++ b/src/pages/(admin)/CodeGenerator.tsx @@ -1,126 +1,197 @@ import Button from "@/components/Low/Button"; import Checkbox from "@/components/Low/Checkbox"; -import {PERMISSIONS} from "@/constants/userPermissions"; -import {Type, User} from "@/interfaces/user"; -import {USER_TYPE_LABELS} from "@/resources/user"; +import { PERMISSIONS } from "@/constants/userPermissions"; +import { Type, User } from "@/interfaces/user"; +import { USER_TYPE_LABELS } from "@/resources/user"; import axios from "axios"; import clsx from "clsx"; -import {capitalize} from "lodash"; +import { capitalize } from "lodash"; import moment from "moment"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import ReactDatePicker from "react-datepicker"; -import {toast} from "react-toastify"; +import { toast } from "react-toastify"; import ShortUniqueId from "short-unique-id"; +import { checkAccess } from "@/utils/permissions"; +import { PermissionType } from "@/interfaces/permissions"; -const USER_TYPE_PERMISSIONS: {[key in Type]: Type[]} = { - student: [], - teacher: [], - agent: [], - corporate: ["student", "teacher"], - mastercorporate: ["student", "teacher", "corporate"], - admin: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], - developer: ["student", "teacher", "agent", "corporate", "admin", "developer","mastercorporate"], +const USER_TYPE_PERMISSIONS: { + [key in Type]: { perm: PermissionType | undefined; list: Type[] }; +} = { + student: { + perm: "createCodeStudent", + list: [], + }, + teacher: { + perm: "createCodeTeacher", + list: [], + }, + agent: { + perm: "createCodeCountryManager", + list: [], + }, + corporate: { + perm: "createCodeCorporate", + list: ["student", "teacher"], + }, + mastercorporate: { + perm: undefined, + list: ["student", "teacher", "corporate"], + }, + admin: { + perm: "createCodeAdmin", + list: [ + "student", + "teacher", + "agent", + "corporate", + "admin", + "mastercorporate", + ], + }, + developer: { + perm: undefined, + list: [ + "student", + "teacher", + "agent", + "corporate", + "admin", + "developer", + "mastercorporate", + ], + }, }; -export default function CodeGenerator({user}: {user: User}) { - const [generatedCode, setGeneratedCode] = useState(); - const [expiryDate, setExpiryDate] = useState( - user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, - ); - const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); - const [type, setType] = useState("student"); +export default function CodeGenerator({ user }: { user: User }) { + const [generatedCode, setGeneratedCode] = useState(); + const [expiryDate, setExpiryDate] = useState( + user?.subscriptionExpirationDate + ? moment(user.subscriptionExpirationDate).toDate() + : null + ); + const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); + const [type, setType] = useState("student"); - useEffect(() => { - if (!isExpiryDateEnabled) setExpiryDate(null); - }, [isExpiryDateEnabled]); + useEffect(() => { + if (!isExpiryDateEnabled) setExpiryDate(null); + }, [isExpiryDateEnabled]); - const generateCode = (type: Type) => { - const uid = new ShortUniqueId(); - const code = uid.randomUUID(6); + const generateCode = (type: Type) => { + const uid = new ShortUniqueId(); + const code = uid.randomUUID(6); - axios - .post("/api/code", {type, codes: [code], expiryDate}) - .then(({data, status}) => { - if (data.ok) { - toast.success(`Successfully generated a ${capitalize(type)} code!`, {toastId: "success"}); - setGeneratedCode(code); - return; - } + axios + .post("/api/code", { type, codes: [code], expiryDate }) + .then(({ data, status }) => { + if (data.ok) { + toast.success(`Successfully generated a ${capitalize(type)} code!`, { + toastId: "success", + }); + setGeneratedCode(code); + return; + } - if (status === 403) { - toast.error(data.reason, {toastId: "forbidden"}); - } - }) - .catch(({response: {status, data}}) => { - if (status === 403) { - toast.error(data.reason, {toastId: "forbidden"}); - return; - } + if (status === 403) { + toast.error(data.reason, { toastId: "forbidden" }); + } + }) + .catch(({ response: { status, data } }) => { + if (status === 403) { + toast.error(data.reason, { toastId: "forbidden" }); + return; + } - toast.error(`Something went wrong, please try again later!`, {toastId: "error"}); - }); - }; + toast.error(`Something went wrong, please try again later!`, { + toastId: "error", + }); + }); + }; - return ( -
- - {user && ( - - )} - {user && (user.type === "developer" || user.type === "admin" || user.type === "corporate") && ( - <> -
- - - Enabled - -
- {isExpiryDateEnabled && ( - - moment(date).isAfter(new Date()) && - (user.subscriptionExpirationDate ? moment(date).isBefore(user.subscriptionExpirationDate) : true) - } - dateFormat="dd/MM/yyyy" - selected={expiryDate} - onChange={(date) => setExpiryDate(date)} - /> - )} - - )} - - -
{ - if (generatedCode) navigator.clipboard.writeText(generatedCode); - }}> - {generatedCode} -
- {generatedCode && Give this code to the user to complete their registration} -
- ); + return ( +
+ + {user && ( + + )} + {user && + checkAccess(user, ["developer", "admin", "corporate"]) && ( + <> +
+ + + Enabled + +
+ {isExpiryDateEnabled && ( + + moment(date).isAfter(new Date()) && + (user.subscriptionExpirationDate + ? moment(date).isBefore(user.subscriptionExpirationDate) + : true) + } + dateFormat="dd/MM/yyyy" + selected={expiryDate} + onChange={(date) => setExpiryDate(date)} + /> + )} + + )} + + +
{ + if (generatedCode) navigator.clipboard.writeText(generatedCode); + }} + > + {generatedCode} +
+ {generatedCode && ( + + Give this code to the user to complete their registration + + )} +
+ ); } diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 44bf67fd..430bd542 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -43,7 +43,8 @@ import { useListSearch } from "@/hooks/useListSearch"; import { getUserCorporate } from "@/utils/groups"; import { asyncSorter } from "@/utils"; import { exportListToExcel, UserListRow } from "@/utils/users"; - +import { checkAccess } from "@/utils/permissions"; +import { PermissionType } from "@/interfaces/permissions"; const columnHelper = createColumnHelper(); const searchFields = [ ["name"], @@ -92,7 +93,7 @@ export default function UserList({ const { users, reload } = useUsers(); const { groups } = useGroups( - user && (['corporate', 'teacher', 'mastercorporate'].includes(user?.type)) + user && ["corporate", "teacher", "mastercorporate"].includes(user?.type) ? user.id : undefined ); @@ -231,9 +232,21 @@ export default function UserList({ }; const actionColumn = ({ row }: { row: { original: User } }) => { + const updateUserPermission = PERMISSIONS.updateUser[row.original.type] as { + list: Type[]; + perm: PermissionType; + }; + const deleteUserPermission = PERMISSIONS.deleteUser[row.original.type] as { + list: Type[]; + perm: PermissionType; + }; return (
- {PERMISSIONS.updateUser[row.original.type]?.includes(user.type) && ( + {checkAccess( + user, + updateUserPermission.list, + updateUserPermission.perm + ) && (
@@ -297,7 +310,11 @@ export default function UserList({ )} {!row.original.isVerified && - PERMISSIONS.updateUser[row.original.type]?.includes(user.type) && ( + checkAccess( + user, + updateUserPermission.list, + updateUserPermission.perm + ) && (
)} - {PERMISSIONS.updateUser[row.original.type]?.includes(user.type) && ( + {checkAccess( + user, + updateUserPermission.list, + updateUserPermission.perm + ) && (
)} - {PERMISSIONS.deleteUser[row.original.type]?.includes(user.type) && ( + {checkAccess( + user, + deleteUserPermission.list, + deleteUserPermission.perm + ) && (
(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.list.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/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 601d4838..9a824ded 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,26 +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 Sidebar from "@/components/Sidebar"; 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"; @@ -29,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/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} +

+
+ 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 -