Skip to content

Commit 9c720be

Browse files
committed
Add rudimentary page for adding questions
1 parent af43945 commit 9c720be

File tree

6 files changed

+173
-25
lines changed

6 files changed

+173
-25
lines changed

peerprep/api/gateway.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Question, ErrorBody } from "./structs";
1+
import { Question, StatusBody, QuestionFullBody } from "./structs";
22

33
const questions: { [key: string]: Question } = {
44
"0" : {
@@ -25,15 +25,15 @@ const questions: { [key: string]: Question } = {
2525
}
2626
};
2727

28-
export async function fetchQuestion(questionId: string): Promise<Question|ErrorBody> {
28+
export async function fetchQuestion(questionId: string): Promise<Question|StatusBody> {
2929
// remove this when services are up
3030
if (process.env.DEV_ENV === "dev") {
3131
return questions[questionId] === undefined
3232
? {error: "Question not found", status: 404}
3333
: questions[questionId];
3434
}
3535
try {
36-
const response = await fetch(`${process.env.QUESTION_SERVICE}/questions/solve/${questionId}`);
36+
const response = await fetch(`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions/solve/${questionId}`);
3737
if (!response.ok) {
3838
return {
3939
...(await response.json()),
@@ -44,4 +44,30 @@ export async function fetchQuestion(questionId: string): Promise<Question|ErrorB
4444
} catch (err: any) {
4545
return { error: err.message, status: 0};
4646
}
47-
}
47+
}
48+
49+
export async function addQuestion(body: QuestionFullBody): Promise<StatusBody> {
50+
try {
51+
const response = await fetch(
52+
`${process.env.NEXT_PUBLIC_QUESTION_SERVICE}/questions`,
53+
{
54+
method: "POST",
55+
body: JSON.stringify(body),
56+
headers: {
57+
"Content-type": "application/json; charset=UTF-8"
58+
}
59+
}
60+
);
61+
if (response.ok) {
62+
return {
63+
status: response.status
64+
};
65+
}
66+
return {
67+
error: (await response.json())["Error adding question: "],
68+
status: response.status
69+
};
70+
} catch (err: any) {
71+
return { error: err.message, status: 0};
72+
}
73+
}

peerprep/api/structs.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,29 @@ export enum Difficulty {
44
Hard
55
}
66

7-
export interface Question {
8-
id: number;
7+
export interface QuestionBody {
98
difficulty: Difficulty;
109
title: string;
1110
description: string;
11+
}
12+
13+
export interface TestCase {
1214
test_cases: {
1315
[key: string]: string;
1416
};
1517
}
1618

17-
export interface ErrorBody {
18-
error: string;
19+
export interface QuestionFullBody extends QuestionBody, TestCase {}
20+
21+
export interface Question extends QuestionFullBody {
22+
id: number;
23+
}
24+
25+
export interface StatusBody {
1926
status: number;
27+
error?: string;
2028
}
2129

22-
export function isError(obj: Question | ErrorBody): obj is ErrorBody {
23-
return (obj as ErrorBody).error !== undefined;
30+
export function isError(obj: Question | StatusBody): obj is StatusBody {
31+
return (obj as StatusBody).error !== undefined;
2432
}

peerprep/app/q/[question]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { fetchQuestion } from '@/api/gateway';
2-
import { Question as QnType, ErrorBody, isError } from "@/api/structs";
2+
import { Question as QnType, StatusBody, isError } from "@/api/structs";
33
import styles from '@/style/question.module.css';
44
import ErrorBlock from '@/components/shared/ErrorBlock';
55
import React from 'react'
@@ -18,7 +18,7 @@ async function Question({ params }: Props) {
1818
<div className={styles.wrapper}>
1919
{
2020
isError(question)
21-
? <ErrorBlock err={question as ErrorBody}/>
21+
? <ErrorBlock err={question as StatusBody}/>
2222
: <QuestionBlock question={question as QnType}/>
2323
}
2424
</div>

peerprep/app/q/[question]/question.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Question, ErrorBody, Difficulty } from "@/api/structs";
1+
import { Question, StatusBody, Difficulty } from "@/api/structs";
22
import styles from '@/style/question.module.css';
33

44
interface Props {
@@ -14,7 +14,7 @@ const difficultyColor = (diff: Difficulty) => {
1414
}
1515

1616
function QuestionBlock({ question }: Props) {
17-
const keys = Object.keys(question.test_cases);
17+
const keys = question.test_cases ? Object.keys(question.test_cases) : [];
1818

1919
const createRow = (key: string) => (
2020
<tr key={key}>
@@ -33,15 +33,17 @@ function QuestionBlock({ question }: Props) {
3333
<br/>
3434
<p>{question.description}</p>
3535
<br/>
36-
<table className={styles.table}>
37-
<tbody>
38-
<tr>
39-
<th className={`${styles.table} ${styles.header} ${styles.input}`}>Input</th>
40-
<th className={`${styles.table} ${styles.header} ${styles.output}`}>Expected Output</th>
41-
</tr>
42-
{keys.map(createRow)}
43-
</tbody>
44-
</table>
36+
{question.test_cases && (
37+
<table className={styles.table}>
38+
<tbody>
39+
<tr>
40+
<th className={`${styles.table} ${styles.header} ${styles.input}`}>Input</th>
41+
<th className={`${styles.table} ${styles.header} ${styles.output}`}>Expected Output</th>
42+
</tr>
43+
{keys.map(createRow)}
44+
</tbody>
45+
</table>
46+
)}
4547
</div>
4648
<form className={styles.editor_container}>
4749
<textarea className={styles.code_editor}/>

peerprep/app/q/new/page.tsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use client';
2+
import { useState, ChangeEvent, MouseEvent, FormEvent } from 'react';
3+
import { QuestionBody, Difficulty, QuestionFullBody } from '@/api/structs';
4+
import { addQuestion } from '@/api/gateway';
5+
6+
type Props = {}
7+
8+
interface Mapping {
9+
key: string,
10+
value: string
11+
}
12+
13+
function NewQuestion({}: Props) {
14+
const [testCases, setTestCases] = useState<Mapping[]>([{
15+
key: "", value: ""
16+
}]);
17+
const [formData, setFormData] = useState<QuestionBody>({
18+
title: "",
19+
difficulty: Difficulty.Easy,
20+
description: "",
21+
});
22+
23+
const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => setFormData({
24+
...formData,
25+
[e.target.name]: e.target.value
26+
});
27+
28+
const handleTestCaseInput = (e: ChangeEvent<HTMLInputElement>, idx: number) => {
29+
const values = [...testCases];
30+
values[idx] = {
31+
...values[idx],
32+
[e.target.name]: e.target.value
33+
};
34+
setTestCases(values);
35+
}
36+
37+
const handleAddField = (e: MouseEvent<HTMLElement>) =>
38+
setTestCases([...testCases, { key: "", value: ""}]);
39+
40+
const handleDeleteField = (e: MouseEvent<HTMLElement>, idx: number) => {
41+
const values = [...testCases];
42+
values.splice(idx, 1);
43+
setTestCases(values);
44+
}
45+
46+
const handleSubmission = async (e: FormEvent<HTMLFormElement>) => {
47+
e.preventDefault();
48+
const question: QuestionFullBody = {
49+
...formData,
50+
test_cases: testCases.map((elem: Mapping) => ({
51+
[elem.key]: elem.value
52+
})).reduce((res, item) => ({...res, ...item}), {})
53+
}
54+
const status = await addQuestion(question);
55+
if (status.error) {
56+
console.log("Failed to add question.");
57+
console.log(`Code ${status.status}: ${status.error}`);
58+
return;
59+
}
60+
console.log(`Successfully added the question.`);
61+
}
62+
63+
return (
64+
<div>
65+
<form style={{color: "black", padding: "5px"}} onSubmit={handleSubmission}>
66+
<input type="text" name="title" /><br/>
67+
<input type="radio" id="easy" name="difficulty" value={1} onChange={handleTextInput} />
68+
<label htmlFor="easy">Easy</label><br/>
69+
<input type="radio" id="med" name="difficulty" value={2} onChange={handleTextInput} />
70+
<label htmlFor="med">Medium</label><br/>
71+
<input type="radio" id="hard" name="difficulty" value={3} onChange={handleTextInput} />
72+
<label htmlFor="hard">Hard</label><br/>
73+
<textarea name="description" /><br/>
74+
{testCases.map((elem, idx) => (
75+
<>
76+
<input
77+
name="key"
78+
type="text"
79+
id={`key_${idx.toLocaleString()}`}
80+
value={elem.key}
81+
onChange={e => handleTestCaseInput(e, idx)} />
82+
<input
83+
name="value"
84+
type="text"
85+
id={`val_${idx.toLocaleString()}`}
86+
value={elem.value}
87+
onChange={e => handleTestCaseInput(e, idx)} />
88+
<input
89+
type="button"
90+
name="del_entry"
91+
value="Delete..."
92+
onClick={e => handleDeleteField(e, idx)}
93+
style={{ backgroundColor: "white"}} />
94+
<br/>
95+
</>
96+
))}
97+
<input
98+
type="button"
99+
name="add_entry"
100+
value="Add..."
101+
onClick={handleAddField}
102+
style={{ backgroundColor: "white"}} />
103+
<button
104+
type="submit"
105+
name="submit"
106+
style={{ backgroundColor: "white"}}>Submit</button>
107+
</form>
108+
</div>
109+
)
110+
}
111+
112+
export default NewQuestion;

peerprep/components/shared/ErrorBlock.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React from 'react'
22
import styles from '@/style/error.module.css';
3-
import { ErrorBody } from '@/api/structs';
3+
import { StatusBody } from '@/api/structs';
44

55
interface Props {
6-
err : ErrorBody
6+
err : StatusBody
77
}
88

99
function ErrorBlock({ err }: Props) {

0 commit comments

Comments
 (0)