Skip to content

Commit

Permalink
Refractor code
Browse files Browse the repository at this point in the history
  • Loading branch information
guanquann committed Sep 21, 2024
1 parent ceef185 commit d4d236b
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 23 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./components/Layout";
import NewQuestion from "./pages/NewQuestion";
import QuestionDetail from "./pages/QuestionDetail";
import QuestionEdit from "./pages/QuestionEdit";
import PageNotFound from "./pages/Error";

function App() {
Expand All @@ -12,6 +13,7 @@ function App() {
<Route path="/questions" element={<>question page list</>} />
<Route path="/questions/new" element={<NewQuestion />} />
<Route path="/questions/:questionId" element={<QuestionDetail />} />
<Route path="/questions/:questionId/edit" element={<QuestionEdit />} />
<Route path="*" element={<PageNotFound />} />
</Route>
</Routes>
Expand Down
15 changes: 1 addition & 14 deletions frontend/src/pages/NewQuestion/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,11 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

import { complexityList, categoryList } from "../../utils/constants";
import AppMargin from "../../components/AppMargin";
import QuestionMarkdown from "../../components/QuestionMarkdown";
import QuestionImageContainer from "../../components/QuestionImageContainer";

// hardcode for now
const complexityList: string[] = ["Easy", "Medium", "Hard"];
const categoryList: string[] = [
"Strings",
"Algorithms",
"Data Structures",
"Bit Manipulation",
"Recursion",
"Databases",
"Arrays",
"Brainteaser",
];

const NewQuestion = () => {
const navigate = useNavigate();

Expand Down Expand Up @@ -106,7 +94,6 @@ const NewQuestion = () => {
return;
}

toast.success("Question successfully created");
navigate("/questions");
} catch (error) {
console.error(error);
Expand Down
193 changes: 193 additions & 0 deletions frontend/src/pages/QuestionEdit/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { useEffect, useState, useReducer } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Autocomplete, Button, IconButton, Stack, TextField } from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

import { complexityList, categoryList } from "../../utils/constants";
import reducer, { getQuestionById, initialState } from "../../reducers/questionReducer";
import AppMargin from "../../components/AppMargin";
import QuestionMarkdown from "../../components/QuestionMarkdown";
import QuestionImageContainer from "../../components/QuestionImageContainer";

const QuestionEdit = () => {
const navigate = useNavigate();

const { questionId } = useParams<{ questionId: string }>();
const [state, dispatch] = useReducer(reducer, initialState);

const [title, setTitle] = useState<string>("");
const [markdownText, setMarkdownText] = useState<string>("");
const [selectedComplexity, setselectedComplexity] = useState<string | null>(null);
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
const [uploadedImagesUrl, setUploadedImagesUrl] = useState<string[]>([]);

useEffect(() => {
if (!questionId) {
return;
}
getQuestionById(questionId, dispatch);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (state.selectedQuestion) {
setTitle(state.selectedQuestion.title);
setMarkdownText(state.selectedQuestion.description);
setselectedComplexity(state.selectedQuestion.complexity);
setSelectedCategories(state.selectedQuestion.categories);
}
}, [state.selectedQuestion]);

const handleBack = () => {
if (!confirm("Are you sure you want to leave this page? All process will be lost.")) {
return;
}
navigate("/questions");
};

const handleImageUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
if (!event.target.files) {
return;
}

const formData = new FormData();
for (const file of event.target.files) {
if (!file.type.startsWith("image/")) {
toast.error(`${file.name} is not an image`);
continue;
}

if (file.size > 5 * 1024 * 1024) {
toast.error(`${file.name} is more than 5MB`);
continue;
}
formData.append("images[]", file);
}

try {
const response = await fetch("http://localhost:3000/api/questions/images", {
method: "POST",
body: formData,
});

if (!response.ok) {
throw new Error("Failed to upload image");
}

const data = await response.json();
for (const imageUrl of data.imageUrls) {
setUploadedImagesUrl((prev) => [...prev, imageUrl]);
}
toast.success("File uploaded successfully");
} catch (error) {
console.error(error);
toast.error("Error uploading file");
}
};

const handleUpdate = async () => {
if (!state.selectedQuestion) {
return;
}

if (
title === state.selectedQuestion.title &&
markdownText === state.selectedQuestion.description &&
selectedComplexity === state.selectedQuestion.complexity &&
selectedCategories === state.selectedQuestion.categories
) {
toast.error("You have not made any changes to the question");
return;
}

try {
const response = await fetch(
`http://localhost:3000/api/questions/${state.selectedQuestion.questionId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title,
description: markdownText,
complexity: selectedComplexity,
category: selectedCategories,
}),
}
);

const data = await response.json();
if (response.status === 400) {
toast.error(data.message);
return;
}

navigate("/questions");
} catch (error) {
console.error(error);
toast.error("Failed to updated question");
}
};

return (
<AppMargin>
<IconButton onClick={handleBack} sx={{ marginTop: 2 }}>
<ArrowBackIcon />
</IconButton>

<TextField
label="Title"
variant="outlined"
size="small"
fullWidth
autoComplete="off"
value={title}
sx={{ marginTop: 2 }}
onChange={(value) => setTitle(value.target.value)}
/>

<Autocomplete
options={complexityList}
size="small"
sx={{ marginTop: 2 }}
value={selectedComplexity}
onChange={(e, newcomplexitySelected) => {
setselectedComplexity(newcomplexitySelected);
}}
renderInput={(params) => <TextField {...params} label="Complexity" />}
/>

<Autocomplete
multiple
options={categoryList}
size="small"
sx={{ marginTop: 2 }}
value={selectedCategories}
onChange={(e, newCategoriesSelected) => {
setSelectedCategories(newCategoriesSelected);
}}
renderInput={(params) => <TextField {...params} label="Category" />}
/>

<QuestionImageContainer
handleImageUpload={handleImageUpload}
uploadedImagesUrl={uploadedImagesUrl}
/>

<QuestionMarkdown markdownText={markdownText} setMarkdownText={setMarkdownText} />

<Stack paddingTop={2} paddingBottom={8}>
<Button variant="contained" fullWidth onClick={handleUpdate}>
Update Question
</Button>
</Stack>

<ToastContainer position="bottom-right" />
</AppMargin>
);
};

export default QuestionEdit;
12 changes: 3 additions & 9 deletions frontend/src/reducers/questionReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ export const initialState: QuestionsState = {
selectedQuestionError: null,
};

export const getQuestionById = (
questionId: string,
dispatch: Dispatch<QuestionActions>
) => {
export const getQuestionById = (questionId: string, dispatch: Dispatch<QuestionActions>) => {
// questionClient
// .get(`/questions/${questionId}`)
// .then((res) =>
Expand All @@ -61,15 +58,12 @@ export const getQuestionById = (
title: "Test Question",
description: md,
complexity: "Easy",
categories: ["Category1", "Category2"],
categories: ["Strings", "Databases"],
},
});
};

const reducer = (
state: QuestionsState,
action: QuestionActions
): QuestionsState => {
const reducer = (state: QuestionsState, action: QuestionActions): QuestionsState => {
const { type } = action;

switch (type) {
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const complexityList: string[] = ["Easy", "Medium", "Hard"];

const categoryList: string[] = [
"Strings",
"Algorithms",
"Data Structures",
"Bit Manipulation",
"Recursion",
"Databases",
"Arrays",
"Brainteaser",
];

export { complexityList, categoryList };

0 comments on commit d4d236b

Please sign in to comment.