[Web_3] STUSSY CLONE PROJECT 1 ๐Ÿค 

08627ยท2022๋…„ 9์›” 29์ผ
2

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
9/13
post-thumbnail


๐Ÿ“Œ Spring Boot Project ์ƒ์„ฑ
๐Ÿ‘พ Header / Login / Register Page

๐Ÿ’ก STUSSY CLONE PROJECT ์ƒ์„ฑ

๐Ÿ‘•STUSSY๐Ÿ‘•

๐ŸฆŽ PROJECT ์ƒ์„ฑ โ†’ stussy.clone.project spring initializr

  • Settings - Build, Execution, Deployment
    - Build Tools - Maven โžก๏ธ Maven Home Path : Bundled
    - Compiler - โœ… Build Project Automatically

  • application.yml ๋กœ ๋ฐ”๊พธ๊ณ 
    server - port ,
    spring - mvc(static-path-pattern) , datasource(driver, url, id, pw) ์„ค์ •.
    โ€ป database driver โ†’ External Libraries ์—์„œ ์‚ฌ์šฉํ•˜๋Š” org.db.jdbc:db ยท ยท ยท ํŒŒ์ผ์„ ์ฐพ์•„์„œ ๋„ฃ์–ด์ค€๋‹ค.

  • MyBatis
    pom.xml โ†’ dependency ์ถ”๊ฐ€

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.2.2</version>
</dependency>

ย ย ย ย  ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค mappers ์„ค์ • (xml ํŒŒ์ผ์— ๋ถ™์—ฌ๋„ฃ๊ธฐ)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
  
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
  
</mapper>

๐ŸŒ€ mybatis ์‹œ์ž‘ํ•˜๊ธฐ


โ–ซ๏ธ application.yml

server:
  port: 8000

