February 21, 2020
HTML/JavaSciript/React/ReactHooks/styled-compomnent/redux
Material-UI/CKEditor/Crypto.js/Webpack build
로그인 및 회원가입 페이지를 작업하면서 아이디 저장기능이 있었는데, 아이디 저장 클릭 시 암호화를 하여 브라우저에 저장을 하고 복호화를 하여 다시 정보를 가져왔다. 암호화 개념이 제대로 잡혀있지않아서 어려움을 겪었다.
const LoginForm = ({ history, handleClick, toSignUp }) => {
const [checkbox, setCheckbox] = useState(false);
const [idState, setId] = useState('');
const [failMsg, setFailMsg] = useState(false);
const classes = useStyles();
useEffect(() => {
const id = localStorage.getItem('saved_id');
if (id) {
const decryptedId = CryptoJS.AES.decrypt(id, '해시값');
const resolveId = decryptedId.toString(CryptoJS.enc.Utf8);
setId(resolveId);
setCheckbox(true);
}
}, []);
크립토js를 사용하여 양방향 암호화를 했는데 로그인시 체크박스가 체크되어있으면 로컬스토리지에 해시값을 붙혀 암호화를 한 후 보내서 저장하고 새로 렌더링시 복호화를 하여 로그인 입력창에 아이디값이 렌더링되도록 구현 하였다.
회원가입 부분 각 인풋별 유효성검사가 들어가는데 입력할때마다 체크를 해주고 가입버튼 클릭시에도 체크를 해주는 것이 잘 풀리지 않았다.
const validators = {
id: value =>
!checkId(value) &&
'아이디는 5~20글자의 영문, 숫자, 언더바, 하이픈만 사용 가능하며 시작 문자는 영문 또는 숫자입니다.',
password: value =>
!(value.length > 3) && '비밀번호의 최소 길이는 4글자입니다.',
rePassword: value =>
!(value === inputState.password) && '비밀번호가 일치하지 않습니다',
phoneNumber: value => {
if (toString(value).length === 0) {
return '필수 입력항목입니다.';
}
if (!checkPhoneNumber(value)) {
return '올바른 정보를 입력해주세요. (ex. 01012345678)';
}
},
sellerName: value => {
if (!value.length > 0) return '필수 입력항목입니다.';
if (!checkSellerName(value)) return '올바른 정보를 입력해주세요.';
},
sellerNameEng: value => {
if (!value.length > 0) return '필수 입력항목입니다.';
if (!checkSellerNameEng(value)) return '올바른 정보를 입력해주세요.';
},
csNumber: value => {
if (!value.length > 0) return '필수 입력항목입니다.';
if (!(value.length === 10 || value.length === 11))
return '10자리 또는 11자리 번호를 입력해주세요';
},
sellerSite: value =>
!checkUrl(value) &&
'올바른 주소를 입력해주세요. (ex. http://www.brandi.co.kr)',
kakaoId: () => {},
instaId: () => {},
};
const [errors, setErrors] = useState([]);
const setValidationResult = (key, data) => {
let nextState = errors.filter(error => error.key !== key);
if (data) {
nextState = nextState.concat({
key,
message: data,
});
}
setErrors(nextState);
};
const checkBeforeSubmit = () => {
const submitErrors = Object.entries(validators).reduce(
(prev, [key, validator]) => {
const value = inputState[key];
const result = validator(value, inputState);
if (!result) return prev;
return prev.concat({
key,
message: result,
});
},
[],
);
setErrors(submitErrors);
유효성 검사를 하는 객체하나를 정의해주면서 값은 체크하는 함수를 넣었다. 가입버튼 클릭시 유효성검사 객체를 순회하며 검사를 한 후 에러상태를 관리하는 배열에 추가해주었다.
<CKEditor
config={{
height: '400',
filebrowserImageUploadUrl: `${server_url}/product/original_image`,
toolbarGroups: [
{ name: 'styles' },
{ name: 'colors', groups: ['colors'] },
{ name: 'basicstyles', groups: ['basicstyles', 'cleanup'] },
{ name: 'insert' },
{ name: 'paragraph', groups: ['align'] },
{ name: 'document', groups: ['mode', 'document', 'doctools'] },
],
extraPlugins: 'justify,showblocks, colorbutton',
colorButton: 'colors',
removeButtons:
'Flash,Table,HorizontalRule,Smiley,SpecialChar,PageBreak,Iframe,Superscript,Subscript,Underline,RemoveFormat',
}}
data={editor.data}
onChange={onEditorChange}
onBeforeLoad={CKEDITOR => (CKEDITOR.disableAutoInline = true)}
>
<label>
<textarea defaultValue={editor.data} onChange={onChange} />
</label>
</CKEditor>
상품 등록부분 에디터는 CKEditor를 사용하였는데 사용자가 필요한 기능만 사용할 수 있게 옵션들을 커스터마이징 해주었다. 입력값을 상태관리하며 백엔드에서 longtext로 받을 수 있게 데이터를 html태그로 보내주며 이미지 업로드는 백앤드서버로 전송 후 s3에 저장한뒤 url을 응답으로 주면 응답을 받고 등록을 하여 img태그로 변환시킨다.
const takeStartDate = () => {
const year = startingDateTime.getFullYear().toString()
const month = (startingDateTime.getMonth() + 1).toString()
const date = startingDateTime.getDate().toString()
return year + '-' + month + '-' + date
}
const takeEndDate = () => {
const year = endingDateTime.getFullYear().toString()
const month = (endingDateTime.getMonth() + 1).toString()
const date = endingDateTime.getDate().toString()
return year + '-' + month + '-' + date
}
const checkType = type => {
if (type === '전체') return ''
if (type === '쇼핑몰') return '&seller_types_id=1;'
if (type === '마켓') return '&seller_types_id=2'
if (type === '로드샵') return '&seller_types_id=3'
if (type === '디자이너브랜드') return '&seller_types_id=4'
if (type === '제너럴브랜드') return '&seller_types_id=5'
if (type === '내셔널브랜드') return '&seller_types_id=6'
if (type === '뷰티') return '&seller_types_id=7'
}
const handleSearch = (value, ofs) => {
setLoading(true)
console.log(offset.value, takeStartDate())
axios
.get(
`${API_URL}/seller/list?limit=${
ofs.value
}&offset=${value}&start_date=${takeStartDate()}&end_date=${takeEndDate()}&account=${sellerId}&name_kr=${krName}&name_en=${enName}&site_url=${siteUrl}&representative_name=${managerName}&mobile_number=${managerNumber}&email=${managerMail}${checkType(
sellerProp
)}&id=${number}`
)
.then(res => {
console.log(res.data)
setSellerData(res.data.seller_info)
setTotal(res.data.seller_count)
console.log(res.data.seller_info)
setLoading(false)
})
.catch(err => {
alert('올바르지 않은 요청입니다')
console.log(err)
location.reload()
// setLoading(false);
})
}
셀러 검색부분 쿼리스트링 요청 부분을 구현하는데 검색옵션이 많고 페이지네이션도 처리해야 했기에 쉽지않은 부분이었다. 각 옵션별 타입을 백엔드와 맞추며 의사소통하고 그 과정에서 문제들을 해결해나가면서 어렵지만 재미를 느낀 부분이었다.