Skip to content

Commit

Permalink
Feat/modal design : 로그인 모달 완성 및 헤더 button과의 연결 (#8)
Browse files Browse the repository at this point in the history
* signup modal design

* feat,styles signupmodal의 input validation 기능 구현

* styles 네이밍 변경, 모달의 세부 스타일 변경

* refactor : 비밀번호 유효성 체크 유틸 함수 생성

* refactor :  리뷰 반영_input 길이 검사 패턴 변경

* refactor : 리뷰 반영_기타사항

* cicd : yaml 파일 변경

* docs : 컨밴션 등의 내용을 포함하여  README 수정

* feat : 로그인 모달 디자인 & LayoutModal에 모달 관리 state 추가

* style : modalContainer css 변경
  • Loading branch information
ohsuhyeon0119 authored Jan 6, 2024
1 parent b2b7561 commit 21eadf3
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 25 deletions.
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
# team6-web (와플피디아)
# team6-web

## 왓챠피디아 클론 코딩
# WafflePedia (왓챠피디아 클론코딩 팀프로젝트)

## 프로젝트 소개

왓챠피디아 웹서비스 클론 코딩 팀프로젝트의 웹서버 레포입니다.

배포 도메인 : <https://d1vexdz72u651e.cloudfront.net/>

개발 기간 : 2023.12.28 ~

## 배포 방식

cloudfront + s3 으로 배포 관리하며, github actions으로 main에 push 시 자동 빌드 및 배포

## 기술스택

- REACT
- Typescript
- scss + css module

## CONVENTION

### 코드

- CSS는 카멜케이스로 네이밍한다. #.module.scss 파일을 컴포넌트 파일에 styles 로 import한 뒤, scss 내의 클래스네임을 컴포넌트 파일에서 styles. 으로 접근하여 사용

### 협업

- 화면 flow는 figma에서 관리하고, FE가 구현해야 할 특별한 기능의 경우 각 화면 페이지 별로 내용과 의견을 적어준다.
- 분담한 작업은 왓챠피디아 웹페이지와 따로 정리한 기능 명세서를 참고하여 구현한다.
- 주 1회의 공통 회의와 별도로 FE 개별 회의 1회를 비대면(슬랙 허들)로 진행한다.

## 깃 관련

- github-flow 방식을 차용한다. main이 배포 브랜치이며, 각 작업은 개별 브랜치(ex. feat/example )를 로컬에서 만들어 진행한다.

- 작업이 끝나면 로컬->원격으로 push를 하고 main에 PR을 올린다. PR을 올린 직후에는 SLACK에 정해진 양식에 맞추어 나머지 팀원에게 알려준다.

- PR을 올린 날 기준 익일 오전 10시까지 각 팀원은 리뷰를 진행하고, 리뷰가 없거나 리뷰 내의 특별한 사항이 없을 시에, PR을 올린 팀원이 스스로 main에 머지한다. 리뷰에 따라 변경해야 할 코드가 있다면 변경 후 원격에 push 한 뒤 재리뷰를 요청한다.

- 특이 사항이 없는 이상 PR 머지 시 squash & merge를 사용한다.

- main에 merge가 이루어진 후에는 slack에서 팀원들에게 merge를 완료하였음을 알려준다. 나머지 팀원은 변경된 원격의 main을 로컬에 반영하도록 한다. 즉 remote main branch -> local branch로 pull하여 최신화

- 작업 중인 브랜치가 feat/example2 인 경우 main -> feat/example2로 머지한다. conflict 발생 시 논의하여 수정하고 다시 작업을 이어나간다.
<- PR시의 conflict를 최대한 방지하려는 의도입니다>

**local main branch -> remote main branch push 하지 말 것**
8 changes: 2 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import "./App.css";
import SignupModal from "./components/SignupModal";

function App() {
return (
<>
<SignupModal />
</>
);
return <></>;
}

export default App;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
width: 100vw;
height: 100vh;
position: fixed;
background-color: rgba(0, 0, 0, 0.297);
z-index: 100;
top: 0px;
background-color: rgba(0, 0, 0, 0.504);
display: flex;
justify-content: center;
align-items: center;
Expand Down Expand Up @@ -31,20 +33,20 @@
section {
margin: 0 20px;

.toLoginModalBox {
.toLoginModalBox,
.toSignUpModalBox {
margin-top: 20px;
font-size: 15px;
color: rgb(140, 140, 140);
text-align: center;
button {
font-size: 15px;
background: none;
padding: 0px;
border: none;
margin: 0px;
cursor: pointer;
color: rgb(255, 47, 110);
font-size: inherit;
font-weight: 400;
}
}
.divisionLine {
Expand Down Expand Up @@ -172,14 +174,14 @@ form {
.kakaoLoginButton {
background: rgb(247, 227, 23);
img {
transform: scale(1);
transform: scale(1.1);
}
}
.googleLoginButton {
border: 1px solid rgb(227, 228, 230);
background: white;
img {
transform: scale(0.9);
transform: scale(1.1);
}
}
.naverLoginButton {
Expand Down
27 changes: 23 additions & 4 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CurrentModalType } from "../pages/Layout";
import styles from "./Header.module.scss";
import {
Link,
Expand All @@ -7,15 +8,19 @@ import {
} from "react-router-dom";
import { useState, useEffect } from "react";

export default function Header() {
type HeaderProps = {
setCurrentModal: (modal: CurrentModalType) => void;
};

export default function Header({ setCurrentModal }: HeaderProps) {
const navigate = useNavigate();
const searchParams = useSearchParams()[0];
const query = searchParams.get("query");
const location = useLocation();

const [searchInput, setSearchInput] = useState("");

const logined = true; //for test
const logined = false; //for test
const transparent = /^\/contents\/[a-zA-Z]+$/.test(location.pathname);

useEffect(() => {
Expand Down Expand Up @@ -75,10 +80,24 @@ export default function Header() {
) : (
<>
<li className={styles.loginLi}>
<button className={styles.loginButton}>로그인</button>
<button
className={styles.loginButton}
onClick={() => {
setCurrentModal("login");
}}
>
로그인
</button>
</li>
<li className={styles.loginLi}>
<button className={styles.registerButton}>회원가입</button>
<button
className={styles.registerButton}
onClick={() => {
setCurrentModal("signup");
}}
>
회원가입
</button>
</li>
</>
)}
Expand Down
162 changes: 162 additions & 0 deletions src/components/LoginModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { useState } from "react";
import Logo from "../assets/logo.svg";
import styles from "./AuthModalStyle.module.scss";
import { CurrentModalType } from "../pages/Layout";

type LoginModalProps = {
setCurrentModal: (currentModal: CurrentModalType) => void;
};

export default function LoginModal({ setCurrentModal }: LoginModalProps) {
const [idInput, setIdInput] = useState("");
const [passwordInput, setPasswordInput] = useState("");
const error = {
id:
idInput.length < 2 || idInput.length > 20 || idInput.includes(" ")
? "정확하지 않은 아이디입니다."
: "",
password:
passwordInput.length < 10 || passwordInput.includes(" ")
? "비밀번호는 공백이 없는 10자리 이상이어야 합니다."
: "",
};
const isAllInputsValid = !error.id && !error.password; // input이 모두 유효한지 확인

return (
<div
className={styles.modalContainer}
onClick={() => {
setCurrentModal(null);
}}
>
<div
className={styles.modalBox}
onClick={(e) => {
e.stopPropagation();
}}
>
<img
src={Logo}
className={styles.watchaPediaLogo}
alt="watchaPediaLogo"
/>
<h2>로그인</h2>
<section>
<form action="#">
<label
className={`${
!error.id || !idInput ? styles.validLabel : styles.invalidLabel
}`}
>
<input
autoComplete="off"
placeholder="아이디"
type="text"
value={idInput}
onChange={(e) => {
setIdInput(e.target.value);
}}
/>
{!!idInput && (
<div
className={styles.inputClearIcon}
onClick={() => setIdInput("")}
/>
)}
{!!idInput && (
<div className={styles.validationIconBox}>
<div
className={`${styles.validationIcon} ${
!error.id ? styles.validIcon : styles.invalidIcon
}`}
/>
</div>
)}
</label>

{!!idInput && <p className={styles.errorMessage}>{error.id}</p>}

<label
className={`${
!error.password || !passwordInput
? styles.validLabel
: styles.invalidLabel
}`}
>
<input
autoComplete="off"
placeholder="비밀번호"
type="password"
value={passwordInput}
onChange={(e) => setPasswordInput(e.target.value)}
/>
{!!passwordInput && (
<div
className={styles.inputClearIcon}
onClick={() => setPasswordInput("")}
/>
)}
{!!passwordInput && (
<div className={styles.validationIconBox}>
<div
className={`${styles.validationIcon} ${
!error.password ? styles.validIcon : styles.invalidIcon
}`}
/>
</div>
)}
</label>

{!!passwordInput && (
<p className={styles.errorMessage}>{error.password}</p>
)}

<button
type="button"
disabled={!isAllInputsValid}
onClick={() => {
// console.log("로그인");
}}
>
로그인
</button>
</form>
<div className={styles.toSignUpModalBox}>
계정이 없으신가요?{" "}
<button
onClick={() => {
setCurrentModal("signup");
}}
>
회원가입
</button>
</div>
<div className={styles.divisionLine}>
<span>OR</span>
</div>
<ul className={styles.socialLoginButtonList}>
<li>
<button className={styles.kakaoLoginButton}>
<img
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTIuMDM5NCAxOC4zQzE3LjAzMTggMTguMyAyMS4wNzg5IDE1LjA5ODggMjEuMDc4OSAxMS4xNUMyMS4wNzg5IDcuMjAxMTYgMTcuMDMxOCA0IDEyLjAzOTQgNEM3LjA0NzA5IDQgMyA3LjIwMTE2IDMgMTEuMTVDMyAxMy43MjQ5IDQuNzIwNzUgMTUuOTgxOSA3LjMwMjI5IDE3LjI0MDdDNy4wMzYwNyAxOC4zNTU0IDYuNTY4NTUgMjAuMzE5OCA2LjU1MTQ3IDIwLjQzODVDNi41Mjc1NCAyMC42MDQ4IDYuNzE5MjUgMjAuNzQwNiA2Ljg4NzU4IDIwLjYyNTFDNy4wMTA1IDIwLjU0MDggOS4yNTI5NSAxOS4wMTAyIDEwLjQ1NDEgMTguMTkwNEMxMC45Njg4IDE4LjI2MjQgMTEuNDk4NiAxOC4zIDEyLjAzOTQgMTguM1oiIGZpbGw9IiMzQzFFMUUiLz4KPC9zdmc+Cg=="
alt="kakaoLoginButton"
/>
</button>
</li>
<li>
<button className={styles.googleLoginButton}>
<img
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMjAuNjQgMTIuMjA0MkMyMC42NCAxMS41NjYgMjAuNTgyNyAxMC45NTI0IDIwLjQ3NjQgMTAuMzYzM0gxMlYxMy44NDQ2SDE2Ljg0MzZDMTYuNjM1IDE0Ljk2OTYgMTYuMDAwOSAxNS45MjI4IDE1LjA0NzcgMTYuNTYxVjE4LjgxOTJIMTcuOTU2NEMxOS42NTgyIDE3LjI1MjQgMjAuNjQgMTQuOTQ1MSAyMC42NCAxMi4yMDQyVjEyLjIwNDJaIiBmaWxsPSIjNDI4NUY0Ii8+CiAgICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTExLjk5OTggMjFDMTQuNDI5OCAyMSAxNi40NjcgMjAuMTk0MSAxNy45NTYxIDE4LjgxOTVMMTUuMDQ3NSAxNi41NjEzQzE0LjI0MTYgMTcuMTAxMyAxMy4yMTA3IDE3LjQyMDQgMTEuOTk5OCAxNy40MjA0QzkuNjU1NjcgMTcuNDIwNCA3LjY3MTU4IDE1LjgzNzIgNi45NjM4NSAxMy43MUgzLjk1NzAzVjE2LjA0MThDNS40Mzc5NCAxOC45ODMxIDguNDgxNTggMjEgMTEuOTk5OCAyMVYyMVoiIGZpbGw9IiMzNEE4NTMiLz4KICAgIDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNi45NjQwOSAxMy43MDk4QzYuNzg0MDkgMTMuMTY5OCA2LjY4MTgyIDEyLjU5MyA2LjY4MTgyIDExLjk5OThDNi42ODE4MiAxMS40MDY2IDYuNzg0MDkgMTAuODI5OCA2Ljk2NDA5IDEwLjI4OThWNy45NTgwMUgzLjk1NzI3QzMuMzQ3NzMgOS4xNzMwMSAzIDEwLjU0NzYgMyAxMS45OTk4QzMgMTMuNDUyMSAzLjM0NzczIDE0LjgyNjYgMy45NTcyNyAxNi4wNDE2TDYuOTY0MDkgMTMuNzA5OFYxMy43MDk4WiIgZmlsbD0iI0ZCQkMwNSIvPgogICAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMi4wNDI3IDYuNTc5NTVDMTMuMzY0MSA2LjU3OTU1IDE0LjU1MDUgNy4wMzM2NCAxNS40ODMyIDcuOTI1NDVMMTguMDY0NSA1LjM0NDA5QzE2LjUwNTkgMy44OTE4MiAxNC40Njg2IDMgMTIuMDQyNyAzQzguNTI0NTUgMyA1LjQ4MDkxIDUuMDE2ODIgNCA3Ljk1ODE4TDcuMDA2ODIgMTAuMjlDNy43MTQ1NSA4LjE2MjczIDkuNjk4NjQgNi41Nzk1NSAxMi4wNDI3IDYuNTc5NTVWNi41Nzk1NVoiIGZpbGw9IiNFQTQzMzUiLz4KPC9zdmc+Cg=="
alt="googleLoginButton"
/>
</button>
</li>
<li>
<button className={styles.naverLoginButton}></button>
</li>
</ul>
</section>
</div>
</div>
);
}
30 changes: 25 additions & 5 deletions src/components/SignupModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useState } from "react";
import validatePassword from "../utils/validatePassword";
import Logo from "../assets/logo.svg";
import styles from "./SignupModal.module.scss";
import styles from "./AuthModalStyle.module.scss";
import { CurrentModalType } from "../pages/Layout";

export default function SignupModal() {
type SignupModalProps = {
setCurrentModal: (currentModal: CurrentModalType) => void;
};

export default function SignupModal({ setCurrentModal }: SignupModalProps) {
const [nameInput, setNameInput] = useState("");
const [idInput, setIdInput] = useState("");
const [passwordInput, setPasswordInput] = useState("");
Expand All @@ -26,8 +31,16 @@ export default function SignupModal() {
const isAllInputsValid = !error.name && !error.id && !error.password; // input이 모두 유효한지 확인

return (
<div className={styles.modalContainer}>
<div className={styles.modalBox}>
<div
className={styles.modalContainer}
onClick={() => setCurrentModal(null)}
>
<div
className={styles.modalBox}
onClick={(e) => {
e.stopPropagation();
}}
>
<img
src={Logo}
className={styles.watchaPediaLogo}
Expand Down Expand Up @@ -148,7 +161,14 @@ export default function SignupModal() {
</button>
</form>
<div className={styles.toLoginModalBox}>
이미 가입하셨나요? <button>로그인</button>
이미 가입하셨나요?{" "}
<button
onClick={() => {
setCurrentModal("login");
}}
>
로그인
</button>
</div>
<div className={styles.divisionLine}>
<span>OR</span>
Expand Down
2 changes: 1 addition & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ body,
height: 100%;
margin: 0;
padding: 0;
font-family: "Noto Sans KR";
}
* {
box-sizing: border-box;
font-family: "Noto Sans KR";
}

button {
Expand Down
Loading

0 comments on commit 21eadf3

Please sign in to comment.