본문 바로가기
프로젝트/미니

프론트엔드 개발

by HWK 2023. 9. 22.

근 3일동안 TIL을 쓰지 못했다. 바쁘고 힘든것도 있지만.. 무었보다 진행이 더뎠기 때문이다.
하지만 드디어 팀장님이 해내셨다! 팀장님 덕분에 프론트와 백을 연결할 수 있었고 그 방법은 아래서 소개하겠다.

각 html 파일을 만드는 것도 중요하지만, 각각의 파일과 연결해줄 주소들이 필요했다.
그 주소들을 아래 homeController에서 만들었다. 이제 각각의 파일들은 html과 연결된다.

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "index";
    }

    @GetMapping("/user/login-page")
    public String loginPage() {
        return "login"; // 로그인 페이지의 뷰 이름 (templates 디렉토리 아래의 HTML 파일 이름)
    }

    @GetMapping("/user/signup")
    public String signupPage() {
        return "signup"; // 로그인 페이지의 뷰 이름 (templates 디렉토리 아래의 HTML 파일 이름)
    }

    @GetMapping("/user/store/cud") // 상점 수정 및 삭제
    public String CUDStore() {
        return "store/storeCUD";
    }

    @GetMapping("/user/store") // 상점 수정 및 삭제
    public String getStore() {
        return "store/storeDetail";
    }

    @GetMapping("/user/menu/cud") // 상점 수정 및 삭제
    public String CUDMenu() {
        return "menu/menuCUD";
    }

    @GetMapping("/user-info")
    @ResponseBody
    public UserInfoDto getUserInfo(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        String username = userDetails.getUser().getUsername();
        UserRoleEnum role = userDetails.getUser().getUserRole();
        boolean isOwner = (role == UserRoleEnum.OWNER);

        return new UserInfoDto(username, isOwner);
    }


}

이제 프론트엔드 코드들을 소개하겠다.


1. 로그인

그냥 쿠키를 이용하는 방식을 사용하고 있었는데, postMan에서 http연결로 넘어오니 그냥 쿠키를 활용하는 방법은 견본이 없어서 로그인 쿠키와 관련된 코드들을 모두 헤더를 활용하는 방식으로 바꿨다.
login.html은 다음과 같다.

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link rel="stylesheet" type="text/css" href="/static/css/style.css">
  <script src="https://code.jquery.com/jquery-3.7.0.min.js"
          integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
  <meta charset="UTF-8">
  <title>로그인 페이지</title>
</head>
<body>
<div id="login-form">
  <div id="login-title">Log into Select Shop</div>
  <button id="login-kakao-btn" onclick="location.href='https://kauth.kakao.com/oauth/authorize?client_id=720857541afb33bbe088af371ebd3a3e&redirect_uri=http://localhost:8080/api/user/kakao/callback&response_type=code'">
    카카오로 로그인하기
  </button>
  <button id="login-id-btn" onclick="location.href='/user/signup'">
    회원 가입하기
  </button>
  <div>
    <div class="login-id-label">아이디</div>
    <input type="text" name="username" id="username" class="login-input-box">

    <div class="login-id-label">비밀번호</div>
    <input type="password" name="password" id="password" class="login-input-box">

    <button id="login-id-submit" onclick="onLogin()">로그인</button>
  </div>
  <div id="login-failed" style="display:none" class="alert alert-danger" role="alert">로그인에 실패하였습니다.</div>
</div>
</body>
<script>
  $(document).ready(function () {
    // 토큰 삭제
    Cookies.remove('Authorization', {path: '/'});
  });

  const href = location.href;
  const queryString = href.substring(href.indexOf("?") + 1)
  if (queryString === 'error') {
    const errorDiv = document.getElementById('login-failed');
    errorDiv.style.display = 'block';
  }

  const host = 'http://' + window.location.host;

  function onLogin() {
    let username = $('#username').val();
    let password = $('#password').val();

    $.ajax({
      type: "POST",
      url: `/api/user/login`,
      contentType: "application/json",
      data: JSON.stringify({username: username, password: password}),
    })
            .done(function (res, status, xhr) {
              const token = xhr.getResponseHeader('Authorization');

              Cookies.set('Authorization', token, {path: '/'})

              $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
                jqXHR.setRequestHeader('Authorization', token);
              });

              window.location.href = host;
            })
            .fail(function (jqXHR, textStatus) {
              alert("Login Fail");
              window.location.href = host + '/api/user/login-page?error'
            });
  }
</script>
</html>

또한 basic.js를 사용했는데, 이는 아래와 같다.

const host = 'http://' + window.location.host;
let targetId;
let folderTargetId;

$(document).ready(function () {
    const auth = getToken();

    if (auth !== undefined && auth !== '') {
        $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
            jqXHR.setRequestHeader('Authorization', auth);
        });
    } else {
        window.location.href = host + '/user/login-page';
        return;
    }
    console.log(getToken());
});

function logout() {
    // 토큰 삭제
    Cookies.remove('Authorization', {path: '/'});
    window.location.href = host + '/user/login-page';
}

