Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions components/_styled/addboardStyled.tsx
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;
}
`;
24 changes: 24 additions & 0 deletions components/_styled/boardsStyled.tsx
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;
}
`;
148 changes: 148 additions & 0 deletions components/addboard/AddBoard.tsx
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
Copy link
Collaborator

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

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>
);
}
189 changes: 189 additions & 0 deletions components/addboard/Styled.tsx
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;
}
`;
Loading