Skip to content

shyoun94/Food-Zip

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FOODZIP

image

배포 사이트 : https://foodzip.netlify.app/

🖥 프로젝트 소개

FOODZIP은 식사를 즐기며 맛있는 음식과 훌륭한 식당을 찾는 이들을 위한 식당 공유 커뮤니티입니다. 이 프로젝트는 사용자들이 맛있는 음식을 찾고 공유하며 즐거운 식사 경험을 공유하는 데 초점을 맞추고 있습니다. 해당 커뮤니티를 통해 사용자들은 원하는 식당을 쉽게 찾고, 다양한 음식에 대한 정보를 얻을 수 있으며, 같은 관심사를 가진 사람들과 소통할 수 있습니다. 함께 맛있는 음식과 즐거운 식사를 즐길 수 있는 FOODZIP에 여러분을 초대합니다!

개발 기간

개발기간

🧑‍🤝‍🧑 어? 금지의 조원 소개


이지수
조장


김율이
조원


윤선호
조원


이은주
조원

역할 분담

역할분담

팀 문화

  • 투명한 정보 공유 : 협업에 있어 서로의 작업에 대한 이해를 깊게 합니다.

  • 신속한 의견 조율 : 실행 속도와 책임감이 높아집니다.

  • 진입장벽 낮추기 : 팀 간의 진입장벽을 낮추고, 팀원 모두가 프로젝트에 적극적으로 참여 하는 환경을 만들었습니다.

  • 건강한 팀 분위기 : 원활한 의사소통을 통해 상호 신뢰를 쌓아갑니다. 결과적으로 모든 팀원이 프로젝트를 적극적으로 참여하는 분위기를 조성했습니다.

  • 협업의 효율성 극대화 : 피드백 과정을 통해 작업 과정을 개선하고 팀원들끼리 서로를 환기할 수 있습니다.

🛠 테크 스택

👨‍👩‍👧‍👧 협업 툴

📄 페이지 소개

스플래시 회원가입
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 앱 시작화면
- 자동 로그인 시 홈피드로 이동
- 로그인 안 한 경우 웰컴 페이지 이동
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 이메일, 비밀번호 유효성 검사
- 유효성 검사 통과 시 버튼 활성화
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
프로필 설정 로그인
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 계정 ID 유효성 검사
- 유효성 검사 통과 시 버튼 활성화
- 이미지 미설정 시 기본 프로필 이미지 적용
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 이메일, 비밀번호 모두 입력 시 버튼 활성화
- 로컬스토리지에 토큰 저장
- 로그인 성공 시 홈피드로 이동
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
검색 팔로우 페이지
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 유저 검색
- 클릭 시 프로필로 이동
- debounce 이용
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 유저 팔로우, 언팔로우 기능
- 유저 프로필 페이지 이동
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
채팅 업로드 페이지
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 채팅 페이지 구현
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 게시물 업로드 기능
- 이미지 압축 기능
- 이미지 3장 업로드 및 드래그 앤 드랍 기능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
게시물 수정 게시물 삭제
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 게시물 이미지 및 내용 변경 가능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 게시물 삭제 기능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
프로필 페이지 프로필 수정 페이지
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 게시글 리스트형, 앨범형
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 프로필 정보를 유지한 상태로 수정 페이지 이동
- accountname 유효성 검사
- validation을 통과해야 수정 가능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
추천맛집 카카오맵
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 추천맛집 등록, 수정, 삭제 기능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 카카오맵 API를 이용하여 추천 맛집 위치 확인
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
게시물 상세 페이지 랜덤 음식 추천
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 댓글 등록, 삭제, 신고 기능
- 좋아요 기능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 로고 클릭 시 랜덤 음식 추천 기능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
SNS 공유하기 로그아웃
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 추천맛집 식당 SNS로 공유 가능
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ
- 로그아웃 시 웰컴 페이지로 이동
- 로컬스토리지에서 토큰 삭제
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ

📌 주요 기능 소개

코드 설명
import useForm react-hook-form 라이브러리 사용을 위해 useForm을 가져옵니다.
useForm useForm 훅을 호출하여 필요한 메소드와 속성을 추출합니다. 이 통해 입력, 제출, 오류 및 유효성을 처리하기 위한 작업을 수행할 수 있습니다. mode: "onChange"를 사용하여 입력 값의 변화를 감지하면서 동시에 유효성 검사를 실행합니다.
setError 폼 컨트롤의 오류 상태를 수동으로 설정 또는 변경할 수 있는 함수입니다.
name: 오류 상태를 설정하려는 폼 컨트롤의 이름.
type: 오류 유형(예: "required", "pattern", "custom" 등).
message: 사용자에게 표시할 오류 메시지.
입력 폼 Register를 통해 value를 제어하고 required와 pattern 을 통해 API 유효성 검사 이전에 패턴 유효성검사를 진행합니다.
validate pattern은 형식을 검증하고 validate는 조건을 검증합다
import { useForm } from "react-hook-form";

