Skip to content

Commit 6191e71

Browse files
committed
add create question operation
1 parent 0b29aff commit 6191e71

File tree

11 files changed

+132
-41
lines changed

11 files changed

+132
-41
lines changed

peerprep-fe/src/app/(main)/components/filter/FilterBar.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,7 @@ import { TopicsPopover } from './TopicsPopover';
88
import { FilterState } from '@/hooks/useFilteredProblems';
99
import { useState, useEffect } from 'react';
1010
import { useDebounce } from '@/hooks/useDebounce';
11-
12-
const DIFFICULTY_OPTIONS = [
13-
{ value: '1', label: 'Easy' },
14-
{ value: '2', label: 'Medium' },
15-
{ value: '3', label: 'Hard' },
16-
];
17-
18-
const STATUS_OPTIONS = [
19-
{ value: 'todo', label: 'Todo' },
20-
{ value: 'solved', label: 'Solved' },
21-
];
11+
import { DIFFICULTY_OPTIONS, STATUS_OPTIONS } from '@/lib/constants';
2212

2313
interface FilterBarProps {
2414
filters: FilterState;
@@ -27,12 +17,16 @@ interface FilterBarProps {
2717
value: string | string[] | null,
2818
) => void;
2919
removeFilter: (key: keyof FilterState, value?: string) => void;
20+
isAdmin?: boolean;
21+
buttonCallback?: () => void;
3022
}
3123

