1. 프로젝트 간략 소개
- 참고 사이트 : 미래식당 https://meesig.com/
- 사이트 소개 : 로컬 푸드 마켓 '미래식당' 사이트에 사용된 기능들을 참고하여 개발한 프로젝트
- 선정 이유 : 웹서비스에서 보편적으로 사용되는 기능들을 이해하고 이를 React를 사용함으로써 웹페이지가 어떻게 구현이 될 수 있는지에 대한 궁금증을 풀어보며, React 숙련도 향상을 위해 구현하기 쉬운 E-commerce 사이트로 선정을 하였습니다.
- 내가 참여한 코딩레스토랑팀 FE Github : https://github.com/Roxy100/justcode-6-1st-coding-restaurant-front
- BE Github : https://github.com/wecode-bootcamp-korea/justcode-6-1st-coding-restaurant-back
- 개발 인원 : 프론트엔드 4명(박예선, 박찬영, 이은지, 임지영), 백엔드 2명(김민우, 이윤재)
2. 팀에서 내가 맡은 역할 및 해당 기능 설명
- 시연 연상(ver. Full) : https://youtu.be/f1He2K5uwyI

주요한 해당 기능 설명 ( 이미지 첨부 및 코드 추가)
✅ 로그인, 회원가입, Footer
🔷 [공통] 소셜로그인 파트는 UI만 구현함. 아직 소셜계정을 통한 가입 및 로그인 기능은 구현은 제외.
🔷 [공통] Footer UI 구현. - 모든 페이지 footer 바탕색 흰색으로 통일하기로 수정.


🔷 이메일로 가입하기 버튼 눌렀을 때, form 입력창 펼치도록 구현