const {
    register,
    handleSubmit,
    clearErrors,
    setError,
    getValues,
    formState: { errors, isValid },
  } = useForm({
    mode: "onChange",
    defaultValues: {
      // 초기값
    },
  });
  const checkEmailValid = async email => {
    try {
      const res = await axios.post(

      // 이메일 Validation API

      const reqMsg = res.data.message;
      clearErrors("email");
      if (reqMsg === "이미 가입된 이메일 주소 입니다.") {
        setError("email", {
          type: "manual",
          message: "이미 가입된 이메일 주소 입니다.",
        });
        return false;
      } else {
        clearErrors("email");
        return true;
  return (
    <StyledForm onSubmit={handleSubmit(handleFormSubmit)}>
      <StyledInputContainer>
        <StyledLabel htmlFor="email">이메일</StyledLabel>
        <StyledInput
          {...register("email", {
            required: "이메일은 필수 입력입니다.",
            pattern: {
              value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
              message: "유효한 이메일 주소를 입력하세요.",
            },
          })}
        />
        {errors.email && (
          <StyledError role="alert">{errors.email.message}</StyledError>
        )}
      </StyledInputContainer>
      // 이메일 부분과 동일
            validate: {
              matchesPreviousPassword: value => {
                const { password } = getValues();
                return password === value || "비밀번호가 일치하지 않습니다.";
코드 설명
useEffect
useDebounce
다음은 useEffectuseDebounce를 사용하여 검색 키워드가 업데이트될 때마다 검색 API를 호출하고 결과를 필터링하는 코드입니다.
useDebounce는 일정 시간 동안의 입력이 멈춘 후 API 요청이 될 수 있도록 제어해 불필요한 API 호출을 방지합니다.
useEffect는 디바운스된 키워드가 변경될 때마다 결과 목록을 업데이트합니다. 추가로 조건에 따라 데이터를 걸러내어 불필요한 이미지 로딩과 처리시간을 줄일수 있게 설계했습니다.
  const [debouncedSearchKeyword] = useDebounce(searchKeyword, 300);

  useEffect(() => {
    const fetchData = async () => {
      if (!debouncedSearchKeyword) {
        return;
      } else {

        // 검색 API 코드 부분

          const filteredData = response.data.filter(
            item => !item.image.startsWith("https://mandarin.api.weniv"),
          );
          setSearchListData(filteredData);
        } 
    };
...
    fetchData();
  }, [debouncedSearchKeyword]);
코드 설명
elapsedTime 댓글이 작성된 시간과 현재 시간 사이의 경과 시간을 계산하여 문자열로 반환합니다. 경과 시간은 년, 개월, 일, 시간, 분 등의 단위로 표현되며, 가장 큰 단위부터 계산되며 문자열로 반환됩니다.
const elapsedTime = commentDate => {
    const now = new Date();
    const commentTime = new Date(commentDate);
    const elapsedSeconds = Math.floor((now - commentTime) / 1000);

    const times = [
      { name: "년", seconds: 60 * 60 * 24 * 365 },
      { name: "개월", seconds: 60 * 60 * 24 * 30 },
      { name: "일", seconds: 60 * 60 * 24 },
      { name: "시간", seconds: 60 * 60 },
      { name: "분", seconds: 60 },
    ];

    for (const value of times) {
      const elapsed = Math.floor(elapsedSeconds / value.seconds);

      if (elapsed > 0) {
        return `${elapsed}${value.name} 전`;
      }
    }
    return "방금 전";
  };
코드 설명
const { kakao }= window; 카카오 API에서 제공하는 기능들을 사용할 수 있도록 구현된 객체입니다.
useState, useEffect Map 컴포넌트 초기화, 사용자 위도, 경도에 따른 지도 마커 표시 설정. 상태 유지와 컴포넌트 라이프 사이클에 맞춰 동작하기 위해 사용합니다.
검색 지도 마커 생성 검색된 위치의 마커와 오버레이를 생성하고 관련 클릭 이벤트를 처리합니다.
const { kakao } = window;

const MapTest = () => {
  const [place, setPlace] = useState("");
  const [map, setMap] = useState(null);
  const location = useLocation();
  const data = location.state;
  const recommendName = data.restaurantname;

  useEffect(() => {
    let container = document.getElementById("map");
    let options = { center: new kakao.maps.LatLng(37.5045, 127.049) };
    let kakaoMap = new kakao.maps.Map(container, options);
    setMap(kakaoMap);
  }, []);
  useEffect(() => {
    if (map && recommendName) {
      const ps = new kakao.maps.services.Places();
      ps.keywordSearch(recommendName, placesSearchCB);
      function placesSearchCB(data, status, pagination) {
        if (status === kakao.maps.services.Status.OK) {
          let bounds = new kakao.maps.LatLngBounds();
          for (let i = 0; i < data.length; i++) {
            displayMarker(data[i]);
            bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x));
          }
          map.setBounds(bounds);
        }
      }
      function displayMarker(place) {
        const imageSize = new kakao.maps.Size(45, 45);
        const imageSrc = Marker;
        let markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize);
        let marker = new kakao.maps.Marker({
          // 마커 커스텀
        });
        let content =
          // 지도 Overlay 커스텀
        setPlace(place.road_address_name);
        let customOverlay = new kakao.maps.CustomOverlay({
          position: new kakao.maps.LatLng(place.y, place.x),
          content: content,
          yAnchor: 1,
        });

        kakao.maps.event.addListener(marker, "click", function () {
          customOverlay.setMap(map);
        });
      }
    }
  }, [recommendName, map]);
코드 설명
options 이미지 압축에 사용되는 옵션을 설정합니다. 이미지 최대 크기, 최대 너비 또는 높이, 웹 작업자 사용 여부를 설정할 수 있습니다.
이미지 압축
변환
선택한 파일을 압축하고 이를 미리보기로 표시하며, 데이터 URL로 변환한 후, 이미지를 핸들링하는데 필요한 업로드 함수를 호출합니다.
formDataHandler base64 데이터 URI를 blob으로 변환하고, 이를 다시 File 객체로 변환하는 함수입니다.
import imageCompression from "browser-image-compression";
const options = {
      maxSizeMB: 0.7,
      maxWidthOrHeight: 500,
      useWebWorker: true,
    };
    try {
      const compressedFile = await imageCompression(file, options);
      setBoardImage(compressedFile);
      const promise = imageCompression.getDataUrlFromFile(compressedFile);
      promise.then(result => {
        setUploadPreview(result);
      });
      const reader = new FileReader();
      reader.readAsDataURL(compressedFile);
      reader.onloadend = () => {
        const base64data = reader.result;
        const imageUrl = formDataHandler(base64data);
        onImageUrlChange(file, imageUrl);
        setImgUrl(imageUrl);
      };
    } catch (error) {
      console.log(error);
    }
  };
  const formDataHandler = async dataURI => {
    const byteString = atob(dataURI.split(",")[1]);
    const ab = new ArrayBuffer(byteString.length);
    const ia = new Uint8Array(ab);
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    const blob = new Blob([ab], { type: "image/jpeg" });
    const file = new File([blob], "image.jpg", { type: "image/jpeg" });
    return file;
  };

리팩토링 & 버전업

  • API 분리
  • 전역상태관리(Recoil)를 이용한 모달 관리
  • 무한 스크롤
  • 이미지 스프라이트 기법
  • 웹 접근성 개선
  • sns 공유하기 기능
  • 이미지 최대 3장까지 등록(드래그 앤 드랍으로 순서 변경 가능)
  • 탑버튼
  • 반응형 및 PC 버전 제작 중

🗂️ 폴더 구조

    src
      ├─ App.js
      ├─ components
      │  ├─ Auth
      │  ├─ Chat
      │  ├─ Comment
      │  ├─ common
      │  │  ├─ Button
      │  │  ├─ Header
      │  │  └─ Nav
      │  ├─ Error
      │  ├─ Feed
      │  ├─ FollowItem
      │  ├─ Modal
      │  ├─ Post
      │  │  ├─ ImgPrev
      │  │  ├─ PostEdit
      │  │  ├─ PostItem
      │  │  ├─ PostList
      │  │  └─ StarRating
      │  ├─ Profile
      │  ├─ Search
      │  └─ styles
      ├─ pages
      │  ├─ AuthorPage
      │  │  ├─ Login
      │  │  └─ SignUp
      │  ├─ Chat
      │  ├─ Error
      │  ├─ FollowerList
      │  ├─ Home
      │  ├─ Loading
      │  ├─ Map
      │  ├─ Post
      │  ├─ Profile
      │  ├─ ProfileSetting
      │  ├─ Search
      │  ├─ Splash
      │  └─ Welcome
      └─ routes

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 98.8%
  • Other 1.2%