프로젝트 회고 - 또로그

Sulhwa Choi·2023년 2월 6일
2
post-thumbnail

🏡 또로그

  • 또로그는 소소한 일상들, 기억하고 기록하고 싶은 것들을 공유하고, 이웃들과 소통할 수 있는 블로그입니다.☕︎︎‎𓂃 𓈒𓏸

⏰ 개발 기간

﹣ 2023.01.03~2023.02.06 (기획+구현)

🌎 배포

⚒️ 기술 스택

[Front-end] - Typescript, React, JavaScript, Router, Sass, Figma(디자인)
[Back-end] - Node.js, Express, Mysql, Typescript
[Communication] - Trello, Discord

🙋🏻‍♀️ 내가 담당한 부분

메인페이지

  1. 잔디 기능
  2. 날씨 기능
  3. 전체 글 리스트 (페이지네이션)
  4. 이웃 새글 리스트
  5. 주제 드롭다운 카테고리

팔로우

  1. 팔로우, 팔로잉 기능


메인페이지

swiper를 이용하여 잔디와 날씨기능을 캐러셀로 구현하였고, 로그인 하지 않았을 시에는 날씨부분만 보여지도록 구현하였습니다.


(1) 잔디 기능

메인페이지의 잔디는 현재 년도의 1월 1일부터 12월 31일까지를 보여주고, 해당 날짜에 블로그 포스팅을 했을 때 잔디에 색이 들어가도록 하였습니다.

  const now = new Date(); //현재 날짜
  const year = now.getFullYear(); //현재 년도
  const start = new Date(year + '-01-01'); //시작일은 현재 년도의 1월 1일
  const timeDiff = now.getTime() - start.getTime(); 
  const day = Math.floor(timeDiff / (1000 * 60 * 60 * 24) + 1); ////1월1일부터 오늘날짜까지의 경과일계산

const [postingData, setPostingData] = useState<postDataInterface[]>([]);
////유저의 데이터에 1월1일과 12월 31일 데이터가 있는지 확인  
const dayCheck1 = postingData.some(
    (data) => data.date === year + '-01-01' && year + '-12-31'
  ); 
  const dayCheck2 = postingData.some((data) => data.date === year + '-01-01');
  const dayCheck3 = postingData.some((data) => data.date === year + '-12-31');

  const firstDate = {
    date: '2023-01-01',
    count: 0,
    level: 0,
  };

  const lastDate = {
    date: '2023-12-31',
    count: 0,
    level: 0,
  };

//1년 범위로 보여주기 위한 데이터 추가
  if (dayCheck1 === false) {
    postingData.unshift(firstDate);
    postingData.push(lastDate);
  } else if (dayCheck2 === false) {
    postingData.unshift(firstDate);
  } else if (dayCheck3 === false) {
    postingData.push(lastDate);
  }

1년단위의 잔디를 보여주기 위해 1월1일과 12월 31일의 범위를 지정해주었고, 유저가 블로그를 시작한 경과일을 보여주었습니다.

(2)날씨 기능

새벽

아침



노을 지는 오후



//현재 시간에 맞추어 (새벽,아침,낮,노을,밤) 으로 저장
useEffect(() => {
    const date = new Date();
    const hour = date.getHours();
    setCurrentHour(hour);

    if (hour >= 4 && hour <= 6) {
      setTime('dawn');
    } else if (hour >= 7 && hour <= 12) {
      setTime('morning');
    } else if (hour >= 13 && hour <= 15) {
      setTime('afternoon');
    } else if (hour >= 16 && hour <= 17) {
      setTime('dusk');
    } else {
      setTime('night');
    }
    getWeather();
  }, [currentHour]);

