Skip to content

Commit

Permalink
feat: 기본 과제 구현 및 주석 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyMan0 committed Dec 17, 2024
1 parent 832306c commit 0c0104c
Show file tree
Hide file tree
Showing 16 changed files with 326 additions and 90 deletions.
34 changes: 34 additions & 0 deletions src/Renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function overrideHistoryMethod(method) {
const originalMethod = window.history[method];
window.history[method] = function (state, title, url) {
originalMethod.apply(this, arguments);
const event = new CustomEvent(method, { detail: { state, title, url } });
window.dispatchEvent(event);
};
}

export const Renderer = {
onPopState: (callback) => {
window.addEventListener("popstate", callback);
},
onPushState: (callback) => {
overrideHistoryMethod("pushState");
window.addEventListener("pushState", callback);
},
onReplaceState: (callback) => {
overrideHistoryMethod("onReplaceState");
window.addEventListener("onReplaceState", callback);
},
onATagClick: (callback) => {
document.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
window.history.pushState({}, "", event.target.getAttribute("href"));
callback();
});
});
},
onHashChange: (callback) => {
window.addEventListener("hashchange", () => callback());
},
};
29 changes: 29 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Error404Page from "./pages/404/404";
import HomePage from "./pages/home/home-page";
import LoginPage from "./pages/login/login-page";
import { ProfilePage } from "./pages/profile/profile-page";
import { MyRouter } from "./shared/router/router";

function createRouter(routes) {
return (path) => {
const route = routes[path] || routes["404"];
return route;
};
}
const routes = {
"/": HomePage,
"/profile": ProfilePage,
"/login": LoginPage,
404: Error404Page,
};

const router = createRouter(routes);

export const App = {
render: () => {
const path = MyRouter.pathname;
const CurrentPage = router(path);
document.querySelector("#root").innerHTML = CurrentPage();
CurrentPage.init?.();
},
};
47 changes: 14 additions & 33 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
import { Error404Page } from "./pages/404";
import { HomePage } from "./pages/home/home-page";
import { LoginPage } from "./pages/login";
import { ProfilePage } from "./pages/profile";

function createRouter(routes) {
return (path) => {
const route = routes[path] || routes["404"];
return route();
};
}

const routes = {
"/": () => HomePage(),
"/profile": () => ProfilePage(),
"/login": () => LoginPage(),
404: () => Error404Page(),
};

const router = createRouter(routes);

const render = () => {
const path = window.location.pathname;
const user = localStorage.getItem("user");
if (!user && path === "/profile") {
document.body.innerHTML = router("/login");
return;
}
document.body.innerHTML = router(path);
};

window.addEventListener("popstate", render);
window.addEventListener("load", render);
import { App } from "./app";
import { Renderer } from "./Renderer";

