Updated the permissions access

This commit is contained in:
Tiago Ribeiro
2024-08-20 00:01:59 +01:00
parent 81b8ceb2b3
commit 688505b4eb
8 changed files with 528 additions and 454 deletions

View File

@@ -1,105 +1,82 @@
import React from "react"; import React from "react";
import { Permission } from "@/interfaces/permissions"; import {Permission} from "@/interfaces/permissions";
import { import {createColumnHelper, flexRender, getCoreRowModel, useReactTable, Row} from "@tanstack/react-table";
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
Row,
} from "@tanstack/react-table";
import Link from "next/link"; import Link from "next/link";
import { convertCamelCaseToReadable } from "@/utils/string"; import {convertCamelCaseToReadable} from "@/utils/string";
interface Props { interface Props {
permissions: Permission[]; permissions: Permission[];
} }
const columnHelper = createColumnHelper<Permission>(); const columnHelper = createColumnHelper<Permission>();
const defaultColumns = [ const defaultColumns = [
columnHelper.accessor("type", { columnHelper.accessor("type", {
header: () => <span>Type</span>, header: () => <span>Type</span>,
cell: ({ row, getValue }) => ( cell: ({row, getValue}) => (
<Link <Link
href={`/permissions/${row.original.id}`} href={`/permissions/${row.original.id}`}
key={row.id} key={row.id}
className="underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer" className="underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer">
> {convertCamelCaseToReadable(getValue() as string)}
{convertCamelCaseToReadable(getValue() as string)} </Link>
</Link> ),
), }),
}),
]; ];
export default function PermissionList({ permissions }: Props) { export default function PermissionList({permissions}: Props) {
const table = useReactTable({ const table = useReactTable({
data: permissions, data: permissions,
columns: defaultColumns, columns: defaultColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
const groupedData: { [key: string]: Row<Permission>[] } = table const groupedData: {[key: string]: Row<Permission>[]} = table.getRowModel().rows.reduce((groups: {[key: string]: Row<Permission>[]}, row) => {
.getRowModel() const parent = row.original.topic;
.rows.reduce((groups: { [key: string]: Row<Permission>[] }, row) => { if (!groups[parent]) {
const parent = row.original.topic; groups[parent] = [];
if (!groups[parent]) { }
groups[parent] = []; groups[parent].push(row);
} return groups;
groups[parent].push(row); }, {});
return groups;
}, {});
return ( return (
<div className="w-full"> <div className="w-full h-full">
<div className="w-full flex flex-col gap-2"> <div className="w-full flex flex-col gap-2">
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full"> <table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<th className="py-4 px-4 text-left" key={header.id}> <th className="py-4 px-4 text-left" key={header.id}>
{header.isPlaceholder {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
? null </th>
: flexRender( ))}
header.column.columnDef.header, </tr>
header.getContext() ))}
)} </thead>
</th> <tbody className="px-2">
))} {Object.keys(groupedData).map((parent) => (
</tr> <React.Fragment key={parent}>
))} <tr>
</thead> <td className="px-2 py-2 items-center w-fit">
<tbody className="px-2"> <strong>{parent}</strong>
{Object.keys(groupedData).map((parent) => ( </td>
<React.Fragment key={parent}> </tr>
<tr> {groupedData[parent].map((row, i) => (
<td className="px-2 py-2 items-center w-fit"> <tr className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" key={row.id}>
<strong>{parent}</strong> {row.getVisibleCells().map((cell) => (
</td> <td className="px-4 py-2 items-center w-fit" key={cell.id}>
</tr> {flexRender(cell.column.columnDef.cell, cell.getContext())}
{groupedData[parent].map((row, i) => ( </td>
<tr ))}
className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" </tr>
key={row.id} ))}
> </React.Fragment>
{row.getVisibleCells().map((cell) => ( ))}
<td </tbody>
className="px-4 py-2 items-center w-fit" </table>
key={cell.id} </div>
> </div>
{flexRender( );
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
))}
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
</div>
);
} }

View File

@@ -316,7 +316,13 @@ export default function TeacherDashboard({user}: Props) {
color="purple" color="purple"
/> />
{checkAccess(user, ["teacher", "developer"], permissions, "viewGroup") && ( {checkAccess(user, ["teacher", "developer"], permissions, "viewGroup") && (
<IconCard Icon={BsPeople} label="Groups" value={groups.length} color="purple" onClick={() => setPage("groups")} /> <IconCard
Icon={BsPeople}
label="Groups"
value={groups.filter((x) => x.admin === user.id).length}
color="purple"
onClick={() => setPage("groups")}
/>
)} )}
<div <div
onClick={() => setPage("assignments")} onClick={() => setPage("assignments")}

View File

@@ -210,7 +210,6 @@ export default function GroupList({user}: {user: User}) {
const {groups, reload} = useGroups({ const {groups, reload} = useGroups({
admin: user && filterTypes.includes(user?.type) ? user.id : undefined, admin: user && filterTypes.includes(user?.type) ? user.id : undefined,
userType: user?.type, userType: user?.type,
adminAdmins: user?.type === "teacher" ? user?.id : undefined,
}); });
useEffect(() => { useEffect(() => {

View File

@@ -1,19 +1,19 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import Head from "next/head"; import Head from "next/head";
import { withIronSessionSsr } from "iron-session/next"; import {withIronSessionSsr} from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import { toast, ToastContainer } from "react-toastify"; import {toast, ToastContainer} from "react-toastify";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import { shouldRedirectHome } from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import { useState } from "react"; import {useState} from "react";
import { Module } from "@/interfaces"; import {Module} from "@/interfaces";
import { RadioGroup, Tab } from "@headlessui/react"; import {RadioGroup, Tab} from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
import { MODULE_ARRAY } from "@/utils/moduleUtils"; import {MODULE_ARRAY} from "@/utils/moduleUtils";
import { capitalize } from "lodash"; import {capitalize} from "lodash";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import { Exercise, ReadingPart } from "@/interfaces/exam"; import {Exercise, ReadingPart} from "@/interfaces/exam";
import Input from "@/components/Low/Input"; import Input from "@/components/Low/Input";
import axios from "axios"; import axios from "axios";
import ReadingGeneration from "./(generation)/ReadingGeneration"; import ReadingGeneration from "./(generation)/ReadingGeneration";
@@ -21,121 +21,114 @@ import ListeningGeneration from "./(generation)/ListeningGeneration";
import WritingGeneration from "./(generation)/WritingGeneration"; import WritingGeneration from "./(generation)/WritingGeneration";
import LevelGeneration from "./(generation)/LevelGeneration"; import LevelGeneration from "./(generation)/LevelGeneration";
import SpeakingGeneration from "./(generation)/SpeakingGeneration"; import SpeakingGeneration from "./(generation)/SpeakingGeneration";
import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import {checkAccess} from "@/utils/permissions";
export const getServerSideProps = withIronSessionSsr(({ req, res }) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
permanent: false, permanent: false,
}, },
}; };
} }
if ( if (shouldRedirectHome(user) || !checkAccess(user, ["admin", "mastercorporate", "developer", "corporate"])) {
shouldRedirectHome(user) || return {
checkAccess(user, getTypesOfUser(["developer"])) redirect: {
) { destination: "/",
return { permanent: false,
redirect: { },
destination: "/", };
permanent: false, }
},
};
}
return { return {
props: { user: req.session.user }, props: {user: req.session.user},
}; };
}, sessionOptions); }, sessionOptions);
export default function Generation() { export default function Generation() {
const [module, setModule] = useState<Module>("reading"); const [module, setModule] = useState<Module>("reading");
const { user } = useUser({ redirectTo: "/login" }); const {user} = useUser({redirectTo: "/login"});
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
return ( return (
<> <>
<Head> <Head>
<title>Exam Generation | EnCoach</title> <title>Exam Generation | EnCoach</title>
<meta <meta
name="description" name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/> />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<Layout user={user} className="gap-6"> <Layout user={user} className="gap-6">
<h1 className="text-2xl font-semibold">Exam Generation</h1> <h1 className="text-2xl font-semibold">Exam Generation</h1>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<Input <Input
type="text" type="text"
placeholder="Insert a title here" placeholder="Insert a title here"
name="title" name="title"
label="Title" label="Title"
onChange={setTitle} onChange={setTitle}
roundness="xl" roundness="xl"
defaultValue={title} defaultValue={title}
required required
/> />
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Module</label>
Module <RadioGroup
</label> value={module}
<RadioGroup onChange={setModule}
value={module} className="flex flex-row -2xl:flex-wrap w-full gap-4 -md:justify-center justify-between">
onChange={setModule} {[...MODULE_ARRAY].map((x) => (
className="flex flex-row -2xl:flex-wrap w-full gap-4 -md:justify-center justify-between" <RadioGroup.Option value={x} key={x}>
> {({checked}) => (
{[...MODULE_ARRAY].map((x) => ( <span
<RadioGroup.Option value={x} key={x}> className={clsx(
{({ checked }) => ( "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
<span "transition duration-300 ease-in-out",
className={clsx( x === "reading" &&
"px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", (!checked
"transition duration-300 ease-in-out", ? "bg-white border-mti-gray-platinum"
x === "reading" && : "bg-ielts-reading/70 border-ielts-reading text-white"),
(!checked x === "listening" &&
? "bg-white border-mti-gray-platinum" (!checked
: "bg-ielts-reading/70 border-ielts-reading text-white"), ? "bg-white border-mti-gray-platinum"
x === "listening" && : "bg-ielts-listening/70 border-ielts-listening text-white"),
(!checked x === "writing" &&
? "bg-white border-mti-gray-platinum" (!checked
: "bg-ielts-listening/70 border-ielts-listening text-white"), ? "bg-white border-mti-gray-platinum"
x === "writing" && : "bg-ielts-writing/70 border-ielts-writing text-white"),
(!checked x === "speaking" &&
? "bg-white border-mti-gray-platinum" (!checked
: "bg-ielts-writing/70 border-ielts-writing text-white"), ? "bg-white border-mti-gray-platinum"
x === "speaking" && : "bg-ielts-speaking/70 border-ielts-speaking text-white"),
(!checked x === "level" &&
? "bg-white border-mti-gray-platinum" (!checked
: "bg-ielts-speaking/70 border-ielts-speaking text-white"), ? "bg-white border-mti-gray-platinum"
x === "level" && : "bg-ielts-level/70 border-ielts-level text-white"),
(!checked )}>
? "bg-white border-mti-gray-platinum" {capitalize(x)}
: "bg-ielts-level/70 border-ielts-level text-white") </span>
)} )}
> </RadioGroup.Option>
{capitalize(x)} ))}
</span> </RadioGroup>
)} </div>
</RadioGroup.Option> {module === "reading" && <ReadingGeneration id={title} />}
))} {module === "listening" && <ListeningGeneration id={title} />}
</RadioGroup> {module === "writing" && <WritingGeneration id={title} />}
</div> {module === "speaking" && <SpeakingGeneration id={title} />}
{module === "reading" && <ReadingGeneration id={title} />} {module === "level" && <LevelGeneration id={title} />}
{module === "listening" && <ListeningGeneration id={title} />} </Layout>
{module === "writing" && <WritingGeneration id={title} />} )}
{module === "speaking" && <SpeakingGeneration id={title} />} </>
{module === "level" && <LevelGeneration id={title} />} );
</Layout>
)}
</>
);
} }

