Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0a29fc9
refactor : 정보 수정 페이지 중첩 라우팅으로 변경
usageness Jun 6, 2022
dd41f77
refactor : 장바구니 액션의 기존 상품 변수 이름 변경
usageness Jun 6, 2022
13d2a2a
refactor : action dispatch 로직 유틸 함수로 분리
usageness Jun 6, 2022
8038be2
refactor : checked 확인 로직 findIndex -> some으로 변경
usageness Jun 6, 2022
4a77763
refactor : 장바구니 상품 총액 계산 로직을 고차함수를 활용하여 변경
usageness Jun 6, 2022
8ba8ba3
refactor : early return 문을 삼항 연산자로 수정
usageness Jun 6, 2022
1c0306c
refactor : 의미없는 html-for 속성 제거
usageness Jun 6, 2022
f9b364b
refactor : 불필요한 옵셔널 체이닝 연산자 제거
usageness Jun 6, 2022
1ef7a2e
refactor : 불필요한 spread 연산자 제거
usageness Jun 6, 2022
6fe2fe8
refactor : 큰 의미없는 useMemo 제거
usageness Jun 6, 2022
83fdea2
feat : MSW를 활용하여 장바구니 API Mocking
usageness Jun 6, 2022
156e6fb
feat : 장바구니 api 정의
usageness Jun 6, 2022
5ba2635
feat : 장바구니 페이지 이동 시 상품 목록 조회
usageness Jun 6, 2022
72a9244
feat : 장바구니 페이지에서 상품 수량 수정 기능 구현
usageness Jun 7, 2022
df6d861
feat : 장바구니 페이지에서 상품 선택 삭제 기능 구현
usageness Jun 7, 2022
24263ce
refactor : 누락된 disabled 버튼에 스타일 적용
usageness Jun 7, 2022
73658cc
feat : 주문하기 버튼 클릭 시 주문 요청 기능 구현
usageness Jun 7, 2022
f3307ea
fix : 바뀐 store 구조에 맞게 리덕스 작업 변경
usageness Jun 7, 2022
b1af945
fix : API 명세에 맞게 데이터 형식 변경
usageness Jun 7, 2022
c31ae3b
feat : 비로그인 유저가 로그인이 필요한 url로 접근시 차단하는 기능 구현
usageness Jun 7, 2022
0a269b6
chore : 로그인 실패시 페이지 이동 없음
usageness Jun 7, 2022
190eb9b
chore : 회원가입시 로그인 페이지로 이동
usageness Jun 7, 2022
33f201e
chore : console.log 제거
usageness Jun 8, 2022
3714e86
feat : 에러 바운더리 적용
usageness Jun 8, 2022
4ac16f3
feat : 장바구니에 중복된 상품 추가시 에러 연결
usageness Jun 8, 2022
986767d
chore : 패스워드 변경 실패시 페이지 이동 삭제
usageness Jun 8, 2022
0811ef9
fix : 삭제와 주문이 state에 반영되지 않던 오류 수정
usageness Jun 8, 2022
484cf3b
chore : 일부 api 요청 로직에 스피너 추가
usageness Jun 8, 2022
227510c
refactor : 알림 메시지 상수화
usageness Jun 8, 2022
38b4f33
style : header 메뉴 스타일 수정
usageness Jun 8, 2022
f587b0d
refactor : user 액션에 흐름 제어 코드 위임
usageness Jun 9, 2022
10f4e08
refactor : 장바구니 불필요한 코드 삭제
usageness Jun 9, 2022
59c8861
refactor : 상품 상세 정보 불필요한 코드 제거
usageness Jun 9, 2022
fd74e44
refactor : signUp 로직 user action으로 이동
usageness Jun 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"default-param-last": "off",
"react/forbid-prop-types": "off",
"function-paren-newline": "off",
"camelcase": "off"
"camelcase": "off",
"react/jsx-wrap-multilines": "off",
"react/destructuring-assignment": "off"
}
}
18 changes: 14 additions & 4 deletions REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,34 @@

## API

- [x] MSW를 활용한 API mocking
- [x] MSW를 활용한 유저 API mocking
- [x] MSW를 활용한 장바구니 API mocking
- [x] Token기반의 로그인
- [x] 회원가입
- [x] 회원 정보 수정
- [x] 회원탈퇴
- [x] 비밀번호 수정
- [x] 장바구니 상품 목록 조회
- [x] 장바구니 상품 추가
- [x] 장바구니 상품 수량 변경
- [x] 장바구니 상품 제거
- [x] 장바구니 비우기
- [x] 장바구니 상품 구매

## 기능

- [x] 비밀번호 확인 후, 회원정보 수정 페이지로 이동
- [x] 로그아웃
- [x] 로그인 시, 유저 아이디를 받아와서 redux에서 관리

- [x] 로그아웃 시 logout action 호출 (Redux 데이터 삭제)
- [x] 회원 탈퇴 시 logout action 호출

