-
Notifications
You must be signed in to change notification settings - Fork 39
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
[장아영] Sprint 10 #270
The head ref may contain hidden characters: "Next-\uC7A5\uC544\uC601-sprint10"
[장아영] Sprint 10 #270
Changes from all commits
b258ec5
623800c
fc30a86
5532bfd
ff26d60
24ca9e2
ffed85b
b08dcf7
e0a52a7
373a9e3
c06112d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import styles from "./styles.module.scss"; | ||
import plusIcon from "@/assets/icons/ic_plus.png"; | ||
import { ST } from "next/dist/shared/lib/utils"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 안쓰이고 있는것같아요! |
||
import Image from "next/image"; | ||
import { useState, useRef, useEffect } from "react"; | ||
import deleteIcon from "@/assets/icons/ic_delete.svg"; | ||
|
||
export default function AddBoard() { | ||
const [preview, setPreview] = useState(); | ||
const [value, setValue] = useState(); | ||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useState에 초기값을 넣어주지않는다면 자동으로 undefined가 들어갑니다! 그러면 state의 type도 undefined가 되어요! 원시값이면 type을 선언해줄 필요까지는 없지만 초기값을 넣어줘어야한답니다! 아니면 | null도 하나의 방법이겠구요! |
||
const fileInputRef = useRef(null); | ||
const [formState, setFormState] = useState({ | ||
title: "", | ||
contents: "", | ||
}); | ||
|
||
const handleInput = (e) => { | ||
setFormState({ | ||
...formState, | ||
[e.target.name]: e.target.value, | ||
}); | ||
}; | ||
|
||
const handleChange = (e) => { | ||
const nextValue = e.target.files[0]; | ||
setValue(nextValue); | ||
}; | ||
Comment on lines
+17
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 각각의 event 타입도 선언을 해주면, 사용하는곳에서 조금 더 정확히 사용할 수 있습니다! |
||
|
||
const handleImageClick = () => { | ||
fileInputRef.current.click(); | ||
}; | ||
|
||
const handleCancelClick = () => { | ||
setPreview(null); | ||
setValue(null); | ||
fileInputRef.current.value = ""; | ||
}; | ||
|
||
useEffect(() => { | ||
if (!value) return; | ||
|
||
const nextPreview = URL.createObjectURL(value); | ||
setPreview(nextPreview); | ||
return () => URL.revokeObjectURL(nextPreview); | ||
}, [value]); | ||
|
||
const isFormValid = formState.title && formState.contents; | ||
|
||
return ( | ||
<div className={styles["page-container"]}> | ||
<div className={styles["title-and-button"]}> | ||
<p className={styles["form-title"]}>게시글 쓰기</p> | ||
<button | ||
className={`${styles["register-button"]} ${ | ||
isFormValid ? styles["register-button-active"] : "" | ||
}`} | ||
disabled={!isFormValid} | ||
type="submit" | ||
> | ||
등록 | ||
</button> | ||
</div> | ||
<form> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아직 api가 없어서 그런것같지만 onSubmit에 대한 핸들러가 없네요! |
||
<div className={styles["form-box"]}> | ||
<div className={styles["title"]}> | ||
<label className={styles["label"]}>*제목</label> | ||
<input | ||
className={styles["title-input-box"]} | ||
type="text" | ||
name="title" | ||
onChange={handleInput} | ||
placeholder="제목을 입력해주세요" | ||
required | ||
/> | ||
</div> | ||
<div className={styles["contents"]}> | ||
<label className={styles["label"]}>*내용</label> | ||
<textarea | ||
className={styles["contents-input-box"]} | ||
placeholder="내용을 입력해주세요" | ||
name="contents" | ||
onChange={handleInput} | ||
required | ||
/> | ||
</div> | ||
<div className={styles["image"]}> | ||
<label className={styles["label"]}>이미지</label> | ||
<div className={styles["img-register-container"]}> | ||
<div | ||
className={styles["img-register-box"]} | ||
onClick={handleImageClick} | ||
> | ||
<input | ||
ref={fileInputRef} | ||
className={styles["img-register"]} | ||
type="file" | ||
name="image" | ||
onChange={handleChange} | ||
/> | ||
<div className={styles["img-register-button"]}> | ||
<Image | ||
className={styles["plus-icon"]} | ||
src={plusIcon} | ||
alt="파일추가버튼" | ||
/> | ||
<p className={styles["register-button-text"]}>이미지 등록</p> | ||
</div> | ||
</div> | ||
{preview && ( | ||
<div className={styles["preview-image-box"]}> | ||
<Image | ||
className={styles["preview-image"]} | ||
src={preview} | ||
alt="이미지 미리보기" | ||
width="282" | ||
height="282" | ||
/> | ||
<button | ||
className={styles["delete-preview-image"]} | ||
onClick={handleCancelClick} | ||
> | ||
<Image | ||
src={deleteIcon} | ||
alt="미리보기 삭제" | ||
width="8" | ||
height="8" | ||
/> | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</form> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
@import "@/styles/import.scss"; | ||
|
||
.page-container { | ||
max-width: 1200px; | ||
margin: 0 auto; | ||
} | ||
|
||
.title-and-button { | ||
margin: 24px 0; | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
} | ||
|
||
.form-title { | ||
font-size: 20px; | ||
font-weight: 700; | ||
line-height: 23.87px; | ||
text-align: left; | ||
color: $cool-gray-800; | ||
} | ||
|
||
.register-button { | ||
width: 74px; | ||
height: 42px; | ||
border-radius: 8px; | ||
padding: 12px 23px; | ||
background-color: $cool-gray-400; | ||
font-size: 16px; | ||
font-weight: 600; | ||
text-align: left; | ||
color: #fff; | ||
:disabled { | ||
cursor: not-allowed; | ||
} | ||
&.register-button-active { | ||
background-color: $brand-blue; | ||
cursor: pointer; | ||
} | ||
} | ||
|
||
.form-box { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 24px; | ||
} | ||
|
||
.label { | ||
font-size: 18px; | ||
font-weight: 700; | ||
text-align: left; | ||
color: $cool-gray-800; | ||
margin-bottom: 12px; | ||
} | ||
|
||
.title-input-box { | ||
cursor: text; | ||
max-width: 1200px; | ||
height: 56px; | ||
border-radius: 12px; | ||
padding: 16px 24px; | ||
background-color: $cool-gray-100; | ||
font-size: 16px; | ||
font-weight: 400; | ||
line-height: 24px; | ||
color: $cool-gray-800; | ||
} | ||
.title-input-box::placeholder { | ||
text-align: left; | ||
color: $cool-gray-400; | ||
} | ||
|
||
.title, | ||
.contents, | ||
.image { | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
|
||
.contents-input-box { | ||
resize: none; | ||
max-width: 1200px; | ||
height: 282px; | ||
border-radius: 12px; | ||
padding: 16px 24px; | ||
background-color: $cool-gray-100; | ||
border: none; | ||
font-size: 16px; | ||
font-weight: 400; | ||
line-height: 24px; | ||
text-align: left; | ||
color: $cool-gray-800; | ||
} | ||
.contents-input-box::placeholder { | ||
color: $cool-gray-400; | ||
} | ||
|
||
.img-register-container { | ||
display: flex; | ||
flex-direction: row; | ||
} | ||
|
||
.img-register-box { | ||
cursor: pointer; | ||
position: relative; | ||
width: 282px; | ||
height: 282px; | ||
border-radius: 12px; | ||
background-color: $cool-gray-100; | ||
} | ||
|
||
.img-register { | ||
display: none; | ||
object-fit: cover; | ||
width: 100%; | ||
height: 100%; | ||
} | ||
|
||
.img-register-button { | ||
position: absolute; | ||
justify-content: center; | ||
align-items: center; | ||
top: 99px; | ||
left: 104px; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 13px; | ||
} | ||
|
||
.plus-icon { | ||
width: 48px; | ||
} | ||
|
||
.register-button-text { | ||
font-size: 16px; | ||
font-weight: 400; | ||
text-align: left; | ||
color: $cool-gray-400; | ||
} | ||
|
||
.preview-image-box { | ||
position: relative; | ||
} | ||
|
||
.preview-image { | ||
width: 282px; | ||
height: 282px; | ||
object-fit: cover; | ||
margin-left: 24px; | ||
border-radius: 12px; | ||
} | ||
|
||
.delete-preview-image { | ||
position: absolute; | ||
top: 8px; | ||
right: 8px; | ||
background-color: #3962ff; | ||
border-radius: 50%; | ||
width: 20px; | ||
height: 20px; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실제 데이터를 받기전에 mock으로 만드신 것은 아주 좋은 방법입니다! 예를들어 상수로 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import styles from "./styles.module.scss"; | ||
import profileImg from "@/assets/icons/profileImg.svg"; | ||
import Image from "next/image"; | ||
import kebabIcon from "@/assets/icons/ic_kebab.svg"; | ||
|
||
export default function Comment() { | ||
return ( | ||
<div className={styles["comment-card"]}> | ||
<div className={styles["card-top-line"]}> | ||
<p className={styles["comment-text"]}> | ||
혹시 사용기간이 어떻게 되실까요? | ||
</p> | ||
<Image src={kebabIcon} alt="케밥아이콘" /> | ||
</div> | ||
<div className={styles["comment-information-container"]}> | ||
<Image src={profileImg} alt="프로필이미지" /> | ||
<div className={styles["nickname-and-time"]}> | ||
<p className={styles["nickname"]}>nickname</p> | ||
<p className={styles["time"]}>1시간전</p> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addBoard 로 컴포넌트 이름을 바꿔주세요 😁
이 컴포넌트안에 너무 많은 컴포넌트들이 있는 것같아요!
파일 업로드를 할 수 있는 부분을 따로 뺀다면, 향후 어디서든 가져다 사용할 수 있고, 가독성도 늘어날 것 같습니다!
사실 파일업로드 뿐 아니라 기본 input이나 textArea도 제어컴포넌트와 함께 새로운 공용컴포넌트로 만들 수 있죠!