View File

@@ -1,209 +1,209 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import Head from "next/head"; import Head from "next/head";
import { useState } from "react"; import {useEffect, useState} from "react";
import { withIronSessionSsr } from "iron-session/next"; import {withIronSessionSsr} from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import { shouldRedirectHome } from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import { Permission, PermissionType } from "@/interfaces/permissions"; import {Permission, PermissionType} from "@/interfaces/permissions";
import { getPermissionDoc } from "@/utils/permissions.be"; import {getPermissionDoc} from "@/utils/permissions.be";
import { User } from "@/interfaces/user"; import {User} from "@/interfaces/user";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import { getUsers } from "@/utils/users.be"; import {getUsers} from "@/utils/users.be";
import { BsTrash } from "react-icons/bs"; import {BsTrash} from "react-icons/bs";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import axios from "axios"; import axios from "axios";
import { toast, ToastContainer } from "react-toastify"; import {toast, ToastContainer} from "react-toastify";
import {Type as UserType} from '@/interfaces/user' import {Type as UserType} from "@/interfaces/user";
import {getGroups} from "@/utils/groups.be";
interface BasicUser { interface BasicUser {
id: string; id: string;
name: string; name: string;
type: UserType type: UserType;
} }
interface PermissionWithBasicUsers { interface PermissionWithBasicUsers {
id: string; id: string;
type: PermissionType; type: PermissionType;
users: BasicUser[]; users: BasicUser[];
} }
export const getServerSideProps = withIronSessionSsr(async (context) => { export const getServerSideProps = withIronSessionSsr(async (context) => {
const { req, params } = context; const {req, params} = context;
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
permanent: false, permanent: false,
}, },
}; };
} }
if (shouldRedirectHome(user)) { if (shouldRedirectHome(user)) {
return { return {
redirect: { redirect: {
destination: "/", destination: "/",
permanent: false, permanent: false,
}, },
}; };
} }
if (!params?.id) { if (!params?.id) {
return { return {
redirect: { redirect: {
destination: "/permissions", destination: "/permissions",
permanent: false, permanent: false,
}, },
}; };
} }
// Fetch data from external API // Fetch data from external API
const permission: Permission = await getPermissionDoc(params.id as string); const permission: Permission = await getPermissionDoc(params.id as string);
const allUserData: User[] = await getUsers(); const allUserData: User[] = await getUsers();
const groups = await getGroups();
const users = allUserData.map((u) => ({
id: u.id,
name: u.name,
type: u.type
})) 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 { const userGroups = groups.filter((x) => x.admin === user.id);
props: { const filteredGroups =
// permissions: permissions.map((p) => ({ id: p.id, type: p.type })), user.type === "corporate"
permission: { ? userGroups
...permission, : user.type === "mastercorporate"
id: params.id, ? groups.filter((x) => userGroups.flatMap((y) => y.participants).includes(x.admin))
users: usersData, : groups;
},
user: req.session.user, const users = allUserData.map((u) => ({
users, id: u.id,
}, name: u.name,
}; type: u.type,
})) as BasicUser[];
const filteredUsers = ["mastercorporate", "corporate"].includes(user.type)
? users.filter((u) => filteredGroups.flatMap((g) => g.participants).includes(u.id))
: users;
// 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 = filteredUsers.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: filteredUsers,
},
};
}, sessionOptions); }, sessionOptions);
interface Props { interface Props {
permission: PermissionWithBasicUsers; permission: PermissionWithBasicUsers;
user: User; user: User;
users: BasicUser[]; users: BasicUser[];
} }
export default function Page(props: Props) { export default function Page(props: Props) {
const { permission, user, users } = props; const {permission, user, users} = props;
const [selectedUsers, setSelectedUsers] = useState<string[]>(() =>
permission.users.map((u) => u.id)
);
const onChange = (value: any) => { const [selectedUsers, setSelectedUsers] = useState<string[]>(() => permission.users.map((u) => u.id));
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 () => { const onChange = (value: any) => {
setSelectedUsers((prev) => {
try { if (value?.value) {
await axios.patch(`/api/permissions/${permission.id}`, { return [...prev, value?.value];
users: selectedUsers, }
}); return prev;
toast.success("Permission updated"); });
} catch (err) { };
toast.error("Failed to update permission"); const removeUser = (id: string) => {
} setSelectedUsers((prev) => prev.filter((u) => u !== id));
}; };
return ( const update = async () => {
<> try {
<Head> await axios.patch(`/api/permissions/${permission.id}`, {
<title>EnCoach</title> users: selectedUsers,
<meta });
name="description" toast.success("Permission updated");
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." } catch (err) {
/> toast.error("Failed to update permission");
<meta name="viewport" content="width=device-width, initial-scale=1" /> }
<link rel="icon" href="/favicon.ico" /> };
</Head>
<ToastContainer /> return (
<Layout user={user} className="gap-6"> <>
<h1 className="text-2xl font-semibold"> <Head>
Permission: {permission.type as string} <title>EnCoach</title>
</h1> <meta
<div className="flex gap-3"> name="description"
<Select content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
value={null} />
options={users <meta name="viewport" content="width=device-width, initial-scale=1" />
.filter((u) => !selectedUsers.includes(u.id)) <link rel="icon" href="/favicon.ico" />
.map((u) => ({ </Head>
label: `${u?.type}-${u?.name}`, <ToastContainer />
value: u.id, <Layout user={user} className="gap-6">
}))} <div className="flex flex-col gap-6 w-full h-[88vh] overflow-y-scroll scrollbar-hide rounded-xl">
onChange={onChange} <h1 className="text-2xl font-semibold">Permission: {permission.type as string}</h1>
/> <div className="flex gap-3">
<Button onClick={update}>Update</Button> <Select
</div> value={null}
<div className="flex flex-row justify-between"> options={users
<div className="flex flex-col gap-3"> .filter((u) => !selectedUsers.includes(u.id))
<h2>Blacklisted Users</h2> .map((u) => ({
<div className="flex gap-3 flex-wrap"> label: `${u?.type}-${u?.name}`,
{selectedUsers.map((userId) => { value: u.id,
const user = users.find((u) => u.id === userId); }))}
return ( onChange={onChange}
<div />
className="flex p-4 rounded-xl w-auto bg-mti-purple-light text-white gap-4" <Button onClick={update}>Update</Button>
key={userId} </div>
> <div className="flex flex-row justify-between">
<span className="text-base first-letter:uppercase">{user?.type}-{user?.name}</span> <div className="flex flex-col gap-3">
<BsTrash <h2>Blacklisted Users</h2>
style={{ cursor: "pointer" }} <div className="flex gap-3 flex-wrap">
onClick={() => removeUser(userId)} {selectedUsers.map((userId) => {
size={20} const user = users.find((u) => u.id === userId);
/> return (
</div> <div className="flex p-4 rounded-xl w-auto bg-mti-purple-light text-white gap-4" key={userId}>
); <span className="text-base first-letter:uppercase">
})} {user?.type}-{user?.name}
</div> </span>
</div> <BsTrash style={{cursor: "pointer"}} onClick={() => removeUser(userId)} size={20} />
<div className="flex flex-col gap-3"> </div>
<h2>Whitelisted Users</h2> );
<div className="flex flex-col gap-3 flex-wrap"> })}
{users.filter(user => !selectedUsers.includes(user.id)).map((user) => { </div>
return ( </div>
<div <div className="flex flex-col gap-3">
className="flex p-4 rounded-xl w-auto bg-mti-purple-light text-white gap-4" <h2>Whitelisted Users</h2>
key={user.id} <div className="flex flex-col gap-3 flex-wrap">
> {users
<span className="text-base first-letter:uppercase">{user?.type}-{user?.name}</span> .filter((user) => !selectedUsers.includes(user.id))
</div> .map((user) => {
); return (
})} <div className="flex p-4 rounded-xl w-auto bg-mti-purple-light text-white gap-4" key={user.id}>
</div> <span className="text-base first-letter:uppercase">
</div> {user?.type}-{user?.name}
</div> </span>
</Layout> </div>
</> );
); })}
</div>
</div>
</div>
</div>
</Layout>
</>
);
} }

View File

@@ -1,78 +1,86 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import Head from "next/head"; import Head from "next/head";
import { withIronSessionSsr } from "iron-session/next"; import {withIronSessionSsr} from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import { shouldRedirectHome } from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import { Permission } from "@/interfaces/permissions"; import {Permission} from "@/interfaces/permissions";
import { getPermissionDocs } from "@/utils/permissions.be"; import {getPermissionDocs} from "@/utils/permissions.be";
import { User } from "@/interfaces/user"; import {User} from "@/interfaces/user";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import PermissionList from '@/components/PermissionList' import PermissionList from "@/components/PermissionList";
export const getServerSideProps = withIronSessionSsr(async ({ req }) => { export const getServerSideProps = withIronSessionSsr(async ({req}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
permanent: false, permanent: false,
}, },
}; };
} }
if (shouldRedirectHome(user)) { if (shouldRedirectHome(user)) {
return { return {
redirect: { redirect: {
destination: "/", destination: "/",
permanent: false, permanent: false,
}, },
}; };
} }
// Fetch data from external API // Fetch data from external API
const permissions: Permission[] = await getPermissionDocs(); const permissions: Permission[] = await getPermissionDocs();
const filteredPermissions = permissions.filter((p) => {
const permissionType = p.type.toString().toLowerCase();
if (user.type === "corporate") return !permissionType.includes("corporate") && !permissionType.includes("admin");
if (user.type === "mastercorporate") return !permissionType.includes("mastercorporate") && !permissionType.includes("admin");
// const res = await fetch("api/permissions"); return true;
// const permissions: Permission[] = await res.json(); });
// Pass data to the page via props
return { // const res = await fetch("api/permissions");
props: { // const permissions: Permission[] = await res.json();
// permissions: permissions.map((p) => ({ id: p.id, type: p.type })), // Pass data to the page via props
permissions: permissions.map((p) => { return {
const { users, ...rest } = p; props: {
return rest; // permissions: permissions.map((p) => ({ id: p.id, type: p.type })),
}), permissions: filteredPermissions.map((p) => {
user: req.session.user, const {users, ...rest} = p;
}, return rest;
}; }),
user: req.session.user,
},
};
}, sessionOptions); }, sessionOptions);
interface Props { interface Props {
permissions: Permission[]; permissions: Permission[];
user: User; user: User;
} }
export default function Page(props: Props) { export default function Page(props: Props) {
const { permissions, user } = props; const {permissions, user} = props;
return (
<> return (
<Head> <>
<title>EnCoach</title> <Head>
<meta <title>EnCoach</title>
name="description" <meta
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." name="description"
/> content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
<meta name="viewport" content="width=device-width, initial-scale=1" /> />
<link rel="icon" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</Head> <link rel="icon" href="/favicon.ico" />
<Layout user={user} className="gap-6"> </Head>
<h1 className="text-2xl font-semibold">Permissions</h1> <Layout user={user} className="gap-6">
<div className="flex gap-3 flex-wrap"> <h1 className="text-2xl font-semibold">Permissions</h1>
<PermissionList permissions={permissions} /> <div className="flex gap-3 flex-wrap overflow-y-scroll scrollbar-hide h-[80vh] rounded-xl">
</div> <PermissionList permissions={permissions} />
</Layout> </div>
</> </Layout>
); </>
);
} }