function getToken() {

    let auth = Cookies.get('Authorization');

    if(auth === undefined) {
        return '';
    }

    // kakao 로그인 사용한 경우 Bearer 추가
    if(auth.indexOf('Bearer') === -1 && auth !== ''){
        auth = 'Bearer ' + auth;
    }

    return auth;
}

이 코드에서 가장 중요했던 점은 basic.js에서 토큰을 넘기는 방식이였는데.. 간과해버렸다.
그 결과 다른 코드들을 작성할 때 많은 시간을 검색과 고민에 할애하게 된다.


2. 회원가입

회원가입은 토큰을 가지고는 할 게 없어서, 크게  어렵진 않았다. 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link rel="stylesheet" type="text/css" href="/css/style.css">
  <meta charset="UTF-8">
  <title>회원가입 페이지</title>
  <script>
    function onclickOwner() {
      // Get the checkbox
      var checkBox = document.getElementById("owner-check");
      // Get the output text
      if (checkBox.checked == true){
        box.style.display = "block";
      } else {
        box.style.display = "none";
      }
    }

    function sendEmailVerification() {
      try {
        // Get the email input field
        var emailInput = document.getElementsByName("email")[0];

        // Extract the email address
        var email = emailInput.value;

        fetch('/api/user/mail', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ email: email }),
        })
      } catch (error) {
        console.error('Error:', error);
        alert('이메일 인증 요청을 보낼 수 없습니다.');
      }
    }
  </script>
  </script>
</head>
<body>
<div id="login-form">
  <div id="login-title">Sign up Select Shop</div>

  <form action="/api/user/signup" method="post">
    <div class="login-id-label">Username</div>
    <input type="text" name="username" placeholder="Username" class="login-input-box">

    <div class="login-id-label">Password</div>
    <input type="password" name="password" class="login-input-box">

    <div class="login-id-label">E-mail</div>
    <input type="text" name="email" placeholder="E-mail" class="login-input-box">

    <button id="email-verification-button" type="button" onclick="sendEmailVerification()">이메일 인증 요청</button>

    <div class="login-id-label">Code</div>
    <input type="text" name="code" placeholder="Code" class="login-input-box">

    <div>
      <input id="owner-check" type="checkbox" name="owner" onclick="onclickOwner()" style="margin-top: 40px;">사장님
    </div>
    <button id="login-id-submit">회원 가입</button>
  </form>
</div>
</body>
</html>

좀 신기한 오류가 발생행했는데, 
기존 이메일 보내는 Controller의 메서드는 void 형이였고 아무 문제가 없었다.
하지만 http방식으로 넘어오니, 이메일을 잘 보내고도 "이메일 인증 요청을 보낼 수 없습니다." 라는 문구와 함께
오류가 발생했다.
ResponseEntity 형으로 교체하니 잘 뜨긴했다. 아무래도 void 보다는 반환형이 있어야 하나보다..

3. Store Create, Update, Delete

여기서 post 요청시 토큰이 사라지는걸 경험했다.
로그인 요청시 코드들의 역할을 제대로 파악하지 못한 내 잘못이였고, 결국 도움을 받아 코드들의 역할을 파악하고,
아래의 코드를 완성시킬 수 있었다.
getToken의 역할에 주목해야 한다. RequestToken을 지정해줘야 다음 요청으로 토큰과 함께 이동할 수 있다.
Create, Update, Delete는 모두 버튼을 클릭하면 실행되게 해놓았고, 잘 실행이 된다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://code.jquery.com/jquery-3.7.0.min.js"
            integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
    <title>Update Store</title>
</head>
<body>
<h1>Update Store</h1>
<form>
    <label for="storeId">Store Id:</label>
    <input type="text" id="storeId" name="storeId" required>
    <br>

    <label for="updateStoreName">Store Name:</label>
    <input type="text" id="updateStoreName" name="updateStoreName" required>
    <br>

    <label for="updateStoreDetails">Store Details:</label>
    <textarea id="updateStoreDetails" name="updateStoreDetails" rows="4" required></textarea>
    <br>

    <label for="updateStoreAddress">Store Address:</label>
    <input type="text" id="updateStoreAddress" name="updateStoreAddress" required>
    <br>

    <button type="button" id="createStore">Create Store</button>
    <button type="button" id="updateStore">Update Store</button>
    <button type="button" id="deleteStore">Delete Store</button>
</form>

<!-- 업데이트 결과를 표시할 엘리먼트 -->
<div id="updateResult"></div>

