ENCOA-126: Corporate should not be allowed to edit is own name

This commit is contained in:
Tiago Ribeiro
2024-08-29 13:18:11 +01:00
parent cd1caf0f53
commit 2b71f2467c
6 changed files with 203 additions and 245 deletions

View File

@@ -2,7 +2,7 @@
import Modal from "@/components/Modal";
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import useUsers from "@/hooks/useUsers";
import {CorporateUser, Group, Stat, User} from "@/interfaces/user";
import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user";
import UserList from "@/pages/(admin)/Lists/UserList";
import {dateSorter} from "@/utils";
import moment from "moment";
@@ -54,6 +54,7 @@ import useUserBalance from "@/hooks/useUserBalance";
interface Props {
user: CorporateUser;
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
type StudentPerformanceItem = User & {corporateName: string; group: string};
@@ -154,11 +155,10 @@ const StudentPerformanceList = ({items, stats, users}: {items: StudentPerformanc
);
};
export default function CorporateDashboard({user}: Props) {
export default function CorporateDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const [corporateUserToShow, setCorporateUserToShow] = useState<CorporateUser>();
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
@@ -193,11 +193,6 @@ export default function CorporateDashboard({user}: Props) {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
useEffect(() => {
// in this case it fetches the master corporate account
getUserCorporate(user.id).then(setCorporateUserToShow);
}, [user]);
const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id);
const teacherFilter = (user: User) => user.type === "teacher" && groups.flatMap((g) => g.participants).includes(user.id);
@@ -455,9 +450,9 @@ export default function CorporateDashboard({user}: Props) {
const DefaultDashboard = () => (
<>
{corporateUserToShow && (
{!!linkedCorporate && (
<div className="absolute top-4 right-4 bg-neutral-200 px-2 rounded-lg py-1">
Linked to: <b>{corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name}</b>
Linked to: <b>{linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name}</b>
</div>
)}
<section className="grid grid-cols-5 -md:grid-cols-2 gap-4 text-center">

View File

@@ -9,7 +9,7 @@ import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import useUsers from "@/hooks/useUsers";
import {Invite} from "@/interfaces/invite";
import {Assignment} from "@/interfaces/results";
import {CorporateUser, Stat, User} from "@/interfaces/user";
import {CorporateUser, MasterCorporateUser, Stat, User} from "@/interfaces/user";
import useExamStore from "@/stores/examStore";
import {getExamById} from "@/utils/exams";
import {getUserCorporate} from "@/utils/groups";
@@ -30,11 +30,10 @@ import {toast} from "react-toastify";
interface Props {
user: User;
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
export default function StudentDashboard({user}: Props) {
const [corporateUserToShow, setCorporateUserToShow] = useState<CorporateUser>();
export default function StudentDashboard({user, linkedCorporate}: Props) {
const {users} = useUsers();
const {gradingSystem} = useGradingSystem();
const {data: stats} = useFilterRecordsByUser<Stat[]>(user.id, !user?.id);
@@ -49,10 +48,6 @@ export default function StudentDashboard({user}: Props) {
const setSelectedModules = useExamStore((state) => state.setSelectedModules);
const setAssignment = useExamStore((state) => state.setAssignment);
useEffect(() => {
getUserCorporate(user.id).then(setCorporateUserToShow);
}, [user]);
const startAssignment = (assignment: Assignment) => {
const examPromises = assignment.exams.filter((e) => e.assignee === user.id).map((e) => getExamById(e.module, e.id));
@@ -76,9 +71,9 @@ export default function StudentDashboard({user}: Props) {
return (
<>
{corporateUserToShow && (
{linkedCorporate && (
<div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1">
Linked to: <b>{corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name}</b>
Linked to: <b>{linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name}</b>
</div>
)}
<ProfileSummary

View File

@@ -2,7 +2,7 @@
import Modal from "@/components/Modal";
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import useUsers from "@/hooks/useUsers";
import {CorporateUser, Group, Stat, User} from "@/interfaces/user";
import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user";
import UserList from "@/pages/(admin)/Lists/UserList";
import {dateSorter} from "@/utils";
import moment from "moment";
@@ -52,15 +52,15 @@ import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter,
interface Props {
user: User;
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
export default function TeacherDashboard({user}: Props) {
export default function TeacherDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const [corporateUserToShow, setCorporateUserToShow] = useState<CorporateUser>();
const {data: stats} = useFilterRecordsByUser<Stat[]>();
const {users, reload} = useUsers();
@@ -89,10 +89,6 @@ export default function TeacherDashboard({user}: Props) {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
useEffect(() => {
getUserCorporate(user.id).then(setCorporateUserToShow);
}, [user]);
const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id);
const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id);
@@ -290,15 +286,15 @@ export default function TeacherDashboard({user}: Props) {
const DefaultDashboard = () => (
<>
{corporateUserToShow && (
{linkedCorporate && (
<div className="absolute top-4 right-4 bg-neutral-200 px-2 rounded-lg py-1">
Linked to: <b>{corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name}</b>
Linked to: <b>{linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name}</b>
</div>
)}
<section
className={clsx(
"flex -lg:flex-wrap gap-4 items-center -lg:justify-center lg:justify-start text-center",
!!corporateUserToShow && "mt-12 xl:mt-6",
!!linkedCorporate && "mt-12 xl:mt-6",
)}>
<IconCard
onClick={() => setPage("students")}

View File

@@ -1,32 +1,24 @@
/* 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 { averageScore, groupBySession, totalExams } from "@/utils/stats";
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 {averageScore, groupBySession, totalExams} from "@/utils/stats";
import useUser from "@/hooks/useUser";
import Diagnostic from "@/components/Diagnostic";
import { ToastContainer } from "react-toastify";
import { capitalize } from "lodash";
import { Module } from "@/interfaces";
import {ToastContainer} from "react-toastify";
import {capitalize} from "lodash";
import {Module} from "@/interfaces";
import ProgressBar from "@/components/Low/ProgressBar";
import Layout from "@/components/High/Layout";
import { calculateAverageLevel } from "@/utils/score";
import {calculateAverageLevel} from "@/utils/score";
import axios from "axios";
import DemographicInformationInput from "@/components/DemographicInformationInput";
import moment from "moment";
import Link from "next/link";
import { MODULE_ARRAY } from "@/utils/moduleUtils";
import {MODULE_ARRAY} from "@/utils/moduleUtils";
import ProfileSummary from "@/components/ProfileSummary";
import StudentDashboard from "@/dashboards/Student";
import AdminDashboard from "@/dashboards/Admin";
@@ -35,22 +27,18 @@ 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 { checkAccess, getTypesOfUser } from "@/utils/permissions";
import {USER_TYPE_LABELS} from "@/resources/user";
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import {getUserCorporate} from "@/utils/groups.be";
export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user;
const envVariables: { [key: string]: string } = {};
const envVariables: {[key: string]: string} = {};
Object.keys(process.env)
.filter((x) => x.startsWith("NEXT_PUBLIC"))
.forEach((x: string) => {
@@ -66,22 +54,25 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
};
}
const linkedCorporate = await getUserCorporate(user.id);
return {
props: { user: req.session.user, envVariables },
props: {user, envVariables, linkedCorporate},
};
}, sessionOptions);
interface Props {
user: any;
envVariables: { [key: string]: string };
envVariables: {[key: string]: string};
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
export default function Home(props: Props) {
const { envVariables } = props;
export default function Home({linkedCorporate}: Props) {
const [showDiagnostics, setShowDiagnostics] = useState(false);
const [showDemographicInput, setShowDemographicInput] = useState(false);
const [selectedScreen, setSelectedScreen] = useState<Type>("admin");
const { user, mutateUser } = useUser({ redirectTo: "/login" });
const {user, mutateUser} = useUser({redirectTo: "/login"});
const router = useRouter();
useEffect(() => {
@@ -90,7 +81,7 @@ export default function Home(props: Props) {
!user.demographicInformation ||
!user.demographicInformation.country ||
!user.demographicInformation.gender ||
!user.demographicInformation.phone
!user.demographicInformation.phone,
);
setShowDiagnostics(user.isFirstLogin && user.type === "student");
}
@@ -105,12 +96,7 @@ export default function Home(props: Props) {
return true;
};
if (
user &&
(user.status === "paymentDue" ||
user.status === "disabled" ||
checkIfUserExpired())
) {
if (user && (user.status === "paymentDue" || user.status === "disabled" || checkIfUserExpired())) {
return (
<>
<Head>
@@ -125,19 +111,12 @@ export default function Home(props: Props) {
{user.status === "disabled" && (
<Layout user={user} navDisabled>
<div className="flex flex-col items-center justify-center text-center w-full gap-4">
<span className="font-bold text-lg">
Your account has been disabled!
</span>
<span>
Please contact an administrator if you believe this to be a
mistake.
</span>
<span className="font-bold text-lg">Your account has been disabled!</span>
<span>Please contact an administrator if you believe this to be a mistake.</span>
</div>
</Layout>
)}
{(user.status === "paymentDue" || checkIfUserExpired()) && (
<PaymentDue hasExpired user={user} reload={router.reload} />
)}
{(user.status === "paymentDue" || checkIfUserExpired()) && <PaymentDue hasExpired user={user} reload={router.reload} />}
</>
);
}
@@ -194,14 +173,10 @@ export default function Home(props: Props) {
<ToastContainer />
{user && (
<Layout user={user}>
{checkAccess(user, ["student"]) && <StudentDashboard user={user} />}
{checkAccess(user, ["teacher"]) && <TeacherDashboard user={user} />}
{checkAccess(user, ["corporate"]) && (
<CorporateDashboard user={user as CorporateUser} />
)}
{checkAccess(user, ["mastercorporate"]) && (
<MasterCorporateDashboard user={user as MasterCorporateUser} />
)}
{checkAccess(user, ["student"]) && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
{checkAccess(user, ["teacher"]) && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
{checkAccess(user, ["corporate"]) && <CorporateDashboard linkedCorporate={linkedCorporate} user={user as CorporateUser} />}
{checkAccess(user, ["mastercorporate"]) && <MasterCorporateDashboard user={user as MasterCorporateUser} />}
{checkAccess(user, ["agent"]) && <AgentDashboard user={user} />}
{checkAccess(user, ["admin"]) && <AdminDashboard user={user} />}
{checkAccess(user, ["developer"]) && (
@@ -215,23 +190,15 @@ export default function Home(props: Props) {
value: selectedScreen,
label: USER_TYPE_LABELS[selectedScreen],
}}
onChange={(value) =>
value
? setSelectedScreen(value.value)
: setSelectedScreen("admin")
}
onChange={(value) => (value ? setSelectedScreen(value.value) : setSelectedScreen("admin"))}
/>
{selectedScreen === "student" && <StudentDashboard user={user} />}
{selectedScreen === "teacher" && <TeacherDashboard user={user} />}
{selectedScreen === "student" && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
{selectedScreen === "teacher" && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
{selectedScreen === "corporate" && (
<CorporateDashboard user={user as unknown as CorporateUser} />
)}
{selectedScreen === "mastercorporate" && (
<MasterCorporateDashboard
user={user as unknown as MasterCorporateUser}
/>
<CorporateDashboard linkedCorporate={linkedCorporate} user={user as unknown as CorporateUser} />
)}
{selectedScreen === "mastercorporate" && <MasterCorporateDashboard user={user as unknown as MasterCorporateUser} />}
{selectedScreen === "agent" && <AgentDashboard user={user} />}
{selectedScreen === "admin" && <AdminDashboard user={user} />}
</>

View File

@@ -12,7 +12,7 @@ import Link from "next/link";
import axios from "axios";
import {ErrorMessage} from "@/constants/errors";
import clsx from "clsx";
import {CorporateUser, EmploymentStatus, EMPLOYMENT_STATUS, Gender, User, DemographicInformation} from "@/interfaces/user";
import {CorporateUser, EmploymentStatus, EMPLOYMENT_STATUS, Gender, User, DemographicInformation, MasterCorporateUser} from "@/interfaces/user";
import CountrySelect from "@/components/Low/CountrySelect";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import moment from "moment";
@@ -34,8 +34,10 @@ import {capitalize} from "lodash";
import TopicModal from "@/components/Medium/TopicModal";
import {v4} from "uuid";
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import {getUserCorporate} from "@/utils/groups.be";
import {InferGetServerSidePropsType} from "next";
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user;
if (!user || !user.isVerified) {
@@ -57,18 +59,19 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}
return {
props: {user: req.session.user},
props: {user, linkedCorporate: await getUserCorporate(user.id)},
};
}, sessionOptions);
interface Props {
user: User;
mutateUser: Function;
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
const DoubleColumnRow = ({children}: {children: ReactNode}) => <div className="flex flex-col lg:flex-row gap-8 w-full">{children}</div>;
function UserProfile({user, mutateUser}: Props) {
function UserProfile({user, mutateUser, linkedCorporate}: Props) {
const [bio, setBio] = useState(user.bio || "");
const [name, setName] = useState(user.name || "");
const [email, setEmail] = useState(user.email || "");
@@ -245,7 +248,7 @@ function UserProfile({user, mutateUser}: Props) {
<h1 className="text-4xl font-bold mb-6 -md:hidden">Edit Profile</h1>
<form className="flex flex-col items-center gap-6 w-full" onSubmit={(e) => e.preventDefault()}>
<DoubleColumnRow>
{user.type !== "corporate" ? (
{user.type !== "corporate" && user.type !== "mastercorporate" ? (
<Input
label={user.type === "agent" ? "English name" : "Name"}
type="text"
@@ -260,6 +263,7 @@ function UserProfile({user, mutateUser}: Props) {
label="Company name"
type="text"
name="name"
disabled={!!linkedCorporate}
onChange={(e) =>
setCorporateInformation((prev) => ({
...prev!,
@@ -502,6 +506,7 @@ function UserProfile({user, mutateUser}: Props) {
label="Department"
placeholder="CEO, Head of Marketing..."
required
disabled={!!linkedCorporate}
/>
</DoubleColumnRow>
</>
@@ -638,7 +643,7 @@ function UserProfile({user, mutateUser}: Props) {
);
}
export default function Home() {
export default function Home({linkedCorporate}: {linkedCorporate?: CorporateUser | MasterCorporateUser}) {
const {user, mutateUser} = useUser({redirectTo: "/login"});
return (
@@ -653,7 +658,7 @@ export default function Home() {
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
{user && <UserProfile user={user} mutateUser={mutateUser} />}
{user && <UserProfile linkedCorporate={linkedCorporate} user={user} mutateUser={mutateUser} />}
</>
);
}

View File

@@ -1,5 +1,5 @@
import {app} from "@/firebase";
import {CorporateUser, Group, StudentUser, TeacherUser} from "@/interfaces/user";
import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser} from "@/interfaces/user";
import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore";
import moment from "moment";
import {getUser} from "./users.be";
@@ -35,14 +35,14 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID
export const getUserCorporate = async (id: string) => {
const user = await getUser(id);
if (user.type === "corporate" || user.type === "mastercorporate") return user;
if (user.type === "mastercorporate") return user;
const groups = await getParticipantGroups(id);
const admins = await Promise.all(groups.map((x) => x.admin).map(getUser));
const corporates = admins.filter((x) => x.type === "corporate");
const corporates = admins.filter((x) => x.type === "corporate" || x.type === "mastercorporate");
if (corporates.length === 0) return undefined;
return corporates.shift() as CorporateUser;
return corporates.shift() as CorporateUser | MasterCorporateUser;
};
export const getGroups = async () => {