Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions apis/images.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { clientFetch } from "@/libs/clientFetch";
import { throwApiError } from "@/utils/api";

export interface ErrorResponse {
code: string;
Expand Down Expand Up @@ -43,13 +44,7 @@ async function getPresignedUrl(fileName: string, contentType: string, folder: st
body: JSON.stringify({ fileName, contentType, folder }),
});

if (!res.ok) {
const error: ErrorResponse = await res.json().catch(() => ({
code: "UNKNOWN_ERROR",
message: "업로드 주소 생성 중 알 수 없는 에러가 발생했습니다.",
}));
throw new Error(error.message);
}
await throwApiError(res, "업로드 주소 생성 중 알 수 없는 에러가 발생했습니다.");
return res.json();
}

Expand All @@ -62,13 +57,7 @@ async function uploadToS3(presignedUrl: string, file: File) {
body: file,
});

if (!res.ok) {
const error: ErrorResponse = await res.json().catch(() => ({
code: "UNKNOWN_ERROR",
message: "업로드 중 알 수 없는 에러가 발생했습니다.",
}));
throw new Error(error.message);
}
await throwApiError(res, "업로드 중 알 수 없는 에러가 발생했습니다.");

return res.json();
}
9 changes: 2 additions & 7 deletions apis/meetingTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Category } from "@/store/category.store";
import { throwApiError } from "@/utils/api";

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

Expand All @@ -15,13 +16,7 @@ export async function getMeetingTypes() {
cache: "force-cache",
});

if (!res.ok) {
const error = await res.json().catch(() => ({
code: "UNKNOWN_ERROR",
message: "모임 카테고리 조회에 실패했습니다.",
}));
throw new Error(error.message);
}
await throwApiError(res, "모임 카테고리 조회에 실패했습니다.");
return res.json();
}

Expand Down
29 changes: 5 additions & 24 deletions apis/meetings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { clientFetch } from "@/libs/clientFetch";
import { throwApiError } from "@/utils/api";

const ROUTE_MEETINGS_FAVORITES = (meetingId: number) => `/meetings/${meetingId}/favorites`;
const ROUTE_MEETINGS_JOIN = (meetingId: number) => `/meetings/${meetingId}/join`;
Expand All @@ -10,12 +11,7 @@ export async function postMeetingsFavorite({ meetingId }: { meetingId: number })
method: "POST",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
const error: ErrorResponse = await res
.json()
.catch(() => ({ code: "UNKNOWN_ERROR", message: "모임 찜 추가에 실패했습니다." }));
throw new Error(error.message);
}
await throwApiError(res, "모임 찜 추가에 실패했습니다.");
return res.json();
}

Expand All @@ -26,12 +22,7 @@ export async function deleteMeetingsFavorite({ meetingId }: { meetingId: number
method: "DELETE",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
const error: ErrorResponse = await res
.json()
.catch(() => ({ code: "UNKNOWN_ERROR", message: "모임 찜 해제에 실패했습니다." }));
throw new Error(error.message);
}
await throwApiError(res, "모임 찜 해제에 실패했습니다.");
return res.json();
}

Expand All @@ -42,12 +33,7 @@ export async function postMeetingsJoin({ meetingId }: { meetingId: number }): Pr
method: "POST",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
const error: ErrorResponse = await res
.json()
.catch(() => ({ code: "UNKNOWN_ERROR", message: "모임 참여에 실패했습니다." }));
throw new Error(error.message);
}
await throwApiError(res, "모임 참여에 실패했습니다.");
return res.json();
}

Expand All @@ -58,12 +44,7 @@ export async function deleteMeetingsJoin({ meetingId }: { meetingId: number }):
method: "DELETE",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
const error: ErrorResponse = await res
.json()
.catch(() => ({ code: "UNKNOWN_ERROR", message: "모임 참여 취소에 실패했습니다." }));
throw new Error(error.message);
}
await throwApiError(res, "모임 참여 취소에 실패했습니다.");
return res.json();
}

Expand Down
8 changes: 3 additions & 5 deletions app/(main)/meetup/list/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const metadata: Metadata = {
},
};

const size = 10;

