-
Notifications
You must be signed in to change notification settings - Fork 18
[김수영] sprint10 #126
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
Merged
GANGYIKIM
merged 12 commits into
codeit-bootcamp-frontend:Next-김수영
from
swim-kim:Next-김수영-sprint10
Nov 5, 2024
The head ref may contain hidden characters: "Next-\uAE40\uC218\uC601-sprint10"
Merged
[김수영] sprint10 #126
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9f15d4f
feat: 폴더구조 수정, header 추가
c19c9d4
feat:베스트게시글 ui
924f174
feat: 게시물리스트 ui
6861cff
feat:전체게시물 불러오기
977828a
feat:게시물 검색
82b1114
style:반응형
b58c07f
fix:github branch-config
c27fd92
feat:add board 페이지
815fbaa
feat:detail페이지
6e7f34e
feat:댓글불러옴
ab49dd4
feat: api Response type 추가
3925c2d
Merge branch 'Next-김수영' into Next-김수영-sprint10
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import styled from "styled-components"; | ||
| export const AddBoardPage = styled.div` | ||
| width:100%; | ||
| display:flex; | ||
| flex-direction:column; | ||
| align-items:center; | ||
| justify-content:center; | ||
| `; | ||
| export const AddBoardPageContainer = styled.div` | ||
| max-width:1200px; | ||
| width:100%; | ||
| height:100%; | ||
| margin-top:24px; | ||
| display:flex; | ||
| gap:40px; | ||
| flex-direction:column; | ||
|
|
||
| @media(max-width:1199px){ | ||
| max-width:696px; | ||
| } | ||
| @media(max-width:767px){ | ||
| max-width:343px; | ||
| } | ||
| `; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import styled from "styled-components"; | ||
|
|
||
| export const BoardPage = styled.div` | ||
| width:100%; | ||
| display:flex; | ||
| flex-direction:column; | ||
| align-items:center; | ||
| justify-content:center; | ||
| `; | ||
| export const BoardPageContainer = styled.div` | ||
| max-width:1200px; | ||
| width:100%; | ||
| margin-top:24px; | ||
| display:flex; | ||
| gap:40px; | ||
| flex-direction:column; | ||
|
|
||
| @media(max-width:1199px){ | ||
| max-width:696px; | ||
| } | ||
| @media(max-width:767px){ | ||
| max-width:343px; | ||
| } | ||
| `; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| import React, { useState, useRef, useEffect } from 'react'; | ||
| import * as AS from './Styled'; | ||
| import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; | ||
|
|
||
| export interface AddItemValues { | ||
| image: File | null; | ||
| description: string; | ||
| title: string; | ||
| } | ||
|
|
||
| export default function AddBoard() { | ||
| const [values, setValues] = useState<AddItemValues>({ | ||
| image: null, | ||
| title: '', | ||
| description: '', | ||
| }); | ||
| const [isValid, setIsValid] = useState(false); | ||
| const [preview, setPreview] = useState<string | null>(null); | ||
| const [errorMessage, setErrorMessage] = useState<string>(""); | ||
| const inputRef = useRef<HTMLInputElement | null>(null); | ||
|
|
||
| const onChange = (name: keyof AddItemValues, value: File | null) => { | ||
| setValues((prevValues) => ({ | ||
| ...prevValues, | ||
| [name]: value, | ||
| })); | ||
| }; | ||
|
|
||
| const handleTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
| const { value } = event.target; | ||
| setValues((prevValues) => ({ | ||
| ...prevValues, | ||
| title: value, | ||
| })); | ||
| }; | ||
|
|
||
| const handleTextareaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
| const { value } = event.target; | ||
| setValues((prevValues) => ({ | ||
| ...prevValues, | ||
| description: value, | ||
| })); | ||
| }; | ||
|
|
||
| const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const file: File | null = e.target.files?.[0] || null; | ||
|
|
||
| if (file) { | ||
| if (values.image) { | ||
| setErrorMessage("이미지 파일이 이미 선택되었습니다."); | ||
| return; | ||
| } | ||
| onChange("image", file); | ||
| const objectUrl = URL.createObjectURL(file); | ||
| setPreview(objectUrl); | ||
|
|
||
| } else { | ||
| onChange("image", null); | ||
| setPreview(null); | ||
| } | ||
| }; | ||
|
|
||
| const handleClearClick = () => { | ||
| if (inputRef.current) { | ||
| inputRef.current.value = ''; | ||
| } | ||
| onChange("image", null); | ||
| setPreview(null); | ||
| setErrorMessage(""); | ||
| }; | ||
|
|
||
| const isFormValid = (values: AddItemValues) => { | ||
| return ( | ||
| values.title.trim() !== '' && | ||
| values.description.trim() !== '' && | ||
| values.image !== null | ||
| ); | ||
| }; | ||
|
|
||
| const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { | ||
| e.preventDefault(); | ||
| if (!isFormValid(values)) { | ||
| return; | ||
| } | ||
| console.log(values); | ||
|
|
||
| }; | ||
|
|
||
| useEffect(() => { | ||
| setIsValid(isFormValid(values)); | ||
| }, [values]); | ||
|
|
||
| return ( | ||
| <AS.AddBoardForm onSubmit={handleSubmit}> | ||
| <AS.TitleContainer> | ||
| <AS.Title>게시물 쓰기</AS.Title> | ||
| <AS.AddButton type="submit" disabled={!isValid}>등록</AS.AddButton> | ||
| </AS.TitleContainer> | ||
| <AS.InputFormContainer> | ||
| <AS.InputContainer> | ||
| <AS.InputLabel>*제목</AS.InputLabel> | ||
| <AS.ContentInput | ||
| type='text' | ||
| name='title' | ||
| value={values.title} | ||
| placeholder="제목을 입력해주세요" | ||
| onChange={handleTitleChange} | ||
| /> | ||
| </AS.InputContainer> | ||
| <AS.InputContainer> | ||
| <AS.InputLabel>*내용</AS.InputLabel> | ||
| <AS.TextareaInput | ||
| name='description' | ||
| value={values.description} | ||
| placeholder="내용을 입력해주세요" | ||
| onChange={handleTextareaChange} | ||
| /> | ||
| </AS.InputContainer> | ||
| <AS.InputContainer> | ||
| <AS.InputLabel>이미지</AS.InputLabel> | ||
| <AS.ImgBox> | ||
| <AS.CustomButton onClick={() => {inputRef.current?.click();}}> | ||
| <AS.ContentInput | ||
| type='file' | ||
| name='image' | ||
| ref={inputRef} | ||
| onChange={handleImageChange} | ||
| style={{ display: 'none' }} | ||
| /> | ||
| <AS.PlusIcon icon={faPlus} style={{ color: "#9CA3AF" }} /> | ||
| <AS.ButtonText>이미지 등록</AS.ButtonText> | ||
| </AS.CustomButton> | ||
| <AS.Upload> | ||
| {preview && ( | ||
| <AS.Preview src={preview} alt="이미지 미리보기" style={{ objectFit: "cover" }} /> | ||
| )} | ||
| {values.image && ( | ||
| <AS.DeleteButton icon={faXmark} onClick={handleClearClick} /> | ||
| )} | ||
| </AS.Upload> | ||
|
|
||
| </AS.ImgBox> | ||
| {errorMessage && <AS.ErrorMessage>{errorMessage}</AS.ErrorMessage>} | ||
| </AS.InputContainer> | ||
| </AS.InputFormContainer> | ||
| </AS.AddBoardForm> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| import styled from "styled-components"; | ||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||
|
|
||
| export const AddBoardForm = styled.form` | ||
| width:100%; | ||
| height:100%; | ||
| display:flex; | ||
| flex-direction:column; | ||
| gap:32px; | ||
| `; | ||
|
|
||
| export const TitleContainer = styled.div` | ||
| width:100%; | ||
| display:flex; | ||
| justify-content:space-between; | ||
| `; | ||
|
|
||
| export const Title = styled.div` | ||
| color: #1F2937; | ||
| font-size: 20px; | ||
| font-weight: 700; | ||
| line-height: 32px; | ||
| `; | ||
|
|
||
| export const AddButton = styled.button` | ||
| width:74px; | ||
| height: 42px; | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| border-radius: 8px; | ||
| border:none; | ||
| background:#3692FF; | ||
| color: #F3F4F6; | ||
| font-size: 16px; | ||
| font-weight: 600; | ||
| line-height: 26px; | ||
| cursor:pointer; | ||
| &:disabled { | ||
| background:#9CA3AF; | ||
| } | ||
| `; | ||
|
|
||
| export const InputFormContainer = styled.div` | ||
| width:100%; | ||
| display:flex; | ||
| gap:24px; | ||
| flex-direction:column; | ||
| `; | ||
| export const InputContainer = styled.div` | ||
| width:100%; | ||
| display:flex; | ||
| gap:12px; | ||
| flex-direction:column; | ||
| `; | ||
|
|
||
| export const InputLabel = styled.label` | ||
| color: #1F2937; | ||
| font-size: 18px; | ||
| font-weight: 700; | ||
| line-height: 26px; | ||
| `; | ||
| export const ContentInput = styled.input` | ||
| display: flex; | ||
| height: 56px; | ||
| padding: 16px 24px; | ||
| border-radius: 12px; | ||
| background: #F3F4F6; | ||
| border:none; | ||
| z-index:0 | ||
| &::placeholder { | ||
| color: #9CA3AF; | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 26px; | ||
| } | ||
| `; | ||
| export const TextareaInput = styled.textarea` | ||
| height: 250px; | ||
| padding: 16px 24px; | ||
| border:none; | ||
| border-radius: 12px; | ||
| background: var(--Cool-Gray-100, #F3F4F6); | ||
| resize:none; | ||
| &::placeholder { | ||
| color: #9CA3AF; | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 26px; | ||
| } | ||
| `; | ||
|
|
||
|
|
||
| export const ImgBox = styled.div` | ||
| width: 588px; | ||
| display: flex; | ||
| gap: 24px; | ||
|
|
||
| @media (max-width: 1199px) { | ||
| width: 346px; | ||
| gap: 10px; | ||
| } | ||
| `; | ||
|
|
||
| export const CustomButton = styled.button` | ||
| width: 282px; | ||
| height: 282px; | ||
| display: flex; | ||
| background: var(--Secondary-200, #F3F4F6); | ||
| border-radius: 12px; | ||
| justify-content: center; | ||
| align-items: center; | ||
| flex-direction: column; | ||
| border: none; | ||
| @media (max-width: 1199px) { | ||
| width: 168px; | ||
| height: 168px; | ||
| } | ||
| `; | ||
|
|
||
|
|
||
| export const PlusIcon = styled(FontAwesomeIcon)` | ||
| width: 48px; | ||
| height: 48px; | ||
| background: none; | ||
|
|
||
| @media (max-width: 1199px) { | ||
| width: 24px; | ||
| height: 24px; | ||
| } | ||
| `; | ||
|
|
||
| export const ButtonText = styled.span` | ||
| color: #9CA3AF; | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 26px; | ||
| background: none; | ||
| `; | ||
|
|
||
| export const Upload = styled.div` | ||
| width: 282px; | ||
| height: 282px; | ||
|
|
||
| @media (max-width: 1199px) { | ||
| width: 168px; | ||
| height: 168px; | ||
| } | ||
| `; | ||
|
|
||
| export const Preview = styled.img` | ||
| width: 100%; | ||
| height: 100%; | ||
| background-color: blue; | ||
| border-radius: 12px; | ||
| object-fit: cover; | ||
| `; | ||
|
|
||
| export const UploadButton = styled.button` | ||
| background: none; | ||
| border: none; | ||
| position: relative; | ||
| left: 248px; | ||
| bottom: 276px; | ||
| cursor: pointer; | ||
| color: #9CA3AF; | ||
| @media (max-width: 1199px) { | ||
| left: 130px; | ||
| bottom: 160px; | ||
| } | ||
| `; | ||
|
|
||
| export const ErrorMessage = styled.span` | ||
| color: #F74747; | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 26px; | ||
| `; | ||
| export const DeleteButton = styled(FontAwesomeIcon)` | ||
| width:24px; | ||
| height:24px; | ||
| position:relative; | ||
| bottom:270px; | ||
| left:240px; | ||
| @media(max-width:1199px) { | ||
| bottom:160px; | ||
| left:130px; | ||
| } | ||
| `; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
P2:
file input의 경우 대부분 정해진 확장자의 파일만을 받게 되는데 이를 위해 input의 accept 속성을 통해 원하는 확장자를 제안하고
파일이 들어오면
handleImageChange같은 함수에서 해당 파일의 확장자를 확인하는 로직이 필요합니다~나중에 시간이 되실때 추가해보세요
https://developer.mozilla.org/ko/docs/Web/HTML/Element/input/file