From 41def25709181880ba09dc6349c8a7202ed745ba Mon Sep 17 00:00:00 2001 From: pricelees Date: Fri, 25 Jul 2025 18:08:47 +0900 Subject: [PATCH] =?UTF-8?q?remove:=20=EB=AA=A8=EB=93=A0=20Thymeleaf=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 5 +- .../kotlin/roomescape/view/PageController.kt | 48 --- src/main/resources/static/css/reservation.css | 15 - src/main/resources/static/css/style.css | 62 ---- src/main/resources/static/css/toss-style.css | 132 --------- src/main/resources/static/favicon.ico | Bin 1492 -> 0 bytes .../resources/static/image/admin-logo.png | Bin 4640 -> 0 bytes .../static/image/default-profile.png | Bin 20300 -> 0 bytes src/main/resources/static/js/ranking.js | 45 --- .../resources/static/js/reservation-mine.js | 57 ---- .../resources/static/js/reservation-new.js | 194 ------------- .../static/js/reservation-with-member.js | 250 ---------------- src/main/resources/static/js/reservation.js | 179 ------------ src/main/resources/static/js/scripts.js | 0 src/main/resources/static/js/theme.js | 136 --------- src/main/resources/static/js/time.js | 135 --------- .../resources/static/js/user-reservation.js | 273 ------------------ src/main/resources/static/js/user-scripts.js | 152 ---------- src/main/resources/static/js/waiting.js | 69 ----- src/main/resources/templates/admin/index.html | 61 ---- .../templates/admin/reservation-new.html | 111 ------- .../templates/admin/reservation.html | 63 ---- src/main/resources/templates/admin/theme.html | 80 ----- src/main/resources/templates/admin/time.html | 78 ----- .../resources/templates/admin/waiting.html | 77 ----- src/main/resources/templates/index.html | 56 ---- src/main/resources/templates/login.html | 64 ---- .../resources/templates/reservation-mine.html | 70 ----- src/main/resources/templates/reservation.html | 103 ------- src/main/resources/templates/signup.html | 67 ----- .../roomescape/view/PageControllerTest.kt | 133 --------- 31 files changed, 2 insertions(+), 2713 deletions(-) delete mode 100644 src/main/kotlin/roomescape/view/PageController.kt delete mode 100644 src/main/resources/static/css/reservation.css delete mode 100644 src/main/resources/static/css/style.css delete mode 100644 src/main/resources/static/css/toss-style.css delete mode 100644 src/main/resources/static/favicon.ico delete mode 100644 src/main/resources/static/image/admin-logo.png delete mode 100644 src/main/resources/static/image/default-profile.png delete mode 100644 src/main/resources/static/js/ranking.js delete mode 100644 src/main/resources/static/js/reservation-mine.js delete mode 100644 src/main/resources/static/js/reservation-new.js delete mode 100644 src/main/resources/static/js/reservation-with-member.js delete mode 100644 src/main/resources/static/js/reservation.js delete mode 100644 src/main/resources/static/js/scripts.js delete mode 100644 src/main/resources/static/js/theme.js delete mode 100644 src/main/resources/static/js/time.js delete mode 100644 src/main/resources/static/js/user-reservation.js delete mode 100644 src/main/resources/static/js/user-scripts.js delete mode 100644 src/main/resources/static/js/waiting.js delete mode 100644 src/main/resources/templates/admin/index.html delete mode 100644 src/main/resources/templates/admin/reservation-new.html delete mode 100644 src/main/resources/templates/admin/reservation.html delete mode 100644 src/main/resources/templates/admin/theme.html delete mode 100644 src/main/resources/templates/admin/time.html delete mode 100644 src/main/resources/templates/admin/waiting.html delete mode 100644 src/main/resources/templates/index.html delete mode 100644 src/main/resources/templates/login.html delete mode 100644 src/main/resources/templates/reservation-mine.html delete mode 100644 src/main/resources/templates/reservation.html delete mode 100644 src/main/resources/templates/signup.html delete mode 100644 src/test/kotlin/roomescape/view/PageControllerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5b4be923..20f59d2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,6 @@ repositories { dependencies { // Spring implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-validation") @@ -71,8 +70,8 @@ tasks.withType { tasks.withType { compilerOptions { freeCompilerArgs.addAll( - "-Xjsr305=strict", - "-Xannotation-default-target=param-property" + "-Xjsr305=strict", + "-Xannotation-default-target=param-property" ) jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } diff --git a/src/main/kotlin/roomescape/view/PageController.kt b/src/main/kotlin/roomescape/view/PageController.kt deleted file mode 100644 index 10425b88..00000000 --- a/src/main/kotlin/roomescape/view/PageController.kt +++ /dev/null @@ -1,48 +0,0 @@ -package roomescape.view - -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import roomescape.auth.web.support.Admin -import roomescape.auth.web.support.LoginRequired - -@Controller -class AuthPageController { - - @GetMapping("/login") - fun showLoginPage(): String = "login" -} - -@Controller -@RequestMapping("/admin") -class AdminPageController { - - @Admin - @GetMapping - fun showIndexPage() = "admin/index" - - @Admin - @GetMapping("/{page}") - fun showAdminSubPage(@PathVariable page: String) = when (page) { - "reservation" -> "admin/reservation-new" - "time" -> "admin/time" - "theme" -> "admin/theme" - "waiting" -> "admin/waiting" - else -> "admin/index" - } -} - -@Controller -class ClientPageController { - @GetMapping("/") - fun showPopularThemePage(): String = "index" - - @LoginRequired - @GetMapping("/reservation") - fun showReservationPage(): String = "reservation" - - @LoginRequired - @GetMapping("/reservation-mine") - fun showReservationMinePage(): String = "reservation-mine" -} diff --git a/src/main/resources/static/css/reservation.css b/src/main/resources/static/css/reservation.css deleted file mode 100644 index de9666b7..00000000 --- a/src/main/resources/static/css/reservation.css +++ /dev/null @@ -1,15 +0,0 @@ -.disabled { - pointer-events: none; - opacity: 0.6; -} - -#theme-slots .theme-slot.active, #time-slots .time-slot.active { - background-color: #0a3711 !important; - color: white; -} - -#time-slots .time-slot.disabled { - background-color: #cccccc; - color: #666666; - cursor: not-allowed; -} diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css deleted file mode 100644 index 81506574..00000000 --- a/src/main/resources/static/css/style.css +++ /dev/null @@ -1,62 +0,0 @@ -.profile-image { - height: 30px; - width: 30px; - border-radius: 50%; - margin-right: 5px; /* 이름과의 간격 조정 */ -} - -.nav-item .dropdown-toggle::after { - display: none; /* 드롭다운 화살표 제거 */ -} - -.nav-item { - margin-right: 10px; /* 네비게이션 간격 조정 */ -} - -.content-container { - width: 70%; - margin: 50px auto; -} - -.content-container-title { - text-align: center; - margin-bottom: 30px; -} - -.form-group input { - width: 100%; - padding: 10px; - margin: 10px 0; - border-radius: 5px; - border: 1px solid #ddd; -} - -/* Solid 버튼 */ -.btn-custom { - background-color: #0a3711; /* 버튼 기본 배경색 */ - color: white; /* 버튼 텍스트 색상 */ - border: 1px solid #0a3711; /* 테두리 색상 일치 */ -} - -.btn-custom:hover { - background-color: #083d0f; /* 호버 상태에서의 배경색 */ - color: white; /* 호버 상태에서의 텍스트 색상 */ - border: 1px solid #083d0f; /* 호버 상태에서의 테두리 색상 */ -} - -/* Outline 버튼 */ -.btn-outline-custom { - background-color: transparent; /* 버튼 기본 배경색 투명 */ - color: #0a3711; /* 버튼 텍스트 색상 */ - border: 1px solid #0a3711; /* 테두리 색상 */ -} - -.btn-outline-custom:hover { - background-color: #0a3711; /* 호버 상태에서의 배경색 */ - color: white; /* 호버 상태에서의 텍스트 색상 */ - border: 1px solid #0a3711; /* 호버 상태에서의 테두리 색상 유지 */ -} - -.cursor-pointer { - cursor: pointer; -} diff --git a/src/main/resources/static/css/toss-style.css b/src/main/resources/static/css/toss-style.css deleted file mode 100644 index a79080d5..00000000 --- a/src/main/resources/static/css/toss-style.css +++ /dev/null @@ -1,132 +0,0 @@ -.w-100 { - width: 100%; -} - -.h-100 { - height: 100%; -} - -a { - text-decoration: none; - text-align: center; -} - -.wrapper { - display: flex; - flex-direction: column; - align-items: center; - padding: 24px; - overflow: auto; -} - -.max-w-540 { - max-width: 540px; -} - -.btn-wrapper { - padding: 0 24px; -} - -.btn { - padding: 11px 22px; - border: none; - border-radius: 8px; - - background-color: #f2f4f6; - color: #4e5968; - font-weight: 600; - font-size: 17px; - cursor: pointer; -} - -.btn.primary { - background-color: #3282f6; - color: #f9fcff; -} - -.text-center { - text-align: center; -} - -.flex { - display: flex; -} - -.flex-column { - display: flex; - flex-direction: column; -} - -.justify-center { - justify-content: center; -} - -.justify-between { - justify-content: space-between; -} - -.align-center { - align-items: center; -} - -.confirm-loading { - margin-top: 72px; - height: 400px; - justify-content: space-between; -} - - -.confirm-success { - display: none; - margin-top: 72px; -} - -.button-group { - margin-top: 32px; - display: flex; - flex-direction: column; - justify-content: center; - gap: 16px; -} - -.title { - margin-top: 32px; - margin-bottom: 0; - color: #191f28; - font-weight: bold; - font-size: 24px; -} - -.description { - margin-top: 8px; - color: #4e5968; - font-size: 17px; - font-weight: 500; -} - -.response-section { - margin-top: 60px; - display: flex; - flex-direction: column; - gap: 16px; - font-size: 20px; -} - -.response-section .response-label { - font-weight: 600; - color: #333d48; - font-size: 17px; -} - -.response-section .response-text { - font-weight: 500; - color: #4e5968; - font-size: 17px; - padding-left: 16px; - word-break: break-word; - text-align: right; -} - -.color-grey { - color: #b0b8c1; -} \ No newline at end of file diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico deleted file mode 100644 index 79a18a8f24b373b82ff960cabd2bbe70948709d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1492 zcmV;_1uOcAP)Px)j!8s8R9Hu?mTPQNWf;f*PutUbw{8Qr)s3xVI4(0>Ld(UNLCi4Fv7iy72}I?C zAQ2VgH4shwpa~d7~*aV3Vlx(mIFvbR~bZt-D^_#7y=h`)mM=K1Y}}&|@A6F6|l@fVH?*gQx+(gzOThSlt9; zo5<-ol+IrfKt67s-wj}`D@!ysT7~6oAoM z9|j{A9}=+h;3af)2T~Fl4Qf2+D#85O`Po4b^FVKWYAi%5K&$H@K&z#w;fRQ@4|mI7 zJAH};dAS^-0>kzG5YDuDQCVijqPbZDjA-K!IXr6;iIV_Wi|Zv2?aA(w?$e5~b>#GX z5%Z1%u$JXTAk{?xIZB~}ynr+3y|9-WQBr6~F$skQoM;}9%XMoof?+{KL9P~4%gu1k z%0tlveay!PCRUO2SfBLbE5rflFv> z_rYw`V9SOoh~)PigNen&&eq!?h}Xt*X2VgCMh#HL%k$nC)T)PwwJkP-H~SK2LSmHY z9Y2C?T~L|yFwL}sxCc@xS$Z(|b7CUw2cUS@3qwH!{=O_TamD$#W68&nrd|vTMj(g` zxn>R(rA9bs<);j%4977*4)>%Oz;OUel^mELVyJnfwMeT$e_<&O9O*$pE(eZN!Rs5w zl|CM#$l!P+4{KIVhEAK+HQhuG_Y*OIw*YKa1c*d2)OvRcS{BX#sge@}_$W@EzKP?f zZs7G7Dp66YI8euif14ccZ6g4uSJed?x^L zC6#>$xCRUrHb7($0z9I=AOz_d z3PS3X#AU3uLzA24Bp8m8(_Jr@HRioTGMF8LS@^=~VaW(*wjm0?)k`@#e1K57}1FzANlYT|fD!uf6I=O;WbP*uM zsEIz3@J!WqrP=T+n7Emo$9@^_L()zPZ$sHOB6>5^DQHV3LSI%W%?bg;E`Xs4BEF#o zo->(zQ`#`mfqejt&szQ!2+xlbNK~8k@0eyz@qklx)4Zd>Y?5XaVWNR=yS-jy;zlBJ u0?+~Ik|u`8Sc}MTqTr35^Y@xG(f)rF^ato2OBhf90000q=XOevX>8Eq`-_69)W4q!^wl5k7evji?wcnK4g zJr}ls-s}rEmHUSu4l%-cLwyOLbGe&J6yGAqdw`A*Ei3Q?HRc~neY*!kwA=91ivT3u zlX<-=SAiU9)sylAAOBtD!I7<2{WJ)%3!u5H?KsDnuPK}sq1%!u>qiF*p9cI0{nj0^ z3c&5oyd06Q0NFVFD{`mto!5+R+WB}K{_6n~m*aF#_A*4iOH7pyAm|I#RQg&^ZGT2w zF~m(AEr6u+xoZ@CcMQG*kUwJlvx|Mh_r@Jb)I{C_xINh`>#Ons^eOmq`$B99B5eWO zeD>|Y^-=nqC0k9?cz)mA`*y}1NK{1D0=Qkvx`59{$v(q1=^$VR|^8GnvPBt$9I%B#vMx(M56T4cx64XDhgj=pu+m*?zV=Bw-&4mpykx; zvsC1rw*7=m%80o(?jWMTx&VAfuK*IZ{aE1KC;Kh)8mj_m?Ok>v$SZC7u!`|N5O)Z+ zu_^#h{lByA!!j?M?|lJ#mD5QQi+F)RvTE{N!h zY}t+!VGC;lP?a-m*^U?y$D1$D@@Lo)z{xZL&$41W27J*|;*P-AQA~J8^5?T>}kN-3qcH=^or^g+GEvyJYe2s0Dj|~KwSI;vUD*{lFxvt}{ zQ?xbi2y9_T0BF*N-58l~&vOtvKLKJ!ZP<;Gy>SO%3rj5kdu-T^4OtYxEYTfy?8b!h zhqyzqg%trL#oJ=TZhYu(-Bv7vQ_)`d1Q3O&`;wI!X{P)P&&5ouYx>i(}!iQp3luc*lqzf;Hhx6 zRUh_vw3ur9O58!%CW_s`#?5E?K)NIF6&k9%x-?k0Iqo=Y6UCZ+U&VPR4U~&VkvkVc zGB2(U(-VmxtpHBnl$iwNqR%v|iz^sQk?lMf42|DXC<1 z+qO2~CA9J?pl)9jiE>%7fB5@x#}OrwCy7dRW>-jpp99B4?Rz>z^_tPa;Y;I=BWfbw zxLMj<*ha$X0e&5|?;uq9cF~%5TC6ZCV zRE#|~R;;Wke=|Aa3KMB!qZZ>s7(Kh#U-(hPe(Q?Za|HvqJ#D9;<^~`ifxB@*zK}|o z5ANEyYu>K)tCqSD008T7+-^ea&0(l`d|4iuuhkfFeP70m!(PwRQRC=7 zzoH}M_K_X@nEi3b3O3VSY^kmlhDt9o4jA?>AnB4Do5C3Y| z>e95j5P;jYtP96KOOZ1~bq$cRCcEw0L?v_l?Glb%IhY+>5=0h{20|6i>cushCN-J&SzIE{7)@51pVFUz_8UE zg;5JY+zp>?{9vO;S&<1{bHEH5+sNbRPk#<~Df`P_N3O|L?z z)9j)0wPxjALjyyhCbm-56`{0m;~Wb#o&c7tX*=HW%;!K~Y|BTjsPf6u;Lzno8^(oW z!!YBRaO?yTz5q_9$!*Pj08cLinnSlA9@m!!3zu88A4m+=3Ze`-=P!xEG9FeMqXU%8@lIk zdvh1S+9O)rQsp+_tU23=9J#VMF!Z6I(o{b8DnU0)YeKn4h3iU{!fnmT%+W@C70mXy?^G{4_`DY^llS*d42AmUiRJKv%5+HDD-k_fX($Pf} zE6+>ibLSoSPt^i&d$R8r<<)j#vWebN8W`#iDoyp~)+qdSlo7>^px4m3+=VIy(A74{+Z!IRFfzfpY>20M{hsl4%8y?#aHI2JG}rBJk9b zRL`<5LV9D@s3`i1KmlYsvu&z;I?8}TN2DB7YfILB8l4xw+K0d($B?tv)iHN{yc_=v#^I-O9KTJ!?tbQQD6S~Hw3^BX7#$e8U47{WY#mj1zwxEJ zG&r;YrkDpv<+I-qc%3EZaZn=RJgvBK*Q3*2)Y`l3M9=qc1y+aX``?QLh4Yw0s{H1j z%u3_SU={lbt486bQmN^0x$lXCVn3t>Xzp%XB@q0LX-rb$dDqT#(cZq@4-_k@UXZVb zKPIJjN6l2U)8m>pMLJE=Y(a30tRIhOJ z*{`$u$eAXAI%@RwtK#NekJV@c01>G@m$4&(Q~3u4U>q*2*->L~p_>}p)ZEn;xNTe2 zZ>i0N{=T;R|HQuL)~!slmZ2Jq&>C2F@L!!jL%%9G$q~~NRH!1eS!Oi_5?*GHyB;o6 z;}BKF58R<;0NkFo=K#4{oHQ6)JoN)RgrZtwdKsNfHk0Y;?MMMNwMe2S@STX6xWIW~ zEe;xtZxl;SUFA(9fz7mt30yo{9THk!s>LtXY|A$UK92!VbRh^p5&1}Qpz!*`$HG%k z)vVn#JU5lk)!LB+K%w^C)ngj?JchL-6rNwRlR8Jye<=13t*`D{zt&g9VpPLeKWP+w zZLLNeBf*`(C8edSgy3G{d_dsV(!lVHvw^H#+3e=C{|xexT79Z;S=;m5YT7rD?#W&N zJiFFdJfnT-?(CF;G>$kY*6N_n85L*zoikmuq`UnDw=~iRyltjyt0Xr*Zob93xxG?v z?#ZlFpU;L7h(N0f-%91P-#D-u_y9&!tY)E#8-*M8-LvnH(_NG5&R$@|`vvfvklml- zZp(gdwjxM%<&H5bKMeCl5P-nz+(fCb_0;x*b#7Gsc`X3r>$j&XcJtZmMfkSGpTW1g z+p<4Qb>*r$C^OxgJ6nvu33#TZcvgFk_Y1c>^Kt-5<+FDRoMFjX+&o?!C@d#nCM}iE zUM*lX5#uDvf_%}3TS@~%gG_!)xb}#aRLMJA@ZSdVqKM)dQ}|19^Vxd|zv-(B1b#L; zP?++q-jh3=x6g8OWmxRtPiq2?0+tiUnn2|z32K~Ws%wDM3X~{tnkVy!dVIK_dX8$mWl8BpDze4@B<*Q2vf5z}bMwCU@7ohT0> zuX97Owdl5819Qm{)Nsg|seCSQ@kgn@uu+8XL>W=sDDvJX7n&>t0f4~gndVu2Pc8)( z#Hc{wuF}BJZ3q6pAOrx~na*bdS3H+C4gCS+nkXZR319FJNrtsR1OSR$oy+Hz29*~} z$*X`JvoN@m+Lw>l$|EqV(?o784vZYq>0K}a0FElFzAv!!z9&Xi(F^wV=;q%2@;TdYoDv&Ed1CwDqdB;xEH2%TEKNdHiT{t~pWxwZm&)VO&CoogE zo6mk6xST_W#}yI&&GWggJXpBmZey6fm1mZXO= z=PSHpiqy3uD!ioBU)XrKv4tf7pqgyqsovZgdnblXm|wWOJ=LAP%9?d0lmjTm564ar2Ca`%w&OsA%iMhC z%hSydOvTdPrMV@&OQ$~vF{E#<=h$umglT)X^B=jWjQ+3NoxOObZR2iF+bM27`=yHS zYh)C5$T$Y!3vO!c6Xdf$6J?`{>{4ML>OWPGrvvAp zhn@tk%IUzA=MeZtwu#orDja`w|JFo@d%K{=f-IxDvy>l3x!IxenmwEMSUuYo;Qs-N W@S<$gOrt{p00007|Op)OC{L7}0c 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 diff --git a/src/main/resources/static/js/ranking.js b/src/main/resources/static/js/ranking.js deleted file mode 100644 index 33be64bc..00000000 --- a/src/main/resources/static/js/ranking.js +++ /dev/null @@ -1,45 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - requestRead(`/themes/most-reserved-last-week?count=10`) // 인기 테마 목록 조회 API endpoint - .then(render) - .catch(error => console.error('Error fetching times:', error)); -}); - -function formatDate(dateString) { - let date = new Date(dateString); - let year = date.getFullYear(); - let month = (date.getMonth() + 1).toString().padStart(2, '0'); // '04' - let day = date.getDate().toString().padStart(2, '0'); // '28' - - return `${year}-${month}-${day}`; // '2024-04-28' -} - -function render(data) { - const container = document.getElementById('theme-ranking'); - data.data.themes.forEach(theme => { - const name = theme.name; - const thumbnail = theme.thumbnail; - const description = theme.description; - - const htmlContent = ` - ${name} -
-
${name}
- ${description} -
- `; - - const div = document.createElement('li'); - div.className = 'media my-4'; - div.innerHTML = htmlContent; - - container.appendChild(div); - }) -} - -function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} diff --git a/src/main/resources/static/js/reservation-mine.js b/src/main/resources/static/js/reservation-mine.js deleted file mode 100644 index 80a4d903..00000000 --- a/src/main/resources/static/js/reservation-mine.js +++ /dev/null @@ -1,57 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - fetch('/reservations-mine') // 내 예약 목록 조회 API 호출 - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }) - .then(render) - .catch(error => console.error('Error fetching reservations:', error)); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - data.data.reservations.forEach(item => { - const row = tableBody.insertRow(); - - const theme = item.themeName; - const date = item.date; - const time = item.time; - const status = item.status.includes('CONFIRMED') ? (item.status === 'CONFIRMED' ? '예약' : '예약 - 결제 필요') : item.rank + '번째 예약 대기'; - - row.insertCell(0).textContent = theme; - row.insertCell(1).textContent = date; - row.insertCell(2).textContent = time; - row.insertCell(3).textContent = status; - - if (status.includes('대기')) { // 예약 대기 상태일 때 예약 대기 취소 버튼 추가하는 코드, 상태 값은 변경 가능 - const cancelCell = row.insertCell(4); - const cancelButton = document.createElement('button'); - cancelButton.textContent = '취소'; - cancelButton.className = 'btn btn-danger'; - cancelButton.onclick = function () { - requestDeleteWaiting(item.id).then(() => window.location.reload()); - }; - cancelCell.appendChild(cancelButton); - } else { // 예약 완료 상태일 때 - /* - TODO: [미션4 - 2단계] 내 예약 목록 조회 시, - 예약 완료 상태일 때 결제 정보를 함께 보여주기 - 결제 정보 필드명은 자신의 response 에 맞게 변경하기 - */ - row.insertCell(4).textContent = ''; - row.insertCell(5).textContent = item.paymentKey; - row.insertCell(6).textContent = item.amount; - } - }); -} - -function requestDeleteWaiting(id) { - const endpoint = '/reservations/waiting/' + id; - return fetch(endpoint, { - method: 'DELETE' - }).then(response => { - if (response.status === 204) return; - throw new Error('Delete failed'); - }); -} diff --git a/src/main/resources/static/js/reservation-new.js b/src/main/resources/static/js/reservation-new.js deleted file mode 100644 index 869d1031..00000000 --- a/src/main/resources/static/js/reservation-new.js +++ /dev/null @@ -1,194 +0,0 @@ -let isEditing = false; -const RESERVATION_API_ENDPOINT = '/reservations'; -const TIME_API_ENDPOINT = '/times'; -const THEME_API_ENDPOINT = '/themes'; -const timesOptions = []; -const themesOptions = []; - -document.addEventListener('DOMContentLoaded', () => { - document.getElementById('add-button').addEventListener('click', addInputRow); - - requestRead(RESERVATION_API_ENDPOINT) - .then(render) - .catch(error => console.error('Error fetching reservations:', error)); - - fetchTimes(); - fetchThemes(); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - - data.data.reservations.forEach(item => { - const row = tableBody.insertRow(); - - row.insertCell(0).textContent = item.id; // 예약 id - row.insertCell(1).textContent = item.name; // 예약자명 - row.insertCell(2).textContent = item.theme.name; // 테마명 - row.insertCell(3).textContent = item.date; // 예약 날짜 - row.insertCell(4).textContent = item.time.startAt; // 시작 시간 - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow)); - }); -} - -function fetchTimes() { - requestRead(TIME_API_ENDPOINT) - .then(data => { - timesOptions.push(...data.data.times); - }) - .catch(error => console.error('Error fetching time:', error)); -} - -function fetchThemes() { - requestRead(THEME_API_ENDPOINT) - .then(data => { - themesOptions.push(...data.data.themes); - }) - .catch(error => console.error('Error fetching theme:', error)); -} - -function createSelect(options, defaultText, selectId, textProperty) { - const select = document.createElement('select'); - select.className = 'form-control'; - select.id = selectId; - - // 기본 옵션 추가 - const defaultOption = document.createElement('option'); - defaultOption.textContent = defaultText; - select.appendChild(defaultOption); - - // 넘겨받은 옵션을 바탕으로 드롭다운 메뉴 아이템 생성 - options.forEach(optionData => { - const option = document.createElement('option'); - option.value = optionData.id; - option.textContent = optionData[textProperty]; // 동적 속성 접근 - select.appendChild(option); - }); - - return select; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function addInputRow() { - if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음 - - const tableBody = document.getElementById('table-body'); - const row = tableBody.insertRow(); - isEditing = true; - - const nameInput = createInput('text'); - const dateInput = createInput('date'); - const timeDropdown = createSelect(timesOptions, "시간 선택", 'time-select', 'startAt'); - const themeDropdown = createSelect(themesOptions, "테마 선택", 'theme-select', 'name'); - - const cellFieldsToCreate = ['', nameInput, themeDropdown, dateInput, timeDropdown]; - - cellFieldsToCreate.forEach((field, index) => { - const cell = row.insertCell(index); - if (typeof field === 'string') { - cell.textContent = field; - } else { - cell.appendChild(field); - } - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow)); - actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => { - row.remove(); - isEditing = false; - })); -} - -function createInput(type) { - const input = document.createElement('input'); - input.type = type; - input.className = 'form-control'; - return input; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function saveRow(event) { - // 이벤트 전파를 막는다 - event.stopPropagation(); - - const row = event.target.parentNode.parentNode; - const nameInput = row.querySelector('input[type="text"]'); - const themeSelect = row.querySelector('#theme-select'); - const dateInput = row.querySelector('input[type="date"]'); - const timeSelect = row.querySelector('#time-select'); - - const reservation = { - name: nameInput.value, - themeId: themeSelect.value, - date: dateInput.value, - timeId: timeSelect.value - }; - - requestCreate(reservation) - .then(() => { - location.reload(); - }) - .catch(error => console.error('Error:', error)); - - isEditing = false; // isEditing 값을 false로 설정 -} - -function deleteRow(event) { - const row = event.target.closest('tr'); - const reservationId = row.cells[0].textContent; - - requestDelete(reservationId) - .then(() => row.remove()) - .catch(error => console.error('Error:', error)); -} - -function requestCreate(reservation) { - const requestOptions = { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(reservation) - }; - - return fetch(RESERVATION_API_ENDPOINT, requestOptions) - .then(response => { - if (response.status === 201) return response.json(); - throw new Error('Create failed'); - }); -} - -function requestDelete(id) { - const requestOptions = { - method: 'DELETE', - }; - - return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions) - .then(response => { - if (response.status !== 204) throw new Error('Delete failed'); - }); -} - -function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} diff --git a/src/main/resources/static/js/reservation-with-member.js b/src/main/resources/static/js/reservation-with-member.js deleted file mode 100644 index c5d804ec..00000000 --- a/src/main/resources/static/js/reservation-with-member.js +++ /dev/null @@ -1,250 +0,0 @@ -let isEditing = false; -const RESERVATION_API_ENDPOINT = '/reservations'; -const TIME_API_ENDPOINT = '/times'; -const THEME_API_ENDPOINT = '/themes'; -const MEMBER_API_ENDPOINT = '/members'; -const timesOptions = []; -const themesOptions = []; -const membersOptions = []; - -document.addEventListener('DOMContentLoaded', () => { - document.getElementById('add-button').addEventListener('click', addInputRow); - document.getElementById('filter-form').addEventListener('submit', applyFilter); - - requestRead(RESERVATION_API_ENDPOINT) - .then(render) - .catch(error => console.error('Error fetching reservations:', error)); - - fetchTimes(); - fetchThemes(); - fetchMembers(); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - - data.data.reservations.forEach(item => { - const row = tableBody.insertRow(); - const isPaid = item.status === 'CONFIRMED' ? '결제 완료' : '결제 대기'; - - row.insertCell(0).textContent = item.id; // 예약 id - row.insertCell(1).textContent = item.member.name; // 사용자 name - row.insertCell(2).textContent = item.theme.name; // 테마 name - row.insertCell(3).textContent = item.date; // date - row.insertCell(4).textContent = item.time.startAt; // 예약 시간 startAt - row.insertCell(5).textContent = isPaid; // 결제 - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow)); - }); -} - -function fetchTimes() { - requestRead(TIME_API_ENDPOINT) - .then(data => { - timesOptions.push(...data.data.times); - }) - .catch(error => console.error('Error fetching time:', error)); -} - -function fetchThemes() { - requestRead(THEME_API_ENDPOINT) - .then(data => { - themesOptions.push(...data.data.themes); - populateSelect('theme', themesOptions, 'name'); - }) - .catch(error => console.error('Error fetching theme:', error)); -} - -function fetchMembers() { - requestRead(MEMBER_API_ENDPOINT) - .then(data => { - membersOptions.push(...data.data.members); - populateSelect('member', membersOptions, 'name'); - }) - .catch(error => console.error('Error fetching member:', error)); -} - -function populateSelect(selectId, options, textProperty) { - const select = document.getElementById(selectId); - options.forEach(optionData => { - const option = document.createElement('option'); - option.value = optionData.id; - option.textContent = optionData[textProperty]; - select.appendChild(option); - }); -} - -function createSelect(options, defaultText, selectId, textProperty) { - const select = document.createElement('select'); - select.className = 'form-control'; - select.id = selectId; - - // 기본 옵션 추가 - const defaultOption = document.createElement('option'); - defaultOption.textContent = defaultText; - select.appendChild(defaultOption); - - // 넘겨받은 옵션을 바탕으로 드롭다운 메뉴 아이템 생성 - options.forEach(optionData => { - const option = document.createElement('option'); - option.value = optionData.id; - option.textContent = optionData[textProperty]; // 동적 속성 접근 - select.appendChild(option); - }); - - return select; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function addInputRow() { - if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음 - - const tableBody = document.getElementById('table-body'); - const row = tableBody.insertRow(); - isEditing = true; - - const dateInput = createInput('date'); - const timeDropdown = createSelect(timesOptions, "시간 선택", 'time-select', 'startAt'); - const themeDropdown = createSelect(themesOptions, "테마 선택", 'theme-select', 'name'); - const memberDropdown = createSelect(membersOptions, "멤버 선택", 'member-select', 'name'); - - const cellFieldsToCreate = ['', memberDropdown, themeDropdown, dateInput, timeDropdown]; - - cellFieldsToCreate.forEach((field, index) => { - const cell = row.insertCell(index); - if (typeof field === 'string') { - cell.textContent = field; - } else { - cell.appendChild(field); - } - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow)); - actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => { - row.remove(); - isEditing = false; - })); -} - -function createInput(type) { - const input = document.createElement('input'); - input.type = type; - input.className = 'form-control'; - return input; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function saveRow(event) { - // 이벤트 전파를 막는다 - event.stopPropagation(); - - const row = event.target.parentNode.parentNode; - const dateInput = row.querySelector('input[type="date"]'); - const memberSelect = row.querySelector('#member-select'); - const themeSelect = row.querySelector('#theme-select'); - const timeSelect = row.querySelector('#time-select'); - - const reservation = { - date: dateInput.value, - themeId: themeSelect.value, - timeId: timeSelect.value, - memberId: memberSelect.value, - }; - - requestCreate(reservation) - .then(() => { - location.reload(); - }) - .catch(error => console.error('Error:', error)); - - isEditing = false; // isEditing 값을 false로 설정 -} - -function deleteRow(event) { - const row = event.target.closest('tr'); - const reservationId = row.cells[0].textContent; - - requestDelete(reservationId) - .then(() => row.remove()) - .catch(error => console.error('Error:', error)); -} - -function applyFilter(event) { - event.preventDefault(); - - const themeId = document.getElementById('theme').value; - const memberId = document.getElementById('member').value; - const dateFrom = document.getElementById('date-from').value; - const dateTo = document.getElementById('date-to').value; - - const queryParams = { - themeId: themeId, - memberId: memberId, - dateFrom: dateFrom, - dateTo: dateTo - } - const searchParams = new URLSearchParams(queryParams); - const endpoint = '/reservations/search'; - - const url = `${endpoint}?${searchParams.toString()}`; - fetch(url, { // 예약 검색 API 호출 - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - }).then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }).then(render) - .catch(error => console.error("Error fetching available times:", error)); -} - -function requestCreate(reservation) { - const requestOptions = { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(reservation) - }; - - return fetch('/reservations/admin', requestOptions) - .then(response => { - if (response.status === 201) return response.json(); - throw new Error('Create failed'); - }); -} - -function requestDelete(id) { - const requestOptions = { - method: 'DELETE', - }; - - return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions) - .then(response => { - if (response.status !== 204) throw new Error('Delete failed'); - }); -} - -function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} diff --git a/src/main/resources/static/js/reservation.js b/src/main/resources/static/js/reservation.js deleted file mode 100644 index a64d3dc5..00000000 --- a/src/main/resources/static/js/reservation.js +++ /dev/null @@ -1,179 +0,0 @@ -let isEditing = false; -const RESERVATION_API_ENDPOINT = '/reservations'; -const TIME_API_ENDPOINT = '/times'; -const timesOptions = []; - -document.addEventListener('DOMContentLoaded', () => { - document.getElementById('add-button').addEventListener('click', addInputRow); - - requestRead(RESERVATION_API_ENDPOINT) - .then(render) - .catch(error => console.error('Error fetching reservations:', error)); - - fetchTimes(); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - - data.data.reservations.forEach(item => { - const row = tableBody.insertRow(); - - row.insertCell(0).textContent = item.id; - row.insertCell(1).textContent = item.name; - row.insertCell(2).textContent = item.date; - row.insertCell(3).textContent = item.time.startAt; - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow)); - }); -} - -function fetchTimes() { - requestRead(TIME_API_ENDPOINT) - .then(data => { - timesOptions.push(...data.data.times); - }) - .catch(error => console.error('Error fetching time:', error)); -} - -function createSelect(options, defaultText, selectId, textProperty) { - const select = document.createElement('select'); - select.className = 'form-control'; - select.id = selectId; - - // 기본 옵션 추가 - const defaultOption = document.createElement('option'); - defaultOption.textContent = defaultText; - select.appendChild(defaultOption); - - // 넘겨받은 옵션을 바탕으로 드롭다운 메뉴 아이템 생성 - options.forEach(optionData => { - const option = document.createElement('option'); - option.value = optionData.id; - option.textContent = optionData[textProperty]; // 동적 속성 접근 - select.appendChild(option); - }); - - return select; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function addInputRow() { - if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음 - - const tableBody = document.getElementById('table-body'); - const row = tableBody.insertRow(); - isEditing = true; - - const nameInput = createInput('text'); - const dateInput = createInput('date'); - const timeDropdown = createSelect(timesOptions, "시간 선택", 'time-select', 'startAt'); - - const cellFieldsToCreate = ['', nameInput, dateInput, timeDropdown]; - - cellFieldsToCreate.forEach((field, index) => { - const cell = row.insertCell(index); - if (typeof field === 'string') { - cell.textContent = field; - } else { - cell.appendChild(field); - } - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow)); - actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => { - row.remove(); - isEditing = false; - })); -} - -function createInput(type) { - const input = document.createElement('input'); - input.type = type; - input.className = 'form-control'; - return input; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function saveRow(event) { - // 이벤트 전파를 막는다 - event.stopPropagation(); - - const row = event.target.parentNode.parentNode; - const nameInput = row.querySelector('input[type="text"]'); - const dateInput = row.querySelector('input[type="date"]'); - const timeSelect = row.querySelector('select'); - - const reservation = { - name: nameInput.value, - date: dateInput.value, - timeId: timeSelect.value - }; - - requestCreate(reservation) - .then(() => { - location.reload(); - }) - .catch(error => console.error('Error:', error)); - - isEditing = false; // isEditing 값을 false로 설정 -} - -function deleteRow(event) { - const row = event.target.closest('tr'); - const reservationId = row.cells[0].textContent; - - requestDelete(reservationId) - .then(() => row.remove()) - .catch(error => console.error('Error:', error)); -} - -function requestCreate(reservation) { - const requestOptions = { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(reservation) - }; - - return fetch(RESERVATION_API_ENDPOINT, requestOptions) - .then(response => { - if (response.status === 201) return response.json(); - throw new Error('Create failed'); - }); -} - -function requestDelete(id) { - const requestOptions = { - method: 'DELETE', - }; - - return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions) - .then(response => { - if (response.status !== 204) throw new Error('Delete failed'); - }); -} - -function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} diff --git a/src/main/resources/static/js/scripts.js b/src/main/resources/static/js/scripts.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/static/js/theme.js b/src/main/resources/static/js/theme.js deleted file mode 100644 index fda35892..00000000 --- a/src/main/resources/static/js/theme.js +++ /dev/null @@ -1,136 +0,0 @@ -let isEditing = false; -const API_ENDPOINT = '/themes'; -const cellFields = ['id', 'name', 'description', 'thumbnail']; -const createCellFields = ['', createInput(), createInput(), createInput()]; - -function createBody(inputs) { - return { - name: inputs[0].value, - description: inputs[1].value, - thumbnail: inputs[2].value, - }; -} - -document.addEventListener('DOMContentLoaded', () => { - document.getElementById('add-button').addEventListener('click', addRow); - requestRead() - .then(render) - .catch(error => console.error('Error fetching times:', error)); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - - data.data.themes.forEach(item => { - const row = tableBody.insertRow(); - - cellFields.forEach((field, index) => { - row.insertCell(index).textContent = item[field]; - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow)); - }); -} - -function addRow() { - if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음 - - const tableBody = document.getElementById('table-body'); - const row = tableBody.insertRow(); - isEditing = true; - - createAddField(row); -} - -function createAddField(row) { - createCellFields.forEach((field, index) => { - const cell = row.insertCell(index); - if (typeof field === 'string') { - cell.textContent = field; - } else { - cell.appendChild(field); - } - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow)); - actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => { - row.remove(); - isEditing = false; - })); -} - -function createInput() { - const input = document.createElement('input'); - input.className = 'form-control'; - return input; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function saveRow(event) { - const row = event.target.parentNode.parentNode; - const inputs = row.querySelectorAll('input'); - const body = createBody(inputs); - - requestCreate(body) - .then(() => { - location.reload(); - }) - .catch(error => console.error('Error:', error)); - - isEditing = false; // isEditing 값을 false로 설정 -} - -function deleteRow(event) { - const row = event.target.closest('tr'); - const id = row.cells[0].textContent; - - requestDelete(id) - .then(() => row.remove()) - .catch(error => console.error('Error:', error)); -} - - -// request - -function requestCreate(data) { - const requestOptions = { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(data) - }; - - return fetch(API_ENDPOINT, requestOptions) - .then(response => { - if (response.status === 201) return response.json(); - throw new Error('Create failed'); - }); -} - -function requestRead() { - return fetch(API_ENDPOINT) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} - -function requestDelete(id) { - const requestOptions = { - method: 'DELETE', - }; - - return fetch(`${API_ENDPOINT}/${id}`, requestOptions) - .then(response => { - if (response.status !== 204) throw new Error('Delete failed'); - }); -} diff --git a/src/main/resources/static/js/time.js b/src/main/resources/static/js/time.js deleted file mode 100644 index 98094df4..00000000 --- a/src/main/resources/static/js/time.js +++ /dev/null @@ -1,135 +0,0 @@ -let isEditing = false; -const API_ENDPOINT = '/times'; -const cellFields = ['id', 'startAt']; -const createCellFields = ['', createInput()]; - -function createBody(inputs) { - return { - startAt: inputs[0].value, - }; -} - -document.addEventListener('DOMContentLoaded', () => { - document.getElementById('add-button').addEventListener('click', addRow); - requestRead() - .then(render) - .catch(error => console.error('Error fetching times:', error)); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - - data.data.times.forEach(item => { - const row = tableBody.insertRow(); - - cellFields.forEach((field, index) => { - row.insertCell(index).textContent = item[field]; - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow)); - }); -} - -function addRow() { - if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음 - - const tableBody = document.getElementById('table-body'); - const row = tableBody.insertRow(); - isEditing = true; - - createAddField(row); -} - -function createAddField(row) { - createCellFields.forEach((field, index) => { - const cell = row.insertCell(index); - if (typeof field === 'string') { - cell.textContent = field; - } else { - cell.appendChild(field); - } - }); - - const actionCell = row.insertCell(row.cells.length); - actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow)); - actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => { - row.remove(); - isEditing = false; - })); -} - -function createInput() { - const input = document.createElement('input'); - input.type = 'time' - input.className = 'form-control'; - return input; -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} - -function saveRow(event) { - const row = event.target.parentNode.parentNode; - const inputs = row.querySelectorAll('input'); - const body = createBody(inputs); - - requestCreate(body) - .then(() => { - location.reload(); - }) - .catch(error => console.error('Error:', error)); - - isEditing = false; // isEditing 값을 false로 설정 -} - -function deleteRow(event) { - const row = event.target.closest('tr'); - const id = row.cells[0].textContent; - - requestDelete(id) - .then(() => row.remove()) - .catch(error => console.error('Error:', error)); -} - - -// request - -function requestCreate(data) { - const requestOptions = { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(data) - }; - - return fetch(API_ENDPOINT, requestOptions) - .then(response => { - if (response.status === 201) return response.json(); - throw new Error('Create failed'); - }); -} - -function requestRead() { - return fetch(API_ENDPOINT) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} - -function requestDelete(id) { - const requestOptions = { - method: 'DELETE', - }; - - return fetch(`${API_ENDPOINT}/${id}`, requestOptions) - .then(response => { - if (response.status !== 204) throw new Error('Delete failed'); - }); -} diff --git a/src/main/resources/static/js/user-reservation.js b/src/main/resources/static/js/user-reservation.js deleted file mode 100644 index 500c016a..00000000 --- a/src/main/resources/static/js/user-reservation.js +++ /dev/null @@ -1,273 +0,0 @@ -const THEME_API_ENDPOINT = '/themes'; - -document.addEventListener('DOMContentLoaded', () => { - requestRead(THEME_API_ENDPOINT) - .then(renderTheme) - .catch(error => console.error('Error fetching times:', error)); - - flatpickr("#datepicker", { - inline: true, - onChange: function (selectedDates, dateStr, instance) { - if (dateStr === '') return; - checkDate(); - } - }); - - // ------ 결제위젯 초기화 ------ - // @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-설치-및-초기화 - // @docs https://docs.tosspayments.com/reference/widget-sdk#renderpaymentmethods선택자-결제-금액-옵션 - const paymentAmount = 1000; - const widgetClientKey = "test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm"; - const paymentWidget = PaymentWidget(widgetClientKey, PaymentWidget.ANONYMOUS); - paymentWidget.renderPaymentMethods( - "#payment-method", - {value: paymentAmount}, - {variantKey: "DEFAULT"} - ); - - document.getElementById('theme-slots').addEventListener('click', event => { - if (event.target.classList.contains('theme-slot')) { - document.querySelectorAll('.theme-slot').forEach(slot => slot.classList.remove('active')); - event.target.classList.add('active'); - checkDateAndTheme(); - } - }); - - document.getElementById('time-slots').addEventListener('click', event => { - if (event.target.classList.contains('time-slot') && !event.target.classList.contains('disabled')) { - document.querySelectorAll('.time-slot').forEach(slot => slot.classList.remove('active')); - event.target.classList.add('active'); - checkDateAndThemeAndTime(); - } - }); - - document.getElementById('reserve-button').addEventListener('click', onReservationButtonClickWithPaymentWidget); - document.getElementById('wait-button').addEventListener('click', onWaitButtonClick); - - function onReservationButtonClickWithPaymentWidget(event) { - onReservationButtonClick(event, paymentWidget); - } -}); - -function renderTheme(themes) { - const themeSlots = document.getElementById('theme-slots'); - themeSlots.innerHTML = ''; - themes.data.themes.forEach(theme => { - const name = theme.name; - const themeId = theme.id; - themeSlots.appendChild(createSlot('theme', name, themeId)); - }); -} - -function createSlot(type, text, id, booked) { - const div = document.createElement('div'); - div.className = type + '-slot cursor-pointer bg-light border rounded p-3 mb-2'; - div.textContent = text; - div.setAttribute('data-' + type + '-id', id); - if (type === 'time') { - div.setAttribute('data-time-booked', booked); - } - return div; -} - -function checkDate() { - const selectedDate = document.getElementById("datepicker").value; - if (selectedDate) { - const themeSection = document.getElementById("theme-section"); - if (themeSection.classList.contains("disabled")) { - themeSection.classList.remove("disabled"); - } - const timeSlots = document.getElementById('time-slots'); - timeSlots.innerHTML = ''; - - requestRead(THEME_API_ENDPOINT) - .then(renderTheme) - .catch(error => console.error('Error fetching times:', error)); - } -} - -function checkDateAndTheme() { - const selectedDate = document.getElementById("datepicker").value; - const selectedThemeElement = document.querySelector('.theme-slot.active'); - if (selectedDate && selectedThemeElement) { - const selectedThemeId = selectedThemeElement.getAttribute('data-theme-id'); - fetchAvailableTimes(selectedDate, selectedThemeId); - } -} - -function fetchAvailableTimes(date, themeId) { - - fetch(`/times/search?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint - method: 'GET', - headers: { - 'Content-Type': 'application/json', - } - }).then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }).then(renderAvailableTimes) - .catch(error => console.error("Error fetching available times:", error)); -} - -function renderAvailableTimes(times) { - const timeSection = document.getElementById("time-section"); - if (timeSection.classList.contains("disabled")) { - timeSection.classList.remove("disabled"); - } - - const timeSlots = document.getElementById('time-slots'); - timeSlots.innerHTML = ''; - if (times.length === 0) { - timeSlots.innerHTML = '
선택할 수 있는 시간이 없습니다.
'; - return; - } - times.data.times.forEach(time => { - const startAt = time.startAt; - const timeId = time.id; - const isAvailable = time.isAvailable; - - const div = createSlot('time', startAt, timeId, isAvailable); // createSlot('time', 시작 시간, time id, 예약 여부) - timeSlots.appendChild(div); - }); -} - -function checkDateAndThemeAndTime() { - const selectedDate = document.getElementById("datepicker").value; - const selectedThemeElement = document.querySelector('.theme-slot.active'); - const selectedTimeElement = document.querySelector('.time-slot.active'); - const reserveButton = document.getElementById("reserve-button"); - const waitButton = document.getElementById("wait-button"); - - if (selectedDate && selectedThemeElement && selectedTimeElement) { - if (selectedTimeElement.getAttribute('data-time-booked') === 'false') { - // 선택된 시간이 이미 예약된 경우 - reserveButton.classList.add("disabled"); - waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화 - } else { - // 선택된 시간이 예약 가능한 경우 - reserveButton.classList.remove("disabled"); - waitButton.classList.add("disabled"); // 예약 대기 버튼 활성화 - } - } else { - // 날짜, 테마, 시간 중 하나라도 선택되지 않은 경우 - reserveButton.classList.add("disabled"); - waitButton.classList.add("disabled"); - } -} - -function onReservationButtonClick(event, paymentWidget) { - const selectedDate = document.getElementById("datepicker").value; - const selectedThemeId = document.querySelector('.theme-slot.active')?.getAttribute('data-theme-id'); - const selectedTimeId = document.querySelector('.time-slot.active')?.getAttribute('data-time-id'); - - if (selectedDate && selectedThemeId && selectedTimeId) { - const reservationData = { - date: selectedDate, - themeId: selectedThemeId, - timeId: selectedTimeId, - }; - - const generateRandomString = () => - window.btoa(Math.random()).slice(0, 20); - - // TOSS 결제 위젯 Javascript SDK 연동 방식 중 'Promise로 처리하기'를 적용함 - // https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0 - const orderIdPrefix = "WTEST"; - paymentWidget.requestPayment({ - orderId: orderIdPrefix + generateRandomString(), - orderName: "테스트 방탈출 예약 결제 1건", - amount: 1000, - }).then(function (data) { - console.debug(data); - fetchReservationPayment(data, reservationData); - }).catch(function (error) { - // TOSS 에러 처리: 에러 목록을 확인하세요 - // https://docs.tosspayments.com/reference/error-codes#failurl 로-전달되는-에러 - alert(error.code + " :" + error.message); - }); - - } else { - alert("Please select a date, theme, and time before making a reservation."); - } -} - -async function fetchReservationPayment(paymentData, reservationData) { - const reservationPaymentRequest = { - date: reservationData.date, - themeId: reservationData.themeId, - timeId: reservationData.timeId, - paymentKey: paymentData.paymentKey, - orderId: paymentData.orderId, - amount: paymentData.amount, - paymentType: paymentData.paymentType, - } - - const reservationURL = "/reservations"; - fetch(reservationURL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(reservationPaymentRequest), - }).then(response => { - if (!response.ok) { - return response.json().then(errorBody => { - console.error("예약 결제 실패 : " + JSON.stringify(errorBody)); - window.alert("예약 결제 실패 메시지: " + errorBody.message); - }); - } else { - response.json().then(successBody => { - alert("예약이 완료되었습니다."); - console.log("예약 결제 성공 : " + JSON.stringify(successBody)); - window.location.href = "/"; - }); - } - }).catch(error => { - console.error(error.message); - }); -} - -function onWaitButtonClick() { - const selectedDate = document.getElementById("datepicker").value; - const selectedThemeId = document.querySelector('.theme-slot.active')?.getAttribute('data-theme-id'); - const selectedTimeId = document.querySelector('.time-slot.active')?.getAttribute('data-time-id'); - - if (selectedDate && selectedThemeId && selectedTimeId) { - const reservationData = { - date: selectedDate, - timeId: selectedTimeId, - themeId: selectedThemeId, - }; - - fetch('/reservations/waiting', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(reservationData) - }) - .then(response => { - if (!response.ok) throw new Error('Reservation waiting failed'); - return response.json(); - }) - .then(data => { - alert('Reservation waiting successful!'); - window.location.href = "/"; - }) - .catch(error => { - alert("An error occurred while making the reservation waiting."); - console.error(error); - }); - } else { - alert("Please select a date, theme, and time before making a reservation waiting."); - } -} - - -function requestRead(endpoint) { - return fetch(endpoint) - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }); -} diff --git a/src/main/resources/static/js/user-scripts.js b/src/main/resources/static/js/user-scripts.js deleted file mode 100644 index 2a1e253a..00000000 --- a/src/main/resources/static/js/user-scripts.js +++ /dev/null @@ -1,152 +0,0 @@ -document.addEventListener('DOMContentLoaded', function () { - updateUIBasedOnLogin(); -}); - -document.getElementById('logout-btn').addEventListener('click', function (event) { - event.preventDefault(); - fetch('/logout', { - method: 'POST', // 또는 서버 설정에 따라 GET 일 수도 있음 - credentials: 'include' // 쿠키를 포함시키기 위해 필요 - }) - .then(response => { - if (response.ok) { - // 로그아웃 성공, 페이지 새로고침 또는 리다이렉트 - window.location.reload(); - } else { - // 로그아웃 실패 처리 - console.error('Logout failed'); - } - }) - .catch(error => { - console.error('Error:', error); - }); -}); - -function updateUIBasedOnLogin() { - fetch('/login/check') // 로그인 상태 확인 API 호출 - .then(response => { - if (!response.ok) { // 요청이 실패하거나 로그인 상태가 아닌 경우 - throw new Error('Not logged in or other error'); - } - return response.json(); // 응답 본문을 JSON으로 파싱 - }) - .then(data => { - // 응답에서 사용자 이름을 추출하여 UI 업데이트 - document.getElementById('profile-name').textContent = data.data.name; // 프로필 이름 설정 - document.querySelector('.nav-item.dropdown').style.display = 'block'; // 드롭다운 메뉴 표시 - document.querySelector('.nav-item a[href="/login"]').parentElement.style.display = 'none'; // 로그인 버튼 숨김 - }) - .catch(error => { - // 에러 처리 또는 로그아웃 상태일 때 UI 업데이트 - console.error('Error:', error); - document.getElementById('profile-name').textContent = 'Profile'; // 기본 텍스트로 재설정 - document.querySelector('.nav-item.dropdown').style.display = 'none'; // 드롭다운 메뉴 숨김 - document.querySelector('.nav-item a[href="/login"]').parentElement.style.display = 'block'; // 로그인 버튼 표시 - }); -} - -// 드롭다운 메뉴 토글 -document.getElementById("navbarDropdown").addEventListener('click', function (e) { - e.preventDefault(); - const dropdownMenu = e.target.closest('.nav-item.dropdown').querySelector('.dropdown-menu'); - dropdownMenu.classList.toggle('show'); // Bootstrap 4에서는 data-toggle 사용, Bootstrap 5에서는 JS로 처리 -}); - - -function login() { - const email = document.getElementById('email').value; - const password = document.getElementById('password').value; - - // 입력 필드 검증 - if (!email || !password) { - alert('Please fill in all fields.'); - return; // 필수 입력 필드가 비어있으면 여기서 함수 실행을 중단 - } - - fetch('/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - email: email, - password: password - }) - }) - .then(response => { - if (200 !== response.status) { - alert('Login failed'); // 로그인 실패 시 경고창 표시 - throw new Error('Login failed'); - } - }) - .then(() => { - updateUIBasedOnLogin(); // UI 업데이트 - window.location.href = '/'; - }) - .catch(error => { - console.error('Error during login:', error); - }); -} - -function signup() { - // Redirect to signup page - window.location.href = '/signup'; -} - -function register(event) { - // 폼 데이터 수집 - const email = document.getElementById('email').value; - const password = document.getElementById('password').value; - const name = document.getElementById('name').value; - - // 입력 필드 검증 - if (!email || !password || !name) { - alert('Please fill in all fields.'); - return; // 필수 입력 필드가 비어있으면 여기서 함수 실행을 중단 - } - - // 요청 데이터 포맷팅 - const formData = { - email: email, - password: password, - name: name - }; - - // AJAX 요청 생성 및 전송 - fetch('/members', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(formData) - }) - .then(response => { - if (!response.ok) { - alert('Signup request failed'); - throw new Error('Signup request failed'); - } - return response.json(); // 여기서 응답을 JSON 형태로 변환 - }) - .then(data => { - // 성공적인 응답 처리 - console.log('Signup successful:', data); - window.location.href = '/login'; - }) - .catch(error => { - // 에러 처리 - console.error('Error during signup:', error); - }); - - // 폼 제출에 의한 페이지 리로드 방지 - event.preventDefault(); -} - -function base64DecodeUnicode(str) { - // Base64 디코딩 - const decodedBytes = atob(str); - // UTF-8 바이트를 문자열로 변환 - const encodedUriComponent = decodedBytes.split('').map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join(''); - return decodeURIComponent(encodedUriComponent); -} diff --git a/src/main/resources/static/js/waiting.js b/src/main/resources/static/js/waiting.js deleted file mode 100644 index eaaf963e..00000000 --- a/src/main/resources/static/js/waiting.js +++ /dev/null @@ -1,69 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - fetch('/reservations/waiting') // 내 예약 목록 조회 API 호출 - .then(response => { - if (response.status === 200) return response.json(); - throw new Error('Read failed'); - }) - .then(render) - .catch(error => console.error('Error fetching reservations:', error)); -}); - -function render(data) { - const tableBody = document.getElementById('table-body'); - tableBody.innerHTML = ''; - - data.data.reservations.forEach(item => { - const row = tableBody.insertRow(); - - const id = item.id; - const name = item.member.name; - const theme = item.theme.name; - const date = item.date; - const startAt = item.time.startAt; - - row.insertCell(0).textContent = id; // 예약 대기 id - row.insertCell(1).textContent = name; // 예약자명 - row.insertCell(2).textContent = theme; // 테마명 - row.insertCell(3).textContent = date; // 예약 날짜 - row.insertCell(4).textContent = startAt; // 시작 시간 - - const actionCell = row.insertCell(row.cells.length); - - actionCell.appendChild(createActionButton('승인', 'btn-primary', approve)); - actionCell.appendChild(createActionButton('거절', 'btn-danger', deny)); - }); -} - -function approve(event) { - const row = event.target.closest('tr'); - const id = row.cells[0].textContent; - - const endpoint = `/reservations/waiting/${id}/confirm` - return fetch(endpoint, { - method: 'POST' - }).then(response => { - if (response.status === 200) return; - throw new Error('Delete failed'); - }).then(() => location.reload()); -} - -function deny(event) { - const row = event.target.closest('tr'); - const id = row.cells[0].textContent; - - const endpoint = `/reservations/waiting/${id}/reject` - return fetch(endpoint, { - method: 'POST' - }).then(response => { - if (response.status === 204) return; - throw new Error('Delete failed'); - }).then(() => location.reload()); -} - -function createActionButton(label, className, eventListener) { - const button = document.createElement('button'); - button.textContent = label; - button.classList.add('btn', className, 'mr-2'); - button.addEventListener('click', eventListener); - return button; -} diff --git a/src/main/resources/templates/admin/index.html b/src/main/resources/templates/admin/index.html deleted file mode 100644 index 3a4a7254..00000000 --- a/src/main/resources/templates/admin/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

방탈출 어드민

-
- - - - diff --git a/src/main/resources/templates/admin/reservation-new.html b/src/main/resources/templates/admin/reservation-new.html deleted file mode 100644 index 527f0475..00000000 --- a/src/main/resources/templates/admin/reservation-new.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

방탈출 예약 페이지

-
-
-
- -
- - - - - - - - - - - - - - -
예약번호예약자테마날짜시간결제 완료 여부
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
-
-
- - - - - diff --git a/src/main/resources/templates/admin/reservation.html b/src/main/resources/templates/admin/reservation.html deleted file mode 100644 index a827db6b..00000000 --- a/src/main/resources/templates/admin/reservation.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

방탈출 예약 페이지

-
- -
-
- - - - - - - - - - - - -
예약번호예약자날짜시간
-
- - - - diff --git a/src/main/resources/templates/admin/theme.html b/src/main/resources/templates/admin/theme.html deleted file mode 100644 index 70b39718..00000000 --- a/src/main/resources/templates/admin/theme.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

테마 관리 페이지

-
- -
-
- - - - - - - - - - - - -
순서제목설명썸네일 URL
-
- - - - - - diff --git a/src/main/resources/templates/admin/time.html b/src/main/resources/templates/admin/time.html deleted file mode 100644 index f0152542..00000000 --- a/src/main/resources/templates/admin/time.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

시간 관리 페이지

-
- -
-
- - - - - - - - - - -
순서시간
-
- - - - - - diff --git a/src/main/resources/templates/admin/waiting.html b/src/main/resources/templates/admin/waiting.html deleted file mode 100644 index 32c30370..00000000 --- a/src/main/resources/templates/admin/waiting.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

예약 대기 관리 페이지

-
- - - - - - - - - - - - - -
예약대기 번호예약자테마날짜시간
-
- - - - - diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html deleted file mode 100644 index 9740e2ef..00000000 --- a/src/main/resources/templates/index.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - 방탈출 예약 페이지 - - - - - - - - -
-

인기 테마

-
    -
-
- - - - - - diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html deleted file mode 100644 index 8faab43f..00000000 --- a/src/main/resources/templates/login.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - Login - - - - - - - - -
-

Login

-
-
- -
-
- -
-
- - -
-
-
- - - - diff --git a/src/main/resources/templates/reservation-mine.html b/src/main/resources/templates/reservation-mine.html deleted file mode 100644 index f2310ca0..00000000 --- a/src/main/resources/templates/reservation-mine.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - 방탈출 어드민 - - - - - - - - -
-

내 예약

-
- - - - - - - - - - - - - - - -
테마날짜시간상태대기 취소paymentKey결제금액
-
- - - - - diff --git a/src/main/resources/templates/reservation.html b/src/main/resources/templates/reservation.html deleted file mode 100644 index 29c9c8ba..00000000 --- a/src/main/resources/templates/reservation.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - 방탈출 예약 페이지 - - - - - - - - - - - - - -
-

예약 페이지

-
- -
-

날짜 선택

-
-
-
-
- - -
-

테마 선택

-
- -
-
- - -
-

시간 선택

-
- -
-
-
- - -
- -
-
-
- - -
-
-
-
-
- -
-
-
- - - - - - - - diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html deleted file mode 100644 index 6f044d2f..00000000 --- a/src/main/resources/templates/signup.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - Signup - - - - - - - - -
-

Signup

-
-
- - -
-
- - -
-
- - -
- -
-
- - - - diff --git a/src/test/kotlin/roomescape/view/PageControllerTest.kt b/src/test/kotlin/roomescape/view/PageControllerTest.kt deleted file mode 100644 index 0b223c7b..00000000 --- a/src/test/kotlin/roomescape/view/PageControllerTest.kt +++ /dev/null @@ -1,133 +0,0 @@ -package roomescape.view - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.test.web.servlet.MockMvc -import roomescape.util.RoomescapeApiTest - -@WebMvcTest(controllers = [ - AuthPageController::class, - AdminPageController::class, - ClientPageController::class -]) -class PageControllerTest( - @Autowired private val mockMvc: MockMvc -) : RoomescapeApiTest() { - - init { - listOf("/", "/login").forEach { - given("GET $it 요청은") { - `when`("로그인 및 권한 여부와 관계없이 성공한다.") { - then("비회원") { - doNotLogin() - - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { isOk() } - } - } - - then("회원") { - loginAsUser() - - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { isOk() } - } - } - - then("관리자") { - loginAsAdmin() - - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { isOk() } - } - } - } - } - } - - listOf("/admin", "/admin/reservation", "/admin/time", "/admin/theme", "/admin/waiting").forEach { - given("GET $it 요청을") { - `when`("관리자가 보내면") { - loginAsAdmin() - - then("성공한다.") { - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { isOk() } - } - } - } - - `when`("회원이 보내면") { - loginAsUser() - - then("로그인 페이지로 이동한다.") { - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } - } - } - } - } - } - - listOf("/reservation", "/reservation-mine").forEach { - given("GET $it 요청을") { - `when`("로그인 된 회원이 보내면 성공한다.") { - then("회원") { - loginAsUser() - - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { isOk() } - } - } - then("관리자") { - loginAsAdmin() - - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { isOk() } - } - } - } - - `when`("로그인 없이 보내면") { - then("로그인 페이지로 이동한다.") { - doNotLogin() - - runGetTest( - mockMvc = mockMvc, - endpoint = it, - ) { - status { is3xxRedirection() } - header { - string("Location", "/login") - } - } - } - } - } - } - } -} \ No newline at end of file