수수마켓

📎 배포 URL


이메일 로그인 테스트 계정
  • ID : susu@market.com
  • Password : susu2023



1.🎨 프로젝트 소개

수수마켓은 예술작품을 사랑하는 사람들을 위한 SNS/커뮤니티 서비스입니다.

기획 의도

  • 개인의 예술품 및 공예품을 쉽게 사고 팔 수 있습니다.
  • 상품을 판매/구매하지 않아도 자신의 작품을 공유하며 즐거운 SNS 활동을 할 수 있습니다.
  • 다른 사용자를 팔로우하여 소식을 공유할 수 있고 댓글과 좋아요를 통해 소통할 수 있습니다.

개발 동기

  • 자신의 예술품을 손쉽게 사고팔 수 있는 플랫폼을 통해 아마추어들에게 기회 창출
  • 일반 사람들도 문화를 좀 더 가까이 즐길 수 있도록 하자!

(Top)


2.👩‍🎨 24시간이 모자라조 팀원 소개

FE 강윤정 FE 남종태 FE 안나별 FE 양서진
🔗 GitHub
디자인 리더
🔗 GitHub
개발 리더
🔗 GitHub
팀장
🔗 GitHub
노션/정리 책임

(Top)


3.🛠️ 개발 환경 및 기술 스택

3-1. 개발 환경

  • IDE : Visual Studio Code 1.74.2
  • OS : Windows 10
구분 설명
FrontEnd
BackEnd 제공된 API 사용
협업 도구
IDE

3-2. 링크

3-3 Version

react: ^18.2.0
react-router-dom: ^6.12.1
axios: ^1.4.0
styled-components: ^5.3.11
styled-reset: ^4.4.7
react-device-detect: ^2.2.3
lodash: ^4.17.21
prettier: 2.8.8

(Top)


4.🗓️ 개발 일정

🔥 2023-06-02 ~ 2023-06-27

image

팀 미팅, 프로젝트 계획 : 2023-06-02 ~ 2023-06-05
  • 아이스 브레이킹
  • 프로젝트 주제 토의
  • 사용 기술 스택정하기
  • 개인 공부
  • 깃&깃헙 전략 이해
컨벤션 및 api학습을 위한 사전과제(날씨앱) 진행 : 2023-06-08 ~ 2023-06-11
사전과제(날씨앱)을 함으로써 얻었던 이점 기대효과 프로젝트에 사용되는 기능을 미리 사용해봄으로써 직후 1차 기능 구현할 때 도움이 됨
코드 컨벤션 연습
API 비동기 이해
styled-component 학습
날씨앱 레포지토리 https://github.com/24-test-project/weatherapp
요구사항 파악 및 프로젝트 규칙 설립 : 2023-06-08 ~ 2023-06-12
  • 팀 규칙
  • 기획, 디자인
  • prettier 설정
  • 코드 컨벤션 설정
  • git 컨벤션 설정
  • 폴더 구조
  • 공통 컴포넌트 지정
  • GlobalStyle 설정
공통UI 컴포넌트 개발 : 2023-06-12 ~ 2023-06-14
    함께 같이 만들었던 것
  • 에러 메시지
  • 스플래시 화면 개발
  • 로그인 시작 페이지 개발
1차 개발 : 2023-06-14 ~ 2023-06-19
2차 개발 : 2023-06-19 ~ 2023-06-24

팔로우 좋아요 게시물작성(3개까지) 캔버스 댓글

버그 수정 및 유지보수 : 2023-06-24 ~ 2023-06-27
"
추가 구현 및 배포 : 2023-06-26 ~ 2023-06-28
"

(Top)

5.📌 Git Branch 전략

GitBranch

  • 소규모 프로젝트에 맞게 Main, Develop, Feature 세 Branch를 사용하는 전략 사용
  • Merge 대신 Rebase를 사용하여 보기 좋은 커밋 히스토리를 유지함

😆 Git&GitHub 가이드 문서작성

image image image image


(Top)



6.🗓️ 프로젝트 관리 및 진행

