From 69e9e11146fb8e8585b8a780c78d9a720b5b5e50 Mon Sep 17 00:00:00 2001 From: murramge Date: Fri, 20 Dec 2024 00:43:51 +0900 Subject: [PATCH] =?UTF-8?q?feature:=20=EC=B5=9C=EC=A2=85=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 1 + src/components/Footer.js | 9 ++ src/components/Header.js | 40 +++++ src/components/RenderPosts.js | 26 ++++ src/formatter/DateFormmat.js | 6 + src/main.js | 267 ++++------------------------------ src/pages/ErrorPage.js | 32 ++++ src/pages/LoginPage.js | 44 ++++++ src/pages/MainPage.js | 51 +++++++ src/pages/ProfilePage.js | 101 +++++++++++++ src/router/Route.js | 13 ++ src/router/Router.js | 29 ++++ src/router/RouterGuard.js | 12 ++ src/store/PostStore.js | 17 +++ src/store/UserStore.js | 34 +++++ 15 files changed, 441 insertions(+), 241 deletions(-) create mode 100644 src/components/Footer.js create mode 100644 src/components/Header.js create mode 100644 src/components/RenderPosts.js create mode 100644 src/formatter/DateFormmat.js create mode 100644 src/pages/ErrorPage.js create mode 100644 src/pages/LoginPage.js create mode 100644 src/pages/MainPage.js create mode 100644 src/pages/ProfilePage.js create mode 100644 src/router/Route.js create mode 100644 src/router/Router.js create mode 100644 src/router/RouterGuard.js create mode 100644 src/store/PostStore.js create mode 100644 src/store/UserStore.js diff --git a/index.html b/index.html index 0005b871..819b8cc0 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ 항해플러스 SNS +
diff --git a/src/components/Footer.js b/src/components/Footer.js new file mode 100644 index 00000000..d7413327 --- /dev/null +++ b/src/components/Footer.js @@ -0,0 +1,9 @@ +const Footer = () => { + return ` + + `; +}; + +export default Footer; diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 00000000..a80bf9d0 --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,40 @@ +import userStore from "../store/UserStore.js"; +import router from "../router/Router.js"; + +const Header = () => { + const LoginState = userStore.LoginState(); + const html = ` +
+

항해플러스

+
+ + `; + + const init = () => { + const nav = document.querySelector("nav"); + + nav.addEventListener("click", (e) => { + e.preventDefault(); + + if (e.target.innerHTML === "로그아웃") { + userStore.deleteUser(); + } + const path = e.target.href.replace(window.location.origin, ""); //상대 경로만 남기기! + router(path); + }); + }; + + return { html, init }; +}; + +export default Header; diff --git a/src/components/RenderPosts.js b/src/components/RenderPosts.js new file mode 100644 index 00000000..7f70d54e --- /dev/null +++ b/src/components/RenderPosts.js @@ -0,0 +1,26 @@ +const renderPosts = (posts = []) => { + const postList = posts + .map((post) => { + return ` +
+
+ 프로필 +
+

${post.username}

+

${post.time}

+
+
+

${post.post}

+
+ + + +
+
+ `; + }) + .join(""); + return postList; +}; + +export default renderPosts; diff --git a/src/formatter/DateFormmat.js b/src/formatter/DateFormmat.js new file mode 100644 index 00000000..f8d0e1e3 --- /dev/null +++ b/src/formatter/DateFormmat.js @@ -0,0 +1,6 @@ +const currentDate = new Date(); +const hours = String(currentDate.getHours()).padStart(2, "0"); // 2자리로 포맷팅 +const minutes = String(currentDate.getMinutes()).padStart(2, "0"); // 2자리로 포맷팅 +const time = `${hours}시 ${minutes}분`; + +export default time; diff --git a/src/main.js b/src/main.js index 036c2a38..33d1b245 100644 --- a/src/main.js +++ b/src/main.js @@ -1,241 +1,26 @@ -const MainPage = () => ` -
-
-
-

항해플러스

-
- - - -
-
- - -
- -
- -
-
- 프로필 -
-

홍길동

-

5분 전

-
-
-

오늘 날씨가 정말 좋네요. 다들 좋은 하루 보내세요!

-
- - - -
-
- -
-
- 프로필 -
-

김철수

-

15분 전

-
-
-

새로운 프로젝트를 시작했어요. 열심히 코딩 중입니다!

-
- - - -
-
- -
-
- 프로필 -
-

이영희

-

30분 전

-
-
-

오늘 점심 메뉴 추천 받습니다. 뭐가 좋을까요?

-
- - - -
-
- -
-
- 프로필 -
-

박민수

-

1시간 전

-
-
-

주말에 등산 가실 분 계신가요? 함께 가요!

-
- - - -
-
- -
-
- 프로필 -
-

정수연

-

2시간 전

-
-
-

새로 나온 영화 재미있대요. 같이 보러 갈 사람?

-
- - - -
-
-
-
- -
-

© 2024 항해플러스. All rights reserved.

-
-
-
-`; - -const ErrorPage = () => ` -
-
-

항해플러스

-

404

-

페이지를 찾을 수 없습니다

-

- 요청하신 페이지가 존재하지 않거나 이동되었을 수 있습니다. -

- - 홈으로 돌아가기 - -
-
-`; - -const LoginPage = () => ` -
-
-

항해플러스

-
-
- -
-
- -
- -
- -
-
- -
-
-
-`; - -const ProfilePage = () => ` -
-
-
-
-

항해플러스

-
- - - -
-
-

- 내 프로필 -

-
-
- - -
-
- - -
-
- - -
- -
-
-
- -
-

© 2024 항해플러스. All rights reserved.

-
-
-
-
-`; - -document.body.innerHTML = ` - ${MainPage()} - ${ProfilePage()} - ${LoginPage()} - ${ErrorPage()} -`; +import router from "./router/Router.js"; + +const LoadedListener = () => { + document.addEventListener("DOMContentLoaded", () => { + const currentPath = window.location.hash || window.location.pathname; + router(currentPath); + }); +}; + +const PopListener = () => { + window.addEventListener("popstate", () => { + const currentPath = window.location.hash || window.location.pathname; + router(currentPath); + }); +}; + +const HashChangeListener = () => { + window.addEventListener("hashchange", () => { + const currentPath = window.location.hash || window.location.pathname; + router(currentPath); + }); +}; + +LoadedListener(); +PopListener(); +HashChangeListener(); diff --git a/src/pages/ErrorPage.js b/src/pages/ErrorPage.js new file mode 100644 index 00000000..941ed24e --- /dev/null +++ b/src/pages/ErrorPage.js @@ -0,0 +1,32 @@ +import router from "../router/Router.js"; + +const ErrorPage = () => { + const html = ` +
+
+

항해플러스

+

404

+

페이지를 찾을 수 없습니다

+

+ 요청하신 페이지가 존재하지 않거나 이동되었을 수 있습니다. +

+ + 홈으로 돌아가기 + +
+
+`; + + const init = () => { + const homeBtn = document.querySelector("#to-main"); + + homeBtn.addEventListener("click", (e) => { + e.preventDefault(); + router("/"); + }); + }; + + return { html, init }; +}; + +export default ErrorPage; diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js new file mode 100644 index 00000000..000d60fe --- /dev/null +++ b/src/pages/LoginPage.js @@ -0,0 +1,44 @@ +import userStore from "../store/UserStore.js"; +import router from "../router/Router.js"; + +const LoginPage = () => { + const html = ` +
+
+

항해플러스

+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+
+ `; + + const init = () => { + const form = document.querySelector("#login-form"); + + form.addEventListener("submit", (e) => { + e.preventDefault(); + e.stopPropagation(); //전파 중지! + const username = form.querySelector(`input[name = "username"]`)?.value; + userStore.setUser({ username, email: "", bio: "" }); + router("/"); + }); + }; + + return { html, init }; +}; + +export default LoginPage; diff --git a/src/pages/MainPage.js b/src/pages/MainPage.js new file mode 100644 index 00000000..12bf9b49 --- /dev/null +++ b/src/pages/MainPage.js @@ -0,0 +1,51 @@ +import Footer from "../components/Footer"; +import Header from "../components/Header"; +import userStore from "../store/UserStore.js"; +import postStore from "../store/PostStore.js"; +import renderPosts from "../components/RenderPosts.js"; +import time from "../formatter/DateFormmat.js"; + +const MainPage = () => { + const { html: Headers, init: Initial } = Header(); + const LoginState = userStore.LoginState(); + const user = userStore.getUser(); + let posts = postStore.getPost(); + + const html = ` +
+
+ ${Headers} +
+
+ + +
+
+ ${renderPosts(posts)} +
+
+ ${Footer()} +
+
+ `; + + const init = () => { + Initial(); + const postbtn = document.querySelector("#postbutton"); + postbtn.addEventListener("click", (e) => { + e.preventDefault(); + const post = document.querySelector(`textarea[name= "postinput"]`)?.value; + LoginState && + postStore.setPost({ + username: user?.username ?? "", + post, + time, + }); + posts = postStore.getPost(); + document.querySelector(".space-y-4").innerHTML = renderPosts(posts); + }); + }; + return { html, init }; +}; + +export default MainPage; diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js new file mode 100644 index 00000000..49aa8599 --- /dev/null +++ b/src/pages/ProfilePage.js @@ -0,0 +1,101 @@ +import userStore from "../store/UserStore.js"; + +import router from "../router/Router.js"; +import Footer from "../components/Footer"; +import Header from "../components/Header"; + +const ProfilePage = () => { + const user = userStore.getUser(); + + const { html: Headers, init: Initial } = Header(); + + const html = ` +
+
+
+ ${Headers} +
+
+

+ 내 프로필 +

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ ${Footer()} +
+
+
+ `; + + const init = () => { + Initial(); + + const form = document.querySelector("#profile-form"); + + form.addEventListener("submit", (e) => { + e.preventDefault(); + e.stopPropagation(); + const username = form.querySelector(`input[name = "username"]`)?.value; + const email = form.querySelector(`input[name = "email"]`)?.value; + const bio = form.querySelector(`textarea[name = "bio"]`)?.value; + + userStore.setUser({ username, email, bio }); + + router("/profile"); + }); + }; + + return { html, init }; +}; + +export default ProfilePage; diff --git a/src/router/Route.js b/src/router/Route.js new file mode 100644 index 00000000..ba52311a --- /dev/null +++ b/src/router/Route.js @@ -0,0 +1,13 @@ +import MainPage from "../pages/MainPage.js"; +import ProfilePage from "../pages/ProfilePage.js"; +import LoginPage from "../pages/LoginPage.js"; +import ErrorPage from "../pages/ErrorPage.js"; + +const routes = { + "/": () => MainPage(), + "/profile": () => ProfilePage(), + "/login": () => LoginPage(), + 404: () => ErrorPage(), +}; + +export default routes; diff --git a/src/router/Router.js b/src/router/Router.js new file mode 100644 index 00000000..ca8ede09 --- /dev/null +++ b/src/router/Router.js @@ -0,0 +1,29 @@ +import routerGuard from "./RouterGuard.js"; +import routes from "./Route.js"; + +export const historyRouter = (historyPath) => { + const path = routerGuard(historyPath); + history.pushState({}, path); + const page = routes[path] ?? routes[404]; + const { html, init } = page(); + document.getElementById("root").innerHTML = html; + init(); +}; + +export const hashRouter = (hash) => { + const hashPath = hash.replace("#", ""); + const path = routerGuard(hashPath); + + window.location.hash = path; + + const page = routes[path] ?? routes[404]; + const { html, init } = page(); + + document.getElementById("root").innerHTML = html; + init(); +}; + +const router = (path) => + window.location.hash ? hashRouter(path) : historyRouter(path); + +export default router; diff --git a/src/router/RouterGuard.js b/src/router/RouterGuard.js new file mode 100644 index 00000000..d0b59d5f --- /dev/null +++ b/src/router/RouterGuard.js @@ -0,0 +1,12 @@ +import userStore from "../store/UserStore.js"; + +const routerGuard = (path) => { + const loginState = userStore.LoginState(); + const redirects = { + "/profile": loginState ? path : "/login", + "/login": loginState ? "/" : path, + }; + return redirects[path] ?? path; +}; + +export default routerGuard; diff --git a/src/store/PostStore.js b/src/store/PostStore.js new file mode 100644 index 00000000..03d34d38 --- /dev/null +++ b/src/store/PostStore.js @@ -0,0 +1,17 @@ +const setPost = ({ username, post, time }) => { + const posts = JSON.parse(localStorage.getItem("posts")) || []; + posts.push({ username, post, time }); + localStorage.setItem("posts", JSON.stringify(posts)); +}; + +const getPost = () => { + const post = JSON.parse(localStorage.getItem("posts")); + return post ? post : []; +}; + +const postStore = { + getPost, + setPost, +}; + +export default postStore; diff --git a/src/store/UserStore.js b/src/store/UserStore.js new file mode 100644 index 00000000..8845db79 --- /dev/null +++ b/src/store/UserStore.js @@ -0,0 +1,34 @@ +const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + +const LoginState = () => { + return !!getUser(); +}; + +const setUser = ({ username, email, bio }) => { + if (email && !isValidEmail(email)) { + return; + } + + localStorage.setItem("user", JSON.stringify({ username, email, bio })); +}; + +const getUser = () => { + const user = localStorage.getItem("user"); + return user ? JSON.parse(user) : null; +}; + +const deleteUser = () => { + localStorage.removeItem("user"); +}; + +const isValidEmail = (val) => emailRegex.test(val); + +const userStore = { + LoginState, + setUser, + getUser, + deleteUser, + isValidEmail, +}; + +export default userStore;