diff --git a/src/components/Exercises/FillBlanks.tsx b/src/components/Exercises/FillBlanks.tsx
index 74f114a3..6172b32b 100644
--- a/src/components/Exercises/FillBlanks.tsx
+++ b/src/components/Exercises/FillBlanks.tsx
@@ -110,13 +110,20 @@ export default function FillBlanks({id, allowRepetition, prompt, solutions, text
setCurrentBlankId(undefined);
}}
/>
- {prompt}
+
+ {prompt.split("\\n").map((line, index) => (
+
+ {line}
+
+
+ ))}
+
- {text.split("\n").map((line) => (
- <>
+ {text.split("\\n").map((line, index) => (
+
{renderLines(line)}
- >
+
))}
diff --git a/src/components/Exercises/WriteBlanks.tsx b/src/components/Exercises/WriteBlanks.tsx
index ab286d2b..ea893bdc 100644
--- a/src/components/Exercises/WriteBlanks.tsx
+++ b/src/components/Exercises/WriteBlanks.tsx
@@ -79,7 +79,7 @@ export default function WriteBlanks({id, prompt, maxWords, solutions, text, onNe
{prompt}
- {text.split("\n").map((line) => (
+ {text.split("\\n").map((line) => (
<>
{renderLines(line)}
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index cabaad95..581c6733 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -35,10 +35,9 @@ export default function Navbar({profilePicture}: Props) {
-
+
Profile
- New
-
+
Settings
diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx
index e7ebe28c..40d97eb6 100644
--- a/src/components/ProfileCard.tsx
+++ b/src/components/ProfileCard.tsx
@@ -17,9 +17,7 @@ export default function ProfileCard({user, className}: Props) {
-
- {user.name.first} {user.name.last}
-
+ {user.name}
diff --git a/src/components/Solutions/FillBlanks.tsx b/src/components/Solutions/FillBlanks.tsx
index ca0142c5..86fafc1d 100644
--- a/src/components/Solutions/FillBlanks.tsx
+++ b/src/components/Solutions/FillBlanks.tsx
@@ -47,7 +47,7 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
{prompt}
- {text.split("\n").map((line) => (
+ {text.split("\\n").map((line) => (
<>
{renderLines(line)}
diff --git a/src/components/Solutions/WriteBlanks.tsx b/src/components/Solutions/WriteBlanks.tsx
index d3ecddd0..4dc3647a 100644
--- a/src/components/Solutions/WriteBlanks.tsx
+++ b/src/components/Solutions/WriteBlanks.tsx
@@ -82,7 +82,7 @@ export default function WriteBlanksSolutions({
{prompt}
- {text.split("\n").map((line) => (
+ {text.split("\\n").map((line) => (
<>
{renderLines(line)}
diff --git a/src/exams/Reading.tsx b/src/exams/Reading.tsx
index 502c0b7c..f57d4d5b 100644
--- a/src/exams/Reading.tsx
+++ b/src/exams/Reading.tsx
@@ -7,6 +7,8 @@ import {infoButtonStyle} from "@/constants/buttonStyles";
import {Dialog, Transition} from "@headlessui/react";
import {renderExercise} from "@/components/Exercises";
import {renderSolution} from "@/components/Solutions";
+import {Panel} from "primereact/panel";
+import {Steps} from "primereact/steps";
interface Props {
exam: ReadingExam;
@@ -45,7 +47,7 @@ function TextModal({isOpen, title, content, onClose}: {isOpen: boolean; title: s
- {content.split("\n").map((line, index) => (
+ {content.split("\\n").map((line, index) => (
{line}
@@ -115,17 +117,13 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
You will be allowed to read the text while doing the exercises
-
-
{exam.text.title}
-
- {exam.text.content.split("\n").map((line, index) => (
-
- {line}
-
-
+
+
+ {exam.text.content.split("\\n").map((line, index) => (
+
{line}
))}
-
-
+
+
>
);
diff --git a/src/exams/Writing.tsx b/src/exams/Writing.tsx
index 8241d490..6b6cbde2 100644
--- a/src/exams/Writing.tsx
+++ b/src/exams/Writing.tsx
@@ -50,7 +50,7 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
{exam.text.info}
- {exam.text.prompt.split("\n").map((line, index) => (
+ {exam.text.prompt.split("\\n").map((line, index) => (
{line}
diff --git a/src/hooks/useUsers.tsx b/src/hooks/useUsers.tsx
new file mode 100644
index 00000000..231aa0f2
--- /dev/null
+++ b/src/hooks/useUsers.tsx
@@ -0,0 +1,19 @@
+import {User} from "@/interfaces/user";
+import axios from "axios";
+import {useEffect, useState} from "react";
+
+export default function useUsers() {
+ const [users, setUsers] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isError, setIsError] = useState(false);
+
+ useEffect(() => {
+ setIsLoading(true);
+ axios
+ .get("/api/users/list")
+ .then((response) => setUsers(response.data))
+ .finally(() => setIsLoading(false));
+ }, []);
+
+ return {users, isLoading, isError};
+}
diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts
index 37c786f9..54e00842 100644
--- a/src/interfaces/user.ts
+++ b/src/interfaces/user.ts
@@ -1,11 +1,10 @@
export interface User {
email: string;
- name: Name;
+ name: string;
profilePicture: string;
id: string;
experience: number;
+ type: Type;
}
-interface Name {
- first: string;
- last: string;
-}
+
+export type Type = "student" | "teacher" | "admin" | "owner" | "developer";
diff --git a/src/pages/api/users/list.ts b/src/pages/api/users/list.ts
new file mode 100644
index 00000000..095e4722
--- /dev/null
+++ b/src/pages/api/users/list.ts
@@ -0,0 +1,26 @@
+// 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} 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.session.user) {
+ res.status(401).json({ok: false});
+ return;
+ }
+
+ const snapshot = await getDocs(collection(db, "users"));
+
+ res.status(200).json(
+ snapshot.docs.map((doc) => ({
+ id: doc.id,
+ ...doc.data(),
+ })),
+ );
+}
diff --git a/src/pages/exam/index.tsx b/src/pages/exam/index.tsx
index e61782b9..dae93891 100644
--- a/src/pages/exam/index.tsx
+++ b/src/pages/exam/index.tsx
@@ -133,7 +133,7 @@ export default function Page({user}: {user: User}) {
-
+
{renderScreen()}
diff --git a/src/pages/login.tsx b/src/pages/login.tsx
index 0dfdbaa2..43e02115 100644
--- a/src/pages/login.tsx
+++ b/src/pages/login.tsx
@@ -6,6 +6,7 @@ import Head from "next/head";
import useUser from "@/hooks/useUser";
import {InputText} from "primereact/inputtext";
import {Button} from "primereact/button";
+import {Password} from "primereact/password";
export default function Login() {
const [email, setEmail] = useState("");
@@ -65,10 +66,11 @@ export default function Login() {
- setPassword(e.target.value)}
autoComplete="current-password"
diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx
new file mode 100644
index 00000000..d652f6a1
--- /dev/null
+++ b/src/pages/profile.tsx
@@ -0,0 +1,49 @@
+import {withIronSessionSsr} from "iron-session/next";
+import {sessionOptions} from "@/lib/session";
+import {User} from "@/interfaces/user";
+import Head from "next/head";
+import Navbar from "@/components/Navbar";
+import {Avatar} from "primereact/avatar";
+
+export const getServerSideProps = withIronSessionSsr(({req, res}) => {
+ const user = req.session.user;
+
+ if (!user) {
+ res.setHeader("location", "/login");
+ res.statusCode = 302;
+ res.end();
+ return {
+ props: {
+ user: null,
+ },
+ };
+ }
+
+ return {
+ props: {user: req.session.user},
+ };
+}, sessionOptions);
+
+export default function Profile({user}: {user: User}) {
+ return (
+ <>
+
+ IELTS GPT | Profile
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/users.tsx b/src/pages/users.tsx
new file mode 100644
index 00000000..f7befcb6
--- /dev/null
+++ b/src/pages/users.tsx
@@ -0,0 +1,103 @@
+import {withIronSessionSsr} from "iron-session/next";
+import {sessionOptions} from "@/lib/session";
+import {Type, User} from "@/interfaces/user";
+import Head from "next/head";
+import Navbar from "@/components/Navbar";
+import {Avatar} from "primereact/avatar";
+import {useEffect, useState} from "react";
+import {FilterMatchMode, FilterOperator} from "primereact/api";
+import useUsers from "@/hooks/useUsers";
+import {DataTable} from "primereact/datatable";
+import {Column} from "primereact/column";
+import _ from "lodash";
+import {levelCalculator} from "@/resources/level";
+import {Dropdown} from "primereact/dropdown";
+
+export const getServerSideProps = withIronSessionSsr(({req, res}) => {
+ const user = req.session.user;
+
+ if (!user) {
+ res.setHeader("location", "/login");
+ res.statusCode = 302;
+ res.end();
+ return {
+ props: {
+ user: null,
+ },
+ };
+ }
+
+ return {
+ props: {user: req.session.user},
+ };
+}, sessionOptions);
+
+export default function Users({user}: {user: User}) {
+ const {users, isLoading} = useUsers();
+ const [filters] = useState({
+ name: {value: null, matchMode: FilterMatchMode.CONTAINS},
+ type: {value: null, matchMode: FilterMatchMode.EQUALS},
+ });
+
+ const userTypes: Type[] = ["admin", "developer", "owner", "student", "teacher"];
+
+ const typeRowFilterTemplate = (options: any) => {
+ return (
+ _.capitalize(x))}
+ onChange={(e) => options.filterApplyCallback(e.value)}
+ placeholder="Select One"
+ className="p-column-filter"
+ showClear
+ style={{minWidth: "12rem"}}
+ />
+ );
+ };
+
+ return (
+ <>
+
+ IELTS GPT | Profile
+
+
+
+
+
+
+
+
+
+
+
+ levelCalculator(data.experience).currentLevel} />
+ _.capitalize(data.type)}
+ />
+
+
+
+ >
+ );
+}
diff --git a/src/resources/permissions.ts b/src/resources/permissions.ts
new file mode 100644
index 00000000..b816ddbf
--- /dev/null
+++ b/src/resources/permissions.ts
@@ -0,0 +1,201 @@
+import {Type, User} from "@/interfaces/user";
+
+interface Permissions {
+ createUser: {[key in Type]: boolean};
+ deleteUser: {[key in Type]: boolean};
+ manageUser: {[key in Type]: boolean};
+ viewUsers: {[key in Type]: boolean};
+ viewStats: boolean;
+ viewUserStats: boolean;
+ viewClassStats: boolean;
+ createClass: boolean;
+ manageClass: boolean;
+ deleteClass: boolean;
+}
+
+const permissions: {[key in Type]: Permissions} = {
+ student: {
+ createUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: false,
+ teacher: false,
+ },
+ deleteUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: false,
+ teacher: false,
+ },
+ manageUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: false,
+ teacher: false,
+ },
+ viewUsers: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: false,
+ teacher: false,
+ },
+ createClass: false,
+ deleteClass: false,
+ manageClass: false,
+ viewStats: true,
+ viewUserStats: false,
+ viewClassStats: false,
+ },
+ teacher: {
+ createUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: false,
+ },
+ deleteUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: false,
+ },
+ manageUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: false,
+ },
+ viewUsers: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: false,
+ },
+ createClass: true,
+ deleteClass: true,
+ manageClass: true,
+ viewStats: true,
+ viewUserStats: true,
+ viewClassStats: true,
+ },
+ admin: {
+ createUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ deleteUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ manageUser: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ viewUsers: {
+ admin: false,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ createClass: true,
+ deleteClass: true,
+ manageClass: true,
+ viewStats: true,
+ viewUserStats: true,
+ viewClassStats: true,
+ },
+ owner: {
+ createUser: {
+ admin: true,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ deleteUser: {
+ admin: true,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ manageUser: {
+ admin: true,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ viewUsers: {
+ admin: true,
+ developer: false,
+ owner: false,
+ student: true,
+ teacher: true,
+ },
+ createClass: true,
+ deleteClass: true,
+ manageClass: true,
+ viewStats: true,
+ viewUserStats: true,
+ viewClassStats: true,
+ },
+ developer: {
+ createUser: {
+ admin: true,
+ developer: true,
+ owner: true,
+ student: true,
+ teacher: true,
+ },
+ deleteUser: {
+ admin: true,
+ developer: true,
+ owner: true,
+ student: true,
+ teacher: true,
+ },
+ manageUser: {
+ admin: true,
+ developer: true,
+ owner: true,
+ student: true,
+ teacher: true,
+ },
+ viewUsers: {
+ admin: true,
+ developer: true,
+ owner: true,
+ student: true,
+ teacher: true,
+ },
+ createClass: true,
+ deleteClass: true,
+ manageClass: true,
+ viewStats: true,
+ viewUserStats: true,
+ viewClassStats: true,
+ },
+};
+
+export default function getPermissions(user: User) {
+ return permissions[user.type];
+}