+ {/* 0
+ ? "var(--mantine-color-teal-6)"
+ : "var(--mantine-color-red-6)",
+ }}
+ size={38}
+ radius="md"
+ >
+
+ */}
+
+ {/*
+ 0 ? "teal" : "red"} fw={700}>
+ {stat.diff}%
+ {" "}
+ {stat.diff > 0 ? "increase" : "decrease"} compared to last month
+ */}
+
+ );
+}
diff --git a/peer-prep/src/hooks/useAuth.tsx b/peer-prep/src/hooks/useAuth.tsx
index a930497bea..b150c1f88e 100644
--- a/peer-prep/src/hooks/useAuth.tsx
+++ b/peer-prep/src/hooks/useAuth.tsx
@@ -1,22 +1,34 @@
import { useLocalStorage } from "@mantine/hooks";
-import { createContext, ReactElement, useContext, useMemo } from "react";
+import {
+ createContext,
+ ReactElement,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
import { useNavigate } from "react-router-dom";
import { User, UserResponseData } from "../types/user";
import useApi, { ServerResponse, SERVICE } from "./useApi";
import { notifications } from "@mantine/notifications";
-
+export enum AUTH_STATUS {
+ LOGGED_IN,
+ LOGGED_OUT,
+ LOADING,
+}
export interface AuthContextType {
user: User | null;
login: (data: any) => void;
logout: () => void;
register: (data: any) => void;
+ authStatus: AUTH_STATUS;
}
const DEFAULT_TEMP_USER: User = {
email: "johndoe@gmail.com",
displayName: "John Doe",
isAdmin: false,
- id: "123",
+ _id: "123",
// password: "password",
};
@@ -27,6 +39,7 @@ const DEFAULT: AuthContextType = {
},
logout: () => {},
register: () => {},
+ authStatus: AUTH_STATUS.LOADING,
};
const AuthContext = createContext(DEFAULT);
@@ -37,9 +50,23 @@ export const AuthProvider = ({
}) => {
const [user, setUser] = useLocalStorage({
key: "user",
- defaultValue: null,
+ defaultValue: undefined,
});
+ const [authStatus, setAuthStatus] = useState(AUTH_STATUS.LOADING);
+
+ console.log({ authStatus });
+
+ useEffect(() => {
+ console.log(`log: user changed: `, user);
+ if (user === null) {
+ setAuthStatus(AUTH_STATUS.LOGGED_OUT);
+ } else if (user && user._id) {
+ // if this is a valid user
+ setAuthStatus(AUTH_STATUS.LOGGED_IN);
+ }
+ }, [user]);
+
const { fetchData, isLoading, error } = useApi();
const navigate = useNavigate();
@@ -159,8 +186,9 @@ export const AuthProvider = ({
login,
logout,
register,
+ authStatus,
}),
- [user]
+ [user, authStatus]
);
return {children};
};
diff --git a/peer-prep/src/main.tsx b/peer-prep/src/main.tsx
index 739abc18b6..ffacc9ea40 100644
--- a/peer-prep/src/main.tsx
+++ b/peer-prep/src/main.tsx
@@ -12,6 +12,7 @@ import LoginOrRegisterPage from "./pages/Login/LoginPage.tsx";
import { Button, createTheme, MantineProvider } from "@mantine/core";
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
+import '@mantine/tiptap/styles.css';
import ApplicationWrapper from "./components/ApplicationWrapper.tsx";
import ProtectedRouteWrapper from "./pages/ProtectedRouteWrapper.tsx";
@@ -19,11 +20,12 @@ import { AuthProvider } from "./hooks/useAuth.tsx";
import DashboardPage from "./pages/Dashboard/DashboardPage.tsx";
import SearchingPage from "./pages/Session/Search/SearchingPage.tsx";
import CreateSessionPage from "./pages/Session/Create/CreateSessionPage.tsx";
-import QuestionPage from "./pages/Questions/QuestionPage.tsx";
+import QuestionsPage from "./pages/Questions/QuestionsPage.tsx";
import CreateQuestionPage from "./pages/Questions/CreateQuestionPage/CreateQuestionPage.tsx";
import EditQuestionPage from "./pages/Questions/EditQuestionPage/EditQuestionPage.tsx";
import { Notifications } from "@mantine/notifications";
import AdminRouteWrapper from "./pages/AdminRouteWrapper.tsx";
+import ReadQuestionPage from "./pages/Questions/ReadQuestionPage/ReadQuestionPage.tsx";
const router = createBrowserRouter([
{
path: "/",
@@ -53,7 +55,7 @@ const router = createBrowserRouter([
children: [
{
path: "/questions",
- element: ,
+ element: ,
loader: async () =>
fetch(
`${
@@ -72,6 +74,16 @@ const router = createBrowserRouter([
path: "/questions/edit/:id",
element: ,
},
+ {
+ path: ":id",
+ element: ,
+ loader: async ({ params }) =>
+ fetch(
+ `${
+ import.meta.env.VITE_API_URL_QUESTION
+ }/question-service/id/${params.id}`
+ ),
+ },
],
},
],
@@ -115,9 +127,10 @@ const router = createBrowserRouter([
]);
const theme = createTheme({
- fontFamily: "Montserrat, sans-serif",
+ fontFamily: "Inter",
defaultRadius: "md",
cursorType: "pointer",
+ primaryColor: "cyan",
});
createRoot(document.getElementById("root")!).render(
diff --git a/peer-prep/src/pages/AdminRouteWrapper.tsx b/peer-prep/src/pages/AdminRouteWrapper.tsx
index 8efd22e8bf..c589623671 100644
--- a/peer-prep/src/pages/AdminRouteWrapper.tsx
+++ b/peer-prep/src/pages/AdminRouteWrapper.tsx
@@ -1,12 +1,16 @@
import { Navigate, Outlet, useNavigate } from "react-router-dom";
-import { useAuth } from "../hooks/useAuth";
+import { AUTH_STATUS, useAuth } from "../hooks/useAuth";
import { notifications } from "@mantine/notifications";
// https://stackoverflow.com/questions/70833727/using-react-router-v6-i-need-a-navbar-to-permanently-be-there-but-cant-display
export default function AdminRouteWrapper() {
- const { user } = useAuth();
+ const { user, authStatus } = useAuth();
const navigate = useNavigate();
- if (!user || !user.isAdmin) {
+
+ if (authStatus === AUTH_STATUS.LOADING) {
+ return <>>;
+ }
+ if (authStatus === AUTH_STATUS.LOGGED_IN && !user.isAdmin) {
// user is not admin
// show an error admin message
notifications.show({
diff --git a/peer-prep/src/pages/Dashboard/DashboardPage.tsx b/peer-prep/src/pages/Dashboard/DashboardPage.tsx
index 9359d94fab..e4585d8963 100644
--- a/peer-prep/src/pages/Dashboard/DashboardPage.tsx
+++ b/peer-prep/src/pages/Dashboard/DashboardPage.tsx
@@ -43,7 +43,7 @@ export default function DashboardPage() {
{SAMPLE_QUESTIONS.map((question, key) => (
-
+
))}
diff --git a/peer-prep/src/pages/Home/HomePage.module.css b/peer-prep/src/pages/Home/HomePage.module.css
index 9887a97f4a..888a797c2d 100644
--- a/peer-prep/src/pages/Home/HomePage.module.css
+++ b/peer-prep/src/pages/Home/HomePage.module.css
@@ -1,10 +1,111 @@
.header {
- min-height: calc(100vh - 60px - var(--mantine-spacing-md));
- /* background-color: var(--mantine-color-gray-0); */
+ .headerText {
+ font-weight: 600;
+ .highlight {
+ font-size: 4rem;
+ font-weight: 700;
+
+ margin-right: 0.65rem;
+
+ /* border-bottom: 8px solid;
+ border-color: var(--mantine-color-cyan-7); */
+ position: relative;
+ &::before {
+ /* content: "";
+ display: block;
+ width: 100%;
+ height: 8px;
+ background-color: var(--mantine-color-cyan-7); */
+ position: absolute;
+ left: 0;
+ top: 1.1em;
+ height: 0;
+ width: 80%;
+ content: "";
+ border-top: 8px solid;
+ border-color: var(--mantine-color-cyan-7);
+ }
+ }
+ }
+ .subtitleText {
+ font-size: 1.5rem;
+ margin-top: 1rem;
+ }
+}
+
+.subHeader {
+ display: flex;
+ .steps {
+ flex-grow: 1;
+ margin-left: 2rem;
+
+ .step {
+ display: flex;
+ flex-direction: column;
+ .stepIcon {
+ padding: 1rem;
+ background-color: var(--mantine-color-cyan-6);
+ width: 48px;
+ height: 48px;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ border-radius: 50%;
+ color: white;
+
+ font-size: x-large;
+ font-weight: 600;
+
+ .stepRing {
+ /* white border then blue ring */
+ width: 64px;
+ height: 64px;
+ position: absolute;
+ /* background-color: red; */
+
+ z-index: -1000;
+
+ border-radius: 50%;
+
+ border: 4px solid var(--mantine-color-cyan-6);
+ }
+ }
+ .stepText {
+ margin-left: 2rem;
+ font-weight: 700;
+ font-size: 1.75rem;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .stepDivider {
+ width: 2px;
+ height: 40px;
+ background-color: var(--mantine-color-cyan-6);
+ margin-top: 1.25rem;
+ margin-bottom: 1.25rem;
+ margin-left: 23px;
+ }
+ }
+ }
+
+ .image {
+ flex-grow: 1;
+ width: 360px;
+ }
}
+/* .header {
+ min-height: calc(100vh - 60px - var(--mantine-spacing-md));
+} */
+
.header-content {
min-height: 100vh;
+ margin-top: 5rem;
}
.title {
text-align: center;
@@ -23,3 +124,9 @@
.shaded {
background-color: var(--mantine-color-gray-0);
}
+
+:root[data-mantine-color-scheme="dark"] {
+ .shaded {
+ background-color: var(--mantine-color-dark-8);
+ }
+}
diff --git a/peer-prep/src/pages/Home/HomePage.tsx b/peer-prep/src/pages/Home/HomePage.tsx
index 437f01acf1..ef32f8cf1f 100644
--- a/peer-prep/src/pages/Home/HomePage.tsx
+++ b/peer-prep/src/pages/Home/HomePage.tsx
@@ -3,10 +3,13 @@ import {
Button,
Center,
Container,
+ Flex,
Group,
+ Image,
rem,
SimpleGrid,
Stack,
+ Stepper,
Text,
ThemeIcon,
Title,
@@ -17,40 +20,50 @@ import {
IconUser,
IconMessage2,
IconLock,
+ IconCircleCheck,
+ IconNumber1,
+ IconNumber2,
+ IconNumber3,
+ IconEdit,
} from "@tabler/icons-react";
import classes from "./HomePage.module.css";
+import { Link, useNavigate } from "react-router-dom";
+import { AUTH_STATUS, useAuth } from "../../hooks/useAuth";
+import React, { useEffect } from "react";
-export const MOCKDATA = [
+import MatchImage from "../../assets/matchimage.svg";
+
+const MOCKDATA = [
{
icon: IconGauge,
- title: "Extreme performance",
+ title: "Collaboration",
description:
- "This dust is actually a powerful poison that will even make a pro wrestler sick, Regice cloaks itself with frigid air of -328 degrees Fahrenheit",
+ "Our live code editor synchronizes all your changes in real-time, so you can see what your partner is typing as they type it",
},
{
icon: IconUser,
- title: "Privacy focused",
+ title: "Communication",
description:
- "People say it can run at the same speed as lightning striking, Its icy body is so cold, it will not melt even if it is immersed in magma",
+ "We offer various ways to talk to your partner, including video chat, audio chat, and text chat",
},
{
icon: IconCookie,
- title: "No third parties",
+ title: "Choose your own adventure",
description:
- "They’re popular, but they’re rare. Trainers who show them off recklessly may be targeted by thieves",
+ "With a large variety of questions and topics, you're able to work on those topics that have been giving you trouble",
},
{
- icon: IconLock,
- title: "Secure by default",
+ icon: IconEdit,
+ title: "Practice makes perfect",
description:
- "Although it still can’t fly, its jumping power is outstanding, in Alola the mushrooms on Paras don’t grow up quite right",
- },
- {
- icon: IconMessage2,
- title: "24/7 Support",
- description:
- "Rapidash usually can be seen casually cantering in the fields and plains, Skitty is known to chase around after its own tail",
+ "Keep track of the topics that you're struggling in, and maybe even attempt the questions again.",
},
+ // {
+ // icon: IconMessage2,
+ // title: "24/7 Support",
+ // description:
+ // "Rapidash usually can be seen casually cantering in the fields and plains, Skitty is known to chase around after its own tail",
+ // },
];
interface FeatureProps {
@@ -59,44 +72,90 @@ interface FeatureProps {
description: React.ReactNode;
}
+interface StepProps {
+ number: any;
+ children: React.ReactNode | React.ReactNode[];
+ last?: boolean;
+}
+
export default function Home() {
+ // redirect to /dashboard if user is logged in
+
+ const navigate = useNavigate();
+ const { user, authStatus } = useAuth();
+
+ useEffect(() => {
+ if (authStatus === AUTH_STATUS.LOGGED_IN) {
+ navigate("/dashboard");
+ }
+ }, [authStatus]);
+
+ const steps: StepProps[] = [
+ {
+ number: 1,
+ children: "Choose topics and difficulty",
+ },
+ {
+ number: 2,
+ children: "Get matched with a partner",
+ },
+ {
+ number: 3,
+ children: "Begin coding!",
+ },
+ {
+ number: "",
+ children: (
+
+ ),
+ last: true,
+ },
+ ];
+
return (
<>
-
-
-
-
- Welcome to PeerPrep!
-
-
- {" "}
- At PeerPrep, we want you to ace your coding interviews. But,
- doing it alone is boring. Find someone to collaborate with on a
- coding problem, with test cases, timed submission, and more!{" "}
-
+
+
+ {" "}
+ PeerPrep is your best
+ friend when it comes to acing the coding interview.
+
+
+ Doing LeetCode by yourself is boring. Why not do it with someone?{" "}
+
+
+
+
+
+ {steps.map(StepComponent)}
-
-
-
-
-
+
+
- Integrate effortlessly with any technology stack
+ Reduce the boredom and frustration of coding alone
- Every once in a while, you’ll see a Golbat that’s missing some
- fangs. This happens when hunger drives it to try biting a
- Steel-type Pokémon.
+ Two heads are better than one. With our collaborative features,
+ think about and solve problems with a friend. As the saying
+ goes, if you can explain it to someone else, you have mastered
+ the topic.{" "}
@@ -163,3 +222,18 @@ export function FeaturesGrid() {
);
}
+
+function StepComponent({ number, children, last }: StepProps, key: number) {
+ return (
+
+
+
+
+ {number}
+
+ {children}
+
+ {!last && }
+
+ );
+}
diff --git a/peer-prep/src/pages/Login/LoginPage.tsx b/peer-prep/src/pages/Login/LoginPage.tsx
index a8e13b4256..4c23bf629d 100644
--- a/peer-prep/src/pages/Login/LoginPage.tsx
+++ b/peer-prep/src/pages/Login/LoginPage.tsx
@@ -103,7 +103,6 @@ export default function LoginOrRegisterPage() {
// call
setIsLoading(true);
- console.log({ email, password, displayName });
register({
email,
password,
diff --git a/peer-prep/src/pages/ProtectedRouteWrapper.tsx b/peer-prep/src/pages/ProtectedRouteWrapper.tsx
index 01bb77eafa..6f5678291d 100644
--- a/peer-prep/src/pages/ProtectedRouteWrapper.tsx
+++ b/peer-prep/src/pages/ProtectedRouteWrapper.tsx
@@ -1,10 +1,15 @@
import { Navigate, Outlet } from "react-router-dom";
-import { useAuth } from "../hooks/useAuth";
+import { AUTH_STATUS, useAuth } from "../hooks/useAuth";
// https://stackoverflow.com/questions/70833727/using-react-router-v6-i-need-a-navbar-to-permanently-be-there-but-cant-display
export default function ProtectedRouteWrapper() {
- const { user } = useAuth();
- if (!user) {
+ const { user, authStatus } = useAuth();
+
+ if (authStatus === AUTH_STATUS.LOADING) {
+ return <>>;
+ }
+
+ if (authStatus === AUTH_STATUS.LOGGED_OUT) {
// user is not authenticated
return ;
}
diff --git a/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.module.css b/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.module.css
index 4d8520e10d..4a8255bbd4 100644
--- a/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.module.css
+++ b/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.module.css
@@ -1,8 +1,3 @@
-.quillEditor {
- border-radius: var(--mantine-radius-sm);
- border: 1px solid var(--mantine-color-gray-2) !important;
-}
-
.testCaseHeader {
font-size: var(--input-label-size, var(--mantine-font-size-sm));
margin-top: 12px;
diff --git a/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.tsx b/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.tsx
index 7a364b03c6..96463b5669 100644
--- a/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.tsx
+++ b/peer-prep/src/pages/Questions/CreateQuestionPage/CreateQuestionPage.tsx
@@ -7,38 +7,51 @@ import {
Container,
Stack,
Center,
- Input,
- Stepper,
Text,
MultiSelect,
Card,
Switch,
Flex,
Divider,
+ Space,
} from "@mantine/core";
-import ReactQuill from "react-quill";
-import "react-quill/dist/quill.snow.css";
import classes from "./CreateQuestionPage.module.css";
import { QuestionResponseData, TestCase } from "../../../types/question";
import useApi, { ServerResponse, SERVICE } from "../../../hooks/useApi";
import { notifications } from "@mantine/notifications";
import { useNavigate } from "react-router-dom";
+import CodeEditorWithLanguageSelector from "../../../components/Questions/LanguageSelector/LanguageSelector";
+import RichTextEditor from "../../../components/Questions/RichTextEditor/RichTextEditor";
export default function CreateQuestionPage() {
const [name, setName] = useState("");
const [difficulty, setDifficulty] = useState("EASY");
const [categories, setCategories] = useState([]);
- const [description, setDescription] = useState("");
+ const [descriptionText, setDescriptionText] = useState("");
+ const [descriptionHtml, setDescriptionHtml] = useState("");
const [testCases, setTestCases] = useState([]);
- const [solution, setSolution] = useState("");
+ const [solution, setSolution] = useState("# Please provide your solution code here \n");
+ const [templateCode, setTemplateCode] = useState("# Please provide your template code here \n");
const [link, setLink] = useState("");
const navigate = useNavigate();
+ // For now, to be changed once backend sends over fixed categories
+ const dummyCategories = [
+ { value: "ARRAYS", label: "Arrays" },
+ { value: "ALGORITHMS", label: "Algorithms" },
+ { value: "DATABASES", label: "Databases" },
+ { value: "DATA STRUCTURES", label: "Data Structures" },
+ { value: "BRAINTEASER", label: "Brainteaser" },
+ { value: "STRINGS", label: "Strings" },
+ { value: "BIT MANIPULATION", label: "Bit Manipulation" },
+ { value: "RECURSION", label: "Recursion" },
+ ];
+
const [fetchedCategories, setFetchedCategories] = useState<
{ value: string; label: string }[]
- >([]);
+ >(dummyCategories);
// Mapping for difficulty display
const difficultyOptions = [
@@ -48,7 +61,9 @@ export default function CreateQuestionPage() {
];
useEffect(() => {
- fetchCategories();
+ // to be uncommented once backend sends over fixed categories
+ // fetchCategories();
+ addTestCase();
}, []);
const { fetchData, isLoading, error } = useApi();
@@ -95,11 +110,12 @@ export default function CreateQuestionPage() {
},
body: JSON.stringify({
title: name,
- description: { testDescription: description },
+ description: { descriptionText, descriptionHtml },
categories,
difficulty,
testCases: updatedTestCases,
solutionCode: solution,
+ templateCode,
link,
}),
}
@@ -149,7 +165,6 @@ export default function CreateQuestionPage() {