트위터(React, TypeScript, Firebase, Vite)
21. User's Timeline
본시모니
2025. 4. 22. 10:38
profile.tsx
import styled from "styled-components";
import { auth, db, storeage } from "./firebase"
import { useEffect, useState } from "react";
import { getDownloadURL, ref, uploadBytes } from "firebase/storage";
import { updateProfile } from "firebase/auth";
import { collection, getDocs, limit, orderBy, query, where } from "firebase/firestore";
import { ITweet } from "../components/timeline";
import Tweet from "../components/tweet";
const Wrapper = styled.div`
display: flex;
align-items: center;
flex-direction: column;
gap: 20px;
`;
const AvatarUpload = styled.label`
width: 80px;
overflow: hidden;
height: 80px;
border-radius: 50%;
background-color: #1d9bf0;
cursor: pointer;
align-items: center;
svg{
width: 50px;
}
`;
const AvatarImg = styled.img`
width : 100%;
`;
const AvartarInput = styled.input`
display: none;
`;
const Name = styled.span`
font-size: 22px;
`;
const Tweets = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`;
export default function Profile(){
const user = auth.currentUser;
const [avartar, setAvartar] = useState(user?.photoURL);
const [tweets, setTweets] = useState<ITweet[]>([]);
const onAvartarChage = async (e : React.ChangeEvent<HTMLInputElement>) => {
const {files} = e.target;
if(!user){
return;
};
if(files && files.length === 1){
const file = files[0];
const locationRef = ref(storeage, `avartars/${user?.uid}`);
const result = await uploadBytes(locationRef, file);
const avartarUrl = await getDownloadURL(result.ref);
setAvartar(avartarUrl);
await updateProfile(user, {
photoURL : avartarUrl
});
};
};
const fetchTweets = async() => {
const tweetQuery = query(
collection(db, "tweets"),
where("userId", "==", user?.uid),
orderBy("createAt", "desc"),
limit(25)
);
const snapshot = await getDocs(tweetQuery);
const tweets = snapshot.docs.map((doc)=>{
const { tweet, createAt, userId, username, photo } = doc.data();
return {
tweet,
createAt,
userId,
username,
photo,
id: doc.id
};
});
setTweets(tweets);
};
useEffect(()=>{
fetchTweets();
},[]);
// https://heroicons.dev/?search=person
return <Wrapper>
<AvatarUpload htmlFor="avartar">
{Boolean(avartar) ? <AvatarImg src={avartar ?? ""}/>
: <svg data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"></path>
</svg>}
</AvatarUpload>
<AvartarInput onChange={onAvartarChage} id = "avartar" type = "file" accept = "image/*"></AvartarInput>
<Name>
{user?.displayName ?? "Anonymous"}
</Name>
<Tweets>
{tweets.map((tweet)=>(
<Tweet key={tweet.id}{...tweet}></Tweet>
))}
</Tweets>
</Wrapper>
}
프로필 페이지가 로드되면 Console에 오류가 나타나는데 해당 링크로 가면
Firebase에서 인덱스를 설정할 수 있다.
nwitter-reloaded.z01
19.53MB
nwitter-reloaded.z02
19.53MB
nwitter-reloaded.z03
19.53MB
nwitter-reloaded.z04
19.53MB
nwitter-reloaded.z05
19.53MB
nwitter-reloaded.zip
15.61MB