Skip to content

Commit

Permalink
Feat: 프레임워크 없이 SPA 만들기 | 기본 과제
Browse files Browse the repository at this point in the history
  • Loading branch information
Suyeon-B committed Dec 19, 2024
1 parent c408d16 commit fd54d47
Show file tree
Hide file tree
Showing 11 changed files with 424 additions and 240 deletions.
5 changes: 5 additions & 0 deletions src/components/layout/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const Footer = () => `
<footer class="bg-gray-200 p-4 text-center">
<p>&copy; 2024 항해플러스. All rights reserved.</p>
</footer>
`;
41 changes: 41 additions & 0 deletions src/components/layout/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { clearUser } from "@/state/handle-state";

const handleLogout = () => {
clearUser();
alert("로그아웃되었습니다.");
};

const addEventListeners = () => {
const logoutButton = document.getElementById("logoutButton");
if (logoutButton) {
logoutButton.addEventListener("click", (e) => {
e.preventDefault();
handleLogout();
});
}
};

export const Header = (isLoggedIn = false) => {
const content = `
<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>
${
isLoggedIn
? '<a href="/login" id="logoutButton" type="button" class="text-gray-600">로그아웃</a>'
: '<a href="/login" class="text-gray-600">로그인</a>'
}
</li>
</ul>
</nav>
`;

setTimeout(addEventListeners, 0);

return content;
};
17 changes: 17 additions & 0 deletions src/components/layout/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";

export const Layout = (content) => {
const userData = JSON.parse(localStorage.getItem("user"));
const isLoggedIn = userData !== null;

return `
<div class="bg-gray-100 min-h-screen flex justify-center">
<div class="max-w-md w-full">
${Header(isLoggedIn)}
${content}
${Footer()}
</div>
</div>
`;
};
59 changes: 59 additions & 0 deletions src/components/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { renderHome } from "@/pages/home";
import { renderLogin } from "@/pages/login";
import { render404 } from "@/pages/not-found";
import { renderProfile } from "@/pages/profile";

class Router {
constructor(routes = {}) {
this.routes = routes;
this.currentRoute = "";
}

initialize() {
this.addEventListeners();
this.navigateTo(location.pathname || "/");
}

addEventListeners() {
document.addEventListener("click", (e) => {
if (e.target.tagName === "A") {
e.preventDefault();
const path = e.target.getAttribute("href");
this.navigateTo(path);
}
});

window.addEventListener("popstate", () => {
this.navigateTo(location.pathname, false);
});
}

navigateTo(path, pushState = true) {
if (this.routes[path]) {
this.currentRoute = path;
if (pushState) {
history.pushState({}, "", path);
}
this.render();
} else {
console.error(`Route ${path} not found.`);
this.navigateTo("/404", pushState);
}
}

render() {
const component = this.routes[this.currentRoute] || this.routes["/"];
component();
}
}

export default Router;

const routes = {
"/": renderHome,
"/login": renderLogin,
"/profile": renderProfile,
"/404": render404,
};

export const router = new Router(routes);
242 changes: 2 additions & 240 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,241 +1,3 @@
const MainPage = () => `
<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>
import { router } from "@/components/router";

<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="#" class="text-gray-600">로그아웃</a></li>
</ul>
</nav>
<main class="p-4">
<div class="mb-4 bg-white rounded-lg shadow p-4">
<textarea class="w-full p-2 border rounded" placeholder="무슨 생각을 하고 계신가요?"></textarea>
<button class="mt-2 bg-blue-600 text-white px-4 py-2 rounded">게시</button>
</div>
<div class="space-y-4">
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center mb-2">
<img src="https://via.placeholder.com/40" alt="프로필" class="rounded-full mr-2">
<div>
<p class="font-bold">홍길동</p>
<p class="text-sm text-gray-500">5분 전</p>
</div>
</div>
<p>오늘 날씨가 정말 좋네요. 다들 좋은 하루 보내세요!</p>
<div class="mt-2 flex justify-between text-gray-500">
<button>좋아요</button>
<button>댓글</button>
<button>공유</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center mb-2">
<img src="https://via.placeholder.com/40" alt="프로필" class="rounded-full mr-2">
<div>
<p class="font-bold">김철수</p>
<p class="text-sm text-gray-500">15분 전</p>
</div>
</div>
<p>새로운 프로젝트를 시작했어요. 열심히 코딩 중입니다!</p>
<div class="mt-2 flex justify-between text-gray-500">
<button>좋아요</button>
<button>댓글</button>
<button>공유</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center mb-2">
<img src="https://via.placeholder.com/40" alt="프로필" class="rounded-full mr-2">
<div>
<p class="font-bold">이영희</p>
<p class="text-sm text-gray-500">30분 전</p>
</div>
</div>
<p>오늘 점심 메뉴 추천 받습니다. 뭐가 좋을까요?</p>
<div class="mt-2 flex justify-between text-gray-500">
<button>좋아요</button>
<button>댓글</button>
<button>공유</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center mb-2">
<img src="https://via.placeholder.com/40" alt="프로필" class="rounded-full mr-2">
<div>
<p class="font-bold">박민수</p>
<p class="text-sm text-gray-500">1시간 전</p>
</div>
</div>
<p>주말에 등산 가실 분 계신가요? 함께 가요!</p>
<div class="mt-2 flex justify-between text-gray-500">
<button>좋아요</button>
<button>댓글</button>
<button>공유</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center mb-2">
<img src="https://via.placeholder.com/40" alt="프로필" class="rounded-full mr-2">
<div>
<p class="font-bold">정수연</p>
<p class="text-sm text-gray-500">2시간 전</p>
</div>
</div>
<p>새로 나온 영화 재미있대요. 같이 보러 갈 사람?</p>
<div class="mt-2 flex justify-between text-gray-500">
<button>좋아요</button>
<button>댓글</button>
<button>공유</button>
</div>
</div>
</div>
</main>
<footer class="bg-gray-200 p-4 text-center">
<p>&copy; 2024 항해플러스. All rights reserved.</p>
</footer>
</div>
</div>
`;