const defaultPageSize = 10;
const ListFiltersStyle = "mx-0 bg-gray-50 px-6 py-2 md:-mx-4 md:px-6";
export default function MeetupListPage() {
return (
<MeetupListScrollProvider>
Expand All @@ -33,7 +33,7 @@ export default function MeetupListPage() {
<ListFilters className={ListFiltersStyle} />
</Suspense>
<QueryErrorBoundary prefix="모임 목록을 ">
<MeetupCardList size={size} />
<MeetupCardList size={defaultPageSize} />
</QueryErrorBoundary>
<Suspense fallback={null}>
<CreateOpenButton className="fixed right-6 bottom-6 z-10" />
Expand All @@ -42,5 +42,3 @@ export default function MeetupListPage() {
</MeetupListScrollProvider>
);
}

const ListFiltersStyle = "mx-0 bg-gray-50 px-6 py-2 md:-mx-4 md:px-6";
28 changes: 7 additions & 21 deletions features/meetup/apis.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { clientFetch } from "@/libs/clientFetch";
import { throwApiError } from "@/utils/api";
import {
MeetupCreateRequest,
MeetupItemResponse,
MeetupListRequest,
MeetupListResponse,
} from "./types";
import { buildMeetupListQuery } from "./list/utils";

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

Expand All @@ -19,10 +21,7 @@ export type getKakaoPlaceFn = typeof getKakaoPlace;
const ROUTE_KAKAO_PLACE = "/kakao/place";
export async function getKakaoPlace(query: string) {
const res = await clientFetch(`${ROUTE_KAKAO_PLACE}?query=${query}`);
if (!res.ok) {
const error = await res.json().catch(() => null);
throw new Error(error?.message ?? "카카오 장소 검색 API 호출에 실패했습니다.");
}
await throwApiError(res, "카카오 장소 검색 API 호출에 실패했습니다.");

const data = await res.json();
const { documents } = data;
Expand All @@ -33,23 +32,13 @@ const ROUTE_MEETINGS = "/meetings";

/** 모임 찾기 */
export async function getMeetups(params: MeetupListRequest): Promise<MeetupListResponse> {
// encodeURIComponent 자동 적용
const queryParams = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value != null) {
queryParams.append(key, String(value));
}
}

const res = await clientFetch(`${ROUTE_MEETINGS}?${queryParams}`, {
const qs = buildMeetupListQuery(params);
const res = await clientFetch(qs ? `${ROUTE_MEETINGS}?${qs}` : ROUTE_MEETINGS, {
method: "GET",
headers: { "Content-Type": "application/json" },
});

if (!res.ok) {
const error = await res.json().catch(() => null);
throw new Error(error?.message ?? "모임 목록을 불러오는데 실패했습니다.");
}
await throwApiError(res, "모임 목록을 불러오는데 실패했습니다.");
return res.json();
}

Expand All @@ -61,9 +50,6 @@ export async function postMeetup(data: MeetupCreateRequest): Promise<MeetupItemR
body: JSON.stringify(data),
});

if (!res.ok) {
const error = await res.json().catch(() => null);
throw new Error(error?.message ?? "모임 생성에 실패했습니다.");
}
await throwApiError(res, "모임 생성에 실패했습니다.");
return res.json();
}
2 changes: 1 addition & 1 deletion features/meetup/components/CapacityField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import InputField from "@/components/ui/Inputs/InputField";
import { MIN_CONFIRMED_COUNT } from "@/features/meetupDetail/components/PersonnelContainer";
import { MIN_CONFIRMED_COUNT } from "@/features/meetup/constants";

