Skip to content

Commit

Permalink
Woof 15 (#65)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: samanthaj9 <[email protected]>
Co-authored-by: laurenrm3 <[email protected]>
Co-authored-by: Nathan Zhang <[email protected]>
  • Loading branch information
5 people authored Jan 31, 2025
1 parent 0e7a39e commit def81c9
Show file tree
Hide file tree
Showing 20 changed files with 1,039 additions and 291 deletions.
99 changes: 99 additions & 0 deletions components/ApiUtils.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<string>>,
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<React.SetStateAction<string>>,
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<React.SetStateAction<string>>,
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');
}
};
126 changes: 108 additions & 18 deletions components/body/ArticleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -16,6 +22,56 @@ const ArticleList = ({
section = '',
color = 'accent-purple',
}: Props) => {

const [articleList, setArticleList] = useState<Articles[]>(articles);
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [editedContent, setEditedContent] = useState<string>('');
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 (
<main
className={`flex-grow flex min-h-screen flex-col justify-between pt-8 pl-8 pb-8 pr-0`}
Expand All @@ -31,14 +87,56 @@ const ArticleList = ({
</div>


<PinnedArticles articles={articles} section={section} color={color} />
{articles.length > 0 ? (
articles.map((article) => (
<PinnedArticles articles={articleList} section={section} color={color} />
{articleList.length > 0 ? (
articleList.map((article) => (
<div key={article._id}>
<Box title={article.title} innerText="" color={color}>
<Markdown className="prose">{article.content}</Markdown>
</Box>
<div className="main-content">
<div className="group">
<Box title={article.title} innerText="" color={color} />
{/* <p className="py-[1.2vmin] px-[3.7vmin] rounded-b-lg">
{article.title}
</p> */}
<div className="flex justify-between items-center">
{editingArticleId === article._id ? (
<textarea
className="prose border rounded p-2 w-full resize-none overflow-hidden"
value={editedContent}
ref={(el) => {
if (el) {
el.style.height = "auto";
el.style.height = `${el.scrollHeight}px`;
}
}}
onChange={(e) => {
setEditedContent(e.target.value);
e.target.style.height = "auto";
e.target.style.height = `${e.target.scrollHeight}px`;
}}
/>

) : (
<Markdown className="prose">{article.content}</Markdown>
)}
<div className="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
{editingArticleId === article._id ? (
<SaveIcon onClick={() => handleSaveClick(article._id, article)} />
) : (
<ModeEditIcon
onClick={() => handleEditClick(article._id, article.content)}
/>
) }
<DeleteIcon onClick={() => handleDeleteClick(article._id)} />
</div>
</div>
</div>
{/* </Box> */}
</div>

))
) : (
<p>No articles available.</p>
)}
{/* <div className="main-content">
<div className="btn-container">
<Link
href={{
Expand All @@ -48,19 +146,11 @@ const ArticleList = ({
>
<button className="btn edit">Edit</button>
</Link>
<Link
href={{ pathname: '/[id]', query: { id: article._id } }}
>
<Link href={{ pathname: '/[id]', query: { id: article._id } }}>
<button className="btn view">View</button>
</Link>
</div>
</div>
</div>
))
) : (
<p>No articles available.</p>
)}

</div> */}
{/* <div className="p-8">
<ul>
<li>How to use InDesign</li>
Expand Down
99 changes: 52 additions & 47 deletions components/body/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,61 @@
import Link from "next/link";
import Link from 'next/link';
import { useSearch } from '../context/SearchContext';

type Props = {
section?: string;
color?: string;
section?: string;
color?: string;
};

export const SearchResults = ({
section = '',
color = 'accent-purple',
}: Props) => {
const { searchTerm, filteredArticles } = useSearch();
section = '',
color = 'accent-purple',
}: Props) => {
const { searchTerm, filteredArticles } = useSearch();

let sectionArticles = filteredArticles;
if (section) {
sectionArticles = filteredArticles?.filter((article) => article.sections?.includes(section)) || [];
}
let sectionArticles = filteredArticles;
if (section) {
sectionArticles =
filteredArticles?.filter((article) =>
article.sections?.includes(section),
) || [];
}

return (
<>
{searchTerm && (
<div className="rounded-2xl border-black border-t-[0.5vmin] border-l-[0.5vmin] border-b-[0.5vmin] border-r-[0.5vmin] mb-[24px] w-fill">
<div className={`border-black border-b-[0.5vmin] bg-${color} h-[6vmin] w-full rounded-t-lg items-center pl-[3vmin] flex p-[1vmin]`}>
<p className="font-semibold text-center text-[3vmin] justify-center text-white bg-transparent quick-links pt-[1vmin]">
Search results for "{ searchTerm }" in {section || 'All'}
</p>
</div>
{sectionArticles.length > 0 && (
<div className="py-[1.2vmin] px-[3.7vmin] rounded-b-lg bg-white max-h-[400px] overflow-auto">
{sectionArticles.map((article) => (
<div key={article._id.$oid} className="search-results">
<Link
href={{
pathname: '/[id]',
query: { id: article._id.$oid },
}}
>
{article.title}
</Link>
</div>
))}
</div>
)}
{sectionArticles.length == 0 && (
<div className="py-[1.2vmin] px-[3.7vmin] rounded-b-lg bg-white">
<div className="no-results">
No articles found.
</div>
</div>
)}
return (
<>
{searchTerm && (
<div className="rounded-2xl border-black border-t-[0.5vmin] border-l-[0.5vmin] border-b-[0.5vmin] border-r-[0.5vmin] mb-[24px] w-fill">
<div
className={`border-black border-b-[0.5vmin] bg-${color} h-[6vmin] w-full rounded-t-lg items-center pl-[3vmin] flex p-[1vmin]`}
>
<p className="font-semibold text-center text-[3vmin] justify-center text-white bg-transparent quick-links pt-[1vmin]">
Search results for "{searchTerm}" in {section || 'All'}
</p>
</div>
{sectionArticles.length > 0 && (
<div className="py-[1.2vmin] px-[3.7vmin] rounded-b-lg bg-white max-h-[400px] overflow-auto">
{sectionArticles.map((article) => (
<div key={article._id.$oid} className="search-results">
<Link
href={{
pathname: '/[id]',
query: { id: article._id.$oid },
}}
style={{ color: 'blue', fontWeight: 'bold' }}
>
{article.title}
</Link>
<div>{article.content}</div>
</div>
)}
</>
)
}
))}
</div>
)}
{sectionArticles.length == 0 && (
<div className="py-[1.2vmin] px-[3.7vmin] rounded-b-lg bg-white">
<div className="no-results">No articles found.</div>
</div>
)}
</div>
)}
</>
);
};
Loading

0 comments on commit def81c9

Please sign in to comment.