Skip to content

Commit

Permalink
add create question operation
Browse files Browse the repository at this point in the history
  • Loading branch information
Kurtyjlee committed Oct 11, 2024
1 parent 0b29aff commit 6191e71
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 41 deletions.
31 changes: 17 additions & 14 deletions peerprep-fe/src/app/(main)/components/filter/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -83,9 +77,18 @@ export default function FilterBar({
>
<Settings className="h-4 w-4" />
</Button>
<Button className="bg-green-600 text-white hover:bg-green-700">
Match
</Button>
{!isAdmin ? (
<Button className="bg-green-600 text-white hover:bg-green-700">
Match
</Button>
) : (
<Button
className="bg-blue-600 text-white hover:bg-blue-700"
onClick={buttonCallback}
>
Add
</Button>
)}
</div>
<div className="flex flex-wrap gap-2">
{filters.difficulty && (
Expand Down
66 changes: 65 additions & 1 deletion peerprep-fe/src/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -61,13 +65,60 @@ 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 (
<div className="min-h-screen bg-gray-900 p-6 pt-24 text-gray-100">
<div className="mx-auto max-w-7xl">
<FilterBar
filters={filters}
updateFilter={updateFilter}
removeFilter={removeFilter}
isAdmin
buttonCallback={() => setIsAddDialogOpen(true)}
/>
<ProblemTable
problems={problems}
Expand All @@ -77,6 +128,19 @@ function AdminPage() {
handleEdit={handleEdit}
/>
</div>
<ProblemInputDialog
isOpen={isAddDialogOpen}
onClose={() => setIsAddDialogOpen(false)}
requestCallback={handleAdd}
requestTitle="Add"
/>

<InformationDialog
isOpen={informationDialog !== ''}
onClose={() => setInformationDialog('')}
title="Status"
description={informationDialog}
/>
</div>
);
}
Expand Down
3 changes: 1 addition & 2 deletions peerprep-fe/src/app/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
1 change: 0 additions & 1 deletion peerprep-fe/src/app/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export default function SignUpPage() {
password: password,
}),
});
console.log('did i manage to fetch?, result: ', result);

const data = await result.json();

Expand Down
7 changes: 6 additions & 1 deletion peerprep-fe/src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -30,6 +30,11 @@ export default function Navbar() {
PeerPrep
</Link>
<div className="flex items-center space-x-4">
{user?.isAdmin && (
<Link href="/admin" className="text-gray-300 hover:text-white">
Admin
</Link>
)}
<Link href="/" className="text-gray-300 hover:text-white">
Questions
</Link>
Expand Down
25 changes: 12 additions & 13 deletions peerprep-fe/src/components/problems/ProblemInputDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -28,28 +29,30 @@ function ProblemInputDialog({
requestCallback,
requestTitle,
}: Props) {
const [problemData, setProblemData] = useState<Problem>(problem);
const [problemData, setProblemData] = useState<Problem>(
problem || INITIAL_PROBLEM_DATA,
);

const handleSubmit = async () => {
requestCallback(problemData);
};

if (!problem) return null;

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="bg-black">
<DialogHeader>
<DialogTitle>Edit Question</DialogTitle>
<DialogTitle>
{problem ? 'Edit Question' : 'Add Question'}
</DialogTitle>
<DialogDescription />
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<p>Description</p>
<p>Title</p>
<Input
name="title"
placeholder="Question Title"
defaultValue={problemData.title}
value={problemData.title}
onChange={(e) => {
setProblemData({ ...problemData, title: e.target.value });
}}
Expand All @@ -60,11 +63,7 @@ function ProblemInputDialog({
<p>Difficulty</p>
<FilterSelect
placeholder="difficulty"
options={[
{ value: '1', label: 'Easy' },
{ value: '2', label: 'Medium' },
{ value: '3', label: 'Hard' },
]}
options={DIFFICULTY_OPTIONS}
onChange={(value) => {
setProblemData({ ...problemData, difficulty: Number(value) });
}}
Expand All @@ -76,7 +75,7 @@ function ProblemInputDialog({
<Textarea
name="description"
placeholder="Question Description"
defaultValue={problemData.description}
value={problemData.description}
onChange={(e) => {
setProblemData({ ...problemData, description: e.target.value });
}}
Expand Down
3 changes: 1 addition & 2 deletions peerprep-fe/src/components/problems/ProblemRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export default function ProblemRow({
}

handleEdit(problemData).catch((e: Error) => {
console.log(e);
setInformationDialog(e.message);
});
};
Expand Down Expand Up @@ -154,7 +153,7 @@ export default function ProblemRow({
<InformationDialog
isOpen={informationDialog !== ''}
onClose={() => setInformationDialog('')}
title="Delete Status"
title="Status"
description={informationDialog}
/>
</>
Expand Down
23 changes: 23 additions & 0 deletions peerprep-fe/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Problem } from '@/types/types';

export const DIFFICULTY_OPTIONS = [
{ value: '1', label: 'Easy' },
{ value: '2', label: 'Medium' },
{ value: '3', label: 'Hard' },
];

export const STATUS_OPTIONS = [
{ value: 'todo', label: 'Todo' },
{ value: 'solved', label: 'Solved' },
];

export const INITIAL_PROBLEM_DATA: Problem = {
_id: Math.floor(Math.random() * 10000), // Generate a temporary ID for new problems
title: '',
difficulty: 1, // Set a default difficulty
description: '',
examples: [],
constraints: '',
tags: [],
title_slug: '',
};
1 change: 0 additions & 1 deletion peerprep-fe/src/network/axiosClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ axiosQuestionClient.interceptors.request.use(
axiosAuthClient.interceptors.request.use(
(config) => {
const token = getCookie('access-token');
console.log('token', token);
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
Expand Down
1 change: 0 additions & 1 deletion peerprep-fe/src/network/axiosServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ axiosQuestionServer.interceptors.request.use(
axiosAuthServer.interceptors.request.use(
(config) => {
const token = cookies().get('access-token')?.value;
console.log('token', token);
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
Expand Down
12 changes: 7 additions & 5 deletions peerprep-fe/src/state/useAuthStore.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { User } from '@/types/types';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

// Define the store interface
interface AuthState {
isAuth: boolean;
token: string | null;
setAuth: (isAuth: boolean, token: string | null) => void;
user: User | null; // Add user object to the state
setAuth: (isAuth: boolean, token: string | null, user: User | null) => void;
clearAuth: () => void;
}

Expand All @@ -15,9 +16,10 @@ export const useAuthStore = create<AuthState>()(
(set) => ({
isAuth: false,
token: null,
setAuth: (isAuth: boolean, token: string | null) =>
set({ isAuth, token }),
clearAuth: () => set({ isAuth: false, token: null }),
user: null, // Initialize user state
setAuth: (isAuth: boolean, token: string | null, user: User | null) =>
set({ isAuth, token, user }), // Update setAuth to include user
clearAuth: () => set({ isAuth: false, token: null, user: null }), // Clear user on logout
}),
{
name: 'auth-storage',
Expand Down

0 comments on commit 6191e71

Please sign in to comment.