Skip to content

Commit 384b4b2

Browse files
authored
Merge pull request #133 from CS3219-AY2425S1/fix/admin-validation
minor fixes for admin validation
2 parents c825b84 + 44aaf03 commit 384b4b2

File tree

6 files changed

+79
-30
lines changed

6 files changed

+79
-30
lines changed

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ import {
77
PopoverTrigger,
88
} from '@/components/ui/popover';
99
import { Button } from '@/components/ui/button';
10-
import { Check, ChevronsUpDown } from 'lucide-react';
10+
import { Check, ChevronsUpDown, Plus } from 'lucide-react';
1111
import { cn } from '@/lib/utils';
1212
import { Input } from '@/components/ui/input';
1313
import { ScrollArea } from '@/components/ui/scroll-area';
1414

1515
interface TopicsPopoverProps {
1616
selectedTopics: string[];
1717
onChange: (value: string[]) => void;
18+
isAdmin?: boolean;
1819
}
1920

2021
export function TopicsPopover({
2122
selectedTopics,
2223
onChange,
24+
isAdmin,
2325
}: TopicsPopoverProps) {
2426
const [open, setOpen] = useState(false);
2527
const [topics, setTopics] = useState<string[]>([]);
@@ -58,7 +60,7 @@ export function TopicsPopover({
5860
</Button>
5961
</PopoverTrigger>
6062
<PopoverContent className="w-[200px] p-0">
61-
<div className="p-2">
63+
<div className="flex gap-1 p-2">
6264
<Input
6365
placeholder="Search topics..."
6466
value={searchTerm}
@@ -68,8 +70,23 @@ export function TopicsPopover({
6870
}}
6971
className="mb-2"
7072
/>
73+
{isAdmin && (
74+
<Button
75+
onClick={async () => {
76+
if (!searchTerm.trim()) {
77+
return;
78+
}
79+
setTopics((prev) => [...prev, searchTerm]);
80+
setSearchTerm('');
81+
}}
82+
className="p-2"
83+
variant="outline"
84+
>
85+
<Plus className="h-4 w-4" />
86+
</Button>
87+
)}
7188
</div>
72-
<ScrollArea className="h-[300px]">
89+
<ScrollArea className="h-[300px] overflow-y-auto">
7390
{filteredTopics.length === 0 ? (
7491
<p className="p-2 text-sm text-muted-foreground">No topic found.</p>
7592
) : (

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ function AdminPage() {
2323
refetchFilter,
2424
} = useFilteredProblems();
2525

26+
const validateEntries = (problem: Problem) => {
27+
if (
28+
problem.description === '' ||
29+
problem.title === '' ||
30+
problem.tags.length === 0
31+
) {
32+
setInformationDialog('Please fill in all required fields');
33+
return false;
34+
}
35+
return true;
36+
};
37+
2638
const handleDelete = async (id: number) => {
2739
const res = await axiosClient.delete(`/questions/${id}`);
2840
if (res.status !== 200) {
@@ -34,6 +46,9 @@ function AdminPage() {
3446

3547
const handleEdit = async (problem: Problem) => {
3648
try {
49+
if (!validateEntries(problem)) {
50+
throw new Error('Invalid problem entries');
51+
}
3752
const res = await axiosClient.put(`/questions/${problem._id}`, {
3853
difficulty: problem.difficulty,
3954
description: problem.description,
@@ -68,16 +83,10 @@ function AdminPage() {
6883
};
6984

7085
const handleAdd = async (problem: Problem) => {
71-
// TODO: Add proper validation of fields
72-
if (
73-
problem.description === '' ||
74-
problem.title === '' ||
75-
problem.tags.length === 0
76-
) {
77-
setInformationDialog('Please fill in all required fields');
78-
return;
79-
}
8086
try {
87+
if (!validateEntries(problem)) {
88+
throw new Error('Invalid problem entries');
89+
}
8190
const res = await axiosClient.post(`/questions`, {
8291
difficulty: problem.difficulty,
8392
description: problem.description,

peerprep-fe/src/components/dialogs/ActionDialog.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { ReactElement } from 'react';
22
import {
33
Dialog,
44
DialogContent,
@@ -16,6 +16,7 @@ type Props = {
1616
description: string;
1717
callback?: () => void;
1818
callbackTitle?: string;
19+
children?: ReactElement;
1920
};
2021

2122
const ActionDialog = ({
@@ -26,18 +27,23 @@ const ActionDialog = ({
2627
description,
2728
callback,
2829
callbackTitle,
30+
children,
2931
}: Props) => {
3032
return (
3133
<Dialog open={isOpen} onOpenChange={onClose}>
32-
<DialogContent className="bg-black">
34+
<DialogContent className="max-h-[80%] overflow-auto bg-black">
3335
<DialogHeader>
3436
<DialogTitle>{title}</DialogTitle>
3537
<DialogDescription>{subtitle}</DialogDescription>
3638
</DialogHeader>
37-
<div className="mt-4">
38-
<h3 className="mb-2 text-lg font-semibold">Description:</h3>
39-
<p>{description}</p>
40-
</div>
39+
{children ? (
40+
children
41+
) : (
42+
<div className="mt-4">
43+
<h3 className="mb-2 text-lg font-semibold">Description:</h3>
44+
<p>{description}</p>
45+
</div>
46+
)}
4147
<div className="mt-6 flex justify-end">
4248
<Button variant="secondary" onClick={callback}>
4349
{callbackTitle}

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

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@ import { Button } from '../ui/button';
55
type Props = {
66
problem: Problem;
77
resetQuestion: () => void; // replace with something more generic
8+
hasHeader?: boolean;
89
};
910

10-
const ProblemDescriptionPanel = ({ problem, resetQuestion }: Props) => {
11+
const ProblemDescriptionPanel = ({
12+
problem,
13+
resetQuestion,
14+
hasHeader = true,
15+
}: Props) => {
1116
return (
1217
<>
13-
<div className="flex justify-between">
14-
<h2 className="mb-4 text-2xl font-bold">{problem.title}</h2>
15-
<Button
16-
variant="outline"
17-
className="border-gray-700 bg-gray-800"
18-
onClick={() => resetQuestion()}
19-
>
20-
Reset
21-
</Button>
22-
</div>
18+
{hasHeader && (
19+
<div className="flex justify-between">
20+
<h2 className="mb-4 text-2xl font-bold">{problem.title}</h2>
21+
<Button
22+
variant="outline"
23+
className="border-gray-700 bg-gray-800"
24+
onClick={() => resetQuestion()}
25+
>
26+
Reset
27+
</Button>
28+
</div>
29+
)}
30+
2331
<p className="mb-4">{problem.description}</p>
2432
{problem.examples.map((example, index) => (
2533
<React.Fragment key={index}>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ function ProblemInputDialog({
9090
onChange={(value) => {
9191
setProblemData({ ...problemData, tags: value });
9292
}}
93+
isAdmin
9394
/>
9495
</div>
9596
<div className="mt-2 flex justify-end">

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import InformationDialog from '../dialogs/InformationDialog';
1010
import ActionDialog from '../dialogs/ActionDialog';
1111
import { useRouter } from 'next/navigation';
1212
import { axiosClient } from '@/network/axiosClient';
13+
import ProblemDescriptionPanel from './ProblemDescriptionPanel';
1314

1415
function ProblemStatus({ status }: { status: string }) {
1516
if (status === 'solved') {
@@ -156,7 +157,14 @@ export default function ProblemRow({
156157
description={problem.description}
157158
callback={handleMatch}
158159
callbackTitle="Match"
159-
/>
160+
>
161+
<ProblemDescriptionPanel
162+
problem={problem}
163+
resetQuestion={() => {}}
164+
hasHeader={false}
165+
/>
166+
</ActionDialog>
167+
160168
{/* Dialog for deleting question */}
161169
<ActionDialog
162170
isOpen={isDeleteDialogOpen}

0 commit comments

Comments
 (0)