From def81c9896ca994ed967dd6fc145f09ed31f0fb1 Mon Sep 17 00:00:00 2001 From: Justin Wang <107002600+justinw-23@users.noreply.github.com> Date: Thu, 30 Jan 2025 18:33:27 -0800 Subject: [PATCH] Woof 15 (#65) * couple of icons * pre-mature working version, it updates, but also reloads? * no more reload but semi-proper update * Search results are from MongoDB now; added a simple ArticleView for testing purposes; issue: clicking a search result redirects to home page (??) * formatted search results * Woof-16: Removed sample search test dependency after connection to MongoDB * edit full text box * working delete functionality * fixed minor errors --------- Co-authored-by: Parsa Hajipour <47697224+ParsaHaji09@users.noreply.github.com> Co-authored-by: samanthaj9 Co-authored-by: laurenrm3 <115776428+laurenrm3@users.noreply.github.com> Co-authored-by: Nathan Zhang --- components/ApiUtils.tsx | 99 ++++ components/body/ArticleList.tsx | 126 ++++- components/body/SearchResults.tsx | 99 ++-- components/context/SearchContext.tsx | 64 ++- components/edit/Form.tsx | 2 +- components/layout/SearchBar.tsx | 2 +- components/layout/searchResults.json | 63 --- components/layout/searchResults.tsx | 27 + components/view/ArticleView.tsx | 45 ++ package-lock.json | 774 +++++++++++++++++++++++---- package.json | 1 + pages/[id]/index.tsx | 4 +- pages/all/index.tsx | 3 +- pages/arts/index.tsx | 3 +- pages/misc/index.tsx | 3 +- pages/new/index.tsx | 3 +- pages/news/index.tsx | 3 +- pages/opinion/index.tsx | 3 +- pages/sports/index.tsx | 3 +- pages/troubleshooting/index.tsx | 3 +- 20 files changed, 1039 insertions(+), 291 deletions(-) create mode 100644 components/ApiUtils.tsx delete mode 100644 components/layout/searchResults.json create mode 100644 components/layout/searchResults.tsx create mode 100644 components/view/ArticleView.tsx diff --git a/components/ApiUtils.tsx b/components/ApiUtils.tsx new file mode 100644 index 0000000..8012b99 --- /dev/null +++ b/components/ApiUtils.tsx @@ -0,0 +1,99 @@ +// apiUtils.ts (or any other utility file) +import { mutate } from 'swr'; + +export interface FormData { + title: string; + content: string; + // created_date: Date; + // updated_date: Date; + sections: string[]; + pinned_sections: string[]; + quick_link: boolean; + image_url: string; + } + +export const putData = async ( + form: FormData, + id: string | string[] | undefined, + contentType: string, + setMessage: React.Dispatch>, + router: any + ) => { + try { + const res = await fetch(`/api/articles/${id}`, { + method: 'PUT', + headers: { + Accept: contentType, + 'Content-Type': contentType, + }, + body: JSON.stringify(form), + }); + + if (!res.ok) { + throw new Error(res.status.toString()); + } + + const { data } = await res.json(); + + mutate(`/api/articles/${id}`, data, false); // Update the local data without revalidation + + } catch (error) { + setMessage('Failed to update article'); + } + }; + + + +export const postData = async ( + form: FormData, + id: string, + contentType: string, + setMessage: React.Dispatch>, + router: any +) => { + + try { + const res = await fetch('/api/articles', { + method: 'POST', + headers: { + Accept: contentType, + 'Content-Type': contentType, + }, + body: JSON.stringify(form), + }); + + // Throw error with status code in case Fetch API req failed + if (!res.ok) { + throw new Error(res.status.toString()); + } + + router.push('/'); + } catch (error) { + setMessage('Failed to add article'); + } + }; + + +export const deleteData = async ( + id: string, + setMessage: React.Dispatch>, + router: any +) => { + try { + const res = await fetch(`/api/articles/${id}`, { + method: 'DELETE', + }); + + if (!res.ok) { + throw new Error(res.status.toString()); + } + + // Remove the deleted item from SWR cache + mutate(`/api/articles/${id}`, null, false); + + setMessage('Article deleted successfully'); + + } catch (error) { + setMessage('Failed to delete article'); + } +}; diff --git a/components/body/ArticleList.tsx b/components/body/ArticleList.tsx index 8dd53c3..9ceeb58 100644 --- a/components/body/ArticleList.tsx +++ b/components/body/ArticleList.tsx @@ -3,7 +3,13 @@ import Box from '../Box'; import Markdown from 'react-markdown'; import { Articles } from '../../models/article'; import PinnedArticles from '../PinnedArticles'; -import { SearchResults } from "./SearchResults"; +import { SearchResults } from './SearchResults'; +import ModeEditIcon from '@mui/icons-material/ModeEdit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import SaveIcon from '@mui/icons-material/Save'; +import { useState } from 'react'; +import { putData, FormData, deleteData } from '../ApiUtils'; +import { useRouter } from 'next/router'; type Props = { articles: Articles[]; @@ -16,6 +22,56 @@ const ArticleList = ({ section = '', color = 'accent-purple', }: Props) => { + + const [articleList, setArticleList] = useState(articles); + const [editingArticleId, setEditingArticleId] = useState(null); + const [editedContent, setEditedContent] = useState(''); + const [message, setMessage] = useState(''); + const router = useRouter(); + + const handleEditClick = (articleId: string, currentContent: string) => { + setEditingArticleId(articleId); + setEditedContent(currentContent); + }; + + const handleDeleteClick = async (articleId: string) => { + console.log("deleting this beautiful article now"); + await deleteData(articleId, setMessage, router); + setArticleList((prevList) => prevList.filter(article => article._id != articleId)); + }; + + const handleSaveClick = async (articleId: string, article: Articles) => { + // Save the edited content logic here (e.g., API call to update the article in the database) + console.log( + 'Save content for article ID:', + articleId, + 'Content:', + editedContent, + ); + + const form: FormData = { + title: article.title, + content: editedContent, + sections: article.sections, + pinned_sections: article.pinned_sections, + quick_link: article.quick_link, + image_url: article.image_url, + }; + + await putData(form, articleId, 'application/json', setMessage, router); + + // i think we need a better way of doing this + + setArticleList((prevArticles) => + prevArticles.map((a) => + a.title === article.title ? ({ ...a, content: editedContent } as Articles) : a + ) + ); + + setEditingArticleId(null); + setEditedContent(''); + }; + return (
- - {articles.length > 0 ? ( - articles.map((article) => ( + + {articleList.length > 0 ? ( + articleList.map((article) => (
- - {article.content} - -
+
+ + {/*

+ {article.title} +

*/} +
+ {editingArticleId === article._id ? ( +