View File

@@ -1,4 +1,90 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer utilities {
.scrollbar-hide {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
/* Chrome, Safari and Opera */
}
}
.training-scrollbar::-webkit-scrollbar {
@apply w-1.5;
}
.training-scrollbar::-webkit-scrollbar-track {
@apply bg-transparent;
}
.training-scrollbar::-webkit-scrollbar-thumb {
@apply bg-gray-400 hover:bg-gray-500 rounded-full transition-colors opacity-50 hover:opacity-75;
}
.training-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
}
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
--foreground-rgb: 53, 51, 56;
--background-start-rgb: 245, 245, 245;
--background-end-rgb: 245, 245, 245;
--primary-glow: conic-gradient(from 180deg at 50% 50%, #16abff33 0deg, #0885ff33 55deg, #54d6ff33 120deg, #0071ff33 160deg, transparent 360deg);
--secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(#00000080, #00000040, #00000030, #00000020, #00000010, #00000010, #00000080);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
min-height: 100vh !important;
height: 100%;
max-width: 100vw;
overflow-x: hidden;
overflow-y: auto;
font-family: "Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif;
}
body {
min-height: 100vh !important;
height: 100%;
max-width: 100vw;
overflow-x: hidden;
font-family: "Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}

View File

@@ -33,6 +33,11 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID
return; return;
}; };
export const getGroups = async () => {
const groupDocs = await getDocs(collection(db, "groups"));
return groupDocs.docs.map((x) => ({...x.data(), id: x.id})) as Group[];
};
export const getUserGroups = async (id: string): Promise<Group[]> => { export const getUserGroups = async (id: string): Promise<Group[]> => {
const groupDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id))); const groupDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id)));
return groupDocs.docs.map((x) => ({...x.data(), id})) as Group[]; return groupDocs.docs.map((x) => ({...x.data(), id})) as Group[];