📊 프로젝트 진행 상황 관리

  • 🔘 GitHub Issues
    • 간편한 이슈 생성을 위해 이슈 템플릿을 만들어 사용했습니다.
    • 이슈 템플릿으로 어떤 이슈인지, 어떤 페이지에 해당하는 지, 구현 해야 하는 내용이 무엇인지를 적도록 했습니다.
    • 팀원이 현재 어떤 작업을 진행하고 있는지를 바로 알 수 있어 의사소통 비용을 줄일 수 있었습니다. image
  • 🗂️ GitHub Projects
    • 칸반 보드를 사용해 프로젝트 진행 상황을 한 눈에 확인할 수 있어 일정을 관리하기 수월했습니다. image

TOP 🔼


🚀 GitHub Action - 브랜치 생성 자동화

image

  • Create Issue Branch

  • 이슈를 생성하면 GitHub Action으로 해당 이슈에 해당하는 브랜치가 자동으로 생성되도록 설정하여 브랜치명을 고민하고 브랜치를 생성하는 시간을 줄였습니다.

    • 브랜치 자동화 설정 상세 내용
  • 예) 자동 생성된 브랜치를 pull 하고 git checkout -t origin/feat/issue-81하여 해당 브랜치로 이동합니다.

TOP 🔼


📐 컨벤션

팀원 간의 원활한 소통과 협업을 위해 커밋 컨벤션과, 코드 컨벤션을 만들어 이를 따랐습니다.

🔗 커밋 컨벤션

  • 다양한 사례를 참고하여 프로젝트에서 주로 쓰일 것 같은 커밋 유형을 간추려 컨벤션으로 지정했습니다.

    1. 커밋 유형 지정
        - 커밋 유형은 영어로 작성하며, 첫 글자를 대문자로 합니다
        - 커밋 유형
        - Feat : 새로운 기능, 특징 추가
        - Fix : 수정, 버그 수정
        - Docs : 문서에 관련된 내용, 문서 수정
        - Style : 스타일링
        - Refactor : 리팩토링
        - Test : 테스트 코드 수정, 누락된 테스트를 추가할 때, 리팩토링 테스트 추가
    	- Remove : 파일을 삭제하는 작업만 수행한 경우
    	- Comment : 필요한 주석 추가 및 변경
    	- Rename : 파일 혹은 폴더명을 수정하거나 옮기는 작업만인 경우
    	- init : 초기 파일 설정
        - Chore : 빌드 업무 수정, 패키지 매니저 수정
    
    🧾 2. 커밋 메시지는 제목 & 본문으로 구성합니다.
    
        git commit -m "Feat: 로그인 기능 구현 #13 //제목
            - 로그인 유효성 검사 //본문
            - 로그인 정보 서버로 전송" //본문
    
    👆 3. 한 커밋에는 한 가지 문제만 담습니다.
    

🔗 코드 컨벤션

  • 리액트 코딩에 주로 쓰이는 컨벤션을 참고하여 저희 조만의 코드 컨벤션을 만들었습니다.

  • 문자열 처리 시 쌍따옴표/홑따옴표의 사용, 혹은 문장 끝 세미콜론의 사용여부와 같은 개인적 취향이 반영될 수 있는 항목들의 경우에는 사전 설문을 통해 다수결에 따라 지정했습니다.

    🛼 컴포넌트로 분리된 파일은 PascalCase으로 작성합니다.
    
    🐫 컴포넌트가 아닌 파일, 함수명, 변수명은 camelCase로 작성합니다.
    
    💄 다른 스타일 시트 파일(Styled-components)은, 스타일 시트 적용할 파일명 .style.js를 붙여주고, 앞글자는 소문자로 합니다. (확장자는 .js)
    
    🐫 함수명, 변수명은 camelCase로 작성합니다.
    
    ❓ 만약 변수에 할당되는 값이 boolean인 경우에는 is를 접두사로 붙입니다.
    
    🔠 상수는 대문자로만 작성합니다.
    
    🔢 컴포넌트 파일 내 import 순서는 모듈 → 컴포넌트 → 스타일컴포넌트 순으로 합니다.
    
    💬 문자열을 처리할 때는 쌍따옴표를 사용하도록 합니다.
    
    🔚 문장이 종료될 때는 세미콜론을 붙여줍니다.
    
    👆 가독성을 위해 한 줄에 하나의 문장만 작성합니다.
    
    ✏️ 주석은 설명하려는 구문에 맞춰 들여쓰기 합니다.
    
    🧮 연산자 사이에는 공백을 추가하여 가독성을 높입니다.
    
    📠 콤마 다음에 값이 올 경우 공백을 추가하여 가독성을 높입니다.
    

