[TIL] 내배캠 사전캠프 9일차

yeols·2023년 9월 8일
0

[TIL]

목록 보기
8/72

오늘의 챌린지

오늘 조원분께서 좋은 사이트를 알려주셨다. 감사합니다!! 😄
frontend기술을 연습할 수 있는 사이트 Frontend Mentor여기 사이트를 소개 시켜주셨는데 소개 받자마자 Newsletter sign-up form with success message라는 챌린지를 도전 했다.

데스크탑 버전과 모바일 버전으로 간단게 만드는 챌린지이지만 아직 실력이 없는 나로서는 아주 좋은 도전 거리라고 할 수 있겠다.


아직 모바일 버전은 하지 않은 상태

html :

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- displays site properly based on user's device -->

    <link
      rel="icon"
      type="image/png"
      sizes="32x32"
      href="./assets/images/favicon-32x32.png"
    />

    <title>
      Frontend Mentor | Newsletter sign-up form with success message
    </title>
    <!--fonts-->
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
      rel="stylesheet"
    />
    <!--fonts end-->
    <link rel="stylesheet" href="./css/styles.css" />

    <!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
    <style>
      /*.attribution { font-size: 11px; text-align: center; }*/
      /*.attribution a { color: hsl(228, 45%, 44%); }*/
    </style>
  </head>
  <body>
    <!-- Sign-up form start -->
    <section class="sign-up-section">
      <div class="sign-up__contents">
        <div>
          <h1>Stay updated!</h1>
          <p>Join 60,000+ product managers receiving monthly updates on:</p>
          <div class="sign-up__check-box">
            <div class="sign-up__check">
              <img src="./assets/images/icon-list.svg" alt="" />
              <span>Product discovery and building what matters</span>
            </div>
            <div class="sign-up__check">
              <img src="./assets/images/icon-list.svg" alt="" />
              <span>Measuring to ensure updates are a success</span>
            </div>
            <div class="sign-up__check">
              <img src="./assets/images/icon-list.svg" alt="" />
              <span>And much more!</span>
            </div>
          </div>
          <form id="sign-up__form">
            <div class="sign-up__btn">
              <div class="sign-up__btn-text">
                <span>Email address</span>
                <span class="error-text hidden">Valid email required</span>
              </div>
              <input id="email" type="email" placeholder="email@company.com" />
              <div class="sign-up__btn-div">
                <a id="sign-up__btn-submit" href="#"
                  >Subscribe to monthly newsletter</a
                >
              </div>
            </div>
          </form>
        </div>
      </div>
      <div class="sign-up__img">
        <img src="./assets/images/illustration-sign-up-desktop.svg" alt="" />
      </div>
    </section>

    <section class="success-section hidden">
      <div class="success__contents">
        <img src="./assets/images/icon-success.svg" alt="" />
        <h1>Thanks for subscribing!</h1>
        <p>
          A confirmation email has been sent to
          <span>ash@loremcompany.com</span>. Please open it and click the button
          inside to confirm your subscription.
        </p>
        <div class="success__btn-div">
          <a id="" href="#">Dismiss message</a>
        </div>
      </div>
    </section>

    <!--<div class="attribution">
      Challenge by
      <a href="https://www.frontendmentor.io?ref=challenge" target="_blank"
        >Frontend Mentor</a
      >. Coded by <a href="#">yeols</a>.
    </div>-->
    <script src="./js/app.js"></script>
  </body>
</html>

하나의 html 파일안에 2가지의 페이지를 넣는다.
1. signup 페이지
2. success 페이지


css:

/* Reset */
body {
  margin: unset;
  padding: unset;
}

h1,
h2,
h3 {
  margin: unset;
  font-size: unset;
}

p,
span {
  margin: unset;
}

a {
  margin: unset;
  text-decoration: unset;
}
input {
  outline: unset;
}

/* Typography*/
:root {
  font-size: 10px;
}

body {
  font-family: 'Roboto', sans-serif;
  font-size: 1.6rem;
  line-height: 1.67;
}

/* Contents*/

body {
  height: 100vh;
  display: flex;
  padding: 40px 20px;
  box-sizing: border-box;
  background-color: #36384d;
  color: hsl(234, 29%, 20%);
}

