table 태그를 사용하여 디자인을 구성하였다. 게시글에 대한 데이터는 db에 저장하였다. 그리고 게시글을 등록했을 경우 페이지가 이동되면서 게시판에서 내가 쓴 글을 볼 수 있도록 구현하였다. 아래는 내가 꾸민 게시판 글쓰기 페이지이다.
게시판 게시글 생성하기
게시판을 구성할 때 필요한 것은 사진을 참조해서 보면 맨 위에서부터 글 제목, 작성자, 내용, 비밀번호와 비밀글 설정, 개인정보 수집 및 동의 등이 필요했다. 이메일의 경우는 비회원과 회원을 구분하지 않으므로 비회원도 글을 작성할 수 있도록 하였다. 그래서 이는 꼭 필요한 항목이 아니기에 없어도 되고, 작성하지 않아도 alert창이 뜨지 않게 구성하였다.
useInput 생성
코드가 너무 길어서 커스텀하여 useInput과 useInputRef를 구성하였다.
//useInput
import { useState, useCallback } from 'react';
// initValue = null해둔다
export default (initValue = null) => {
const [value, setter] = useState(initValue);
const handler = useCallback((e) => {
setter(e.target.value);
}, []);
// 이 return은 위의 밸류값과 핸들러를 써야 이 useInput을 쓸 수 있다
return [value, handler];
};
//useInputRef
import { useState, useCallback, useRef } from 'react';
// initValue = null해둔다
export default (initValue = null) => {
const [value, setter] = useState(initValue);
const handler = useCallback((e) => {
setter(e.target.value);
}, []);
const valueFocus = useRef(null);
// 이 return은 위의 밸류값과 핸들러를 써야 이 useInput을 쓸 수 있다
return [value, handler, valueFocus];
};
이는 Handler도 구성하여 써야 하는데 그렇게 되면 길어진 코드를 더 길게 만드는 것이다. 그래서 간략하게 하기 위하여 자주 쓰는 코드를 커스텀하여 useInput에 넣어 쓰도록 하였다.
추가)
import { useState, useCallback } from 'react';
// initValue = null해둔다
export default (initValue = null, props) => {
const [value, setter] = useState(initValue);
const handler = useCallback((e) => {
setter(e.target.value);
}, []);
const validate = `${props}를 입력하세요`;
// 이 return은 위의 밸류값과 핸들러를 써야 이 useInput을 쓸 수 있다
return [value, handler, validate];
};
useInput창에 validate를 추가하여 props를 받아와 alert()을 띄우게 하였다.
이러한 alert창이 여러개 띄워야 한다면 계속적으로 반복되는 코드를 useinput에 넣어 주어 효과적으로 코드가 실행되게 하였다.
항목 구성
글쓰기 항목을 차례대로 useState와 useInput으로 구성해주었다. useInputRef로 구성한 것을 작성하지 못 했을 때 사용자가 바로 작성할 수 있도록 설계하였다. 그래야 사용자의 편의와 사용자의 글쓰기 작성 속도가 올라가기 때문이다.
1) 글제목
글제목은 select태그로 상품, 배송, 교환/반품, 배송전 취소, 입금, 기타 등의 메뉴로 구성하여 사용자가 자주 찾는 항목으로 구성하였다.
2) 작성자
이는 필수 항목이며, 작성하지 않고 작성완료 버튼을 눌렀을 경우 ref속성을 사용하여 사용자가 바로 입력할 수 있도록 설정하였다.
3) 이메일
이는 필수 항목은 아니며, 비회원의 경우 따로 알림을 보내주기 위하여 설정하였다. 구성은 input을 두 개로 설정하고, 이메일 항목에 대한 것은 select태그로 만들어 놓았다. 그리고 자주 쓰는 메일 목록을 option으로 하여 기입이 편하게 하였다. 그리고 직접입력에서 애를 먹었다.
input과 select값은 연결되어 있다. 즉, select에서 option을 선택했을 경우 이를 input에 이 값이 들어가게 하였다. input에 id값을 설정하여 태그에 value값을 넣어주게 하였다. 그리고, 직접 입력의 경우 해당되는 value값 etc일 경우 사용자가 바로 기입할 수 있도록 focus를 해주어 input값을 넣어주도록 하였다. 나머지는 코드를 참고하여 이해할 수 있도록 아래에 코드를 첨부하였다.
- 프론트엔드 코드
<input id="email" type="text" onChange={onEmailPreHandler} />@
<input type="text" id="email_txt" ref={emailFocus}
onChange={(e) => setEmailPost(e.target.value)} />
<select className="select" name="sel_email" onChange={onChangeHandler}>
<option value="">- 이메일 선택 -</option>
<option value="naver.com">naver.com</option>
<option value="hanmail.net">hanmail.net</option>
<option value="gmail.com">gmail.com</option>
<option value="yahoo.com">yahoo.com</option>
<option value="hotmail.com">hotmail.com</option>
<option value="korea.com">korea.com</option>
<option value="nate.com">nate.com</option>
<option value="etc">직접입력</option></select>
- 프론트엔드 핸들러
const email_text = document.querySelector("#email_txt");
const onChangeHandler = (event) => {
if (event.target.value === "etc") {
email_text.value = "";
email_text.disabled = false;
emailFocus.current.focus();
} else {
email_text.value = event.target.value;
setEmailPost(event.target.value);
email_text.disabled = true;
}
};
4) 내용
CKEditor를 사용 기본값 주문번호, 상품명(옵션), 문의 내용을 미리 기입하여 원하는 값만 얻고자 하였다. 그리고 내용을 에디터에 내용을 기입하더라도 위의 세개를 기입하지 않았다면 알림이 뜨게 설정하였다. 이는 editor의 값을 가져와 모든 내용을 가져와서 해당부분을 split하여 주문번호, 상품명, 문의 내용의 길이가 달라지지 않았다면 알림이 뜨게 하였다.
const data = editor.getData();
setContents(data);
setOrderNumber(contents.split("</p>")[2]?.length);
setProductOption(contents.split("</p>")[3]?.length);
setOrderContent(contents.split("</p>")[4]?.length);
5) 첨부파일
첨부파일의 경우 필수 항목은 아니지만, 배열로 백엔드에 읽어줘야 한다. 이의 경우는 구글링해도 잘 나오지 않아 삽질을 조금 했다. formData로 하여 해당 구문을 묶어서 보내줘야 했다. 처음에 input으로 받아오는 첨부파일들을 배열로 만들어줘서 보냈는데 백엔드에서 읽지를 못해 s3와 연동하여 이미지가 업로드되게 하였다. 이 부분이 변화가 일어나지 않았다. 그래서 formData에 배열로 보내준 부분을 하나씩 각각해서 보내주니 백엔드에서 잘 읽어 주어 s3에 제대로 업로드되었다.
formData.append("imageArray", imageFile1);
formData.append("imageArray", imageFile2);
formData.append("imageArray", imageFile3);
formData.append("imageArray", imageFile4);
6) 비밀글 설정
이는 모든 사용자의 글을 공개글이 아닌 비밀글이라는 것을 인지시키기 위하여 공개글과 비밀글을 같이 두었다. 그리고 공개글을 사용할 수 없게 막았고, 비밀글만 활성화시켰다.
7) 개인정보 수집 및 이용 동의
위의 이메일등과 사용자가 입력한 정보를 활용하기 위하여 개인정보 제공 동의에 동의함을 얻기 위해 설정하였다. 요즘 개인정보에 민감한 사람들이 많기 때문에 이러한 부분을 잘 설정해놔야 한다.
8) 작성완료 버튼
모든 필수항목이 작성되었을 때 백엔드에 해당 정보를 들어가게 하였다.
const onSubmit = (event) => {
// 이벤트를 막아준다.
event.preventDefault();
if ([writer].includes("")) {
alert("작성자 항목은 필수 입력값입니다. ");
return writerFocus.current.focus();
}
if (emailPre !== "" && emailPost !== "") {
email = emailPre + "@" + emailPost;
}
if (contents === "") {
return alert("내용을 입력해주세요.");
}
if (orderNumber === 9) {
return alert("주문번호를 입력해주세요.");
}
if (productOption === 11) {
return alert("상품명을 입력해주세요.");
}
if (orderContent === 9) {
return alert("문의내용을 입력해주세요.");
}
if (password === "") {
alert("비밀번호 항목은 필수 입력값입니다.");
return passwordFocus.current.focus();
}
if (privacy === false) {
return alert("개인정보 수집 및 이용에 동의해 주시기 바랍니다.");
}
let formData = new FormData();
formData.append("title", title);
formData.append("writer", writer);
formData.append("email", email);
formData.append("contents", contents);
formData.append("imageArray", imageFile1);
formData.append("imageArray", imageFile2);
formData.append("imageArray", imageFile3);
formData.append("imageArray", imageFile4);
formData.append("password", password);
axios
.post("http://localhost:3001/notice", formData)
.then((response) => {
console.log("res", response);
alert("업로드가 되었습니다. ");
navigate("/board/cs");
})
.catch((data) => {
console.log(data);
alert("업로드가 되지 못했습니다. ");
});
};
폼으로 만들어 모든 정보를 옮겨주게 하였고, 이가 제대로 업로드되었을 경우 게시판으로 이동되게 설정하였다.