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 79a18a8f..00000000 Binary files a/src/main/resources/static/favicon.ico and /dev/null differ diff --git a/src/main/resources/static/image/admin-logo.png b/src/main/resources/static/image/admin-logo.png deleted file mode 100644 index b46677ec..00000000 Binary files a/src/main/resources/static/image/admin-logo.png and /dev/null differ diff --git a/src/main/resources/static/image/default-profile.png b/src/main/resources/static/image/default-profile.png deleted file mode 100644 index 12e01edd..00000000 Binary files a/src/main/resources/static/image/default-profile.png and /dev/null differ 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