TOP 🔼


7.👥 협업 문화

모두가 참여하는 회의

노션을 이용하여 함께 의견을 나누고 실시간으로 의견을 텍스트화 시켜 더 활발한 회의를 진행 서로의 의견이 묻히지 않도록 각 팀원의 의견을 집중해서 들었고 팀원 모두가 이해했는지 항상 물어봄 1차개발, 2차개발을 나누어 진행하였는데 각 개발단계에서 끝내지 못한 팀원이 있다면 모두 함께 모여 도와줌


💬 디스코드 및 노션활용

  • 디스코드의 쓰레드 및 화면공유 기능을 이용하여 각자의 개발진전도와 구현상황을 편리하게 공유했습니다.
  • 노션 팀 스페이스 페이지를 사용하여 업무 및 요구사항, 일정을 상시 확인할 수 있었습니다.

image


💡 팀 규칙

  1. 코드 리뷰를 통해 함께 성장해나가기❤️
  2. 프로젝트 끝날 때까지 긍정적인 생각만 하기❤️
  3. 코드 컨벤션에 맞춰 작성하기❤️
  4. 문제가 생기면 혼자 끙끙 앓지말고 꼭 팀원들과 상의하기❤️
  5. 회의에 참석하지 못하는 경우가 생기면 사전에 얘기하기❤️
  6. 저희 친해져요❤️
  7. 생각나는 아이디어가 있으면 바로바로 이야기하기❤️
  8. 질문하면 친절하게 대답해주기❤️
  9. 주간 마무리회의 때 힘들었던 점, 고민 나누는 시간 가져요 !❤️
  10. 프로젝트 관련 공지사항 잘 확인하기❤️
  11. 공지사항이 있으면 이모지 남겨주기

팀규칙을 정함으로 소속감 및 팀 정체성을 강화했습니다.

✨ 이슈 관리 프로세스

- 작업 전 GitHub Issues 등록

  • 아무리 작은 작업이라도 수월한 이슈 추적을 위해 이슈 반드시 등록 후 작업 진행 (작업 하나당 이슈 하나) image

  • 컨벤션 통일을 위해 이슈 템플릿 사용

🧚 이슈 해결 후 Pull Request 생성

  • 컨벤션 통일을 위해 PR 템플릿 사용
  • 팀원 2명 이상의 승인을 받아야 머지 가능
  • 코드리뷰 후에 PR 승인

🕵 이슈 진행 상황 관리

- GitHub Projects를 이용한 칸반 보드

image

  • 이슈 진행 상황을 한 눈에 볼 수 있도록 칸반 보드 형태로 시각화


(Top)


8.🧙 구현 기능 및 담당자


(Top)


9.📃 페이지 캡쳐

1) 홈

시작 화면 회원가입 페이지 로그인 페이지
splash 회원가입 로그인
피드 페이지 검색 페이지 404 페이지
피드 검색 404

2) 채팅

채팅 목록 페이지

3) 게시글

게시글 상세 페이지 게시글 작성 페이지 게시글 수정 페이지
게시물상세 게시물작성 게시물수정
게시글 삭제 게시글 신고 댓글 기능
최종_게시글삭제 최종_게시글신고 최종_게시물댓글

4) 프로필

마이 프로필 페이지 프로필 수정 페이지 팔로워/팔로잉 페이지
앨범형리스트형 팔로우페이지

5) 판매 상품

상품 등록 페이지 상품 수정 페이지 상품 삭제 페이지
상품 등록 상품 수정 상품 삭제
상품 상세페이지

6) 캔버스

캔버스
캔버스

(Top)


10.🖥️ 차별화 코드 설명

1 ) CustomAxios

  • CustomAxios : axios를 커스텀 하여 반복되어 들어가는 헤더 값과 baseUrl를 설정하여 axios 사용 시 헤더값과 baseUrl를 생략하도록 하였습니다.
  • 이를 통해 코드를 단축 시킬수 있었고, 편리하게 axios를 사용할 수 있었습니다.