- [x] alert -> snackbar
- [ ] confirm -> modal
- [x] 서버 응답 대기시 스피너 애니메이션 띄워주기
- [x] redux에서 isPending 상태로 관리하기
- [ ] 잘못된 url로 접근시 home으로 이동시키기
- [x] 비로그인 유저가 로그인이 필요한 url로 접근시 차단하기
- [x] 장바구니 페이지 이동 시 상품 목록 조회
- [x] 장바구니 페이지에서 상품 수량 수정
- [x] 장바구니 페이지에서 상품 선택 삭제
- [x] 주문하기 버튼 클릭 시 주문 요청
- [x] 에러 바운더리 적용
33 changes: 20 additions & 13 deletions src/ShoppingCartApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,34 @@ import ProductDetail from 'pages/ProductDetail';
import Cart from 'pages/Cart';
import Login from 'pages/Login';
import SignUp from 'pages/SignUp';
import EditUserData from 'pages/EditUserData';
import EditUserInfo from 'pages/EditUserInfo';
import Identification from 'pages/Identification';
import EditUserPassword from 'pages/EditUserPassword';
import Auth from 'utils/Auth';
import ErrorBoundary from 'components/ErrorBoundary';

const ShoppingCartApp = () => (
<Provider store={store}>
<Global styles={GlobalStyles} />
<Snackbar />
<BrowserRouter>
<Routes>
<Route path="/" element={<ProductList />} />
<Route path="/product" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/login" element={<Login />} />
<Route path="/signUp" element={<SignUp />} />
<Route path="/identification" element={<Identification />} />
<Route path="/editUserInfo" element={<EditUserInfo />} />
<Route path="/editUserPassword" element={<EditUserPassword />} />
<Route path="*" element={<ProductList />} />
</Routes>
</BrowserRouter>
<ErrorBoundary>
<BrowserRouter>
<Routes>
<Route path="/" element={<ProductList />} />
<Route path="/products/:productId" element={<ProductDetail />} />
<Route path="/cart" element={<Auth element={<Cart />} />} />
<Route path="/login" element={<Login />} />
<Route path="/signUp" element={<SignUp />} />
<Route path="/edit/*" element={<Auth element={<EditUserData />} />}>
<Route path="identification" element={<Identification />} />
<Route path="userInfo" element={<EditUserInfo />} />
<Route path="userPassword" element={<EditUserPassword />} />
</Route>
<Route path="*" element={<ProductList />} />
</Routes>
</BrowserRouter>
</ErrorBoundary>
</Provider>
);

Expand Down
129 changes: 113 additions & 16 deletions src/actions/cart.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,123 @@
import { 장바구니_액션 } from './types';
import asyncDispatchAction from 'utils/asyncDispatchAction';
import {
requestGetCartList,
requestAddCartItem,
requestDeleteCartItems,
requestSetCartItemQuantity,
requestOrderCartItem,
} from 'api/cart';
import { snackbar } from 'actions/snackbar';
import { 비동기_요청, 알림_메시지 } from 'constants/';
import { 장바구니_액션, 장바구니_불러오기_액션 } from './types';
import { hideSpinner, showSpinner } from './spinner';

