Skip to content

Commit 7ce889b

Browse files
committed
Formatted AddQuestion page
1 parent fe23d88 commit 7ce889b

File tree

10 files changed

+290
-63
lines changed

10 files changed

+290
-63
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Question, StatusBody, Difficulty } from "@/api/structs";
2+
import Chip from "@/components/shared/Chip";
23
import styles from '@/style/question.module.css';
34

45
interface Props {
@@ -7,9 +8,11 @@ interface Props {
78

89
const difficultyColor = (diff: Difficulty) => {
910
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>
11+
diff === Difficulty.Easy
12+
? <Chip className={styles.easy}>Easy</Chip>
13+
: diff === Difficulty.Medium
14+
? <Chip className={styles.med}>Med</Chip>
15+
: <Chip className={styles.hard}>Hard</Chip>
1316
);
1417
}
1518

peerprep/app/questions/new/page.tsx

Lines changed: 97 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
import { useState, ChangeEvent, MouseEvent, FormEvent } from 'react';
33
import { QuestionBody, Difficulty, QuestionFullBody } from '@/api/structs';
44
import { addQuestion } from '@/api/gateway';
5+
import style from "@/style/form.module.css";
6+
import FormTextInput from '@/components/shared/form/FormTextInput';
7+
import RadioButtonGroup from '@/components/shared/form/RadioButtonGroup';
8+
import FormTextAreaInput from '@/components/shared/form/FormTextAreaInput';
9+
import { useRouter } from 'next/navigation';
510

611
type Props = {}
712

@@ -11,40 +16,73 @@ interface Mapping {
1116
}
1217

1318
function NewQuestion({}: Props) {
14-
const [testCases, setTestCases] = useState<Mapping[]>([{
15-
key: "", value: ""
16-
}]);
19+
const router = useRouter();
20+
// Form Data is handled as a single submission
1721
const [formData, setFormData] = useState<QuestionBody>({
1822
title: "",
1923
difficulty: Difficulty.Easy,
2024
description: "",
25+
categories: []
26+
});
27+
// Choice 1: Test cases handled separately to allow modification of multiple fields
28+
const [testCases, setTestCases] = useState<Mapping[]>([]);
29+
// TODO: Resolve this mess of hooks to combine the form data
30+
const [mapping, setMapping] = useState<Mapping>({
31+
key: "", value: ""
2132
});
33+
// Choice 2: Categories handled in a separate state, inject into formData on confirm
34+
const [category, setCategory] = useState<string>("");
35+
const [loading, setLoading] = useState<boolean>(false);
36+
37+
const handleCategoriesInput = (e: ChangeEvent<HTMLInputElement>) => setCategory(e.target.value);
38+
const handleCategoryAdd = (e: MouseEvent<HTMLElement>) => {
39+
if (category.length == 0)
40+
return;
41+
setFormData({
42+
...formData,
43+
categories: [...formData.categories, category]
44+
})
45+
setCategory("");
46+
}
47+
const handleCategoryDel = (e: MouseEvent<HTMLParagraphElement>, idx: number) => {
48+
if (loading)
49+
return;
50+
const values = [...formData.categories];
51+
values.splice(idx, 1);
52+
setFormData({
53+
...formData,
54+
categories: values
55+
})
56+
}
2257

23-
const handleTextInput = (e: ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) => setFormData({
58+
const handleFormTextInput = (e: ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) => setFormData({
2459
...formData,
2560
[e.target.name]: e.target.value
2661
});
2762

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-
}
63+
const handleMappingInput = (e: ChangeEvent<HTMLInputElement>) => setMapping({
64+
...mapping,
65+
[e.target.name]: e.target.value
66+
});
3667

37-
const handleAddField = (e: MouseEvent<HTMLElement>) =>
38-
setTestCases([...testCases, { key: "", value: ""}]);
68+
const handleMappingAdd = (e: MouseEvent<HTMLElement>) => {
69+
if (mapping.key.length == 0 || mapping.value.length == 0)
70+
return;
71+
setTestCases([...testCases, mapping]);
72+
setMapping({key: "", value: ""})
73+
}
3974

40-
const handleDeleteField = (e: MouseEvent<HTMLElement>, idx: number) => {
75+
const handleMappingDel = (e: MouseEvent<HTMLElement>, idx: number) => {
76+
if (loading)
77+
return;
4178
const values = [...testCases];
4279
values.splice(idx, 1);
4380
setTestCases(values);
4481
}
4582

4683
const handleSubmission = async (e: FormEvent<HTMLFormElement>) => {
4784
e.preventDefault();
85+
setLoading(true);
4886
const question: QuestionFullBody = {
4987
...formData,
5088
test_cases: testCases.map((elem: Mapping) => ({
@@ -55,55 +93,60 @@ function NewQuestion({}: Props) {
5593
if (status.error) {
5694
console.log("Failed to add question.");
5795
console.log(`Code ${status.status}: ${status.error}`);
96+
setLoading(false);
5897
return;
5998
}
6099
console.log(`Successfully added the question.`);
100+
router.push('/questions');
61101
}
62102

63103
return (
64-
<div>
65-
<form style={{color: "black", padding: "5px"}} onSubmit={handleSubmission}>
66-
<input type="text" name="title" value={formData.title} onChange={handleTextInput}/><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" value={formData.description} onChange={handleTextInput}/><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-
</>
104+
<div className={style.wrapper}>
105+
<form className={style.form_container} onSubmit={handleSubmission}>
106+
<h1 className={style.title}>Create a new Question</h1>
107+
<FormTextInput required disabled={loading} label="Question Title: " name="title"
108+
value={formData.title} onChange={handleFormTextInput} />
109+
<RadioButtonGroup required disabled={loading} label="Difficulty: " group="difficulty"
110+
options={{ "Easy": 1, "Medium": 2, "Hard": 3 }} onChange={handleFormTextInput} />
111+
<FormTextAreaInput required disabled={loading} label="Description: " name="description"
112+
value={formData.description} onChange={handleFormTextInput}/>
113+
<FormTextInput disabled={loading} label="Categories: " name="categories"
114+
value={category} onChange={handleCategoriesInput}>
115+
<input type="button" onClick={handleCategoryAdd} value="Add" disabled={loading}/>
116+
</FormTextInput>
117+
<div className={style.radio_container}>
118+
{formData.categories.length == 0
119+
? (<p className={style.disabledText}>No Categories added.</p>)
120+
: formData.categories.map((elem, idx) => (
121+
<p key={idx} className={style.deletableText}
122+
onClick={e => handleCategoryDel(e, idx)}>
123+
{elem}
124+
</p>
125+
))}
126+
</div>
127+
<div className={style.input_container}>
128+
<div>
129+
<FormTextInput disabled={loading} label="Test Case: " name="key"
130+
value={mapping.key} onChange={handleMappingInput} />
131+
<FormTextInput disabled={loading} label="Expected: " name="value"
132+
value={mapping.value} onChange={handleMappingInput} />
133+
</div>
134+
<input type="button" onClick={handleMappingAdd} value="Add" disabled={loading}/>
135+
</div>
136+
{testCases.length == 0
137+
? (<p className={style.disabledText}>No Test Cases added.</p>)
138+
: testCases.map((elem, idx) => (
139+
<p key={idx} className={style.deletableText}
140+
onClick={e => handleMappingDel(e, idx)}>
141+
{elem.key}/{elem.value}
142+
</p>
96143
))}
97-
<input
98-
type="button"
99-
name="add_entry"
100-
value="Add..."
101-
onClick={handleAddField}
102-
style={{ backgroundColor: "white"}} />
103144
<button
145+
disabled={loading}
104146
type="submit"
105147
name="submit"
106-
style={{ backgroundColor: "white"}}>Submit</button>
148+
className={`${style.title}`
149+
}>Submit</button>
107150
</form>
108151
</div>
109152
)

peerprep/components/homepage/PLACEHOLDER

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react'
2+
import styles from '@/style/layout.module.css';
3+
4+
interface Props {
5+
className: string;
6+
children: React.ReactNode;
7+
}
8+
9+
function Chip({ className, children }: Props) {
10+
return (
11+
<p className={`${styles.chip} ${className}`}>
12+
{children}
13+
</p>
14+
)
15+
}
16+
17+
export default Chip;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ChangeEvent } from 'react';
2+
import style from '@/style/form.module.css';
3+
4+
type Props = {
5+
name: string;
6+
label: string;
7+
value: string;
8+
className?: string;
9+
required?: boolean;
10+
disabled?: boolean
11+
id?: string;
12+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
13+
}
14+
15+
function TextInput({ name, label, value, className, required, id, disabled, onChange }: Props) {
16+
return (
17+
<div className={style.input_container}>
18+
<p className={style.label}>{label}</p>
19+
<textarea required={required} name={name} id={id} value={value}
20+
className={`${style.text_input} ${className ? className : ""}`}
21+
onChange={onChange} disabled={disabled}/>
22+
</div>
23+
)
24+
}
25+
26+
export default TextInput;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ChangeEvent, ReactNode } from 'react';
2+
import style from '@/style/form.module.css';
3+
4+
type Props = {
5+
name: string;
6+
label: string;
7+
value: string;
8+
children?: ReactNode;
9+
className?: string;
10+
required?: boolean;
11+
disabled?: boolean;
12+
id?: string;
13+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
14+
}
15+
16+
function TextInput({ name, label, value, className, required, id, children, disabled, onChange }: Props) {
17+
return (
18+
<div className={style.input_container}>
19+
<p className={style.label}>{label}</p>
20+
<input required={required} type="text" name={name} id={id} value={value}
21+
className={`${style.text_input} ${className ? className : ""}`}
22+
onChange={onChange} disabled={disabled}/>
23+
{children}
24+
</div>
25+
)
26+
}
27+
28+
export default TextInput;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ChangeEvent } from 'react';
2+
import style from '@/style/form.module.css';
3+
4+
interface Props {
5+
label: string;
6+
group: string;
7+
options: {
8+
[label: string]: number;
9+
};
10+
required?: boolean;
11+
disabled?: boolean;
12+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
13+
}
14+
15+
function RadioButtonGroup({ label, group, options, required, disabled, onChange }: Props) {
16+
const options_label = Object.keys(options);
17+
return (
18+
<div className={style.radio_container}>
19+
<p className={style.label}>{label}</p>
20+
{options_label.map((lbl, idx) => (
21+
<div key={idx}>
22+
<input type="radio" id={lbl} name={group} required={required} disabled={disabled}
23+
value={options[lbl]} onChange={onChange}/>
24+
<label htmlFor={lbl}>{lbl}</label>
25+
</div>
26+
))}
27+
</div>
28+
)
29+
}
30+
31+
export default RadioButtonGroup

0 commit comments

Comments
 (0)