Skip to content

Commit 4e150b5

Browse files
authored
Merge pull request #58 from codingbaraGo/enhance/view/#56 (see PR #57)
develop <- enhance/view/#56
2 parents a4d038f + b04c211 commit 4e150b5

10 files changed

Lines changed: 201 additions & 83 deletions

File tree

src/main/java/app/handler/RegisterWithPost.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import web.filter.authentication.UserRole;
1212
import web.handler.SingleArgHandler;
1313
import web.response.HandlerResponse;
14-
import web.response.StaticViewResponse;
14+
import web.response.RedirectResponse;
1515

1616
public class RegisterWithPost extends SingleArgHandler<QueryParameters> {
1717
private static final Logger log = LoggerFactory.getLogger(RegisterWithPost.class);
@@ -27,6 +27,6 @@ public HandlerResponse handle(QueryParameters params) {
2727
String password = params.getQueryValue("password").orElseThrow(()-> new ServiceException(ErrorCode.MISSING_REGISTER_TOKEN, "password required"));
2828
Database.addUser(new User(password, nickname, email, UserRole.MEMBER.toString()));
2929
log.info("Registered - password:{}, nickname:{}, email:{}", password, nickname, email);
30-
return StaticViewResponse.of("/login");
30+
return RedirectResponse.to("/login");
3131
}
3232
}

src/main/java/config/AppConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import web.dispatch.argument.resolver.HttpRequestResolver;
1919
import web.dispatch.argument.resolver.QueryParamsResolver;
2020
import web.filter.*;
21+
import web.handler.DefaultViewHandler;
2122
import web.handler.StaticContentHandler;
2223
import web.handler.WebHandler;
2324
import web.renderer.DynamicViewRenderer;
@@ -88,7 +89,8 @@ public List<WebHandler> webHandlerList() {
8889
registerWithPost(),
8990
loginWithPost(),
9091
logoutWithPost(),
91-
homeHandler())
92+
homeHandler(),
93+
defaultViewHandler())
9294
);
9395
}
9496

@@ -99,6 +101,10 @@ public StaticContentHandler staticContentHandler() {
99101
);
100102
}
101103