//현재 위치정보 이용 동의 여부와 , 현재 위치의 현재 날씨 정보를 가져옴
  const getWeather = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition((position) => {
        const lat = position.coords.latitude;
        const lon = position.coords.longitude;
        const url = `https://api.openweathermap.org/data/2.5/weather?&lat=${lat}&lon=${lon}&appid=${process.env.REACT_APP_WEATHER_API_KEY}&units=metric`;

        const fetchData = async () => {
          try {
            const weatherResponse = await fetch(url);
            const weather = await weatherResponse.json();
            setWeatherData(weather.weather[0].main);
            //현재 지역
            setLocation(weather.name);
            //현재 위치 날씨
            setWeatherId(weather.weather[0].id);
            //현재 온도
            const temp = Math.floor(weather.main.temp) + '°C';
            setTemperature(temp);
            //위치정보 동의
            setAgree(true);
          } catch (error) {
            console.error('error');
          }
        };
        fetchData();
      });
    } else {
      setAgree(false);
    }
  };

//현재날씨 한글로 변역
  useEffect(() => {
    const translateData = async () => {
      try {
        const weatherTranslateResponse = await fetch('./data/weather.json');
        const weatherTranslate = await weatherTranslateResponse.json();
        setTranslateData(weatherTranslate.data);
      } catch (error) {
        console.error('error');
      }
    };
    translateData();
  }, []);

{translateData.map((data) => {
              const { id, content } = data;
              if (id === weatherId) {
                return (
                  <span key={id} className={css.currentWeather}>
                    {content}
                  </span>
                );
              }
            })}

☀️ 날씨 api는 openWeatherApi를 사용하였습니다.

날씨기능은 현재 날씨 api와 3시간 간격의 날씨 api 두가지는 가져와 fetch 해주었습니다.
현재 시간에 맞추어 배경색상을 변경해주었고, 아이콘 또한 낮과 밤 2가지 버전으로 넣어주어 확인하기 편하도록 구현하였습니다. 현재 날씨를 한글로 보여주고 싶어 번역파일을 목데이터로 만들어 번역해주었습니다.

(2)-1 날씨 배경 애니메이션

눈과 비가 내리는 모습을 위해 조건을 true로 하고 영상을 찍어보았습니다. 이 애니메이션들은 현재 날씨에 맞추어 화면에 나타납니다.

//비내리는 애니메이션 코드
const Rainy = () => {
  const RainDrop = ({ style }: any) => {
    return (
      <p className={css.rainDrop} style={style}>
        💧
      </p>
    );
  };

  const makeRainDrop = () => {
    let animationDelay = '0s';
    let fontSize = '5px';
    const arr = Array.from({ length: 50 }, (v, i) => i);

    return arr.map((el, i) => {
      animationDelay = `${-Math.random() * 10}s`; //랜덤 delay
      fontSize = `${Math.floor(Math.random() * 20)}px`; //랜덤 물방울 크기

      const style = {
        animationDelay,
        fontSize,
      };
      return <RainDrop key={i} style={style} />;
    });
  };

  return <div className={css.rainContainer}>{makeRainDrop()}</div>;
};

(3)전체 글 리스트 (페이지네이션)

//타입지정
interface AllPostInterface {
  id: string;
  title: string;
  content: string;
  thumbnailImgUrl: string;
  createdAt: string;
  user: {
    id: number;
    nickname: string;
    profileImgUrl: string;
  };
}
//주제 드롭다운 인터페이스
export interface TopicDataInterface {
  setTopicIdData: Dispatch<SetStateAction<number>>;
  setPagination: Dispatch<SetStateAction<number>>;
}

  //전체글 데이터
 //주제 아이디
  const [topicIdData, setTopicIdData] = useState(0);
  //페이지네이션
  const [pagination, setPagination] = useState(1);
  const [allPostData, setAllPostData] = useState<AllPostInterface[]>([]);
  const [maxCountPage, setMaxCountPage] = useState<number>(1);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          `${process.env.REACT_APP_API_URL}/posts?${
            topicIdData !== 0 ? `topicId=${topicIdData}&` : ''
          }countPerPage=8&pageNumber=${pagination}`,
          {
            headers: requestHeaders,
          }
        );
        const json = await response.json();
        setMaxCountPage(json.maxPage);
        setAllPostData(json.data);
      } catch (error) {
        console.error('error');
      }
    };
    fetchData();
  }, [pagination, topicIdData]);


  const prevPage = () => {
    setPagination(pagination - 1);
  };

  const nextPage = () => {
    setPagination(pagination + 1);
  };