<script>
    $(document).ready(function () {
        const auth = getToken();
        if (auth !== undefined && auth !== '') {
            $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
                jqXHR.setRequestHeader('Authorization', auth);
            });
        } else {
            window.location.href = host + '/user/login-page';
            return;
        }

        $('#createStore').click(function () {
            event.preventDefault(); // 기본 동작 방지

            // 폼 데이터 가져오기
            const formData = {
                storeName: $('#updateStoreName').val(),
                storeDetails: $('#updateStoreDetails').val(),
                storeAddress: $('#updateStoreAddress').val()
            };

            // 상점 생성 요청 보내기
            $.ajax({
                type: 'POST',
                url: `/api/store/create`,
                data: JSON.stringify(formData),
                contentType: 'application/json',
                success: function (response) {
                    // 상점 생성 성공 메시지를 표시
                    $('#updateResult').html('<p>Store created successfully!</p>');
                },
                error: function (error) {
                    // 상점 생성 실패 메시지를 표시
                    $('#updateResult').html('<p>Error creating store.</p>');
                }
            });
        });

        $('#updateStore').click(function () {
            // 폼 데이터 가져오기
            const formData = {
                storeId: $('#storeId').val(), // 상점 ID를 가져와서 사용
                storeName: $('#updateStoreName').val(),
                storeDetails: $('#updateStoreDetails').val(),
                storeAddress: $('#updateStoreAddress').val()
            };

            // 상점 업데이트 요청 보내기
            $.ajax({
                type: 'PUT',
                url: `/api/store/${formData.storeId}`, // 업데이트할 Store의 ID를 포함한 URL
                data: JSON.stringify(formData),
                contentType: 'application/json',
                success: function (response) {
                    // 상점 업데이트 성공 메시지를 표시
                    $('#updateResult').html('<p>Store updated successfully!</p>');
                },
                error: function (error) {
                    // 상점 업데이트 실패 메시지를 표시
                    $('#updateResult').html('<p>Error updating store.</p>');
                }
            });
        });

        // Delete 버튼 클릭 시 상점 삭제
        $('#deleteStore').click(function () {
            const storeId = $('#storeId').val();

            $.ajax({
                type: 'DELETE',
                url: `/api/store/${storeId}`,
                success: function (response) {
                    // 상점 삭제 성공 메시지를 표시
                    $('#updateResult').html('<p>Store deleted successfully!</p>');
                },
                error: function (error) {
                    // 상점 삭제 실패 메시지를 표시
                    $('#updateResult').html('<p>Error deleting store.</p>');
                }
            });
        });
    });
    // 쿠키에서 유저 정보를 읽어옵니다.
    function getToken() {
        let auth = Cookies.get('Authorization');
        if (auth === undefined) {
            return '';
        }

        // kakao 로그인 사용한 경우 Bearer 추가
        if (auth.indexOf('Bearer') === -1 && auth !== '') {
            auth = 'Bearer ' + auth;
        }

        return auth;
    }
</script>
</body>
</html>

 

4. Store Read

거의 모든 기능을 js가 해줬고, 그 이유는 딱히 디자인에 신경쓰지 않았기 때문이다.
들어가면 모든 상점의 정보가 나온다.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>User Order Information</title>
    <meta charset="UTF-8">
    <script src="https://code.jquery.com/jquery-3.7.0.min.js"
            integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
</head>
<body>
<h1>Store Information</h1>
<div id="storeInfo">
    <!-- 상점 정보가 여기에 표시됩니다. -->
</div>

<script>
    $(document).ready(function () {
        const auth = getToken();
        if (auth !== undefined && auth !== '') {
            $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
                jqXHR.setRequestHeader('Authorization', auth);
            });
        } else {
            window.location.href = host + '/user/login-page';
            return;
        }

        // 상점 정보 가져오기
        $.ajax({
            type: 'GET',
            url: `/api/store`,
            success: function (stores) {
                const storeInfoDiv = document.getElementById("storeInfo");
                stores.forEach(store => {
                    const storeItem = document.createElement("div");
                    storeItem.innerHTML = `
                        <br>
                        <p>Store Name: ${store.storeName}</p>
                        <p>Store Details: ${store.storeDetails}</p>
                        <p>Store Address: ${store.storeAddress}</p>
                        <p>Average Star: ${store.avgStar.toFixed(1)}</p>
                    `;
                    storeInfoDiv.appendChild(storeItem);
                });
            }
        });
    });

    // 쿠키에서 유저 정보를 읽어옵니다.
    function getToken() {
        let auth = Cookies.get('Authorization');
        if (auth === undefined) {
            return '';
        }

        // kakao 로그인 사용한 경우 Bearer 추가
        if (auth.indexOf('Bearer') === -1 && auth !== '') {
            auth = 'Bearer ' + auth;
        }

        return auth;
    }
</script>
</body>
</html>


이외에도 메뉴 CUD 기능을 만들었다. 하지만 pass하겠다.
프론트가 어렵다기보다는.. 너무 아무 지침도 없이 챌린지팀을 던져놓은 것 같다.
물론 고민하는 과정이 도움이 되겠지만, 다른 곳에서 고민하는게 더 좋지않을까..?
챌린지팀 운영에 조금더 신경을 써야할 듯 하다.

'프로젝트 > 미니' 카테고리의 다른 글

Eclipse, MS-SQL 연습용 프로젝트  (2) 2024.10.10
웹 미니 프로젝트 총평  (1) 2023.10.03
Cache에 Redis 적용시키기  (0) 2023.09.23
효과적인 부분 문자열 검색(Full-Text, 캐시, Redis)  (0) 2023.09.23
코드 병합  (0) 2023.09.18