개발기간: 25.01.03 - 25.02.12
Frontend
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|
| 곽정원 | 이에스더 | 박태현 | 정민재 |
| 프론트 배포 CI-CD 로그인 랜딩페이지 |
유저 페이지 프로필 수정 리뷰 활동 |
찜한 모임 모임 상세 |
메인페이지 모임 생성 |
Backend & 디자이너
| 김기현 | 김은지 |
| 백엔드 | 디자이너 |
페이지 상세 설명 보기
- 프로필 정보와 4가지 활동 탭(
내 모임/내 리뷰/만든 모임/수강평)으로 구성되어 있습니다.- 내 모임: 참여 중인 스터디/과외 목록
- 내 리뷰: 스터디/과외별로 구분되며, 각각 '작성 가능한 리뷰'와 '작성한 리뷰' 목록 제공
- 만든 모임: 생성한 스터디/과외 목록
- 수강평: 과외선생님이 생성한 과외에 대한 리뷰 목록
- 데이터는 10개씩 무한 스크롤로 자동 로딩되며, 로딩 중에는 스켈레톤 UI를 보여줍니다.
- 페이지 진입 시 새로운 데이터를 요청하고, 이후 10분간은 탭 전환 시에도 캐시된 데이터를 바로 확인할 수 있습니다.
- 본인 페이지에서만 프로필 수정과 리뷰 관리가 가능합니다.
과외선생님은 수강평 탭이 추가로 제공됩니다.
| 마이페이지 (일반유저) | 다른 유저페이지 (과외선생님) |
|---|---|
![]() |
![]() |
- 기존 정보(
닉네임,이메일,자기소개,태그)가 기본값으로 표시되며, 이메일을 제외한 모든 정보를 수정할 수 있습니다. - 프로필 이미지는 선택 즉시 미리보기로 확인되며, IndexedDB를 활용해 새로고침해도 유지됩니다.
- 변경사항이 없거나 유효하지 않은 값이 있으면 수정이 제한됩니다.
- 페이지 이탈 시 확인 모달이 표시되어 실수를 방지합니다.
- 3단계 평점 시스템(그냥 그래요/괜찮아요/추천해요)으로 평가합니다.
- 평점과 리뷰 내용은 필수로 입력해야 하며, 사진은 1장까지 선택적으로 첨부할 수 있습니다.
- 수정 시에는 기존 리뷰 정보가 기본값으로 표시되며, 변경사항이 있어야만 수정이 가능합니다.
- 작성/수정 완료 시 유저 페이지의 리뷰 목록이 자동으로 업데이트됩니다.
| 리뷰 작성 | 리뷰 수정 |
|---|---|
![]() |
![]() |
- 유저 페이지에서 더보기 메뉴를 통해 즉시 삭제할 수 있습니다.
- 삭제 전 확인 모달을 통해 실수를 방지합니다.
- 삭제 즉시 리뷰 목록에서 제거되며, 관련 데이터가 자동으로 업데이트됩니다.
아래의 이미지를 클릭하면 데모 영상을 확인할 수 있습니다.

명확한 API 명세서를 직접 설계하고 백엔드 개발자와 지속적인 피드백을 주고 받으며 효율적인 협업 환경 구축
예시
| API URL, 파라미터 및 응답 구조 | 작성가능한 리뷰 데이터 구조 | 작성한 리뷰 데이터 구조 |
|---|---|---|
![]() |
![]() |
![]() |
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);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 호출을 방지
const { mutate: deleteReview } = useMutation({
mutationFn: (reviewId: number) => deleteReviewRequest(reviewId),
onSuccess: () => {
// 리뷰 삭제 시 연관된 데이터 자동 갱신
queryClient.invalidateQueries({
queryKey: ["reviews"],
});
},
});useMutation의onSuccess콜백에서invalidateQueries로 관련 쿼리들을 무효화하여 최신 데이터로 자동 갱신
// Intersection Observer로 스크롤 감지
const { ref } = useInView({
onChange: (inView) => {
// 요소가 화면에 보이고, 다음 페이지가 있으며, 현재 요청 중이 아닐 때
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage(); // 다음 페이지 데이터 요청
}
},
});
// 마지막 요소에 ref 연결
<li ref={isLastItem ? ref : undefined}>
<Card />
</li>react-intersection-observer의useInView로 스크롤을 감지하고, 마지막 아이템이 뷰포트에 진입 시 다음 페이지 데이터를 요청
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;
}
디바이스 화면 크기에 따라 최적화된 개수의 스켈레톤 UI 제공
| 태블릿 | 데스크탑 |
|---|---|
![]() |
![]() |
- Node.js v20.10.0 or higher
- React v18 or higher
$ git clone https://github.com/mogua-station/fe.git
$ cd fe
$ npm i
$ npm run dev