const ErrorPage = () => `
<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">
<h1 class="text-2xl font-bold text-blue-600 mb-4">항해플러스</h1>
<p class="text-4xl font-bold text-gray-800 mb-4">404</p>
<p class="text-xl text-gray-600 mb-8">페이지를 찾을 수 없습니다</p>
<p class="text-gray-600 mb-8">
요청하신 페이지가 존재하지 않거나 이동되었을 수 있습니다.
</p>
<a href="/" class="bg-blue-600 text-white px-4 py-2 rounded font-bold">
홈으로 돌아가기
</a>
</div>
</main>
`;

const LoginPage = () => `
<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">
<h1 class="text-2xl font-bold text-center text-blue-600 mb-8">항해플러스</h1>
<form>
<div class="mb-4">
<input type="text" placeholder="이메일 또는 전화번호" class="w-full p-2 border rounded">
</div>
<div class="mb-6">
<input type="password" placeholder="비밀번호" class="w-full p-2 border rounded">
</div>
<button type="submit" class="w-full bg-blue-600 text-white p-2 rounded font-bold">로그인</button>
</form>
<div class="mt-4 text-center">
<a href="#" class="text-blue-600 text-sm">비밀번호를 잊으셨나요?</a>
</div>
<hr class="my-6">
<div class="text-center">
<button class="bg-green-500 text-white px-4 py-2 rounded font-bold">새 계정 만들기</button>
</div>
</div>
</main>
`;

const ProfilePage = () => `
<div id="root">
<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="#" class="text-gray-600">로그아웃</a></li>
</ul>
</nav>
<main class="p-4">
<div class="bg-white p-8 rounded-lg shadow-md">
<h2 class="text-2xl font-bold text-center text-blue-600 mb-8">
내 프로필
</h2>
<form>
<div class="mb-4">
<label
for="username"
class="block text-gray-700 text-sm font-bold mb-2"
>사용자 이름</label
>
<input
type="text"
id="username"
name="username"
value="홍길동"
class="w-full p-2 border rounded"
/>
</div>
<div class="mb-4">
<label
for="email"
class="block text-gray-700 text-sm font-bold mb-2"
>이메일</label
>
<input
type="email"
id="email"
name="email"
value="[email protected]"
class="w-full p-2 border rounded"
/>
</div>
<div class="mb-6">
<label
for="bio"
class="block text-gray-700 text-sm font-bold mb-2"
>자기소개</label
>
<textarea
id="bio"
name="bio"
rows="4"
class="w-full p-2 border rounded"
>
안녕하세요, 항해플러스에서 열심히 공부하고 있는 홍길동입니다.</textarea
>
</div>
<button
type="submit"
class="w-full bg-blue-600 text-white p-2 rounded font-bold"
>
프로필 업데이트
</button>
</form>
</div>
</main>
<footer class="bg-gray-200 p-4 text-center">
<p>&copy; 2024 항해플러스. All rights reserved.</p>
</footer>
</div>
</div>
</div>
`;

document.body.innerHTML = `
${MainPage()}
${ProfilePage()}
${LoginPage()}
${ErrorPage()}
`;
router.initialize();
Loading

0 comments on commit fd54d47

Please sign in to comment.