diff --git a/src/components/High/Layout.tsx b/src/components/High/Layout.tsx
index 79eb2d2a..38a971db 100644
--- a/src/components/High/Layout.tsx
+++ b/src/components/High/Layout.tsx
@@ -33,8 +33,7 @@ export default function Layout({user, children, className, navDisabled = false,
focusMode={focusMode}
onFocusLayerMouseEnter={onFocusLayerMouseEnter}
className="-md:hidden"
- userType={user.type}
- userId={user.id}
+ user={user}
/>
void;
- className?: string;
- userType?: Type;
- userId?: string;
+ path: string;
+ navDisabled?: boolean;
+ focusMode?: boolean;
+ onFocusLayerMouseEnter?: () => void;
+ className?: string;
+ user: User;
}
interface NavProps {
- Icon: IconType;
- label: string;
- path: string;
- keyPath: string;
- disabled?: boolean;
- isMinimized?: boolean;
- badge?: number;
+ Icon: IconType;
+ label: string;
+ path: string;
+ keyPath: string;
+ disabled?: boolean;
+ isMinimized?: boolean;
+ badge?: number;
}
-const Nav = ({Icon, label, path, keyPath, disabled = false, isMinimized = false, badge}: NavProps) => {
- return (
-
-
- {!isMinimized &&
{label}}
- {!!badge && badge > 0 && (
-
- {badge}
-
- )}
-
- );
+const Nav = ({
+ Icon,
+ label,
+ path,
+ keyPath,
+ disabled = false,
+ isMinimized = false,
+ badge,
+}: NavProps) => {
+ return (
+
+
+ {!isMinimized &&
{label}}
+ {!!badge && badge > 0 && (
+
+ {badge}
+
+ )}
+
+ );
};
-export default function Sidebar({path, navDisabled = false, focusMode = false, userType, onFocusLayerMouseEnter, className, userId}: Props) {
- const router = useRouter();
+export default function Sidebar({
+ path,
+ navDisabled = false,
+ focusMode = false,
+ user,
+ onFocusLayerMouseEnter,
+ className,
+}: Props) {
+ const router = useRouter();
- const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]);
+ const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [
+ state.isSidebarMinimized,
+ state.toggleSidebarMinimized,
+ ]);
- const {totalAssignedTickets} = useTicketsListener(userId);
+ const { totalAssignedTickets } = useTicketsListener(user.id);
- const logout = async () => {
- axios.post("/api/logout").finally(() => {
- setTimeout(() => router.reload(), 500);
- });
- };
+ const logout = async () => {
+ axios.post("/api/logout").finally(() => {
+ setTimeout(() => router.reload(), 500);
+ });
+ };
- const disableNavigation = preventNavigation(navDisabled, focusMode);
+ const disableNavigation = preventNavigation(navDisabled, focusMode);
- return (
-
-
-
- {(userType === "student" || userType === "teacher" || userType === "developer") && (
- <>
-
-
- >
- )}
- {(userType || "") !== 'agent' && (
- <>
-
-
- >
- )}
- {["admin", "developer", "agent", "corporate", "mastercorporate"].includes(userType || "") && (
-
- )}
- {["admin", "developer", "corporate", "teacher", "mastercorporate"].includes(userType || "") && (
-
- )}
- {["admin", "developer", "agent"].includes(userType || "") && (
-
- )}
- {userType === "developer" && (
-
- )}
-
-
-
-
-
- {(userType || "") !== 'agent' && (
- <>
-
-
- >
- )}
- {userType !== "student" && (
-
- )}
- {userType === "developer" && (
-
- )}
-
+ return (
+
+
+
+ {checkAccess(
+ user,
+ ["student", "teacher", "developer"],
+ "viewExams"
+ ) && (
+
+ )}
+ {checkAccess(
+ user,
+ ["student", "teacher", "developer"],
+ "viewExercises"
+ ) && (
+
+ )}
+ {checkAccess(user, getTypesOfUser(["agent"]), "viewStats") && (
+
+ )}
+ {checkAccess(user, getTypesOfUser(["agent"]), "viewRecords") && (
+
+ )}
+ {checkAccess(
+ user,
+ ["admin", "developer", "agent", "corporate", "mastercorporate"],
+ "viewPaymentRecords"
+ ) && (
+
+ )}
+ {checkAccess(user, [
+ "admin",
+ "developer",
+ "corporate",
+ "teacher",
+ "mastercorporate",
+ ]) && (
+
+ )}
+ {checkAccess(user, [
+ "admin",
+ "developer",
+ "corporate",
+ "teacher",
+ "mastercorporate",
+ ]) && (
+
+ )}
+ {checkAccess(user, ["admin", "developer", "agent"], "viewTickets") && (
+
+ )}
+ {checkAccess(user, ["developer"]) && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+ {checkAccess(user, getTypesOfUser(["agent"]), "viewStats") && (
+
+ )}
+ {checkAccess(user, getTypesOfUser(["agent"]), "viewRecords") && (
+
+ )}
+ {checkAccess(user, getTypesOfUser(["student"])) && (
+
+ )}
+ {checkAccess(user, getTypesOfUser(["student"])) && (
+
+ )}
+ {checkAccess(user, ["developer"]) && (
+ <>
+
+
+ >
+ )}
+
-
-
- {isMinimized ? : }
- {!isMinimized && Minimize}
-
-
{} : logout}
- className={clsx(
- "hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out",
- isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
- )}>
-
- {!isMinimized && Log Out}
-
-
- {focusMode && }
-
- );
+
+
+ {isMinimized ? (
+
+ ) : (
+
+ )}
+ {!isMinimized && (
+ Minimize
+ )}
+
+
{} : logout}
+ className={clsx(
+ "hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out",
+ isMinimized ? "w-fit" : "w-full min-w-[250px] px-8"
+ )}
+ >
+
+ {!isMinimized && (
+ Log Out
+ )}
+
+
+ {focusMode && (
+
+ )}
+
+ );
}
diff --git a/src/interfaces/permissions.ts b/src/interfaces/permissions.ts
new file mode 100644
index 00000000..2e448af5
--- /dev/null
+++ b/src/interfaces/permissions.ts
@@ -0,0 +1,49 @@
+export const markets = ["au", "br", "de"] as const;
+
+export const permissions = [
+ // generate codes are basicly invites
+ "createCodeStudent",
+ "createCodeTeacher",
+ "createCodeCorporate",
+ "createCodeCountryManager",
+ "createCodeAdmin",
+ // exams
+ "createReadingExam",
+ "createListeningExam",
+ "createWritingExam",
+ "createSpeakingExam",
+ "createLevelExam",
+ // view pages
+ "viewExams",
+ "viewExercises",
+ "viewRecords",
+ "viewStats",
+ "viewTickets",
+ "viewPaymentRecords",
+ // view data
+ "viewStudent",
+ "viewTeacher",
+ "viewCorporate",
+ "viewCountryManager",
+ "viewAdmin",
+ // edit data
+ "editStudent",
+ "editTeacher",
+ "editCorporate",
+ "editCountryManager",
+ "editAdmin",
+ // delete data
+ "deleteStudent",
+ "deleteTeacher",
+ "deleteCorporate",
+ "deleteCountryManager",
+ "deleteAdmin",
+] as const;
+
+export type PermissionType = (typeof permissions)[keyof typeof permissions];
+
+export interface Permission {
+ id: string;
+ type: PermissionType;
+ users: string[];
+}
diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts
index 542b36bf..ddda1ccb 100644
--- a/src/interfaces/user.ts
+++ b/src/interfaces/user.ts
@@ -1,5 +1,6 @@
import { Module } from ".";
import { InstructorGender } from "./exam";
+import { PermissionType } from "./permissions";
export type User =
| StudentUser
@@ -26,6 +27,7 @@ export interface BasicUser {
subscriptionExpirationDate?: null | Date;
registrationDate?: Date;
status: UserStatus;
+ permissions: PermissionType[],
}
export interface StudentUser extends BasicUser {
diff --git a/src/pages/api/permissions/[id].ts b/src/pages/api/permissions/[id].ts
new file mode 100644
index 00000000..7c379719
--- /dev/null
+++ b/src/pages/api/permissions/[id].ts
@@ -0,0 +1,30 @@
+// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
+import type { NextApiRequest, NextApiResponse } from "next";
+import { app } from "@/firebase";
+import { getFirestore, doc, setDoc } from "firebase/firestore";
+import { withIronSessionApiRoute } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+
+const db = getFirestore(app);
+
+export default withIronSessionApiRoute(handler, sessionOptions);
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method === "PATCH") return patch(req, res);
+}
+
+async function patch(req: NextApiRequest, res: NextApiResponse) {
+ if (!req.session.user) {
+ res.status(401).json({ ok: false });
+ return;
+ }
+ const { id } = req.query as { id: string };
+ const { users } = req.body;
+ try {
+ await setDoc(doc(db, "permissions", id), { users }, { merge: true });
+ return res.status(200).json({ ok: true });
+ } catch (err) {
+ console.error(err);
+ return res.status(500).json({ ok: false });
+ }
+}
diff --git a/src/pages/api/permissions/bootstrap.ts b/src/pages/api/permissions/bootstrap.ts
new file mode 100644
index 00000000..cd36b52a
--- /dev/null
+++ b/src/pages/api/permissions/bootstrap.ts
@@ -0,0 +1,43 @@
+// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
+import type { NextApiRequest, NextApiResponse } from "next";
+import { app } from "@/firebase";
+import {
+ getFirestore,
+ collection,
+ getDocs,
+ query,
+ where,
+ doc,
+ setDoc,
+ addDoc,
+ getDoc,
+ deleteDoc,
+} from "firebase/firestore";
+import { withIronSessionApiRoute } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import { Permission } from "@/interfaces/permissions";
+import { bootstrap } from "@/utils/permissions.be";
+
+const db = getFirestore(app);
+
+export default withIronSessionApiRoute(handler, sessionOptions);
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method === "GET") return get(req, res);
+}
+
+async function get(req: NextApiRequest, res: NextApiResponse) {
+ if (!req.session.user) {
+ res.status(401).json({ ok: false });
+ return;
+ }
+
+ console.log("Boostrap");
+ try {
+ await bootstrap();
+ return res.status(200).json({ ok: true });
+ } catch (err) {
+ console.error("Failed to update permissions", err);
+ return res.status(500).json({ ok: false });
+ }
+}
diff --git a/src/pages/api/permissions/index.ts b/src/pages/api/permissions/index.ts
new file mode 100644
index 00000000..3f9fbfac
--- /dev/null
+++ b/src/pages/api/permissions/index.ts
@@ -0,0 +1,36 @@
+// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
+import type { NextApiRequest, NextApiResponse } from "next";
+import { app } from "@/firebase";
+import {
+ getFirestore,
+ collection,
+ getDocs,
+ query,
+ where,
+ doc,
+ setDoc,
+ addDoc,
+ getDoc,
+ deleteDoc,
+} from "firebase/firestore";
+import { withIronSessionApiRoute } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import { Permission } from "@/interfaces/permissions";
+import { getPermissions, getPermissionDocs } from "@/utils/permissions.be";
+
+const db = getFirestore(app);
+
+export default withIronSessionApiRoute(handler, sessionOptions);
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method === "GET") return get(req, res);
+}
+
+async function get(req: NextApiRequest, res: NextApiResponse) {
+ if (!req.session.user) {
+ res.status(401).json({ ok: false });
+ return;
+ }
+ const docs = await getPermissionDocs();
+ res.status(200).json(docs);
+}
diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts
index 921c0222..88925234 100644
--- a/src/pages/api/user.ts
+++ b/src/pages/api/user.ts
@@ -1,11 +1,22 @@
-import {PERMISSIONS} from "@/constants/userPermissions";
-import {app, adminApp} from "@/firebase";
-import {Group, User} from "@/interfaces/user";
-import {sessionOptions} from "@/lib/session";
-import {collection, deleteDoc, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore";
-import {getAuth} from "firebase-admin/auth";
-import {withIronSessionApiRoute} from "iron-session/next";
-import {NextApiRequest, NextApiResponse} from "next";
+import { PERMISSIONS } from "@/constants/userPermissions";
+import { app, adminApp } from "@/firebase";
+import { Group, User } from "@/interfaces/user";
+import { sessionOptions } from "@/lib/session";
+import {
+ collection,
+ deleteDoc,
+ doc,
+ getDoc,
+ getDocs,
+ getFirestore,
+ query,
+ setDoc,
+ where,
+} from "firebase/firestore";
+import { getAuth } from "firebase-admin/auth";
+import { withIronSessionApiRoute } from "iron-session/next";
+import { NextApiRequest, NextApiResponse } from "next";
+import { getPermissions, getPermissionDocs } from "@/utils/permissions.be";
const db = getFirestore(app);
const auth = getAuth(adminApp);
@@ -13,89 +24,132 @@ const auth = getAuth(adminApp);
export default withIronSessionApiRoute(user, sessionOptions);
async function user(req: NextApiRequest, res: NextApiResponse) {
- if (req.method === "GET") return get(req, res);
- if (req.method === "DELETE") return del(req, res);
+ if (req.method === "GET") return get(req, res);
+ if (req.method === "DELETE") return del(req, res);
- res.status(404).json(undefined);
+ res.status(404).json(undefined);
}
async function del(req: NextApiRequest, res: NextApiResponse) {
- if (!req.session.user) {
- res.status(401).json({ok: false});
- return;
- }
+ if (!req.session.user) {
+ res.status(401).json({ ok: false });
+ return;
+ }
- const {id} = req.query as {id: string};
+ const { id } = req.query as { id: string };
- const docUser = await getDoc(doc(db, "users", req.session.user.id));
- if (!docUser.exists()) {
- res.status(401).json({ok: false});
- return;
- }
+ const docUser = await getDoc(doc(db, "users", req.session.user.id));
+ if (!docUser.exists()) {
+ res.status(401).json({ ok: false });
+ return;
+ }
- const user = docUser.data() as User;
+ const user = docUser.data() as User;
- const docTargetUser = await getDoc(doc(db, "users", id));
- if (!docTargetUser.exists()) {
- res.status(404).json({ok: false});
- return;
- }
+ const docTargetUser = await getDoc(doc(db, "users", id));
+ if (!docTargetUser.exists()) {
+ res.status(404).json({ ok: false });
+ return;
+ }
- const targetUser = {...docTargetUser.data(), id: docTargetUser.id} as User;
+ const targetUser = { ...docTargetUser.data(), id: docTargetUser.id } as User;
- if (user.type === "corporate" && (targetUser.type === "student" || targetUser.type === "teacher")) {
- res.json({ok: true});
+ if (
+ user.type === "corporate" &&
+ (targetUser.type === "student" || targetUser.type === "teacher")
+ ) {
+ res.json({ ok: true });
- const userParticipantGroup = await getDocs(query(collection(db, "groups"), where("participants", "array-contains", id)));
- await Promise.all([
- ...userParticipantGroup.docs
- .filter((x) => (x.data() as Group).admin === user.id)
- .map(async (x) => await setDoc(x.ref, {participants: x.data().participants.filter((y: string) => y !== id)}, {merge: true})),
- ]);
+ const userParticipantGroup = await getDocs(
+ query(
+ collection(db, "groups"),
+ where("participants", "array-contains", id)
+ )
+ );
+ await Promise.all([
+ ...userParticipantGroup.docs
+ .filter((x) => (x.data() as Group).admin === user.id)
+ .map(
+ async (x) =>
+ await setDoc(
+ x.ref,
+ {
+ participants: x
+ .data()
+ .participants.filter((y: string) => y !== id),
+ },
+ { merge: true }
+ )
+ ),
+ ]);
- return;
- }
+ return;
+ }
- const permission = PERMISSIONS.deleteUser[targetUser.type];
- if (!permission.includes(user.type)) {
- res.status(403).json({ok: false});
- return;
- }
+ const permission = PERMISSIONS.deleteUser[targetUser.type];
+ if (!permission.includes(user.type)) {
+ res.status(403).json({ ok: false });
+ return;
+ }
- res.json({ok: true});
+ res.json({ ok: true });
- await auth.deleteUser(id);
- await deleteDoc(doc(db, "users", id));
- const userCodeDocs = await getDocs(query(collection(db, "codes"), where("userId", "==", id)));
- const userParticipantGroup = await getDocs(query(collection(db, "groups"), where("participants", "array-contains", id)));
- const userGroupAdminDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id)));
- const userStatsDocs = await getDocs(query(collection(db, "stats"), where("user", "==", id)));
+ await auth.deleteUser(id);
+ await deleteDoc(doc(db, "users", id));
+ const userCodeDocs = await getDocs(
+ query(collection(db, "codes"), where("userId", "==", id))
+ );
+ const userParticipantGroup = await getDocs(
+ query(collection(db, "groups"), where("participants", "array-contains", id))
+ );
+ const userGroupAdminDocs = await getDocs(
+ query(collection(db, "groups"), where("admin", "==", id))
+ );
+ const userStatsDocs = await getDocs(
+ query(collection(db, "stats"), where("user", "==", id))
+ );
- await Promise.all([
- ...userCodeDocs.docs.map(async (x) => await deleteDoc(x.ref)),
- ...userGroupAdminDocs.docs.map(async (x) => await deleteDoc(x.ref)),
- ...userStatsDocs.docs.map(async (x) => await deleteDoc(x.ref)),
- ...userParticipantGroup.docs.map(
- async (x) => await setDoc(x.ref, {participants: x.data().participants.filter((y: string) => y !== id)}, {merge: true}),
- ),
- ]);
+ await Promise.all([
+ ...userCodeDocs.docs.map(async (x) => await deleteDoc(x.ref)),
+ ...userGroupAdminDocs.docs.map(async (x) => await deleteDoc(x.ref)),
+ ...userStatsDocs.docs.map(async (x) => await deleteDoc(x.ref)),
+ ...userParticipantGroup.docs.map(
+ async (x) =>
+ await setDoc(
+ x.ref,
+ {
+ participants: x.data().participants.filter((y: string) => y !== id),
+ },
+ { merge: true }
+ )
+ ),
+ ]);
}
async function get(req: NextApiRequest, res: NextApiResponse) {
- if (req.session.user) {
- const docUser = await getDoc(doc(db, "users", req.session.user.id));
- if (!docUser.exists()) {
- res.status(401).json(undefined);
- return;
- }
+ if (req.session.user) {
+ const docUser = await getDoc(doc(db, "users", req.session.user.id));
+ if (!docUser.exists()) {
+ res.status(401).json(undefined);
+ return;
+ }
- const user = docUser.data() as User;
+ const user = docUser.data() as User;
+
+ const permissionDocs = await getPermissionDocs();
- req.session.user = {...user, id: req.session.user.id};
- await req.session.save();
+ const userWithPermissions = {
+ ...user,
+ permissions: getPermissions(req.session.user.id, permissionDocs),
+ };
+ req.session.user = {
+ ...userWithPermissions,
+ id: req.session.user.id,
+ };
+ await req.session.save();
- res.json({...user, id: req.session.user.id});
- } else {
- res.status(401).json(undefined);
- }
+ res.json({ ...userWithPermissions, id: req.session.user.id });
+ } else {
+ res.status(401).json(undefined);
+ }
}
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 601d4838..0ef4fea7 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -8,7 +8,6 @@ import {useEffect, useState} from "react";
import useStats from "@/hooks/useStats";
import {averageScore, groupBySession, totalExams} from "@/utils/stats";
import useUser from "@/hooks/useUser";
-import Sidebar from "@/components/Sidebar";
import Diagnostic from "@/components/Diagnostic";
import {ToastContainer} from "react-toastify";
import {capitalize} from "lodash";
diff --git a/src/pages/permissions/[id].tsx b/src/pages/permissions/[id].tsx
new file mode 100644
index 00000000..093c946b
--- /dev/null
+++ b/src/pages/permissions/[id].tsx
@@ -0,0 +1,190 @@
+/* eslint-disable @next/next/no-img-element */
+import Head from "next/head";
+import { useState } from "react";
+import { withIronSessionSsr } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import { shouldRedirectHome } from "@/utils/navigation.disabled";
+import { Permission, PermissionType } from "@/interfaces/permissions";
+import { getPermissionDoc } from "@/utils/permissions.be";
+import { User } from "@/interfaces/user";
+import Layout from "@/components/High/Layout";
+import { getUsers } from "@/utils/users.be";
+import { BsTrash } from "react-icons/bs";
+import Select from "@/components/Low/Select";
+import Button from "@/components/Low/Button";
+import axios from "axios";
+import { toast, ToastContainer } from "react-toastify";
+
+interface BasicUser {
+ id: string;
+ name: string;
+}
+
+interface PermissionWithBasicUsers {
+ id: string;
+ type: PermissionType;
+ users: BasicUser[];
+}
+
+export const getServerSideProps = withIronSessionSsr(async (context) => {
+ const { req, params } = context;
+ const user = req.session.user;
+
+ if (!user || !user.isVerified) {
+ return {
+ redirect: {
+ destination: "/login",
+ permanent: false,
+ },
+ };
+ }
+
+ if (shouldRedirectHome(user)) {
+ return {
+ redirect: {
+ destination: "/",
+ permanent: false,
+ },
+ };
+ }
+
+ if (!params?.id) {
+ return {
+ redirect: {
+ destination: "/permissions",
+ permanent: false,
+ },
+ };
+ }
+
+ // Fetch data from external API
+ const permission: Permission = await getPermissionDoc(params.id as string);
+
+ const allUserData: User[] = await getUsers();
+ const users = allUserData.map((u) => ({
+ id: u.id,
+ name: u.name,
+ })) as BasicUser[];
+
+ // const res = await fetch("api/permissions");
+ // const permissions: Permission[] = await res.json();
+ // Pass data to the page via props
+ const usersData: BasicUser[] = permission.users.reduce(
+ (acc: BasicUser[], userId) => {
+ const user = users.find((u) => u.id === userId) as BasicUser;
+ if (user) {
+ acc.push(user);
+ }
+ return acc;
+ },
+ []
+ );
+
+ return {
+ props: {
+ // permissions: permissions.map((p) => ({ id: p.id, type: p.type })),
+ permission: {
+ ...permission,
+ id: params.id,
+ users: usersData,
+ },
+ user: req.session.user,
+ users,
+ },
+ };
+}, sessionOptions);
+
+interface Props {
+ permission: PermissionWithBasicUsers;
+ user: User;
+ users: BasicUser[];
+}
+
+export default function Page(props: Props) {
+ console.log("Props", props);
+
+ const { permission, user, users } = props;
+
+ const [selectedUsers, setSelectedUsers] = useState
(() =>
+ permission.users.map((u) => u.id)
+ );
+
+ const onChange = (value: any) => {
+ console.log("value", value);
+ setSelectedUsers((prev) => {
+ if (value?.value) {
+ return [...prev, value?.value];
+ }
+ return prev;
+ });
+ };
+ const removeUser = (id: string) => {
+ setSelectedUsers((prev) => prev.filter((u) => u !== id));
+ };
+
+ const update = async () => {
+ console.log("update", selectedUsers);
+ try {
+ await axios.patch(`/api/permissions/${permission.id}`, {
+ users: selectedUsers,
+ });
+ toast.success("Permission updated");
+ } catch (err) {
+ toast.error("Failed to update permission");
+ }
+ };
+
+ return (
+ <>
+
+ EnCoach
+
+
+
+
+
+
+
+ Permission: {permission.type as string}
+
+
+
+
+
Blacklisted Users
+
+ {selectedUsers.map((userId) => {
+ const name = users.find((u) => u.id === userId)?.name;
+ return (
+
+ {name}
+ removeUser(userId)}
+ size={20}
+ />
+
+ );
+ })}
+
+
+
+ >
+ );
+}
diff --git a/src/pages/permissions/index.tsx b/src/pages/permissions/index.tsx
new file mode 100644
index 00000000..08c422db
--- /dev/null
+++ b/src/pages/permissions/index.tsx
@@ -0,0 +1,94 @@
+/* eslint-disable @next/next/no-img-element */
+import Head from "next/head";
+import { withIronSessionSsr } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import { shouldRedirectHome } from "@/utils/navigation.disabled";
+import { Permission } from "@/interfaces/permissions";
+import { getPermissionDocs } from "@/utils/permissions.be";
+import { User } from "@/interfaces/user";
+import Layout from "@/components/High/Layout";
+import Link from "next/link";
+
+export const getServerSideProps = withIronSessionSsr(async ({ req }) => {
+ const user = req.session.user;
+
+ if (!user || !user.isVerified) {
+ return {
+ redirect: {
+ destination: "/login",
+ permanent: false,
+ },
+ };
+ }
+
+ if (shouldRedirectHome(user)) {
+ return {
+ redirect: {
+ destination: "/",
+ permanent: false,
+ },
+ };
+ }
+
+ // Fetch data from external API
+ const permissions: Permission[] = await getPermissionDocs();
+
+ console.log("Permissions", permissions);
+
+ // const res = await fetch("api/permissions");
+ // const permissions: Permission[] = await res.json();
+ // Pass data to the page via props
+ return {
+ props: {
+ // permissions: permissions.map((p) => ({ id: p.id, type: p.type })),
+ permissions: permissions.map((p) => {
+ const { users, ...rest } = p;
+ return rest;
+ }),
+ user: req.session.user,
+ },
+ };
+}, sessionOptions);
+
+interface Props {
+ permissions: Permission[];
+ user: User;
+}
+
+export default function Page(props: Props) {
+ console.log("Props", props);
+
+ const { permissions, user } = props;
+
+ return (
+ <>
+
+ EnCoach
+
+
+
+
+
+ Permissions
+
+ {permissions.map((permission: Permission) => {
+ const id = permission.id as string;
+ const type = permission.type as string;
+ return (
+
+
+
+ );
+ })}
+
+
+ >
+ );
+}
diff --git a/src/utils/permissions.be.ts b/src/utils/permissions.be.ts
new file mode 100644
index 00000000..e5b1d979
--- /dev/null
+++ b/src/utils/permissions.be.ts
@@ -0,0 +1,83 @@
+import { app, adminApp } from "@/firebase";
+import { getAuth } from "firebase-admin/auth";
+
+import {
+ collection,
+ deleteDoc,
+ doc,
+ getDoc,
+ getDocs,
+ getFirestore,
+ query,
+ setDoc,
+ where,
+} from "firebase/firestore";
+import {
+ Permission,
+ PermissionType,
+ permissions,
+} from "@/interfaces/permissions";
+import {v4} from "uuid";
+
+const db = getFirestore(app);
+
+async function createPermission(type: string) {
+ const permData = doc(db, "permissions", v4());
+ const permDoc = await getDoc(permData);
+ if (permDoc.exists()) {
+ return true;
+ }
+
+ await setDoc(permData, {
+ type,
+ users: [],
+ });
+}
+export function getPermissions(userId: string | undefined, docs: Permission[]) {
+ if (!userId) {
+ return [];
+ }
+ // the concept is like a blacklist
+ // if the user exists in the list, he can't access this permission
+ // even if his profile allows
+ const permissions = docs.reduce((acc: PermissionType[], doc: Permission) => {
+ // typescript was complaining even with the validation on the top
+ if (doc.users.includes(userId)) {
+ return acc;
+ }
+
+ return [...acc, doc.type];
+ }, []) as PermissionType[];
+ return permissions;
+}
+
+export async function bootstrap() {
+ await permissions.forEach(async (type) => {
+ await createPermission(type);
+ });
+}
+
+export async function getPermissionDoc(id: string) {
+ const docRef = doc(db, "permissions", id);
+ const docSnap = await getDoc(docRef);
+
+ if (docSnap.exists()) {
+ return docSnap.data() as Permission;
+ }
+
+ throw new Error("Permission not found");
+}
+
+export async function getPermissionDocs() {
+ const q = query(collection(db, "permissions"));
+ // firebase is missing something like array-not-contains
+
+ const snapshot = await getDocs(q);
+
+ const docs = snapshot.docs.map((doc) => ({
+ id: doc.id,
+ ...doc.data(),
+ })) as Permission[];
+
+ return docs;
+}
diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts
new file mode 100644
index 00000000..daa309a3
--- /dev/null
+++ b/src/utils/permissions.ts
@@ -0,0 +1,45 @@
+import { PermissionType } from "@/interfaces/permissions";
+import { User, Type, userTypes } from "@/interfaces/user";
+
+export function checkAccess(
+ user: User,
+ types: Type[],
+ permission?: PermissionType
+) {
+ if (!user) {
+ return false;
+ }
+
+ // if(user.type === '') {
+ if (!user.type) {
+ console.warn("User type is empty");
+ return false;
+ }
+
+ if (types.length === 0) {
+ console.warn("No types provided");
+ return false;
+ }
+
+ if (!types.includes(user.type)) {
+ return false;
+ }
+
+ // we may not want a permission check as most screens dont even havr a specific permission
+ if (permission) {
+ // this works more like a blacklist
+ // therefore if we don't find the permission here, he can't do it
+ if (!(user.permissions || []).includes(permission)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function getTypesOfUser(types: Type[]) {
+ // basicly generate a list of all types except the excluded ones
+ return userTypes.filter((userType) => {
+ return !types.includes(userType);
+ })
+}
\ No newline at end of file
diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts
new file mode 100644
index 00000000..7650f73c
--- /dev/null
+++ b/src/utils/users.be.ts
@@ -0,0 +1,14 @@
+import { app } from "@/firebase";
+
+import { collection, getDocs, getFirestore } from "firebase/firestore";
+import { User } from "@/interfaces/user";
+const db = getFirestore(app);
+
+export async function getUsers() {
+ const snapshot = await getDocs(collection(db, "users"));
+
+ return snapshot.docs.map((doc) => ({
+ id: doc.id,
+ ...doc.data(),
+ })) as User[];
+}