import axios from "axios";

export const customAxios = axios.create();

customAxios.interceptors.request.use(
  (config) => {
    const accessToken = localStorage.getItem("accessToken")||"";
    config.baseURL = process.env.REACT_APP_BASE_URL;
    config.headers.Authorization = accessToken
      ? `Bearer ${accessToken}`
      : "";

    return config;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  },
);

2 ) loadsh 라이브러리 debounce 기능을 이용한 검색 최적화

  • 기존 검색시 onChange 이벤트에서 input의 변화가 감지될 때 마다 API요청이 발생하여 불필요한 API 요청이 발생합니다.
  • debunce을 통해 설정한 시간이 경과한 이후 이벤트가 호출되지 않을 때 이벤트를 한 번만 호출하게 해주어 불필요한 API 호출을 막아줍니다.
  • lodash debounce는 첫 번째 인자로 실행할 함수, 두 번째 인자로 시간을 받습니다.
// debounce패턴 적용한 유저 검색 함수
 const handleSearch = useCallback(
    debounce(async (value) => {
      try {
        const response = await customAxios.get(
          `/user/searchuser/?keyword=${value}`,
        );
        setUserList(response.data);
      } catch (error) {
        console.error(error);
      }
    }, 500), // 500ms 동안 입력이 없다면 함수실행
    [],
  );
  • 구현 화면

    • debounce 적용 전

    디바운싱적용전

    • debounce 적용 후

    디바운싱적용후


3 ) img Validation

  • imgValidation 함수를 만들어서 이미지 사용 페이지에 공통으로 사용 가능하도록 적용하였습니다.
  • imgValidation을 함수로 만들어서 코드 중복사용을 피하고 imgValidation 하나로 통일하였습니다.
  • 이미지 파일이 없거나 API에서 제공하는 이미지 형식이 아니거나 크기가 초과한다면 이미지를 올리지 못하도록 제한하고, 경고창이 출력되도록 하였습니다.
export const imgValidation = (file) => {
  const reg = /(.*?)\.(jpg|jpeg|png|gif|bmp|tif|heic)$/;
  // 파일 확인
  if (!file) {
    return false;
  }
  // 파일 사이즈 확인
  if (file.size > 1024 * 1024 * 10) {
    alert("이미지 파일의 크기를 초과하였습니다.(최대 10MB)");
    return false;
  }
  // 이미지 지원 형식 확인
  if (
    !reg.test(file.name)
  ) {
    alert(
      "이미지 형식을 확인해 주세요!\n(지원형식 : .jpg,.gif, .png,.jpeg, .bmp,.tif, *.heic)"
    );
    return false;
  }
  // 모두 만족 한다면 true 반환
  return true;
};

  • 구현 화면

    • 프로필 이미지 변경에 imgValidation 적용

    imgvalidation


4 ) 무한스크롤

  • 무한스크롤을 이용하여 데이터를 일부만 가져와 서버의 부담을 줄이고 로딩속도를 개선 하였습니다.
  • react-intersection-observer 라이브러리를 이용하여 무한스크롤을 구현하였습니다.
  • react-intersection-observer 라이브러이의 useView()의 ref값을 관찰요소 ref값에 넣으면 관찰요소를 지정할 수 있습니다.
  • 만약 관찰요소가 화면 출력되면 inView true로 화면에서 사라진다면 false로 변경되게 됩니다.
  • hasMore를 통해 다음 데이터가 없다면 api 요청을 일어나지 않게 조건을 걸어 주었습니다.
  • hasMore는 현재 API에서 받아온 post의 length와 limt가 같은지 비교하여 다음 데이터가 있는지 판단해줍니다.
  • API 요청을 보낼 때 마다 skip 값을 limit 값만 만큼 증가 시켜주어 다음 데이터를 받아올수 있도록 하였습니다.