//첫 페이지 일 경우 버튼 안보이도록 
  {pagination === 1 ? null : (
      <FaChevronLeft className={css.chevron} onClick={prevPage} />
  )}
          
 //최대 페이지 수 일 경우 버튼 안보이도록
  {pagination === maxCountPage ? null : (
       <FaChevronRight className={css.chevron} onClick={nextPage} />
  )}

주제가 선택되지 않을 시 모든 주제의 전체글을 보여주고, 주제 선택 시 주제에 해당하는 글들만을 보여줍니다. 화살표 버튼 클릭 시 이전과 다음페이지로 이동을 합니다!

(4)이웃 새글 리스트

이웃이 없을 시

이웃이 있으면 이웃의 글을 보여주고, 주제별로 확인이 가능합니다.

// 팔로잉 여부 확인
  const [newPostData, setNewPostData] = useState<NewPostInterface[]>([]);

useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          `${process.env.REACT_APP_API_URL}/following?myFollowing=true`,
          {
            headers: requestHeaders,
          }
        );
        const json = await response.json();
        if (json.data.length === 0) {
          setBuddyData(false);
        } else {
          setBuddyData(true);
        }
      } catch (error) {
        console.error('error');
      }
    };
    fetchData();
  }, []);

  useEffect(() => {
    const fetchData = async () => {
      const isSelected = topicIdData !== 0;
      try {
        const response = await fetch(
          `${process.env.REACT_APP_API_URL}/posts?myFollowing=true${
            isSelected ? `&topicId=${topicIdData}` : ''
          }`,
          {
            headers: requestHeaders,
          }
        );
        const json = await response.json();
        if (json.data.length === 0) {
          setBuddyPostData(false);
        } else {
          setNewPostData(json.data);
          setBuddyPostData(true);
        }
      } catch (error) {
        console.error('error');
      }
    };
    fetchData();
  }, [topicIdData]);

로그인한 유저가 팔로잉한 유저가 있는지 확인 하였고, 주제를 선택했는지 안했는지에 여부에 따라 fetch를 하였습니다.

(5)주제 드롭다운 카테고리

const DropDown = ({ setTopicIdData, setPagination }: TopicDataInterface) => {
  const token = localStorage.getItem('token');

  const requestHeaders: HeadersInit = new Headers();
  requestHeaders.set('Content-Type', 'application/json');
  if (token) {
    requestHeaders.set('Authorization', token);
  }

  //드롭타운 클릭여부
  const [isOpen, setIsOpen] = useState<boolean>(false);
  const [topic, setTopic] = useState<string>('주제');
  const onToggle = () => setIsOpen(!isOpen);
  const onOptionClicked = (value: string, index: number) => () => {
    setIsOpen(false);
    setTopic(value);
    setTopicIdData(index);
    setPagination(1);
  };
  const [topicData, setTopicData] = useState<TopicInterface[]>([]);
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(`${process.env.REACT_APP_API_URL}/topics`);
        const json = await response.json();
        let topicList = json.data as TopicInterface[];
        topicList.push({ content: '전체', id: 0 });
        setTopicData(json.data);
      } catch (error) {
        console.error('error');
      }
    };
    fetchData();
  }, []);
  
  
  {topicData.map((topic) => {
                const { id, content } = topic;
                return (
                  <li
                    key={id}
                    className={css.topicItem}
                    onClick={onOptionClicked(content, id)}
                  >
                    {content}
                  </li>
                );
              })}

주제 데이터를 fetch를 통해 가져오고 해당 주제를 클릭 하였을 때, value값을 저장해주었습니다.