.sign-up-section,
.success-section {
  position: absolute;
  inset: 0;
  max-width: 1000px;
  max-height: 700px;
  margin: auto;
  display: flex;
  justify-content: space-between;
  padding: 25px;
  box-sizing: border-box;
  background-color: #ffffff;
  border-radius: 33px;
}

.sign-up__contents {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 50%;
  font-size: 1.6rem;
  overflow: scroll;
}

.sign-up__contents > div {
  margin: auto;
  width: 80%;
}

h1 {
  font-size: 5.5rem;
  line-height: 1.2;
}

.sign-up__contents p {
  margin: 1.2rem 0;
}

.sign-up__check-box {
  margin: 20px 0;
}

.sign-up__contents .sign-up__check {
  display: flex;
  align-items: center;
}

.sign-up__contents .sign-up__check > img {
  margin-right: 10px;
}

.sign-up__contents .sign-up__btn {
  display: flex;
  flex-direction: column;
}

.sign-up__contents .sign-up__btn-text span {
  font-size: 1.2rem;
  font-weight: bold;
}

.sign-up__contents .sign-up__btn-text {
  display: flex;
  justify-content: space-between;
}

.sign-up__contents .sign-up__btn-text span:first-child {
  color: #545361;
}
.sign-up__contents .sign-up__btn-text span:last-child,
.sign-up__contents .sign-up__btn > input.error {
  color: #d97772;
}

.sign-up__contents .sign-up__btn > input.error {
  border: 0.1rem solid #d97772;
  background-color: #ffe8e6;
}

.sign-up__contents .sign-up__btn > input.error:focus {
  border: 0.2rem solid #d97772;
}

.sign-up__contents .sign-up__btn > input,
.sign-up__contents .sign-up__btn > div,
.success__btn-div {
  border-radius: 9px;
}

.sign-up__contents .sign-up__btn > input {
  padding: 18px;
  margin-top: 5px;
  margin-bottom: 20px;
  border: 0.1rem solid rgba(0, 0, 0, 0.2);
}
.sign-up__contents .sign-up__btn > input:focus {
  border: 0.1rem solid rgba(0, 0, 0, 0.5);
}

.sign-up__contents .sign-up__btn > input::placeholder {
  color: rgba(0, 0, 0, 0.4);
  font-size: 1.6rem;
}

