위코드 생활의 5주차! 드디어 기대했던 프로젝트가 시작되는 주이다.
조 구성은 위코드 측에서 알아서 짜주는거라 어떤 분들이랑 하게될지 무척 궁금했는데, 5명으로 구성된 조에 속하게 되었다.
우리는 쿠캣을 클론하는 조였고, 백엔드 3명, 프론트엔드 2명의 인원으로 시작했다.
백엔드는 성규님, 경철님, 혜지님 프론트는 준희님 그리고 나 인데, 준희님을 제외한 분들은 일대일로 대화해본적이 없는 분들이라 좀 낯설었지만 프로젝트 기간동안 으쌰으쌰하면서 친해질것이 뻔해서 인원 구성에 대해서는 만족한 편이다.
조가 구성되고 첫 날엔 우리가 '어떤 범위까지 할수 있을까?' 라는 주제를 가지고 엄청나게 긴 토론을 했다. 2주가 지난 지금도 그때의 기억이 생생하다.(당연한건가..?)
프론트쪽에서는 쿠켓의 페이지 전체를 해보는걸로 범위를 정했고 백엔드는 기존 메인페이지를 삭제하고 상품목록이 출력되는 페이지를 메인으로 바꾼뒤에 추후에 시간이 남으면 메인페이지를 해보는 쪽으로 하자는 입장이었는데, 프론트인 우리 입장에선 다 해볼수 있을거 같아서 쉽게 양보하긴 힘든 입장이라 멘토님이 의견 조율을 해준 뒤에 아래와 같은 페이지를 구성해보았다.
우리 조는 Trello를 사용해서 일정을 관리했고, 회의록 및 기타 참고사항에 대한 기록용으로 Notion을 사용했다.
Notion은 기존에 사용하고 있던 툴이라서 어렵지 않게 사용했고, Trello는 처음 써봄에도 불구하고 UI가 직관적으로 설계되어있어 어려움없이 사용할 수 있었다.
지금은 다 완료된 상태여서 Done으로 모든 티켓들이 옮겨져있지만, 우리가 진행할 목록들을 back-log에 넣어서 프리뷰를 해보고 해당 주차에 할것들은 this SPRINT에 넣어서 넣어놓은 것은 꼭 다 하는걸 목표로 하였다. 트렐로 툴 자체가 카드 하나를 옮기면 바로 알림 메세지에 누가 어디로 뭘 옮겼다는 게 뜨기 때문에 못해서 다시 back-log로 옮겨놓는 것 자체가 부끄러워서 빠짐없이 했었다ㅎㅎ
노션은 개인 페이지로만 사용해보고 프로젝트에 활용해본 것은 이번이 처음이었는데, 협업에 최적화 된 툴이 아닐까 싶다. 서로 자신의 페이지에 대한 내용을 공유할 때도 그렇고 회의에 대한 내용을 기록해두니 나중에 우리가 무슨말을 했는지 무조건 알수있어서 노션의 유용함을 이번 프로젝트를 통해 다시 한번 느낄수 있었다.
기존 쿠캣사이트는 모바일 최적화 사이트라 반응형이 없기 때문에 우리 역시 반응형 레이아웃은 진행하지 않기로 했다.
나는 맡은 페이지들에 대한 레이아웃을 먼저 만들고 그 뒤에 기능을 구현하는 방식으로 프로젝트를 시작했는데, 먼저 공통으로 사용되는 헤더, 푸터를 만들었고 그 뒤에 주요 페이지인 회원가입 -> 상품 상세페이지 -> 구매용 모달창 -> 검색 -> 검색 결과페이지 -> 장바구니 순으로 진행을 했다.
페이지 레이아웃은 생각보다 빠른 3일차 오전에 끝나서 다시 위와 같은 순으로 리액트를 사용하여 기능 구현을 계획했다.
1주차는 회원가입, 상품 상세페이지, 구매용 모달창을 구현했는데, 생각보다 이해 안가는 부분이 있어서 예상 소요시간보다 시간이 초과됐다.
이건 단순한 레이아웃이기 때문에 따로 작성하진 않겠다.
회원가입은 정규표현식으로 모든걸 해결할 수 있어서 크게 어려운 것 없이 마무리 지을수 있었다. 정규표현식은 아래와 같은 코드로 작성하였다.
validateEmail = () => {
const { email } = this.state;
const regexEmail = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{3,10})+$/;
return email.match(regexEmail);
};
validatePassword = () => {
const { password, passwordConfirm } = this.state;
let validatedResult = '';
if (password === passwordConfirm) {
const regexPassword =
/^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^*+=-]).{7,20}$/;
passwordConfirm.match(regexPassword)
? (validatedResult = '유효한 비밀번호입니다')
: (validatedResult = '8~20글자의 비밀번호를 입력해주세요');
} else if (password !== passwordConfirm) {
validatedResult = '비밀번호가 일치하지 않습니다';
}
return validatedResult;
};
validateUserName = () => {
const { username } = this.state;
const regexUsername = /^[가-힣]{2,4}|[a-zA-Z]{2,10}\s[a-zA-Z]{2,10}$/;
return username.match(regexUsername);
};
validatePhoneNumber = () => {
const { phoneNumber } = this.state;
const regexPhoneNumber = /^\d{3}-\d{4}-\d{4}$/;
return phoneNumber.match(regexPhoneNumber);
};
상세페이지에서 배울수 있던건 toLocaleString() 이다.
상품 가격을 노출하는 페이지이다보니 천의자리에서 , 를 넣어야했는데 나는 정규표현식으로 복잡하게 넣었지만 멘토님의 피드백에 맞춰서 toLocalString을 사용해보라는 조언을 얻고 해당 내용을 찾아보니 너무 간단하게 해결할 수가 있어서 허무했다.
그냥 금액관련변수.toLocaleString()
이라고 입력하면 위 가격 표시처럼 천의 자리 단위로 알아서 , 가 입력된다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/toLocaleString
모달창에 대한 내용은 이 글을 쓰기전에 한번 남겼기때문에 그 페이지 링크로 대체를 하겠다
https://velog.io/@devpark_0x1c/TID-Modal%EC%9D%84-%EB%A7%8C%EB%93%A4%EB%8B%A4
상품 모달창을 구현하면서 너무 멍청한 짓을 해서 지금 생각해도 웃음만 나온다ㅎㅎ
constructor() {
super();
this.state = {
totalCount: 0
totalAmount: 0
}
}
calculateQuantity = () => {
const { selectedItems } = this.state;
const totalCount = selectedItems.reduce((acc, cur) => {
return acc + cur.quantity;
}, 0);
this.setState({ totalCount });
};
calculateMoney = () => {
const { selectedItems } = this.state;
const totalMoney = selectedItems.reduce((acc, cur) => {
return acc + cur.price * cur.quantity;
}, 0);
this.setState({ totalMoney });
};
countQuantity = (idx, kind) => {
const { selectedItems } = this.state;
const sign = kind === 'plus' ? +1 : -1;
const newSelectedItems = selectedItems.map(value => {
return value.id === idx
? {
...value,
quantity: value.quantity + sign,
}
: { ...value };
});
this.setState({ selectedItems: newSelectedItems }, () => {
this.calculateMoney()
this.calculateQuantity()
});
};
위 코드를 보면 내가 왜 그랬는지는 모르겠지만 불필요한 state를 만들어서 수량에 대한 클릭 이벤트가 발생했을 때, 돈과 수량에 대한 업데이트가 일어나도록 만들고 그 함수들을 실행하기 위해 countQuantity
가 실행될 때마다 콜백으로 실행되게 하였는데... 전혀 저렇게 구성할 필요 없이 아래와 같은 코드를 통해 수량과, 돈을 리턴받아 JSX로 직접적으로 뿌려주면 되는거라서 위의 코드로 작성했을 때의 나를 보면 쥐구멍으로 숨고 싶다🥲
calculateQuantity = () => {
const { selectedItems } = this.state;
const totalCount = selectedItems.reduce((acc, cur) => {
return acc + cur.quantity;
}, 0);
return totalCount;
};
calculateMoney = () => {
const { selectedItems } = this.state;
const totalMoney = selectedItems.reduce((acc, cur) => {
return acc + cur.price * cur.quantity;
}, 0);
return totalMoney;
};
2주차에는 많은 일들이 있었다.. 백엔드 분들이 작성한 모델링이 좀 꼬여서 전부 갈아엎었고, 새로운 모델링을 하기 위해 꽤 많은 시간이 소요됐다. 정말 테스트를 위해 백엔드와 붙여본건 프로젝트 발표 하루전이었다. 이 때 시간이 너무 부족해서 백엔드팀은 전체페이지에서 장바구니와 검색 페이지를 제외해야 할수도 있다해서 멘붕이 심하게 왔었다.
백엔드 멘토님과 우리 백엔드팀의 회의 결과 전체페이지에서 장바구니만 제외하기로 결정을 했다.
흙.. 안그래도 해보고 싶었던 페이지가 하나 줄어서 좀 아쉬웠는데, 장바구니가 제외되서 너무 아쉬웠다..ㅠㅠ 그래도 멘붕인 상태에서도 포기하지 않고 진행해준 우리 백엔드팀에게 감사하다👏
searchByInput = () => {
const { productData, inputValue } = this.state;
const filteredData = productData.result.filter(data =>
data.name.includes(inputValue)
);
this.setState({ searchedData: !inputValue ? [] : filteredData });
};
searchByKeyword = keyword => {
const { productData } = this.state;
const filteredData
= productData.result.filter(data => data.name.includes(keyword));
this.setState({ searchedData: filteredData });
};
검색은 백엔드쪽에서 아직 모델링을 하던 중에 만들고 있어서 Mock Data를 활용했는데, 내가 만든 방식은 전체 데이터를 불러와서 필터링 하는 방식을 이용했다. 전체데이터를 기반으로 필터링
을 해서 인풋에 값이 입력될 때마다 해당 값에 맞는 데이터가 list로 출력되고, 추천검색어에 있는 검색어도 역시 클릭하면 연관 상품이 출력된다.
handleLikeButton = () => {
const {
match: {
params: { id },
},
} = this.props;
const authToken = localStorage.getItem('token');
fetch(`http://10.58.5.96:8000/wishes`, {
method: 'POST',
headers: {
Authorization: authToken,
},
body: JSON.stringify({
food_id: id,
}),
})
.then(res => res.json())
.then(res => {
res && this.state.isLiked
? this.setState({ isLiked: false })
: this.setState({ isLiked: true });
});
};
로그인을 할 때 서버로부터 토큰을 부여받는 데, 우린 토큰을 LocalStorage에 저장을 했다.
좋아요를 누를 땐 서버에게 Token을 넘겨줘야 하기 때문에 localstorage로 부터 토큰을 불러와서 서버에게 넘겨주며 진행을 하였다. 좋아요를 해제하는 버튼은 위의 역순으로 method만 DELETE로 변경하여 작성하였다.
logout = () => {
localStorage.removeItem('token');
};
로그아웃은 버튼 자체에게 RemoveItem으로 토큰을 제거하는 방식으로 작성하였다.
2주라는 시간이 되게 길줄 알았는데 생각보다 시간이 너무 빨리 간 프로젝트 기간이었다. 길면 길고 짧으면 짧은 2주였지만 직접 무언가를 만들어봄으로써 내 코드에 대해서 객관적으로 되돌아볼수 있었던 좋은 기회였다.
프로젝트 2주차에 데이터베이스를 전부 다 날려야하는 불상사가 있었음에도 끝까지 포기하지 않아준 우리 백엔드 조원에게 먼저 감사함을 전하고 싶다.
나와 함께 프론트의 한 축을 맡아준 준희님도 너무 감사하다. 대신 충분히 잘하고 있으니 자신감을 좀 더 가지셨으면 하는 작은 바램이 있다~
다들 너무 고생했습니다.
고생 많이 하셨을 것 같은 느낌이 드네요. 밀캣마켓 너무 멋졌어요! 준우님 2차 프로젝트도 기대 됩니당🥸🥸🥸