Skip to content

Stilllee/mogua-fe

Repository files navigation

møgua

image

목차

møgua station

개발기간: 25.01.03 - 25.02.12

FrontEnd Repository

배포 사이트


Frontend

곽정원 이에스더 박태현 정민재
프론트 배포
CI-CD
로그인
랜딩페이지
유저 페이지
프로필 수정
리뷰 활동
찜한 모임
모임 상세
메인페이지
모임 생성

Backend & 디자이너

김기현 김은지
백엔드 디자이너

기술 스택


담당 페이지 소개

페이지 상세 설명 보기

유저 페이지

  • 프로필 정보와 4가지 활동 탭(내 모임/내 리뷰/만든 모임/수강평)으로 구성되어 있습니다.
    • 내 모임: 참여 중인 스터디/과외 목록
    • 내 리뷰: 스터디/과외별로 구분되며, 각각 '작성 가능한 리뷰'와 '작성한 리뷰' 목록 제공
    • 만든 모임: 생성한 스터디/과외 목록
    • 수강평: 과외선생님이 생성한 과외에 대한 리뷰 목록
  • 데이터는 10개씩 무한 스크롤로 자동 로딩되며, 로딩 중에는 스켈레톤 UI를 보여줍니다.
  • 페이지 진입 시 새로운 데이터를 요청하고, 이후 10분간은 탭 전환 시에도 캐시된 데이터를 바로 확인할 수 있습니다.
  • 본인 페이지에서만 프로필 수정과 리뷰 관리가 가능합니다.
  • 과외선생님은 수강평 탭이 추가로 제공됩니다.
마이페이지 (일반유저) 다른 유저페이지 (과외선생님)

유저 프로필 수정 페이지

  • 기존 정보(닉네임, 이메일, 자기소개, 태그)가 기본값으로 표시되며, 이메일을 제외한 모든 정보를 수정할 수 있습니다.
  • 프로필 이미지는 선택 즉시 미리보기로 확인되며, IndexedDB를 활용해 새로고침해도 유지됩니다.
  • 변경사항이 없거나 유효하지 않은 값이 있으면 수정이 제한됩니다.
  • 페이지 이탈 시 확인 모달이 표시되어 실수를 방지합니다.

리뷰 작성/수정

  • 3단계 평점 시스템(그냥 그래요/괜찮아요/추천해요)으로 평가합니다.
  • 평점과 리뷰 내용은 필수로 입력해야 하며, 사진은 1장까지 선택적으로 첨부할 수 있습니다.
  • 수정 시에는 기존 리뷰 정보가 기본값으로 표시되며, 변경사항이 있어야만 수정이 가능합니다.
  • 작성/수정 완료 시 유저 페이지의 리뷰 목록이 자동으로 업데이트됩니다.
리뷰 작성 리뷰 수정

리뷰 삭제

  • 유저 페이지에서 더보기 메뉴를 통해 즉시 삭제할 수 있습니다.
  • 삭제 전 확인 모달을 통해 실수를 방지합니다.
  • 삭제 즉시 리뷰 목록에서 제거되며, 관련 데이터가 자동으로 업데이트됩니다.


데모 영상

아래의 이미지를 클릭하면 데모 영상을 확인할 수 있습니다.


주요 작업

1. API 통신

1.1 API 명세서 설계

명확한 API 명세서를 직접 설계하고 백엔드 개발자와 지속적인 피드백을 주고 받으며 효율적인 협업 환경 구축

예시

API URL, 파라미터 및 응답 구조 작성가능한 리뷰 데이터 구조 작성한 리뷰 데이터 구조

1.2 커스텀 fetch 모듈 개발

fetcher.ts

Next.js와 fetch의 캐싱 옵션을 최대한 활용하기 위해 axios 대신 fetch API를 기반으로 한 재사용 가능한 모듈 개발

// fetcher.ts 핵심 부분

async function fetcher(url: string, method = "GET", data, options = {}) {
  const config = {
    ...options,
    method,
    credentials: "include",
    headers: {
      // FormData 처리
      ...(data instanceof FormData
        ? {}
        : { "Content-Type": "application/json" }),
      ...options.headers,
    },
    // 요청 바디 설정
    ...(data && {
      body: data instanceof FormData ? data : JSON.stringify(data),
    }),
  };

  return fetch(`${process.env.NEXT_PUBLIC_BASE_URL}${url}`, config);
}