spring:
  mvc:
    static-path-pattern: /static/**

  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb:// . . . :3306/ . . . ?allowMultiQueries=true 
    # allowMultiQueries=true ; ์„œ๋ธŒ์ฟผ๋ฆฌ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•˜๋Š” ์˜ต์…˜
    username: . . .
    password: . . .
    
  security:
    user:
      name: admin
      password: . . .

mybatis:
  mapper-locations:
    - /mappers/*.xml

๐ŸฆŽ ๊ธฐ๋ณธ ํŒจํ‚ค์ง€ ์„ค์ •
java - com . . .
> config
> controller
ย  ย  ย  > api
> domain
> dto
> repository
> service


resources
> mappers
> static
ย  ย  ย  ย  > css
ย  ย  ย  ย  > js
> templates


๐Ÿ‘พ Header / Login / Register Page

โ–ซ๏ธ header

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Account</title>
  
  	<!-- โญ GoogleFont -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  
    <link rel="stylesheet" href="/static/css/style.css">
    <link rel="stylesheet" href="/static/css/header.css">
    <link rel="stylesheet" href="/static/css/account.css">
    <script src="http://code.jquery.com/jquery-latest.min.js"></script>    
</head>
<body>
    <div id="container">
        <header>
          
            <div class="header-container">
                <div class="header-flex">
                    <div class="header-left-container">
                        <div class="logo-container">
                            <img class="site-logo" src="/static/images/logo/site_logo.png" alt="site_logo">
                        </div>
                        <div class="menu-container">
                            <div class="site-nav-menu">
                                <a href="#ALL" class="site-nav-parent">SHOP</a>
                            </div>
                        </div>
                    </div>
                    <div class="header-right-container">
                        <button class="site-header-button">SEARCH</button>
                        <button class="site-header-button">BAG(<span class="bac-count">1</span>)</button>
                        <button class="site-header-button"><img class="korea-img" src="/static/images/logo/korea_img.PNG" alt="korea"></button>
                    </div>
                </div>
            </div>
          
            <nav class="nav-invisible">
                <div class="site-nav-mega">
                    <a href="/collections/new-arrivals" class="site-nav-child-link">NEW</a>
                    <a href="/collections/denim" class="site-nav-child-link">DENIM</a>
                    <a href="/collections/tees" class="site-nav-child-link">TEES</a>
                    <a href="/collections/sweats" class="site-nav-child-link">SWEATS</a>
                    <a href="/collections/overdyed" class="site-nav-child-link">OVERDYED</a>
                    <a href="/collections/pants" class="site-nav-child-link">PANTS</a>
                    <a href="/collections/shorts" class="site-nav-child-link">SHORTS</a>
                    <a href="/collections/outerwear" class="site-nav-child-link">OUTERWEAR</a>
                    <a href="/collections/knits" class="site-nav-child-link">KNITS</a>
                    <a href="/collections/tops-shirts" class="site-nav-child-link">TOPS & SHIRTS</a>
                    <a href="/collections/headwear" class="site-nav-child-link">HEADWEAR</a>
                    <a href="/collections/accessories" class="site-nav-child-link">ACCESSORIES</a>
                    <a href="/collections/all" class="site-nav-child-link">ALL</a>
                </div>
                <div class="site-nav-mega">
                    <a href="/account/login" class="site-nav-child-link">ACCOUNT</a>
                    <a href="/pages/customer-support" class="site-nav-child-link">CONTACT</a>
                    <a href="/blogs/features" class="site-nav-child-link">FEATURES</a>
                    <a href="/blogs/chapters" class="site-nav-child-link">CHAPTERS</a>
                </div>
            </nav>
          
            <script src="/static/js/header.js"></script>
        </header>

        <main>
            <!-- ๋กœ๊ทธ์ธ login / ํšŒ์›๊ฐ€์ž… register -->
        </main>

        <footer>
        </footer>
      
    </div>
  	<!--โญ login โžก๏ธ --> <script src="/static/js/login.js"></script>
  	<!--โญ register โžก๏ธ --> <script src="/static/js/register.js"></script>
</body>
</html>

โ€ป header - js ยท css ์ƒ๋žต . . . ย 

โ–ซ๏ธ login.html

. . .
		<main>
            <div class="login">
                <h1 class="login-title">ACCOUNT</h1>
                <div class="login-container">
                    <section class="login-new">
                        <h2 class="login-subheader">ํšŒ์›๊ฐ€์ž…</h2>
                        <p class="login-content">ํšŒ์›๊ฐ€์ž…์„ ํ•˜์‹œ๋ฉด, ์ฃผ๋ฌธ ์กฐํšŒ์™€ ๊ฐœ์ธ์ •๋ณด ๊ด€๋ฆฌ ๋ฐ ๋น ๋ฅธ ์ฒดํฌ์•„์›ƒ ๋“ฑ ๋‹ค์–‘ํ•œ ํ˜œํƒ์„ ๋ˆ„๋ฆฌ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
                        <button type="button" class="black-button login-button">์‹ ๊ทœ๊ฐ€์ž…</button>
                    </section>
                    <section class="login-current">
                        <โญform action="/account/login" method="post">
                            <h2 class="login-subheader">๋กœ๊ทธ์ธ</h2>
                          
                          <!-- ๋กœ๊ทธ์ธ ์š”์ฒญ ์‹คํŒจ ์‹œ Error ๋ฅผ ๋„์›Œ์คŒ -->
                            <div class="account-errors">
                                <ul>
                                    <li th:text="${error}"></li>
                                </ul>
                            </div>
                          
                            <input type="email" class="login-input" name="email" placeholder="์ด๋ฉ”์ผ" required>
                            <input type="password" class="login-input" name="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" required>
                            <a href="" class="login-show-recover">๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์žŠ์œผ์…จ๋‚˜์š”?</a>
                            <button ๐Ÿ“type="submit" class="black-button login-button">๋กœ๊ทธ์ธ</button>
                        </form>
                    </section>
                </div>
            </div>
        </main>
. . .

<form> action : ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ URL ์ฃผ์†Œ

๐ŸฆŽ Thymeleaf

ํƒ€์ž„๋ฆฌํ”„๋Š” View Template(๋ทฐ ํ…œํ”Œ๋ฆฟ)์˜ ์ผ์ข…์œผ๋กœ, ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ „๋‹ฌํ•˜๋Š” Model(Key, Value) ๊ฐ์ฒด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ๋™์ ์œผ๋กœ ํ™”๋ฉด์— ๋„์›Œ์ค€๋‹ค.

  • th:text="${key}"
    โžก๏ธ ํ…์ŠคํŠธ๋กœ ์ง€์ •ํ•˜๊ณ , key์˜ value๋ฅผ ํ˜ธ์ถœํ•จ.

โ–ซ๏ธ login.js

// ์‹ ๊ทœ๊ฐ€์ž…
const registerGoButton = document.querySelectorAll(".login-button")[0]; 
// ๋กœ๊ทธ์ธ
const loginButton = document.querySelectorAll(".login-button")[1]; 

registerGoButton.onclick = () => {
    location.href = "/account/register";
}

loginButton.onclick = () => { // form ์— ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๋‹ด์•„์„œ ์ œ์ถœ
    const loginform = document.querySelector("form"); 
}

โ–ซ๏ธ register.html

. . .
		<main>          
            <div class="login">
                <h1 class="login-title">ACCOUNT</h1>
                <div class="login-container">
                    <section class="login-current">
                        <h2 class="login-subheader">์‹ ๊ทœ ํšŒ์›๊ฐ€์ž…</h2>
                      
                      <!-- ValidationError ๋ฅผ ๋„์›Œ์คŒ -->
                        <div class="account-errors errors-invisible">
                            <ul>
                                
                            </ul>
                        </div>
                      
                        <div class="login-name">
                            <div class="login-half">
                                <input type="text" class="login-input" placeholder="์ด๋ฆ„">
                            </div>
                            <div class="login-half">
                                <input type="text" class="login-input" placeholder="์„ฑ">
                            </div>
                        </div>
                        <input type="email" class="login-input" placeholder="์ด๋ฉ”์ผ">
                        <input type="password" class="login-input" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ">
                        <button type="button" class="black-button login-button">ํ™•์ธ</button>
                        <a href="/account/login" class="login-return">๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</a>
                    </section>
                </div>
            </div>          
        </main>
. . .

โ–ซ๏ธ register.js

const registerButton = document.querySelector(".login-button");
const registerInputs = document.querySelectorAll(".login-input");

registerButton.onclick = () => {

    let registerInfo = {
        lastName: registerInputs[0].value,
        firstName: registerInputs[1].value,
        email: registerInputs[2].value,
        password: registerInputs[3].value
    }

    $.ajax({
        async: false,
        type: "post",
        url: "/api/account/register",
        โœ”๏ธ contentType: "application/json",
        โœ”๏ธ data: JSON.stringify(registerInfo), // JSON ์œผ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ๋ณด๋ƒ„
        dataType: "json",
        success: (response) => {	// ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต ์‹œ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
            location.replace("/account/login"); 
        },
        error: (error) => {
            console.log(error);
          validationError(error.responseJSON.data);
        }
    });
}


function validationError(error) {
    const accountErrors = document.querySelector(".account-errors");
    const accountErrorList = accountErrors.querySelector("ul");

    const errorValues = Object.values(error); // โžก๏ธ error์˜ value๋“ค์„ ๋ฐฐ์—ด๋กœ ๋ฆฌํ„ดํ•จ. 

    accountErrorList.innerHTML = "";

    errorValues.forEach((value) => {
        accountErrorList.innerHTML += `
            <li>${value}</li>
        `;
    });
	
  	// โ€ป error ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ์—๋งŒ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋จ. 
  	// ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ณด์ด์ง€ ์•Š๋„๋ก ํ•˜๊ณ , ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰ ๋˜๋Š” ๊ฒฝ์šฐ ๋ณด์ด๋„๋ก ํ•จ. (css ์„ค์ •)
    accountErrors.classList.remove("errors-invisible");
}

โ€ป css ์ƒ๋žต . . . ย 



์ฐธ๊ณ  ๐Ÿง™
- ์Šคํ”„๋ง ๊ฐ€์ด๋“œ ์ฐธ๊ณ  ์‚ฌ์ดํŠธ ๋ชจ์Œ
- ์นด์นด์˜ค ์ฃผ์†Œ API
- ๋กœ์ปฌ(Local) API ๊ตฌํ˜„ ๋ฐฉ๋ฒ•


๐Ÿ“ข ์†Œ๊ฐ ๐Ÿค 
์ด๋ฒˆ์ฃผ๋Š” ์™• ๋ฐ”๋นด๋‹ค

0๊ฐœ์˜ ๋Œ“๊ธ€