[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <posting.js>
2024. 12. 20. 11:04ㆍ웹개발 포트폴리오
728x90
반응형
import { addDoc, collection, updateDoc, serverTimestamp } from "firebase/firestore";
import React, { useState } from "react";
import styled from "styled-components";
import { auth, db, storage } from "../firebase";
import { getDownloadURL, ref, uploadBytes } from "firebase/storage";
import ReactQuill from 'react-quill';
import { useNavigate } from "react-router-dom";
import 'react-quill/dist/quill.snow.css';
const Wrapper = styled.div`
margin-left: 20vw;
overflow-y: auto;
`;
const Form = styled.form`
margin-left: 40vw;
width: 90%;
max-width: 1000px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
overflow-y: auto;
@media (max-width: 768px) {
width: 95%;
gap: 15px;
}
`;
const StyledQuill = styled(ReactQuill)`
margin-top: 15vh;
width: 100%;
height: 50vh;
@media (max-width: 768px) {
height: 40vh;
}
`;
const AttachFileButton = styled.label`
color: #333;
cursor: pointer;
border: 2px solid #333;
padding: 10px 20px;
text-align: center;
width: auto;
@media (max-width: 768px) {
padding: 8px 16px;
}
`;
const AttachFileInput = styled.input`
display: none;
`;
const SubmitBtn = styled.input`
color: white;
background-color: #B63249;
border: none;
cursor: pointer;
&:disabled {
background-color: #666;
}
font-size: 1.5rem;
padding: 10px 20px;
margin-bottom: 10vh;
@media (max-width: 768px) {
font-size: 1.2rem;
padding: 8px 16px;
}
`;
const FilePreview = styled.div`
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 20px;
img {
width: 150px;
height: auto;
object-fit: cover;
cursor: pointer;
border-radius: 8px;
@media (max-width: 768px) {
width: 100px;
}
}
margin-bottom: 10vh;
`;
export default function Posting() {
const navigate = useNavigate();
const [isLoading, setLoading] = useState(false);
const [files, setFiles] = useState([]);
const [filePreviews, setFilePreviews] = useState([]);
const [text, setText] = useState("");
const [remainingChars, setRemainingChars] = useState(70);
const maxChars = 70;
const onFileChange = (e) => {
const { files } = e.target;
if (files) {
const fileArray = Array.from(files).slice(0, 4);
setFiles(fileArray);
const previewUrls = fileArray.map(file => URL.createObjectURL(file));
setFilePreviews(previewUrls);
}
};
const handleFileDelete = (index) => {
const updatedFiles = [...files];
const updatedFilePreviews = [...filePreviews];
updatedFiles.splice(index, 1);
updatedFilePreviews.splice(index, 1);
setFiles(updatedFiles);
setFilePreviews(updatedFilePreviews);
};
const handleChange = (value) => {
const plainText = value.replace(/<[^>]+>/g, ""); // HTML 태그 제거한 텍스트
setText(value);
setRemainingChars(maxChars - plainText.length); // 남은 글자 수 업데이트
};
const onSubmit = async (e) => {
e.preventDefault();
const user = auth.currentUser;
if (!user) {
alert("로그인이 필요합니다.");
return;
}
if (remainingChars < 0) {
alert(`글자 수가 ${maxChars}자를 초과했습니다. 다시 작성해 주세요.`);
return;
}
try {
setLoading(true);
const docRef = await addDoc(collection(db, "posts"), {
post: text,
createdAt: serverTimestamp(),
username: user.displayName || "익명",
userId: user.uid,
likes: 0,
});
const urls = await Promise.all(
files.map(async (file) => {
const locationRef = ref(storage, `posts/${docRef.id}/photos/${file.name}`);
await uploadBytes(locationRef, file);
return getDownloadURL(locationRef);
})
);
if (urls.length > 0) {
await updateDoc(docRef, { photos: urls });
}
setText("");
setFiles([]);
setFilePreviews([]);
navigate("/");
} catch (error) {
console.error("Error posting:", error);
alert("포스팅 중 문제가 발생했습니다. 다시 시도해 주세요.");
} finally {
setLoading(false);
}
};
return (
<Wrapper>
<Form onSubmit={onSubmit}>
<StyledQuill
maxLength={maxChars}
value={text}
onChange={handleChange}
placeholder="이야기를 나누어 보아요."
theme="snow"
/>
<p style={{ marginTop: '10vh', textAlign: 'right', color: remainingChars < 0 ? 'red' : 'black' }}>
{remainingChars < 0
? `글자수를 초과했습니다! (${remainingChars * -1}자 초과)`
: `남은 글자 수: ${remainingChars}`}
</p>
<AttachFileButton htmlFor="file">
{files.length > 0 ? "파일 첨부 완료" : "이미지 첨부"}
</AttachFileButton>
<AttachFileInput
onChange={onFileChange}
type="file"
id="file"
accept="image/*"
multiple
/>
<p>사진은 4장까지 첨부 가능합니다.</p>
<FilePreview>
{filePreviews.map((url, index) => (
<img key={index} src={url} alt={`preview-${index}`} onClick={() => handleFileDelete(index)} />
))}
</FilePreview>
<p>선택한 사진은 클릭하여 삭제할 수 있습니다.</p>
<SubmitBtn type="submit" value={isLoading ? "포스팅..." : "포스트"} disabled={isLoading || remainingChars < 0} />
</Form>
</Wrapper>
);
}
728x90
반응형
'웹개발 포트폴리오' 카테고리의 다른 글
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <App.css & index.css> (0) | 2024.12.20 |
---|---|
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <timeline.js> (0) | 2024.12.20 |
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <post.js> (0) | 2024.12.20 |
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <profile.js> (0) | 2024.12.20 |
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <loading-screen.js> (0) | 2024.12.20 |