.sign-up__contents .sign-up__btn > .sign-up__btn-div,
.success__btn-div {
  text-align: center;
  background-color: #222641;
  position: relative;
  transition: opacity 0.5s;
}
.sign-up__contents .sign-up__btn > .sign-up__btn-div:after,
.success__btn-div:after {
  content: '';
  opacity: 0;
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  border-radius: inherit;
  transition: opacity 0.5s;
  background: linear-gradient(to left, #ff693b, #f3597c);
}
.sign-up__contents .sign-up__btn > .sign-up__btn-div:hover:after,
.success__btn-div:hover:after {
  opacity: 1;
}

.sign-up__contents .sign-up__btn > .sign-up__btn-div > a,
.success__btn-div > a {
  display: block;
  color: #ffffff;
  position: relative;
  padding: 15px 0;
  height: 100%;
  z-index: 2;
}

.sign-up__img {
  display: flex;
}

.hidden {
  display: none;
}

/* success */
.success-section {
  max-width: 500px;
  max-height: 500px;
  padding: 50px 65px 50px;
  box-sizing: border-box;
}

.success__contents {
  margin: auto;
  height: 100%;
  overflow: scroll;
}
.success__contents > img {
  width: 60px;
  height: 60px;
}

.success__contents > h1 {
  margin-top: 10px;
}
.success__contents > p {
  margin-top: 20px;
  margin-bottom: 30px;
  font-size: 1.6rem;
}
.success__contents > p > span {
  font-weight: bold;
}

css는 필요한 테그들의 reset을 하고 베이스가되는 font-sizefonts, line-height를 설정한다.

이번 주제는 contents가 계속 증가하는 페이지가 아닌 고정형이기에
body에 height: 100vh;로 잡고 display: flex;걸어 주었다.
그리고 body하위의 section에 margin: auto;를 주어 각 section이 중앙 정렬 되게 만들었다.

sign-up section내부 큰 div를 넣고 그 하위에 텍스트, input, btn이 있는 그룹 div와 이미지를 담을 div 2개를 나누었고 그 상위에 display: flex;로 각각 좌 우로 나누었다.
flex를 사용한 이유는 추 후에 모바일 버전은 이미지가 상단에 나오고 텍스트쪽이 하단으로 가야하기때문에 flex를 사용하였고 모바일때 flex-direction: column-reverse;를 걸어줄 생각이다.


javascript:

// 필요 테그
const emailInput = document.querySelector('#email');
const emailForm = document.querySelector('#sign-up__form');
const emailErrorText = emailForm.querySelector('.error-text');
const emailSubmitBtn = emailForm.querySelector('#sign-up__btn-submit');
const signSection = document.querySelector('.sign-up-section');
const successSection = document.querySelector('.success-section');
const printEmail = document.querySelector('.success__contents > p > span');
const dismissBtn = document.querySelector('.success__btn-div');
// const 전역 변수 설정
const CLASS_HIDDEN = 'hidden';
const CLASS_ERROR = 'error';

// email 정규식 함수
const isEmail = (emailValue) => {
  const regExp =
    /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
  return regExp.test(emailValue);
};

// class 초기화 함수
const onResetClass = (emailInputClass, emailErrorClass) => {
  emailInputClass.remove(CLASS_ERROR);
  emailErrorClass.add(CLASS_HIDDEN);
};

// error 상태 class 추가 함수
const onAddErrorClass = () => {
  const emailErrorClass = emailErrorText.classList;
  const emailInputClass = emailInput.classList;

  // error text hidden class 제거
  emailErrorClass.remove(CLASS_HIDDEN);
  // email input error class 추가
  emailInputClass.add(CLASS_ERROR);
};

// section hidden class toggle 함수
const onSectionClass = () => {
  // subscribe 페이지 hidden 추가 or 삭제
  signSection.classList.toggle(CLASS_HIDDEN);
  // 완료 페이지 hidden class 추가 or 삭제
  successSection.classList.toggle(CLASS_HIDDEN);
};
//email input에 입력되는 값 추적
emailInput.addEventListener('input', ({ target: { value } }) => {
  // 지역 변수
  const emailErrorClass = emailErrorText.classList;
  const emailInputClass = emailInput.classList;

  // 아무 입력이 없을때 class 초기화
  if (value === '') {
    onResetClass(emailInputClass, emailErrorClass);
    return;
  }

  if (isEmail(value)) {
    // email의 형식이 맞다면 class 초기화
    onResetClass(emailInputClass, emailErrorClass);
  } else {
    // error 상태 class 추가 함수 호출
    onAddErrorClass();
  }
});

// submit 버튼이 입력 됐을 때 email 확인
emailSubmitBtn.addEventListener('click', (event) => {
  event.preventDefault();
  // 입력된 이메일 데이터 받기
  let submitEmailValue = emailInput.value;
  if (submitEmailValue === '' || !isEmail(submitEmailValue)) {
    // error 상태 class 추가 함수 호출
    onAddErrorClass();
    // email input 으로 focus 옮기기
    emailInput.focus();
    return;
  }

  // section class toggle 함수 호출
  onSectionClass();

  // 입력된 이메일 출력
  printEmail.innerText = submitEmailValue;
  // 이메일 input 초기화
  emailInput.value = '';
});

// dismiss message btn 클릭시 sign-up 페이지로 돌리기
dismissBtn.addEventListener('click', (event) => {
  // section class toggle 함수 호출
  onSectionClass();
});

js파일에서는 사용자가 입력할 때 마다 email 유효성 검증을하고 email 양식에 맞지않으면 input에 error class를 추가하고, error text는 hidden class를 삭제하여 지금 입력 하고있는 text는 email형식에 맞지않다는걸 알려준다.

그리고 Subscribe to monthly newsletter 버튼을 누르면 email을 유효성 검사를 하고 맞지않으면 위와 같은 error text를 출력하고 input박스에 error class를 추가하고 input에 focus가 갈수있도록 했다.

email 유효성 검사가 통과했다면 .sign-up-section은 hidden class를 추가하여 숨기고 email input을 초기화한다.
.success-section에 hidden class를 삭제하고 화면에 보여주며 입력받은 email을 출력한다.

email에 검증되는 정규식은 RFC 5322형식을 사용하였다.


profile
흠..

0개의 댓글

관련 채용 정보