[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 12 'post-tweet-form.tsx'
2024. 7. 8. 18:59ㆍ웹개발 포트폴리오
728x90
반응형
post-tweet--form.tsx
import { addDoc, collection, updateDoc } from "firebase/firestore";
import { useState } from "react";
import styled from "styled-components";
import { auth, db, storage } from "../firebase";
import { getDownloadURL, ref, uploadBytes } from "firebase/storage";
// 폼 스타일 정의
const Form = styled.form`
display: flex;
flex-direction: column;
gap: 10px;
`;
// 텍스트 에어리어 스타일 정의
const TextArea = styled.textarea`
border: 2px solid #9D7FF5;
padding: 20px;
border-radius: 20px;
color: black;
width: 100%;
resize: none;
font-family: pretendard-regular;
word-wrap: break-word;
&:focus {
outline: none;
}
input::placeholder {
font-size: 1.2rem; /* 기본 설정 */
}
input::placeholder {
font-size: 1.2rem; /* 일반적인 설정 */
}
`;
// 파일 첨부 버튼 스타일 정의
const AttachFileButton = styled.label`
padding: 10px 0px;
color: #9D7FF5;
text-align: center;
border-radius: 20px;
border: 1px solid #9D7FF5;
cursor: pointer;
font-weight: 600;
`;
// 파일 첨부 입력 스타일 정의
const AttachFileInput = styled.input`
display: none;
object-fit: cover;
`;
// 제출 버튼 스타일 정의
const SubmitBtn = styled.input`
background-color: #9D7FF5;
color: white;
border: none;
padding: 10px 0px;
border-radius: 20px;
cursor: pointer;
font-weight: 600;
font-size: 1rem;
&:hover,
&:active {
opacity: 0.9;
}
@media (max-width: 1024px) {
font-size: 1rem; // 아이패드 크기 조정
padding: 15px 0px; // 아이패드 패딩 조정
input::placeholder {
font-size: 1rem; /* 태블릿 설정 */
}
}
@media (max-width: 768px) {
font-size: 1rem; // 모바일 크기 조정
padding: 12px 0px; // 모바일 패딩 조정
input::placeholder {
font-size: 0.9rem; /* 모바일 설정 */
}
}
`;
export default function PostTweetForm() {
const [isLoading, setLoading] = useState(false); // 로딩 상태
const [tweet, setTweet] = useState(""); // 트윗 내용 상태
const [files, setFiles] = useState<File[]>([]); // 파일 상태
// 파일 변경 핸들러
const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { files } = e.target;
if (files) {
const fileArray = Array.from(files).slice(0, 4); // 최대 4개의 파일을 선택하도록 제한
setFiles(fileArray);
}
};
// 텍스트 에어리어 변경 핸들러
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTweet(e.target.value);
};
// 폼 제출 핸들러
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const user = auth.currentUser;
if (!user || isLoading || tweet === "" || tweet.length > 180) return; // 유효성 검사
try {
setLoading(true);
// Firestore에 트윗 데이터 추가
const docRef = await addDoc(collection(db, "tweets"), {
tweet,
createdAt: Date.now(),
username: user.displayName || "Anonymous",
userId: user.uid,
});
// 이미지 파일 업로드 및 URL 얻기
const urls = [];
for (const file of files) {
const locationRef = ref(storage, `tweets/${user.uid}/${docRef.id}/${file.name}`);
const result = await uploadBytes(locationRef, file);
const url = await getDownloadURL(result.ref);
urls.push(url);
}
// Firestore에 이미지 URL 업데이트
if (urls.length > 0) {
await updateDoc(docRef, {
photos: urls,
});
}
// 폼 초기화
setTweet("");
setFiles([]);
} catch (e) {
console.log(e);
} finally {
setLoading(false);
}
};
return (
<Form onSubmit={onSubmit}>
<TextArea
required
rows={5}
maxLength={180}
onChange={onChange}
value={tweet}
placeholder="자유롭게 적어보세요."
/>
<AttachFileButton htmlFor="file">
{files.length > 0 ? "파일 첨부 완료" : "이미지 첨부"}
</AttachFileButton>
<AttachFileInput
onChange={onFileChange}
type="file"
id="file"
accept="image/*"
multiple
/>
<SubmitBtn
type="submit"
value={isLoading ? "포스팅..." : "포스트"}
/>
</Form>
);
}
위 코드는 트윗을 작성하고 업로드하는 폼을 구현한 React 컴포넌트입니다. 주요 기능은 다음과 같습니다:
- 포스트 내용 입력: TextArea를 사용하여 사용자가 포스트 내용을 입력할 수 있습니다.
- 이미지 파일 첨부: AttachFileButton과 AttachFileInput을 사용하여 최대 4개의 이미지를 첨부할 수 있습니다.
- 포스트 업로드: SubmitBtn을 사용하여 포스트를 업로드할 수 있습니다. 업로드 시 Firestore에 트윗 데이터를 저장하고, 이미지를 Firebase Storage에 업로드합니다. 업로드된 이미지 URL을 Firestore 문서에 업데이트합니다.
- 스타일링: 여러 styled-components를 사용하여 폼, 버튼, 텍스트 에어리어 등의 스타일을 정의합니다.
어려웠던 부분 설명
const fileArray = Array.from(files).slice(0, 4); // 최대 4개의 파일을 선택하도록 제한
- files 객체:
- files는 일반적으로 파일 입력 필드 (<input type="file">)에서 사용자가 선택한 파일들의 목록을 나타냅니다. 이 객체는 FileList 타입으로, 배열과 유사하지만 실제 배열은 아닙니다.
- Array.from(files):
- Array.from 메서드는 유사 배열 객체나 반복 가능한 객체(예: FileList)를 배열로 변환합니다.
- files 객체는 유사 배열 객체이므로 Array.from을 사용하여 실제 배열로 변환합니다.
- .slice(0, 4):
- slice 메서드는 배열의 일부분을 선택하여 새로운 배열을 반환합니다. 원본 배열은 변경되지 않습니다.
- slice(0, 4)는 배열의 처음부터 (0 인덱스) 네 번째 요소까지(4 인덱스, 끝 인덱스는 포함되지 않음)를 선택합니다.
- 따라서, 최대 4개의 파일만 선택되도록 제한합니다.
728x90
반응형
'웹개발 포트폴리오' 카테고리의 다른 글
[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 13 'timeline.tsx' (0) | 2024.07.08 |
---|---|
[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 11 'post.tsx' (0) | 2024.07.08 |
[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 10 'profile.tsx' (0) | 2024.07.08 |
[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 9 'create-account.tsx' & 'login.tsx' (0) | 2024.07.08 |
[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 8 'home.tsx' (0) | 2024.07.08 |