Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

상품 등록페이지 구현 #60

Merged
merged 16 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 4 additions & 0 deletions web-apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.7.0",
"@material-ui/icons": "^4.5.1",
"axios": "^0.19.0",
"clsx": "^1.0.4",
"immer": "^5.0.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
Expand Down
4 changes: 4 additions & 0 deletions web-apps/client/src/assets/uris.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const imageHandleURI = 'http://localhost:5000/products/picture';
const productHandleURI = 'http://localhost:5000/products';
const loginStatusHandleURI = 'http://localhost:5001/myInfo';
export {imageHandleURI, productHandleURI, loginStatusHandleURI};
62 changes: 62 additions & 0 deletions web-apps/client/src/components/addPicture.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, {useState, useContext, useRef} from 'react';
import IconButton from '@material-ui/core/IconButton';
import PhotoCamera from '@material-ui/icons/PhotoCamera';
import {makeStyles} from '@material-ui/core/styles';

import {ProductContext} from '../contexts/productStore';

import useImageUpload from '../hooks/useImageUpload';

const useStyles = makeStyles(() => ({
input: {display: 'none'},
}));
const AddPicture = () => {
const classes = useStyles();
const fileMaximumUploadErrorMessage = '사진은 10장까지만 입력 가능합니다.';
const [file, setFile] = useState([]);
const inputRef = useRef(false);
const {images, setImages, setAlertMessage} = useContext(ProductContext);

useImageUpload(images, file, inputRef, setImages, setAlertMessage);

const imageUploadListener = async (evt) => {
const selectedFiles = Array.from(evt.target.files);

const numberOfCurrentUploadedImage = images.length;
const allowToUpload = 10 - numberOfCurrentUploadedImage;
const numberOfSelectedFile = selectedFiles.length;

if (numberOfSelectedFile > allowToUpload) {
const allowed = selectedFiles.filter((file, index) => {
if (index < allowToUpload) {
return file;
}
});
setFile(allowed);
setAlertMessage(fileMaximumUploadErrorMessage);
return;
}
setFile(selectedFiles);
};

return (
<>
<input
accept='image/*'
className={classes.input}
id='icon-button-file'
type='file'
onChange={imageUploadListener}
multiple
ref={inputRef}
/>
<label htmlFor='icon-button-file'>
<IconButton aria-label='upload picture' component='span'>
<PhotoCamera />
</IconButton>
</label>
</>
);
};

export default AddPicture;
48 changes: 48 additions & 0 deletions web-apps/client/src/components/alertDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, {useState, useEffect, useContext} from 'react';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import Button from '@material-ui/core/Button';
import {ProductContext} from '../contexts/productStore';

const AlertDialog = () => {
const {alertMessage} = useContext(ProductContext);
const [open, setOpen] = useState(false);

useEffect(() => {
if (alertMessage.length > 0) {
setOpen(true);
} else {
setOpen(false);
}
}, [alertMessage]);

const handleClose = () => {
setOpen(false);
};

return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby='alert-dialog-title'
aria-describedby='alert-dialog-description'
>
<DialogContent>
<DialogContentText id='alert-dialog-description'>
{alertMessage}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color='primary' autoFocus>
확인
</Button>
</DialogActions>
</Dialog>
</div>
);
};

export default AlertDialog;
45 changes: 45 additions & 0 deletions web-apps/client/src/components/dealType.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import RadioButton from './radioButton';

const useStyles = makeStyles(() => ({
radioGroup: {
marginTop: '0.3rem',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
},
}));

const DealType = ({onDealTypeChange}) => {
const classes = useStyles();

const onRadioButtonChange = (e) => {
onDealTypeChange(e.target.value);
};

return (
<RadioGroup
defaultValue='직거래'
aria-label='거래유형'
name='customized-radios'
className={classes.radioGroup}
onChange={onRadioButtonChange}
>
<FormControlLabel
value='택배거래'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

별도의 상수로 해당 hook 함수의 최상단에 사용해보는 것은 어떨까요?
지금 value, label각각 값이 중복되어 사용해 있는데 해당 값을 나중에 수정시 바로 찾아갈 수 있을 것 같고, 동일한 element를 반복문으로 생성하여 활용할 수 있을 것 같습니다.
ex: const TYPENAME = ['택배거래', '직거래'];

하지만 여러 요소도 아니라서 단순히 두개의 값만 출력하는 경우에는 이 경우도 적절하다 생각이 듭니다.

control={<RadioButton />}
label='택배거래'
/>
<FormControlLabel
value='직거래'
control={<RadioButton />}
label='직거래'
/>
</RadioGroup>
);
};