// Q.왜 load로 이벤트 속성을 걸면 렌더링이 안되는거지?
document.addEventListener("DOMContentLoaded", () => {
App.render();
});
// NOTE : history.go() history.back() history.forward() 매소드 호출시 해당 이벤트 트리거 됨.
// 확인해보니 PopState는 뒤로 앞으로 가기를 담당
Renderer.onPopState(() => App.render());
Renderer.onPushState(() => App.render());
Renderer.onReplaceState(() => App.render());
Renderer.onHashChange(() => App.render());
Renderer.onATagClick(() => App.render());
4 changes: 3 additions & 1 deletion src/pages/404/404.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const Error404Page = () => {
const Error404Page = () => {
return `
<main class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="bg-white p-8 rounded-lg shadow-md w-full text-center" style="max-width: 480px">
Expand All @@ -15,3 +15,5 @@ export const Error404Page = () => {
</main>
`;
};

export default Error404Page;
1 change: 0 additions & 1 deletion src/pages/404/index.js

This file was deleted.

28 changes: 13 additions & 15 deletions src/pages/home/home-page.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
export const HomePage = () => {
import { Footer } from "../../shared/ui/footer";
import { Header } from "../../shared/ui/header";

const HomePage = () => {
return `
<div class="bg-gray-100 min-h-screen flex justify-center">
<div class="max-w-md w-full">
<header class="bg-blue-600 text-white p-4 sticky top-0">
<h1 class="text-2xl font-bold">항해플러스</h1>
</header>
<nav class="bg-white shadow-md p-2 sticky top-14">
<ul class="flex justify-around">
<li><a href="/" class="text-blue-600">홈</a></li>
<li><a href="/profile" class="text-gray-600">프로필</a></li>
<li><a href="#" id="logout" class="text-gray-600">로그아웃</a></li>
</ul>
</nav>
${Header()}
<main class="p-4">
<div class="mb-4 bg-white rounded-lg shadow p-4">
Expand Down Expand Up @@ -104,10 +97,15 @@ export const HomePage = () => {
</div>
</main>
<footer class="bg-gray-200 p-4 text-center">
<p>&copy; 2024 항해플러스. All rights reserved.</p>
</footer>
${Footer()}
</div>
</div>
`;
};

HomePage.init = () => {
Header.init?.();
Footer.init?.();
};

export default HomePage;
1 change: 0 additions & 1 deletion src/pages/home/index.js

This file was deleted.

1 change: 0 additions & 1 deletion src/pages/login/index.js

This file was deleted.

25 changes: 24 additions & 1 deletion src/pages/login/login-page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const LoginPage = () => {
import { userService } from "../../services/userService";
import { MyRouter } from "../../shared/router/router";

const LoginPage = () => {
return `
<main class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
Expand All @@ -23,3 +26,23 @@ export const LoginPage = () => {
</main>
`;
};

LoginPage.init = () => {
if (userService.isLogin()) {
MyRouter.push("/");
return;
}

const form = document.getElementById("login-form");
form.addEventListener("submit", (e) => {
e.preventDefault();

userService.login({
username: form.querySelector("input[type=text]").value,
});

MyRouter.push("/");
});
};

export default LoginPage;
1 change: 0 additions & 1 deletion src/pages/profile/index.js

This file was deleted.

59 changes: 36 additions & 23 deletions src/pages/profile/profile-page.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
export const ProfilePage = () => {
return `
<div id="root">
import { userService } from "../../services/userService";
import { MyRouter } from "../../shared/router/router";
import { Footer } from "../../shared/ui/footer";
import { Header } from "../../shared/ui/header";

export const ProfilePage = () => `
<div class="bg-gray-100 min-h-screen flex justify-center">
<div class="max-w-md w-full">
<header class="bg-blue-600 text-white p-4 sticky top-0">
<h1 class="text-2xl font-bold">항해플러스</h1>
</header>
<nav class="bg-white shadow-md p-2 sticky top-14">
<ul class="flex justify-around">
<li><a href="/" class="text-gray-600">홈</a></li>
<li><a href="/profile" class="text-blue-600">프로필</a></li>
<li><a href="#" id="logout" class="text-gray-600">로그아웃</a></li>
</ul>
</nav>
${Header()}
<main class="p-4">
<div class="bg-white p-8 rounded-lg shadow-md">
Expand All @@ -31,7 +25,7 @@ export const ProfilePage = () => {
type="text"
id="username"
name="username"
value="홍길동"
value="${userService.userProfile.username ?? ""}"
class="w-full p-2 border rounded"
/>
</div>
Expand All @@ -45,7 +39,7 @@ export const ProfilePage = () => {
type="email"
id="email"
name="email"
value="[email protected]"
value="${userService.userProfile.email ?? ""}"
class="w-full p-2 border rounded"
/>
</div>
Expand All @@ -60,9 +54,7 @@ export const ProfilePage = () => {
name="bio"
rows="4"
class="w-full p-2 border rounded"
>
안녕하세요, 항해플러스에서 열심히 공부하고 있는 홍길동입니다.</textarea
>
>${userService.userProfile.bio ?? ""}</textarea>
</div>
<button
type="submit"
Expand All @@ -74,11 +66,32 @@ export const ProfilePage = () => {
</div>
</main>
<footer class="bg-gray-200 p-4 text-center">
<p>&copy; 2024 항해플러스. All rights reserved.</p>
</footer>
${Footer()}
</div>
</div>
</div>
`;

ProfilePage.init = () => {
if (!userService.isLogin()) {
MyRouter.push("/login");
return;
}

const form = document.getElementById("profile-form");
const usernameInput = document.getElementById("username");
const emailInput = document.getElementById("email");
const bioInput = document.getElementById("bio");

form.addEventListener("submit", (e) => {
e.preventDefault();

const username = usernameInput.value;
const email = emailInput.value;
const bio = bioInput.value;

userService.updateProfile({ username, email, bio });
});

Header.init?.();
Footer.init?.();
};
43 changes: 43 additions & 0 deletions src/services/userService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { store } from "../store";

const DEFAULT_USER_INFO = {
username: "",
email: "",
bio: "",
};

// NOTE: 즉시 실행 함수에 정리해볼 필요가 있다. 함수를 실행하여 반환된 객체를 userSerive 변수에 할당한 것. 코드 은닉에 이점
export const userService = (() => {
return {
isLogin: () => {
return store.get("user") != null;
},
login: (user) => {
store.set(
"user",
{
...DEFAULT_USER_INFO,
...user,
},
{ persistent: true },
);
},
logout: () => {
store.remove("user");
},
updateProfile: (profile) => {
const user = store.get("user");
store.set(
"user",
{
...user,
...profile,
},
{ persistent: true },
);
},
get userProfile() {
return store.get("user") ?? DEFAULT_USER_INFO;
},
};
})();
40 changes: 27 additions & 13 deletions src/shared/router/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,39 @@
// 1. history.pushState() 스택에 추가
// 1. history.relaceState() 스택 최근 데이터로 대체

export const Router = {
initialized: false,
push: (url) => {
!Router.initialized && Router.init();
function preventDefaultATagLogic({ callback = () => {} }) {
document.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
const url = event.target.getAttribute("href");
MyRouter.push(url);

callback();
});
});
}

window.history.pushState(null, null, url);
export const MyRouter = {
callback: null,
push: (url) => {
window.history.pushState({}, "", url);
MyRouter.callback?.();
},
replace: (url) => {
!Router.initialized && Router.init();

window.history.replaceState(null, null, url);
replace: (url) => {
window.history.replaceState({}, "", url);
MyRouter.callback?.();
},
init: () => {
Router.initialized = true;
// Q: onPopState와 hashChange가 무엇이 다른거지?
window.onpopstate = (event) => {
console.log("URL 변경 감지:", event.state);

init: ({ callback = () => {} }) => {
MyRouter.callback = callback;
preventDefaultATagLogic({ callback });

window.onpopstate = () => {
callback();
};
},

get pathname() {
return window.location.pathname;
},
Expand Down
Loading

0 comments on commit 0c0104c

Please sign in to comment.