diff --git a/src/components/UserCard.tsx b/src/components/UserCard.tsx index 93e9bac3..5c143f57 100644 --- a/src/components/UserCard.tsx +++ b/src/components/UserCard.tsx @@ -1,15 +1,15 @@ import useStats from "@/hooks/useStats"; -import {EMPLOYMENT_STATUS, User} from "@/interfaces/user"; -import {groupBySession, averageScore} from "@/utils/stats"; -import {RadioGroup} from "@headlessui/react"; +import { EMPLOYMENT_STATUS, User } 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, BsStar} from "react-icons/bs"; -import {toast} from "react-toastify"; +import { BsFileEarmarkText, BsPencil, 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,598 +17,770 @@ 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"; 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; + user: User; + loggedInUser: User; + onClose: (reload?: boolean) => void; + onViewStudents?: () => void; + onViewTeachers?: () => void; + onViewCorporate?: () => void; + disabled?: 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}: 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, +}: 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 [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 [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?.arabName : 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(); - 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, - } - : 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" }); + }); + }; - return ( - <> - , - 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", - }, - ]} - /> + return ( + <> + + ), + 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", + }, + ]} + /> - {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)} - /> - )} -
-
- {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={ + !["developer", "admin"].includes(loggedInUser.type) + } + /> + )} +
+
+ {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" && ( -
- - - {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" && ( +
+ + + {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} + /> +
+
+ + )} +
-
-
- {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/interfaces/user.ts b/src/interfaces/user.ts index 5a092e67..6d1cd2ef 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -1,150 +1,177 @@ -import {Module} from "."; -import {InstructorGender} from "./exam"; +import { Module } from "."; +import { InstructorGender } from "./exam"; -export type User = StudentUser | TeacherUser | CorporateUser | AgentUser | AdminUser | DeveloperUser; +export type User = + | StudentUser + | TeacherUser + | CorporateUser + | AgentUser + | AdminUser + | DeveloperUser; export type UserStatus = "active" | "disabled" | "paymentDue"; export interface BasicUser { - email: string; - name: string; - profilePicture: string; - id: string; - isFirstLogin: boolean; - focus: "academic" | "general"; - levels: {[key in Module]: number}; - desiredLevels: {[key in Module]: number}; - type: Type; - bio: string; - isVerified: boolean; - subscriptionExpirationDate?: null | Date; - registrationDate?: Date; - status: UserStatus; + email: string; + name: string; + profilePicture: string; + id: string; + isFirstLogin: boolean; + focus: "academic" | "general"; + levels: { [key in Module]: number }; + desiredLevels: { [key in Module]: number }; + type: Type; + bio: string; + isVerified: boolean; + subscriptionExpirationDate?: null | Date; + registrationDate?: Date; + status: UserStatus; } export interface StudentUser extends BasicUser { - type: "student"; - preferredGender?: InstructorGender; - demographicInformation?: DemographicInformation; - preferredTopics?: string[]; + type: "student"; + preferredGender?: InstructorGender; + demographicInformation?: DemographicInformation; + preferredTopics?: string[]; } export interface TeacherUser extends BasicUser { - type: "teacher"; - demographicInformation?: DemographicInformation; + type: "teacher"; + demographicInformation?: DemographicInformation; } export interface CorporateUser extends BasicUser { - type: "corporate"; - corporateInformation: CorporateInformation; - demographicInformation?: DemographicCorporateInformation; + type: "corporate"; + corporateInformation: CorporateInformation; + demographicInformation?: DemographicCorporateInformation; } export interface AgentUser extends BasicUser { - type: "agent"; - agentInformation: AgentInformation; - demographicInformation?: DemographicInformation; + type: "agent"; + agentInformation: AgentInformation; + demographicInformation?: DemographicInformation; } export interface AdminUser extends BasicUser { - type: "admin"; - demographicInformation?: DemographicInformation; + type: "admin"; + demographicInformation?: DemographicInformation; } export interface DeveloperUser extends BasicUser { - type: "developer"; - preferredGender?: InstructorGender; - demographicInformation?: DemographicInformation; - preferredTopics?: string[]; + type: "developer"; + preferredGender?: InstructorGender; + demographicInformation?: DemographicInformation; + preferredTopics?: string[]; } export interface CorporateInformation { - companyInformation: CompanyInformation; - monthlyDuration: number; - payment?: { - value: number; - currency: string; - commission: number; - }; - referralAgent?: string; + companyInformation: CompanyInformation; + monthlyDuration: number; + payment?: { + value: number; + currency: string; + commission: number; + }; + referralAgent?: string; } export interface AgentInformation { - companyName: string; - commercialRegistration: string; + companyName: string; + commercialRegistration: string; + arabName?: string; } export interface CompanyInformation { - name: string; - userAmount: number; + name: string; + userAmount: number; } export interface DemographicInformation { - country: string; - phone: string; - gender: Gender; - employment: EmploymentStatus; - passport_id?: string; - timezone?: string; + country: string; + phone: string; + gender: Gender; + employment: EmploymentStatus; + passport_id?: string; + timezone?: string; } export interface DemographicCorporateInformation { - country: string; - phone: string; - gender: Gender; - position: string; - timezone?: string; + country: string; + phone: string; + gender: Gender; + position: string; + timezone?: string; } export type Gender = "male" | "female" | "other"; -export type EmploymentStatus = "employed" | "student" | "self-employed" | "unemployed" | "retired" | "other"; -export const EMPLOYMENT_STATUS: {status: EmploymentStatus; label: string}[] = [ - {status: "student", label: "Student"}, - {status: "employed", label: "Employed"}, - {status: "unemployed", label: "Unemployed"}, - {status: "self-employed", label: "Self-employed"}, - {status: "retired", label: "Retired"}, - {status: "other", label: "Other"}, -]; +export type EmploymentStatus = + | "employed" + | "student" + | "self-employed" + | "unemployed" + | "retired" + | "other"; +export const EMPLOYMENT_STATUS: { status: EmploymentStatus; label: string }[] = + [ + { status: "student", label: "Student" }, + { status: "employed", label: "Employed" }, + { status: "unemployed", label: "Unemployed" }, + { status: "self-employed", label: "Self-employed" }, + { status: "retired", label: "Retired" }, + { status: "other", label: "Other" }, + ]; export interface Stat { - id: string; - user: string; - exam: string; - exercise: string; - session: string; - date: number; - module: Module; - solutions: any[]; - type: string; - timeSpent?: number; - assignment?: string; - score: { - correct: number; - total: number; - missing: number; - }; - isDisabled?: boolean; + id: string; + user: string; + exam: string; + exercise: string; + session: string; + date: number; + module: Module; + solutions: any[]; + type: string; + timeSpent?: number; + assignment?: string; + score: { + correct: number; + total: number; + missing: number; + }; + isDisabled?: boolean; } export interface Group { - admin: string; - name: string; - participants: string[]; - id: string; - disableEditing?: boolean; + admin: string; + name: string; + participants: string[]; + id: string; + disableEditing?: boolean; } export interface Code { - code: string; - creator: string; - expiryDate: Date; - type: Type; - creationDate?: string; - userId?: string; - email?: string; - name?: string; - passport_id?: string; + code: string; + creator: string; + expiryDate: Date; + type: Type; + creationDate?: string; + userId?: string; + email?: string; + name?: string; + passport_id?: string; } -export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent"; -export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent"]; +export type Type = + | "student" + | "teacher" + | "corporate" + | "admin" + | "developer" + | "agent"; +export const userTypes: Type[] = [ + "student", + "teacher", + "corporate", + "admin", + "developer", + "agent", +]; diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index f5dcc27b..ea1ab049 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -1,597 +1,780 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {ChangeEvent, Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState} from "react"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { + ChangeEvent, + Dispatch, + ReactNode, + SetStateAction, + useEffect, + useRef, + useState, +} from "react"; import useUser from "@/hooks/useUser"; -import {toast, ToastContainer} from "react-toastify"; +import { toast, ToastContainer } from "react-toastify"; import Layout from "@/components/High/Layout"; import Input from "@/components/Low/Input"; import Button from "@/components/Low/Button"; import Link from "next/link"; import axios from "axios"; -import {ErrorMessage} from "@/constants/errors"; +import { ErrorMessage } from "@/constants/errors"; import clsx from "clsx"; -import {CorporateUser, EmploymentStatus, EMPLOYMENT_STATUS, Gender, User} from "@/interfaces/user"; +import { + CorporateUser, + EmploymentStatus, + EMPLOYMENT_STATUS, + Gender, + User, +} from "@/interfaces/user"; import CountrySelect from "@/components/Low/CountrySelect"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; import moment from "moment"; -import {BsCamera, BsQuestionCircleFill} from "react-icons/bs"; -import {USER_TYPE_LABELS} from "@/resources/user"; +import { BsCamera, BsQuestionCircleFill } from "react-icons/bs"; +import { USER_TYPE_LABELS } from "@/resources/user"; import useGroups from "@/hooks/useGroups"; import useUsers from "@/hooks/useUsers"; -import {convertBase64} from "@/utils"; -import {Divider} from "primereact/divider"; +import { convertBase64 } from "@/utils"; +import { Divider } from "primereact/divider"; import GenderInput from "@/components/High/GenderInput"; import EmploymentStatusInput from "@/components/High/EmploymentStatusInput"; import TimezoneSelect from "@/components/Low/TImezoneSelect"; import Modal from "@/components/Modal"; -import {Module} from "@/interfaces"; +import { Module } from "@/interfaces"; import ModuleLevelSelector from "@/components/Medium/ModuleLevelSelector"; import Select from "@/components/Low/Select"; -import {InstructorGender} from "@/interfaces/exam"; -import {capitalize} from "lodash"; +import { InstructorGender } from "@/interfaces/exam"; +import { capitalize } from "lodash"; import TopicModal from "@/components/Medium/TopicModal"; -import {v4} from "uuid"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { - const user = req.session.user; +import { v4 } from "uuid"; +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)) { - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } + if (shouldRedirectHome(user)) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } - return { - props: {user: req.session.user}, - }; + return { + props: { user: req.session.user }, + }; }, sessionOptions); interface Props { - user: User; - mutateUser: Function; + user: User; + mutateUser: Function; } -const DoubleColumnRow = ({children}: {children: ReactNode}) =>
{children}
; +const DoubleColumnRow = ({ children }: { children: ReactNode }) => ( +
{children}
+); -function UserProfile({user, mutateUser}: Props) { - const [bio, setBio] = useState(user.bio || ""); - const [name, setName] = useState(user.name || ""); - const [email, setEmail] = useState(user.email || ""); - const [password, setPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [profilePicture, setProfilePicture] = useState(user.profilePicture); +function UserProfile({ user, mutateUser }: Props) { + const [bio, setBio] = useState(user.bio || ""); + const [name, setName] = useState(user.name || ""); + const [email, setEmail] = useState(user.email || ""); + const [password, setPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [profilePicture, setProfilePicture] = useState(user.profilePicture); - const [desiredLevels, setDesiredLevels] = useState<{[key in Module]: number} | undefined>( - ["developer", "student"].includes(user.type) ? user.desiredLevels : undefined, - ); + const [desiredLevels, setDesiredLevels] = useState< + { [key in Module]: number } | undefined + >( + ["developer", "student"].includes(user.type) + ? user.desiredLevels + : undefined, + ); - const [country, setCountry] = useState(user.demographicInformation?.country || ""); - const [phone, setPhone] = useState(user.demographicInformation?.phone || ""); - const [gender, setGender] = useState(user.demographicInformation?.gender || undefined); - const [employment, setEmployment] = useState( - user.type === "corporate" ? undefined : user.demographicInformation?.employment, - ); - const [passport_id, setPassportID] = useState(user.type === "student" ? user.demographicInformation?.passport_id : undefined); + const [country, setCountry] = useState( + user.demographicInformation?.country || "", + ); + const [phone, setPhone] = useState( + user.demographicInformation?.phone || "", + ); + const [gender, setGender] = useState( + user.demographicInformation?.gender || undefined, + ); + const [employment, setEmployment] = useState( + user.type === "corporate" + ? undefined + : user.demographicInformation?.employment, + ); + const [passport_id, setPassportID] = useState( + user.type === "student" + ? user.demographicInformation?.passport_id + : undefined, + ); - const [preferredGender, setPreferredGender] = useState( - user.type === "student" || user.type === "developer" ? user.preferredGender || "varied" : undefined, - ); - const [preferredTopics, setPreferredTopics] = useState( - user.type === "student" || user.type === "developer" ? user.preferredTopics : undefined, - ); + const [preferredGender, setPreferredGender] = useState< + InstructorGender | undefined + >( + user.type === "student" || user.type === "developer" + ? user.preferredGender || "varied" + : undefined, + ); + const [preferredTopics, setPreferredTopics] = useState( + user.type === "student" || user.type === "developer" + ? user.preferredTopics + : undefined, + ); - const [position, setPosition] = useState(user.type === "corporate" ? user.demographicInformation?.position : undefined); - const [corporateInformation, setCorporateInformation] = useState(user.type === "corporate" ? user.corporateInformation : undefined); - const [companyName, setCompanyName] = useState(user.type === "agent" ? user.agentInformation?.companyName : undefined); - const [commercialRegistration, setCommercialRegistration] = useState( - user.type === "agent" ? user.agentInformation?.commercialRegistration : undefined, - ); - const [timezone, setTimezone] = useState(user.demographicInformation?.timezone || moment.tz.guess()); + const [position, setPosition] = useState( + user.type === "corporate" + ? user.demographicInformation?.position + : undefined, + ); + const [corporateInformation, setCorporateInformation] = useState( + user.type === "corporate" ? user.corporateInformation : undefined, + ); + const [companyName, setCompanyName] = useState( + user.type === "agent" ? user.agentInformation?.companyName : undefined, + ); + const [commercialRegistration, setCommercialRegistration] = useState< + string | undefined + >( + user.type === "agent" + ? user.agentInformation?.commercialRegistration + : undefined, + ); + const [arabName, setArabName] = useState( + user.type === "agent" ? user.agentInformation?.arabName : undefined, + ); - const [isPreferredTopicsOpen, setIsPreferredTopicsOpen] = useState(false); + const [timezone, setTimezone] = useState( + user.demographicInformation?.timezone || moment.tz.guess(), + ); - const {groups} = useGroups(); - const {users} = useUsers(); + const [isPreferredTopicsOpen, setIsPreferredTopicsOpen] = useState(false); - const profilePictureInput = useRef(null); - const expirationDateColor = (date: Date) => { - const momentDate = moment(date); - const today = moment(new Date()); + const { groups } = useGroups(); + const { users } = useUsers(); - if (today.add(1, "days").isAfter(momentDate)) return "!bg-mti-red-ultralight border-mti-red-light"; - if (today.add(3, "days").isAfter(momentDate)) return "!bg-mti-rose-ultralight border-mti-rose-light"; - if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light"; - }; + const profilePictureInput = useRef(null); + const expirationDateColor = (date: Date) => { + const momentDate = moment(date); + const today = moment(new Date()); - const uploadProfilePicture = async (event: ChangeEvent) => { - if (event.target.files && event.target.files[0]) { - const picture = event.target.files[0]; - const base64 = await convertBase64(picture); - setProfilePicture(base64 as string); - } - }; + if (today.add(1, "days").isAfter(momentDate)) + return "!bg-mti-red-ultralight border-mti-red-light"; + if (today.add(3, "days").isAfter(momentDate)) + return "!bg-mti-rose-ultralight border-mti-rose-light"; + if (today.add(7, "days").isAfter(momentDate)) + return "!bg-mti-orange-ultralight border-mti-orange-light"; + }; - const updateUser = async () => { - setIsLoading(true); - if (email !== user?.email && !password) { - toast.error("To update your e-mail you need to input your password!"); - setIsLoading(false); - return; - } + const uploadProfilePicture = async (event: ChangeEvent) => { + if (event.target.files && event.target.files[0]) { + const picture = event.target.files[0]; + const base64 = await convertBase64(picture); + setProfilePicture(base64 as string); + } + }; - if (newPassword && !password) { - toast.error("To update your password you need to input your current one!"); - setIsLoading(false); - return; - } + const updateUser = async () => { + setIsLoading(true); + if (email !== user?.email && !password) { + toast.error("To update your e-mail you need to input your password!"); + setIsLoading(false); + return; + } - if (email !== user?.email) { - const userAdmins = groups.filter((x) => x.participants.includes(user.id)).map((x) => x.admin); - const message = - users.filter((x) => userAdmins.includes(x.id) && x.type === "corporate").length > 0 - ? "If you change your e-mail address, you will lose all benefits from your university/institute. Are you sure you want to continue?" - : "Are you sure you want to update your e-mail address?"; + if (newPassword && !password) { + toast.error( + "To update your password you need to input your current one!", + ); + setIsLoading(false); + return; + } - if (!confirm(message)) { - setIsLoading(false); - return; - } - } + if (email !== user?.email) { + const userAdmins = groups + .filter((x) => x.participants.includes(user.id)) + .map((x) => x.admin); + const message = + users.filter((x) => userAdmins.includes(x.id) && x.type === "corporate") + .length > 0 + ? "If you change your e-mail address, you will lose all benefits from your university/institute. Are you sure you want to continue?" + : "Are you sure you want to update your e-mail address?"; - axios - .post("/api/users/update", { - bio, - name, - email, - password, - newPassword, - profilePicture, - desiredLevels, - preferredGender, - preferredTopics, - demographicInformation: { - phone, - country, - employment: user?.type === "corporate" ? undefined : employment, - position: user?.type === "corporate" ? position : undefined, - gender, - passport_id, - timezone, - }, - ...(user.type === "corporate" ? {corporateInformation} : {}), - }) - .then((response) => { - if (response.status === 200) { - toast.success("Your profile has been updated!"); - mutateUser((response.data as {user: User}).user); - setIsLoading(false); - return; - } - }) - .catch((error) => { - console.log(error); - toast.error((error.response.data as ErrorMessage).message); - }) - .finally(() => { - setIsLoading(false); - }); - }; + if (!confirm(message)) { + setIsLoading(false); + return; + } + } - const ExpirationDate = () => ( -
- - - {!user.subscriptionExpirationDate && "Unlimited"} - {user.subscriptionExpirationDate && moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} - -
- ); + axios + .post("/api/users/update", { + bio, + name, + email, + password, + newPassword, + profilePicture, + desiredLevels, + preferredGender, + preferredTopics, + demographicInformation: { + phone, + country, + employment: user?.type === "corporate" ? undefined : employment, + position: user?.type === "corporate" ? position : undefined, + gender, + passport_id, + timezone, + }, + ...(user.type === "corporate" ? { corporateInformation } : {}), + ...(user.type === "agent" + ? { + agentInformation: { + companyName, + commercialRegistration, + arabName, + }, + } + : {}), + }) + .then((response) => { + if (response.status === 200) { + toast.success("Your profile has been updated!"); + mutateUser((response.data as { user: User }).user); + setIsLoading(false); + return; + } + }) + .catch((error) => { + console.log(error); + toast.error((error.response.data as ErrorMessage).message); + }) + .finally(() => { + setIsLoading(false); + }); + }; - const TimezoneInput = () => ( -
- - -
- ); + const ExpirationDate = () => ( +
+ + + {!user.subscriptionExpirationDate && "Unlimited"} + {user.subscriptionExpirationDate && + moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} + +
+ ); - const manualDownloadLink = ["student", "teacher", "corporate"].includes(user.type) ? `/manuals/${user.type}.pdf` : ""; + const TimezoneInput = () => ( +
+ + +
+ ); - return ( - -
-

Edit Profile

-
-
-

Edit Profile

-
- - {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 - /> - )} + const manualDownloadLink = ["student", "teacher", "corporate"].includes( + user.type, + ) + ? `/manuals/${user.type}.pdf` + : ""; - 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 corporate name" - defaultValue={companyName} - disabled - /> - null} - placeholder="Enter commercial registration" - defaultValue={commercialRegistration} - disabled - /> -
- )} + return ( + +
+

Edit Profile

+
+
+

Edit Profile

+ + + {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 + /> + )} - -
- - -
- setPhone(e)} - placeholder="Enter phone number" - defaultValue={phone} - required - /> -
+ {user.type === "agent" && ( + setArabName(e)} + placeholder="Enter your arab name" + defaultValue={arabName} + required + /> + )} - {user.type === "student" ? ( - - setPassportID(e)} - placeholder="Enter National ID or Passport number" - value={passport_id} - 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 + /> +
+ )} - + +
+ + +
+ setPhone(e)} + placeholder="Enter phone number" + defaultValue={phone} + required + /> +
- {desiredLevels && ["developer", "student"].includes(user.type) && ( -
- - >} - /> -
- )} + {user.type === "student" ? ( + + setPassportID(e)} + placeholder="Enter National ID or Passport number" + value={passport_id} + required + /> + + + ) : ( + + )} - {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" }, + ]} + /> +
+
+ + +
+
- {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 - /> - - - - )} + setIsPreferredTopicsOpen(false)} + selectTopics={setPreferredTopics} + initialTopics={preferredTopics || []} + /> - {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" && ( + <> + + 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 + /> + + + + )} - null} - placeholder="Not available" - defaultValue={ - users.find((x) => x.id === user.corporateInformation.referralAgent)?.demographicInformation?.phone - } - disabled - required - /> -
- - )} + {user.type === "corporate" && ( + <> + + + setName(e)} + placeholder="Enter your name" + defaultValue={name} + required + /> + + + + )} - {user.type !== "corporate" && ( - - + {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 + /> +
-
- - -
-
- )} - -
-
-
(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 -