3224
export default function FilterBar({
3325
filters,
3426
updateFilter,
3527
removeFilter,
28+
isAdmin = false,
29+
buttonCallback,
3630
}: FilterBarProps) {
3731
const [searchTerm, setSearchTerm] = useState('');
3832
const debouncedSearchTerm = useDebounce(searchTerm, 300); // 300ms delay
@@ -83,9 +77,18 @@ export default function FilterBar({
8377
>
8478
<Settings className="h-4 w-4" />
8579
</Button>
86-
<Button className="bg-green-600 text-white hover:bg-green-700">
87-
Match
88-
</Button>
80+
{!isAdmin ? (
81+
<Button className="bg-green-600 text-white hover:bg-green-700">
82+
Match
83+
</Button>
84+
) : (
85+
<Button
86+
className="bg-blue-600 text-white hover:bg-blue-700"
87+
onClick={buttonCallback}
88+
>
89+
Add
90+
</Button>
91+
)}
8992
</div>
9093
<div className="flex flex-wrap gap-2">
9194
{filters.difficulty && (

peerprep-fe/src/app/admin/page.tsx

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
'use client';
2-
import React from 'react';
2+
import React, { useState } from 'react';
33
import { useFilteredProblems } from '@/hooks/useFilteredProblems';
44
import FilterBar from '../(main)/components/filter/FilterBar';
55
import ProblemTable from '../../components/problems/ProblemTable';
66
import { axiosQuestionClient } from '@/network/axiosClient';
77
import { Problem } from '@/types/types';
88
import { isAxiosError } from 'axios';
9+
import ProblemInputDialog from '@/components/problems/ProblemInputDialog';
10+
import InformationDialog from '@/components/dialogs/InformationDialog';
911

1012
function AdminPage() {
13+
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
14+
const [informationDialog, setInformationDialog] = useState('');
1115
const {
1216
problems,
1317
filters,
@@ -61,13 +65,60 @@ function AdminPage() {
6165
}
6266
};
6367

68+
const handleAdd = async (problem: Problem) => {
69+
// TODO: Add proper validation of fields
70+
if (
71+
problem.description === '' ||
72+
problem.title === '' ||
73+
problem.tags.length === 0
74+
) {
75+
setInformationDialog('Please fill in all required fields');
76+
return;
77+
}
78+
try {
79+
const res = await axiosQuestionClient.post(`/questions`, {
80+
difficulty: problem.difficulty,
81+
description: problem.description,
82+
examples: problem.examples,
83+
constraints: problem.constraints,
84+
tags: problem.tags,
85+
title_slug: problem.title_slug,
86+
title: problem.title,
87+
});
88+
89+
refetchFilter();
90+
setIsAddDialogOpen(false);
91+
return res;
92+
} catch (e: unknown) {
93+
if (isAxiosError(e)) {
94+
switch (e.status) {
95+
case 400:
96+
throw new Error('Invalid question data. Please check your input.');
97+
case 409:
98+
throw new Error('Question already exists');
99+
case 404:
100+
throw new Error('Question not found');
101+
default:
102+
throw new Error('Failed to update question');
103+
}
104+
}
105+
if (e instanceof Error) {
106+
throw new Error(e.message);
107+
} else {
108+
throw new Error('An unknown error occurred');
109+
}
110+
}
111+
};
112+
64113
return (
65114
<div className="min-h-screen bg-gray-900 p-6 pt-24 text-gray-100">
66115
<div className="mx-auto max-w-7xl">
67116
<FilterBar
68117
filters={filters}
69118
updateFilter={updateFilter}
70119
removeFilter={removeFilter}
120+
isAdmin
121+
buttonCallback={() => setIsAddDialogOpen(true)}
71122
/>
72123
<ProblemTable
73124
problems={problems}
@@ -77,6 +128,19 @@ function AdminPage() {
77128
handleEdit={handleEdit}
78129
/>
79130
</div>
131+
<ProblemInputDialog
132+
isOpen={isAddDialogOpen}
133+
onClose={() => setIsAddDialogOpen(false)}
134+
requestCallback={handleAdd}
135+
requestTitle="Add"
136+
/>
137+
138+
<InformationDialog
139+
isOpen={informationDialog !== ''}
140+
onClose={() => setInformationDialog('')}
141+
title="Status"
142+
description={informationDialog}
143+
/>
80144
</div>
81145
);
82146
}

peerprep-fe/src/app/signin/page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,12 @@ export default function LoginForm() {
3333
const token = data.accessToken;
3434
const res = await login(token);
3535
if (res) {
36-
setAuth(true, token);
36+
setAuth(true, token, data);
3737
router.push('/');
3838
return;
3939
}
4040
}
4141
setError(data.error || 'Please provide correct email and password');
42-
console.error('Login failed');
4342
};
4443

4544
return (

peerprep-fe/src/app/signup/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export default function SignUpPage() {
3838
password: password,
3939
}),
4040
});
41-
console.log('did i manage to fetch?, result: ', result);
4241

4342
const data = await result.json();
4443

peerprep-fe/src/components/navbar/Navbar.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { useAuthStore } from '@/state/useAuthStore';
1414

1515
export default function Navbar() {
16-
const { isAuth, clearAuth } = useAuthStore();
16+
const { isAuth, clearAuth, user } = useAuthStore();
1717

1818
const handleLogout = async () => {
1919
const res = await logout();
@@ -30,6 +30,11 @@ export default function Navbar() {
3030
PeerPrep
3131
</Link>
3232
<div className="flex items-center space-x-4">
33+
{user?.isAdmin && (
34+
<Link href="/admin" className="text-gray-300 hover:text-white">
35+
Admin
36+
</Link>
37+
)}
3338
<Link href="/" className="text-gray-300 hover:text-white">
3439
Questions
3540
</Link>

peerprep-fe/src/components/problems/ProblemInputDialog.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import { Textarea } from '../ui/textarea';
1212
import { Input } from '../ui/input';
1313
import { FilterSelect } from '@/app/(main)/components/filter/FilterSelect';
1414
import { TopicsPopover } from '@/app/(main)/components/filter/TopicsPopover';
15+
import { DIFFICULTY_OPTIONS, INITIAL_PROBLEM_DATA } from '@/lib/constants';
1516

1617
type Props = {
1718
isOpen: boolean;
1819
onClose: () => void;
19-
problem: Problem;
20+
problem?: Problem;
2021
requestCallback: (problem: Problem) => void;
2122
requestTitle: string;
2223
};
@@ -28,28 +29,30 @@ function ProblemInputDialog({
2829
requestCallback,
2930
requestTitle,
3031
}: Props) {
31-
const [problemData, setProblemData] = useState<Problem>(problem);
32+
const [problemData, setProblemData] = useState<Problem>(
33+
problem || INITIAL_PROBLEM_DATA,
34+
);
3235

3336
const handleSubmit = async () => {
3437
requestCallback(problemData);
3538
};
3639

37-
if (!problem) return null;
38-
3940
return (
4041
<Dialog open={isOpen} onOpenChange={onClose}>
4142
<DialogContent className="bg-black">
4243
<DialogHeader>
43-
<DialogTitle>Edit Question</DialogTitle>
44+
<DialogTitle>
45+
{problem ? 'Edit Question' : 'Add Question'}
46+
</DialogTitle>
4447
<DialogDescription />
4548
</DialogHeader>
4649
<div className="space-y-4">
4750
<div className="space-y-2">
48-
<p>Description</p>
51+
<p>Title</p>
4952
<Input
5053
name="title"
5154
placeholder="Question Title"
52-
defaultValue={problemData.title}
55+
value={problemData.title}
5356
onChange={(e) => {
5457
setProblemData({ ...problemData, title: e.target.value });
5558
}}
@@ -60,11 +63,7 @@ function ProblemInputDialog({
6063
<p>Difficulty</p>
6164
<FilterSelect
6265
placeholder="difficulty"
63-
options={[
64-
{ value: '1', label: 'Easy' },
65-
{ value: '2', label: 'Medium' },
66-
{ value: '3', label: 'Hard' },
67-
]}
66+
options={DIFFICULTY_OPTIONS}
6867
onChange={(value) => {
6968
setProblemData({ ...problemData, difficulty: Number(value) });
7069
}}
@@ -76,7 +75,7 @@ function ProblemInputDialog({
7675
<Textarea
7776
name="description"
7877
placeholder="Question Description"
79-
defaultValue={problemData.description}
78+
value={problemData.description}
8079
onChange={(e) => {
8180
setProblemData({ ...problemData, description: e.target.value });
8281
}}

peerprep-fe/src/components/problems/ProblemRow.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export default function ProblemRow({
5555
}
5656

5757
handleEdit(problemData).catch((e: Error) => {
58-
console.log(e);
5958
setInformationDialog(e.message);
6059
});
6160
};
@@ -154,7 +153,7 @@ export default function ProblemRow({
154153
<InformationDialog
155154
isOpen={informationDialog !== ''}
156155
onClose={() => setInformationDialog('')}
157-
title="Delete Status"
156+
title="Status"
158157
description={informationDialog}
159158
/>
160159
</>

peerprep-fe/src/lib/constants.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Problem } from '@/types/types';
2+
3+
export const DIFFICULTY_OPTIONS = [
4+
{ value: '1', label: 'Easy' },
5+
{ value: '2', label: 'Medium' },
6+
{ value: '3', label: 'Hard' },
7+
];
8+
9+
export const STATUS_OPTIONS = [
10+
{ value: 'todo', label: 'Todo' },
11+
{ value: 'solved', label: 'Solved' },
12+
];
13+
14+
export const INITIAL_PROBLEM_DATA: Problem = {
15+
_id: Math.floor(Math.random() * 10000), // Generate a temporary ID for new problems
16+
title: '',
17+
difficulty: 1, // Set a default difficulty
18+
description: '',
19+
examples: [],
20+
constraints: '',
21+
tags: [],
22+
title_slug: '',
23+
};

peerprep-fe/src/network/axiosClient.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ axiosQuestionClient.interceptors.request.use(
3333
axiosAuthClient.interceptors.request.use(
3434
(config) => {
3535
const token = getCookie('access-token');
36-
console.log('token', token);
3736
if (token) {
3837
config.headers['Authorization'] = `Bearer ${token}`;
3938
}

peerprep-fe/src/network/axiosServer.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ axiosQuestionServer.interceptors.request.use(
3131
axiosAuthServer.interceptors.request.use(
3232
(config) => {
3333
const token = cookies().get('access-token')?.value;
34-
console.log('token', token);
3534
if (token) {
3635
config.headers['Authorization'] = `Bearer ${token}`;
3736
}

0 commit comments

Comments
 (0)