export default DealType;
67 changes: 67 additions & 0 deletions web-apps/client/src/components/drawer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, {useState} from 'react';
import {makeStyles} from '@material-ui/core/styles';
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';

import DrawerList from './drawerList';

const useStyles = makeStyles(() => ({
field: {
display: 'flex',
justifyContent: 'space-between',
borderBottom: '1px solid black',
marginTop: '1rem',
},
}));

const Drawer = ({name, data, loading, onDrawerSelected}) => {
const classes = useStyles();
const [state, setState] = useState({
bottom: false,
});

const toggleDrawer = (side, open) => (event) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closure 잘 활용하신 것같습니다.

const isTabOrShift = (evt) => {
if (
evt &&
evt.type === 'keydown' &&
(evt.key === 'Tab' || evt.key === 'Shift')
) {
return true;
} else {
return false;
}
};

if (isTabOrShift(event)) {
return;
}

setState({...state, [side]: open});
};

return (
<>
<div onClick={toggleDrawer('bottom', true)} className={classes.field}>
<div>{name}</div>
<ExpandMoreIcon />
</div>
<SwipeableDrawer
anchor='bottom'
open={state.bottom}
onClose={toggleDrawer('bottom', false)}
onOpen={toggleDrawer('bottom', true)}
>
<DrawerList
loading={loading}
side='bottom'
data={data}
onDrawerSelected={onDrawerSelected}
toggleDrawer={toggleDrawer}
/>
</SwipeableDrawer>
</>
);
};

export default Drawer;
43 changes: 43 additions & 0 deletions web-apps/client/src/components/drawerList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import {makeStyles} from '@material-ui/core';

const useStyles = makeStyles(() => ({
fullList: {
width: 'auto',
height: '15rem',
},
}));

const DrawerList = ({loading, side, data, onDrawerSelected, toggleDrawer}) => {
const classes = useStyles();

const onListClick = (e) => {
const name = e.target.textContent;
onDrawerSelected(name);
};

if (loading) {
return 'loading';
}
return (
<div
className={classes.fullList}
role='presentation'
onClick={toggleDrawer(side, false)}
onKeyDown={toggleDrawer(side, false)}
>
<List>
{data.map((text) => (
<ListItem button key={text} onClick={onListClick}>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
};

export default DrawerList;
38 changes: 38 additions & 0 deletions web-apps/client/src/components/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import ExitToAppIcon from '@material-ui/icons/ExitToApp';
import AddPicture from './addPicture';

const useStyles = makeStyles(() => ({
root: {
height: '2.5rem',
},
headerButtons: {
display: 'flex',
justifyContent: 'space-between',
background: 'white',
color: '#555',
},
exit: {
color: '#555',
},
}));

const Header = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position='static'>
<Toolbar variant='dense' className={classes.headerButtons}>
<ExitToAppIcon className={classes.exit} />
중고거래 글쓰기
<AddPicture />
</Toolbar>
</AppBar>
</div>
);
};

export default Header;
60 changes: 60 additions & 0 deletions web-apps/client/src/components/imageList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, {useState, useEffect, useContext} from 'react';
import {ProductContext} from '../contexts/productStore';
import Loading from './loading';
import ProductImage from './productImage';
import {makeStyles} from '@material-ui/core';

const useStyle = makeStyles(() => ({
container: {
width: '13rem',
height: '4.2rem',
display: 'flex',
alignItems: 'center',
overflowX: 'auto',
overflowY: 'hidden',
marginTop: '0.3rem',
color: '#555',
},
}));

const ImageList = () => {
const classes = useStyle();
const {images} = useContext(ProductContext);
const [imageList, setImageList] = useState('');

const buildImageList = (images) => {
let result = '';

if (images.length) {
result = images.map((image, index) => {
if (!image.loading) {
return (
<ProductImage
key={index}
mobile={image.mobile}
name={image.name}
deskTop={image.deskTop}
/>
);
} else {
return <Loading key={index} />;
}
});
} else if (images.length === 0) {
result = '사진을 등록해 주세요';
} else {
throw new Error('image context is not an array!!!');
}

return result;
};

useEffect(() => {
const newImageList = buildImageList(images);
setImageList(newImageList);
}, [images]);

return <div className={classes.container}>{imageList}</div>;
};

export default ImageList;
Loading