diff --git a/firebase-practice/.gitignore b/firebase-practice/.gitignore index d2205b7..5e62f0c 100644 --- a/firebase-practice/.gitignore +++ b/firebase-practice/.gitignore @@ -36,3 +36,6 @@ yarn-error.log* # test page test.tsx + +# env +.env \ No newline at end of file diff --git a/firebase-practice/pages/_app.tsx b/firebase-practice/pages/_app.tsx index a81d718..82e4996 100644 --- a/firebase-practice/pages/_app.tsx +++ b/firebase-practice/pages/_app.tsx @@ -6,8 +6,8 @@ import { onAuthStateChanged } from "firebase/auth" import { authService, DBService } from "@FireBase" import { RecoilRoot, useSetRecoilState } from "recoil" import { darkModeState, userDataState } from "@share/recoil/recoilList" -import { UserData } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" +import { FeedItem, UserData } from "backend/dto" +import axios from "axios" const SetDarkMode = () => { const setDarkRecoil = useSetRecoilState(darkModeState) @@ -20,11 +20,18 @@ const SetDarkMode = () => { const SetCurrnentUser = () => { const setCurrentUser = useSetRecoilState(userDataState) useEffect(() => { - onAuthStateChanged(authService, (user) => { + onAuthStateChanged(authService, async (user) => { if (user) { - onSnapshot(doc(DBService, "users", `${user.uid}`), (response) => { - setCurrentUser(response.data() as UserData) - }) + const userId = user.uid + + const userInfo = await axios(`/api/profile?userId=${userId}`).then< + Omit + >((res) => res.data) + const userFeeds = await axios(`/api/userFeed?userId=${userId}`).then< + FeedItem[] + >((res) => res.data) + + setCurrentUser({ ...userInfo, feed: userFeeds }) } }) }, []) diff --git a/firebase-practice/pages/api/comment.ts b/firebase-practice/pages/api/comment.ts index fa898f9..6b7ff66 100644 --- a/firebase-practice/pages/api/comment.ts +++ b/firebase-practice/pages/api/comment.ts @@ -25,7 +25,6 @@ import { v4 } from "uuid" * update : commentId, oldComment, uploadTime 모두 포함한 데이터 전송 * new comment : userId, comment, feedId 만을 포함한 데이터 전송 * - * * method : DELETE * request url : /api/comment * request body : {commentId, comment, uploadTime, userId, feedId} @@ -38,7 +37,6 @@ export default async function getComment( ) { if (req.method === "GET") { const commentId = req.query?.commentId - console.log(commentId) const getCommentRef = doc(DBService, "Comments", `${commentId}`) const docSnapShot = await getDoc(getCommentRef) @@ -46,7 +44,7 @@ export default async function getComment( const data = docSnapShot.data()?.AllComments res.status(200).json(data as Comment[]) } else { - res.status(500).json("Fail") + res.status(200).json([]) } } if (req.method === "POST") { diff --git a/firebase-practice/pages/api/feed.ts b/firebase-practice/pages/api/feed.ts index 042b53c..9c39c0f 100644 --- a/firebase-practice/pages/api/feed.ts +++ b/firebase-practice/pages/api/feed.ts @@ -1,8 +1,9 @@ import { DBService } from "@FireBase" -import { FeedData } from "backend/dto" +import { FeedItem, UserInfo } from "backend/dto" import { arrayRemove, arrayUnion, + deleteDoc, doc, getDoc, setDoc, @@ -16,12 +17,12 @@ import type { NextApiRequest, NextApiResponse } from "next" * request url : /api/feed * response : 메인 페이지의 피드 정보들 * - * TODO: 새로운 피드 등록과 피드 수정 기능 구분하기 * method : POST * request url : /api/feed * request body for new feed : {imageUrl, desc, location, isPrivate, creator} * response : Success * + * uploadTime이 new feed 인 경우 * method : POST * request url : /api/feed * request body for new feed : {imageUrl, desc, location, isPrivate, storageId, creator, uploadTime, newDesc, newLocation, newIsPrivate} @@ -35,19 +36,39 @@ import type { NextApiRequest, NextApiResponse } from "next" export default async function getFeed( req: NextApiRequest, - res: NextApiResponse, + res: NextApiResponse, ) { if (req.method === "GET") { const getFeedRef = doc(DBService, "mainPage", "userFeedDataAll") const docSnapShot = await getDoc(getFeedRef) if (docSnapShot.exists()) { - const data = docSnapShot.data()?.feed as FeedData[] + const data = docSnapShot.data()?.feed as FeedItem[] + + const feedItems = await Promise.all( + data.map(async (feedItem) => { + const userId = feedItem.creator + const getUserInfoRef = doc(DBService, "users", `${userId}`) + const userInfoDocSnapShot = await getDoc(getUserInfoRef) + + const userInfo = userInfoDocSnapShot.data()?.info as UserInfo + + return { + ...feedItem, + creator: { + ...userInfo, + }, + } + }), + ) + res .status(200) - .json(data.sort((a, b) => Number(a.uploadTime) - Number(b.uploadTime))) + .json( + feedItems.sort((a, b) => Number(b.uploadTime) - Number(a.uploadTime)), + ) } else { - res.status(500).json("Fail") + res.status(404).json("not found") } } else if (req.method === "POST") { const { @@ -58,89 +79,66 @@ export default async function getFeed( storageId, creator, uploadTime, - newDesc, - newLocation, - newIsPrivate, } = req.body - const firestoreAllRef = doc(DBService, "mainPage", `userFeedDataAll`) - const firestorePersonalRef = doc(DBService, "users", `${creator}`) - - if (!uploadTime) { - const feed: FeedData = { - imageUrl: imageUrl, - desc: desc, - location: location, - isPrivate: isPrivate, - storageId: storageId, - creator: creator, + const firestoreRef = doc(DBService, "mainPage", `userFeedDataAll`) + + const isNewFeed = uploadTime === "new feed" + + if (isNewFeed) { + const feed: FeedItem = { + imageUrl, + desc, + location, + isPrivate, + storageId, + creator, uploadTime: getCurrentTime(), } - await updateDoc(firestoreAllRef, { - feed: arrayUnion(feed), - }).catch(async (error) => { - if (error.code === "not-found") { - await setDoc(firestoreAllRef, { - feed: [feed], - }) - } else { - res.status(500).json(error.code) - } - }) - - await updateDoc(firestorePersonalRef, { + await updateDoc(firestoreRef, { feed: arrayUnion(feed), }).catch(async (error) => { if (error.code === "not-found") { - await setDoc(firestorePersonalRef, { + setDoc(firestoreRef, { feed: [feed], }) } else { res.status(500).json(error.code) } }) - - res.status(200).json("Success") - } else { - const feedToRemove: FeedData = { - imageUrl: imageUrl, - desc: desc, - location: location, - isPrivate: isPrivate, - storageId: storageId, - creator: creator, - uploadTime: uploadTime, + } + if (!isNewFeed) { + const { newDesc, newLocation, newIsPrivate } = req.body + + const feedToRemove: FeedItem = { + imageUrl, + desc, + location, + isPrivate, + storageId, + creator, + uploadTime, } - const newFeed: FeedData = { - imageUrl: imageUrl, + const newFeed: FeedItem = { + imageUrl, desc: newDesc, location: newLocation, isPrivate: newIsPrivate, - storageId: storageId, - creator: creator, - uploadTime: uploadTime, + storageId, + creator, + uploadTime, } - await updateDoc(firestoreAllRef, { - feed: arrayRemove(feedToRemove), - }).catch((error) => res.status(500).json(error.code)) - - await updateDoc(firestorePersonalRef, { + updateDoc(firestoreRef, { feed: arrayRemove(feedToRemove), - }).catch((error) => { - res.status(500).json(error.code) }) - - await updateDoc(firestoreAllRef, { - feed: arrayUnion(newFeed), - }).catch((error) => res.status(500).json(error.code)) - - await updateDoc(firestorePersonalRef, { - feed: arrayUnion(newFeed), - }).catch((error) => res.status(500).json(error.code)) - - res.status(200).json("Success") + .then(() => { + updateDoc(firestoreRef, { + feed: arrayUnion(newFeed), + }).catch((error) => res.status(500).json(error.code)) + }) + .catch((error) => res.status(500).json(error.code)) } } else if (req.method === "DELETE") { const { @@ -151,33 +149,37 @@ export default async function getFeed( storageId, creator, uploadTime, - } = req.body as FeedData - - const firestoreAllRef = doc(DBService, "mainPage", `userFeedDataAll`) - const firestorePersonalRef = doc(DBService, "users", `${creator}`) - - const feed: FeedData = { - imageUrl: imageUrl, - desc: desc, - location: location, - isPrivate: isPrivate, - storageId: storageId, - creator: creator, - uploadTime: uploadTime, + userId, + } = req.body + + const feedRef = doc(DBService, "mainPage", `userFeedDataAll`) + const userRef = doc(DBService, "users", `${userId}`) + const commentRef = doc(DBService, "Comments", `${storageId}`) + + const feed: Omit & { creator: string } = { + creator, + desc, + imageUrl, + isPrivate: isPrivate === "true", + location, + storageId, + uploadTime, } - await updateDoc(firestoreAllRef, { + updateDoc(feedRef, { feed: arrayRemove(feed), }).catch((error) => { res.status(500).json(error.code) }) - await updateDoc(firestorePersonalRef, { - feed: arrayRemove(feed), + updateDoc(userRef, { + likeFeedIds: arrayRemove(storageId), }).catch((error) => { res.status(500).json(error.code) }) - res.status(200).json("Success") + deleteDoc(commentRef).catch((error) => { + res.status(500).json(error.code) + }) } } diff --git a/firebase-practice/pages/api/like.ts b/firebase-practice/pages/api/like.ts index 3d168b2..4f1d79d 100644 --- a/firebase-practice/pages/api/like.ts +++ b/firebase-practice/pages/api/like.ts @@ -1,9 +1,12 @@ import { DBService } from "@FireBase" +import { UserData } from "backend/dto" import { arrayRemove, arrayUnion, + collection, doc, getDoc, + getDocs, setDoc, updateDoc, } from "firebase/firestore" @@ -11,8 +14,12 @@ import { NextApiRequest, NextApiResponse } from "next" /** * method : GET - * request url : /api/like?storageId=${이미지 저장 스토리지 아이디} - * response : storageId를 가진 피드를 좋아요 한 사람들 리스트 + * request url : /api/like?userId=${userId} + * response : userId를 가진 유저가 좋아요 누른 피드 아이디 리스트 + * + * method : GET + * request url : /api/like?storageId=${storageId} + * response : storageId를 가진 피드를 좋아요 한 유저들의 아이디 리스트 * * method : PUT * requset url : /api/like @@ -30,40 +37,59 @@ export default async function getLike( res: NextApiResponse, ) { if (req.method === "GET") { - const storageId = req.query?.storageId - console.log(storageId) - const getLikeRef = doc(DBService, "like", `${storageId}`) - const docSnapShot = await getDoc(getLikeRef) + const { storageId, userId } = req.query + + if (userId !== undefined) { + const getLikeFeedIdsRef = doc(DBService, "users", `${userId}`) + const docSnapShot = await getDoc(getLikeFeedIdsRef) + + if (docSnapShot.exists()) { + const likeFeedIds = docSnapShot.data().likeFeedIds + + res.status(200).json(likeFeedIds as string[]) + return + } + res.status(404).json("not-found") + return + } + if (storageId !== undefined && typeof storageId === "string") { + const querySnapShot = await getDocs(collection(DBService, "users")) + const userIds: string[] = [] + + querySnapShot.forEach((doc) => { + const user = doc.data() as Omit + + if ((user.likeFeedIds ?? []).includes(storageId)) + userIds.push(user.info.userId) + }) - if (docSnapShot.exists()) { - const data = docSnapShot.data()?.likerList - res.status(200).json(data as string[]) + res.status(200).json(userIds) } } else if (req.method === "PUT") { const { storageId, userId } = req.body - const likeRef = doc(DBService, "like", storageId) + const userLikeFeedIdsRef = doc(DBService, "users", userId) - await updateDoc(likeRef, { - likerList: arrayUnion(userId), + updateDoc(userLikeFeedIdsRef, { + likeFeedIds: arrayUnion(storageId), }) - .catch(async (error) => { - if (error.code === "not-found") { - await setDoc(likeRef, { - likerList: [userId], - }).catch((error) => res.status(500).json(error.code)) - } else { - res.status(500).json(error.code) - } - }) .then(() => { - res.status(200).json("Success") + res.status(200).json("success") + }) + .catch((error) => { + if (error === "not-found") { + setDoc(userLikeFeedIdsRef, { + likeFeedIds: [storageId], + }) + return + } + res.status(500).json(error.code) }) } else if (req.method === "DELETE") { - const { storageId, userId } = req.body - const likeRef = doc(DBService, "like", storageId) + const { storageId, userId } = req.query + const likeFeedIdsRef = doc(DBService, "users", `${userId}`) - await updateDoc(likeRef, { - likerList: arrayRemove(userId), + await updateDoc(likeFeedIdsRef, { + likeFeedIds: arrayRemove(storageId), }) .catch((error) => { res.status(500).json(error.code) diff --git a/firebase-practice/pages/api/profile.ts b/firebase-practice/pages/api/profile.ts index a86ae29..d04cf45 100644 --- a/firebase-practice/pages/api/profile.ts +++ b/firebase-practice/pages/api/profile.ts @@ -20,7 +20,7 @@ export default async function profile( ) { if (req.method === "GET") { const userId = req.query?.userId - console.log(userId) + const getProfileRef = doc(DBService, "users", `${userId}`) const docSnapShot = await getDoc(getProfileRef) @@ -35,10 +35,10 @@ export default async function profile( const setProfileRef = doc(DBService, "users", `${userId}`) const profileForm: UserInfo = { - userId: userId, - profileImage: profileImage, + userId, + profileImage, name: userName, - email: email, + email, } await updateDoc(setProfileRef, { diff --git a/firebase-practice/pages/api/requireAPI.md b/firebase-practice/pages/api/requireAPI.md index fb1ec6a..eb821bc 100644 --- a/firebase-practice/pages/api/requireAPI.md +++ b/firebase-practice/pages/api/requireAPI.md @@ -11,6 +11,7 @@ 7. 코멘트 등록/삭제하기 (o) 8. 좋아요 등록/취소하기 (o) 9. 피드 등록/수정/삭제하기 (등록: o, 삭제: o, 수정: o) +10. DM 정보 불러오기, 등록하기
@@ -23,4 +24,4 @@ ## 목표 nextjs의 api기능을 통해 firebase와 상호작용하고, tsx파일에서 직접적으로 요청을 넣는 방식은 지양하자. -새로운 방식 도입 후 기능이 정상적으로 수행된다면 useSWR을 사용하여 최대한 기능 최적화해보자. +낙관적 업데이트를 적용해 사용성을 높여보자. diff --git a/firebase-practice/pages/api/userFeed.ts b/firebase-practice/pages/api/userFeed.ts new file mode 100644 index 0000000..8cbf045 --- /dev/null +++ b/firebase-practice/pages/api/userFeed.ts @@ -0,0 +1,48 @@ +import { DBService } from "@FireBase" +import axios from "axios" +import { FeedItem } from "backend/dto" +import { doc, getDoc } from "firebase/firestore" +import type { NextApiRequest, NextApiResponse } from "next" + +/** + * method : GET + * request url : /api/userFeed?userId={userId} + * response : userId를 가진 유저의 피드 목록 + */ + +export default async function getFeed( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === "GET") { + const userId = req.query.userId + + const getFeedRef = doc(DBService, "mainPage", "userFeedDataAll") + const getProfileRef = doc(DBService, "users", `${userId}`) + + const docSnapShot = await getDoc(getFeedRef) + const userInfoDocSnapShot = await getDoc(getProfileRef) + + if (userId === undefined) res.status(400).json("userId query is missing") + + if (docSnapShot.exists()) { + const data = docSnapShot.data()?.feed as (Omit & { + creator: string + })[] + const userData = data + .filter((feedItem) => feedItem.creator === userId) + .map((feedItem) => ({ + ...feedItem, + creator: userInfoDocSnapShot.data()?.info, + })) + + userData.sort((a, b) => Number(b.uploadTime) - Number(a.uploadTime)) + + res.status(200).json(userData) + } else { + res.status(500).json("Fail") + } + + res.status(400).json("method error") + } +} diff --git a/firebase-practice/pages/auth.tsx b/firebase-practice/pages/auth.tsx index f77bce7..083f9f5 100644 --- a/firebase-practice/pages/auth.tsx +++ b/firebase-practice/pages/auth.tsx @@ -21,6 +21,7 @@ import { UserInfo } from "backend/dto" import { GitHubIcon, GoogleIcon } from "icons" import { useRecoilValue } from "recoil" import { darkModeState } from "@share/recoil/recoilList" +import { useAuth } from "lib/hooks/useAuth" const Style = { Wrapper: styled.div` @@ -107,159 +108,24 @@ const Style = { export default function Auth() { const router = useRouter() - const [Email, setEmail] = useState("") - const [Password, setPassword] = useState("") - const [confirmPassword, setConfirmPassword] = useState("") - const [isNewAccount, setIsNewAccount] = useState(false) - const [name, setName] = useState("") - - const [isLogin, setIsLogin] = useState(false) - const githubProvider = new GithubAuthProvider() - const googleProvider = new GoogleAuthProvider() - const isDarkMode = useRecoilValue(darkModeState) - const handleOnInputChange: React.ChangeEventHandler = ( - event, - ) => { - if (event.target.name === "Email") { - if (event.target.value === "서경") alert("사랑해") - setEmail(event.target.value) - return - } - setPassword(event.target.value) - } - const handleNameOnChange: React.ChangeEventHandler = ( - event, - ) => { - setName(event.target.value) - } - const handleOnSubmit: React.FormEventHandler = async ( - event, - ) => { - event.preventDefault() - if (Email.length === 0) return - if (Password.length < 6) return - if (isNewAccount === true) { - await createUserWithEmailAndPassword(authService, Email, Password) - .then((response) => { - if (response && authService.currentUser !== null) { - updateProfile(authService.currentUser, { - displayName: name, - }) - signOut(authService) - setIsNewAccount(false) - alert( - "회원가입에 성공 하셨습니다! 회원가입하신 정보로 로그인 바랍니다.", - ) - setEmail("") - setPassword("") - } - }) - .catch((error) => { - if (error.code === "auth/weak-password") { - alert("비밀번호는 최소 6자리 이상이어야 합니다.") - setConfirmPassword("") - setPassword("") - } else if (error.code === "auth/email-already-in-use") { - alert("이미 사용중인 이메일 입니다.") - setPassword("") - setConfirmPassword("") - setEmail("") - } else if (error.code === "auth/invalid-email") { - alert("올바른 이메일 형식을 입력해주세요") - setEmail("") - } - }) - return - } - setPersistence(authService, browserSessionPersistence) - .then(async () => { - return signInWithEmailAndPassword(authService, Email, Password) - .then(async (response) => { - if (response) { - setIsLogin(true) - CreateNewUserToFirestore(response) - } - }) - .catch((error) => { - if (error.code === "auth/wrong-password") { - alert("비밀번호가 잘못되었습니다") - setPassword("") - return - } - if (error.code === "auth/invalid-email") { - alert("이메일 똑바로 쓰세요") - } else if (error.code === "auth/user-not-found") { - alert("등록되지 않은 사용자 입니다") - } - setEmail("") - setPassword("") - }) - }) - .catch((error) => console.log(error.code)) - } - - const handleGoogleAuth = async () => { - setIsLogin(true) - await signInWithPopup(authService, googleProvider) - .then(async (response) => { - if (response) { - await CreateNewUserToFirestore(response) - } - }) - .catch((error) => { - if (error.code === "auth/cancelled-popup-request") { - alert( - "로그인 진행중에 오류가 발생하였습니다. 팝업창을 닫지 않도록 주의하시기 바랍니다.", - ) - } - if (error.code === "auth/account-exists-with-different-credential") { - alert("동일한 이메일 주소로 이미 가입된 계정이 있습니다.") - } - router.push("/auth") - }) - } - - const handleGitHubAuth = async () => { - setIsLogin(true) - await signInWithPopup(authService, githubProvider) - .then(async (response) => { - if (response) { - await CreateNewUserToFirestore(response) - } - }) - .catch((error) => { - if (error.code === "auth/cancelled-popup-request") { - alert( - "로그인 진행중에 오류가 발생하였습니다. 팝업창을 닫지 않도록 주의하시기 바랍니다.", - ) - } - if (error.code === "auth/account-exists-with-different-credential") { - alert("동일한 이메일 주소로 이미 가입된 계정이 있습니다.") - } - router.push("/auth") - }) - } - - const CreateNewUserToFirestore = async (response: UserCredential) => { - const newUserToFirestoreRef = doc(DBService, "users", response.user.uid) - const UserDataForm: UserInfo = { - userId: response.user.uid, - profileImage: response.user.photoURL, - name: response.user.displayName, - email: response.user.email, - } - await updateDoc(newUserToFirestoreRef, { info: UserDataForm }).catch( - async (error) => { - if (error.code === "not-found") { - await setDoc(newUserToFirestoreRef, { info: UserDataForm }).catch( - (error) => console.log(error.code), - ) - } - }, - ) - } + const { + isLogin, + isNewAccount, + email, + name, + password, + confirmPassword, + setIsLogin, + setConfirmPassword, + setIsNewAccount, + handleOnInputChange, + handleGitHubAuth, + handleGoogleAuth, + handleOnSubmit, + handleNameOnChange, + } = useAuth() useEffect(() => { if (isLogin) router.push("/") @@ -292,7 +158,7 @@ export default function Auth() { required onChange={handleOnInputChange} name="Email" - value={Email} + value={email} autoComplete="off" style={ isDarkMode @@ -304,7 +170,7 @@ export default function Auth() { : {} } /> - {Email.search(/\@\w+\.com|\@\w+\.net/) === -1 && Email.length > 3 ? ( + {email.search(/\@\w+\.com|\@\w+\.net/) === -1 && email.length > 3 ? ( 올바르지 않은 이메일 형식입니다. @@ -318,11 +184,11 @@ export default function Auth() { required onChange={handleOnInputChange} name="Password" - value={Password} + value={password} style={ - Password !== confirmPassword && + password !== confirmPassword && confirmPassword !== "" && - Password !== "" + password !== "" ? { backgroundColor: "red" } : { backgroundColor: isDarkMode ? "#373e47" : "white", @@ -342,16 +208,16 @@ export default function Auth() { }} type="password" style={ - Password !== confirmPassword && + password !== confirmPassword && confirmPassword !== "" && - Password !== "" + password !== "" ? { backgroundColor: "red" } : { backgroundColor: "white" } } /> - {Password !== confirmPassword && + {password !== confirmPassword && confirmPassword !== "" && - Password !== "" ? ( + password !== "" ? ( 비밀번호가 일치하지 않습니다 @@ -375,26 +241,26 @@ export default function Auth() { value={isNewAccount ? "Create Account" : "Log in"} color={ isNewAccount - ? Email.length !== 0 && - Password.length >= 6 && - Password === confirmPassword && + ? email.length !== 0 && + password.length >= 6 && + password === confirmPassword && name !== "" ? "" : "fail" - : Email.length !== 0 && Password.length >= 6 + : email.length !== 0 && password.length >= 6 ? "" : "fail" } disabled={ isNewAccount ? !( - Email.length !== 0 && - Password.length >= 6 && - Password === confirmPassword && + email.length !== 0 && + password.length >= 6 && + password === confirmPassword && confirmPassword !== "" && name !== "" ) - : !(Email.length !== 0 && Password.length >= 6) + : !(email.length !== 0 && password.length >= 6) } /> @@ -412,7 +278,7 @@ export default function Auth() { height={40} onClick={() => { setPersistence(authService, browserSessionPersistence).then( - async () => { + () => { handleGoogleAuth() }, ) @@ -424,7 +290,7 @@ export default function Auth() { height={40} onClick={() => { setPersistence(authService, browserSessionPersistence).then( - async () => { + () => { handleGitHubAuth() }, ) diff --git a/firebase-practice/pages/dm.tsx b/firebase-practice/pages/dm.tsx index 107d5ad..e796041 100644 --- a/firebase-practice/pages/dm.tsx +++ b/firebase-practice/pages/dm.tsx @@ -1,6 +1,6 @@ import Layout from "components/layout" import PCDM from "@feature/dm/PC" -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" import MobileDM from "@feature/dm/Mobile" export default function Dm() { diff --git a/firebase-practice/pages/index.tsx b/firebase-practice/pages/index.tsx index 84e904a..cd4307c 100644 --- a/firebase-practice/pages/index.tsx +++ b/firebase-practice/pages/index.tsx @@ -1,11 +1,8 @@ import type { NextPage } from "next" -import { useEffect, useState } from "react" -import { DBService } from "@FireBase" -import { doc, DocumentData, onSnapshot } from "firebase/firestore" +import { Suspense, useState } from "react" import { FlexBox, Margin } from "ui" import FeedList from "@share/Feed/mainPage/FeedList" import Layout from "components/layout" -import { FeedData } from "backend/dto" import FollowListAtMainPage from "@feature/followListAtMainPage" import { useRecoilValue } from "recoil" import { @@ -13,15 +10,11 @@ import { userDataState, userListState, } from "@share/recoil/recoilList" -import { useRouter } from "next/router" import CommentModal from "@share/Modal/comment/CommentModal" import UserListModal from "@share/Modal/userList/UserListModal" import Loading from "@share/Loading/Loading" const Home: NextPage = () => { - const router = useRouter() - const [dataFromFirestore, setDataFromFirestore] = useState() - const [feedData, setFeedData] = useState() const currentUserData = useRecoilValue(userDataState) const [isCommentModalOpen, setIsCommentModalOpen] = useState(false) @@ -30,18 +23,6 @@ const Home: NextPage = () => { const [isLikeModalOpen, setIsLikeModalOpen] = useState(false) const likerListData = useRecoilValue(userListState) - useEffect(() => { - const AllFeedRef = doc(DBService, "mainPage", `userFeedDataAll`) - onSnapshot(AllFeedRef, { includeMetadataChanges: true }, (doc) => { - if (doc) setDataFromFirestore(doc.data()) - }) - }, []) - useEffect(() => { - if (dataFromFirestore !== undefined) { - setFeedData(dataFromFirestore.feed) - } - }, [dataFromFirestore]) - return ( { /> {currentUserData?.follow !== undefined && - currentUserData.follow.length > 0 ? ( - - - - ) : ( - - - - )} + currentUserData.follow.length > 0 && ( + + }> + + + + )} - {feedData !== undefined && currentUserData.info.userId !== "" ? ( + {currentUserData.info.userId !== "" ? ( !data.isPrivate) : undefined - } setIsCommentModalOpen={setIsCommentModalOpen} setIsLikeModalOpen={setIsLikeModalOpen} /> diff --git a/firebase-practice/pages/mypage.tsx b/firebase-practice/pages/mypage.tsx index 0d1c9e0..c3455cb 100644 --- a/firebase-practice/pages/mypage.tsx +++ b/firebase-practice/pages/mypage.tsx @@ -3,7 +3,7 @@ import FeedSortList from "@share/Feed/mypage/FeedSortList" import ProfileHeader from "@feature/profile/mypage" import styled from "styled-components" import Layout from "components/layout" -import { FeedData } from "backend/dto" +import { FeedItem } from "backend/dto" import { useRecoilValue } from "recoil" import { FeedDataFilter, @@ -14,6 +14,8 @@ import { import CommentModal from "@share/Modal/comment/CommentModal" import FeedUploadModal from "@share/Modal/feed/FeedUploadModal" import UserListModal from "@share/Modal/userList/UserListModal" +import { FlexBox } from "ui" +import Loading from "@share/Loading/Loading" const Style = { Wrapper: styled.div` @@ -32,19 +34,18 @@ export default function Profile() { const currentUserData = useRecoilValue(userDataState) const feedDataType = useRecoilValue(FeedDataFilter) - const [feedData, setFeedData] = useState([]) const selectedFeedData = useRecoilValue(feedDataState) + const likeUserList = useRecoilValue(userListState) + const [feedData, setFeedData] = useState([]) const [isCommentModalOpen, setIsCommentModalOpen] = useState(false) const [isUserListModalOpen, setIsUserListModalOpen] = useState(false) - const likeUserList = useRecoilValue(userListState) - const [isFeedUploadModalOpen, setIsFeedUploadModalOpen] = useState(false) useEffect(() => { - if (currentUserData === undefined || currentUserData.feed === undefined) - return + if (currentUserData.feed === undefined) return + if (feedDataType === "public") { setFeedData( currentUserData.feed.filter((eachFeed) => !eachFeed.isPrivate), @@ -78,13 +79,17 @@ export default function Profile() { /> - {feedData !== undefined && ( + {feedData !== undefined ? ( + ) : ( + + + )} diff --git a/firebase-practice/pages/profile/[id].tsx b/firebase-practice/pages/profile/[id].tsx index 42aef5b..f2f4758 100644 --- a/firebase-practice/pages/profile/[id].tsx +++ b/firebase-practice/pages/profile/[id].tsx @@ -1,18 +1,19 @@ import { useEffect, useState } from "react" -import { DBService } from "@FireBase" -import { doc, DocumentData, onSnapshot } from "firebase/firestore" +import { DocumentData } from "firebase/firestore" import { GetServerSideProps } from "next" import ProfileHeader from "@feature/profile/customerProfile" import styled from "styled-components" import { Margin } from "ui" import Layout from "components/layout" -import { FeedData, UserData } from "backend/dto" +import { FeedItem, UserData } from "backend/dto" import { useRouter } from "next/router" import FeedGrid from "@share/Feed/profilepage/FeedGrid" import { useRecoilValue } from "recoil" import { feedDataState, userListState } from "@share/recoil/recoilList" import CommentModal from "@share/Modal/comment/CommentModal" import UserListModal from "@share/Modal/userList/UserListModal" +import axios from "axios" +import ProfileLoadingGrid from "@share/Loading/ProfileLoadingGrid" const Style = { Wrapper: styled.div` @@ -30,7 +31,7 @@ const Style = { export default function Profile({ userId }: Props) { const router = useRouter() const [userData, setUserData] = useState() - const [feedData, setFeedData] = useState() + const [feedData, setFeedData] = useState() const selectedFeedData = useRecoilValue(feedDataState) const [isCommentModalOpen, setIsCommentModalOpen] = useState(false) const [isUserListModalOpen, setIsUserListModalOpen] = useState(false) @@ -40,14 +41,17 @@ export default function Profile({ userId }: Props) { setIsUserListModalOpen(false) if (router.query !== undefined && router.query.id !== userId) router.push(`/profile/${router.query.id}`) - const userDataRef = doc(DBService, "users", `${userId}`) - onSnapshot(userDataRef, { includeMetadataChanges: true }, (doc) => { - if (doc) { - setUserData(doc.data()) - } - }) }, [router.query, userId]) + useEffect(() => { + axios({ + method: "GET", + url: `/api/profile?userId=${userId}`, + }).then((response) => { + setUserData(response.data) + }) + }, [userId]) + useEffect(() => { setFeedData(userData?.feed) }, [userData, router.query]) @@ -71,11 +75,13 @@ export default function Profile({ userId }: Props) { userData={userData as UserData} setIsUserListModalOpen={setIsUserListModalOpen} /> - {feedData !== undefined && ( + {feedData !== undefined ? ( + ) : ( + )} diff --git a/firebase-practice/src/FireBase.tsx b/firebase-practice/src/FireBase.tsx index ed9a1cf..a8fd41e 100644 --- a/firebase-practice/src/FireBase.tsx +++ b/firebase-practice/src/FireBase.tsx @@ -4,13 +4,14 @@ import { getFirestore } from "firebase/firestore" import { getStorage } from "firebase/storage" const firebaseConfig = { - apiKey: "AIzaSyC7IT7tMtvVzSI2tj7JSPfdU3tZSgXCcG4", - authDomain: "fir-practice-d0e2e.firebaseapp.com", - projectId: "fir-practice-d0e2e", - storageBucket: "fir-practice-d0e2e.appspot.com", - messagingSenderId: "116460764746", - appId: "1:116460764746:web:73f450e7b7442ff3a230b3", + apiKey: process.env.NEXT_PUBLIC_API_KEY, + authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN, + projectId: process.env.NEXT_PUBLIC_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_APP_ID, } + const FireBasApp = initializeApp(firebaseConfig) export const authService = getAuth(FireBasApp) export const DBService = getFirestore(FireBasApp) diff --git a/firebase-practice/src/backend/dto.tsx b/firebase-practice/src/backend/dto.d.ts similarity index 93% rename from firebase-practice/src/backend/dto.tsx rename to firebase-practice/src/backend/dto.d.ts index 5db03bf..c1ae432 100644 --- a/firebase-practice/src/backend/dto.tsx +++ b/firebase-practice/src/backend/dto.d.ts @@ -8,13 +8,13 @@ * @creator 피드를 올린 사람의 userId * @uploadTime 피드의 업로드 시간 (components/lib/getCurrentTime.tsx 사용) */ -export type FeedData = { +export type FeedItem = { imageUrl: string desc: string location: string isPrivate: boolean storageId: string - creator: string + creator: UserInfo uploadTime: string } @@ -47,9 +47,11 @@ export type UserInfo = { name: string | null email: string | null } + export type UserData = { info: UserInfo follow: string[] follower: string[] - feed: FeedData[] + likeFeedIds: string[] + feed: FeedItem[] } diff --git a/firebase-practice/src/components/feature/followListAtMainPage/FollowCard.tsx b/firebase-practice/src/components/feature/followListAtMainPage/FollowCard.tsx index b267883..26ce703 100644 --- a/firebase-practice/src/components/feature/followListAtMainPage/FollowCard.tsx +++ b/firebase-practice/src/components/feature/followListAtMainPage/FollowCard.tsx @@ -1,7 +1,5 @@ -import { DBService } from "@FireBase" import { darkModeState, userDataState } from "@share/recoil/recoilList" -import { UserData } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" +import { UserData, UserInfo } from "backend/dto" import Image from "next/image" import { useRouter } from "next/router" import { useEffect, useState } from "react" @@ -10,9 +8,11 @@ import styled from "styled-components" import { CustomH6Light, FlexBox, Margin } from "ui" import { ProfileIcon } from "icons" import Link from "next/link" +import axios from "axios" +import Loading from "@share/Loading/Loading" type Props = { - userId: string + followUser: UserInfo } const Style = { Wrapper: styled.div` @@ -24,51 +24,43 @@ const Style = { cursor: pointer; `, } -export default function FollowCard({ userId }: Props) { +export default function FollowCard({ followUser }: Props) { const router = useRouter() - const [userData, setUserData] = useState() + const currentUser = useRecoilValue(userDataState) const isDarkMode = useRecoilValue(darkModeState) - useEffect(() => { - onSnapshot(doc(DBService, "users", userId), (data) => { - if (data) setUserData(data.data() as UserData) - }) - }, []) + return ( - <> - {userData && ( - - - - {userData.info.profileImage ? ( - profile - ) : ( - - )} - - - {userData.info.name && ( - - {userData.info.name?.length > 4 - ? `${userData.info.name.slice(0, 4)}..` - : userData.info.name} - - )} - - - )} - + + + + {followUser.profileImage ? ( + profile + ) : ( + + )} + + + {followUser.name && ( + + {followUser.name?.length > 4 + ? `${followUser.name.slice(0, 4)}..` + : followUser.name} + + )} + + ) } diff --git a/firebase-practice/src/components/feature/followListAtMainPage/index.tsx b/firebase-practice/src/components/feature/followListAtMainPage/index.tsx index a90109c..c15c83a 100644 --- a/firebase-practice/src/components/feature/followListAtMainPage/index.tsx +++ b/firebase-practice/src/components/feature/followListAtMainPage/index.tsx @@ -1,11 +1,13 @@ -import { useEffect, useState } from "react" +import { Suspense, useEffect, useState } from "react" import styled from "styled-components" -import { FlexBox } from "ui" +import { FlexBox, Margin } from "ui" import FollowCard from "./FollowCard" import { v4 } from "uuid" import { LeftArrowForCarouselIcon, RightArrowForCarouselIcon } from "icons" import { useRecoilValue } from "recoil" import { darkModeState, userDataState } from "@share/recoil/recoilList" +import { followUsersSelector } from "@share/recoil/user" +import Loading from "@share/Loading/Loading" const Style = { Wrapper: styled.div` @@ -35,68 +37,75 @@ const Style = { } export default function FollowListAtMainPage() { - const currentUserData = useRecoilValue(userDataState) + const followUsers = useRecoilValue(followUsersSelector) + const isDarkMode = useRecoilValue(darkModeState) + const [movingRange, setMovingRange] = useState(0) const [followNumber, setFollowNumber] = useState(0) - const isDarkMode = useRecoilValue(darkModeState) useEffect(() => { - if (currentUserData === undefined) return - if (currentUserData.follow === undefined) return if (window.innerWidth < 470) { - setFollowNumber(currentUserData.follow.length - 5) + setFollowNumber(followUsers.length - 5) return } - setFollowNumber(currentUserData.follow.length - 6) - }, [currentUserData]) + setFollowNumber(followUsers.length - 6) + }, [followUsers]) + useEffect(() => { if (followNumber < 0) setFollowNumber(0) }, [followNumber]) return ( - <> - {currentUserData !== undefined && currentUserData.follow !== undefined ? ( - + + {followUsers.map((followUser) => { + return ( + + + + + + } + > + + + ) + })} + + {movingRange === 0 || ( + { + setMovingRange((current) => current + 76) + setFollowNumber((current) => current + 1) }} > - - {currentUserData.follow.map((followUserId) => { - return - })} - - {movingRange === 0 || ( - { - setMovingRange((current) => current + 76) - setFollowNumber((current) => current + 1) - }} - > - - - )} - {followNumber === 0 || ( - { - setMovingRange((current) => current - 76) - setFollowNumber((current) => current - 1) - }} - > - - - )} - - ) : ( - <> + + + )} + {followNumber === 0 || ( + { + setMovingRange((current) => current - 76) + setFollowNumber((current) => current - 1) + }} + > + + )} - + ) } diff --git a/firebase-practice/src/components/feature/profile/customerProfile/index.tsx b/firebase-practice/src/components/feature/profile/customerProfile/index.tsx index 183f2d2..1ce235e 100644 --- a/firebase-practice/src/components/feature/profile/customerProfile/index.tsx +++ b/firebase-practice/src/components/feature/profile/customerProfile/index.tsx @@ -2,7 +2,7 @@ import MobileHeader from "./MobileHeader" import PCHeader from "./PCHeader" import { Margin } from "ui" import { UserData } from "backend/dto" -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" import { SetStateAction } from "react" type Props = { diff --git a/firebase-practice/src/components/feature/profile/mypage/PCHeader.tsx b/firebase-practice/src/components/feature/profile/mypage/PCHeader.tsx index ac1ce31..d23e7e1 100644 --- a/firebase-practice/src/components/feature/profile/mypage/PCHeader.tsx +++ b/firebase-practice/src/components/feature/profile/mypage/PCHeader.tsx @@ -132,17 +132,19 @@ export default function PCHeader({ setIsUserListModalOpen }: Props) { )} - { - setIsOpen(true) - }} - style={{ - color: isDarkMode ? "white" : "", - backgroundColor: isDarkMode ? "black" : "", - }} - > - 프로필 편집 - + {userData.info.userId !== "" && ( + { + setIsOpen(true) + }} + style={{ + color: isDarkMode ? "white" : "", + backgroundColor: isDarkMode ? "black" : "", + }} + > + 프로필 편집 + + )} @@ -205,44 +207,46 @@ export default function PCHeader({ setIsUserListModalOpen }: Props) { )} - - { - setFeedDataType("all") - }} - color={isDarkMode ? "white" : "grey"} - > - - 전체 게시물 - - - - { - setFeedDataType("public") - }} - color={isDarkMode ? "white" : "grey"} - > - - 공개 게시물 - - - - { - setFeedDataType("private") - }} - color={isDarkMode ? "white" : "grey"} - > - - 숨김 게시물 - - - - + {userData.info.userId !== "" && ( + + { + setFeedDataType("all") + }} + color={isDarkMode ? "white" : "grey"} + > + + 전체 게시물 + + + + { + setFeedDataType("public") + }} + color={isDarkMode ? "white" : "grey"} + > + + 공개 게시물 + + + + { + setFeedDataType("private") + }} + color={isDarkMode ? "white" : "grey"} + > + + 숨김 게시물 + + + + + )} ) } diff --git a/firebase-practice/src/components/feature/profile/mypage/index.tsx b/firebase-practice/src/components/feature/profile/mypage/index.tsx index 14b2e51..24d3d7f 100644 --- a/firebase-practice/src/components/feature/profile/mypage/index.tsx +++ b/firebase-practice/src/components/feature/profile/mypage/index.tsx @@ -1,4 +1,4 @@ -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" import { SetStateAction } from "react" import { Margin } from "ui" import MobileHeader from "./MobileHeader" diff --git a/firebase-practice/src/components/layout/Header.tsx b/firebase-practice/src/components/layout/Header.tsx index b62d025..f9ebddf 100644 --- a/firebase-practice/src/components/layout/Header.tsx +++ b/firebase-practice/src/components/layout/Header.tsx @@ -148,7 +148,7 @@ export default function Header() { { - if (userData !== undefined && userData.info.userId !== "") { + if (userData !== undefined && userData?.info.userId !== "") { setIsModalOpen(true) return } @@ -160,7 +160,8 @@ export default function Header() { ) : ( <> - {userData.info.profileImage !== "" && + {userData && + userData.info.profileImage !== "" && userData.info.profileImage !== null ? ( > name: string | null } diff --git a/firebase-practice/src/components/share/Feed/mainPage/FeedCard/Icons.tsx b/firebase-practice/src/components/share/Feed/mainPage/FeedCard/Icons.tsx index 3eb5d49..a2e1c63 100644 --- a/firebase-practice/src/components/share/Feed/mainPage/FeedCard/Icons.tsx +++ b/firebase-practice/src/components/share/Feed/mainPage/FeedCard/Icons.tsx @@ -1,5 +1,5 @@ import { feedDataState } from "@share/recoil/recoilList" -import { FeedData } from "backend/dto" +import { FeedItem } from "backend/dto" import React, { SetStateAction } from "react" import { useSetRecoilState } from "recoil" import { @@ -13,7 +13,7 @@ import { type Props = { isCurrentUserLike: boolean - feedData: FeedData + feedData: FeedItem setIsCommentModalOpen: React.Dispatch> } @@ -32,9 +32,9 @@ export default function Icons({ > {isCurrentUserLike ? ( - + ) : ( - + )} > setIsCommentModalOpen: React.Dispatch> } @@ -31,34 +30,31 @@ export default function LikeCommentInfo({ setIsCommentModalOpen, }: Props) { const [commentData, setCommentData] = useState([]) - const [likerList, setLikerList] = useState([]) const currentUser = useRecoilValue(userDataState) const setLikeUserList = useSetRecoilState(userListState) const setSelectedFeedData = useSetRecoilState(feedDataState) const isDarkMode = useRecoilValue(darkModeState) - const [isCurrentUserLike, setIsCurrentUserLike] = useState(false) + const [likeUserIds, setLikeUserIds] = useState([]) useEffect(() => { - onSnapshot(doc(DBService, "Comments", `${feedData.storageId}`), (doc) => { - setCommentData(doc.data()?.AllComments) + axios({ + method: "GET", + url: `/api/comment?commentId=${feedData.storageId}`, }) - onSnapshot(doc(DBService, "like", `${feedData.storageId}`), (doc) => { - setLikerList(doc.data()?.likerList) + .then((res) => { + setCommentData(res.data) + }) + .catch((error) => console.log(error)) + + axios.get(`/api/like?storageId=${feedData.storageId}`).then((response) => { + const likeUserIdsResponse = response.data + + setLikeUserIds(likeUserIdsResponse) }) }, [feedData]) - useEffect(() => { - if (!likerList) return - if (!currentUser) return - if (currentUser.info.userId === "") return - if (likerList.includes(currentUser.info.userId)) setIsCurrentUserLike(true) - else { - setIsCurrentUserLike(false) - } - }, [likerList, currentUser]) - return ( <> - {isCurrentUserLike ? ( - + {(currentUser.likeFeedIds ?? []).includes(feedData.storageId) ? ( + ) : ( - + )} { + setLikeUserList(likeUserIds) setIsLikeModalOpen(true) - setLikeUserList(likerList) }} > - 좋아요 {likerList ? likerList.length : "0"}개 + 좋아요 {likeUserIds ? likeUserIds.length : "0"}개 댓글 {commentData ? commentData.length : "0"}개 diff --git a/firebase-practice/src/components/share/Feed/mainPage/FeedCard/index.tsx b/firebase-practice/src/components/share/Feed/mainPage/FeedCard/index.tsx index 4a04702..9ad28ed 100644 --- a/firebase-practice/src/components/share/Feed/mainPage/FeedCard/index.tsx +++ b/firebase-practice/src/components/share/Feed/mainPage/FeedCard/index.tsx @@ -1,10 +1,7 @@ -import { DBService } from "@FireBase" import { darkModeState, userDataState } from "@share/recoil/recoilList" -import { FeedData, UserData } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" +import { FeedItem } from "backend/dto" import Image from "next/image" -import { useRouter } from "next/router" -import { SetStateAction, useEffect, useState } from "react" +import { SetStateAction } from "react" import { useRecoilValue } from "recoil" import styled from "styled-components" import { FlexBox, Margin } from "ui" @@ -12,11 +9,14 @@ import { ProfileIcon } from "icons" import Desc from "./Desc" import LikeCommentInfo from "./LikeCommentInfo" import Link from "next/link" +import ThreeDotMenu from "@share/Feed/mypage/ThreeDotMenu" type Props = { - feedData: FeedData + feedData: FeedItem + isCurrentUserFeed?: boolean setIsCommentModalOpen: React.Dispatch> setIsLikeModalOpen: React.Dispatch> + setIsFeedUploadModalOpen?: React.Dispatch> } const Style = { @@ -70,103 +70,94 @@ const Style = { export default function FeedCard({ feedData, + isCurrentUserFeed, setIsCommentModalOpen, setIsLikeModalOpen, + setIsFeedUploadModalOpen, }: Props) { - const router = useRouter() - const [feedCreatorData, setFeedCreatorData] = useState() const currentUser = useRecoilValue(userDataState) - const [routingPath, setRoutingPath] = useState("") const isDarkMode = useRecoilValue(darkModeState) - useEffect(() => { - onSnapshot(doc(DBService, "users", `${feedData.creator}`), (data) => { - setFeedCreatorData(data.data() as UserData) - }) - }, [feedData]) - - useEffect(() => { - if (routingPath !== "") router.replace(`${routingPath}`) - }, [routingPath]) - return ( - <> - {feedCreatorData && ( - + + - - - {feedCreatorData?.info.profileImage ? ( - - - creator - - - ) : ( - + creator - )} - - - {feedCreatorData?.info.name} - - {feedData.location} - - - - {feedData.imageUrl ? ( - Image + + ) : ( - Image )} - - - + + {feedData.creator.name} + + {feedData.location} + + + {isCurrentUserFeed && setIsFeedUploadModalOpen && ( + - + )} + + {feedData.imageUrl ? ( + Image + ) : ( + Image )} - + + + + ) } diff --git a/firebase-practice/src/components/share/Feed/mainPage/FeedList.tsx b/firebase-practice/src/components/share/Feed/mainPage/FeedList.tsx index d2b4828..d7457a6 100644 --- a/firebase-practice/src/components/share/Feed/mainPage/FeedList.tsx +++ b/firebase-practice/src/components/share/Feed/mainPage/FeedList.tsx @@ -1,25 +1,19 @@ -import { FeedData } from "backend/dto" +import { FeedItem } from "backend/dto" import { CameraIcon } from "icons" import { SetStateAction } from "react" import styled from "styled-components" import { CustomH2Light, CustomH5Light } from "ui" import FeedCard from "./FeedCard" +import { useRecoilValue } from "recoil" +import { mainFeedItemsAtom } from "@share/recoil/feed" type Props = { - FeedData: FeedData[] | undefined setIsCommentModalOpen: React.Dispatch> setIsLikeModalOpen: React.Dispatch> } const Style = { - ImageCard: styled.div` - width: 200px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - `, - ImageContainer: styled.div` + Container: styled.div` display: flex; align-items: center; flex-direction: column; @@ -29,38 +23,35 @@ const Style = { } export default function FeedList({ - FeedData, setIsCommentModalOpen, setIsLikeModalOpen, }: Props) { + const feedItems = useRecoilValue(mainFeedItemsAtom) + return ( - - {FeedData !== undefined && FeedData.length !== 0 ? ( - FeedData.sort(function (a, b) { - return Number(b.uploadTime) - Number(a.uploadTime) - }).map((data, index) => { - return ( - - ) - }) + + {feedItems.length !== 0 ? ( + feedItems + .filter((feed) => !feed.isPrivate) + .map((data) => { + return ( + + ) + }) ) : ( <> - {FeedData?.length === 0 && ( - <> - - 사진 공유 - - 상단 바에 있는 아이콘을 클릭하여 사진을 공유할 수 있습니다. - - - )} + + 사진 공유 + + 상단 바에 있는 아이콘을 클릭하여 사진을 공유할 수 있습니다. + )} - + ) } diff --git a/firebase-practice/src/components/share/Feed/mypage/FeedSortList.tsx b/firebase-practice/src/components/share/Feed/mypage/FeedSortList.tsx index f60d5ff..39d016f 100644 --- a/firebase-practice/src/components/share/Feed/mypage/FeedSortList.tsx +++ b/firebase-practice/src/components/share/Feed/mypage/FeedSortList.tsx @@ -1,16 +1,16 @@ import Loading from "@share/Loading/Loading" import { darkModeState, userDataState } from "@share/recoil/recoilList" -import { FeedData } from "backend/dto" +import { FeedItem } from "backend/dto" import { CameraIcon } from "icons" import React, { SetStateAction } from "react" import { useRecoilValue } from "recoil" import styled from "styled-components" import { CustomH2Light, CustomH5Light } from "ui" import { v4 } from "uuid" -import FeedSortingCard from "./FeedSortingCard" +import FeedCard from "../mainPage/FeedCard" type Props = { - feedData: FeedData[] | undefined + feeds: FeedItem[] | undefined setIsCommentModalOpen: React.Dispatch> setIsFeedUploadModalOpen: React.Dispatch> setIsUserListModalOpen: React.Dispatch> @@ -34,7 +34,7 @@ const Style = { } export default function FeedSortList({ - feedData, + feeds, setIsCommentModalOpen, setIsFeedUploadModalOpen, setIsUserListModalOpen: setIsLikeModalOpen, @@ -48,17 +48,18 @@ export default function FeedSortList({ ) : ( <> - {feedData !== undefined && feedData.length !== 0 ? ( + {feeds !== undefined && feeds.length !== 0 ? ( <> - {[...feedData] + {[...feeds] .sort((a, b) => { return Number(b.uploadTime) - Number(a.uploadTime) }) - .map((data) => { + .map((feed) => { return ( - > - name: string | null | undefined -} - -const Style = { - CommentBox: styled.div` - width: 100%; - white-space: pre-wrap; - padding: 0px 10px; - max-height: 200px; - display: flex; - overflow-y: scroll; - ::-webkit-scrollbar { - display: none; - } - `, -} - -export default function Desc({ feedData, setIsCommentModalOpen, name }: Props) { - const setSelectedFeedData = useSetRecoilState(feedDataState) - const isDarkMode = useRecoilValue(darkModeState) - return ( - - - {feedData.desc.length > 0 ? ( - - {name} - - ) : ( - { - setSelectedFeedData(feedData) - setIsCommentModalOpen(true) - }} - > - 더보기 - - )} - - {feedData.desc.length > 10 ? ( - - {`${feedData.desc.slice(0, 10)}...`} - - { - setSelectedFeedData(feedData) - setIsCommentModalOpen(true) - }} - > - 더보기 - - - ) : ( - - {feedData.desc} - - )} - - - ) -} diff --git a/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/LikeCommentInfo.tsx b/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/LikeCommentInfo.tsx deleted file mode 100644 index 15d93f1..0000000 --- a/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/LikeCommentInfo.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { DBService } from "@FireBase" -import { - darkModeState, - feedDataState, - userDataState, - userListState, -} from "@share/recoil/recoilList" -import { FeedData } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" -import React, { SetStateAction, useCallback, useEffect, useState } from "react" -import { useRecoilValue, useSetRecoilState } from "recoil" -import { - CommentIcon, - CustomH6, - FlexBox, - FullHeart, - HeartIcon, - Margin, - ShareIcon, -} from "ui" - -type Props = { - feedData: FeedData - setIsCommentModalOpen: React.Dispatch> - setIsLikeModalOpen: React.Dispatch> -} - -export default function LikeCommentInfo({ - feedData, - setIsCommentModalOpen, - setIsLikeModalOpen, -}: Props) { - const [commentData, setCommentData] = useState([]) - const [likerList, setLikerList] = useState([]) - const [isCurrentUserLike, setIsCurrentUserLike] = useState(false) - const currentUser = useRecoilValue(userDataState) - const setLikeUserList = useSetRecoilState(userListState) - const setSelectedFeedData = useSetRecoilState(feedDataState) - const isDarkMode = useRecoilValue(darkModeState) - - useEffect(() => { - onSnapshot(doc(DBService, "Comments", `${feedData.storageId}`), (doc) => { - setCommentData(doc.data()?.AllComments) - }) - onSnapshot(doc(DBService, "like", `${feedData.storageId}`), (doc) => { - setLikerList(doc.data()?.likerList) - }) - }, []) - - const handleCurrentUserLike = useCallback((isLike: boolean) => { - setIsCurrentUserLike(isLike) - }, []) - - useEffect(() => { - if (!likerList) return - if (!currentUser) return - if (currentUser.info.userId === "") return - if (likerList.includes(currentUser.info.userId)) handleCurrentUserLike(true) - else handleCurrentUserLike(false) - }, [likerList, currentUser]) - - return ( - <> - - - {isCurrentUserLike ? ( - - ) : ( - - )} - - { - setSelectedFeedData(feedData) - setIsCommentModalOpen(true) - }} - /> - - - - - - { - setIsLikeModalOpen(true) - setLikeUserList(likerList) - }} - > - 좋아요 {likerList ? likerList.length : "0"}개 - - - 댓글 {commentData ? commentData.length : "0"}개 - - - - ) -} diff --git a/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/index.tsx b/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/index.tsx deleted file mode 100644 index 7717507..0000000 --- a/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/index.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { DBService } from "@FireBase" -import { darkModeState, userDataState } from "@share/recoil/recoilList" -import { FeedData, UserInfo } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" -import { ProfileIcon } from "icons" -import Image from "next/image" -import { SetStateAction, useEffect, useState } from "react" -import { useRecoilValue } from "recoil" -import styled from "styled-components" -import { FlexBox, Margin } from "ui" -import Desc from "./Desc" -import LikeCommentInfo from "./LikeCommentInfo" -import ThreeDotMenu from "./ThreeDotMenu" - -type Props = { - feedData: FeedData - setIsCommentModalOpen: React.Dispatch> - setIsFeedUploadModalOpen: React.Dispatch> - setIsLikeModalOpen: React.Dispatch> -} - -const Style = { - ImageHeader: styled.div` - width: 470px; - height: 58px; - display: flex; - align-items: center; - padding-left: 15px; - justify-content: space-between; - border-radius: 10px; - border-bottom: none; - position: relative; - @media (max-width: 500px) { - width: 95%; - padding: 0px 5px; - } - `, - HeaderText: styled.div` - display: flex; - flex-direction: column; - height: 38px; - justify-content: center; - `, - UserName: styled.span` - font-size: 12px; - font-weight: bold; - color: black; - `, - ImageTitle: styled.span` - font-size: 7px; - font-weight: 400; - color: gray; - `, - ImageCard: styled.div` - width: 470px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - border: 1px solid lightgrey; - border-radius: 10px; - padding-bottom: 10px; - background-color: white; - max-width: 470px; - @media (max-width: 900px) { - width: 95%; - } - `, -} - -export default function FeedSortingCard({ - feedData, - setIsCommentModalOpen, - setIsFeedUploadModalOpen, - setIsLikeModalOpen, -}: Props) { - const userData = useRecoilValue(userDataState) - const [creatorInfo, setCreatorInfo] = useState() - const isDarkMode = useRecoilValue(darkModeState) - - useEffect(() => { - onSnapshot(doc(DBService, "users", `${feedData.creator}`), (doc) => { - setCreatorInfo(doc.data()?.info) - }) - }, [feedData]) - - return ( - <> - {userData && ( - - - - {userData.info.profileImage ? ( - creator - ) : ( - - )} - - - {userData?.info.name} - - {feedData.location} - - - - - {feedData.imageUrl ? ( - Image - ) : ( - Image - )} - - - - - )} - - ) -} diff --git a/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/ThreeDotMenu.tsx b/firebase-practice/src/components/share/Feed/mypage/ThreeDotMenu.tsx similarity index 63% rename from firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/ThreeDotMenu.tsx rename to firebase-practice/src/components/share/Feed/mypage/ThreeDotMenu.tsx index c1b35fc..1d79920 100644 --- a/firebase-practice/src/components/share/Feed/mypage/FeedSortingCard/ThreeDotMenu.tsx +++ b/firebase-practice/src/components/share/Feed/mypage/ThreeDotMenu.tsx @@ -1,18 +1,5 @@ -import { DBService, storageService } from "@FireBase" -import { - darkModeState, - feedDataState, - userDataState, -} from "@share/recoil/recoilList" -import { FeedData } from "backend/dto" -import { - arrayRemove, - arrayUnion, - deleteDoc, - doc, - updateDoc, -} from "firebase/firestore" -import { deleteObject, ref } from "firebase/storage" +import { darkModeState, feedDataState } from "@share/recoil/recoilList" +import { FeedItem } from "backend/dto" import { DeleteIcon, DotMenuIcon, @@ -21,12 +8,13 @@ import { LogoutIcon, UnLockIcon, } from "icons" +import { useFeedCRUD } from "lib/hooks/useFeedCRUD" import { SetStateAction, useState } from "react" import { useRecoilValue, useSetRecoilState } from "recoil" import styled from "styled-components" type Props = { - feedData: FeedData + feedData: FeedItem setIsFeedUploadModalOpen: React.Dispatch> } @@ -144,87 +132,15 @@ export default function ThreeDotMenu({ }: Props) { const [isMenuOpen, setIsMenuOpen] = useState(false) const setSelectedFeedData = useSetRecoilState(feedDataState) - const setCurrentUserData = useSetRecoilState(userDataState) const isDarkMode = useRecoilValue(darkModeState) const handleThreeDotMenuClick = () => { setIsMenuOpen((current) => !current) } - const handleDeleteFeed = async () => { - const feed: FeedData = { - creator: feedData.creator, - desc: feedData.desc, - imageUrl: feedData.imageUrl, - location: feedData.location, - isPrivate: feedData.isPrivate, - storageId: feedData.storageId, - uploadTime: feedData.uploadTime, - } - const storageImageRef = ref( - storageService, - `images/${feedData.creator}/${feedData.storageId}`, - ) - const firestoreAllRef = doc(DBService, "mainPage", "userFeedDataAll") - const firestoreCommentRef = doc(DBService, "Comments", feedData.storageId) - const firestorePersonalRef = doc(DBService, `users`, `${feedData.creator}`) - const firestoreLikeRef = doc(DBService, "like", feedData.storageId) - - handleThreeDotMenuClick() - - await updateDoc(firestorePersonalRef, { - feed: arrayRemove(feed), - }).catch((error) => console.log(error.code)) - await updateDoc(firestoreAllRef, { - feed: arrayRemove(feed), - }).catch((error) => console.log(error.code)) - - await deleteObject(storageImageRef).catch((error) => - console.log(error.code), - ) - await deleteDoc(firestoreCommentRef).catch((error) => - console.log(error.code), - ) - await deleteDoc(firestoreLikeRef).catch((error) => console.log(error.code)) - } - const handlePrivateToggle = async () => { - const firestoreImageAllRef = doc(DBService, "mainPage", "userFeedDataAll") - const firestorePersonalRef = doc(DBService, `users`, `${feedData.creator}`) - const feed: FeedData = { - creator: feedData.creator, - desc: feedData.desc, - imageUrl: feedData.imageUrl, - location: feedData.location, - isPrivate: feedData.isPrivate, - storageId: feedData.storageId, - uploadTime: feedData.uploadTime, - } - const toggleFeed: FeedData = { - creator: feedData.creator, - desc: feedData.desc, - imageUrl: feedData.imageUrl, - location: feedData.location, - isPrivate: !feedData.isPrivate, - storageId: feedData.storageId, - uploadTime: feedData.uploadTime, - } - - handleThreeDotMenuClick() - await updateDoc(firestorePersonalRef, { - feed: arrayRemove(feed), - }).then(async () => { - await updateDoc(firestorePersonalRef, { - feed: arrayUnion(toggleFeed), - }) - }) - await updateDoc(firestoreImageAllRef, { - feed: arrayRemove(feed), - }).then(async () => { - await updateDoc(firestoreImageAllRef, { - feed: arrayUnion(toggleFeed), - }) - }) - } + const { handleDeleteFeed, handlePrivateToggle } = useFeedCRUD({ + handleThreeDotMenuClick, + }) return ( <> @@ -250,7 +166,7 @@ export default function ThreeDotMenu({ 편집 handlePrivateToggle(feedData)} style={ isDarkMode ? { backgroundColor: "black", color: "white" } : {} } @@ -264,7 +180,7 @@ export default function ThreeDotMenu({ {feedData.isPrivate ? "공개" : "비공개"} handleDeleteFeed(feedData)} style={ isDarkMode ? { backgroundColor: "black", color: "white" } : {} } diff --git a/firebase-practice/src/components/share/Feed/profilepage/FeedGrid.tsx b/firebase-practice/src/components/share/Feed/profilepage/FeedGrid.tsx index 8c5d161..aa59e5b 100644 --- a/firebase-practice/src/components/share/Feed/profilepage/FeedGrid.tsx +++ b/firebase-practice/src/components/share/Feed/profilepage/FeedGrid.tsx @@ -1,12 +1,12 @@ -import { FeedData } from "backend/dto" +import { FeedItem } from "backend/dto" import styled from "styled-components" import FeedGridCard from "./FeedGridCard" import { v4 } from "uuid" -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" import { SetStateAction, useEffect, useState } from "react" type Props = { - feedDatas: FeedData[] | undefined + feedDatas: FeedItem[] | undefined setIsCommentModalOpen: React.Dispatch> } @@ -29,13 +29,13 @@ const Style = { export default function FeedGrid({ feedDatas, setIsCommentModalOpen }: Props) { const windowSize = useWindowSize() const [feedDataSortedByUploadTime, setFeedDataSortedByUploadTime] = useState< - FeedData[] + FeedItem[] >([]) useEffect(() => { if (feedDatas === undefined) return if (feedDatas.length >= 0) setFeedDataSortedByUploadTime( - (JSON.parse(JSON.stringify(feedDatas)) as FeedData[]).sort(function ( + (JSON.parse(JSON.stringify(feedDatas)) as FeedItem[]).sort(function ( a, b, ) { diff --git a/firebase-practice/src/components/share/Feed/profilepage/FeedGridCard.tsx b/firebase-practice/src/components/share/Feed/profilepage/FeedGridCard.tsx index a81934c..39ab530 100644 --- a/firebase-practice/src/components/share/Feed/profilepage/FeedGridCard.tsx +++ b/firebase-practice/src/components/share/Feed/profilepage/FeedGridCard.tsx @@ -1,14 +1,13 @@ -import { DBService } from "@FireBase" import { feedDataState } from "@share/recoil/recoilList" -import { Comment, FeedData } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" +import axios from "axios" +import { Comment, FeedItem } from "backend/dto" import { SetStateAction, useEffect, useState } from "react" import { useSetRecoilState } from "recoil" import styled from "styled-components" import { Margin } from "ui" type Props = { - feedData: FeedData + feedData: FeedItem setIsCommentModalOpen: React.Dispatch> } @@ -54,11 +53,18 @@ export default function FeedGridCard({ const setSelectedFeedData = useSetRecoilState(feedDataState) useEffect(() => { - onSnapshot(doc(DBService, "Comments", feedData.storageId), (data) => { - if (data) setCommentData(data.data()?.AllComments as Comment[]) + axios({ + method: "GET", + url: `/api/comment?commentId=${feedData.storageId}`, + }).then((response) => { + setCommentData(response.data) }) - onSnapshot(doc(DBService, "like", feedData.storageId), (data) => { - if (data) setLikeData(data.data()?.likerList as string[]) + + axios({ + method: "GET", + url: `/api/like?storageId=${feedData.storageId}`, + }).then((response) => { + setLikeData(response.data) }) }, []) diff --git a/firebase-practice/src/components/share/Loading/Loading.tsx b/firebase-practice/src/components/share/Loading/Loading.tsx index 61bf339..a508de9 100644 --- a/firebase-practice/src/components/share/Loading/Loading.tsx +++ b/firebase-practice/src/components/share/Loading/Loading.tsx @@ -8,9 +8,16 @@ type Props = { height: number | string borderRadius?: number | string count?: number + paddingTop?: string } -export default function Loading({ width, height, borderRadius, count }: Props) { +export default function Loading({ + width, + height, + borderRadius, + count, + paddingTop, +}: Props) { const isDarkMode = useRecoilValue(darkModeState) return ( ) diff --git a/firebase-practice/src/components/share/Loading/ProfileLoadingGrid.tsx b/firebase-practice/src/components/share/Loading/ProfileLoadingGrid.tsx new file mode 100644 index 0000000..1428393 --- /dev/null +++ b/firebase-practice/src/components/share/Loading/ProfileLoadingGrid.tsx @@ -0,0 +1,37 @@ +import Loading from "@share/Loading/Loading" +import Layout from "components/layout" +import styled from "styled-components" +import { FlexBox } from "ui" + +const Style = { + Wrapper: styled.div` + width: 50vw; + height: fit-content; + display: grid; + gap: 10px; + grid-template-columns: repeat(3, minmax(100px, auto)); + grid-template-rows: repeat(autofill, minmax(100px, auto)); + grid-auto-flow: row; + @media (max-width: 900px) { + width: 100vw; + gap: 3px; + } + `, +} + +export default function ProfileLoadingGrid() { + return ( + + + + + + + + + + + + + ) +} diff --git a/firebase-practice/src/components/share/Modal/YoungstagramModal.tsx b/firebase-practice/src/components/share/Modal/YoungstagramModal.tsx index 3118045..2af6550 100644 --- a/firebase-practice/src/components/share/Modal/YoungstagramModal.tsx +++ b/firebase-practice/src/components/share/Modal/YoungstagramModal.tsx @@ -1,6 +1,6 @@ import { darkModeState } from "@share/recoil/recoilList" import { XIcon } from "icons" -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" import React, { ReactNode, SetStateAction } from "react" import ReactModal from "react-modal" import { useRecoilValue } from "recoil" diff --git a/firebase-practice/src/components/share/Modal/comment/CommentList.tsx b/firebase-practice/src/components/share/Modal/comment/CommentList.tsx index 9e0d1e5..e15d417 100644 --- a/firebase-practice/src/components/share/Modal/comment/CommentList.tsx +++ b/firebase-practice/src/components/share/Modal/comment/CommentList.tsx @@ -1,6 +1,4 @@ -import { DBService } from "@FireBase" -import { FeedData, UserData, Comment } from "backend/dto" -import { doc, onSnapshot } from "firebase/firestore" +import { FeedItem, UserData, Comment } from "backend/dto" import Image from "next/image" import { useRouter } from "next/router" import { useEffect, useState } from "react" @@ -12,9 +10,11 @@ import { useRecoilValue } from "recoil" import { darkModeState, userDataState } from "@share/recoil/recoilList" import { ProfileIcon } from "icons" import Link from "next/link" +import axios from "axios" +import Loading from "@share/Loading/Loading" type Props = { - feedData: FeedData + feedData: FeedItem commentAreaRef: React.RefObject } @@ -67,11 +67,18 @@ export default function CommentList({ feedData, commentAreaRef }: Props) { const isDarkMode = useRecoilValue(darkModeState) useEffect(() => { - onSnapshot(doc(DBService, "users", `${feedData.creator}`), (data) => { - if (data) setUserData(data.data() as UserData) + axios({ + method: "GET", + url: `/api/profile?userId=${feedData.creator}`, + }).then((response) => { + setUserData(response.data) }) - onSnapshot(doc(DBService, "Comments", `${feedData.storageId}`), (data) => { - if (data) setCommentData(data.data()?.AllComments) + + axios({ + method: "GET", + url: `/api/comment?commentId=${feedData.storageId}`, + }).then((response) => { + setCommentData(response.data) }) }, []) @@ -79,37 +86,47 @@ export default function CommentList({ feedData, commentAreaRef }: Props) { <> - {userData?.info.profileImage ? ( - - - + ) : ( + <> + {userData.info.profileImage !== null ? ( + + + profile + + + ) : ( + - - - ) : ( - + )} + )} - - {userData?.info.name} - + {userData?.info.name === undefined ? ( + + ) : ( + + {userData?.info.name} + + )} {feedData.location} @@ -117,40 +134,47 @@ export default function CommentList({ feedData, commentAreaRef }: Props) { - {userData?.info.profileImage ? ( - - - + ) : ( + <> + {userData.info.profileImage !== null ? ( + + + profile { + if ( + userData?.info.userId === + currentUserData.info.userId + ) { + router.push(`/mypage`) + return + } + router.push(`/profile/${userData?.info.userId}`) + }} + style={{ borderRadius: "32px", cursor: "pointer" }} + priority={true} + /> + + + ) : ( + { - if ( - userData?.info.userId === currentUserData.info.userId - ) { - router.push(`/mypage`) - return - } - router.push(`/profile/${userData?.info.userId}`) - }} - style={{ borderRadius: "32px", cursor: "pointer" }} - priority={true} + userId={userData?.info.userId} /> - - - ) : ( - + )} + )} @@ -160,9 +184,13 @@ export default function CommentList({ feedData, commentAreaRef }: Props) { style={{ paddingRight: "20px", color: isDarkMode ? "white" : "" }} > - - {userData?.info.name} - + {userData?.info.name === undefined ? ( + + ) : ( + + {userData?.info.name} + + )} {feedData.desc} diff --git a/firebase-practice/src/components/share/Modal/comment/CommentModal.tsx b/firebase-practice/src/components/share/Modal/comment/CommentModal.tsx index 7d75c41..661284a 100644 --- a/firebase-practice/src/components/share/Modal/comment/CommentModal.tsx +++ b/firebase-practice/src/components/share/Modal/comment/CommentModal.tsx @@ -1,16 +1,17 @@ -import { FeedData } from "backend/dto" -import React, { SetStateAction, useRef } from "react" +import { FeedItem } from "backend/dto" +import React, { SetStateAction, useEffect, useRef } from "react" import styled from "styled-components" import YoungstagramModal from "../YoungstagramModal" import CommentInput from "./Input" -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" import CommentList from "./CommentList" import Icons from "./Icons" +import Loading from "@share/Loading/Loading" type Props = { isOpen: boolean setIsOpen: React.Dispatch> - feedData: FeedData + feedData: FeedItem } const Style = { @@ -61,7 +62,11 @@ export default function CommentModal({ isOpen, setIsOpen, feedData }: Props) { > - + {feedData.imageUrl ? ( + + ) : ( + + )} diff --git a/firebase-practice/src/components/share/Modal/comment/CommentWrapper.tsx b/firebase-practice/src/components/share/Modal/comment/CommentWrapper.tsx index 2fe2901..5894a32 100644 --- a/firebase-practice/src/components/share/Modal/comment/CommentWrapper.tsx +++ b/firebase-practice/src/components/share/Modal/comment/CommentWrapper.tsx @@ -1,13 +1,9 @@ import { authService, DBService } from "@FireBase" +import Loading from "@share/Loading/Loading" import { darkModeState, userDataState } from "@share/recoil/recoilList" +import axios from "axios" import { Comment, UserData } from "backend/dto" -import { - arrayRemove, - arrayUnion, - doc, - onSnapshot, - updateDoc, -} from "firebase/firestore" +import { arrayRemove, arrayUnion, doc, updateDoc } from "firebase/firestore" import { ProfileIcon } from "icons" import Image from "next/image" import Link from "next/link" @@ -64,11 +60,11 @@ export default function CommentWrapper({ commentData, storageId }: Props) { const isDarkMode = useRecoilValue(darkModeState) useEffect(() => { - const userInfoRef = doc(DBService, "users", commentData.userId) - onSnapshot(userInfoRef, (docData) => { - if (docData) { - setUserData(docData.data() as UserData) - } + axios({ + method: "GET", + url: `/api/profile?userId=${commentData.userId}`, + }).then((response) => { + setUserData(response.data) }) }, []) @@ -104,39 +100,49 @@ export default function CommentWrapper({ commentData, storageId }: Props) { <> - {userData?.info.profileImage ? ( - - - + ) : ( + <> + {userData.info.profileImage !== null ? ( + + + profile + + + ) : ( + - - - ) : ( - + )} + )} - {`${userData?.info.name}`} + {userData?.info.name ? ( + {`${userData?.info.name}`} + ) : ( + + )} {isModifyMode ? ( @@ -172,12 +178,17 @@ export default function CommentWrapper({ commentData, storageId }: Props) { <> {isShowAllComment ? ( <> - - {commentData.comment} - + {commentData.comment ? ( + + {commentData.comment} + + ) : ( + + )} + (false) useEffect(() => { - onSnapshot(doc(DBService, "like", `${storageId}`), (data) => { - if (data) setLikerList(data.data()?.likerList) + axios({ + method: "GET", + url: `/api/like?storageId=${storageId}`, + }).then((response) => { + setLikerList(response.data) }) - onSnapshot(doc(DBService, "Comments", `${storageId}`), (data) => { - if (data) setCommentData(data.data()?.AllComments) + + axios({ + method: "GET", + url: `/api/like?storageId=${storageId}`, + }).then((response) => { + setCommentData(response.data) }) }, [storageId]) @@ -56,9 +64,9 @@ export default function Icons({ storageId, inputRef }: Props) { {isCurrentUserLiked ? ( - + ) : ( - + )} commentAreaRef: React.RefObject } diff --git a/firebase-practice/src/components/share/Modal/feed/FeedUploadModal.tsx b/firebase-practice/src/components/share/Modal/feed/FeedUploadModal.tsx index 23fbc33..cb9cd31 100644 --- a/firebase-practice/src/components/share/Modal/feed/FeedUploadModal.tsx +++ b/firebase-practice/src/components/share/Modal/feed/FeedUploadModal.tsx @@ -1,14 +1,14 @@ import { SetStateAction, useState } from "react" import ModalForImageUpload from "./ModalForFeedUpload" -import { FeedData } from "backend/dto" -import useWindowSize from "lib/useWindowSize" +import { FeedItem } from "backend/dto" +import useWindowSize from "lib/hooks/useWindowSize" import TextInput from "./TextInput" import ImageInput from "./ImageInput" type Props = { isOpen: boolean setIsOpen: React.Dispatch> - feedData?: FeedData + feedData?: FeedItem } export default function FeedUploadModal({ diff --git a/firebase-practice/src/components/share/Modal/feed/ImageInput.tsx b/firebase-practice/src/components/share/Modal/feed/ImageInput.tsx index 37f6c6d..de3b1d5 100644 --- a/firebase-practice/src/components/share/Modal/feed/ImageInput.tsx +++ b/firebase-practice/src/components/share/Modal/feed/ImageInput.tsx @@ -1,5 +1,5 @@ import { darkModeState } from "@share/recoil/recoilList" -import { FeedData } from "backend/dto" +import { FeedItem } from "backend/dto" import { SetStateAction, useEffect } from "react" import { useDropzone } from "react-dropzone" import { useRecoilValue } from "recoil" @@ -7,7 +7,7 @@ import styled from "styled-components" import { CustomH3, FeedUPloadModalIcon, FlexBox, Margin } from "ui" type Props = { - feedData?: FeedData + feedData?: FeedItem setIsFileExist: React.Dispatch> setImagePreviewSrc: React.Dispatch> setImageFile: React.Dispatch> diff --git a/firebase-practice/src/components/share/Modal/feed/TextInput.tsx b/firebase-practice/src/components/share/Modal/feed/TextInput.tsx index 83207e2..03ace08 100644 --- a/firebase-practice/src/components/share/Modal/feed/TextInput.tsx +++ b/firebase-practice/src/components/share/Modal/feed/TextInput.tsx @@ -1,25 +1,16 @@ -import { authService, DBService, storageService } from "@FireBase" +import { authService } from "@FireBase" import { darkModeState, userDataState } from "@share/recoil/recoilList" -import { FeedData } from "backend/dto" -import { - arrayRemove, - arrayUnion, - doc, - setDoc, - updateDoc, -} from "firebase/firestore" -import { getDownloadURL, ref, uploadBytes } from "firebase/storage" +import { FeedItem } from "backend/dto" import { LocationIcon, ProfileIcon } from "icons" -import getCurrentTime from "lib/getCurrentTime" +import { useFeedCRUD } from "lib/hooks/useFeedCRUD" import Image from "next/image" -import React, { SetStateAction, useEffect, useState } from "react" +import React, { SetStateAction, useState } from "react" import { useRecoilValue } from "recoil" import styled from "styled-components" import { CustomH5, FlexBox, Margin } from "ui" -import { v4 } from "uuid" type Props = { - feedData?: FeedData + feedData?: FeedItem imagePreviewSrc: string setIsOpen: React.Dispatch> imageFile: File | undefined @@ -163,117 +154,39 @@ export default function TextInput({ setIsFileExist, }: Props) { const isDarkMode = useRecoilValue(darkModeState) - const [isSubmit, setIsSubmit] = useState(false) const currentUserData = useRecoilValue(userDataState) - const [desc, setDesc] = useState(feedData ? feedData.desc : "") - const [location, setLocation] = useState( - feedData ? feedData.location : "", - ) - const [isPrivate, setIsPrivate] = useState( + const [isSubmit, setIsSubmit] = useState(false) + const [desc, setDesc] = useState(feedData ? feedData.desc : "") + const [location, setLocation] = useState(feedData ? feedData.location : "") + const [isPrivate, setIsPrivate] = useState( feedData ? feedData.isPrivate : false, ) - const [randomId, setRandomId] = useState( - feedData ? feedData.storageId : "", - ) - - useEffect(() => { - setRandomId(v4()) - }, []) - const uploadToStorage = async () => { - const storageRef = ref( - storageService, - `images/${authService.currentUser?.uid}/${randomId}`, - ) - if (imageFile !== undefined) - await uploadBytes(storageRef, imageFile) - .then( - async () => - await getDownloadURL(storageRef).then(async (response) => { - uploadToFirestore(response) - }), - ) - .catch((error) => { - console.log(error.code) - }) + const resetInputs = () => { + setDesc("") + setIsPrivate(false) + setLocation("") } - const uploadToFirestore = async (downloadUrl: string) => { - const feed: FeedData = { - imageUrl: downloadUrl, - desc: desc, - location: location, - isPrivate: isPrivate, - storageId: feedData?.storageId ? feedData.storageId : randomId, - uploadTime: feedData?.uploadTime ? feedData.uploadTime : getCurrentTime(), - creator: `${authService.currentUser?.uid}`, - } - const firestoreAllRef = doc(DBService, "mainPage", `userFeedDataAll`) - const firestorePersonalRef = doc( - DBService, - "users", - `${authService.currentUser?.uid}`, - ) - await updateDoc(firestoreAllRef, { - feed: arrayUnion(feed), - }) - .catch(async (error) => { - if (error.code === "not-found") { - await setDoc(firestoreAllRef, { - feed: [feed], - }) - } - }) - .then(() => { - setIsOpen(false) - setIsSubmit(false) - if (feedData) return - setDesc("") - setIsPrivate(false) - setLocation("") - setRandomId(v4()) - setImageFile(undefined) - setIsFileExist(false) - }) - await updateDoc(firestorePersonalRef, { - feed: arrayUnion(feed), - }).catch(async (error) => { - if (error.code === "not-found") { - await setDoc(firestorePersonalRef, { - feed: [feed], - }) - } - }) - } + const { EditToFireStore, uploadToStorage } = useFeedCRUD({ + imageFile, + setImageFile, + setIsFileExist, + setIsOpen, + resetInputs, + setIsSubmit, + }) + + const submitFeed = () => { + setIsSubmit(true) - const EditToFireStore = async () => { - if (feedData === undefined) return - const firestoreAllRef = doc(DBService, "mainPage", `userFeedDataAll`) - const firestorePersonalRef = doc( - DBService, - "users", - `${authService.currentUser?.uid}`, - ) - const feed: FeedData = { - imageUrl: feedData.imageUrl, - desc: feedData.desc, - location: feedData.location, - isPrivate: feedData.isPrivate, - storageId: feedData.storageId, - uploadTime: feedData.uploadTime, - creator: feedData.creator, + if (feedData) { + EditToFireStore(desc, location, isPrivate, feedData) + return } - await updateDoc(firestorePersonalRef, { - feed: arrayRemove(feed), - }).catch((error) => console.log(error.code)) - await updateDoc(firestoreAllRef, { - feed: arrayRemove(feed), - }) - .then(async () => { - await uploadToFirestore(feedData.imageUrl) - }) - .catch((error) => console.log(error.code)) + + uploadToStorage(desc, location, isPrivate) } return ( @@ -291,12 +204,8 @@ export default function TextInput({ { event.preventDefault() - setIsSubmit(true) - if (feedData) { - EditToFireStore() - return - } - uploadToStorage() + + submitFeed() }} > @@ -357,17 +266,7 @@ export default function TextInput({ - { - setIsSubmit(true) - if (feedData) { - EditToFireStore() - return - } - uploadToStorage() - }} - about={isSubmit ? "none" : ""} - > + 공유하기 diff --git a/firebase-practice/src/components/share/Modal/userList/UserListModal.tsx b/firebase-practice/src/components/share/Modal/userList/UserListModal.tsx index 0ded514..f4e22d7 100644 --- a/firebase-practice/src/components/share/Modal/userList/UserListModal.tsx +++ b/firebase-practice/src/components/share/Modal/userList/UserListModal.tsx @@ -3,7 +3,7 @@ import styled from "styled-components" import YoungstagramModal from "../YoungstagramModal" import FollowUserWrapper from "./UserWrapper" import { v4 } from "uuid" -import useWindowSize from "lib/useWindowSize" +import useWindowSize from "lib/hooks/useWindowSize" type Props = { userList: string[] diff --git a/firebase-practice/src/components/share/recoil/feed.ts b/firebase-practice/src/components/share/recoil/feed.ts new file mode 100644 index 0000000..ad45328 --- /dev/null +++ b/firebase-practice/src/components/share/recoil/feed.ts @@ -0,0 +1,21 @@ +import axios from "axios" +import { FeedItem } from "backend/dto" +import { atom } from "recoil" + +export const mainFeedItemsAtom = atom({ + key: "mainFeedItemsKey", + default: [], + effects: [ + ({ setSelf, trigger }) => { + const getFeedItems = async () => { + const feedItems = await axios + .get("/api/feed") + .then((response) => response.data) + + setSelf(feedItems) + } + + if (trigger === "get") getFeedItems() + }, + ], +}) diff --git a/firebase-practice/src/components/share/recoil/recoilList.tsx b/firebase-practice/src/components/share/recoil/recoilList.tsx index 04955b3..1289b34 100644 --- a/firebase-practice/src/components/share/recoil/recoilList.tsx +++ b/firebase-practice/src/components/share/recoil/recoilList.tsx @@ -1,5 +1,5 @@ import { atom } from "recoil" -import { FeedData, UserData } from "backend/dto" +import { FeedItem, UserData } from "backend/dto" export const userDataState = atom({ key: "USERDATAATOM", @@ -21,7 +21,7 @@ export const FeedDataFilter = atom<"all" | "public" | "private">({ default: "all", }) -export const feedDataState = atom({ +export const feedDataState = atom({ key: "FEEDDATAATOM", default: { imageUrl: "", diff --git a/firebase-practice/src/components/share/recoil/user.ts b/firebase-practice/src/components/share/recoil/user.ts new file mode 100644 index 0000000..c8e2377 --- /dev/null +++ b/firebase-practice/src/components/share/recoil/user.ts @@ -0,0 +1,23 @@ +import { atom, selector } from "recoil" +import { userDataState } from "./recoilList" +import axios from "axios" +import { UserData, UserInfo } from "backend/dto" + +export const followUsersSelector = selector({ + key: "followUsersSelectorKey", + get: async ({ get }) => { + const currentUser = get(userDataState) + const followUserIds = currentUser.follow ?? [] + const followUsers = await Promise.all( + followUserIds.map(async (userId) => { + const followUser = await axios + .get(`/api/profile?userId=${userId}`) + .then((response) => response.data) + + return followUser.info + }), + ) + + return followUsers + }, +}) diff --git a/firebase-practice/src/icons.tsx b/firebase-practice/src/icons.tsx index 84faabb..bf1660f 100644 --- a/firebase-practice/src/icons.tsx +++ b/firebase-practice/src/icons.tsx @@ -17,6 +17,9 @@ type ProfileIconProps = { export function ProfileIcon({ userId, width, height }: ProfileIconProps) { const isDarkMode = useRecoilValue(darkModeState) const currentUserData = useRecoilValue(userDataState) + + if (currentUserData === undefined) return <> + return ( { + const router = useRouter() + + const [email, setEmail] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [password, setPassword] = useState("") + const [name, setName] = useState("") + const [isNewAccount, setIsNewAccount] = useState(false) + const [isLogin, setIsLogin] = useState(false) + + const githubProvider = new GithubAuthProvider() + const googleProvider = new GoogleAuthProvider() + + const setCurrentUser = useSetRecoilState(userDataState) + + const handleOnInputChange: React.ChangeEventHandler = ( + event, + ) => { + if (event.target.name === "Email") { + if (event.target.value === "서경") alert("사랑해") + setEmail(event.target.value) + return + } + setPassword(event.target.value) + } + const handleNameOnChange: React.ChangeEventHandler = ( + event, + ) => { + setName(event.target.value) + } + const handleOnSubmit: React.FormEventHandler = async ( + event, + ) => { + event.preventDefault() + if (email.length === 0) return + if (password.length < 6) return + if (isNewAccount === true) { + await createUserWithEmailAndPassword(authService, email, password) + .then((response) => { + if (response && authService.currentUser !== null) { + updateProfile(authService.currentUser, { + displayName: name, + }) + signOut(authService) + setIsNewAccount(false) + alert( + "회원가입에 성공 하셨습니다! 회원가입하신 정보로 로그인 바랍니다.", + ) + setEmail("") + setPassword("") + } + }) + .catch((error) => { + if (error.code === "auth/weak-password") { + alert("비밀번호는 최소 6자리 이상이어야 합니다.") + setConfirmPassword("") + setPassword("") + } else if (error.code === "auth/email-already-in-use") { + alert("이미 사용중인 이메일 입니다.") + setPassword("") + setConfirmPassword("") + setEmail("") + } else if (error.code === "auth/invalid-email") { + alert("올바른 이메일 형식을 입력해주세요") + setEmail("") + } + }) + return + } + setPersistence(authService, browserSessionPersistence) + .then(async () => { + return signInWithEmailAndPassword(authService, email, password) + .then(async (response) => { + if (response) { + setIsLogin(true) + CreateNewUserToFirestore(response) + } + }) + .catch((error) => { + if (error.code === "auth/wrong-password") { + alert("비밀번호가 잘못되었습니다") + setPassword("") + return + } + if (error.code === "auth/invalid-email") { + alert("이메일 똑바로 쓰세요") + } else if (error.code === "auth/user-not-found") { + alert("등록되지 않은 사용자 입니다") + } + setEmail("") + setPassword("") + }) + }) + .catch((error) => console.log(error.code)) + } + + const handleGoogleAuth = async () => { + signInWithPopup(authService, googleProvider) + .then((response) => { + CreateNewUserToFirestore(response) + }) + .catch((error) => { + if (error.code === "auth/cancelled-popup-request") { + alert( + "로그인 진행중에 오류가 발생하였습니다. 팝업창을 닫지 않도록 주의하시기 바랍니다.", + ) + } + if (error.code === "auth/account-exists-with-different-credential") { + alert("동일한 이메일 주소로 이미 가입된 계정이 있습니다.") + } + router.push("/auth") + }) + } + + const handleGitHubAuth = async () => { + signInWithPopup(authService, githubProvider) + .then((response) => { + if (response) { + CreateNewUserToFirestore(response) + } + }) + .catch((error) => { + if (error.code === "auth/cancelled-popup-request") { + alert( + "로그인 진행중에 오류가 발생하였습니다. 팝업창을 닫지 않도록 주의하시기 바랍니다.", + ) + } + if (error.code === "auth/account-exists-with-different-credential") { + alert("동일한 이메일 주소로 이미 가입된 계정이 있습니다.") + } + router.push("/auth") + }) + } + + const CreateNewUserToFirestore = async (response: UserCredential) => { + const newUserToFirestoreRef = doc(DBService, "users", response.user.uid) + + const UserDataForm: UserInfo = { + userId: response.user.uid, + profileImage: response.user.photoURL, + name: response.user.displayName, + email: response.user.email, + } + + setIsLogin(true) + + await updateDoc(newUserToFirestoreRef, { info: UserDataForm }).catch( + (error) => { + setIsLogin(false) + + if (error.code === "not-found") { + setDoc(newUserToFirestoreRef, { info: UserDataForm }) + .then(async () => { + const userDocument = await getDoc(newUserToFirestoreRef) + + setCurrentUser(userDocument.data() as UserData) + setIsLogin(true) + }) + .catch((error) => console.log(error.code)) + } + }, + ) + } + + return { + isLogin, + isNewAccount, + email, + name, + password, + confirmPassword, + setIsLogin, + setConfirmPassword, + setIsNewAccount, + handleOnInputChange, + handleOnSubmit, + handleNameOnChange, + handleGitHubAuth, + handleGoogleAuth, + } +} diff --git a/firebase-practice/src/lib/hooks/useFeedCRUD.ts b/firebase-practice/src/lib/hooks/useFeedCRUD.ts new file mode 100644 index 0000000..295f459 --- /dev/null +++ b/firebase-practice/src/lib/hooks/useFeedCRUD.ts @@ -0,0 +1,256 @@ +import { DBService, authService, storageService } from "@FireBase" +import { mainFeedItemsAtom } from "@share/recoil/feed" +import { userDataState } from "@share/recoil/recoilList" +import axios from "axios" +import { FeedItem } from "backend/dto" +import { + arrayRemove, + arrayUnion, + deleteDoc, + doc, + updateDoc, +} from "firebase/firestore" +import { + deleteObject, + getDownloadURL, + ref, + uploadBytes, +} from "firebase/storage" +import { SetStateAction } from "react" +import { useRecoilValue, useSetRecoilState } from "recoil" +import { v4 } from "uuid" + +interface Params { + imageFile?: File + setImageFile?: React.Dispatch> + setIsFileExist?: React.Dispatch> + setIsOpen?: (isOpen: boolean) => void + resetInputs?: () => void + setIsSubmit?: React.Dispatch> + handleThreeDotMenuClick?: () => void +} + +interface UploadToFirestoreParams { + downloadUrl: string + desc: string + location: string + isPrivate: boolean + storageId?: string + uploadTime?: string +} + +export const useFeedCRUD = ({ + imageFile, + setImageFile, + setIsFileExist, + setIsOpen, + resetInputs, + setIsSubmit, + handleThreeDotMenuClick, +}: Params) => { + const randomId = v4() + + const currentUser = useRecoilValue(userDataState) + const setMainFeedItems = useSetRecoilState(mainFeedItemsAtom) + const setCurrentUserData = useSetRecoilState(userDataState) + + const uploadToStorage = async ( + desc: string, + location: string, + isPrivate: boolean, + ) => { + const storageRef = ref( + storageService, + `images/${authService.currentUser?.uid}/${randomId}`, + ) + if (imageFile !== undefined) + await uploadBytes(storageRef, imageFile) + .then( + async () => + await getDownloadURL(storageRef).then(async (downloadUrl) => { + uploadToFirestore({ + downloadUrl, + desc, + location, + isPrivate, + }) + }), + ) + .catch((error) => { + console.log(error.code) + }) + } + + const uploadToFirestore = async ({ + downloadUrl, + desc, + location, + isPrivate, + storageId, + uploadTime, + }: UploadToFirestoreParams) => { + const feed = { + imageUrl: downloadUrl, + desc, + location, + isPrivate, + storageId: storageId ?? randomId, + uploadTime: uploadTime ?? "new feed", + creator: `${authService.currentUser?.uid}`, + } + + setMainFeedItems((feedItems) => [ + { ...feed, creator: currentUser.info }, + ...feedItems, + ]) + setCurrentUserData((userData) => { + return { + ...userData, + feed: [{ ...feed, creator: currentUser.info }, ...userData.feed], + } + }) + + // TODO: 여기서 서버랑 동기화 시키기 + axios.post(`/api/feed`, { + ...feed, + }) + + setIsOpen?.(false) + resetInputs?.() + setIsSubmit?.(false) + if (storageId) return + resetInputs?.() + setImageFile?.(undefined) + setIsFileExist?.(false) + } + + const EditToFireStore = async ( + desc: string, + location: string, + isPrivate: boolean, + feedData: FeedItem, + ) => { + setMainFeedItems((feedItems) => + feedItems.map((feedItem) => { + if (feedItem.storageId === feedData.storageId) + return { ...feedData, desc, location, isPrivate } + return feedItem + }), + ) + + setCurrentUserData((userData) => { + return { + ...userData, + feed: userData.feed.map((feedItem) => { + if (feedItem.storageId === feedData.storageId) + return { ...feedData, desc, location, isPrivate } + return feedItem + }), + } + }) + + // TODO: 여기서 서버랑 동기화 시키기 + axios.post(`/api/feed`, { + ...feedData, + newDesc: desc, + newLocation: location, + newIsPrivate: isPrivate, + }) + + setIsOpen?.(false) + resetInputs?.() + setIsSubmit?.(false) + if (feedData.storageId) return + resetInputs?.() + setImageFile?.(undefined) + setIsFileExist?.(false) + } + + const handleDeleteFeed = (feedData: FeedItem) => { + setMainFeedItems((feedItems) => + feedItems.filter((feedItem) => feedItem.storageId !== feedData.storageId), + ) + + setCurrentUserData((userData) => { + return { + ...userData, + feed: userData.feed.filter( + (feedItem) => feedItem.storageId !== feedData.storageId, + ), + } + }) + + handleThreeDotMenuClick?.() + + // TODO: 이 부분을 API 요청으로 대체하고, 요청 후 response에서 새로 바뀐 리스트를 반환해 + // 해당 리스트로 다시 recoil을 set해주도록 수정하기 + + axios.delete(`/api/feed`, { + data: { + userId: currentUser.info.userId, + storageId: feedData.storageId, + imageUrl: feedData.imageUrl, + desc: feedData.desc, + location: feedData.location, + isPrivate: feedData.isPrivate, + creator: feedData.creator.userId, + uploadTime: feedData.uploadTime, + }, + }) + } + + const handlePrivateToggle = (feedData: FeedItem) => { + const firestoreImageAllRef = doc(DBService, "mainPage", "userFeedDataAll") + const firestorePersonalRef = doc( + DBService, + `users`, + `${feedData.creator.userId}`, + ) + + setMainFeedItems((feedItems) => + feedItems.map((feedItem) => { + if (feedItem.storageId === feedData.storageId) + return { ...feedData, isPrivate: !feedData.isPrivate } + return feedItem + }), + ) + + setCurrentUserData((userData) => { + return { + ...userData, + feed: userData.feed.map((feedItem) => { + if (feedItem.storageId === feedData.storageId) + return { ...feedData, isPrivate: !feedData.isPrivate } + return feedItem + }), + } + }) + + handleThreeDotMenuClick?.() + + // TODO: 이 부분을 API 요청으로 대체하고, 요청 후 response에서 새로 바뀐 리스트를 반환해 + // 해당 리스트로 다시 recoil을 set해주도록 수정하기 + updateDoc(firestorePersonalRef, { + feed: arrayRemove(feedData), + }).then(() => { + updateDoc(firestorePersonalRef, { + feed: arrayUnion({ ...feedData, isPrivate: !feedData.isPrivate }), + }) + }) + + updateDoc(firestoreImageAllRef, { + feed: arrayRemove(feedData), + }).then(() => { + updateDoc(firestoreImageAllRef, { + feed: arrayUnion({ ...feedData, isPrivate: !feedData.isPrivate }), + }) + }) + } + + return { + EditToFireStore, + uploadToStorage, + handleDeleteFeed, + handlePrivateToggle, + } +} diff --git a/firebase-practice/src/lib/useWindowSize.tsx b/firebase-practice/src/lib/hooks/useWindowSize.ts similarity index 91% rename from firebase-practice/src/lib/useWindowSize.tsx rename to firebase-practice/src/lib/hooks/useWindowSize.ts index 818df0e..c2f8e0c 100644 --- a/firebase-practice/src/lib/useWindowSize.tsx +++ b/firebase-practice/src/lib/hooks/useWindowSize.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react" -import debounce from "./debounce" +import debounce from "../debounce" // TODO: 전역상태로 바꿔보기 export default function useWindowSize() { diff --git a/firebase-practice/src/ui.tsx b/firebase-practice/src/ui.tsx index 06cb6c0..addaed2 100644 --- a/firebase-practice/src/ui.tsx +++ b/firebase-practice/src/ui.tsx @@ -1,5 +1,6 @@ import { authService, DBService } from "@FireBase" import { darkModeState, userDataState } from "@share/recoil/recoilList" +import axios from "axios" import { arrayRemove, arrayUnion, @@ -8,7 +9,7 @@ import { updateDoc, } from "firebase/firestore" import Link from "next/link" -import { useRecoilValue } from "recoil" +import { useRecoilValue, useRecoilState } from "recoil" import styled from "styled-components" type FlexBoxProperty = { @@ -135,22 +136,24 @@ export const CustomH4Light = TextStyle.CustomH4Light export const CustomH5Light = TextStyle.CustomH5Light export const CustomH6Light = TextStyle.CustomH6Light -type HeartProps = { storgateId: string } +type HeartProps = { storageId: string } -export function HeartIcon({ storgateId }: HeartProps) { - const handleLike = async () => { - const likeRef = doc(DBService, "like", storgateId) - await updateDoc(likeRef, { - likerList: arrayUnion(authService.currentUser?.uid), - }).catch(async (error) => { - if (error.code === "not-found") { - await setDoc(likeRef, { - likerList: [authService.currentUser?.uid], - }) - } +export function HeartIcon({ storageId }: HeartProps) { + const [currentUser, setCurrentUser] = useRecoilState(userDataState) + + const handleLike = () => { + setCurrentUser((user) => ({ + ...user, + likeFeedIds: [...(user.likeFeedIds ?? []), storageId], + })) + + axios.put(`/api/like`, { + userId: currentUser.info.userId, + storageId, }) } const isDarkMode = useRecoilValue(darkModeState) + return ( { - await updateDoc(likeRef, { - likerList: arrayRemove(authService.currentUser?.uid), - }) + setCurrentUser((user) => ({ + ...user, + likeFeedIds: (user.likeFeedIds ?? []).filter( + (feedId) => feedId !== storageId, + ), + })) + + axios.delete( + `/api/like?userId=${currentUser.info.userId}&storageId=${storageId}`, + ) } return (