const addCartList = (product, cartList) => {
const foundExistProduct = cartList.find((item) => item.id === product.id);
const addCartList = (product) => async (dispatch) => {
dispatch({ type: 장바구니_액션.PENDING });

if (foundExistProduct) {
const response = await requestAddCartItem(product.id);

if (response.content.redirect) {
dispatch(snackbar.pushMessageSnackbar(알림_메시지.장바구니_추가_중복));
dispatch({
type: 장바구니_액션.FAILURE,
payload: response.content,
});
return;
}

if (response.status === 비동기_요청.FAILURE) {
dispatch(snackbar.pushMessageSnackbar(알림_메시지.장바구니_추가_오류));
dispatch({
type: 장바구니_액션.FAILURE,
payload: response.content,
});
return;
}

dispatch({ type: 장바구니_액션.SUCCESS });
dispatch(snackbar.pushMessageSnackbar(알림_메시지.장바구니_추가(product.name)));
};

const deleteCartItem =
(productId, productName = null) =>
async (dispatch) => {
dispatch({
type: 장바구니_액션.PENDING,
});

const response = await requestDeleteCartItems(productId);

if (response.status === 비동기_요청.FAILURE) {
dispatch(snackbar.pushMessageSnackbar(알림_메시지.장바구니_삭제_실패));
dispatch({
type: 장바구니_액션.FAILURE,
payload: response.content,
});
}

dispatch({ type: 장바구니_액션.SUCCESS });
dispatch({ type: 장바구니_액션.DELETE_PRODUCT, payload: productId });

if (productName) {
dispatch(snackbar.pushMessageSnackbar(알림_메시지.장바구니_개별_삭제(productName)));
return;
}

dispatch(snackbar.pushMessageSnackbar(알림_메시지.장바구니_다중_삭제));
};

const modifyCartItemQuantity = (productId, quantity) => async (dispatch) => {
dispatch({ type: 장바구니_액션.PENDING });

const response = await requestSetCartItemQuantity(productId, quantity);

if (response.status === 비동기_요청.FAILURE) {
return {
type: 장바구니_액션.ADD_EXIST_PRODUCT,
payload: { ...product, count: Number(foundExistProduct.count) + 1, isChecked: true },
type: 장바구니_액션.FAILURE,
payload: response.content,
};
}

return {
type: 장바구니_액션.ADD_NEW_PRODUCT,
payload: { ...product, count: 1, isChecked: true },
};
dispatch({ type: 장바구니_액션.SUCCESS });
dispatch({
type: 장바구니_액션.SET_PRODUCT_QUANTITY,
payload: { productId, quantity },
});
};

const getCartList = () => async (dispatch) => {
dispatch({
type: 장바구니_불러오기_액션.PENDING,
});

const response = await requestGetCartList();

asyncDispatchAction(dispatch, response, 장바구니_불러오기_액션);
};

const deleteCartItem = (productId) => ({ type: 장바구니_액션.DELETE_PRODUCT, payload: productId });
const orderCartItem = (productId) => async (dispatch) => {
dispatch({
type: 장바구니_액션.PENDING,
});

const modifyCartItemCount = (productId, count) => ({
type: 장바구니_액션.MODIFY_PRODUCT_COUNT,
payload: { productId, count },
});
dispatch(showSpinner());

const response = await requestOrderCartItem(productId);

dispatch(hideSpinner());

if (response.status === 비동기_요청.FAILURE) {
dispatch(snackbar.pushMessageSnackbar(알림_메시지.상품_주문_실패));
dispatch({
type: 장바구니_액션.FAILURE,
payload: response.content,
});
return;
}

dispatch({ type: 장바구니_액션.SUCCESS });
dispatch(snackbar.pushMessageSnackbar(알림_메시지.상품_주문_성공));
dispatch({ type: 장바구니_액션.ORDER_PRODUCT, payload: productId });
};

export { addCartList, deleteCartItem, modifyCartItemCount };
export { addCartList, deleteCartItem, modifyCartItemQuantity, getCartList, orderCartItem };
30 changes: 3 additions & 27 deletions src/actions/products.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { requestGetProductList, requestGetProduct } from 'api';
import { 비동기_요청 } from 'constants/';
import asyncDispatchAction from 'utils/asyncDispatchAction';
import { 상품리스트_불러오기_액션, 상품_불러오기_액션 } from './types';

const getProductList = () => async (dispatch) => {
Expand All @@ -9,19 +9,7 @@ const getProductList = () => async (dispatch) => {

const response = await requestGetProductList();

if (response.status === 비동기_요청.FAILURE) {
dispatch({
type: 상품리스트_불러오기_액션.FAILURE,
payload: response.content,
});

return;
}

dispatch({
type: 상품리스트_불러오기_액션.SUCCESS,
payload: response.content,
});
asyncDispatchAction(dispatch, response, 상품리스트_불러오기_액션);
};

const getProduct = (id) => async (dispatch) => {
Expand All @@ -31,19 +19,7 @@ const getProduct = (id) => async (dispatch) => {

const response = await requestGetProduct(id);

if (response.status === 비동기_요청.FAILURE) {
dispatch({
type: 상품_불러오기_액션.FAILURE,
payload: response.content,
});

return;
}

dispatch({
type: 상품_불러오기_액션.SUCCESS,
payload: response.content,
});
asyncDispatchAction(dispatch, response, 상품_불러오기_액션);
};

export { getProductList, getProduct };
7 changes: 5 additions & 2 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ const createAsyncAction = (actionName) => ({

const 상품리스트_불러오기_액션 = createAsyncAction('상품리스트_불러오기');
const 상품_불러오기_액션 = createAsyncAction('상품_불러오기');
const 장바구니_불러오기_액션 = createAsyncAction('장바구니_불러오기');

const 유저_액션 = {
SET_USER_DATA: '유저 데이터를 저장합니다.',
REMOVE_USER_DATA: '유저 데이터를 삭제합니다.',
};

const 장바구니_액션 = {
...createAsyncAction('장바구니'),
ADD_NEW_PRODUCT: '장바구니에 새로운 상품을 추가합니다.',
ADD_EXIST_PRODUCT: '장바구니에 존재하는 상품을 추가합니다.',
DELETE_PRODUCT: '장바구니에 존재하는 상품을 삭제합니다.',
MODIFY_PRODUCT_COUNT: '장바구니 상품의 구매 수량을 변경합니다',
SET_PRODUCT_QUANTITY: '장바구니 상품의 구매 수량을 변경합니다',
ORDER_PRODUCT: '장바구니에서 선택한 상품을 주문합니다.',
};

const 스낵바_액션 = {
Expand All @@ -32,6 +34,7 @@ const 스피너_액션 = {
export {
상품리스트_불러오기_액션,
상품_불러오기_액션,
장바구니_불러오기_액션,
유저_액션,
장바구니_액션,
스낵바_액션,
Expand Down
Loading