From cc04a47f4e1597b4fe3d92235929d553205dac4a Mon Sep 17 00:00:00 2001 From: realtradam Date: Sat, 27 Jul 2024 04:17:50 -0400 Subject: implement search and better UX --- .../blog/web/controllers/ArticleController.java | 19 +- frontend/src/components/Layout.tsx | 362 +++++++++++++-------- frontend/src/pages/Home.tsx | 297 ++++++++++------- frontend/src/pages/auth/Login.tsx | 137 ++++---- frontend/src/routes/index.tsx | 44 ++- 5 files changed, 523 insertions(+), 336 deletions(-) diff --git a/backend/src/main/java/com/blog/web/controllers/ArticleController.java b/backend/src/main/java/com/blog/web/controllers/ArticleController.java index ec8af85..0df26a2 100644 --- a/backend/src/main/java/com/blog/web/controllers/ArticleController.java +++ b/backend/src/main/java/com/blog/web/controllers/ArticleController.java @@ -40,8 +40,14 @@ public class ArticleController { } @GetMapping("/articles") - public HashSet listArticles() { - HashSet articles = new HashSet(articleService.findAllArticles()); + public HashSet listArticles(@RequestParam(value = "search", required = false) String search) { + HashSet articles; + if(search != null) { + articles = articleService.searchPublicArticles(search); + } + else { + articles = new HashSet(articleService.findAllArticles()); + } return articles; } @@ -103,15 +109,6 @@ public class ArticleController { return "redirect:/articles"; } - @GetMapping("/articles/search") - public String searchArticle(@RequestParam(value = "search") String search, Model model) { - //UserEntity user = userService.getLoggedInUser().orElse(new UserEntity()); - //model.addAttribute("user", user); - HashSet articles = articleService.searchPublicArticles(search); - //model.addAttribute("articles", articles); - return "index"; - } - @GetMapping("/") public String getArticles() { return "redirect:/articles"; diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index cdd3929..2ebaa79 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,152 +1,246 @@ -import { Outlet } from "react-router-dom"; -import { useState, useEffect } from "react"; - - -export default function Layout() -{ - const [user, setUser] = useState(null); +import { Outlet, useNavigate } from "react-router-dom"; +import { useState, useEffect, FormEvent } from "react"; + +type user = { + set: React.Dispatch>; + value: string | null; +}; +type articleSearch = user; + +export default function Layout({ + user, + articleSearch, +}: { + user: user; + articleSearch: articleSearch; +}) { + const navigate = useNavigate(); useEffect(() => { const url = `${import.meta.env.VITE_API_TITLE}/api/v1/profile`; fetch(url, { - credentials: 'include', - method: 'get', - }).then((response) => { - if (response.ok) { - return response.json(); - } - throw new Error("Network response was not ok."); - }).then((response) => { setUser(response.username); console.log(response.username) }); + credentials: "include", + method: "get", + }) + .then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error("Network response was not ok."); + }) + .then((response) => { + user.set(response.username); + console.log(response.username); + }); }, [user]); const logout = () => { fetch(`${import.meta.env.VITE_API_TITLE}/api/v1/logout`, { - credentials: 'include', - method: 'get', + credentials: "include", + method: "get", }).then(() => { - setUser(null); + user.set(null); }); - } - - return( - <> -
- - - -{/*slide in nav*/} - + const search = (e: FormEvent) => { + e.preventDefault(); -{/*Header*/} -
-
- {/*Title*/} -

- ☕ Spring! -

-

Welcome to my Blog

-
-
+ const target = e.target as typeof e.target & { + search: { value: string }; + }; - + articleSearch.set(target.search.value); -
-
+ navigate("/"); + }; -
-
- - - -
-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
- - - -
-
- - - - -
+ return ( + <> +
+ + + {/*slide in nav*/} + + + {/*Header*/} +
+
+ {/*Title*/} +

+ ☕ Spring! +

+

Welcome to my Blog

+
+
+ + + +
+
+
+
+ + + +
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+
+ + + + +
); } diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index eed745b..682f794 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,123 +1,194 @@ import { useState, useEffect } from "react"; -export default function Home() { - const [articles, setArticles] = useState([]); - +type article = { + id: number; + title: string; + photoUrl: string; + content: string; + createdOn: string; + updateOn: string; +}; +type articleSearch = { + set: React.Dispatch>; + value: string | null; +}; + +export default function Home({ + articleSearch, +}: { + articleSearch: articleSearch; +}) { + const [articles, setArticles] = useState([]); + const [allArticles, setAllArticles] = useState(null); + + // pull data when new search is given useEffect(() => { - const url = `${import.meta.env.VITE_API_TITLE}/api/v1/articles`; - fetch(url).then((response) => { - if (response.ok) { - return response.json(); - } - throw new Error("Network response was not ok."); - }).then((response) => setArticles(response)); - }, []); - - const allArticles = articles.map((article) => ( -
-
- {/*th:href="@{/articles/{articleId}(articleId=${article.id})}"*/} - - {/*th:src="${article.photoUrl}"*/} - - {/*th:text="${article.title}"*/} -
{article.title}
-

-
+ let url; + if (articleSearch.value === null) { + //alert("not searched"); + url = `${import.meta.env.VITE_API_TITLE}/api/v1/articles`; + } else { + //alert("searched"); + url = `${import.meta.env.VITE_API_TITLE}/api/v1/articles?search=${ + articleSearch.value + }`; + } + fetch(url) + .then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error("Network response was not ok."); + }) + .then((response) => setArticles(response)); + }, [articleSearch.value]); + + // when new data is pulled update the articles shown + useEffect(() => { + setAllArticles( + articles.map((article) => ( +
+
+ {/*th:href="@{/articles/{articleId}(articleId=${article.id})}"*/} + + +
+ {article.title} +
+

+
{/*th:if="${user.id} == ${article.createdBy.id}"*/} -
+
{/*th:href="@{/articles/edit/{articleId}(articleId=${article.id})}"*/} - Edit - {/*th:href="@{/articles/delete/{articleId}(articleId=${article.id})}"*/} - Delete - -
-
-
-

-
-
-
- )); - return( + + Edit + + {/*th:href="@{/articles/delete/{articleId}(articleId=${article.id})}"*/} + + Delete + +
+
+
+

+
+
+
+ )), + ); + }, [articles]); + + return ( <> - {/*Container*/} -
- -
- - {/**/} - - - -
); } diff --git a/frontend/src/pages/auth/Login.tsx b/frontend/src/pages/auth/Login.tsx index 5a1e858..d064acf 100644 --- a/frontend/src/pages/auth/Login.tsx +++ b/frontend/src/pages/auth/Login.tsx @@ -2,12 +2,15 @@ import { FormEvent } from "react"; import { useNavigate } from "react-router-dom"; //type setUser = { setUser: { func: React.Dispatch> } }; -type user = { set: React.Dispatch>, value: string | null }; +type user = { + set: React.Dispatch>; + value: string | null; +}; -export default function Login ({user}: {user: user}) { +export default function Login({ user }: { user: user }) { const navigate = useNavigate(); -const handleSubmit = async (e: FormEvent) => { + const handleSubmit = async (e: FormEvent) => { e.preventDefault(); //stops submit from happening const target = e.target as typeof e.target & { @@ -17,75 +20,85 @@ const handleSubmit = async (e: FormEvent) => { }; const formData = new FormData(); - formData.append('username', target.username.value); - formData.append('password', target.password.value); + formData.append("username", target.username.value); + formData.append("password", target.password.value); - const response = await fetch(`${import.meta.env.VITE_API_TITLE}/api/v1/login`, { - credentials: 'include', - method: 'post', - body: formData, - }).then((res) => { - if(res.ok) { + const response = await fetch( + `${import.meta.env.VITE_API_TITLE}/api/v1/login`, + { + credentials: "include", + method: "post", + body: formData, + }, + ).then((res) => { + if (res.ok) { const url = `${import.meta.env.VITE_API_TITLE}/api/v1/profile`; fetch(url, { - credentials: 'include', - method: 'get', - }).then((response) => { - if (response.ok) { - return response.json(); - } - throw new Error("Network response was not ok."); - }).then((response) => { - user.set(response.username); - console.log("USER:"); - console.log(user); - console.log(user.value); - console.log(response.username); - navigate("/"); - }); - } - else { + credentials: "include", + method: "get", + }) + .then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error("Network response was not ok."); + }) + .then((response) => { + user.set(response.username); + console.log("USER:"); + console.log(user); + console.log(user.value); + console.log(response.username); + navigate("/"); + }); + } else { console.log(response); alert("check console for error"); } }); }; - return( + return ( <> -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
); - } diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx index e77421c..b46315b 100644 --- a/frontend/src/routes/index.tsx +++ b/frontend/src/routes/index.tsx @@ -7,25 +7,37 @@ import NewArticle from "../pages/articles/New"; import Register from "../pages/auth/Register"; import Login from "../pages/auth/Login"; -type user = { set: React.Dispatch>, value: string | null }; +type user = { + set: React.Dispatch>; + value: string | null; +}; +type articleSearch = user; -export default function Index() -{ +export default function Index() { const [user, setUser] = useState(null); + const [articleSearch, setArticleSearch] = useState(null); const userProp: user = { set: setUser, value: user }; + const articleSearchProp: articleSearch = { + set: setArticleSearch, + value: articleSearch, + }; - return (<> - - - }> - } /> - } /> - } /> - } /> - } /> - - - - ); + return ( + <> + + + }> + } /> + } /> + } /> + } /> + } /> + + + + + ); } -- cgit v1.2.3