diff options
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/src/pages/Home.tsx | 53 | ||||
| -rw-r--r-- | frontend/src/pages/articles/Article.tsx | 23 | ||||
| -rw-r--r-- | frontend/src/pages/articles/Edit.tsx | 142 | ||||
| -rw-r--r-- | frontend/src/routes/index.tsx | 12 |
4 files changed, 206 insertions, 24 deletions
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 71683e7..df9b4c1 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -5,6 +5,7 @@ type article = { title: string; photoUrl: string; content: string; + createdBy: string; createdOn: string; updateOn: string; }; @@ -14,11 +15,12 @@ type articleSearch = { value: string | null; }; - export default function Home({ articleSearch, + username, }: { articleSearch: articleSearch; + username: string | null; }) { const [articles, setArticles] = useState<article[]>([]); const [allArticles, setAllArticles] = useState<JSX.Element[]>([]); @@ -40,12 +42,30 @@ export default function Home({ if (!response.ok) { console.log(response); alert("check console for error"); + } else { + fetchArticles(); } - else { - fetchArticles(); - } }; + function renderButtons(article: article) { + return ( + <div> + <a + href={`/article/edit/${article.id}`} + className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 ml-4 text-sm rounded" + > + Edit + </a> + <form onSubmit={handleDelete} method="post"> + <input type="hidden" name="id" value={article.id} /> + <button className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 ml-4 text-sm rounded"> + Delete + </button> + </form> + </div> + ); + } + const fetchArticles = useCallback(() => { let url; if (articleSearch.value === null) { @@ -68,7 +88,9 @@ export default function Home({ }, [articleSearch.value]); // pull data when new search is given - useEffect(() => { fetchArticles(); } , [articleSearch.value, fetchArticles]); + useEffect(() => { + fetchArticles(); + }, [articleSearch.value, fetchArticles]); // when new data is pulled update the articles shown useEffect(() => { @@ -80,7 +102,10 @@ export default function Home({ > <div className="flex-1 bg-white rounded-t rounded-b-none overflow-hidden shadow-lg"> {/*th:href="@{/articles/{articleId}(articleId=${article.id})}"*/} - <a className="flex flex-wrap no-underline hover:no-underline"> + <a + href={`/article/${article.id}`} + className="flex flex-wrap no-underline hover:bg-sky-200 border-b-2 hover:no-underline" + > <img src={article.photoUrl} className="h-full w-full rounded-t pb-6" @@ -90,19 +115,7 @@ export default function Home({ </div> <p className="text-gray-800 font-serif text-base px-6 mb-5"></p> </a> - {/*th:if="${user.id} == ${article.createdBy.id}"*/} - <div></div> - {/*th:href="@{/articles/edit/{articleId}(articleId=${article.id})}"*/} - <a className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 ml-4 text-sm rounded"> - Edit - </a> - {/*th:href="@{/articles/delete/{articleId}(articleId=${article.id})}"*/} - <form onSubmit={handleDelete} method="post"> - <input type="hidden" name="id" value={article.id}/> - <button className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 ml-4 text-sm rounded"> - Delete - </button> - </form> + {username == article?.createdBy && renderButtons(article)} </div> <div className="flex-none mt-auto bg-white rounded-b rounded-t-none overflow-hidden shadow-lg p-6"> <div className="flex items-center justify-between"> @@ -112,7 +125,7 @@ export default function Home({ </div> )), ); - }, [articles]); + }, [articles, username]); return ( <> diff --git a/frontend/src/pages/articles/Article.tsx b/frontend/src/pages/articles/Article.tsx index 1a96ada..633e418 100644 --- a/frontend/src/pages/articles/Article.tsx +++ b/frontend/src/pages/articles/Article.tsx @@ -7,7 +7,7 @@ type article = { photoUrl: string; content: string; createdOn: string; - updateOn: string; + updatedOn: string; }; export default function Article() { @@ -28,8 +28,25 @@ export default function Article() { return ( <> - <h1>{articleData?.title}</h1> - <div>{articleData?.content}</div> + <div className="text-center pt-16 md:pt-32"> + <p className="text-sm md:text-base text-green-500 font-bold"> + {articleData?.createdOn} + </p> + <h1 className="font-bold break-normal text-3xl md:text-5xl"> + {articleData?.title} + </h1> + </div> + <div + className="container w-full max-w-6xl mx-auto bg-white bg-cover mt-8 rounded" + style={{ background: `url('${articleData?.photoUrl}'); height: 75vh;` }} + ></div> + <div className="container max-w-5xl mx-auto -mt-32"> + <div className="mx-0 sm:mx-6"> + <div className="bg-white w-full p-8 md:p-24 text-xl md:text-2xl text-gray-800 leading-normal"> + <div>{articleData?.content}</div> + </div> + </div> + </div> </> ); } diff --git a/frontend/src/pages/articles/Edit.tsx b/frontend/src/pages/articles/Edit.tsx new file mode 100644 index 0000000..4d8e2d8 --- /dev/null +++ b/frontend/src/pages/articles/Edit.tsx @@ -0,0 +1,142 @@ +import { useState, useEffect, FormEvent, ChangeEvent } from "react"; +import { useParams, useNavigate } from "react-router-dom"; + +type article = { + id: number; + title: string; + photoUrl: string; + content: string; + createdOn: string; + updatedOn: string; +}; + +export default function EditArticle() { + const navigate = useNavigate(); + const { id } = useParams(); + const [articleData, setArticleData] = useState<article>({ + id: 0, + title: "", + photoUrl: "", + content: "", + createdOn: "", + updatedOn: "", + }); + + useEffect(() => { + const url = `${import.meta.env.VITE_API_TITLE}/api/v1/article/${id}`; + fetch(url) + .then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error("Network response was not ok."); + }) + .then((response) => setArticleData(response)); + }, [id]); + + const handleChange = (e: ChangeEvent<HTMLInputElement>) => { + setArticleData({ ...articleData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { + e.preventDefault(); //stops submit from happening + + const target = e.target as typeof e.target & { + title: { value: string }; + photoUrl: { value: string }; + content: { value: string }; + }; + + const formData = new FormData(); + formData.append("title", target.title.value); + formData.append("photoUrl", target.photoUrl.value); + formData.append("content", target.content.value); + + const response = await fetch( + `${import.meta.env.VITE_API_TITLE}/api/v1/articles/edit/${articleData.id}`, + { + credentials: "include", + method: "post", + body: formData, + }, + ); + if (response.ok) { + navigate("/"); + } else { + console.log(response); + alert("check console for error"); + } + }; + + return ( + <> + <div className="flex justify-center bg-white p-12"> + <form onSubmit={handleSubmit} method="post" className="w-full max-w-lg"> + <input type="hidden" /> + <div className="flex flex-wrap -mx-3 mb-6"> + <div className="w-full md:w-1/2 px-3 mb-6 md:mb-0"> + <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"> + Title + </label> + <input + className="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" + id="title" + type="text" + name="title" + value={articleData.title} + onChange={handleChange} + placeholder="Yep" + /> + <p className="text-red-500 text-xs italic"> + Please fill out this field. + </p> + </div> + <div className="w-full md:w-1/2 px-3"> + <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"> + Photo URL + </label> + <input + className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" + id="photoUrl" + type="text" + name="photoUrl" + value={articleData.photoUrl} + onChange={handleChange} + placeholder="Doe" + /> + <p className="text-red-500 text-xs italic"> + Please fill out this field. + </p> + </div> + </div> + <div className="flex flex-wrap -mx-3 mb-6"> + <div className="w-full px-3"> + <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"> + Content + </label> + <input + className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" + id="content" + type="text" + name="content" + value={articleData.content} + onChange={handleChange} + placeholder="Doe" + /> + <p className="text-red-500 text-xs italic"> + Please fill out this field. + </p> + </div> + </div> + <div className="flex flex-wrap mb-2"></div> + <button + type="submit" + className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" + > + Update + </button> + </form> + </div> + </> + ); +} diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx index cee3529..df38459 100644 --- a/frontend/src/routes/index.tsx +++ b/frontend/src/routes/index.tsx @@ -4,6 +4,7 @@ import Home from "../pages/Home"; import Layout from "../components/Layout"; import Article from "../pages/articles/Article"; import NewArticle from "../pages/articles/New"; +import EditArticle from "../pages/articles/Edit"; import Register from "../pages/auth/Register"; import Login from "../pages/auth/Login"; @@ -33,9 +34,18 @@ export default function Index() { <Layout user={userProp} articleSearch={articleSearchProp} /> } > - <Route index element={<Home articleSearch={articleSearchProp} />} /> + <Route + index + element={ + <Home + username={userProp.value} + articleSearch={articleSearchProp} + /> + } + /> <Route path="/article/:id" element={<Article />} /> <Route path="/article/new" element={<NewArticle />} /> + <Route path="/article/edit/:id" element={<EditArticle />} /> <Route path="register" element={<Register />} /> <Route path="login" element={<Login user={userProp} />} /> </Route> |