- 해당 코드
<div className={css['account-type-box']}>
<a className={css.btn} onClick={emailSignUpClick}>
이메일로 가입하기
</a>
</div>
{emailSignUp && (
<div className={css['member-by-email']}>
<form
className={css['account-form-body']}
onSubmit={e => {
e.preventDefault();
}}
>
///....해당 form 입력창 부분들 생략
</form>
</div>
// 초기값으로 설정된 false값이
const [emailSignUp, setEmailSignUp] = useState(false);
// 클릭했을 때 true로 바꿀 수 있게 해주는 함수
const emailSignUpClick = e => {
setEmailSignUp(true);
};
🔷 회원가입 유효성 검사
- 이메일 - @포함, '.' 2개 이상 포함
- 비밀번호 - 10자리 이상
- 이름 - 한글 2~4자리
- 전화번호 - 010으로 시작, 10~11자리
- 생년월일 - 1900년대생, 올바른 월/일 입력
🔷 위에 있는 유효성 검사에 맞지 않을 시, alert창으로 자세하게 안내말 적음.
🔷 유효성 검사가 다 맞을 시,
✔ 백엔드에서 요청한 메세지 'userCreated'가 맞을 시.
alert창으로 '로그인 페이지로 이동합니다' 띄우고, Login페이지로 이동.
✔ 맞지 않을 시, alert창으로 '회원가입에 실패하였습니다' 띄움.
// 각각 초기값으로 설정된 이메일,패스워드,재패스워드,이름,전화번호,생일,성별 state들
const [isValid, setIsValid] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [rePassword, setRePassword] = useState('');
const [name, setName] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [birth, setBirth] = useState('');
const [gender, setGender] = useState('');
const [femaleBorder, setFemaleBorder] = useState('#d3d3d3');
const [maleBorder, setMaleBorder] = useState('#d3d3d3');
// 이메일 input에 onChange함수
const onEmailHandle = e => {
setEmail(e.target.value);
};
// 패스워드 input에 onChange함수
const onPasswordHandle = e => {
setPassword(e.target.value);
};
// 재패스워드 input에 onChange함수
const onRePasswordHandle = e => {
setRePassword(e.target.value);
};
// 이름 input에 onChange함수
const onNameHandle = e => {
setName(e.target.value);
};
// 전화번호 input에 onChange함수
const onPhoneNumberHandle = e => {
setPhoneNumber(e.target.value);
};
// 생일 input에 onChange함수
const onBirthHandle = e => {
setBirth(e.target.value);
};
// 해당 성별을 클릭했을 때 나타나는 border색깔
const genderClick = e => {
const genderValue = e.target.value;
setGender(genderValue);
if (genderValue == 'female') {
setFemaleBorder('#bfaf96');
setMaleBorder('#d3d3d3');
} else if (genderValue == 'male') {
setMaleBorder('#bfaf96');
setFemaleBorder('#d3d3d3');
}
};
// 이름, 전화번호, 생일은 정규식패턴으로 설정하여 보다 더 편하도록 변수로 설정.
// 이름 - 한글 2~4자리
const name_pattern = /^[가-힣]{2,4}$/;
// 전화번호 - 010으로 시작, 10~11자리
const phone_pattern = /^010-?([0-9]{3,4})-?([0-9]{4})$/;
// 생년월일 - 1900년대생, 올바른 월/일 입력
const birthday_pattern =
/^(19[0-9][0-9]|20\d{2})(0[0-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$/;
// 이메일로 가입하기 클릭했을 때
const userSignUp = () => {
// 이메일 - @포함, '.' 2개 이상 포함
email.includes('@') &&
2 >= email.split('.').length - 1 >= 1 &&
// 비밀번호 - 10자리 이상
password.length >= 10
? setIsValid(true)
: setIsValid(false);
if (password !== rePassword) {
alert('비밀번호를 확인하세요!');
} else if (name_pattern.test(name) == false) {
alert('이름은 2~4자리여야 합니다.');
} else if (phone_pattern.test(phoneNumber) == false) {
alert('전화번호는 010으로 시작하는 10~11자리여야 합니다.');
} else if (birthday_pattern.test(birth) == false) {
alert('생년월일 8자리를 확인해주세요.');
} else if (!gender) {
alert('성별을 확인해주세요.');
} else {
fetch('http://localhost:8000/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
name
phoneNumber,
birth,
gender,
}),
})
.then(response => response.json())
.then(result => {
if (result.message == 'userCreated') {
alert(
'미래식당의 회원이 되어주셔서 감사합니다:) \n로그인 페이지로 이동합니다.'
);
navigate('/login');
} else {
alert('회원가입에 실패하였습니다.');
}
});
}
};
🔷 로그인 유효성 검사
- 이메일 - @포함, '.'2개 이상
- 비밀번호 - 10자리 이상
🔷 위에 있는 유효성 검사에 맞지 않을 시, alert창으로 자세하게 안내말 적음.
🔷 유효성 검사가 다 맞을 시,
✔ email과 password를 localStorage에 보관하며,
alert창으로 '로그인에 성공하였습니다' 띄우고, Main페이지로 이동.
✔ 로그인 실패시, alert창으로 '이메일과 비밀번호에 실패하였습니다.' 띄움.로그인, 회원가입
// 각각 초기값으로 설정된 이메일,패스워드 state들
const [userEmail, setUserEmail] = useState('');
const [userPassword, setUserPassword] = useState('');
// 사용자이메일 input에 onChange함수
const onUserEmailHandle = e => {
setUserEmail(e.target.value);
};
// 사용자패스워드 input에 onChange함수
const onUserPasswordHandle = e => {
setUserPassword(e.target.value);
};
// 로그인하기 클릭했을 때
const userLogin = () => {
if (
// 이메일 - @포함, '.'2개 이상
!userEmail.includes('@') ||
userEmail.split('.').length - 1 < 1 ||
userEmail.split('.').length - 1 > 2
) {
alert('사용자 이메일이 맞지 않습니다.');
// 비밀번호 - 10자리 이상
} else if (userPassword.length < 10) {
alert('비밀번호는 10자리이상이어야 합니다.');
} else {
fetch('http://localhost:8000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: userEmail,
password: userPassword,
}),
})
.then(response => response.json())
.then(result => {
if (result.token) {
localStorage.setItem('token', result.token);
alert('로그인에 성공하였습니다.');
window.location.replace('/');
} else {
alert('이메일과 비밀번호를 찾을 수 없습니다.');
}
});
}
};
✅ 장바구니 기능
🔷 장바구니 기능 (조회, 삭제, 수정)