export default function ProfilePost({
  onClickButton,
  settingPostModalProps,
  closeModal,
  userData,
  isFeed,
}) {
  const [postData, setPostData] = useState([]);
  const [isNonePostData, setIsNonePostData] = useState(false);
  const [isGallery, setIsGallery] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [ref, inVeiw] = useInView();
  const limit = 5;
  const [skip, setSkip] = useState(0);
  const [hasMore, setHasMore] = useState(true);
  const navigate = useNavigate();

  // 게시물 정보를 받아옴
  const fetchPostData = async () => {
    try {
      const response = await customAxios(
        isFeed
          ? `post/feed?limit=${limit}&skip=${skip}`
          : `post/${userData.accountname}/userpost?limit=${limit}&skip=${skip}`,
      );
      const data = isFeed ? response.data.posts : response.data.post;
      setPostData((prev) => [...prev, ...data]);
      setHasMore(data.length === limit);
      setSkip((prev) => prev + limit);
      setIsLoading(false);
      if (data.length === 0 && skip === 0) {
        setIsNonePostData(true);
      } else {
        setIsNonePostData(false);
      }
    } catch (error) {
      console.log(error);
    }
  };

  useEffect(() => {
    if (skip === 0 && !inVeiw) {
      fetchPostData();
    }
    if (hasMore && inVeiw) {
      fetchPostData();
    }
  }, [inVeiw]);

  return (
    // isFeed를 통해 profile 페이지에서 출력될 요소와 feed 페이지에서 출력될 요소를 구분
    <ProfilePostWrapper>
           (중략)...
            <ProfilePostUl>
              {postData.map((post) => (
                <ProfilePostList
                  key={post.id}
                  onClickButton={onClickButton}
                  settingPostModalProps={settingPostModalProps}
                  closeModal={closeModal}
                  reFetchPostData={fetchPostData}
                  post={post}
                  setPostData={setPostData}
                  isFeed={isFeed}
                  userData={userData}
                />
              ))}
              <div ref={ref}></div>
            </ProfilePostUl>
          )}
        </>
      )}
    </ProfilePostWrapper>
  );
}
  • 구현 화면

무한스크롤


11.💫 느낀점

강윤정

리액트 프로젝트가 처음이었고 제 실력에 대한 불안감이 컸기 때문에 프로젝트에 대한 두려움이 컸습니다. 하지만 개발 시작과 동시에 거의 매일 오전부터 새벽까지 디스코드에 함께 접속해서 정말 열심히 코딩하고 서로 도왔던 저희 팀원 분들 덕분에 실력 향상은 물론 프로젝트를 원하는 목표대로 잘 진행할 수 있었던 것 같습니다. 이번 프로젝트를 통해 협업은 물론 github부터 react까지 정말 많은 것을 배워갑니다. 저희 조를 비롯하여 모든 멋사 분들 수고하셨습니다!


남종태

저는 프로젝트가 시작 될 때 협업이 처음이고 git 사용이 서툴러서 팀 프로젝트를 잘 할 수 있을지 걱정이 많았는데, 좋은 팀원들을 만나서 무사히 프로젝트를 마무리 할 수 있었던 것 같습니다. 힘들 때는 서로 응원해주고, 모르는 것이 있을 때는 함께 공유하면서 더 많이 성장 할 수 있었던 것 같습니다. 이번 팀프로젝트를 통해서 협업이 어떻게 진행되고, 어떻게 이루어지는 자세히 알 수 있었고, git 사용에 대해서도 많이 배울 수 있었습니다. 이번 팀프로젝트를 계기로 다음 팀 프로젝트가 있다면 더욱 성장한 모습으로 프로젝트를 진행할 수 있겠다고 생각합니다. 저희 24조 팀원, 멋사 모두 고생하셨고, 정말 감사합니다.


안나별

리액트의 리자도 모르던 제가 팀의 리더를 맡게되어서 걱정이 많았었지만 좋은 팀원 분들과 함께 프로젝트를 진행해서 부담없이 다양한 협업시도와 개발을 해봤던 것 같습니다. 프로젝트 과정동안 새롭게 배운것만 몇 개인지 모르겠습니다. 프로젝트 시작 전만해도 협업에 대해 두려움이 많았던 저지만 앞으로는 프론트엔드 협업에 대해 두렵지 않을 것 같네요.


양서진