팔로우

(6) 팔로우, 팔로잉 기능

팔로워 클릭 시 팔로워 리스트가 보이고, 팔로잉 클릭 시 팔로잉 리스트가 보입니다.
인원이 많을 시 scroll을 이용하였고, 팔로잉에서 언팔로우 클릭 시 리스트에서 사라지며, 팔로워에서 내가 팔로우 했는지 안했는지 여부를 확인할 수 있고 언팔로우 시 팔로워버튼이 뜨도록 하였습니다.
다른사람의 팔로우에서 내 계정은 버튼이 안보이도록 하였습니다.
유저의 정보를 클릭 시 해당 유저의 블로그로 이동할 수 있습니다.

//팔로워와 팔로잉 페이지 띄우는 조건
 const onFollowingBtn = () => {
    if (selectFollowingBtn === false) {
      setSelectFollowingBtn(true);
      setSelectFollowerBtn(false);
    } else if (selectFollowingBtn === true) {
      setSelectFollowingBtn(true);
    }
  };

  const onFollowerBtn = () => {
    if (selectFollowerBtn === false) {
      setSelectFollowerBtn(true);
      setSelectFollowingBtn(false);
    } else if (selectFollowerBtn === true) {
      setSelectFollowerBtn(true);
    }
  };


  //팔로우, 언팔로우 버튼
  const follow = (value: string, index: number) => () => {
    fetch(`${process.env.REACT_APP_API_URL}/follow`, {
      method: 'POST',
      headers: requestHeaders,
      body: JSON.stringify({
        targetUsersId: index,
      }),
    })
      .then((res) => res.json())
      .then((result) => {
        if (result.message === 'FOLLOW_SUCCESSFULLY') {
          fetch(`${process.env.REACT_APP_API_URL}/follower/user/${params.id}`, {
            headers: requestHeaders,
          })
            .then((res) => res.json())
            .then((res) => setFollowerData(res.data));
        }
      });
  }; // 팔로우 버튼 클릭하고 다시 데이터 fetch하기

  const unFollow = (value: string, index: number) => () => {
    fetch(`${process.env.REACT_APP_API_URL}/follow/${index}`, {
      method: 'DELETE',
      headers: requestHeaders,
    })
      .then((res) => res.json())
      .then((result) => {
        if (result.message === 'UNFOLLOW_SUCCESSFULLY') {
          fetch(`${process.env.REACT_APP_API_URL}/follower/user/${params.id}`, {
            headers: requestHeaders,
          })
            .then((res) => res.json())
            .then((res) => setFollowerData(res.data));
        }
      });
  }; //언팔로우 버튼 클릭 후 다시 데이터 fetch!

👏 프로젝트 마치는 소감

﹣기획부터 한 프로젝트여서 디자인 부분 또한 많이 신경을 써서 만들었다(날씨 아이콘, 배경, 전체 레이아웃, 팔로우 레이아웃, 로고 등등).그리고 처음으로 타입스크립트를 공부하며 프로젝트를 해보았는데 아직 익숙치 않아 그런지 조금 더 공부를 해야겠다는 생각이 들었다. 부트캠프 수료 후 만든 첫 프로젝트였는데 함께 프로젝트를 해왔던 팀원들과 작업해서 회의뿐만 아닌 소통 모두 수월하고 재미있게 진행했던 것 같다. 중간에 설 연휴로 인해 1주일 조금 안되는 시간 동안은 작업을 할 수 없었어서 기획부터 프로젝트 완성까지 3주정도 소요 된 것 같다. 3주동안 많이 성장하고, 또 많이 즐거웠던 시간들이었다.! 마지막으로 은또 팀원 모두 원하는 곳에 취뽀 성공하길..! 。 ♡ ˙˚ ʚ ᕱ⑅ᕱ ɞ˚˙ ♡ 。

profile
개발 공부 중 〰️ ٩(๑•̀o•́๑)و ✨

0개의 댓글