interface CapacityFieldProps {
/** 필드 이름 @default "capacity" */
Expand Down
8 changes: 8 additions & 0 deletions features/meetup/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { MIN_CONFIRMED_COUNT as MIN_CONFIRMED_COUNT_DEFAULT } from "@/features/meetupDetail/components/PersonnelContainer";
Comment thread
PollyGotACracker marked this conversation as resolved.

/** 모임 개설 확정 등에 쓰는 최소 인원(모집 정원 하한) */
export const MIN_CONFIRMED_COUNT = MIN_CONFIRMED_COUNT_DEFAULT;
/** 모임 이름 최대 길이 */
export const MAX_NAME_LENGTH = 20;
/** 주소 상세 최대 길이 */
export const MAX_ADDRESS_LENGTH = 50;
48 changes: 29 additions & 19 deletions features/meetup/create/components/StepSchedule/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import { useEffect } from "react";
import { useFormData } from "../../providers/FormDataProvider";
import CapacityField from "@/features/meetup/components/CapacityField";
import DateTimeField from "@/features/meetup/components/DateTimeField";
import { validateCapacity, validateDateTime, validateDateTimeOrder } from "../../../utils";
import { validateMaxCapacity } from "@/features/meetupDetail/edit/utils";
import { MIN_CONFIRMED_COUNT } from "@/features/meetupDetail/components/PersonnelContainer";
import {
validateDateTimeIsFuture,
validateDateTimeOrder,
validateMaxCapacity,
} from "@/features/meetupDetail/edit/utils";
import { validateCapacity } from "@/features/meetup/utils";
import { MIN_CONFIRMED_COUNT } from "@/features/meetup/constants";
import { useToast } from "@/providers/toast-provider";

interface StepScheduleProps {
Expand Down Expand Up @@ -39,14 +43,14 @@ export default function StepSchedule({ step }: StepScheduleProps) {
!!meetTime &&
!!regDate &&
!!regTime &&
!validateDateTimeOrder({
dateTime: next._dateTime,
registrationEnd: next._registrationEnd,
});
!validateDateTimeOrder(
`${next._dateTime.date} ${next._dateTime.time}`,
`${next._registrationEnd.date} ${next._registrationEnd.time}`,
);

if (isDateTimeKey) {
if (!meetDate || !meetTime) return;
if (!validateDateTime(meetDate, meetTime)) {
if (!validateDateTimeIsFuture(`${meetDate} ${meetTime}`)) {
handleShowToast({ message: MESSAGE_SCHEDULE_AFTER_NOW, status: "error" });
return;
}
Expand All @@ -56,7 +60,7 @@ export default function StepSchedule({ step }: StepScheduleProps) {
}
} else {
if (!regDate || !regTime) return;
if (!validateDateTime(regDate, regTime)) {
if (!validateDateTimeIsFuture(`${regDate} ${regTime}`)) {
handleShowToast({ message: MESSAGE_REGISTRATION_END_AFTER_NOW, status: "error" });
return;
}
Expand All @@ -73,21 +77,27 @@ export default function StepSchedule({ step }: StepScheduleProps) {

// 데이터 유효성 검사
useEffect(() => {
const isDateTimeValid = validateDateTime(data._dateTime.date, data._dateTime.time);
const isRegEndValid = validateDateTime(data._registrationEnd.date, data._registrationEnd.time);
const isDateTimeOrderValid = validateDateTimeOrder({
dateTime: data._dateTime,
registrationEnd: data._registrationEnd,
});
const isCapacityValid = validateCapacity(data.capacity);
const isMaxCapacityValid = validateMaxCapacity(data.capacity, MIN_CONFIRMED_COUNT);
const isDateTimeValid =
!!data._dateTime.date &&
!!data._dateTime.time &&
validateDateTimeIsFuture(`${data._dateTime.date} ${data._dateTime.time}`);
const isRegEndValid =
!!data._registrationEnd.date &&
!!data._registrationEnd.time &&
validateDateTimeIsFuture(`${data._registrationEnd.date} ${data._registrationEnd.time}`);
const isDateTimeOrderValid = validateDateTimeOrder(
`${data._dateTime.date} ${data._dateTime.time}`,
`${data._registrationEnd.date} ${data._registrationEnd.time}`,
);
const isCapacityEntered = validateCapacity(data.capacity);
const isCapacityMinOk = validateMaxCapacity(data.capacity, MIN_CONFIRMED_COUNT);
Comment thread
PollyGotACracker marked this conversation as resolved.
setStepValid(
step,
isDateTimeValid &&
isRegEndValid &&
isDateTimeOrderValid &&
isCapacityValid &&
isMaxCapacityValid,
isCapacityEntered &&
isCapacityMinOk,
);
}, [data, setStepValid, step]);

Expand Down
7 changes: 2 additions & 5 deletions features/meetup/list/components/ListFilters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect, useRef, useState } from "react";
import { useCategoryStore } from "@/store/category.store";
import type { RegionFilterValue } from "@/features/meetup/types";
import {
CATEGORY_TYPE_ALL,
QUERY_KEYS,
Expand All @@ -20,7 +21,6 @@ import useScrollVisibilityDynamic from "@/hooks/useScrollVisibilityDynamic";
import TabButton from "@/components/ui/Buttons/TabButton";
import DateFilter from "@/components/ui/Filter/DateFilter";
import RegionFilter from "@/components/ui/Filter/RegionFilter";
import type { Option } from "@/components/ui/Filter/RegionFilter/option";
import { FilterDropdown } from "@/components/ui/Filter/FilterDropdown";
import SearchInput from "@/components/ui/SearchInput";
import IcSearch from "@/components/ui/icons/IcSearch";
Expand Down Expand Up @@ -231,10 +231,7 @@ function KeywordFilter() {
}

// 우측 드롭다운 필터 목록
export type RegionFilterValue = {
region: Option | null;
district: Option | null;
};

export type RegionFilterParams = {
fullLabel: string;
} & RegionFilterValue;
Expand Down
4 changes: 2 additions & 2 deletions features/meetup/list/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export const QUERY_KEYS = {
* 클라이언트 기본값 createdAt
* 서버 기본값 dateTime
*/
SORT_BY: "sort",
SORT_BY: "sortBy",
/** 정렬 순서 (오름차, 내림차)
* 클라이언트 기본값 desc, 서버 기본값 asc
* sortBy=createAt의 경우 서버 기본값 desc
*/
SORT_ORDER: "order",
SORT_ORDER: "sortOrder",
};

/** 정렬 기준 항목 */
Expand Down
Loading
Loading