프로젝트 시작 전에는 협업에 대한 경험이 적었기에, 제가 잘할 수 있을까 하는 생각이 많이 들었습니다. 그래서 소통에 대해 가지고 있던 막연한 두려움이 있었지만, 저희 조원 분들이 서로 힘든 점에 대해 이해해 주시고, 누군가가 어려워한다면 함께 해결해주려고 하는 부분 때문에 저도 크게 성장할 수 있는 기회가 된 것 같아서 좋았던 것 같습니다. 또한, 리액트, axois, styled-component와 같이 공부해보고 싶었던 부분에 대해 몇주간 몰입해서 사용해볼 수 있었던 점도 제 개발스킬 향상에 도움이 된 것 같습니다. 마지막으로 저희 24조 조원들 프로젝트기간동안 항상 같이 밤새면서 고생해서 수고했단 말 드리고 싶고, 멋사 5기 분들 모두 수고 너무 많으셨습니다!

(Top)


12. 프로젝트 발표 자료


(Top)


13.🗂️ 프로젝트 구조

  • src/components/ : 서비스에서 사용하는 컴포넌트 (캐러셀, 공통 컴포넌트, 공통 레이아웃)
  • src/commons/ : 공통컴포넌트 중 UI와 관련된 파일
  • src/units : 재사용을 위해 분리한 유틸 파일
  • src/context/ : 전역 데이터를 공유하기 위해 정의한 Context 파일
  • src/hooks/ : 재사용을 위해 분리한 Custom Hook
  • src/img/ : 서비스에서 사용하는 에셋 파일 (폰트, 아이콘, 이미지)
  • src/library/ : 일반적으로 재사용 가능한 파일(customAxios, imgValidation)
  • src/pages/ : 공통 컴포넌트를 사용해 만든 페이지
  • src/routes/ : 페이지 라우팅을 위한 파일
📦susuMarket
 ┣ 📂public
   ┗ 📜index.html
 ┣ 📂src
   ┣ 📂components
   ┃ ┣ 📂commons
   ┃ ┃ ┣ 📂button
   ┃ ┃ ┣ 📂confirmModal
   ┃ ┃ ┣ 📂dataInput
   ┃ ┃ ┣ 📂dateFormat
   ┃ ┃ ┣ 📂errorMessage
   ┃ ┃ ┣ 📂menuBar
   ┃ ┃ ┣ 📂newTopHeader
   ┃ ┃ ┣ 📂postModal
   ┃ ┃ ┣ 📂topButton
   ┃ ┃ ┗ 📂topHeader
   ┃ ┗ 📂units
   ┃ ┃ ┗ 📂profile
   ┃ ┃ ┃ ┣ 📂profileInfo
   ┃ ┃ ┃ ┣ 📂ProfilePost
   ┃ ┃ ┃ ┗ 📂ProfileProduct
   ┣ 📂context
   ┣ 📂hook
   ┣ 📂img
   ┣ 📂library
   ┣ 📂pages
   ┃ ┣ 📂chat
   ┃ ┃ ┣ 📂chatList
   ┃ ┃ ┗ 📂chatRoom
   ┃ ┣ 📂drawing
   ┃ ┣ 📂feed
   ┃ ┃ ┣ 📂post
   ┃ ┃ ┃ ┣ 📂postDetail
   ┃ ┃ ┃ ┣ 📂postEdit
   ┃ ┃ ┃ ┣ 📂postUpload
   ┃ ┃ ┣ 📂product
   ┃ ┃ ┃ ┣ 📂productDetail
   ┃ ┃ ┃ ┣ 📂productEdit
   ┃ ┃ ┃ ┗ 📂productUpload
   ┃ ┃ ┗ 📂search
   ┃ ┣ 📂login
   ┃ ┃ ┣ 📂loginEmail
   ┃ ┣ 📂notFound
   ┃ ┣ 📂profile
   ┃ ┃ ┣ 📂follow
   ┃ ┃ ┃ ┣ 📂followers
   ┃ ┃ ┃ ┗ 📂following
   ┃ ┃ ┣ 📂profileEdit
   ┃ ┃ ┗ 📂userProfile
   ┃ ┣ 📂signup
   ┃ ┃ ┣ 📂profileSetting
   ┃ ┃ ┗ 📂userAccount
   ┃ ┗ 📂splash
   ┣ 📂routes
   ┣ 📜App.js
   ┣ 📜GlobalStyle.js
   ┗ 📜index.js

(Top)