주요한 해당 기능 설명 ( 코드 추가)
🔹 조회(GET) - 장바구니 페이지로 갔을 때, 장바구니에 담았던 데이터들을 보여주도록 구현.
// fetch부분 구현 나에게 역할 추가
useEffect(() => {
fetch('http://localhost:8000/carts', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
})
.then(res => res.json())
.then(req => {
setUserName(req.data.name);
setCartList(req.data.cartList);
setItemTotal(req.data.totalPrice);
setTotalDelivery(req.data.deliveryFee);
setTotalPrice(req.data.orderPrice);
});
}, [itemState]);
🔹 삭제(DELETE) - 삭제 버튼 눌렀을 때, 상품 목록이 사라지는 구현.
const deleteClick = () => {
// 여기부터 fetch부분 구현 나에게 역할 추가
fetch('http://localhost:8000/carts', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify({
cartsId: item.id,
}),
}).then(res => res.json());
// 여기까지...
itemState === true ? setItemState(false) : setItemState(true);
setCartCount(cartCount - 1);
};
🔹 수정(PATCH) - 수량을 + or - 할 때, 상품의 수량이 변화되는 구현.
const countPlus = () => {
let newPrice = Number(`${(count + 1) * item.price}`);
setCount(count + 1);
setItemTotalPrice(newPrice);
// 여기부터 fetch부분 구현 나에게 역할 추가
fetch('http://localhost:8000/carts', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify({
cartsId: item.id,
quantity: 1,
}),
}).then(res => res.json());
// 여기까지...
itemState === true ? setItemState(false) : setItemState(true);
};
const countMinus = () => {
count >= 2 && setCount(count - 1);
count >= 2 && setItemTotalPrice(Number(`${(count - 1) * item.price}`));
count >= 2 &&
// 여기부터 fetch부분 구현 나에게 역할 추가
fetch('http://localhost:8000/carts', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify({
cartsId: item.id,
quantity: -1,
}),
}).then(res => res.json());
// 여기까지...
itemState === true ? setItemState(false) : setItemState(true);
};
나는 '미래식당' 사이트를 봤을 때, 내가 자신있게 구현할 수 있는 건, 로그인, 회원가입 페이지들이었다.
Justgram할 때 배웠던 거 복습하고자 아직 함수 메소드 map등 쓰는 게 자신이 없기 때문에 그 페이지들을 먼저 선택했다. 팀원분들과 상의하면서 디테일한 UI는 제외하고, 큰 틀 레이아웃 중심으로 짜보면서, 모르는 것은 팀원분들께 물어보기도 하고, 소셜로그인 파트는 제대로 된 기능은 하지 않지만, UI만 만들어 놓는 걸 중심으로 했다. 나중에, 소셜계정을 통한 가입 및 로그인 기능은 더 공부해서 구현하도록 해야겠다.
그리고, 거의 완성되어갈 쯤, Footer UI 구현도 말끔히 2~3시간 만에 구현하고, 다른 분이 맡으신 장바구니페이지(UI만 작업완료된 상태)에서 장바구니 기능으로 조회, 삭제, 수정을 하는 작업이 나에게 추가되어, fetch를 이용한 코드 공부(구글링)도 하면서 작은 보탬이 되어 보다 더 빠른 상황으로 나갈 수 있었다. 그리고 계속적으로 팀원분들과도 소통하면서, 버그 픽스 및 리팩토링도 진행하였다.
3. 기억하고 싶은 코드
(1) 가장 어려웠던 코드 및 아쉬운 코드
const name_pattern = /^[가-힣]{2,4}$/;
const phone_pattern = /^010-?([0-9]{3,4})-?([0-9]{4})$/;
const birthday_pattern =
/^(19[0-9][0-9]|20\d{2})(0[0-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$/;
const userSignUp = () => {
email.includes('@') &&
2 >= email.split('.').length - 1 >= 1 &&
password.length >= 10
? setIsValid(true)
: setIsValid(false);
if (password !== rePassword) {
alert('비밀번호를 확인하세요!');
} else if (name_pattern.test(name) == false) {
alert('이름은 2~4자리여야 합니다.');
} else if (phone_pattern.test(phoneNumber) == false) {
alert('전화번호는 010으로 시작하는 10~11자리여야 합니다.');
} else if (birthday_pattern.test(birth) == false) {
alert('생년월일 8자리를 확인해주세요.');
} else if (!gender) {
alert('성별을 확인해주세요.');
}
- 익숙하지 않은 정규식 코드로 유효성 검사하는 것 자체가 어려웠다. 그래서 구글링을 통해, 회원가입 유효성 검사 구현 중, 이름, 전화번호, 생년월일은 반드시 정규식 코드로 정해야 더 편했고, 이것 때문에 정규식 문법을 살짝 맛보면서 표시방법들도 알게 되었다. 그 외, 다른 부분은 때에 따라 유효성 검사가 틀릴 시, alert창으로 자세하게 안내창을 구현하면서, 팀원들이 봤을 때, 효율적으로 알게 한 점이 뿌듯했다.
- 현재, if, else if 경우의 수마다 alert창으로 코드를 짰는데... 이보다 더 효율적인 코딩을 짜 볼 아이디어가 있었으면 좋겠다.
(2) 깨닫고 싶은 코드
<div className={css['account-type-box']}>
<a className={css.btn} onClick={emailSignUpClick}>
이메일로 가입하기
</a>
</div>
{emailSignUp && (
<div className={css['member-by-email']}>
<form
className={css['account-form-body']}
onSubmit={e => {
e.preventDefault();
}}
>
<div className={css['user-email']}>
<label htmlFor="userId" className={css.string}>
아이디(이메일)*
</label>
<input
id="userId"
type="email"
name="userId"
value={email}
placeholder="이메일을 입력해주세요."
required={true}
className={css['input-text']}
onChange={onEmailHandle}
/>
</div>
- 이 기능은 팀원분께 도움을 요청하여 코드를 짰다. && 연산자를 통해 emailSignup 함수가 실행(true)시. 다음 코드들이 보여지게 되는(입력창들이 펼치게 되는) 기능이 된다는 게 신기하고 보다 더 && 연산자 (조건부 렌더링)를 이해하게 되었다.
- 👌 label input 태그를 사용해서 그런지, javascript에서는 label for 사용하지만, JSX문법에서는 label htmlFor 을 사용해야 되는 것도 알게 되었다.
4. 프로젝트를 마치고 느낀 점
협업
- 1차 프로젝트 때 느낀 건, Git, Github사용할 때 많이 느꼈다. 팀원분들과 상의하여, branch(기능별)와 Commit 컨벤션에 맞춰 하다보니 적응해가면서 보다 더 효율적인 작업(pr이나 원격 main에 커밋 시)이 이루어졌다. 그러나, 멋모르고 터미널에 적었던 명령어가 멘토님 세션 이후 흐름이 확 이해가 갔다. 프로젝트 초반에는 Conflict가 일어나면서, 대체 어떡하지?? 하고서 멘토님 말씀에 따라 그냥 실행한 반면, Git 명령어 흐름에 대한 이해 후, 어떤 터미널 메세지가 와도 당황하지 않고, 차근차근 해결해 나갈 수 있는 마음가짐과 자세를 가지게 되었다.
- [공통] 우리 팀은 작업 진행상황 관리를 Trello보드로 이용하다보니, 보다 더 확연한 진행상황을 볼 수 있었고, 팀원분들의 상황을 자주 공유하면서 (즉, 미니미팅 제도를 도입하여) 서로 도와가면서 프로젝트를 진행할 수 있었다. 그 이유는 팀프로젝트가 다들 처음이다 보니, 첫 플래닝 미팅 때, 전반적인 프로젝트의 흐름을 어떻게 계획해야 하는지에 대한 어려움이 있었다. 나름대로 일정을 계획하였으나, 실제로 진행하다보니 변동사항이 많았기에 잦은 소통이 필요했기 때문이다.
- API 작업시, 백엔드분들과 통신을 하면서, 잦은 에러도 잡고, fetch API구조에 대해서 공부를 많이 하게 된 시간이었다. 단순히 통신만 하는 게 아니라 서로 어떤 방식으로 동작하는지 이해하는 과정이 좋았다.
- 다음 2차 프로젝트 때는 백엔드분들과도 프로젝트 초기에 db의 데이터들을 잘 정해놓고, 서로 어떤 것들을 미리 알려줘야 나중에 고칠 일 없도록 해야 좋은 진행으로 나갈 수 있을 것 같다.
개인
- 언젠가 중요한 로그인, 회원가입 기능을 맡아야 다음 프로젝트 때, 그 외 다른 부분들을 맡았을 때 페이지에 대한 이해가 가기 위해서 이 부분들을 선택했었다. 막상 이 부분들을 맡다보니, 하나하나 생각하면서 코딩짜는 게 그리 쉽지 않다는 걸 느꼈다. 팀원분의 조언을 참고해서 하나씩하나씩 그려나가는 연습 및 해야 할 순서정리들을 배워본 것 같다.
- 👌 그리고 시간의 여유가 생기더라도, 내가 맡은 부분이 아닐지라도, 다른 사람이 짜놓은 코드를 보고 읽으며 피드백을 준다거나 다른 사람을 도와주면서, 담당자의 설명을 듣고 이해하는 능력이 필요하다는 것을 알 수 있었다.
- 다음 2차 프로젝트 때는 보다 더 도전적인 작업을 할 수 있도록 어떤 것이 오든 부딪혀서 '나는 구현할 수 있다!'라는 마음을 가지고 해야 되겠다.
기술스택을 사용하면서...
이 프로젝트에서 백엔드와 통신하기 위한 준비로 Fetch를 사용하며 내 기억속에 많이 남아 경험을 들려주고자 한다.
- API문서에 따르면, 내가 코드에 적용할 메서드가 GET, POST, DELETE, PATCH이기 때문에 headers와 body값에 어떤 게 들어가있는지 공부하며 코드를 짜기 위해, 'fetch {해당 메서드}'이라고 구글링해서 여러 블로그의 예시들을 보면서 공부했었다.
공부한 바로는,
- 회원가입, 로그인 할 때는 POST, GET 이기 때문에 headers에 request에 보내는 데이터(body)의 타입정보를 표현하기 위해
headers: {
'Content-Type': 'application/json',
},
를 하는 것이었고,
- 장바구니 조회, 삭제, 수정할 때는 이미 로그인한 상태 즉, token값 정보도 포함이 된 데이터를 보내기 위해, Authorizaiton(인증) 정보도 필요하다는 것을 알 수 있었다.
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
💦 표현방법에 있어서...
처음에는 다른사람이 짜놓은 코드에서 통신테스트를 하면서 에러상황을 맞닺뜨렸는데.. 원인은 스펠링이 틀려서 나타난 문제이기도 했었다. (Authorization 스펠링)
Authorization: `Bearer ${localStorage.getItem('token')}`
👌 아하! 모먼트
처음 Foundation기간에 fetch를 사용할 때, headers에 왜 저렇게 적는지 생각을 안하고 코드복붙하면서 짰다가,
이번 프로젝트 때 문서도 찾아보며 깊이 fetch구조에 대해 탐구해보면서 앞으로 코드를 짤 때는 기본 구조에 대해 탐구해보고 프로그래밍적 사고를 생각하면서 짜야한다는 걸 느꼈다. 그리고 테스트하기 전에 꼭 스펠링 문제가 없는지 확인한 뒤 해봐야 그 다음 과정에 무리가 없을 것 같다.
✨ fetch 구글링을 하게 되면...
같이 나오는 키워드가 axios 인데, 현재는 fetch사용하는 것에 익숙해질 정도로 공부했다면, 다른 프로젝트에서는 axios를 써보는 것도 좋을 것 같다. 왜냐하면 2개를 사용하면 장단점이 명확해지기 때문에 보다 더 앞으로 펼쳐질 프로젝트에서 사용할 스택을 정할 때 도움이 될 것 같다는 생각이 든다.
'교육참여 > [저스트코드]' 카테고리의 다른 글
[회고록] 기업협업 프로젝트 3rd 개인과제 (221011 ~ 221019) (0) | 2022.10.23 |
---|---|
[회고록] 기업협업 프로젝트 2nd 과제 (221007 ~221009) (0) | 2022.10.23 |
[회고록] 기업협업 팀프로젝트 1st 과제 (221004 ~ 221006) (0) | 2022.10.10 |
[회고록] 팀 포르젝트 2차_'FLO'을 참고하여 개발한 프로젝트 (220919 ~ 220930) (0) | 2022.10.10 |
[저스트코드 회고록] 팀 프로젝트 들어가기 전 (220707 ~ 220826) (1) | 2022.09.13 |