// HTTP 메소드별 간편 함수
export const get = (url, options) => fetcher(url, "GET", undefined, options);
export const post = (url, data, options) => fetcher(url, "POST", data, options);
export const patch = (url, data, options) =>
  fetcher(url, "PATCH", data, options);
export const del = (url, options) => fetcher(url, "DELETE", undefined, options);

2. 데이터 관리 전략

2.1 복잡한 구조의 탭 데이터 관리

useInfiniteMeetings.ts

const { data: 탭별데이터 } = useInfiniteQuery({
  queryKey: ["탭이름", ...필터상태],
  queryFn: ({ pageParam = 1 }) =>
    fetchTabData({ type: 모임타입, page: pageParam, ...필터상태 }),
  getNextPageParam: (lastPage) =>
    lastPage.isLast ? undefined : lastPage.nextPage,
  staleTime: 1000 * 60 * 10,
});
  • queryKey에 탭과 필터 상태를 포함시켜 각 조건별 데이터를 독립적으로 캐싱
  • 동일한 탭과 필터 조합 재방문 시에는 staleTime동안 캐시된 데이터를 사용하여 불필요한 API 호출을 방지

2.2 데이터 동기화

const { mutate: deleteReview } = useMutation({
  mutationFn: (reviewId: number) => deleteReviewRequest(reviewId),
  onSuccess: () => {
    // 리뷰 삭제 시 연관된 데이터 자동 갱신
    queryClient.invalidateQueries({
      queryKey: ["reviews"],
    });
  },
});
  • useMutationonSuccess 콜백에서 invalidateQueries로 관련 쿼리들을 무효화하여 최신 데이터로 자동 갱신

2.3 무한 스크롤 구현

// Intersection Observer로 스크롤 감지
const { ref } = useInView({
  onChange: (inView) => {
    // 요소가 화면에 보이고, 다음 페이지가 있으며, 현재 요청 중이 아닐 때
    if (inView && hasNextPage && !isFetchingNextPage) {
      fetchNextPage(); // 다음 페이지 데이터 요청
    }
  },
});

// 마지막 요소에 ref 연결
<li ref={isLastItem ? ref : undefined}>
  <Card />
</li>
  • react-intersection-observeruseInView로 스크롤을 감지하고, 마지막 아이템이 뷰포트에 진입 시 다음 페이지 데이터를 요청

3. 사용자 경험

3.1 전역 에러 처리

error.tsx

Next.js의 error.tsx를 활용한 일관된 에러 UI 제공

// error.tsx

export default function Error({ error }: ErrorProps) {
  const router = useRouter();

  return (
    <div className='flex h-full flex-1 flex-col items-center justify-center gap-4'>
      <p className='text-gray-100'>
        // 프론트에서 처리한 에러 메시지 또는 기본 메시지 표시
        {error?.message || "오류가 발생했습니다."}
      </p>

      <SolidButton
        className='w-fit'
        size='small'
        onClick={() => router.replace("/")}
      >
        홈으로 돌아가기
      </SolidButton>
    </div>
  );
}
활용 예시
// /user/[id]/page.tsx

const userInfo = await getUserProfile(userId, { cache: "no-store" });
// getUserProfile.ts

export async function getUserProfile(userId: string, options?: RequestInit) {
  const res = await fetcher(`/user/profile/${userId}`, "", { ...options });
  const { data, message } = await res.json();

  if (!res.ok) {
    // 디버깅을 위한 상세 에러 로깅
    console.error("사용자 프로필 조회 실패:", {
      url: `/user/profile/${userId}`,
      status: res.status,
      errorMessage: message,
    });

    // 사용자 친화적인 에러 메시지 표시
    throw new Error(message || "유저 프로필을 불러오는데 실패했습니다.");
  }

  return data as UserProfile;
}

3.2 반응형 스켈레톤 UI

디바이스 화면 크기에 따라 최적화된 개수의 스켈레톤 UI 제공

태블릿 데스크탑

시작 가이드

Requirements

  • Node.js v20.10.0 or higher
  • React v18 or higher

Installation

$ git clone https://github.com/mogua-station/fe.git
$ cd fe
$ npm i
$ npm run dev

About

스터디 & 과외 매칭 플랫폼. 모두의 과외, 모과

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 5

Languages