Skip to content

Commit

Permalink
Merge pull request #167 from woowa-techcamp-2021/dev
Browse files Browse the repository at this point in the history
v0.6.0
  • Loading branch information
Seogeurim authored Aug 26, 2021
2 parents e5c0934 + 1807e5c commit d81086d
Show file tree
Hide file tree
Showing 146 changed files with 5,034 additions and 643 deletions.
2 changes: 1 addition & 1 deletion client/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default {
},
testEnvironment: 'jsdom',
testMatch: ['<rootDir>/**/*.test.(js|jsx|ts|tsx)'],
setupFilesAfterEnv: ['<rootDir>/setup-tests.ts'],
setupFilesAfterEnv: ['<rootDir>/setup-tests.ts', 'jest-localstorage-mock'],
transformIgnorePatterns: ['<rootDir>/node_modules/'],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
Expand Down
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "store-2-client",
"version": "0.1.0",
"version": "0.6.0",
"description": "배민문구사 클라이언트",
"main": "src/index.tsx",
"type": "module",
Expand Down Expand Up @@ -68,6 +68,7 @@
"eslint-plugin-testing-library": "^4.10.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.0.6",
"jest-localstorage-mock": "^2.4.17",
"mini-css-extract-plugin": "^2.2.0",
"prettier": "^2.3.2",
"style-loader": "^3.2.1",
Expand Down
Binary file modified client/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
SIGNUP_URL,
ORDER_LIST_URL,
PAYMENT_URL,
ADDRESS_URL,
CART_URL,
} from 'constants/urls';

