diff --git a/src/components/PermissionList.tsx b/src/components/PermissionList.tsx index 2911b22f..e3424c08 100644 --- a/src/components/PermissionList.tsx +++ b/src/components/PermissionList.tsx @@ -1,105 +1,82 @@ import React from "react"; -import { Permission } from "@/interfaces/permissions"; -import { - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, - Row, -} from "@tanstack/react-table"; +import {Permission} from "@/interfaces/permissions"; +import {createColumnHelper, flexRender, getCoreRowModel, useReactTable, Row} from "@tanstack/react-table"; import Link from "next/link"; -import { convertCamelCaseToReadable } from "@/utils/string"; +import {convertCamelCaseToReadable} from "@/utils/string"; interface Props { - permissions: Permission[]; + permissions: Permission[]; } const columnHelper = createColumnHelper(); const defaultColumns = [ - columnHelper.accessor("type", { - header: () => Type, - cell: ({ row, getValue }) => ( - - {convertCamelCaseToReadable(getValue() as string)} - - ), - }), + columnHelper.accessor("type", { + header: () => Type, + cell: ({row, getValue}) => ( + + {convertCamelCaseToReadable(getValue() as string)} + + ), + }), ]; -export default function PermissionList({ permissions }: Props) { - const table = useReactTable({ - data: permissions, - columns: defaultColumns, - getCoreRowModel: getCoreRowModel(), - }); +export default function PermissionList({permissions}: Props) { + const table = useReactTable({ + data: permissions, + columns: defaultColumns, + getCoreRowModel: getCoreRowModel(), + }); - const groupedData: { [key: string]: Row[] } = table - .getRowModel() - .rows.reduce((groups: { [key: string]: Row[] }, row) => { - const parent = row.original.topic; - if (!groups[parent]) { - groups[parent] = []; - } - groups[parent].push(row); - return groups; - }, {}); + const groupedData: {[key: string]: Row[]} = table.getRowModel().rows.reduce((groups: {[key: string]: Row[]}, row) => { + const parent = row.original.topic; + if (!groups[parent]) { + groups[parent] = []; + } + groups[parent].push(row); + return groups; + }, {}); - return ( -
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {Object.keys(groupedData).map((parent) => ( - - - - - {groupedData[parent].map((row, i) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - - ))} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} -
- {parent} -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
- ); + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {Object.keys(groupedData).map((parent) => ( + + + + + {groupedData[parent].map((row, i) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + + ))} + +
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} +
+ {parent} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+
+ ); } diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx index 243df376..a87caa94 100644 --- a/src/dashboards/Teacher.tsx +++ b/src/dashboards/Teacher.tsx @@ -316,7 +316,13 @@ export default function TeacherDashboard({user}: Props) { color="purple" /> {checkAccess(user, ["teacher", "developer"], permissions, "viewGroup") && ( - setPage("groups")} /> + x.admin === user.id).length} + color="purple" + onClick={() => setPage("groups")} + /> )}
setPage("assignments")} diff --git a/src/pages/(admin)/Lists/GroupList.tsx b/src/pages/(admin)/Lists/GroupList.tsx index 47197b36..db035825 100644 --- a/src/pages/(admin)/Lists/GroupList.tsx +++ b/src/pages/(admin)/Lists/GroupList.tsx @@ -210,7 +210,6 @@ export default function GroupList({user}: {user: User}) { const {groups, reload} = useGroups({ admin: user && filterTypes.includes(user?.type) ? user.id : undefined, userType: user?.type, - adminAdmins: user?.type === "teacher" ? user?.id : undefined, }); useEffect(() => { diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx index 2fd4660d..82577a61 100644 --- a/src/pages/generation.tsx +++ b/src/pages/generation.tsx @@ -1,19 +1,19 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import { withIronSessionSsr } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; +import {withIronSessionSsr} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; import useUser from "@/hooks/useUser"; -import { toast, ToastContainer } from "react-toastify"; +import {toast, ToastContainer} from "react-toastify"; import Layout from "@/components/High/Layout"; -import { shouldRedirectHome } from "@/utils/navigation.disabled"; -import { useState } from "react"; -import { Module } from "@/interfaces"; -import { RadioGroup, Tab } from "@headlessui/react"; +import {shouldRedirectHome} from "@/utils/navigation.disabled"; +import {useState} from "react"; +import {Module} from "@/interfaces"; +import {RadioGroup, Tab} from "@headlessui/react"; import clsx from "clsx"; -import { MODULE_ARRAY } from "@/utils/moduleUtils"; -import { capitalize } from "lodash"; +import {MODULE_ARRAY} from "@/utils/moduleUtils"; +import {capitalize} from "lodash"; import Button from "@/components/Low/Button"; -import { Exercise, ReadingPart } from "@/interfaces/exam"; +import {Exercise, ReadingPart} from "@/interfaces/exam"; import Input from "@/components/Low/Input"; import axios from "axios"; import ReadingGeneration from "./(generation)/ReadingGeneration"; @@ -21,121 +21,114 @@ import ListeningGeneration from "./(generation)/ListeningGeneration"; import WritingGeneration from "./(generation)/WritingGeneration"; import LevelGeneration from "./(generation)/LevelGeneration"; import SpeakingGeneration from "./(generation)/SpeakingGeneration"; -import { checkAccess, getTypesOfUser } from "@/utils/permissions"; +import {checkAccess} from "@/utils/permissions"; -export const getServerSideProps = withIronSessionSsr(({ req, res }) => { - const user = req.session.user; +export const getServerSideProps = withIronSessionSsr(({req, res}) => { + const user = req.session.user; - if (!user || !user.isVerified) { - return { - redirect: { - destination: "/login", - permanent: false, - }, - }; - } + if (!user || !user.isVerified) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } - if ( - shouldRedirectHome(user) || - checkAccess(user, getTypesOfUser(["developer"])) - ) { - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } + if (shouldRedirectHome(user) || !checkAccess(user, ["admin", "mastercorporate", "developer", "corporate"])) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } - return { - props: { user: req.session.user }, - }; + return { + props: {user: req.session.user}, + }; }, sessionOptions); export default function Generation() { - const [module, setModule] = useState("reading"); + const [module, setModule] = useState("reading"); - const { user } = useUser({ redirectTo: "/login" }); + const {user} = useUser({redirectTo: "/login"}); - const [title, setTitle] = useState(""); - return ( - <> - - Exam Generation | EnCoach - - - - - - {user && ( - -

Exam Generation

-
- + const [title, setTitle] = useState(""); + return ( + <> + + Exam Generation | EnCoach + + + + + + {user && ( + +

Exam Generation

+
+ - - - {[...MODULE_ARRAY].map((x) => ( - - {({ checked }) => ( - - {capitalize(x)} - - )} - - ))} - -
- {module === "reading" && } - {module === "listening" && } - {module === "writing" && } - {module === "speaking" && } - {module === "level" && } -
- )} - - ); + + + {[...MODULE_ARRAY].map((x) => ( + + {({checked}) => ( + + {capitalize(x)} + + )} + + ))} + +
+ {module === "reading" && } + {module === "listening" && } + {module === "writing" && } + {module === "speaking" && } + {module === "level" && } +
+ )} + + ); } diff --git a/src/pages/permissions/[id].tsx b/src/pages/permissions/[id].tsx index bafcaaf8..022ba933 100644 --- a/src/pages/permissions/[id].tsx +++ b/src/pages/permissions/[id].tsx @@ -1,209 +1,209 @@ /* 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 {useEffect, 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 {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"; -import {Type as UserType} from '@/interfaces/user' +import {toast, ToastContainer} from "react-toastify"; +import {Type as UserType} from "@/interfaces/user"; +import {getGroups} from "@/utils/groups.be"; interface BasicUser { - id: string; - name: string; - type: UserType + id: string; + name: string; + type: UserType; } interface PermissionWithBasicUsers { - id: string; - type: PermissionType; - users: BasicUser[]; + id: string; + type: PermissionType; + users: BasicUser[]; } export const getServerSideProps = withIronSessionSsr(async (context) => { - const { req, params } = context; - const user = req.session.user; + const {req, params} = context; + 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, + }, + }; + } - if (!params?.id) { - return { - redirect: { - destination: "/permissions", - 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); + // 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, - 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; - }, - [] - ); + const allUserData: User[] = await getUsers(); + const groups = await getGroups(); - return { - props: { - // permissions: permissions.map((p) => ({ id: p.id, type: p.type })), - permission: { - ...permission, - id: params.id, - users: usersData, - }, - user: req.session.user, - users, - }, - }; + const userGroups = groups.filter((x) => x.admin === user.id); + const filteredGroups = + user.type === "corporate" + ? userGroups + : user.type === "mastercorporate" + ? groups.filter((x) => userGroups.flatMap((y) => y.participants).includes(x.admin)) + : groups; + + const users = allUserData.map((u) => ({ + 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); interface Props { - permission: PermissionWithBasicUsers; - user: User; - users: BasicUser[]; + permission: PermissionWithBasicUsers; + user: User; + users: BasicUser[]; } export default function Page(props: Props) { - const { permission, user, users } = props; - - - const [selectedUsers, setSelectedUsers] = useState(() => - permission.users.map((u) => u.id) - ); + const {permission, user, users} = props; - const onChange = (value: any) => { - - setSelectedUsers((prev) => { - if (value?.value) { - return [...prev, value?.value]; - } - return prev; - }); - }; - const removeUser = (id: string) => { - setSelectedUsers((prev) => prev.filter((u) => u !== id)); - }; + const [selectedUsers, setSelectedUsers] = useState(() => permission.users.map((u) => u.id)); - const update = async () => { - - 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} -

-
- !selectedUsers.includes(u.id)) + .map((u) => ({ + label: `${u?.type}-${u?.name}`, + value: u.id, + }))} + onChange={onChange} + /> + +
+
+
+

Blacklisted Users

+
+ {selectedUsers.map((userId) => { + const user = users.find((u) => u.id === userId); + return ( +
+ + {user?.type}-{user?.name} + + removeUser(userId)} size={20} /> +
+ ); + })} +
+
+
+

Whitelisted Users

+
+ {users + .filter((user) => !selectedUsers.includes(user.id)) + .map((user) => { + return ( +
+ + {user?.type}-{user?.name} + +
+ ); + })} +
+
+
+
+ + + ); } diff --git a/src/pages/permissions/index.tsx b/src/pages/permissions/index.tsx index bce6ed26..373707e7 100644 --- a/src/pages/permissions/index.tsx +++ b/src/pages/permissions/index.tsx @@ -1,78 +1,86 @@ /* 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 {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 PermissionList from '@/components/PermissionList' +import PermissionList from "@/components/PermissionList"; -export const getServerSideProps = withIronSessionSsr(async ({ req }) => { - const user = req.session.user; +export const getServerSideProps = withIronSessionSsr(async ({req}) => { + 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, + }, + }; + } - // Fetch data from external API - const permissions: Permission[] = await getPermissionDocs(); + // Fetch data from external API + 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"); - // 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, - }, - }; + return true; + }); + + // 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: filteredPermissions.map((p) => { + const {users, ...rest} = p; + return rest; + }), + user: req.session.user, + }, + }; }, sessionOptions); interface Props { - permissions: Permission[]; - user: User; + permissions: Permission[]; + user: User; } export default function Page(props: Props) { - const { permissions, user } = props; - return ( - <> - - EnCoach - - - - - -

Permissions

-
- -
-
- - ); + const {permissions, user} = props; + + return ( + <> + + EnCoach + + + + + +

Permissions

+
+ +
+
+ + ); } diff --git a/src/styles/globals.css b/src/styles/globals.css index ee65b924..1db0b8a7 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,4 +1,90 @@ @tailwind base; @tailwind components; @tailwind utilities; - \ No newline at end of file + +@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; +} diff --git a/src/utils/groups.be.ts b/src/utils/groups.be.ts index 77fc5d23..5b793f8d 100644 --- a/src/utils/groups.be.ts +++ b/src/utils/groups.be.ts @@ -33,6 +33,11 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID 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 => { const groupDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id))); return groupDocs.docs.map((x) => ({...x.data(), id})) as Group[];