104+
public DefaultViewHandler defaultViewHandler(){
105+
return getOrCreate("defaultViewHandler", DefaultViewHandler::new);
106+
}
107+
102108
public RegisterWithGet registerWithGet() {
103109
return getOrCreate(
104110
"registerWithGet",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package web.handler;
2+
3+
import config.VariableConfig;
4+
import exception.ErrorException;
5+
import http.HttpMethod;
6+
import http.HttpStatus;
7+
import http.request.HttpRequest;
8+
import web.response.DynamicViewResponse;
9+
import web.response.HandlerResponse;
10+
11+
import java.io.File;
12+
import java.util.List;
13+
14+
public class DefaultViewHandler implements DefaultHandler{
15+
private final List<String> roots = VariableConfig.DYNAMIC_RESOURCE_ROOTS;
16+
private final HttpMethod method = HttpMethod.GET;
17+
18+
@Override
19+
public String getPath() {
20+
throw new ErrorException("DynamicViewHandler::getPath should not be called");
21+
}
22+
23+
@Override
24+
public HttpMethod getMethod() {
25+
return this.method;
26+
}
27+
@Override
28+
public boolean checkEndpoint(HttpMethod method, String path) {
29+
if(!method.equals(this.method)) return false;
30+
return roots.stream().anyMatch(root ->{
31+
File requestedFile = new File(root + path);
32+
String indexFilePath = path + (path.endsWith("/") ? "index.html" : "/index.html");
33+
File indexFile = new File(root + indexFilePath);
34+
return (requestedFile.exists() && requestedFile.isFile()) || (indexFile.exists() && indexFile.isFile());
35+
});
36+
}
37+
38+
@Override
39+
public HandlerResponse handle(HttpRequest request) {
40+
String path = request.getPath() + (request.getPath().endsWith("/") ? "index.html" : "/index.html");
41+
return DynamicViewResponse.of(HttpStatus.OK, path);
42+
}
43+
}

src/main/resources/static/article/index.html renamed to src/main/resources/templates/article/index.html

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,12 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<link href="../reset.css" rel="stylesheet" />
7-
<link href="../global.css" rel="stylesheet" />
6+
<link href="../../static/reset.css" rel="stylesheet" />
7+
<link href="../../static/global.css" rel="stylesheet" />
88
</head>
99
<body>
1010
<div class="container">
11-
<header class="header">
12-
<a href="/main"><img src="../img/signiture.svg" /></a>
13-
<ul class="header__menu">
14-
<li class="header__menu__item">
15-
<a class="btn btn_contained btn_size_s" href="/article">글쓰기</a>
16-
</li>
17-
<li class="header__menu__item">
18-
<button id="logout-btn" class="btn btn_ghost btn_size_s">
19-
로그아웃
20-
</button>
21-
</li>
22-
</ul>
23-
</header>
11+
{{> /layout/header.html}}
2412
<div class="page">
2513
<h2 class="page-title">게시글 작성</h2>
2614
<form class="form">

src/main/resources/static/comment/index.html renamed to src/main/resources/templates/comment/index.html

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,12 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<link href="../reset.css" rel="stylesheet" />
7-
<link href="../global.css" rel="stylesheet" />
6+
<link href="../../static/reset.css" rel="stylesheet" />
7+
<link href="../../static/global.css" rel="stylesheet" />
88
</head>
99
<body>
1010
<div class="container">
11-
<header class="header">
12-
<a href="/main"><img src="../img/signiture.svg" /></a>
13-
<ul class="header__menu">
14-
<li class="header__menu__item">
15-
<a class="btn btn_contained btn_size_s" href="/article">글쓰기</a>
16-
</li>
17-
<li class="header__menu__item">
18-
<button id="logout-btn" class="btn btn_ghost btn_size_s">
19-
로그아웃
20-
</button>
21-
</li>
22-
</ul>
23-
</header>
11+
{{> /layout/header.html}}
2412
<div class="page">
2513
<h2 class="page-title">댓글 작성</h2>
2614
<form class="form">
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<style>
2+
.error-popup-wrapper {
3+
position: fixed;
4+
top: 16px;
5+
right: 50px;
6+
z-index: 1000;
7+
}
8+
.error-popup {
9+
display: none;
10+
background: #ffffff;
11+
border-radius: 8px;
12+
padding: 14px 18px;
13+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
14+
min-width: 260px;
15+
}
16+
.error-popup__title {
17+
font-size: 14px;
18+
font-weight: 600;
19+
margin-bottom: 6px;
20+
color: #d32f2f;
21+
}
22+
.error-popup__message {
23+
font-size: 13px;
24+
margin-bottom: 10px;
25+
}
26+
.error-popup__actions {
27+
display: flex;
28+
justify-content: flex-end;
29+
}
30+
.error-popup__ok-btn {
31+
cursor: pointer;
32+
padding: 4px 12px;
33+
font-size: 13px;
34+
border-radius: 4px;
35+
border: none;
36+
}
37+
</style>
38+
39+
<div class="error-popup-wrapper">
40+
<div id="error-popup" class="error-popup">
41+
<div class="error-popup__title">알림</div>
42+
<div id="error-popup-message" class="error-popup__message"></div>
43+
<div class="error-popup__actions">
44+
<button id="error-popup-ok" class="error-popup__ok-btn" type="button">
45+
OK
46+
</button>
47+
</div>
48+
</div>
49+
</div>
50+
51+
<script>
52+
(function () {
53+
const popup = document.getElementById("error-popup");
54+
const msgEl = document.getElementById("error-popup-message");
55+
const okBtn = document.getElementById("error-popup-ok");
56+
57+
window.showErrorPopup = function (message, title) {
58+
const titleEl = popup.querySelector(".error-popup__title");
59+
msgEl.textContent = message || "오류가 발생했습니다.";
60+
if (title) titleEl.textContent = title;
61+
popup.style.display = "block";
62+
};
63+
64+
window.hideErrorPopup = function () {
65+
popup.style.display = "none";
66+
};
67+
68+
okBtn.addEventListener("click", () => window.hideErrorPopup());
69+
70+
// ---- 공통 submit 가로채기 (B안: data-ajax 있는 폼만) ----
71+
document.addEventListener("submit", async (e) => {
72+
const form = e.target;
73+
if (!(form instanceof HTMLFormElement)) return;
74+
75+
// 안전하게: data-ajax="true"인 폼만 가로챔
76+
if (form.dataset.ajax !== "true") return;
77+
78+
e.preventDefault();
79+
80+
// multipart/form-data는 여기서 처리 안 함(파일 업로드 등)
81+
const enctype = (form.enctype || "").toLowerCase();
82+
if (enctype.includes("multipart/form-data")) {
83+
window.showErrorPopup("파일 업로드 폼은 지원하지 않습니다.", "오류");
84+
return;
85+
}
86+
87+
const body = new URLSearchParams(new FormData(form));
88+
89+
try {
90+
const res = await fetch(form.action, {
91+
method: (form.method || "POST").toUpperCase(),
92+
headers: {
93+
"Accept": "application/json",
94+
"Content-Type": "application/x-www-form-urlencoded",
95+
},
96+
body,
97+
credentials: "same-origin",
98+
});
99+
100+
if (res.redirected) {
101+
window.location.href = res.url;
102+
return;
103+
} else if (res.ok) {
104+
window.location.href = "/";
105+
return;
106+
}
107+
108+
let err = null;
109+
try { err = await res.json(); } catch {}
110+
111+
const message =
112+
(err && (err.message || err.errorMessage)) ||
113+
"요청 처리에 실패했습니다.";
114+
115+
window.showErrorPopup(message, "요청 실패");
116+
} catch (networkError) {
117+
window.showErrorPopup("서버와 통신 중 문제가 발생했습니다.", "오류");
118+
}
119+
}, true); // capture로 잡으면 더 안정적으로 submit을 가로챌 수 있음
120+
})();
121+
</script>

src/main/resources/templates/layout/header.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
<ul class="header__menu">
55
{{#if1 isLoginUser}}
66
<li class="header__menu__item">
7-
<span class="header__menu__nickname">닉네임: {{userNickname}}</span>
7+
<a class="header__menu__nickname" href="/mypage">닉네임: {{userNickname}}</a>
8+
</li>
9+
<li class="header__menu__item">
10+
<a class="btn btn_contained btn_size_s" href="/article">글쓰기</a>
811
</li>
912
<form action="/user/logout" method="POST">
1013
<button type="submit">로그아웃</button>

src/main/resources/static/login/index.html renamed to src/main/resources/templates/login/index.html

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,15 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<link href="../reset.css" rel="stylesheet" />
7-
<link href="../global.css" rel="stylesheet" />
6+
<link href="../../static/reset.css" rel="stylesheet" />
7+
<link href="../../static/global.css" rel="stylesheet" />
88
</head>
99
<body>
1010
<div class="container">
11-
<header class="header">
12-
<a href="/"><img src="../img/signiture.svg" /></a>
13-
<ul class="header__menu">
14-
<li class="header__menu__item">
15-
<a class="btn btn_contained btn_size_s" href="/login">로그인</a>
16-
</li>
17-
<li class="header__menu__item">
18-
<a class="btn btn_ghost btn_size_s" href="/registration">
19-
회원 가입
20-
</a>
21-
</li>
22-
</ul>
23-
</header>
11+
{{> /layout/header.html}}
2412
<div class="page">
2513
<h2 class="page-title">로그인</h2>
26-
<form class="form" action="/user/login" method="post">
14+
<form class="form" action="/user/login" method="post" data-ajax="true">
2715
<div class="textfield textfield_size_s">
2816
<p class="title_textfield">이메일</p>
2917
<input
@@ -54,5 +42,6 @@ <h2 class="page-title">로그인</h2>
5442
</form>
5543
</div>
5644
</div>
45+
{{> /layout/error-popup.html}}
5746
</body>
5847
</html>

src/main/resources/static/mypage/index.html renamed to src/main/resources/templates/mypage/index.html

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,13 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<link href="../reset.css" rel="stylesheet" />
7-
<link href="../global.css" rel="stylesheet" />
6+
<link href="../../static/reset.css" rel="stylesheet" />
7+
<link href="../../static/global.css" rel="stylesheet" />
88
</head>
99
<body>
1010
<div class="container">
11-
<header class="header">
12-
<a href="/"><img src="../img/signiture.svg" /></a>
13-
<ul class="header__menu">
14-
<li class="header__menu__item">
15-
<a class="btn btn_contained btn_size_s" href="/article">글쓰기</a>
16-
</li>
17-
<li class="header__menu__item">
18-
<button id="logout-btn" class="btn btn_ghost btn_size_s">
19-
로그아웃
20-
</button>
21-
</li>
22-
</ul>
23-
</header>
11+
{{> /layout/header.html}}
12+
2413
<div class="page">
2514
<h2 class="page-title">마이페이지</h2>
2615

@@ -74,8 +63,10 @@ <h2 class="page-title">마이페이지</h2>
7463
변경사항 저장
7564
</button>
7665

77-
</form>
78-
</div>
66+
</form>
67+
</div>
68+
</div>
7969
</div>
70+
{{> /layout/error-popup.html}}
8071
</body>
8172
</html>

src/main/resources/static/registration/index.html renamed to src/main/resources/templates/registration/index.html

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,15 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<link href="../reset.css" rel="stylesheet" />
7-
<link href="../global.css" rel="stylesheet" />
6+
<link href="../../static/reset.css" rel="stylesheet" />
7+
<link href="../../static/global.css" rel="stylesheet" />
88
</head>
99
<body>
1010
<div class="container">
11-
<header class="header">
12-
<a href="/"><img src="../img/signiture.svg" /></a>
13-
<ul class="header__menu">
14-
<li class="header__menu__item">
15-
<a class="btn btn_contained btn_size_s" href="/login">로그인</a>
16-
</li>
17-
<li class="header__menu__item">
18-
<a class="btn btn_ghost btn_size_s" href="/registration">
19-
회원 가입
20-
</a>
21-
</li>
22-
</ul>
23-
</header>
11+
{{> /layout/header.html}}
2412
<div class="page">
2513
<h2 class="page-title">회원가입</h2>
26-
<form class="form" action="/user/create" method="post">
14+
<form class="form" action="/user/create" method="post" data-ajax="true">
2715
<div class="textfield textfield_size_s">
2816
<p class="title_textfield">이메일</p>
2917
<input
@@ -63,5 +51,6 @@ <h2 class="page-title">회원가입</h2>
6351
</form>
6452
</div>
6553
</div>
54+
{{> /layout/error-popup.html}}
6655
</body>
6756
</html>

0 commit comments

Comments
 (0)