Front-End Github - Dobby Project
Product Manager : 홍지영(Front-End)
Project Manager : 유진서(Back-End)
Template Designer : 김성호(Front-End)
Front-End Teammate : 김성호, 박주희, 최민지
Back-End Teammate : 조민수, 김영범, 최현수
Front-End Stack
React
, Javascript
, HTML
, SCSS
Back-End Tool
Javascript
, Node.js
, Express
, MySQL
Communication Tool
Git & Github
, Slack
, Notion
, Trello
Code Program
Terminal
, Visual Studio Code
우리 서비스 회원으로의 유입과 전환을 책임져 줄 웹서비스 기술을 구현.
우리 서비스가 판매할 가상의 제품을 분석하고 이를 판매할 기술을 구현.
고객의 실제 매출로 이루어질 수 있는 결제 절차를 기술적으로 구현.
로그인/회원가입 - 제품 - 주문
의 절차 중 해당 내용을 미리 받아내어 결제 허들을 낮추는 방법이 있지 않을까?자사 제품을 주문하는 절차를 웹 서비스 내에서 기술적으로 구현.
-> 워크 플로우는 메인페이지에 로그인이 되어 있는지 유무를 확인한뒤 회원으로 가입되어 로그인이 되어있으면 구독 서비스를 이용할수 있도록 하였고, 만약에 Dobby 회원으로 가입되어 있지 않다면? 새로 회원가입을 해서 로그인 후에 서비스를 이용할수 있도록 구성했고, 반대로 회원으로 가입되어 있지만 로그인을 안했을땐? 로그인 페이지로 이동해서 로그인후에 서비스(상품결제, 장바구니)를 이용할 수 있도록 플로우를 구성하였습니다.
-> ERD는 총 9가지 순으로 구성되어 있으며 Dobby 서비스를 이용하는 회원이 새로 회원가입하면 users의 정보가 DataBase에 저장되고 로그인 했을때 comments, products, cart와 연결되어 각각의 기능들에게 적용될수 있도록 ERD를 구성하였습니다.
1) 로그인 -> 유저
- 요구사항
- email과 Password를 이용해서 가입자 여부를 확인할수 있어야 합니다.
- 서비스 이용 토큰 발급 여부가 가능해야 합니다.
- 필수 데이터 : email, password
- 예외 처리
password
유효성 검사시 버튼 비활성화, 활성화 여부가 확인 되어야 합니다.- 쿠키에 토큰(token)을 담아서 발행해야 합니다.
- 로그인 성공시
nickname
과token
을 발행해야 합니다.
2) 회원가입 -> 유저
- 요구사항 : 회원 이용 약관을 모두 동의했을때 회원가입이 성공할수 있도록 해야합니다.
- 필수 데이터 : email, password, name, nickname, phonenumber, 약관
- 예외 처리
- email에는
.
과@
이 필수 포함되어야 합니다.- 비밀번호는 10자리 이상 필요합니다.(추가. 비밀번호 확인)
- name은 유효성 검사가 필요합니다.
3) 로그아웃 -> 유저
- 요구사항
- 쿠키에 담겨있는 Token이 삭제되어야 합니다.
- 로그아웃을 눌렀을때 Main 페이지로 이동해야 합니다.
- 필수 데이터 : 쿠키에 담겨있는 Token
4) 메인 구독 -> 메인
- 요구사항 : 사용자가 DIY 구독제를 선택하여 바로 결제할수 있도록 결제 페이지로 이동할수 있도록 구성해주세요.
5) 메인 스토어 -> 메인
- 요구사항
- 카테고리 3개(전체 창작적, 수집성), 스와이퍼(슬라이더) 3개 구성해야합니다.
- 스와이퍼(신상품 , Best, MD 추천)
- 스와이퍼는 좌우로 클릭이 가능해야 합니다.
- ICON 이미지 클릭시 리스트 페이지로 이동합니다.
6) 제품 리스트 페이지 -> 제품
- 요구사항
- 메인 카테고리 ICON 클릭시 제품 리스트 페이지로 이동합니다.
- 창작적(리빙, 패브릭,푸드 DIY) & 수집성(캐릭터 & 인형, 스티커 & 메모지)
- 제품 클릭시 제품 상세 페이지로 이동합니다.
7) 제품 상세 페이지 -> 제품
- 요구사항
- 클릭한 제품의 상세 페이지를 볼수있도록 해야 합니다.
- 원하는 수량을 장바구니에 담을 수 있습니다.
- 후기를 확인할수 있어야 합니다.
8) 장바구니 페이지 -> 제품
- 요구사항
- 장바구니에 담은 제품의 목록을 확인할수 있습니다.
- 장바구니에 담긴 제품의 수량을 수정할수 있습니다.(추가 & 삭제)
9) 결제 페이지 -> 제품
- 요구사항
- 선택한 제품을 포인트로 결제할수 있어야 합니다.
- 배송지 입력이 가능해야 합니다.
- 배송지 여러개 일수 있어서 List 페이지를 만들어야 합니다.
- 장바구니에 담은 결제할 상품 List를 보여줄수 있어야 합니다.(사진 포함)
상세역할 : 프로덕트 개발 담당
• 로그인 : 입력한 회원 정보를 Back-End API로 전송하여 로그인 여부 확인
• 회원가입 : 회원 이용 약관 동의 시 회원가입 버튼 활성화 및 중복 가입 방지 기능 구현
• 아이디 찾기 : 휴대폰 인증번호 입력 후 매칭되는 ID를 알림창에 표시
• 비밀번호 찾기 : 이메일 인증번호 입력 후 새로운 비밀번호 DataBase 저장
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import './Login.scss';
const Login = () => {
// ID(이메일)
const [id, setID] = useState('');
const saveUserID = event => {
setID(event.target.value);
};
// PW(비밀번호)
const [pw, setPW] = useState('');
const saveUserPW = event => {
setPW(event.target.value);
};
// 로그인 버튼
const isInvalid = id.includes('@', '.') && pw.length >= 10;
// 회원가입 버튼(회원가입 페이지로 이동)
const navigate = useNavigate();
const goToSignup = () => {
navigate('/signup');
};
const goToFindID = () => {
navigate('/findid');
};
const goToFindPW = () => {
navigate('/findpw');
};
const goToMain = () => {
fetch('http://10.58.52.121:8000/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
email: id,
password: pw,
}),
})
.then(response => response.json())
.then(data => {
if (data.message === 'LOG_IN_SUCCESS') {
alert('로그인 되었습니다.');
localStorage.setItem('nickname', data.nickname);
localStorage.setItem('token', data.token);
navigate('/main?dobbyBox=basic');
} else if (data.message === 'INVALID EMAIL OR PASSWORD') {
alert('가입되지 않은 정보입니다.');
}
});
};
return (
<div className="mainLoginBody">
<h1 className="helloText">안녕하세요!😊</h1>
<p className="intoText">즐거움으로 찾아오는 인생취미, Dobby 입니다.</p>
<div className="loginTextFrame">
<h2 className="loginText">LOGIN</h2>
</div>
<div className="inputFrame">
<input
className="idInput"
type="text"
onChange={saveUserID}
placeholder="이메일"
/>
<input
className="pwInput"
type="password"
onChange={saveUserPW}
placeholder="비밀번호"
/>
<button
className={isInvalid ? 'loginButton' : 'disabledButton'}
disabled={isInvalid ? false : true}
onClick={goToMain}
>
로그인
</button>
</div>
<div className="idpwButtonFrame">
<button className="idButton" onClick={goToFindID}>
아이디 찾기
</button>
<button className="pwButton" onClick={goToFindPW}>
비밀번호 찾기
</button>
</div>
<div className="signupButtonFrame">
<button className="signupButton" onClick={goToSignup}>
회원가입
</button>
</div>
</div>
);
};
export default Login;
이메일, 비밀번호를 입력했을때 로그인 버튼 활성화 (로그인 가능)
(2) 회원가입 : 정규 표현식을 사용하여 이메일, 비밀번호, 닉네임, 전화번호, 이름에 맞게 회원정보를 작성할수 있도록 유효성 검사 및 오류 메세지 표시기능과, fetch 메서드를 이용하여 회원 이용 약관에 모두 동의했을때 회원가입 버튼 활성화 여부 및 회원가입 성공/실패(중복가입방지) 기능을 구현하였습니다.
import React, { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import './Signup.scss';
const Signup = () => {
// 이메일, 비밀번호, 비밀번호 확인, 이름, 닉네임 확인
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [passwordConfir, setPasswordConfir] = useState('');
const [name, setName] = useState('');
const [phonenumber, setPhoneNumber] = useState('');
const [nickname, setNickName] = useState('');
// (이메일, 비밀번호, 비밀번호 확인, 이름, 닉네임) 오류 메세지 상태 저장
const [emailMessage, setEmailMessage] = useState('');
const [passwordMessage, setPasswordMessage] = useState('');
const [passwordConfirMessage, setPasswordConfirMessage] = useState('');
const [nameMessage, setNameMessage] = useState('');
const [phonenumberMessage, setPhonenumberMessage] = useState('');
const [nicknameMessage, setNickNameMessage] = useState('');
// (이메일, 비밀번호, 비밀번호 확인, 이름, 닉네임) 입력창 유효성 검사
const [isEmail, setIsEmail] = useState(false);
const [isPassword, setIsPassword] = useState(false);
const [isPasswordConfir, setIsPasswordConfir] = useState(false);
const [isName, setIsName] = useState(false);
const [isPhonenumber, setIsPhonenumber] = useState(false);
const [isNickName, setIsNickName] = useState(false);
// 체크박스(state)
const [allCheck, setAllcheck] = useState(false);
const [useCheck, setUsecheck] = useState(false);
const [infoCheck, setInfocheck] = useState(false);
// 이메일
const onChangeEmail = useCallback(event => {
const emailRagex =
/([\w-.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
const emailCurrent = event.target.value;
setEmail(emailCurrent);
if (!emailRagex.test(emailCurrent)) {
setEmailMessage('올바른 이메일 형식이 아닙니다.');
setIsEmail(false);
} else {
setEmailMessage('올바른 이메일 형식입니다.');
setIsEmail(true);
}
}, []);
// 비밀번호
const onChangePassword = useCallback(event => {
const passwordRagex = /^(?=.*[a-zA-Z])(?=.*\d).{9,}$/;
const passwordCurrent = event.target.value;
setPassword(passwordCurrent);
if (!passwordRagex.test(passwordCurrent)) {
setPasswordMessage('영문과 숫자를 혼합해서 10자리 이상 입력해주세요.');
setIsPassword(false);
} else {
setPasswordMessage('안전한 비밀번호 입니다.');
setIsPassword(true);
}
}, []);
// 비밀번호 확인
const onChangePasswordConfir = useCallback(
event => {
const passwordConfirCurrent = event.target.value;
setPasswordConfir(passwordConfirCurrent);
if (password === passwordConfirCurrent) {
setPasswordConfirMessage('비밀번호가 일치합니다.');
setIsPasswordConfir(true);
} else {
setPasswordConfirMessage(
'비밀번호가 일치하지 않습니다. 다시한번 확인해주세요',
);
setIsPasswordConfir(false);
}
},
[password],
);
// 이름
const onChangeName = useCallback(event => {
const nameRagex = /^[ㄱ-ㅎ|가-힣]+$/;
const nameCurrent = event.target.value;
setName(nameCurrent);
if (!nameRagex.test(nameCurrent)) {
setNameMessage('올바른 이름 형식이 아닙니다.');
setIsName(false);
} else {
setNameMessage('올바른 이름 형식입니다.');
setIsName(true);
}
}, []);
// 전화번호
const onChangePhoneNumber = useCallback(event => {
const phoneNumberRagex = /^(01[016789]{1})[0-9]{3,4}[0-9]{4}$/;
const phoneNumberCurrent = event.target.value;
setPhoneNumber(phoneNumberCurrent);
if (!phoneNumberRagex.test(phoneNumberCurrent)) {
setPhonenumberMessage('전화번호를 올바르게 입력해주세요');
setIsPhonenumber(false);
} else {
setPhonenumberMessage('정확한 전화번호 입니다.');
setIsPhonenumber(true);
}
}, []);
// 닉네임
const onChangeNickName = useCallback(event => {
const nicknameRagex = /^[a-z|A-Z]+$/;
const nicknameCurrent = event.target.value;
setNickName(nicknameCurrent);
if (!nicknameRagex.test(nicknameCurrent)) {
setNickNameMessage('닉네임은 영어로 입력해주세요');
setIsNickName(false);
} else {
setNickNameMessage('올바른 닉네임 형식입니다.');
setIsNickName(true);
}
}, []);
// 모두 동의 버튼(전체 체크박스를 클릭했을때)
const inValid = !allCheck;
const allBtnevent = () => {
if (allCheck === false) {
setAllcheck(true);
setUsecheck(true);
setInfocheck(true);
} else {
setAllcheck(false);
setUsecheck(false);
setInfocheck(false);
}
};
// 이용약관 동의 버튼
const useBtnevent = () => {
if (useCheck === false) {
setUsecheck(true);
} else {
setUsecheck(false);
}
};
// 개인정보 수집 버튼
const infoBtnevent = () => {
if (infoCheck === false) {
setInfocheck(true);
} else {
setInfocheck(false);
}
};
// 2개 버튼 모두체크시 전체동의 자동체크
useEffect(() => {
if (useCheck === true && infoCheck === true) {
setAllcheck(true);
} else {
setAllcheck(false);
}
}, [useCheck, infoCheck]);
// 로그인 페이지 이동
const navigate = useNavigate();
const goToLogin = () => {
navigate('/');
};
// 회원가입 정보 입력후 회원가입 하기 버튼
const goToSignup = () => {
fetch('http://10.58.52.121:8000/users/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
email: email,
password: password,
name: name,
phonenumber: phonenumber,
nickname: nickname,
}),
})
.then(response => response.json())
.then(data => {
if (data.message === 'SIGN_UP_SUCCESS') {
alert('회원가입이 완료 되었습니다.');
goToLogin('/');
} else {
alert('이미 가입된 사용자 입니다.');
}
console.log(data);
});
};
return (
<div className="signupBody">
<header className="headerFrame">
<h1 className="headerText">환영합니다!</h1>
<p className="subText">지금 회원가입하면 최대 100 POINT를 드려요</p>
</header>
<form className="inputFrame">
<div className="formbox">
<input
className="userInput"
type="text"
value={email}
onChange={onChangeEmail}
placeholder="이메일을 입력해주세요"
/>
{email.length > 0 && (
<span className={`message ${isEmail ? 'success' : 'error'}`}>
{emailMessage}
</span>
)}
</div>
<div className="formbox">
<input
className="userInput"
type="password"
value={password}
onChange={onChangePassword}
placeholder="비밀번호를 입력해주세요"
maxLength={10}
/>
{password.length > 0 && (
<span className={`message ${isPassword ? 'success' : 'error'}`}>
{passwordMessage}
</span>
)}
</div>
<div className="formbox">
<input
className="userInput"
type="password"
onChange={onChangePasswordConfir}
placeholder="비밀번호를 다시한번 입력해주세요"
maxLength={10}
/>
{passwordConfir.length > 0 && (
<span
className={`message ${isPasswordConfir ? 'success' : 'error'}`}
>
{passwordConfirMessage}
</span>
)}
</div>
<div className="formbox">
<input
className="userInput"
type="text"
value={name}
onChange={onChangeName}
placeholder="이름을 입력해주세요"
/>
{name.length > 0 && (
<span className={`message ${isName ? 'success' : 'error'}`}>
{nameMessage}
</span>
)}
</div>
<div className="formbox">
<input
className="userInput"
type="phonenumber"
value={phonenumber}
onChange={onChangePhoneNumber}
placeholder="전화번호를 입력해주세요"
maxLength={13}
/>
{phonenumber.length > 0 && (
<span className={`message ${isPhonenumber ? 'success' : 'error'}`}>
{phonenumberMessage}
</span>
)}
</div>
<div className="formbox">
<input
className="userInput"
type="text"
value={nickname}
onChange={onChangeNickName}
placeholder="닉네임을 입력해주세요"
/>
{nickname.length > 0 && (
<span className={`message ${isNickName ? 'success' : 'error'}`}>
{nicknameMessage}
</span>
)}
</div>
</form>
<p className="coditionsText">Dobby 서비스 이용약관에 동의해주세요.</p>
<div className="checkFormBox">
<span className="checkboxFrame">
<input
className="allCheck"
type="checkbox"
checked={allCheck}
onChange={allBtnevent}
/>
<p className="checkText">모두 동의합니다</p>
</span>
<span className="checkboxFrame">
<input
className="termsCheck"
type="checkbox"
checked={useCheck}
onChange={useBtnevent}
/>
<p className="checkText">(필수) 이용 약관 동의</p>
</span>
<span className="checkboxFrame">
<input
className="personalCheck"
type="checkbox"
checked={infoCheck}
onChange={infoBtnevent}
/>
<p className="checkText">(필수) 개인정보 수집 및 이용 동의</p>
</span>
</div>
<div>
<button
className={inValid ? 'disableButton' : 'signupButton'}
onClick={goToSignup}
>
회원가입 하기
</button>
</div>
</div>
);
};
export default Signup;
import React, { useState } from 'react';
import './FindID.scss';
const FindID = () => {
// 휴대폰 번호 입력시
const [phoneNumber, setPhoneNumber] = useState('');
// 인증번호 입력시
const [number, setNumber] = useState('');
// 휴대폰 유효성 검사
const [phoneNumberMessage, setPhoneNumberMessage] = useState('');
// 인증번호 유효성 검사
const [numberMessage, setNumberMessage] = useState('');
// 휴대폰 오류 메세지
const [isPhoneNumber, setIsPhoneNumber] = useState(false);
// 인증번호 오류 메세지
const [isNumber, setIsNumber] = useState(false);
// 전화번호 조건식
const onChangePhoneNumber = event => {
const phoneNumberRagex = /^01(?:0|1|[6-9])(?:\d{3}|\d{4})\d{4}$/;
const phoneNumberCurrent = event.target.value;
setPhoneNumber(phoneNumberCurrent);
if (!phoneNumberRagex.test(phoneNumberCurrent)) {
setPhoneNumberMessage('입력한 전화번호를 다시한번 확인해주세요');
setIsPhoneNumber(false);
} else {
setPhoneNumberMessage('확인되었습니다.');
setIsPhoneNumber(true);
}
};
// 인증번호 조건식
const onChangeVerifinNumber = event => {
const numberRagex = /^[0-9]+$/;
const numberCurrent = event.target.value;
setNumber(numberCurrent);
if (!numberRagex.test(numberCurrent)) {
setNumberMessage('전송받은 인증번호를 다시한번 확인해주세요');
setIsNumber(false);
} else {
setNumberMessage('확인되었습니다.');
setIsNumber(true);
}
};
// 이름과 휴대폰 번호 입력값이 모두 비어있지 않을때 버튼 활성화
const phoneinValid = phoneNumber.length !== 11;
const verifinValid = number.length !== 6;
// 인증번호 받기 버튼
const handleVerifin = () => {
fetch('http://10.58.52.121:8000/users/phoneauth', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
phoneNumber: phoneNumber,
}),
})
.then(response => response.json())
.then(data => {
if (data.message === 'AUTHENTICATION_NUMBER_SUCCESS') {
alert('문자로 인증번호를 전송했습니다. 확인해주세요');
} else {
alert('아이디를 찾을 수 없습니다.');
}
});
};
// 아이디 찾기 버튼
const handleIDFind = () => {
fetch('http://10.58.52.121:8000/users/phoneauth/phoneverifynumber', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
phoneNumber: phoneNumber,
number: number,
}),
})
.then(response => response.json())
.then(data => {
if (data.message === 'FIND_ID_SUCCESS') {
alert(`회원님의 아이디는 ${data.email} 입니다.`);
} else {
alert('아이디를 찾을 수 없습니다.');
}
});
};
return (
<div className="idFindFrame">
<h1 className="idText">아이디 찾기</h1>
<form className="findIDFrame">
<input
className="userInput"
type="phomenumber"
maxLength={11}
onChange={onChangePhoneNumber}
placeholder="휴대폰 번호를 입력해주세요 (- 제외)"
/>
{phoneNumber.length > 0 && (
<span className={`message ${isPhoneNumber ? 'success' : 'error'}`}>
{phoneNumberMessage}
</span>
)}
</form>
<form className="findIDFrame">
<input
className="userInput"
type="text"
maxLength={6}
onChange={onChangeVerifinNumber}
placeholder="인증번호를 입력해주세요"
/>
{number.length > 0 && (
<span className={`message ${number ? 'success' : 'error'}`}>
{numberMessage}
</span>
)}
</form>
<div className="phoneButtonFrame">
<button
className={phoneinValid ? 'disabledButton' : 'findIDButton'}
disabled={phoneinValid}
onClick={handleVerifin}
>
인증번호 받기
</button>
<div className="verifinButtonFrame">
<button
className={verifinValid ? 'disabledButton' : 'findIDButton'}
disabled={verifinValid}
onClick={handleIDFind}
>
아이디 찾기
</button>
</div>
</div>
</div>
);
};
export default FindID;
휴대폰 인증번호 확인후 입력창에 인증번호 6자리 입력시 아이디 찾기 버튼 활성화
(버튼을 누르면 alert(알림창)로 회원의 아이디(이메일)을 보여줍니다.)
(4) 비밀번호 찾기 : fetch 메서드를 사용하여 이메일을 입력후 본인 인증하기 버튼을 누르면, 이메일 인증번호 전송 여부 성공/실패 기능과 이메일로 인증번호 6자리를 받고 새로운 비밀번호를 입력하면, 이전의 비밀번호는 초기화 되고 새로운 비밀번호가 회원 정보로 새로 DataBase에 등록되어 로그인 할때 적용되는 기능을 구현하였습니다.
import React, { useState } from 'react';
import './FindPW.scss';
import { useNavigate } from 'react-router-dom';
const FindPW = () => {
// 이메일 입력시
const [email, setEmail] = useState('');
// 인증번호, 새 비밀번호, 새 비밀번호 확인 입력시
const [number, setNumber] = useState('');
const [newPassword, setNewPassword] = useState('');
const [newpasswordConfir, setNewPasswordConfir] = useState('');
// 이메일 유효성 검사
const [emailMessage, setEmailMessage] = useState('');
// 인증번호, 새 비밀번호, 새 비밀번호 확인 유효성 검사
const [numberMessage, setNumberMessage] = useState('');
const [newpasswordMessage, setNewPasswordMessage] = useState('');
const [newpasswordConfirMessage, setNewPasswordConfirMessage] = useState('');
// 이메일 오류 메세지
const [isEmail, setIsEmail] = useState(false);
// 인증번호, 새 비밀번호, 새 비밀번호 확인 오류 메세지
const [isNumber, setIsNumber] = useState(false);
const [isNewPassword, setIsNewPassword] = useState(false);
const [isNewPasswordConfir, setIsNewPasswordConfir] = useState(false);
// 이메일 조건식
const onChangeEmail = event => {
const emailRagex =
/([\w-.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
const emailCurrent = event.target.value;
setEmail(emailCurrent);
if (!emailRagex.test(emailCurrent)) {
setEmailMessage('올바른 이메일 형식이 아닙니다.');
setIsEmail(false);
} else {
setEmailMessage('올바르게 입력하셨습니다.');
setIsEmail(true);
}
};
// 인증번호 조건식
const onChangeNumber = event => {
const numberRagex = /^[0-9]+$/;
const numberCurrent = event.target.value;
setNumber(numberCurrent);
if (!numberRagex.test(numberCurrent)) {
setNumberMessage('전송받은 인증번호를 다시한번 확인해주세요');
setIsNumber(false);
} else {
setNumberMessage('확인되었습니다.');
setIsNumber(true);
}
};
// 새 비밀번호 조건식
const onChangeNewPassword = event => {
const newPasswordRagex = /^(?=.*[a-zA-Z])(?=.*\d).{9,}$/;
const passwordCurrent = event.target.value;
setNewPassword(passwordCurrent);
if (!newPasswordRagex.test(passwordCurrent)) {
setNewPasswordMessage('영문+숫자 조합으로 10자리 이상 입력해주세요.');
setIsNewPassword(false);
} else {
setNewPasswordMessage('안전한 비밀번호 입니다.');
setIsNewPassword(true);
}
};
// 새 비밀번호 확인 조건식
const onChangeNewPasswordConfir = event => {
const newPasswordConfirCurrent = event.target.value;
setNewPasswordConfir(newPasswordConfirCurrent);
if (newPassword === newPasswordConfirCurrent) {
setNewPasswordConfirMessage('비밀번호가 일치합니다.');
setIsNewPasswordConfir(true);
} else {
setNewPasswordConfirMessage(
'비밀번호가 일치하지 않습니다. 다시한번 확인해주세요',
);
setIsNewPasswordConfir(false);
}
};
// 이메일 예외처리
const emailIsvalid = email.includes('@') && email.includes('.');
// 새 비밀번호, 새 비밀번호 확인 예외 처리
const newPasswordIsvalid =
number.length && newPassword.length >= 8 && newpasswordConfir.length >= 8;
const navigate = useNavigate();
const goToLogin = () => {
navigate('/');
};
// 인증받기 버튼
const handleCertificate = () => {
fetch('http://10.58.52.121:8000/users/emailauth', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
email: email,
}),
})
.then(response => response.json())
.then(data => {
if (data.message === 'AUTHENTICATION_MAIL_SUCCESS') {
alert('이메일로 인증번호를 전송했습니다. 확인해주세요');
} else {
alert('아이디를 찾을 수 없습니다.');
}
});
};
// 새 비밀번호 등록하기 버튼
const handleNewPassword = () => {
fetch('http://10.58.52.121:8000/users/emailauth/emailverifynumber', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({ email, number, newPassword }),
})
.then(response => response.json())
.then(data => {
if (data.message === 'AUTHENTICATION_PASSWORD_SUCCESS') {
alert('비밀번호 등록이 완료되었습니다.');
goToLogin('/');
} else {
alert('비밀번호를 다시한번 확인해주세요');
}
});
};
return (
<div className="findPWFrame">
<h1 className="findpwText">비밀번호 찾기</h1>
<form className="formPWFrame">
<div>
<input
className="emailInput"
type="text"
onChange={onChangeEmail}
placeholder="이메일을 입력해주세요"
/>
{email.length > 0 && (
<span className={`message ${isEmail ? 'success' : 'error'}`}>
{emailMessage}
</span>
)}
</div>
</form>
<div className="confirButtonFrame">
<button
className={emailIsvalid ? 'confirButton' : 'disabledButton'}
disabled={emailIsvalid ? false : true}
onClick={handleCertificate}
>
본인 인증하기
</button>
</div>
<form className="formCertFrame">
<div>
<input
className="userInput"
type="text"
onChange={onChangeNumber}
maxLength={6}
placeholder="인증번호를 입력해주세요"
/>
{number.length > 0 && (
<span className={`message ${isNumber ? 'success' : 'error'}`}>
{numberMessage}
</span>
)}
</div>
</form>
<form className="formCertFrame">
<div>
<input
className="userInput"
type="password"
onChange={onChangeNewPassword}
placeholder="새 비밀번호를 입력해주세요"
/>
{newPassword.length > 0 && (
<span className={`message ${isNewPassword ? 'success' : 'error'}`}>
{newpasswordMessage}
</span>
)}
</div>
</form>
<form className="formCertFrame">
<div>
<input
className="userInput"
type="password"
onChange={onChangeNewPasswordConfir}
placeholder="새 비밀번호를 다시한번 입력해주세요"
/>
{newpasswordConfir.length > 0 && (
<span
className={`message ${isNewPasswordConfir ? 'success' : 'error'}`}
>
{newpasswordConfirMessage}
</span>
)}
</div>
</form>
<div className="confirButtonFrame">
<button
className={newPasswordIsvalid ? 'confirButton' : 'disabledButton'}
disabled={newPasswordIsvalid ? false : true}
onClick={handleNewPassword}
>
새 비밀번호 등록하기
</button>
</div>
</div>
);
};
export default FindPW;
[기술 문제]
- 글자 형식으로 된 버튼은 UI 시인성이 낮아, 사용자들이 버튼인지 Text인지 인식하기 어렵고, 시력이 좋지 않은 사용자의 경우에는 시각적인 피로감이 증가하여 서비스 이용에 불편함을 겪을 수 있기 때문에, 이에 따른 글자 형식의 아이디 & 비밀번호 찾기 버튼을 개선해야 할 문제가 있었습니다.
[고민]
- 이로써 사용자의 이탈이 언제든 쉽게 이루어진다는 것을 근거로 사용자들의 혼란을 줄이고 편의성을 높이기 위해 버튼의 User Interface를 보완시킬 필요성을 느끼게 되었습니다.
[시도 방법]
- 글자 형식으로 된 아이디 & 비밀번호 찾기 버튼을 색깔별로 구분해서 쉽게 인식할수 있도록 UI를 변경하여 사용자들이 쉽게 파악할 수 있도록 하는 방법으로 보완하였습니다.
[개선 성과]
- 사용자들이 로그인 페이지 내에서 아이디 & 비밀번호 찾기 버튼을 쉽게 찾을 수 있도록 개선함과 동시에 사용자 이탈을 방지하고 편의성을 증가시킬수 있었습니다.
긍정적인 점
배웠던 점
아쉬웠던 부분