diff --git a/peerprep-fe/src/app/(main)/components/filter/FilterBar.tsx b/peerprep-fe/src/app/(main)/components/filter/FilterBar.tsx index 5ad1fabe2e..bb6ef52337 100644 --- a/peerprep-fe/src/app/(main)/components/filter/FilterBar.tsx +++ b/peerprep-fe/src/app/(main)/components/filter/FilterBar.tsx @@ -8,17 +8,7 @@ import { TopicsPopover } from './TopicsPopover'; import { FilterState } from '@/hooks/useFilteredProblems'; import { useState, useEffect } from 'react'; import { useDebounce } from '@/hooks/useDebounce'; - -const DIFFICULTY_OPTIONS = [ - { value: '1', label: 'Easy' }, - { value: '2', label: 'Medium' }, - { value: '3', label: 'Hard' }, -]; - -const STATUS_OPTIONS = [ - { value: 'todo', label: 'Todo' }, - { value: 'solved', label: 'Solved' }, -]; +import { DIFFICULTY_OPTIONS, STATUS_OPTIONS } from '@/lib/constants'; interface FilterBarProps { filters: FilterState; @@ -27,12 +17,16 @@ interface FilterBarProps { value: string | string[] | null, ) => void; removeFilter: (key: keyof FilterState, value?: string) => void; + isAdmin?: boolean; + buttonCallback?: () => void; } export default function FilterBar({ filters, updateFilter, removeFilter, + isAdmin = false, + buttonCallback, }: FilterBarProps) { const [searchTerm, setSearchTerm] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 300); // 300ms delay @@ -83,9 +77,18 @@ export default function FilterBar({ > - + {!isAdmin ? ( + + ) : ( + + )}
{filters.difficulty && ( diff --git a/peerprep-fe/src/app/admin/page.tsx b/peerprep-fe/src/app/admin/page.tsx index 5c9219cc23..7b2a44e65c 100644 --- a/peerprep-fe/src/app/admin/page.tsx +++ b/peerprep-fe/src/app/admin/page.tsx @@ -1,13 +1,17 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { useFilteredProblems } from '@/hooks/useFilteredProblems'; import FilterBar from '../(main)/components/filter/FilterBar'; import ProblemTable from '../../components/problems/ProblemTable'; import { axiosQuestionClient } from '@/network/axiosClient'; import { Problem } from '@/types/types'; import { isAxiosError } from 'axios'; +import ProblemInputDialog from '@/components/problems/ProblemInputDialog'; +import InformationDialog from '@/components/dialogs/InformationDialog'; function AdminPage() { + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); + const [informationDialog, setInformationDialog] = useState(''); const { problems, filters, @@ -61,6 +65,51 @@ function AdminPage() { } }; + const handleAdd = async (problem: Problem) => { + // TODO: Add proper validation of fields + if ( + problem.description === '' || + problem.title === '' || + problem.tags.length === 0 + ) { + setInformationDialog('Please fill in all required fields'); + return; + } + try { + const res = await axiosQuestionClient.post(`/questions`, { + difficulty: problem.difficulty, + description: problem.description, + examples: problem.examples, + constraints: problem.constraints, + tags: problem.tags, + title_slug: problem.title_slug, + title: problem.title, + }); + + refetchFilter(); + setIsAddDialogOpen(false); + return res; + } catch (e: unknown) { + if (isAxiosError(e)) { + switch (e.status) { + case 400: + throw new Error('Invalid question data. Please check your input.'); + case 409: + throw new Error('Question already exists'); + case 404: + throw new Error('Question not found'); + default: + throw new Error('Failed to update question'); + } + } + if (e instanceof Error) { + throw new Error(e.message); + } else { + throw new Error('An unknown error occurred'); + } + } + }; + return (
@@ -68,6 +117,8 @@ function AdminPage() { filters={filters} updateFilter={updateFilter} removeFilter={removeFilter} + isAdmin + buttonCallback={() => setIsAddDialogOpen(true)} />
+ setIsAddDialogOpen(false)} + requestCallback={handleAdd} + requestTitle="Add" + /> + + setInformationDialog('')} + title="Status" + description={informationDialog} + />
); } diff --git a/peerprep-fe/src/app/signin/page.tsx b/peerprep-fe/src/app/signin/page.tsx index 7818aad824..2663cb1c0e 100644 --- a/peerprep-fe/src/app/signin/page.tsx +++ b/peerprep-fe/src/app/signin/page.tsx @@ -33,13 +33,12 @@ export default function LoginForm() { const token = data.accessToken; const res = await login(token); if (res) { - setAuth(true, token); + setAuth(true, token, data); router.push('/'); return; } } setError(data.error || 'Please provide correct email and password'); - console.error('Login failed'); }; return ( diff --git a/peerprep-fe/src/app/signup/page.tsx b/peerprep-fe/src/app/signup/page.tsx index b60cbba15f..a862e3cf4e 100644 --- a/peerprep-fe/src/app/signup/page.tsx +++ b/peerprep-fe/src/app/signup/page.tsx @@ -38,7 +38,6 @@ export default function SignUpPage() { password: password, }), }); - console.log('did i manage to fetch?, result: ', result); const data = await result.json(); diff --git a/peerprep-fe/src/components/navbar/Navbar.tsx b/peerprep-fe/src/components/navbar/Navbar.tsx index 09f254e9b0..184d6a3771 100644 --- a/peerprep-fe/src/components/navbar/Navbar.tsx +++ b/peerprep-fe/src/components/navbar/Navbar.tsx @@ -13,7 +13,7 @@ import { import { useAuthStore } from '@/state/useAuthStore'; export default function Navbar() { - const { isAuth, clearAuth } = useAuthStore(); + const { isAuth, clearAuth, user } = useAuthStore(); const handleLogout = async () => { const res = await logout(); @@ -30,6 +30,11 @@ export default function Navbar() { PeerPrep
+ {user?.isAdmin && ( + + Admin + + )} Questions diff --git a/peerprep-fe/src/components/problems/ProblemInputDialog.tsx b/peerprep-fe/src/components/problems/ProblemInputDialog.tsx index ea89ea853d..293111b997 100644 --- a/peerprep-fe/src/components/problems/ProblemInputDialog.tsx +++ b/peerprep-fe/src/components/problems/ProblemInputDialog.tsx @@ -12,11 +12,12 @@ import { Textarea } from '../ui/textarea'; import { Input } from '../ui/input'; import { FilterSelect } from '@/app/(main)/components/filter/FilterSelect'; import { TopicsPopover } from '@/app/(main)/components/filter/TopicsPopover'; +import { DIFFICULTY_OPTIONS, INITIAL_PROBLEM_DATA } from '@/lib/constants'; type Props = { isOpen: boolean; onClose: () => void; - problem: Problem; + problem?: Problem; requestCallback: (problem: Problem) => void; requestTitle: string; }; @@ -28,28 +29,30 @@ function ProblemInputDialog({ requestCallback, requestTitle, }: Props) { - const [problemData, setProblemData] = useState(problem); + const [problemData, setProblemData] = useState( + problem || INITIAL_PROBLEM_DATA, + ); const handleSubmit = async () => { requestCallback(problemData); }; - if (!problem) return null; - return ( - Edit Question + + {problem ? 'Edit Question' : 'Add Question'} +
-

Description

+

Title

{ setProblemData({ ...problemData, title: e.target.value }); }} @@ -60,11 +63,7 @@ function ProblemInputDialog({

Difficulty

{ setProblemData({ ...problemData, difficulty: Number(value) }); }} @@ -76,7 +75,7 @@ function ProblemInputDialog({