From f1ffc6f71ccaa23d9be607415738fd43486415f1 Mon Sep 17 00:00:00 2001 From: sryung Date: Mon, 18 Dec 2023 13:08:49 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[=F0=9F=A5=81=20:=20feat]=20=ED=8F=AC?= =?UTF-8?q?=EC=8A=A4=ED=8C=85=20=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=20(#1?= =?UTF-8?q?,=20#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 본인이 작성한 포스팅에 노출되는 수정 버튼 클릭 시, 기존 데이터를 담은 수정 폼 팝업 노출 - 기존 데이터는 tweet 컴포넌트를 통해 props로 전달. 과정에서 ITweet의 타입 일부를 가져와 사용하기 위해 Pick 활용 - 별도의 이미지 수정 없는 경우 기존 이미지 유지. 교체 또는 삭제가 있는 경우 storage에서 기존 이미지를 삭제시켜 용량 최적화 --- src/assets/images/i-edit.svg | 3 + src/components/edit-tweet-form.tsx | 109 ++++++++++++++++++ src/components/post-tweet-form.tsx | 6 +- src/components/tweet.tsx | 25 +++- src/styles/popup.ts | 7 +- .../{post-tweet-form.ts => tweet-form.ts} | 5 + src/styles/tweet.ts | 1 + 7 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 src/assets/images/i-edit.svg create mode 100644 src/components/edit-tweet-form.tsx rename src/styles/{post-tweet-form.ts => tweet-form.ts} (96%) diff --git a/src/assets/images/i-edit.svg b/src/assets/images/i-edit.svg new file mode 100644 index 0000000..dc4d356 --- /dev/null +++ b/src/assets/images/i-edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/edit-tweet-form.tsx b/src/components/edit-tweet-form.tsx new file mode 100644 index 0000000..097acf1 --- /dev/null +++ b/src/components/edit-tweet-form.tsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; +import { deleteField, doc, updateDoc } from 'firebase/firestore'; +import { + deleteObject, + getDownloadURL, + ref, + uploadBytes, +} from 'firebase/storage'; +import { auth, db, storage } from '../firebase.ts'; +import ITweet from '../interfaces/ITweet.ts'; +import * as S from '../styles/tweet-form.ts'; +import { ReactComponent as IconPhoto } from '../assets/images/i-photo.svg'; + +interface IEditTweetForm extends Pick { + onClose: () => void; +} + +export default function EditTweetForm({ + id, + tweet: initialTweet, + photo: initialPhoto, + onClose, +}: IEditTweetForm) { + const [isLoading, setLoading] = useState(false); + const [tweet, setTweet] = useState(initialTweet); + const [image, setImage] = useState(null); + const [imagePreview, setImagePreview] = useState(initialPhoto); + + const onChange = (e: React.ChangeEvent) => { + setTweet(e.target.value); + }; + const onImageChange = (e: React.ChangeEvent) => { + const images = e.target.files; + if (images && images.length === 1) { + setImage(images[0]); + const previewUrl = URL.createObjectURL(images[0]); + setImagePreview(previewUrl); + } + }; + const onImageDelete = () => { + setImage(null); + setImagePreview(''); + }; + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const user = auth.currentUser; + if (!user || isLoading || tweet === '' || tweet.length > 180) return; + try { + setLoading(true); + const tweetDocRef = doc(db, 'tweets', id); + await updateDoc(tweetDocRef, { + tweet, + }); + if (image) { + const locationRef = ref( + storage, + initialPhoto || `tweets/${user.uid}/${id}`, + ); + const result = await uploadBytes(locationRef, image); + const url = await getDownloadURL(result.ref); + await updateDoc(tweetDocRef, { + photo: url, + }); + } else if (!image && initialPhoto && initialPhoto !== imagePreview) { + const locationRef = ref(storage, initialPhoto); + await deleteObject(locationRef); + await updateDoc(tweetDocRef, { + photo: deleteField(), + }); + } + setImage(null); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + onClose(); + } + }; + return ( + + + {imagePreview ? ( + <> + + + + ) : ( + + + + )} + + + {isLoading ? '수정중...' : '수정'} + + + ); +} diff --git a/src/components/post-tweet-form.tsx b/src/components/post-tweet-form.tsx index f8ce76e..d928de4 100644 --- a/src/components/post-tweet-form.tsx +++ b/src/components/post-tweet-form.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { addDoc, collection, updateDoc } from 'firebase/firestore'; import { getDownloadURL, ref, uploadBytes } from 'firebase/storage'; import { auth, db, storage } from '../firebase.ts'; -import * as S from '../styles/post-tweet-form.ts'; +import * as S from '../styles/tweet-form.ts'; import { ReactComponent as IconPhoto } from '../assets/images/i-photo.svg'; export default function PostTweetForm() { @@ -56,7 +56,7 @@ export default function PostTweetForm() { } }; return ( - + {isLoading ? '포스팅 중...' : '포스팅'} - + ); } diff --git a/src/components/tweet.tsx b/src/components/tweet.tsx index d6117d3..9da3043 100644 --- a/src/components/tweet.tsx +++ b/src/components/tweet.tsx @@ -4,9 +4,11 @@ import { deleteObject, ref } from 'firebase/storage'; import { auth, db, storage } from '../firebase.ts'; import ITweet from '../interfaces/ITweet.ts'; import FormattedDate from '../utils/formattedDate.tsx'; +import EditTweetForm from './edit-tweet-form.tsx'; import * as S from '../styles/tweet.ts'; import * as P from '../styles/popup.ts'; import { ReactComponent as IconUser } from '../assets/images/i-user.svg'; +import { ReactComponent as IconEdit } from '../assets/images/i-edit.svg'; export default function Tweet({ id, @@ -23,6 +25,10 @@ export default function Tweet({ const userData = userDoc.data(); setUserAvatar(userData?.userAvatar || null); }; + const [editPopup, setEditPopup] = useState(false); + const toggleEditPopup = () => { + setEditPopup(!editPopup); + }; const [deletePopup, setDeletePopup] = useState(false); const toggleDeletePopup = () => { setDeletePopup(!deletePopup); @@ -43,7 +49,7 @@ export default function Tweet({ }; useEffect(() => { fetchUserAvatar(); - }, [userId, userAvatar]); + }, [userName, userAvatar]); return ( @@ -57,11 +63,28 @@ export default function Tweet({ {photo ? : null} {user?.uid === userId ? ( <> + + 포스팅 수정하기 + + 포스팅 삭제하기 ) : null} + {editPopup ? ( + + + + setEditPopup(false)} + /> + + + ) : null} {deletePopup ? ( diff --git a/src/styles/popup.ts b/src/styles/popup.ts index d663660..6402733 100644 --- a/src/styles/popup.ts +++ b/src/styles/popup.ts @@ -27,15 +27,14 @@ export const PopupBox = styled.div` align-items: center; justify-content: center; width: calc(100vw - 100px); - padding: 30px; + padding: 50px 30px 30px 30px; background-color: ${whiteColor}; border: 3px solid ${blackColor}; `; export const Popup = styled(PopupBox)` - max-width: 757px; - height: calc(100vh - 100px); - max-height: 728px; + max-width: 600px; + max-height: calc(100vh - 100px); border-radius: 10px; `; diff --git a/src/styles/post-tweet-form.ts b/src/styles/tweet-form.ts similarity index 96% rename from src/styles/post-tweet-form.ts rename to src/styles/tweet-form.ts index 2fa8165..53a1592 100644 --- a/src/styles/post-tweet-form.ts +++ b/src/styles/tweet-form.ts @@ -9,6 +9,9 @@ export const Form = styled.form` gap: 10px; width: 100%; padding-bottom: 20px; +`; + +export const PostForm = styled(Form)` &::after { content: ''; position: absolute; @@ -21,6 +24,8 @@ export const Form = styled.form` } `; +export const EditForm = styled(Form)``; + export const TextArea = styled.textarea` width: calc(100% - 120px); padding: 20px; diff --git a/src/styles/tweet.ts b/src/styles/tweet.ts index 98f4a64..11175aa 100644 --- a/src/styles/tweet.ts +++ b/src/styles/tweet.ts @@ -110,5 +110,6 @@ export const EditButton = styled.button` width: 16px; height: 16px; stroke: ${primaryColor}; + stroke-width: 2; } `; From d6d6e72dc472cba8acaa5cb708a2940fd9e430e4 Mon Sep 17 00:00:00 2001 From: sryung Date: Mon, 18 Dec 2023 15:39:52 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[=F0=9F=8E=80=20:=20style]=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=EC=8A=A4=ED=94=BC=EB=84=88=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EB=A7=81=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 +- src/assets/images/loading-spinner.svg | 64 +++++++++++++++++++++++++ src/components/loading-screen.tsx | 9 ---- src/components/loading-spinner.tsx | 10 ++++ src/styles/components/loading-screen.ts | 12 ----- src/styles/loading-spinner.ts | 22 +++++++++ 6 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 src/assets/images/loading-spinner.svg delete mode 100644 src/components/loading-screen.tsx create mode 100644 src/components/loading-spinner.tsx delete mode 100644 src/styles/components/loading-screen.ts create mode 100644 src/styles/loading-spinner.ts diff --git a/src/App.tsx b/src/App.tsx index 8bc3121..2155651 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import Profile from './routes/profile.tsx'; import SearchResult from './routes/search-result.tsx'; import Auth from './routes/auth.tsx'; import Layout from './components/layout.tsx'; -import LoadingScreen from './components/loading-screen.tsx'; +import LoadingSpinner from './components/loading-spinner.tsx'; import * as S from './styles/global.ts'; const router = createBrowserRouter([ @@ -51,7 +51,7 @@ function App() { return ( <> - {isLoading ? : } + {isLoading ? : } ); } diff --git a/src/assets/images/loading-spinner.svg b/src/assets/images/loading-spinner.svg new file mode 100644 index 0000000..d55d18e --- /dev/null +++ b/src/assets/images/loading-spinner.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/loading-screen.tsx b/src/components/loading-screen.tsx deleted file mode 100644 index be4175e..0000000 --- a/src/components/loading-screen.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as S from '../styles/components/loading-screen.ts'; - -export default function LoadingScreen() { - return ( - - Loading... - - ); -} diff --git a/src/components/loading-spinner.tsx b/src/components/loading-spinner.tsx new file mode 100644 index 0000000..1ed6dd3 --- /dev/null +++ b/src/components/loading-spinner.tsx @@ -0,0 +1,10 @@ +import Wrapper from '../styles/loading-spinner.ts'; +import { ReactComponent as Spinner } from '../assets/images/loading-spinner.svg'; + +export default function LoadingSpinner() { + return ( + + + + ); +} diff --git a/src/styles/components/loading-screen.ts b/src/styles/components/loading-screen.ts deleted file mode 100644 index bd6848b..0000000 --- a/src/styles/components/loading-screen.ts +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components'; - -export const Wrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - height: 100vh; -`; - -export const Text = styled.span` - font-size: 24px; -`; diff --git a/src/styles/loading-spinner.ts b/src/styles/loading-spinner.ts new file mode 100644 index 0000000..c246a75 --- /dev/null +++ b/src/styles/loading-spinner.ts @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + z-index: 100; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + justify-content: center; + align-items: center; + svg { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } +`; + +export default Wrapper; From 96eb42c957e8010be6ed1277394e9d6f3caa4c4b Mon Sep 17 00:00:00 2001 From: sryung Date: Mon, 18 Dec 2023 15:50:49 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[=F0=9F=8E=80=20:=20style]=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=EC=8A=A4=ED=94=BC=EB=84=88=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EB=A7=81=20-=20=EB=B2=84=ED=8A=BC=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/loading-spinner-mini.svg | 40 ++++++++++++++++++++++ src/components/edit-tweet-form.tsx | 3 +- src/components/post-tweet-form.tsx | 3 +- src/components/signIn.tsx | 3 +- src/components/signUp.tsx | 3 +- src/styles/button.ts | 7 +++- src/styles/tweet-form.ts | 6 +++- 7 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 src/assets/images/loading-spinner-mini.svg diff --git a/src/assets/images/loading-spinner-mini.svg b/src/assets/images/loading-spinner-mini.svg new file mode 100644 index 0000000..976b5bd --- /dev/null +++ b/src/assets/images/loading-spinner-mini.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/edit-tweet-form.tsx b/src/components/edit-tweet-form.tsx index 097acf1..8f1103c 100644 --- a/src/components/edit-tweet-form.tsx +++ b/src/components/edit-tweet-form.tsx @@ -10,6 +10,7 @@ import { auth, db, storage } from '../firebase.ts'; import ITweet from '../interfaces/ITweet.ts'; import * as S from '../styles/tweet-form.ts'; import { ReactComponent as IconPhoto } from '../assets/images/i-photo.svg'; +import { ReactComponent as LoadingSpinner } from '../assets/images/loading-spinner-mini.svg'; interface IEditTweetForm extends Pick { onClose: () => void; @@ -102,7 +103,7 @@ export default function EditTweetForm({ accept="image/*" /> - {isLoading ? '수정중...' : '수정'} + {isLoading ? : '수정'} ); diff --git a/src/components/post-tweet-form.tsx b/src/components/post-tweet-form.tsx index d928de4..dd905b9 100644 --- a/src/components/post-tweet-form.tsx +++ b/src/components/post-tweet-form.tsx @@ -4,6 +4,7 @@ import { getDownloadURL, ref, uploadBytes } from 'firebase/storage'; import { auth, db, storage } from '../firebase.ts'; import * as S from '../styles/tweet-form.ts'; import { ReactComponent as IconPhoto } from '../assets/images/i-photo.svg'; +import { ReactComponent as LoadingSpinner } from '../assets/images/loading-spinner-mini.svg'; export default function PostTweetForm() { const [isLoading, setLoading] = useState(false); @@ -81,7 +82,7 @@ export default function PostTweetForm() { accept="image/*" /> - {isLoading ? '포스팅 중...' : '포스팅'} + {isLoading ? : '포스팅'} ); diff --git a/src/components/signIn.tsx b/src/components/signIn.tsx index 3856e39..39c4242 100644 --- a/src/components/signIn.tsx +++ b/src/components/signIn.tsx @@ -6,6 +6,7 @@ import { auth } from '../firebase.ts'; import * as S from '../styles/auth.ts'; import * as P from '../styles/popup.ts'; import ImageComputer from '../assets/images/logo-small.png'; +import { ReactComponent as LoadingSpinner } from '../assets/images/loading-spinner-mini.svg'; interface ISignInProps { onClose: () => void; @@ -90,7 +91,7 @@ export default function SignIn({ onClose }: ISignInProps) { required /> - {isLoading ? '로딩...' : '로그인하기'} + {isLoading ? : '로그인하기'} {firebaseError !== '' ? {firebaseError} : null} diff --git a/src/components/signUp.tsx b/src/components/signUp.tsx index 4007a09..0cdd9b1 100644 --- a/src/components/signUp.tsx +++ b/src/components/signUp.tsx @@ -7,6 +7,7 @@ import { auth, db } from '../firebase.ts'; import * as S from '../styles/auth.ts'; import * as P from '../styles/popup.ts'; import ImageComputer from '../assets/images/logo-small.png'; +import { ReactComponent as LoadingSpinner } from '../assets/images/loading-spinner-mini.svg'; interface ISignUpProps { onClose: () => void; @@ -116,7 +117,7 @@ export default function SignUp({ onClose }: ISignUpProps) { required /> - {isLoading ? '로딩...' : '가입하기'} + {isLoading ? : '가입하기'} {firebaseError !== '' ? {firebaseError} : null} diff --git a/src/styles/button.ts b/src/styles/button.ts index d52540c..5aee82a 100644 --- a/src/styles/button.ts +++ b/src/styles/button.ts @@ -4,9 +4,14 @@ import { blackColor, grayColor, primaryColor, whiteColor } from './global.ts'; const Button = styled.button` width: 100%; margin: 8px 0; - padding: 10px 20px; + padding: 0 20px; border-radius: 50px; font-size: 16px; + line-height: 36px; + svg { + width: 36px; + height: 36px; + } `; export const LineButton = styled(Button)` diff --git a/src/styles/tweet-form.ts b/src/styles/tweet-form.ts index 53a1592..a82b072 100644 --- a/src/styles/tweet-form.ts +++ b/src/styles/tweet-form.ts @@ -113,10 +113,14 @@ export const AttachImageInput = styled.input` `; export const SubmitButton = styled.button` - padding: 10px 0px; background-color: ${primaryColor}; border: none; border-radius: 20px; color: white; font-size: 16px; + line-height: 36px; + svg { + width: 36px; + height: 36px; + } `; From 8bf37ba26693264f495276257b7836d0e4209752 Mon Sep 17 00:00:00 2001 From: sryung Date: Mon, 18 Dec 2023 16:15:07 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[=F0=9F=92=8E=20:=20refactor]=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=BC=80=EB=B0=A5=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컴포넌트 이름은 파스칼 케이스로 짓되, 파일 이름은 케밥 케이스로 통일화 진행 --- src/components/{signIn.tsx => sign-in.tsx} | 0 src/components/{signUp.tsx => sign-up.tsx} | 0 src/components/{socialSignIn.tsx => social-sign-in.tsx} | 0 src/components/tweet-form.tsx | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/components/{signIn.tsx => sign-in.tsx} (100%) rename src/components/{signUp.tsx => sign-up.tsx} (100%) rename src/components/{socialSignIn.tsx => social-sign-in.tsx} (100%) create mode 100644 src/components/tweet-form.tsx diff --git a/src/components/signIn.tsx b/src/components/sign-in.tsx similarity index 100% rename from src/components/signIn.tsx rename to src/components/sign-in.tsx diff --git a/src/components/signUp.tsx b/src/components/sign-up.tsx similarity index 100% rename from src/components/signUp.tsx rename to src/components/sign-up.tsx diff --git a/src/components/socialSignIn.tsx b/src/components/social-sign-in.tsx similarity index 100% rename from src/components/socialSignIn.tsx rename to src/components/social-sign-in.tsx diff --git a/src/components/tweet-form.tsx b/src/components/tweet-form.tsx new file mode 100644 index 0000000..e69de29