-
Notifications
You must be signed in to change notification settings - Fork 37
[이석찬]Sprint8 #290
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
The head ref may contain hidden characters: "React-\uC774\uC11D\uCC2C-Sprint8"
[이석찬]Sprint8 #290
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import React, { useState, useRef, ChangeEvent } from "react"; | ||
| import PlusIcon from "../asset/icon/ic_plus.png"; | ||
| import CloseIcon from "../asset/icon/ic_X.png"; | ||
| import styles from "./AddImgForm.module.css"; | ||
|
|
||
| interface AddImgFormProps { | ||
| label: string; | ||
| } | ||
|
|
||
| function AddImgForm({ label }: AddImgFormProps) { | ||
| const [imagePreviewUrl, setImagePreviewUrl] = useState<string>(""); | ||
| const fileInputRef = useRef<HTMLInputElement>(null); | ||
|
|
||
| function handleImageChange(e: ChangeEvent<HTMLInputElement>) { | ||
| const file = e.target.files?.[0]; | ||
| if (file) { | ||
| const imageUrl = URL.createObjectURL(file); | ||
| setImagePreviewUrl(imageUrl); | ||
| } | ||
| } | ||
|
|
||
| function handleClick() { | ||
| fileInputRef.current?.click(); | ||
| } | ||
|
|
||
| function handleRemoveImage() { | ||
| setImagePreviewUrl(""); | ||
| if (fileInputRef.current) { | ||
| fileInputRef.current.value = ""; | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <label>{label}</label> | ||
| <div className={styles["img-upload-container"]}> | ||
| <div | ||
| onClick={handleClick} | ||
| className={styles["img-upload-btn"]} | ||
| style={{ cursor: "pointer" }} | ||
| > | ||
| <img | ||
| src={PlusIcon} | ||
| alt="업로드 아이콘" | ||
| className={styles["plus-icon"]} | ||
| /> | ||
| <p className={styles["img-upload-text"]}>이미지 등록</p> | ||
| </div> | ||
|
|
||
| {imagePreviewUrl && ( | ||
| <div className={styles["img-preview-container"]}> | ||
| <img | ||
| src={imagePreviewUrl} | ||
| alt="미리보기" | ||
| className={styles["img-preview"]} | ||
| /> | ||
| <img | ||
| src={CloseIcon} | ||
| alt="삭제 버튼" | ||
| onClick={handleRemoveImage} | ||
| className={styles["img-remove-button"]} | ||
| style={{ cursor: "pointer" }} | ||
| /> | ||
| </div> | ||
| )} | ||
| </div> | ||
|
|
||
| <input | ||
| ref={fileInputRef} | ||
| id="img-upload" | ||
| type="file" | ||
| accept="image/*" | ||
| onChange={handleImageChange} | ||
| style={{ display: "none" }} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default AddImgForm; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,42 @@ | ||
| import React from "react"; | ||
| import "./AddItemForm.css"; | ||
| import styles from "./AddItemForm.module.css"; | ||
|
|
||
| interface AddItemFormProps { | ||
| label: string; | ||
| type?: string; | ||
| placeholder: string; | ||
| id: string; | ||
| value: string; | ||
| onChange: ( | ||
| event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | ||
| ) => void; | ||
| isTextArea?: boolean; | ||
| onKeyDown?: ( | ||
| event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement> | ||
| ) => void; | ||
| } | ||
|
Comment on lines
+4
to
+17
Collaborator
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. 오 공통 컴포넌트를 설계해보신것같네요ㅎㅎ 저라면 기존에 사용하고 있는 속성들을 그대로 가져와서 정의했을것같아요...! interface AddItemFormProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement | HTMLInputElement> {
label: string;
}이런식으로 하면, 깜빡하고 누락한 속성도 처리할 수 있거든요ㅎㅎ 이건 조금 어려우니 멘토링떄 같이 보면 좋을것같아요~ |
||
|
|
||
| function AddItemForm({ | ||
| label, | ||
| type, | ||
| type = "text", | ||
| placeholder, | ||
| id, | ||
| value, | ||
| onChange, | ||
| isTextArea, | ||
| isTextArea = false, | ||
| onKeyDown, | ||
| }) { | ||
| }: AddItemFormProps) { | ||
| return ( | ||
| <div className="form-field"> | ||
| <div className={styles["form-field"]}> | ||
| <label htmlFor={id}>{label}</label> | ||
| {isTextArea ? ( | ||
| <textarea | ||
| id={id} | ||
| type={type} | ||
| placeholder={placeholder} | ||
| value={value} | ||
| onChange={onChange} | ||
| onKeyDown={onKeyDown} | ||
| className={styles["textarea"]} | ||
| /> | ||
| ) : ( | ||
| <input | ||
|
|
@@ -31,6 +46,7 @@ function AddItemForm({ | |
| value={value} | ||
| onChange={onChange} | ||
| onKeyDown={onKeyDown} | ||
| className={styles["input"]} | ||
| /> | ||
| )} | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,20 @@ | ||
| import React, { useState } from "react"; | ||
| import AddItemForm from "./AddItemForm"; | ||
| import React, { useState, KeyboardEvent } from "react"; | ||
| import AddItemForm from "./AddItemForm.tsx"; | ||
| import CloseIcon from "../asset/icon/ic_X.png"; | ||
| import "./AddTagForm.css"; | ||
| import styles from "./AddTagForm.module.css"; | ||
|
|
||
| function AddTagForm({ tags, onAddTag, onRemoveTag }) { | ||
| const [input, setInput] = useState(""); | ||
| interface AddTagFormProps { | ||
| tags: string[]; | ||
| onAddTag: (tag: string) => void; | ||
| onRemoveTag: (tag: string) => void; | ||
| } | ||
|
Comment on lines
+6
to
+10
Collaborator
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. 필요한거만 잘 만들어주신것같네요ㅎㅎ! 조금더 개선한다면 중복된 tag는 걸러주는 로직을 넣으면 좀더 좋겠네요ㅎㅎ! 왜냐하면 tag를 key로 사용하는데, 중복된 값이 들어오면 문제가 될꺼거든요ㅠ |
||
|
|
||
| function AddTagForm({ tags, onAddTag, onRemoveTag }: AddTagFormProps) { | ||
| const [input, setInput] = useState<string>(""); | ||
|
|
||
| const onPressEnter = (event) => { | ||
| function onPressEnter( | ||
| event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement> | ||
| ): void { | ||
| if (event.nativeEvent.isComposing) return; | ||
|
|
||
| const inputString = input.trim(); | ||
|
|
@@ -15,7 +23,7 @@ function AddTagForm({ tags, onAddTag, onRemoveTag }) { | |
| onAddTag(inputString); | ||
| setInput(""); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
|
|
@@ -28,16 +36,16 @@ function AddTagForm({ tags, onAddTag, onRemoveTag }) { | |
| placeholder="태그를 입력해주세요" | ||
| /> | ||
|
|
||
| <div className="tag-buttons-section"> | ||
| <div className={styles["tag-buttons-section"]}> | ||
| {tags.map((tag) => ( | ||
| <div key={`tag-${tag}`} className="tag"> | ||
| <span className="tag-text">#{tag}</span> | ||
| <div key={`tag-${tag}`} className={styles["tag"]}> | ||
| <span className={styles["tag-text"]}>#{tag}</span> | ||
| <img | ||
| src={CloseIcon} | ||
| alt="태그 삭제 버튼" | ||
| onClick={() => onRemoveTag(tag)} | ||
| aria-label={`${tag} 태그 삭제`} | ||
| className="tag-delete-button" | ||
| className={styles["tag-delete-button"]} | ||
| /> | ||
| </div> | ||
| ))} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,39 +1,43 @@ | ||
| import React, { useState } from "react"; | ||
| import "./detailInquiry.css"; | ||
| import React, { useState, ChangeEvent } from "react"; | ||
| import styles from "./detailInquiry.module.css"; | ||
|
|
||
| function Inquiry() { | ||
| const [inputValue, setInputValue] = useState(""); | ||
| interface InquiryProps {} | ||
|
|
||
| const handleChange = (event) => { | ||
| function Inquiry(props: InquiryProps) { | ||
| const [inputValue, setInputValue] = useState<string>(""); | ||
|
|
||
| function handleChange(event: ChangeEvent<HTMLTextAreaElement>): void { | ||
| setInputValue(event.target.value); | ||
| }; | ||
| } | ||
|
|
||
| const handleSubmit = () => { | ||
| function handleSubmit(): void { | ||
| if (inputValue.trim()) { | ||
| alert(`문의 내용: ${inputValue}`); | ||
| setInputValue(""); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| const isButtonEnabled = inputValue.trim().length > 0; | ||
|
Collaborator
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. 이런 값은 물론 이렇게 해도 되지만 좀더 성능을 끌어올리고 싶다면 useMemo를 활용하는것도 좋아요ㅎㅎ! const isButtonEnabled = useMemo(() => {
return inputValue.trim().length > 0;
}, [inputValue]); |
||
|
|
||
| return ( | ||
| <div className="form-container"> | ||
| <label htmlFor="inquiry" className="form-label"> | ||
| <div className={styles["form-container"]}> | ||
| <label htmlFor="inquiry" className={styles["form-label"]}> | ||
| 문의하기 | ||
| </label> | ||
| <textarea | ||
| className="form-textarea" | ||
| className={styles["form-textarea"]} | ||
| id="inquiry" | ||
| placeholder="개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다." | ||
| value={inputValue} | ||
| onChange={handleChange} | ||
| /> | ||
| <div className="button-container"> | ||
| <div className={styles["button-container"]}> | ||
| <button | ||
| className={`form-button ${isButtonEnabled ? "enabled" : "disabled"}`} | ||
| className={`${styles["form-button"]} ${ | ||
| isButtonEnabled ? styles["enabled"] : styles["disabled"] | ||
| }`} | ||
| onClick={handleSubmit} | ||
| disabled={!inputValue.trim()} | ||
| disabled={!isButtonEnabled} | ||
| > | ||
| 등록 | ||
| </button> | ||
|
|
||
This file was deleted.
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.
잘 만드신것같아요 AddImgForm 컴포넌트
조금만 더 나아가서... 아마 이미지는 서버에다가 업로드를 하고, 응답값으로 업로드한 이미지의 url을 받을꺼에요
https://panda-market-api.vercel.app/docs/#/Image/ImageUpload
그럼 여기서 이 컴포넌트를 사용했을때 서버에 업로드하고 url을 위로 올리는 작업이 필요하게 될거에요~
그래서
위 코드처럼 이미지를 미리보기로 만든건 좋은데, 좀더 발전하면 서버에 이미지를 업로드하고 url을 받아와서 해당 url로 이미지를 보여주는게 좀더 좋을것같아요ㅎㅎㅎ!