Skip to content

Commit 4596f44

Browse files
authored
Merge pull request #15 from CS3219-AY2425S1/frontend
Merge frontend into main
2 parents edb1a30 + 9a0a23f commit 4596f44

27 files changed

+789
-272
lines changed

peerprep/api/gateway.ts

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,125 @@
11
import { Question, StatusBody, QuestionFullBody } from "./structs";
22

33
const questions: { [key: string]: Question } = {
4-
"0" : {
5-
"id": 0,
6-
"difficulty": 2,
7-
"title": "Two Sum",
8-
"description": "Given an array of integers, return indices of the two numbers such that they add up to a specific target.",
9-
"test_cases": {
10-
"[2, 7, 11, 15], 9" : "[0, 1]",
11-
"[3, 2, 4], 6" : "[1, 2]",
12-
"[3, 3], 6" : "[0, 1]"
13-
}
4+
"0": {
5+
id: 0,
6+
difficulty: 2,
7+
title: "Two Sum",
8+
description:
9+
"Given an array of integers, return indices of the two numbers such that they add up to a specific target.",
10+
categories: ["Hash Table", "Array"],
11+
test_cases: {
12+
"[2, 7, 11, 15], 9": "[0, 1]",
13+
"[3, 2, 4], 6": "[1, 2]",
14+
"[3, 3], 6": "[0, 1]",
15+
},
16+
},
17+
"1": {
18+
id: 1,
19+
difficulty: 1,
20+
title: "Reverse Integer",
21+
description: "Given a 32-bit signed integer, reverse digits of an integer.",
22+
categories: ["Math"],
23+
test_cases: {
24+
"123": "321",
25+
"1": "1",
26+
"22": "22",
27+
},
1428
},
15-
"1" : {
16-
"id": 1,
17-
"difficulty": 1,
18-
"title": "Reverse Integer",
19-
"description": "Given a 32-bit signed integer, reverse digits of an integer.",
20-
"test_cases": {
21-
"123" : "321",
22-
"1" : "1",
23-
"22" : "22"
24-
}
25-
}
2629
};
2730

28-
export async function fetchQuestion(questionId: string): Promise<Question|StatusBody> {
31+
export async function fetchQuestion(
32+
questionId: string
33+
): Promise<Question | StatusBody> {
2934
// remove this when services are up
3035
if (process.env.DEV_ENV === "dev") {
3136
return questions[questionId] === undefined
32-
? {error: "Question not found", status: 404}
33-
: questions[questionId];
37+
? { error: "Question not found", status: 404 }
38+
: questions[questionId];
3439
}
3540
try {
36-
const response = await fetch(`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/solve/${questionId}`);
41+
const response = await fetch(
42+
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/solve/${questionId}`
43+
);
3744
if (!response.ok) {
3845
return {
39-
...(await response.json()),
40-
status: response.status
46+
error: await response.text(),
47+
status: response.status,
4148
};
4249
}
43-
return await response.json() as Question;
50+
return (await response.json()) as Question;
4451
} catch (err: any) {
45-
return { error: err.message, status: 0};
52+
return { error: err.message, status: 400 };
4653
}
4754
}
4855

4956
export async function addQuestion(body: QuestionFullBody): Promise<StatusBody> {
5057
try {
5158
const response = await fetch(
52-
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions`,
53-
{
54-
method: "POST",
55-
body: JSON.stringify(body).replace(/(\"difficulty\":)\"([1-3])\"/, `$1$2`),
56-
headers: {
57-
"Content-type": "application/json; charset=UTF-8"
58-
}
59-
}
59+
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions`,
60+
{
61+
method: "POST",
62+
body: JSON.stringify(body).replace(
63+
/(\"difficulty\":)\"([1-3])\"/,
64+
`$1$2`
65+
),
66+
headers: {
67+
"Content-type": "application/json; charset=UTF-8",
68+
},
69+
}
6070
);
6171
if (response.ok) {
6272
return {
63-
status: response.status
73+
status: response.status,
6474
};
6575
}
6676
return {
6777
error: (await response.json())["Error adding question: "],
68-
status: response.status
78+
status: response.status,
79+
};
80+
} catch (err: any) {
81+
return { error: err.message, status: 0 };
82+
}
83+
}
84+
85+
export async function deleteQuestion(question: Question): Promise<StatusBody> {
86+
try {
87+
const response = await fetch(
88+
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/delete/${question.id}`,
89+
{
90+
method: "DELETE",
91+
headers: {
92+
"Content-type": "application/json; charset=UTF-8",
93+
},
94+
}
95+
);
96+
if (response.ok) {
97+
return {
98+
status: response.status,
99+
};
100+
}
101+
return {
102+
error: (await response.json())["Error deleting question: "],
103+
status: response.status,
69104
};
70105
} catch (err: any) {
71-
return { error: err.message, status: 0};
106+
return { error: err.message, status: 0 };
107+
}
108+
}
109+
110+
export async function getAllQuestions(): Promise<Question[] | StatusBody> {
111+
try {
112+
const response = await fetch(
113+
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions`
114+
);
115+
if (!response.ok) {
116+
return {
117+
error: await response.text(),
118+
status: response.status,
119+
};
120+
}
121+
return (await response.json()) as Question[];
122+
} catch (err: any) {
123+
return { error: err.message, status: 400 };
72124
}
73125
}

peerprep/api/structs.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
export enum Difficulty {
2+
All = 0,
23
Easy = 1,
34
Medium,
4-
Hard
5+
Hard,
56
}
67

78
export interface QuestionBody {
89
difficulty: Difficulty;
910
title: string;
1011
description: string;
12+
categories: string[];
1113
}
1214

1315
export interface TestCase {
@@ -27,6 +29,8 @@ export interface StatusBody {
2729
error?: string;
2830
}
2931

30-
export function isError(obj: Question | StatusBody): obj is StatusBody {
31-
return (obj as StatusBody).error !== undefined;
32+
export function isError(
33+
obj: Question[] | Question | StatusBody
34+
): obj is StatusBody {
35+
return (obj as StatusBody).status !== undefined;
3236
}

peerprep/app/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ body,
1313
p,
1414
h1,
1515
h2 {
16-
@apply text-gray-100;
16+
@apply text-text-2;
1717
}
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
import { fetchQuestion } from '@/api/gateway';
1+
import { fetchQuestion } from "@/api/gateway";
22
import { Question as QnType, StatusBody, isError } from "@/api/structs";
3-
import styles from '@/style/question.module.css';
4-
import ErrorBlock from '@/components/shared/ErrorBlock';
5-
import React from 'react'
6-
import QuestionBlock from './question';
3+
import styles from "@/style/question.module.css";
4+
import ErrorBlock from "@/components/shared/ErrorBlock";
5+
import React from "react";
6+
import QuestionBlock from "./question";
77

88
type Props = {
99
params: {
10-
question: string
11-
}
12-
}
10+
question: string;
11+
};
12+
};
1313

1414
async function Question({ params }: Props) {
1515
const question = await fetchQuestion(params.question);
1616

1717
return (
1818
<div className={styles.wrapper}>
19-
{
20-
isError(question)
21-
? <ErrorBlock err={question as StatusBody}/>
22-
: <QuestionBlock question={question as QnType}/>
23-
}
19+
{isError(question) ? (
20+
<ErrorBlock err={question as StatusBody} />
21+
) : (
22+
<QuestionBlock question={question as QnType} />
23+
)}
2424
</div>
25-
)
25+
);
2626
}
2727

28-
export default Question;
28+
export default Question;
Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,114 @@
1-
import { Question, StatusBody, Difficulty } from "@/api/structs";
2-
import styles from '@/style/question.module.css';
1+
"use client";
2+
import { deleteQuestion } from "@/api/gateway";
3+
import { Question, Difficulty } from "@/api/structs";
4+
import Chip from "@/components/shared/Chip";
5+
import PeerprepButton from "@/components/shared/PeerprepButton";
6+
import styles from "@/style/question.module.css";
7+
import { useRouter } from "next/navigation";
38

49
interface Props {
5-
question : Question;
10+
question: Question;
611
}
712

8-
const difficultyColor = (diff: Difficulty) => {
9-
return (
10-
diff === Difficulty.Easy ? <p className={`${styles.title} ${styles.easy}`}>Easy</p>
11-
: diff === Difficulty.Medium ? <p className={`${styles.title} ${styles.med}`}>Med</p>
12-
: <p className={`${styles.title} ${styles.hard}`}>Hard</p>
13+
interface DifficultyChipProps {
14+
diff: Difficulty;
15+
}
16+
17+
function DifficultyChip({ diff }: DifficultyChipProps) {
18+
return diff === Difficulty.Easy ? (
19+
<Chip className={styles.easy}>Easy</Chip>
20+
) : diff === Difficulty.Medium ? (
21+
<Chip className={styles.med}>Med</Chip>
22+
) : (
23+
<Chip className={styles.hard}>Hard</Chip>
1324
);
1425
}
1526

1627
function QuestionBlock({ question }: Props) {
28+
const router = useRouter();
1729
const keys = question.test_cases ? Object.keys(question.test_cases) : [];
1830

1931
const createRow = (key: string) => (
2032
<tr key={key}>
2133
<td className={`${styles.table} ${styles.cell}`}>{key}</td>
22-
<td className={`${styles.table} ${styles.cell}`}>{question.test_cases[key]}</td>
34+
<td className={`${styles.table} ${styles.cell}`}>
35+
{question.test_cases[key]}
36+
</td>
2337
</tr>
2438
);
2539

40+
const handleDelete = async () => {
41+
if (
42+
confirm(
43+
`Are you sure you want to delete ${question.title}? (ID: ${question.id}) `
44+
)
45+
) {
46+
const status = await deleteQuestion(question);
47+
if (status.error) {
48+
alert(
49+
`Failed to delete question. Code ${status.status}: ${status.error}`
50+
);
51+
return;
52+
}
53+
console.log(`Successfully deleted the question.`);
54+
router.push("/questions");
55+
} else {
56+
console.log("Deletion cancelled.");
57+
}
58+
};
59+
2660
return (
2761
<>
2862
<div className={styles.qn_container}>
2963
<div className={styles.title_wrapper}>
30-
<h1 className={styles.title}>Q{question.id}: {question.title}</h1>
31-
{difficultyColor(question.difficulty)}
64+
<div className={styles.label_wrapper}>
65+
<h1 className={styles.title}>
66+
Q{question.id}: {question.title}
67+
</h1>
68+
<DifficultyChip diff={question.difficulty} />
69+
</div>
70+
<PeerprepButton
71+
className={` ${styles.button}`}
72+
onClick={handleDelete}
73+
>
74+
Delete
75+
</PeerprepButton>
76+
</div>
77+
<div className={styles.label_wrapper}>
78+
<p>Categories: </p>
79+
{question.categories.length == 0 ? (
80+
<p>No categories listed.</p>
81+
) : (
82+
question.categories.map((elem, idx) => <p key={idx}>{elem}</p>)
83+
)}
3284
</div>
33-
<br/>
3485
<p>{question.description}</p>
35-
<br/>
86+
<br />
3687
{question.test_cases && (
3788
<table className={styles.table}>
3889
<tbody>
3990
<tr>
40-
<th className={`${styles.table} ${styles.header} ${styles.input}`}>Input</th>
41-
<th className={`${styles.table} ${styles.header} ${styles.output}`}>Expected Output</th>
91+
<th
92+
className={`${styles.table} ${styles.header} ${styles.input}`}
93+
>
94+
Input
95+
</th>
96+
<th
97+
className={`${styles.table} ${styles.header} ${styles.output}`}
98+
>
99+
Expected Output
100+
</th>
42101
</tr>
43102
{keys.map(createRow)}
44103
</tbody>
45104
</table>
46105
)}
47106
</div>
48107
<form className={styles.editor_container}>
49-
<textarea className={styles.code_editor}/>
108+
<textarea className={styles.code_editor} />
50109
</form>
51110
</>
52111
);
53112
}
54113

55-
export default QuestionBlock;
114+
export default QuestionBlock;

0 commit comments

Comments
 (0)