import {
Expand All @@ -22,6 +24,8 @@ import {
ItemDetailPage,
MyOrderListPage,
OrderPage,
MyAddressPage,
CartPage,
} from 'pages';
import Theme from './styles/theme';

Expand All @@ -37,6 +41,9 @@ const App: React.FC = () => {
<Route exact path={ITEM_LIST_URL} component={ItemListPage} />
<Route exact path={`${ITEM_URL}/:id`} component={ItemDetailPage} />
<Route exact path={ORDER_LIST_URL} component={MyOrderListPage} />
<Route exact path={CART_URL} component={CartPage} />
<Route exact path={ADDRESS_URL} component={MyAddressPage} />
<Route exact path={PAYMENT_URL} component={OrderPage} />
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>
Expand Down
Binary file removed client/src/assets/icons/back.png
Binary file not shown.
1 change: 1 addition & 0 deletions client/src/assets/icons/back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/src/assets/images/no_image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
151 changes: 151 additions & 0 deletions client/src/components/cart/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { useState, useCallback, Fragment, FC } from 'react';
import { useSelector } from 'react-redux';
import styled from 'lib/woowahan-components';
import { useHistory } from 'lib/router';

import { PAYMENT_URL, SIGNIN_URL } from 'constants/urls';
import { cartGenerator } from 'utils/cart-generator';

import { RootState } from 'store';

import { TextButton } from 'components';
import Modal from 'components/common/modal';
import PriceCalculator from 'components/common/price-calculator';
import { TableSection, CartItem } from './table-section';

const SectionTitle = styled.h4`
width: 100%;
font-size: 18px;
font-weight: ${({ theme }) => theme?.weightBold};
padding-bottom: 12px;
margin-top: 5px;
`;

const ContinueLink = styled.div`
cursor: pointer;
width: 80px;
padding-top: 20px;
font-size: 12px;
color: ${({ theme }) => theme?.colorSoftBlack};
&:hover {
color: ${({ theme }) => theme?.colorGreyMid};
}
`;

const ButtonDiv = styled.div`
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding-bottom: 50px;
@media all and (max-width: 800px) {
gap: 14px;
justify-content: center;
}
`;

const OrderButtonDiv = styled.div`
display: flex;
gap: 10px;
`;

const Cart: FC = () => {
const [prices, setPrices] = useState([0]);
const [totalCount, setTotalCount] = useState(0);
const [cartItems, setCartItems] = useState(cartGenerator());
const [checkAll, setCheckAll] = useState(false);
const [checkedItems, setCheckedItems] = useState(new Set<number>());
const [modalVisible, setModalVisible] = useState(false);
const history = useHistory();

const onClick = useCallback(() => history.goBack(), [history]);

const moveSignin = () => {
history.push(SIGNIN_URL);
};

const { userId } = useSelector(({ auth }: RootState) => ({
userId: auth.user.userId,
}));

const deleteSelectCartItem = () => {
const data = localStorage.getItem('select') as string;
const select = data.split(',');
let cartItems = cartGenerator();
cartItems = cartItems.filter((item, index) => select.indexOf(index.toString()) < 0);
let cartItemsString = '';
cartItems.forEach(item => {
cartItemsString += `${item.id},${item.thumbnail},${item.title},${item.count},${item.price},`;
});
cartItemsString = cartItemsString.slice(0, cartItemsString.length - 1);
localStorage.setItem('cart', cartItemsString);
localStorage.removeItem('select');
setCartItems(cartGenerator());
setPrices([0]);
setTotalCount(0);
setCheckAll(false);
setCheckedItems(new Set<number>());
};

const orderCartItem = (isAll: boolean) => {
let selectCartItems: CartItem[] = [];
if (isAll) {
selectCartItems = cartItems;
} else {
Array.from(checkedItems).forEach(index => selectCartItems.push(cartItems[index]));
}

let selectCartItemsString = '';
selectCartItems.forEach(item => {
selectCartItemsString += `${item.id}-${item.count},`;
});
selectCartItemsString = selectCartItemsString.slice(0, selectCartItemsString.length - 1);

if (selectCartItemsString !== '') {
sessionStorage.setItem('order', selectCartItemsString);
history.push(PAYMENT_URL);
}
};

const onClickOrder = (isAll: boolean) => () => {
if (userId) {
orderCartItem(isAll);
} else {
setModalVisible(true);
}
};

return (
<>
<SectionTitle> </SectionTitle>
<TableSection
cartItems={cartItems}
checkedItems={checkedItems}
checkAll={checkAll}
setPrices={setPrices}
setTotalCount={setTotalCount}
setCheckAll={setCheckAll}
setCheckedItems={setCheckedItems}
/>
<ContinueLink onClick={onClick}>{'<'} 쇼핑 계속하기</ContinueLink>
<PriceCalculator prices={prices} totalCount={totalCount} />
<ButtonDiv>
<TextButton title="선택 상품 삭제" type="submit" styleType="white" onClick={deleteSelectCartItem} />
<OrderButtonDiv>
<TextButton title="선택 상품 주문" type="submit" styleType="white" onClick={onClickOrder(false)} />
<TextButton title="전체 상품 주문" type="submit" styleType="black" onClick={onClickOrder(true)} />
</OrderButtonDiv>
</ButtonDiv>
<Modal
type="confirm"
header={<div>로그인이 필요합니다</div>}
body={<div>로그인 페이지로 이동하시겠습니까?</div>}
visible={modalVisible}
setVisible={setModalVisible}
onConfirm={moveSignin}
/>
</>
);
};

export default Cart;
164 changes: 164 additions & 0 deletions client/src/components/cart/table-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React, { Fragment, FC } from 'react';
import styled from 'lib/woowahan-components';
import { Link } from 'lib/router';

import { formatPrice } from 'utils';
import { ITEM_URL } from 'constants/urls';
import { CartItem } from 'types/cart';

import Table from 'components/common/table';
import { CheckBox } from 'components';

interface TableSectionProps {
cartItems: CartItem[];
checkedItems: Set<number>;
checkAll: boolean;
setPrices: React.Dispatch<React.SetStateAction<number[]>>;
setTotalCount: React.Dispatch<React.SetStateAction<number>>;
setCheckAll: React.Dispatch<React.SetStateAction<boolean>>;
setCheckedItems: React.Dispatch<React.SetStateAction<Set<number>>>;
}

const TableRowTitle = styled.div`
display: flex;
align-items: center;
font-size: 12px;
font-weight: ${({ theme }) => theme?.weightMid};
img {
width: 42px;
height: auto;
margin-right: 8px;
}
${({ theme }) => theme?.mobile} {
img {
display: none;
}
}
.item-link {
color: ${({ theme }) => theme?.colorBlack};
}
`;

const TableRowText = styled.div`
text-align: center;
`;

const CheckBoxDiv = styled.div`
margin-bottom: 20px;
`;

const ItemTitle = styled.div`
font-size: 14px;
${({ theme }) => theme?.mobile} {
font-size: 12px;
}
`;

const tableHeaders = [
{ column: '상품/옵션 정보', span: 1 },
{ column: '수량', span: 1 },
{ column: '상품금액', span: 1 },
{ column: '배송비', span: 1 },
];

const TableSection: FC<TableSectionProps> = ({
cartItems,
checkedItems,
checkAll,
setPrices,
setTotalCount,
setCheckAll,
setCheckedItems,
}) => {
const updatePrice = (set: Set<number>) => {
const prices = [] as number[];
let totalCount = 0;
Array.from(set).forEach(index => {
const item = cartItems[Number(index)];
prices.push(item.price * item.count);
totalCount += item.count;
});
if (prices.length === 0) {
prices.push(0);
}
setPrices(prices);
setTotalCount(totalCount);
};

const checkedItemHandler = (id: number) => () => {
const checkedSet = new Set<number>(checkedItems);
if (checkedSet.has(id)) {
checkedSet.delete(id);
setCheckedItems(checkedSet);
} else {
checkedSet.add(id);
setCheckedItems(checkedSet);
}

if (cartItems.length === checkedSet.size) {
setCheckAll(true);
} else {
setCheckAll(false);
}
updatePrice(checkedSet);
localStorage.setItem('select', Array.from(checkedSet).join(','));
};

const checkAllHandler = () => {
const checkedSet = new Set<number>();
if (checkAll) {
setCheckAll(false);
setCheckedItems(checkedSet);
updatePrice(checkedSet);
localStorage.setItem('select', '');
} else {
setCheckAll(true);
cartItems.forEach((item, index) => checkedSet.add(index));
setCheckedItems(checkedSet);
updatePrice(checkedSet);
localStorage.setItem('select', Array.from(checkedSet).join(','));
}
};

return (
<Table
headers={[
{
column: <CheckBox id="all" text="" onChange={checkAllHandler} check={checkAll} />,
span: 1,
},
...tableHeaders,
]}
>
{cartItems.map((item, idx) => {
const { id, title, thumbnail, count, price } = item;
return (
<Fragment key={id}>
<CheckBoxDiv className="dd">
<CheckBox id={idx.toString()} text="" onChange={checkedItemHandler(idx)} check={checkedItems.has(idx)} />
</CheckBoxDiv>
<TableRowTitle>
<div>
<Link className="item-link" to={`${ITEM_URL}/${id}`}>
<ItemTitle>
<img src={thumbnail} alt={title} />
{title}
</ItemTitle>
</Link>
</div>
</TableRowTitle>
<TableRowText>{count}</TableRowText>
<TableRowText>{formatPrice(price)}</TableRowText>
<TableRowText>공짜!</TableRowText>
</Fragment>
);
})}
</Table>
);
};

export { TableSection, CartItem };
Loading

0 comments on commit d81086d

Please sign in to comment.