diff --git a/src/@types/api.ts b/src/@types/api.ts index 727f7c11..e1d52e06 100644 --- a/src/@types/api.ts +++ b/src/@types/api.ts @@ -3,11 +3,21 @@ export interface APIError extends Error { message: string; } -export interface ApiResponse { +export interface FetcherError extends Error { + status?: number; +} + +export interface BaseResponse { status: string; data: T; } -export interface FetcherError extends Error { - status?: number; +export interface ListResponse { + status: string; + data: { + content: T[]; + total: number; + currentPage: number; + hasNext: boolean; + }; } diff --git a/src/@types/auth.ts b/src/@types/auth.ts index a080fe43..64bb8662 100644 --- a/src/@types/auth.ts +++ b/src/@types/auth.ts @@ -1,23 +1,18 @@ -interface TextInput { +export interface AuthInput { email: string; verifyNumber: number; name: string; nickname: string; birthDate: number; contact: string; -} - -interface PasswordInput { currentPassword: string; password: string; newPassword: string; passwordConfirm: string; } -interface ValidateOptions { - name: keyof TextInput | keyof PasswordInput; +export interface ValidateOptions { + name: keyof AuthInput; value: string; password?: string; } - -export type { TextInput, PasswordInput, ValidateOptions }; diff --git a/src/@types/chat.ts b/src/@types/chat.ts index d249d69b..49b52a69 100644 --- a/src/@types/chat.ts +++ b/src/@types/chat.ts @@ -1,26 +1,30 @@ import { CHAT_SORT_OPTIONS } from '@/constants/chat'; +import { User } from './user'; -export type SortType = keyof typeof CHAT_SORT_OPTIONS; - -export interface RoomResponse { +export interface ChatRoom { chatId: string; chattingName: string; + description: string; host: string; + hostProfileImage: string; hasJoined: boolean; + unreadMessageCount: number; lastMessageTime: string; image: string; - unreadMessageCount: number; membersCount: number; totalMembersCount: number; - hostProfileImage: string; - description: string; } -export interface ChattingResponse { +export interface Chat { chatTitle: string; chatMessages: ChatMessage[]; } +export interface ChatOverview { + participants: Participant[]; + album: ImageInfo[]; +} + export interface ChatMessage { chatMessageId: string; images: string[]; @@ -31,20 +35,15 @@ export interface ChatMessage { unreadCount: number; } -export interface ChatOverview { - participants: Participant[]; - album: ImageInfo[]; -} - export interface ImageInfo { images: string[]; uploadDate: string; uploader: string; } -export interface Participant { - user: string; - email: string; - description: string; - profileImage: string; + +export interface Participant + extends Pick { travelCount: number; } + +export type SortType = keyof typeof CHAT_SORT_OPTIONS; diff --git a/src/@types/review.ts b/src/@types/review.ts index f06414d6..21603728 100644 --- a/src/@types/review.ts +++ b/src/@types/review.ts @@ -1,58 +1,41 @@ export interface Review { + // 리뷰 정보 reviewId: number; - id: number; - title: string; - nickname?: string; - profileImage?: string; - imageUrl: string; reviewImage: string; + title: string; content: string; - score?: number; + reviewImages: string[]; starRating: number; - travelLocation: string; + likesCount: number; + likesFlag: boolean; createdAt: string; - isLast: boolean; - likesFlag?: boolean; -} - -export interface ReviewResponse { - content: Review[]; - currentPage: number; - size: number; - total: number; - hasNext: boolean; -} - -export interface Filters { - sortOrder: 'createdAt' | 'popular'; -} -export interface ReviewListFilters { - sortOrder: 'LATEST' | 'POPULAR'; -} + travelLocation: string; -export interface ReviewDetail { - reviewId: number; + // 여행 정보 travelId: number; travelTitle: string; - userProfileImage: string; + + // 유저 정보 nickname: string; - title: string; - comment: string; - starRating: number; - reviewImages: string[]; - likesCount: number; - likesFlag: boolean; - travelLocation: string; - createdAt: string; + profileImage: string; } -export interface ReviewDetailResponse { - status: string; - data: ReviewDetail; +export interface ReviewRatings { + totalReviews: number; + oneStarReviews: number; + twoStarReviews: number; + threeStarReviews: number; + fourStarReviews: number; + fiveStarReviews: number; } -export interface CreateReview { - score: number; - content: string; - reviewImage: File[]; +export interface ReviewScore { + totalRating: number; + reviewRatings: ReviewRatings; +} + +export interface ReviewListFilters { + sortOrder: 'LATEST' | 'POPULAR'; + pageParam: number; + size: number; } diff --git a/src/@types/travel.ts b/src/@types/travel.ts index 7ffc704a..dcdb70da 100644 --- a/src/@types/travel.ts +++ b/src/@types/travel.ts @@ -1,47 +1,18 @@ -export interface Travel { - travelId: number; - travelName: string; - isDomestic: boolean; - travelStatus?: string; - location: string; - image: string; - startAt: string; - endAt: string; - maxTravelMateCount: number; - currentTravelMateCount: number; - formattedStartDate?: string; - expectedTripCost?: number; - isBookmark: boolean | null; -} +import { User } from './user'; -export interface TravelPlan { - tripDay: number; - tripOrderNumber: number; - destination: string; - description: string; - image: string; -} - -export interface Participant { - id: number; - nickname: string; - role: string; - profileImage: string; -} - -export interface TravelDetail { +export interface Travel { travelId: number; travelName: string; description: string; - image: string; - expectedTripCost: number; + travelImage: string; + expectedTripCost?: number; currentTravelMateCount: number; minTravelMateCount: number; maxTravelMateCount: number; hashTags: string; isDomestic: boolean; travelLocation: string; - departureLocation: string; + departureLocation?: string; startAt: string; endAt: string; registrationEnd: string; @@ -52,6 +23,33 @@ export interface TravelDetail { bookmarkFlag: boolean | null; } +export interface TravelPlan { + tripDay: number; + tripOrderNumber: number; + destination: string; + description: string; + travelPlanImage: string; +} + +export type TravelCard = Pick< + Travel, + | 'travelId' + | 'travelImage' + | 'isDomestic' + | 'travelName' + | 'travelLocation' + | 'maxTravelMateCount' + | 'currentTravelMateCount' + | 'startAt' + | 'endAt' + | 'bookmarkFlag' +>; + +export interface Participant + extends Pick { + role: string; +} + export interface TravelReviewRateScore { oneStarReviews: number; twoStarReviews: number; @@ -82,11 +80,6 @@ export interface TravelParams { filters?: Filters; } -export interface TravelFilterResponse { - content: Travel[]; - currentPage: number; - hasNext: boolean; -} export interface TravelList { travelId: number; travelName: string; @@ -103,7 +96,7 @@ export interface TravelList { } export interface MyTravel { - content: TravelList[]; + content: Travel[]; total: number; currentPage: number; hasNext: boolean; diff --git a/src/@types/user.ts b/src/@types/user.ts index 102cdbd2..c57364d8 100644 --- a/src/@types/user.ts +++ b/src/@types/user.ts @@ -1,32 +1,7 @@ export interface User { - id: number; - email: string; - password: string; - name: string; - nickname: string; - contact: string; - birthDate: number; - image: string; - createdAt: string; - updatedAt: string; - SaveList: []; - LikesList: []; - ParticipationList: []; -} - -export interface UserList { - userId: number; - profileImage: string; - nickName: string; - openTravelCount: number; - reviewCount: number; - hashTags: string; -} - -export interface MyPageProfile { userId: number; email: string; nickname: string; - image: string; + profileImage: string; description: string; } diff --git a/src/api/auth/deleteAccount.ts b/src/api/auth/deleteAccount.ts index e10b247b..91c0ee22 100644 --- a/src/api/auth/deleteAccount.ts +++ b/src/api/auth/deleteAccount.ts @@ -1,9 +1,17 @@ +import { AuthInput } from '@/@types/auth'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; -export const checkPassword = (password: { password: string }) => { - return http.post('/users/check/password', password); +type PasswordRequestBody = Pick; + +interface CheckPasswordToken { + deleteUserToken: string; +} + +export const checkPassword = (password: PasswordRequestBody) => { + return http.post>('/users/check/password', password); }; export const deleteAccount = () => { - return http.delete('/users'); + return http.delete>('/users'); }; diff --git a/src/api/auth/login.ts b/src/api/auth/login.ts index df32a036..131fdd0b 100644 --- a/src/api/auth/login.ts +++ b/src/api/auth/login.ts @@ -1,8 +1,9 @@ -import { User } from '@/@types/user'; +import { AuthInput } from '@/@types/auth'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; -type LoginRequestBody = Pick; +type LoginRequestBody = Pick; export const login = (credentials: LoginRequestBody) => { - return http.post('/auth/sign-in', credentials); + return http.post>('/auth/sign-in', credentials); }; diff --git a/src/api/auth/logout.ts b/src/api/auth/logout.ts index cb010910..a60d267c 100644 --- a/src/api/auth/logout.ts +++ b/src/api/auth/logout.ts @@ -1,5 +1,6 @@ +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; export const logout = () => { - return http.post('/auth/sign-out'); + return http.post>('/auth/sign-out'); }; diff --git a/src/api/auth/resetPassword.ts b/src/api/auth/resetPassword.ts index c90ceb48..74e6db21 100644 --- a/src/api/auth/resetPassword.ts +++ b/src/api/auth/resetPassword.ts @@ -1,24 +1,27 @@ +import { AuthInput } from '@/@types/auth'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; -interface ResetAuthPasswordRequestBody { - email: string; - password: string; +interface ResetAuthPasswordRequestBody + extends Pick { token: string; } -interface ResetUserPasswordRequestBody { - currentPassword: string; - newPassword: string; -} +type ResetUserPasswordRequestBody = Pick< + AuthInput, + 'currentPassword' | 'newPassword' +>; +/* ---------------------------- 비밀번호 재설정 (비 로그인) ---------------------------- */ export const resetAuthPassword = ( credentials: ResetAuthPasswordRequestBody, ) => { - return http.put('/auth/password', credentials); + return http.put>('/auth/password', credentials); }; +/* ---------------------------- 비밀번호 재설정 (로그인) ---------------------------- */ export const resetUserPassword = ( credentials: ResetUserPasswordRequestBody, ) => { - return http.put('/users/password', credentials); + return http.put>('/users/password', credentials); }; diff --git a/src/api/auth/signup.ts b/src/api/auth/signup.ts index 78fcc7d1..a728f530 100644 --- a/src/api/auth/signup.ts +++ b/src/api/auth/signup.ts @@ -1,15 +1,12 @@ +import { AuthInput } from '@/@types/auth'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; -interface SignupRequestBody { - email: string; - password: string; - name: string; - nickname: string; - birthDate: number; - contact: string; - verifiedToken: string; -} +type SignupRequestBody = Pick< + AuthInput, + 'email' | 'password' | 'name' | 'nickname' | 'birthDate' | 'contact' +>; export const signup = (credentials: SignupRequestBody) => { - return http.post('/auth/sign-up', credentials); + return http.post>('/auth/sign-up', credentials); }; diff --git a/src/api/auth/verifyEmail.ts b/src/api/auth/verifyEmail.ts index a65b1855..7c6c5386 100644 --- a/src/api/auth/verifyEmail.ts +++ b/src/api/auth/verifyEmail.ts @@ -1,23 +1,27 @@ -import { TextInput } from '@/@types/auth'; +import { AuthInput } from '@/@types/auth'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; -type SendMailRequestBody = Pick; -type CheckCodeRequestBody = Pick; +type SendMailRequestBody = Pick; +type CheckCodeRequestBody = Pick; +interface EmailVerifyToken { + emailVerifyToken: string; +} /* -------------------------------- 인증 이메일 전송 (회원가입)------------------------------- */ export const sendMail = (credentials: SendMailRequestBody) => { - return http.post('/auth/sign-up/emails', credentials); + return http.post>('/auth/emails/sign-up', credentials); }; /* -------------------------------- 인증 이메일 전송 (비밀번호 재설정)------------------------------- */ export const passwordSendMail = (credentials: SendMailRequestBody) => { - return http.post('/auth/password/emails', credentials); + return http.post>('/auth/emails/password', credentials); }; /* ------------------------------- 이메일 인증코드 확인 ------------------------------ */ -export const checkCode = ({ email, verifyNumber }: CheckCodeRequestBody) => { - return http.post('/auth/emails/verify', { - email, - verifyNumber, - }); +export const checkCode = (credentials: CheckCodeRequestBody) => { + return http.post>( + '/auth/emails/verification', + credentials, + ); }; diff --git a/src/api/auth/verifyToken.ts b/src/api/auth/verifyToken.ts index 1d8b4d53..892cbf09 100644 --- a/src/api/auth/verifyToken.ts +++ b/src/api/auth/verifyToken.ts @@ -1,4 +1,5 @@ import type { NextRequest } from 'next/server'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; export const verifyToken = (request?: NextRequest) => { @@ -10,5 +11,5 @@ export const verifyToken = (request?: NextRequest) => { } : undefined; - return http.get<{ status: number }>('/auth/token/verify', config); + return http.get>('/auth/token/verification', config); }; diff --git a/src/api/chat/chatApi.ts b/src/api/chat/chatApi.ts index 7f6dc7ed..8b28321e 100644 --- a/src/api/chat/chatApi.ts +++ b/src/api/chat/chatApi.ts @@ -1,52 +1,35 @@ -import { ChatOverview, ChattingResponse } from '@/@types/chat'; +import { BaseResponse } from '@/@types/api'; +import { ChatOverview, Chat } from '@/@types/chat'; import { http } from '../fetcher'; -interface ChattingsResponse { - status: string; - data: ChattingResponse; -} - -interface ChatOverviewResponse { - status: string; - data: ChatOverview; -} - -interface UploadChatImagesResponse { - status: string; - data: string[]; -} - -interface JoinResponse { - status: string; - data: string[]; -} - export const getChat = async ( chatId: string, latest: number, -): Promise => { +): Promise> => { const queryParams = latest !== -1 ? `?latest=${latest}` : ''; - return http.get(`/chat/${chatId}${queryParams}`); + return http.get>(`/chat/${chatId}${queryParams}`); }; export const getChatOverview = async ( chatId: string, -): Promise => { - return http.get(`/chat/${chatId}/overview`); +): Promise> => { + return http.get>(`/chat/${chatId}/overview`); }; -export const setIsJoined = async (chatId: string): Promise => { - return http.post(`/chat/${chatId}`); +export const setIsJoined = async ( + chatId: string, +): Promise> => { + return http.post>(`/chat/${chatId}`); }; export const uploadChatImages = async ( chatId: string, images: File[], -): Promise => { +): Promise> => { const formData = new FormData(); images.forEach((image) => { formData.append('files', image); }); - return http.post(`/chat/${chatId}/image`, formData); + return http.post>(`/chat/${chatId}/image`, formData); }; diff --git a/src/api/chat/chatRoomsApi.ts b/src/api/chat/chatRoomsApi.ts index 8e836858..2537fce6 100644 --- a/src/api/chat/chatRoomsApi.ts +++ b/src/api/chat/chatRoomsApi.ts @@ -1,23 +1,15 @@ -import { RoomResponse, SortType } from '@/@types/chat'; +import { BaseResponse } from '@/@types/api'; +import { ChatRoom, SortType } from '@/@types/chat'; import { http } from '../fetcher'; -interface RoomsResponse { - status: string; - data: RoomResponse[]; -} - -interface LeaveChatResponse { - status: string; - data: null; -} - export const getChatRooms = async ( sortType: SortType, -): Promise => { - return http.get(`/chat?sortType=${sortType}`); +): Promise> => { + return http.get>(`/chat?sortType=${sortType}`); }; -export const leaveChat = async (chatId: string): Promise => { - return http.delete(`/chat/${chatId}`); +export const leaveChat = async ( + chatId: string, +): Promise> => { + return http.delete>(`/chat/${chatId}`); }; -// 나가기 기능 diff --git a/src/api/review/createReview.ts b/src/api/review/createReview.ts index d8e31111..2ef4e6d7 100644 --- a/src/api/review/createReview.ts +++ b/src/api/review/createReview.ts @@ -1,15 +1,9 @@ import { Travel } from '@/@types/travel'; +import { ListResponse } from '@/@types/api'; import { http } from '../fetcher'; -interface WritableTravelResponse { - content: Travel[]; - total: number; - currentPage: number; - hasNext: boolean; -} - export const getWritableTravelReview = (size: number, page: number) => { - return http.get( + return http.get>( `/travels/reviews/pending?limit=${size}&page=${page}`, ); }; diff --git a/src/api/review/review.ts b/src/api/review/review.ts index 3d95f767..730d3508 100644 --- a/src/api/review/review.ts +++ b/src/api/review/review.ts @@ -1,80 +1,53 @@ -import { Review, ReviewDetailResponse, ReviewResponse } from '@/@types/review'; -import { ApiResponse } from '@/@types/api'; -import { TravelReviewRateScore } from '@/@types/travel'; +import { Review, ReviewListFilters, ReviewScore } from '@/@types/review'; +import { BaseResponse, ListResponse } from '@/@types/api'; import { http } from '../fetcher'; -interface ReviewParams { - pageParam: number; - sortOrder: string; -} - -interface TravelReviewParams { - travelId: number; - pageParam: number; -} - -interface MyReview { - content: Review[]; - total: number; - currentPage: number; - hasNext: boolean; -} - -interface MyReviewResponse { - status: string; - data: MyReview; -} - -interface TravelReview { - content: Review[]; - total: number; - hasNext: boolean; - currentPage: number; -} -interface TravelReviewRate { - reviews: TravelReviewRateScore; - totalRating: number; -} - export const getPopularReview = () => { - return http.get>('/reviews/popular'); + return http.get>('/reviews/popular'); +}; + +export const getTravelReviewRate = ({ travelId }: { travelId: number }) => { + return http.get>(`/travels/${travelId}/ratings`); }; export const getTravelReview = ({ travelId, pageParam, -}: TravelReviewParams) => { - return http.get>( - `/reviews?id=${travelId}&page=${pageParam}`, - ); -}; - -export const getTravelReviewRate = ({ travelId }: { travelId: number }) => { - return http.get>( - `/reviews/${travelId}/ratings`, +}: { + travelId: number; + pageParam: number; +}) => { + return http.get>( + `/reviews/travels/${travelId}?page=${pageParam}&size=8`, ); }; -export const getReview = ({ pageParam, sortOrder }: ReviewParams) => { - return http.get>( - `/reviews?page=${pageParam}&sortBy=${sortOrder}&size=12`, +export const getReview = ({ + pageParam, + sortOrder, +}: { + pageParam: number; + sortOrder: ReviewListFilters['sortOrder']; +}) => { + return http.get>( + `/reviews?page=${pageParam}&sortByType=${sortOrder}&size=12`, ); }; export const getMyReview = (limit: number, offset: number) => { - return http.get( - `/reviews/published?size=${limit}&page=${offset}`, + return http.get>( + `/reviews/mine?size=${limit}&page=${offset}`, ); }; -export const getReviewDetail = (id: number) => { - return http.get(`/reviews/${id}`); +export const getReviewDetail = (reviewId: number) => { + return http.get>(`/reviews/${reviewId}`); }; -export const postReviewLike = (id: number) => { - return http.post(`/reviews/${id}/likes`); +export const postReviewLike = (reviewId: number) => { + return http.post>(`/reviews/${reviewId}/likes`); }; -export const deleteReviewLike = (id: number) => { - return http.delete(`/reviews/${id}/likes`); +export const deleteReviewLike = (reviewId: number) => { + return http.delete>(`/reviews/${reviewId}/likes`); }; diff --git a/src/api/travel/createTravel.ts b/src/api/travel/createTravel.ts index 3bd48aac..66f2c65a 100644 --- a/src/api/travel/createTravel.ts +++ b/src/api/travel/createTravel.ts @@ -1,73 +1,8 @@ -import { FormTravelData } from '@/@types/travelForm'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; -export const createTravel = (data: FormTravelData) => { - const formData = new FormData(); - - if (data.registrationEnd.startDate && data.startAt) { - const registrationEndDate = new Date( - data.registrationEnd.startDate.getTime() - - data.registrationEnd.startDate.getTimezoneOffset() * 60000, - ); - registrationEndDate.setHours(23, 59, 59, 999); - - const registrationEnd = registrationEndDate - .toLocaleString('sv-SE', { hour12: false }) - .replace(' ', 'T') - .slice(0, 16); - - const startAt = `${new Date( - data.startAt.getTime() - data.startAt.getTimezoneOffset() * 60000, - ) - .toISOString() - .slice(0, 10)}T${data.startTime.hour}:${data.startTime.minute}`; - - const endAt = data.endAt - ? `${new Date( - data.endAt.getTime() - data.endAt.getTimezoneOffset() * 60000, - ) - .toISOString() - .slice(0, 10)}T${data.endTime.hour}:${data.endTime.minute}` - : `${new Date( - data.startAt.getTime() - data.startAt.getTimezoneOffset() * 60000, - ) - .toISOString() - .slice(0, 10)}T${data.endTime.hour}:${data.endTime.minute}`; - - formData.append('registrationEnd', registrationEnd); - formData.append('startAt', startAt); - formData.append('endAt', endAt); - } - - formData.append('travelName', data.travelName); - formData.append('expectedTripCost', data.expectedTripCost); - formData.append('minTravelMateCount', data.minTravelMateCount); - formData.append('maxTravelMateCount', data.maxTravelMateCount); - formData.append('travelDescription', data.travelDescription); - formData.append('hashTags', `#${data.hashTags.join('#')}`); - formData.append('travelLocation', data.travelLocation); - formData.append('departureLocation', data.departureLocation); - formData.append('isDomestic', `${data.isDomestic}`); - - if (data.travelImage) { - formData.append('travelImage', data.travelImage); - } - - data.detailTravel.forEach((detail, index) => { - formData.append(`detailTravel[${index}].tripDay`, `${detail.tripDay}`); - formData.append( - `detailTravel[${index}].tripOrderNumber`, - `${detail.tripOrderNumber}`, - ); - formData.append(`detailTravel[${index}].destination`, detail.destination); - formData.append(`detailTravel[${index}].description`, detail.description); - if (detail.destinationImage) { - formData.append( - `detailTravel[${index}].destinationImage`, - detail.destinationImage, - ); - } - }); - - return http.post('/travels', formData); +export const createTravel = async ( + formData: FormData, +): Promise> => { + return http.post>('/travels', formData); }; diff --git a/src/api/travel/travels.ts b/src/api/travel/travels.ts index 6d19a9df..9b55346d 100644 --- a/src/api/travel/travels.ts +++ b/src/api/travel/travels.ts @@ -1,47 +1,46 @@ import { Filters, Travel, - TravelDetail, - TravelFilterResponse, MyTravel, MyTravelResponse, + TravelCard, } from '@/@types/travel'; import buildTravelUrl from '@/utils/buildTravelUrl'; -import { ApiResponse } from '@/@types/api'; +import { BaseResponse, ListResponse } from '@/@types/api'; import { http } from '../fetcher'; export const postTravelParticipation = (travelId: number) => { - return http.post(`/travels/${travelId}/participation`); + return http.post(`/travels/${travelId}/participation`); }; export const deleteTravelParticipation = (travelId: number) => { - return http.delete(`/travels/${travelId}/participation`); + return http.delete(`/travels/${travelId}/participation`); }; export const deleteTravel = (travelId: number) => { - return http.delete(`/travels/${travelId}`); + return http.delete(`/travels/${travelId}`); }; export const getPopularTravel = () => { - return http.get>('/travels/popular'); + return http.get>('/travels/popular'); }; export const getTravelDetail = ({ id }: { id: string }) => { - return http.get>(`/travels/${id}`); + return http.get>(`/travels/${id}`); }; export const getTravels = (props: Filters & { pageParam?: number }) => { const { pageParam, ...filters } = props; const url = buildTravelUrl(filters, pageParam); - return http.get>(url); + return http.get>(url); }; export const postTravelBookMark = (id: number) => { - return http.post(`/travels/bookmark?travelId=${id}`); + return http.post(`/travels/bookmark?travelId=${id}`); }; export const deleteTravelBookMark = (id: number) => { - return http.delete(`/travels/bookmark?travelId=${id}`); + return http.delete(`/travels/bookmark?travelId=${id}`); }; /* ----------------------------- Apis in mypage ----------------------------- */ diff --git a/src/api/user/editProfile.ts b/src/api/user/editProfile.ts index db031ac4..6433b0fa 100644 --- a/src/api/user/editProfile.ts +++ b/src/api/user/editProfile.ts @@ -1,5 +1,6 @@ +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; export const editProfile = (formData: FormData) => { - return http.put('/users', formData); + return http.put>('/users', formData); }; diff --git a/src/api/user/userInfo.ts b/src/api/user/userInfo.ts index 4e6d18b8..ced7d56c 100644 --- a/src/api/user/userInfo.ts +++ b/src/api/user/userInfo.ts @@ -1,11 +1,7 @@ -import { MyPageProfile } from '@/@types/user'; +import { BaseResponse } from '@/@types/api'; +import { User } from '@/@types/user'; import { http } from '../fetcher'; -export interface UserInfoResponse { - status: string; - data: MyPageProfile; -} - export const getUserInfo = () => { - return http.get('/users'); + return http.get>('/users'); }; diff --git a/src/api/user/userList.ts b/src/api/user/userList.ts index 9d766c72..a640c613 100644 --- a/src/api/user/userList.ts +++ b/src/api/user/userList.ts @@ -1,7 +1,13 @@ -import { UserList } from '@/@types/user'; -import { ApiResponse } from '@/@types/api'; +import { User } from '@/@types/user'; +import { BaseResponse } from '@/@types/api'; import { http } from '../fetcher'; +interface Props extends Pick { + openTravelCount: number; + reviewCount: number; + hashTags: string; +} + export const getPopularUser = () => { - return http.get>('/users/popular'); + return http.get>('/users/popular'); }; diff --git a/src/app/(chat)/chat/page.tsx b/src/app/(chat)/chat/page.tsx index b1665d8d..2c8ab8e7 100644 --- a/src/app/(chat)/chat/page.tsx +++ b/src/app/(chat)/chat/page.tsx @@ -6,7 +6,6 @@ import ChatRoomsContainer from '@/components/chat/chatRoomList/ChatRoomsContaine import ChatRoomContainer from '@/components/chat/chatRoom/ChatRoomContainer'; import { useState, useEffect } from 'react'; import { useChatRooms } from '@/hooks/useChatRooms'; -import { RoomResponse } from '@/@types/chat'; import MainNavigation from '@/components/nav/MainNavigation'; import PCHeader from '@/components/header/PCHeader'; import { useWebSocketStore } from '@/store/useWebSocketStore'; @@ -83,15 +82,15 @@ const ChatRoomsPage = () => { chatRoomsData={chatRoomsData} /> -
- -
+ {chatRoomId && selectedRoom && ( +
+ +
+ )}
getReviewDetail(id), }); - const data = queryClient.getQueryData([ + const data = queryClient.getQueryData>([ 'reviews', 'detail', id, @@ -26,7 +27,7 @@ const ReviewDetailPage = async ({ return (
- + {data && }
); }; diff --git a/src/components/a11y/ListSkip.tsx b/src/components/a11y/ListSkip.tsx new file mode 100644 index 00000000..db4a13c8 --- /dev/null +++ b/src/components/a11y/ListSkip.tsx @@ -0,0 +1,43 @@ +interface Props { + skipId: string; + skipLabel: string; + currentElement: number; + onClick?: () => void; +} + +export const ListSkip = { + /** + * 리스트 탈출을 위한 링크 컴포넌트 + */ + Link: ({ currentElement, skipId, skipLabel, onClick }: Partial) => { + const pageInfo = currentElement + ? ` (현재 ${currentElement} : ''}번째 요소)` + : ''; + + return ( + + ); + }, + + /** + * 리스트 탈출 후 도착 지점 컴포넌트 + */ + Destination: ({ skipId, skipLabel }: Partial) => { + return ( +
+ ); + }, +}; diff --git a/src/components/auth/form/ResetAuthPasswordForm.tsx b/src/components/auth/form/ResetAuthPasswordForm.tsx index 6211f319..b52b1406 100644 --- a/src/components/auth/form/ResetAuthPasswordForm.tsx +++ b/src/components/auth/form/ResetAuthPasswordForm.tsx @@ -31,7 +31,7 @@ const ResetAuthPassword = () => { resetAuthPassword({ email: email ?? '', - password: password.value, + newPassword: password.value, token: token ?? '', }); }; diff --git a/src/components/auth/input/AuthEmailCertification.test.tsx b/src/components/auth/input/AuthEmailCertification.test.tsx index 0186bb5d..13ed67ab 100644 --- a/src/components/auth/input/AuthEmailCertification.test.tsx +++ b/src/components/auth/input/AuthEmailCertification.test.tsx @@ -215,15 +215,20 @@ describe('AuthEmailCertification', () => { fireEvent.click(confirmButton); await waitFor(() => { - const emailInput = screen.getByPlaceholderText(AUTH_PLACEHOLDER.email); - const codeInput = screen.getByPlaceholderText( - AUTH_PLACEHOLDER.verifyNumber, - ); + expect( + screen.getByPlaceholderText(AUTH_PLACEHOLDER.email), + ).toBeDisabled(); + }); - expect(emailInput).toBeDisabled(); - expect(codeInput).toBeDisabled(); - expect(confirmButton).toBeDisabled(); + const resendButton = screen.getByRole('button', { name: '재전송' }); + const confirmButtonAfterCertification = screen.getByRole('button', { + name: '확인', }); + + expect(resendButton).toHaveClass('disabled:bg-background-alternative'); + expect(confirmButtonAfterCertification).toHaveClass( + 'disabled:bg-background-alternative', + ); }); it('재전송 버튼 클릭 시 타이머가 리셋되어야 한다', async () => { diff --git a/src/components/auth/input/AuthPassword.tsx b/src/components/auth/input/AuthPassword.tsx index 29a73d1d..9c9f0bea 100644 --- a/src/components/auth/input/AuthPassword.tsx +++ b/src/components/auth/input/AuthPassword.tsx @@ -6,12 +6,12 @@ import { AUTH_ERROR_MESSAGE, AUTH_PLACEHOLDER, } from '@/constants/auth'; -import type { PasswordInput as PasswordInputType } from '@/@types/auth'; +import type { AuthInput } from '@/@types/auth'; import PasswordInput from '@/components/common/input/PasswordInput'; import { memo } from 'react'; interface Props { - name: keyof PasswordInputType; + name: keyof AuthInput; value: string; isValid: boolean | null; important?: boolean; diff --git a/src/components/auth/input/AuthText.tsx b/src/components/auth/input/AuthText.tsx index e6a331d8..02c33cf1 100644 --- a/src/components/auth/input/AuthText.tsx +++ b/src/components/auth/input/AuthText.tsx @@ -8,12 +8,12 @@ import { AUTH_MAX_LENGTH, } from '@/constants/auth'; import TextInput from '@/components/common/input/TextInput'; -import type { TextInput as TextInputType } from '@/@types/auth'; +import type { AuthInput } from '@/@types/auth'; import { memo } from 'react'; interface Props { type: 'text' | 'email' | 'tel' | 'number'; - name: keyof TextInputType; + name: keyof AuthInput; value: string; isValid: boolean | null; disabled?: boolean; diff --git a/src/components/card/Review/ReviewCard.test.tsx b/src/components/card/Review/ReviewCard.test.tsx index 0ab7f3ae..3ece8bb7 100644 --- a/src/components/card/Review/ReviewCard.test.tsx +++ b/src/components/card/Review/ReviewCard.test.tsx @@ -32,13 +32,13 @@ describe('리뷰 카드 컴포넌트', () => { const props = { reviewId: 1, profileImage: '/user.jpg', - image: '/test.png', + reviewImage: '/test.png', title: '테스트 리뷰', content: '이것은 테스트 리뷰 내용입니다.', - score: 4.5, + starRating: 4.5, travelLocation: '서울', createdAt: '2023-10-10', - isLiked: false, + likesFlag: false, }; it('리뷰 카드가 올바르게 렌더링되어야 합니다 (리뷰 페이지)', () => { diff --git a/src/components/card/Review/ReviewCard.tsx b/src/components/card/Review/ReviewCard.tsx index 7dd114f1..ba1e7374 100644 --- a/src/components/card/Review/ReviewCard.tsx +++ b/src/components/card/Review/ReviewCard.tsx @@ -5,34 +5,24 @@ import StarIcon from '@/assets/icon/star_20px.svg'; import UserIcon from '@/components/common/user/UserIcon'; import { useState } from 'react'; import { formatDateToShortWithDay } from '@/utils/dateChangeKr'; +import { Review } from '@/@types/review'; import ReviewHeart from './ReviewHeart'; -interface Props { - reviewId: number; - nickname?: string; - profileImage?: string; - image: string; - title: string; - content: string; - score: number; - travelLocation: string; - createdAt: string; - isLiked?: boolean; -} +type Props = Partial; const ReviewCard = ({ reviewId, nickname, profileImage, - image, + reviewImage, title, content, - score, + starRating, travelLocation, createdAt, - isLiked, + likesFlag, }: Props) => { - const [isLikedState, setIsLikedState] = useState(isLiked); + const [isLikedState, setIsLikedState] = useState(likesFlag); return (
{title} { @@ -52,9 +42,9 @@ const ReviewCard = ({ }} /> - {isLiked !== undefined && ( + {likesFlag !== undefined && ( @@ -89,7 +79,7 @@ const ReviewCard = ({
-

{score}

+

{starRating}

diff --git a/src/components/card/Review/ReviewCardAddText.test.tsx b/src/components/card/Review/ReviewCardAddText.test.tsx index 112ca04c..ab61c24b 100644 --- a/src/components/card/Review/ReviewCardAddText.test.tsx +++ b/src/components/card/Review/ReviewCardAddText.test.tsx @@ -6,7 +6,7 @@ const mockReveiwAndText = { nickname: '녹차', reviewImage: 'https://example.com/image.jpg', content: '맛있는 녹차', - score: 5, + starRating: 5, }; const renderReviewCard = (overrides = {}) => { @@ -19,7 +19,7 @@ const renderReviewCard = (overrides = {}) => { nickname={props.nickname} reviewImage={props.reviewImage} content={props.content} - score={props.score} + starRating={props.starRating} />, ); }; @@ -52,7 +52,7 @@ describe('ReviewCardAddText', () => { }); it('점수가 없을 경우 점수 텍스트가 렌더링되지 않습니다', () => { - renderReviewCard({ score: undefined }); + renderReviewCard({ starRating: undefined }); const score = screen.queryByText('5'); expect(score).not.toBeInTheDocument(); }); diff --git a/src/components/card/Review/ReviewCardAddText.tsx b/src/components/card/Review/ReviewCardAddText.tsx index b63df0d0..8e346cb4 100644 --- a/src/components/card/Review/ReviewCardAddText.tsx +++ b/src/components/card/Review/ReviewCardAddText.tsx @@ -2,14 +2,16 @@ import { Review } from '@/@types/review'; import StartIcon from '@/assets/blue_star.svg'; import Image from 'next/image'; -interface Props - extends Pick {} +type Props = Pick< + Review, + 'nickname' | 'reviewImage' | 'content' | 'starRating' +>; const ReviewCardAddText = ({ nickname, reviewImage, content, - score, + starRating, }: Props) => { return (
@@ -32,7 +34,7 @@ const ReviewCardAddText = ({ {nickname} - {score} + {starRating}
diff --git a/src/components/card/travel/TravelCard.test.tsx b/src/components/card/travel/TravelCard.test.tsx index 99a40df0..8033b482 100644 --- a/src/components/card/travel/TravelCard.test.tsx +++ b/src/components/card/travel/TravelCard.test.tsx @@ -7,13 +7,14 @@ const mock = { travelId: 12, isDomestic: true, travelName: '부여로 떠나는 다함께 시골투어', - location: '충남 부여', + travelLocation: '충남 부여', maxTravelMateCount: 6, currentTravelMateCount: 1, startAt: '2001-12-03', endAt: '2001-12-20', formattedStartDate: '2001-12-03', - image: '/test/travel/test1.png', + travelImage: '/test/travel/test1.png', + bookmarkFlag: false, }; const renderTravelCard = (overrides = {}) => { @@ -29,13 +30,14 @@ const renderTravelCard = (overrides = {}) => { travelId={props.travelId} isDomestic={props.isDomestic} travelName={props.travelName} - location={props.location} + travelLocation={props.travelLocation} maxTravelMateCount={props.maxTravelMateCount} currentTravelMateCount={props.currentTravelMateCount} startAt={props.startAt} endAt={props.endAt} formattedStartDate={props.formattedStartDate} - image={props.image} + travelImage={props.travelImage} + bookmarkFlag={props.bookmarkFlag} /> , ); @@ -83,7 +85,7 @@ describe('TravelCard를 렌더링 합니다', () => { describe('TravelCard가 올바르게 렌더링 되지 않습니다', () => { it('여행 이미지가 없을 경우, 이미지를 렌더링하지 않습니다', () => { - renderTravelCard({ image: '' }); + renderTravelCard({ travelImage: '' }); const travelImage = screen.queryByAltText( '부여로 떠나는 다함께 시골투어 - 충남 부여 여행 이미지', ); @@ -97,8 +99,8 @@ describe('TravelCard가 올바르게 렌더링 되지 않습니다', () => { }); it('여행 위치가 없을 경우, 렌더링하지 않습니다', () => { - renderTravelCard({ location: '' }); - const travelLocation = screen.queryByText(mock.location); + renderTravelCard({ travelLocation: '' }); + const travelLocation = screen.queryByText(mock.travelLocation); expect(travelLocation).not.toBeInTheDocument(); }); diff --git a/src/components/card/travel/TravelCard.tsx b/src/components/card/travel/TravelCard.tsx index a44ab059..fe5184f8 100644 --- a/src/components/card/travel/TravelCard.tsx +++ b/src/components/card/travel/TravelCard.tsx @@ -1,7 +1,7 @@ import Image from 'next/image'; import Location from '@/assets/location.svg'; import ProfileICon from '@/assets/profile.svg'; -import { Travel } from '@/@types/travel'; +import { TravelCard as TravelCardProps } from '@/@types/travel'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import cn from '@/utils/cn'; @@ -15,26 +15,24 @@ import ProgressBar from '../../common/progressbar/ProgressBar'; import ExpiredTag from '../../common/tag/ExpiredTag'; import CheckMarkButton from '../../common/button/CheckMarkButton'; -interface Props extends Omit { +interface Props extends TravelCardProps { closed?: boolean; - checkMark?: boolean; - isChecked?: boolean; + formattedStartDate?: string; } const TravelCard = ({ travelId, isDomestic, travelName, - location, + travelLocation, maxTravelMateCount, currentTravelMateCount, formattedStartDate, - image, + travelImage, closed, - checkMark, - isChecked, + bookmarkFlag, }: Props) => { - const [isCheckedState, setIsCheckedState] = useState(isChecked); + const [isCheckedState, setIsCheckedState] = useState(bookmarkFlag); const [animate, setAnimate] = useState(false); const progressRate = useMemo( @@ -79,8 +77,8 @@ const TravelCard = ({ )} > {`${travelName} )} - {checkMark && ( + {isCheckedState !== null && ( - {location} + {travelLocation} diff --git a/src/components/card/travel/TravelCardBig.tsx b/src/components/card/travel/TravelCardBig.tsx index 0fbba885..8038aff0 100644 --- a/src/components/card/travel/TravelCardBig.tsx +++ b/src/components/card/travel/TravelCardBig.tsx @@ -3,7 +3,7 @@ import Image from 'next/image'; import Location from '@/assets/location.svg'; import ProfileICon from '@/assets/profile.svg'; -import { Travel } from '@/@types/travel'; +import { TravelCard } from '@/@types/travel'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import cn from '@/utils/cn'; @@ -17,25 +17,24 @@ import ProgressBar from '../../common/progressbar/ProgressBar'; import ExpiredTag from '../../common/tag/ExpiredTag'; import CheckMarkButton from '../../common/button/CheckMarkButton'; -interface Props extends Travel { +interface Props extends TravelCard { closed?: boolean; - isBookmark: boolean | null; } const TravelCardBig = ({ travelId, isDomestic, travelName, - location, + travelLocation, maxTravelMateCount, currentTravelMateCount, startAt, endAt, - image, + travelImage, closed, - isBookmark, + bookmarkFlag, }: Props) => { - const [isCheckedState, setIsCheckedState] = useState(isBookmark); + const [isCheckedState, setIsCheckedState] = useState(bookmarkFlag); const [animate, setAnimate] = useState(false); const progressRate = useMemo( @@ -75,12 +74,13 @@ const TravelCardBig = ({ })} > {`${travelName} { + onLoad={(event) => { + const img = event.currentTarget; img.classList.remove('opacity-0'); img.classList.add('opacity-100'); }} @@ -112,7 +112,7 @@ const TravelCardBig = ({
- {location} + {travelLocation}
diff --git a/src/components/card/travel/TravelPlanCard.test.tsx b/src/components/card/travel/TravelPlanCard.test.tsx index 1699bec4..19a76ec8 100644 --- a/src/components/card/travel/TravelPlanCard.test.tsx +++ b/src/components/card/travel/TravelPlanCard.test.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'; import TravelPlanCard from './TravelPlanCard'; const mockPlan = { - image: 'https://example.com/image.jpg', + travelPlanImage: 'https://example.com/image.jpg', destination: '대전 성심당', description: '웨이팅 1시간 예상, 빵 종류별로 사기', }; @@ -15,7 +15,7 @@ const renderTravelPlanCard = (overrides = {}) => { }; render( , diff --git a/src/components/card/travel/TravelPlanCard.tsx b/src/components/card/travel/TravelPlanCard.tsx index 4b7deeb9..00c09b14 100644 --- a/src/components/card/travel/TravelPlanCard.tsx +++ b/src/components/card/travel/TravelPlanCard.tsx @@ -3,7 +3,7 @@ import LocationIcon from '@/assets/location.svg'; import Image from 'next/image'; const TravelPlanCard = ({ - image, + travelPlanImage, destination, description, }: Omit) => { @@ -11,7 +11,7 @@ const TravelPlanCard = ({
{`${destination} { render( { + openTravelCount: number; + reviewCount: number; + hashTags: string; +} + const UserCard = ({ - nickName, + nickname, profileImage, openTravelCount, reviewCount, hashTags, userId, -}: UserList) => { +}: Props) => { return ( - +
-

{nickName}

+

{nickname}

모임장 {openTravelCount}회 • 리뷰 {reviewCount}개

diff --git a/src/components/chat/chatRoom/ChatRoom.tsx b/src/components/chat/chatRoom/ChatRoom.tsx index a92e98d9..208c2677 100644 --- a/src/components/chat/chatRoom/ChatRoom.tsx +++ b/src/components/chat/chatRoom/ChatRoom.tsx @@ -5,7 +5,6 @@ import HamburgerMenu from '@/assets/menu.svg'; import Header from '@/components/common/header/Header'; import 'swiper/css'; import 'swiper/css/navigation'; -import { ChatOverview, ChatMessage } from '@/@types/chat'; import ChatSideBar from '@/components/chat/chatRoom/ChatSideBar'; import ChatAlbum from '@/components/chat/chatRoom/ChatAlbum'; import ChatImageViewer from '@/components/chat/chatRoom/ChatImageViewer'; @@ -28,8 +27,6 @@ const ChatRoom = ({ chatId, onCloseChatRoom }: Props) => { const previousScrollTopRef = useRef(null); const { data } = useGetUser(); - const nickname = data?.nickname; - const { chatUpdates } = useWebSocketStore(); const { @@ -89,7 +86,8 @@ const ChatRoom = ({ chatId, onCloseChatRoom }: Props) => { ); } - if (!chatInfo || !chatOverview) return ; + if (!chatInfo || !chatOverview || !data) return ; + const { nickname } = data; return ( <> @@ -114,9 +112,9 @@ const ChatRoom = ({ chatId, onCloseChatRoom }: Props) => { {hasNextPage && !isFetchingNextPage ?
: null} @@ -126,8 +124,8 @@ const ChatRoom = ({ chatId, onCloseChatRoom }: Props) => { onHeightChange={setTextareaHeight} /> void; } diff --git a/src/components/chat/chatRoom/ChatRoomEntrance.tsx b/src/components/chat/chatRoom/ChatRoomEntrance.tsx index 413d26f8..8fe6fe0a 100644 --- a/src/components/chat/chatRoom/ChatRoomEntrance.tsx +++ b/src/components/chat/chatRoom/ChatRoomEntrance.tsx @@ -1,6 +1,6 @@ import Header from '@/components/common/header/Header'; import Image from 'next/image'; -import { RoomResponse } from '@/@types/chat'; +import { ChatRoom } from '@/@types/chat'; import { Button } from '@/components/common/button/Button'; import { useSetIsJoined } from '@/queries/chat/useSetChat'; import UserIcon from '@/components/common/user/UserIcon'; @@ -8,7 +8,7 @@ import { useWebSocketStore } from '@/store/useWebSocketStore'; interface Props { chatId: string; - chatRoomData: RoomResponse; + chatRoomData: ChatRoom; onCloseChatRoom: () => void; } diff --git a/src/components/chat/chatRoom/ChatSideBar.tsx b/src/components/chat/chatRoom/ChatSideBar.tsx index 6f2177a4..a0c5ec2a 100644 --- a/src/components/chat/chatRoom/ChatSideBar.tsx +++ b/src/components/chat/chatRoom/ChatSideBar.tsx @@ -50,7 +50,7 @@ const ChatSideBar = ({ if ( update.status === 'JOIN' && update.participant && - !participants.some((p) => p.user === update.participant?.user) + !participants.some((p) => p.nickname === update.participant?.nickname) ) { setParticipants((prev) => [...prev, update.participant!]); } @@ -116,19 +116,19 @@ const ChatSideBar = ({ - {nickname === participant.user && ( + {nickname === participant.nickname && (
ME
)}
- {participant.user} + {participant.nickname}
))} diff --git a/src/components/chat/chatRoomList/ChatRoomItem.tsx b/src/components/chat/chatRoomList/ChatRoomItem.tsx index aef5165b..937fddff 100644 --- a/src/components/chat/chatRoomList/ChatRoomItem.tsx +++ b/src/components/chat/chatRoomList/ChatRoomItem.tsx @@ -6,13 +6,13 @@ import Other from '@/assets/other.svg'; import Chat from '@/assets/chat_blue.svg'; import { useEffect, useState } from 'react'; import { Button } from '@/components/common/button/Button'; -import { RoomResponse } from '@/@types/chat'; +import { ChatRoom } from '@/@types/chat'; import { formatDateToStringWithDot } from '@/utils/calendarHelper'; import { useWebSocketStore } from '@/store/useWebSocketStore'; import { useLeaveChat } from '@/queries/chat/useSetChat'; interface Props { - room: RoomResponse; + room: ChatRoom; onChatRoomId: (chatId: string) => void; selectedChatRoomId: string; } diff --git a/src/components/chat/chatRoomList/ChatRoomList.tsx b/src/components/chat/chatRoomList/ChatRoomList.tsx index 2fa8a25e..961fc780 100644 --- a/src/components/chat/chatRoomList/ChatRoomList.tsx +++ b/src/components/chat/chatRoomList/ChatRoomList.tsx @@ -1,8 +1,8 @@ import ChatRoomItem from '@/components/chat/chatRoomList/ChatRoomItem'; -import { RoomResponse } from '@/@types/chat'; +import { ChatRoom } from '@/@types/chat'; interface Props { - rooms: RoomResponse[]; + rooms: ChatRoom[]; onChatRoomId: (chatId: string) => void; chatRoomId: string; } diff --git a/src/components/chat/chatRoomList/ChatRoomsContainer.tsx b/src/components/chat/chatRoomList/ChatRoomsContainer.tsx index 2a924a14..e4cb7776 100644 --- a/src/components/chat/chatRoomList/ChatRoomsContainer.tsx +++ b/src/components/chat/chatRoomList/ChatRoomsContainer.tsx @@ -3,16 +3,16 @@ import ChatHeader from '@/components/chat/chatRoomList/ChatHeader'; import ChatRoomList from '@/components/chat/chatRoomList/ChatRoomList'; import Chat from '@/assets/chat_gray.svg'; -import { RoomResponse, SortType } from '@/@types/chat'; +import { ChatRoom, SortType } from '@/@types/chat'; import ChatRoomListSkeleton from '@/components/chat/skeleton/ChatRoomListSkeleton'; interface Rooms { - data: RoomResponse[]; + data: ChatRoom[]; } interface ChatRooms { data: Rooms | undefined; - sortedRooms: RoomResponse[] | null; + sortedRooms: ChatRoom[] | null; currentSort: SortType; isFetching: boolean; isLoading: boolean; @@ -70,7 +70,7 @@ const ChatRoomsContainer = ({ {!isLoading && sortedRooms && sortedRooms.length > 0 && ( diff --git a/src/components/common/tag/DomesticTag.stories.tsx b/src/components/common/tag/DomesticTag.stories.tsx new file mode 100644 index 00000000..3a01a287 --- /dev/null +++ b/src/components/common/tag/DomesticTag.stories.tsx @@ -0,0 +1,32 @@ +import { Meta, StoryObj } from '@storybook/react'; +import DomesticTag from './DomesticTag'; + +const meta = { + title: 'Components/Common/tag', + component: DomesticTag, + tags: ['autodocs'], + argTypes: { + isDomestic: { + control: 'boolean', + description: '여행 타입을 설정합니다. true는 국내, false는 해외입니다.', + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const Template = (args: { isDomestic: boolean }) => ( +
+ + +
+); + +export const AllVariants: Story = { + render: Template, + args: { + isDomestic: true, + }, +}; diff --git a/src/components/common/user/UserIconList.test.tsx b/src/components/common/user/UserIconList.test.tsx index 6d469a66..4872b125 100644 --- a/src/components/common/user/UserIconList.test.tsx +++ b/src/components/common/user/UserIconList.test.tsx @@ -5,7 +5,7 @@ import UserIconList from './UserIconList'; const mock = { participant: [ { - id: 1, + userId: 1, nickname: '이름', role: 'role', profileImage: 'https://example.com/image.jpg', @@ -16,31 +16,31 @@ const mock = { const mockList = { participant: [ { - id: 1, + userId: 1, nickname: '이름', role: 'role', profileImage: 'https://example.com/image.jpg', }, { - id: 2, + userId: 2, nickname: '이름', role: 'role', profileImage: 'https://example.com/image.jpg', }, { - id: 3, + userId: 3, nickname: '이름', role: 'role', profileImage: 'https://example.com/image.jpg', }, { - id: 4, + userId: 4, nickname: '4번', role: 'role', profileImage: 'https://example.com/image.jpg', }, { - id: 5, + userId: 5, nickname: '마지막', role: 'role', profileImage: 'https://example.com/image.jpg', diff --git a/src/components/common/user/UserIconList.tsx b/src/components/common/user/UserIconList.tsx index 9bb404cf..9cb2ceb1 100644 --- a/src/components/common/user/UserIconList.tsx +++ b/src/components/common/user/UserIconList.tsx @@ -10,7 +10,7 @@ const UserIconList = ({ participant }: { participant: Participant[] }) => {
{visibleUsers.map((user, index) => (
{ - const [isBookmarked, setIsBookmarked] = useState(participant); - - const onClickBookMark = () => { - setIsBookmarked((prev) => !prev); - }; - - const hashTagList = hashTags - .split('#') - .filter((tag) => tag !== '') - .map((tag) => tag.trim()); - - const userId = 4; - - return ( -
-
-
- -
-
{organizer && organizer.nickname}
-
2시간 전 업로드
-
-
- -
- {organizer?.id !== userId && ( - - )} - - -
-
-
-
{description}
-
- {hashTagList.map((tag) => ( - - ))} -
-
-
- ); -}; - -export default PreviewSelectTravelDetail; diff --git a/src/components/createTrip/travelPreview/PreviewSelectTravelReview.tsx b/src/components/createTrip/travelPreview/PreviewSelectTravelReview.tsx deleted file mode 100644 index 2dd155fc..00000000 --- a/src/components/createTrip/travelPreview/PreviewSelectTravelReview.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import BlankIcon from '@/assets/blank.svg'; - -const PreviewSelectTravelReview = () => { - return ( -
- -
아직 작성된 리뷰가 없어요!
-
- ); -}; - -export default PreviewSelectTravelReview; diff --git a/src/components/createTrip/travelPreview/PreviewTravelDetailCategory.tsx b/src/components/createTrip/travelPreview/PreviewTravelDetailCategory.tsx deleted file mode 100644 index ad5515a6..00000000 --- a/src/components/createTrip/travelPreview/PreviewTravelDetailCategory.tsx +++ /dev/null @@ -1,86 +0,0 @@ -'use client'; - -import { TravelDetail } from '@/@types/travel'; -import { useState } from 'react'; -import SelectTravelItinerary from '@/components/travel/detail/category/TabTravelItinerary'; -import { Button } from '@/components/common/button/Button'; -import PreviewSelectTravelReview from './PreviewSelectTravelReview'; -import PreviewSelectTravelDetail from './PreviewSelectTravelDetail'; - -type PreviewTravelDetailCategoryType = Pick< - TravelDetail, - | 'hashTags' - | 'participant' - | 'description' - | 'tripDuration' - | 'travelPlan' - | 'startAt' ->; - -interface Props extends PreviewTravelDetailCategoryType { - onSubmit: () => void; -} - -const PreviewTravelDetailCategory = ({ - hashTags, - participant, - description, - tripDuration, - travelPlan, - startAt, - onSubmit, -}: Props) => { - const [category, setCategory] = useState(0); - const onClickCategory = (index: number) => { - setCategory(index); - }; - const organizer = participant.find((part) => part.role === 'ORGANIZER'); - const selectCss = 'border-b-[3px] border-label-normal text-label-normal'; - return ( -
-
- - - -
-
- {category === 0 && ( - - )} - {category === 1 && ( - - )} - {category === 2 && } - -
-
- ); -}; -export default PreviewTravelDetailCategory; diff --git a/src/components/header/HomeHeader.tsx b/src/components/header/HomeHeader.tsx index 125ada06..bebf0ee8 100644 --- a/src/components/header/HomeHeader.tsx +++ b/src/components/header/HomeHeader.tsx @@ -74,7 +74,7 @@ const HomeHeader = () => { {user?.nickname}님 { {user?.nickname}님 { {user ? ( { +const WeeklyPopular = ({ travelList }: { travelList: TravelCard[] }) => { if (travelList.length === 0) { return (
@@ -30,15 +30,15 @@ const WeeklyPopular = ({ travelList }: { travelList: Travel[] }) => { ))}
diff --git a/src/components/main/weeklyTravel/WeeklyPopularContainer.tsx b/src/components/main/weeklyTravel/WeeklyPopularContainer.tsx index 0b217eef..3a7f93b7 100644 --- a/src/components/main/weeklyTravel/WeeklyPopularContainer.tsx +++ b/src/components/main/weeklyTravel/WeeklyPopularContainer.tsx @@ -13,6 +13,7 @@ const WeeklyPopularContainer = async () => { queryKey: QUERY_KEYS.TRAVEL.POPULAR_TRAVEL, queryFn: getPopularTravel, }); + return ( diff --git a/src/components/main/weeklyUser/WeeklyUser.tsx b/src/components/main/weeklyUser/WeeklyUser.tsx index 9e4624cf..5ee60cc8 100644 --- a/src/components/main/weeklyUser/WeeklyUser.tsx +++ b/src/components/main/weeklyUser/WeeklyUser.tsx @@ -3,13 +3,19 @@ import { Swiper, SwiperSlide } from 'swiper/react'; import 'swiper/css'; import 'swiper/css/pagination'; -import { UserList } from '@/@types/user'; +import { User } from '@/@types/user'; import Link from 'next/link'; import ButtonRounded from '@/components/common/button/ButtonRounded'; import UserCard from '../../card/user/UserCard'; import WeeklyUserHeader from './WeeklyUserHeader'; -const WeeklyUser = ({ userList }: { userList: UserList[] }) => { +interface Props extends Pick { + openTravelCount: number; + reviewCount: number; + hashTags: string; +} + +const WeeklyUser = ({ userList }: { userList: Props[] }) => { if (userList.length === 0) { return (
@@ -51,7 +57,7 @@ const WeeklyUser = ({ userList }: { userList: UserList[] }) => { > { > {travels && travels.total > 0 ? (
- {travels.content.map((travel: TravelList) => ( + {travels.content.map((travel) => (
{ maxTravelMateCount={travel.maxTravelMateCount} currentTravelMateCount={travel.currentTravelMateCount} isDomestic={travel.isDomestic} - location={travel.location} - image={travel.image} + travelLocation={travel.travelLocation} + travelImage={travel.travelImage} startAt={travel.startAt} endAt={travel.endAt} formattedStartDate={checkTomorrow(travel.startAt)} + bookmarkFlag closed /> diff --git a/src/components/mypage/contents/content/myReview/Written.tsx b/src/components/mypage/contents/content/myReview/Written.tsx index 4504dbf0..4f88711c 100644 --- a/src/components/mypage/contents/content/myReview/Written.tsx +++ b/src/components/mypage/contents/content/myReview/Written.tsx @@ -23,10 +23,10 @@ const Written = () => { diff --git a/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.test.tsx b/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.test.tsx index 19233e81..3d008441 100644 --- a/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.test.tsx +++ b/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.test.tsx @@ -30,8 +30,8 @@ describe('MySelfTravel', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '서울', - image: 'https://example.com/image.jpg', + travelLocation: '서울', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, @@ -70,8 +70,8 @@ describe('MySelfTravel', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '서울', - image: 'https://example.com/image.jpg', + travelLocation: '서울', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, @@ -81,8 +81,8 @@ describe('MySelfTravel', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '부산', - image: 'https://example.com/image.jpg', + travelLocation: '부산', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, diff --git a/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.tsx b/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.tsx index 4711f812..b45e297f 100644 --- a/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.tsx +++ b/src/components/mypage/contents/content/mySelfTravel/MySelfTravel.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import TravelCard from '@/components/card/travel/TravelCard'; import Pagination from '@/components/common/pagination/Pagination'; import { useMySelfTravel } from '@/queries/travel/useGetMyTravel'; -import { TravelList } from '@/@types/travel'; +import { TravelCard as TravelCardProps } from '@/@types/travel'; import HorizontalDivider from '@/components/common/divider/HorizontalDivider'; import MyTravelCardSkeleton from '@/components/mypage/skeleton/MyTravelCardSkeleton'; import NoTravel from '../myTravel/NoTravel'; @@ -28,7 +28,7 @@ const MySelfTravel = () => { > {travels && travels.data.total > 0 ? (
- {travels.data.content.map((travel: TravelList) => ( + {travels.data?.content.map((travel: TravelCardProps) => (
{ maxTravelMateCount={travel.maxTravelMateCount} currentTravelMateCount={travel.currentTravelMateCount} isDomestic={travel.isDomestic} - location={travel.location} - image={travel.image} + travelLocation={travel.travelLocation} + travelImage={travel.travelImage} startAt={travel.startAt} endAt={travel.endAt} + bookmarkFlag={travel.bookmarkFlag} formattedStartDate={checkTomorrow(travel.startAt)} /> diff --git a/src/components/mypage/contents/content/myTravel/CheckedTravel.test.tsx b/src/components/mypage/contents/content/myTravel/CheckedTravel.test.tsx index aa15406d..f6423ab5 100644 --- a/src/components/mypage/contents/content/myTravel/CheckedTravel.test.tsx +++ b/src/components/mypage/contents/content/myTravel/CheckedTravel.test.tsx @@ -30,8 +30,8 @@ describe('CheckedTravel', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '서울', - image: 'https://example.com/image.jpg', + travelLocation: '서울', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, @@ -70,8 +70,8 @@ describe('CheckedTravel', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '서울', - image: 'https://example.com/image.jpg', + travelLocation: '서울', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, @@ -81,8 +81,8 @@ describe('CheckedTravel', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '부산', - image: 'https://example.com/image.jpg', + travelLocation: '부산', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, diff --git a/src/components/mypage/contents/content/myTravel/CheckedTravel.tsx b/src/components/mypage/contents/content/myTravel/CheckedTravel.tsx index 4f7bb639..8f132286 100644 --- a/src/components/mypage/contents/content/myTravel/CheckedTravel.tsx +++ b/src/components/mypage/contents/content/myTravel/CheckedTravel.tsx @@ -3,7 +3,7 @@ import Pagination from '@/components/common/pagination/Pagination'; import { checkTomorrow } from '@/utils/dateChangeKr'; import { useState } from 'react'; import { useCheckedTravel } from '@/queries/travel/useGetMyTravel'; -import { TravelList } from '@/@types/travel'; +import { TravelCard as TravelCardProps } from '@/@types/travel'; import HorizontalDivider from '@/components/common/divider/HorizontalDivider'; import MyTravelCardSkeleton from '@/components/mypage/skeleton/MyTravelCardSkeleton'; import NoTravel from './NoTravel'; @@ -28,7 +28,7 @@ const CheckedTravel = () => { > {travels && travels.data.total > 0 ? (
- {travels.data.content.map((travel: TravelList) => ( + {travels.data?.content.map((travel: TravelCardProps) => (
{ maxTravelMateCount={travel.maxTravelMateCount} currentTravelMateCount={travel.currentTravelMateCount} isDomestic={travel.isDomestic} - location={travel.location} - image={travel.image} + travelLocation={travel.travelLocation} + travelImage={travel.travelImage} startAt={travel.startAt} endAt={travel.endAt} formattedStartDate={checkTomorrow(travel.startAt)} - checkMark - isChecked + bookmarkFlag={travel.bookmarkFlag} />
diff --git a/src/components/mypage/contents/content/myTravel/PastTravel.tsx b/src/components/mypage/contents/content/myTravel/PastTravel.tsx index 90de9e20..dc6e41b3 100644 --- a/src/components/mypage/contents/content/myTravel/PastTravel.tsx +++ b/src/components/mypage/contents/content/myTravel/PastTravel.tsx @@ -3,7 +3,7 @@ import Pagination from '@/components/common/pagination/Pagination'; import { checkTomorrow } from '@/utils/dateChangeKr'; import { useState } from 'react'; import { usePastTravel } from '@/queries/travel/useGetMyTravel'; -import { TravelList } from '@/@types/travel'; +import { TravelCard as TravelCardProps } from '@/@types/travel'; import HorizontalDivider from '@/components/common/divider/HorizontalDivider'; import MyTravelCardSkeleton from '@/components/mypage/skeleton/MyTravelCardSkeleton'; import NoTravel from './NoTravel'; @@ -28,7 +28,7 @@ const PastTravel = () => { > {travels && travels.data.total > 0 ? (
- {travels.data.content.map((travel: TravelList) => ( + {travels.data?.content.map((travel: TravelCardProps) => (
{ maxTravelMateCount={travel.maxTravelMateCount} currentTravelMateCount={travel.currentTravelMateCount} isDomestic={travel.isDomestic} - location={travel.location} - image={travel.image} + travelLocation={travel.travelLocation} + travelImage={travel.travelImage} startAt={travel.startAt} endAt={travel.endAt} formattedStartDate={checkTomorrow(travel.startAt)} + bookmarkFlag={travel.bookmarkFlag} closed /> diff --git a/src/components/mypage/contents/content/myTravel/Upcomming.test.tsx b/src/components/mypage/contents/content/myTravel/Upcomming.test.tsx index dd24e73d..fb5f1314 100644 --- a/src/components/mypage/contents/content/myTravel/Upcomming.test.tsx +++ b/src/components/mypage/contents/content/myTravel/Upcomming.test.tsx @@ -30,8 +30,8 @@ describe('Upcomming', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '서울', - image: 'https://example.com/image.jpg', + travelLocation: '서울', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, @@ -70,8 +70,8 @@ describe('Upcomming', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '서울', - image: 'https://example.com/image.jpg', + travelLocation: '서울', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, @@ -81,8 +81,8 @@ describe('Upcomming', () => { maxTravelMateCount: 5, currentTravelMateCount: 2, isDomestic: true, - location: '부산', - image: 'https://example.com/image.jpg', + travelLocation: '부산', + travelImage: 'https://example.com/image.jpg', startAt: '2023-10-01', endAt: '2023-10-10', }, diff --git a/src/components/mypage/contents/content/myTravel/Upcomming.tsx b/src/components/mypage/contents/content/myTravel/Upcomming.tsx index 693cec60..a4265524 100644 --- a/src/components/mypage/contents/content/myTravel/Upcomming.tsx +++ b/src/components/mypage/contents/content/myTravel/Upcomming.tsx @@ -3,7 +3,7 @@ import Pagination from '@/components/common/pagination/Pagination'; import { checkTomorrow } from '@/utils/dateChangeKr'; import { useState } from 'react'; import { useUpcommingTravel } from '@/queries/travel/useGetMyTravel'; -import { TravelList } from '@/@types/travel'; +import { TravelCard as TravelCardProps } from '@/@types/travel'; import HorizontalDivider from '@/components/common/divider/HorizontalDivider'; import MyTravelCardSkeleton from '@/components/mypage/skeleton/MyTravelCardSkeleton'; import NoTravel from './NoTravel'; @@ -28,19 +28,20 @@ const Upcomming = () => { > {travels && travels.data.total > 0 ? (
- {travels.data.content.map((travel: TravelList) => ( + {travels.data?.content.map((travel: TravelCardProps) => (
diff --git a/src/components/mypage/profile/ProfileSection.test.tsx b/src/components/mypage/profile/ProfileSection.test.tsx index 4a19cfa0..1dfe007b 100644 --- a/src/components/mypage/profile/ProfileSection.test.tsx +++ b/src/components/mypage/profile/ProfileSection.test.tsx @@ -23,7 +23,7 @@ describe('ProfileSection', () => { (useGetUser as jest.Mock).mockReturnValue({ data: { userId: 1, - image: null, + profileImage: null, nickname: '테스트 사용자', email: 'test@example.com', description: '테스트 설명', @@ -41,7 +41,7 @@ describe('ProfileSection', () => { it('사용자 프로필 이미지가 있을 때 해당 이미지가 렌더링되어야 한다', () => { (useGetUser as jest.Mock).mockReturnValue({ data: { - image: '/path/to/profile/image.jpg', + profileImage: '/path/to/profile/image.jpg', nickname: '테스트 사용자', email: 'test@example.com', description: '테스트 설명', @@ -60,7 +60,7 @@ describe('ProfileSection', () => { it('프로필 수정 버튼이 렌더링되어야 한다', () => { (useGetUser as jest.Mock).mockReturnValue({ data: { - image: null, + profileImage: null, nickname: '테스트 사용자', email: 'test@example.com', description: '테스트 설명', diff --git a/src/components/mypage/profile/ProfileSection.tsx b/src/components/mypage/profile/ProfileSection.tsx index e19af8ae..038e88d5 100644 --- a/src/components/mypage/profile/ProfileSection.tsx +++ b/src/components/mypage/profile/ProfileSection.tsx @@ -32,9 +32,9 @@ const ProfileSection = () => {
- {user?.image ? ( + {user?.profileImage ? ( 프로필 이미지; } const ReviewDetailContainer = ({ data }: Props) => { @@ -26,7 +26,7 @@ const ReviewDetailContainer = ({ data }: Props) => {

{data?.nickname}

@@ -41,7 +41,7 @@ const ReviewDetailContainer = ({ data }: Props) => { diff --git a/src/components/review/new/SelectTravel.tsx b/src/components/review/new/SelectTravel.tsx index 194df398..528285b1 100644 --- a/src/components/review/new/SelectTravel.tsx +++ b/src/components/review/new/SelectTravel.tsx @@ -72,7 +72,7 @@ const SelectTravel = () => { if (isError) return
에러{error?.message}
; - if (travels?.pages[0].content.length === 0) { + if (travels?.pages[0].data.content.length === 0) { return
다녀온 여행이 없어요
; } @@ -108,7 +108,7 @@ const SelectTravel = () => { {travels && travels.pages.map((page) => - page.content.map((travel) => ( + page.data.content.map((travel) => (
); }; diff --git a/src/components/review/reviewList/reviewFilter/ReviewFilter.test.tsx b/src/components/review/reviewList/reviewFilter/ReviewFilter.test.tsx index 5cb6ca55..64251568 100644 --- a/src/components/review/reviewList/reviewFilter/ReviewFilter.test.tsx +++ b/src/components/review/reviewList/reviewFilter/ReviewFilter.test.tsx @@ -48,6 +48,8 @@ describe('ReviewFilter', () => { expect(setFiltersMock).toHaveBeenCalledWith({ sortOrder: 'POPULAR', + pageParam: 0, + size: 12, }); }); diff --git a/src/components/review/reviewList/reviewFilter/ReviewFilter.tsx b/src/components/review/reviewList/reviewFilter/ReviewFilter.tsx index 435d22c1..1881f268 100644 --- a/src/components/review/reviewList/reviewFilter/ReviewFilter.tsx +++ b/src/components/review/reviewList/reviewFilter/ReviewFilter.tsx @@ -8,7 +8,7 @@ const ReviewFilter = () => { const setFilters = useReviewStore((state) => state.setFilters); const handleSortOrder = (value: 'LATEST' | 'POPULAR') => { - setFilters({ sortOrder: value }); + setFilters({ sortOrder: value, pageParam: 0, size: 12 }); }; return ( diff --git a/src/components/travel/detail/TravelDetail.test.tsx b/src/components/travel/detail/TravelDetail.test.tsx index 73d392db..4d65cd62 100644 --- a/src/components/travel/detail/TravelDetail.test.tsx +++ b/src/components/travel/detail/TravelDetail.test.tsx @@ -10,7 +10,7 @@ const userInfoMock = { userId: 1, email: 'user@test.com', nickname: 'TestUser', - image: null, + profileImage: null, description: null, }; diff --git a/src/components/travel/detail/TravelDetail.tsx b/src/components/travel/detail/TravelDetail.tsx index 910e1ad6..69d1f31c 100644 --- a/src/components/travel/detail/TravelDetail.tsx +++ b/src/components/travel/detail/TravelDetail.tsx @@ -1,11 +1,11 @@ import TravelInformation from '@/components/travel/detail/information/TravelInformation'; -import { TravelDetail as TravelType } from '@/@types/travel'; +import { Travel } from '@/@types/travel'; import dayjs from 'dayjs'; import TravelDetailCategory from './TravelDetailCategory'; import TravelImage from './image/TravelImage'; import TravelButtons from './buttons/TravelButtons'; -const TravelDetail = ({ travelDetail }: { travelDetail: TravelType }) => { +const TravelDetail = ({ travelDetail }: { travelDetail: Travel }) => { const organizer = travelDetail && travelDetail.participant.find((part) => part.role === 'ORGANIZER'); @@ -17,7 +17,7 @@ const TravelDetail = ({ travelDetail }: { travelDetail: TravelType }) => {
@@ -38,7 +38,7 @@ const TravelDetail = ({ travelDetail }: { travelDetail: TravelType }) => { )} @@ -59,7 +59,7 @@ const TravelDetail = ({ travelDetail }: { travelDetail: TravelType }) => { )} diff --git a/src/components/travel/detail/TravelDetailCategory.test.tsx b/src/components/travel/detail/TravelDetailCategory.test.tsx index 9ed59f8c..3f48f8e7 100644 --- a/src/components/travel/detail/TravelDetailCategory.test.tsx +++ b/src/components/travel/detail/TravelDetailCategory.test.tsx @@ -19,14 +19,14 @@ const mockTravelPlan = [ tripOrderNumber: 1, destination: '서울', description: '서울 탐방', - image: '/test1.png', + travelPlanImage: '/test1.png', }, { tripDay: 2, tripOrderNumber: 1, destination: '부산', description: '부산 여행', - image: '/test1.png', + travelPlanImage: '/test1.png', }, ]; const mockStartAt = '2024-12-10'; diff --git a/src/components/travel/detail/TravelDetailCategory.tsx b/src/components/travel/detail/TravelDetailCategory.tsx index 89831f14..32fb0a16 100644 --- a/src/components/travel/detail/TravelDetailCategory.tsx +++ b/src/components/travel/detail/TravelDetailCategory.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Participant, TravelDetail } from '@/@types/travel'; +import { Participant, Travel } from '@/@types/travel'; import React, { Suspense, useState } from 'react'; import dayjs from 'dayjs'; import SpinnerIcon from '@/assets/spinner_round.svg'; @@ -8,7 +8,7 @@ import TabTravelReview from './category/TabTravelReview'; import TabTravelDetail from './category/TabTravelDetail'; type Props = Pick< - TravelDetail, + Travel, | 'travelId' | 'hashTags' | 'description' diff --git a/src/components/travel/detail/category/TabTravelDetail.test.tsx b/src/components/travel/detail/category/TabTravelDetail.test.tsx index 99a57a4a..31c2b551 100644 --- a/src/components/travel/detail/category/TabTravelDetail.test.tsx +++ b/src/components/travel/detail/category/TabTravelDetail.test.tsx @@ -22,7 +22,7 @@ const mock = { travelId: 1, participationFlag: true, organizer: { - id: 1, + userId: 1, nickname: '녹차라떼', role: 'string', profileImage: '/image.jpg', @@ -106,7 +106,7 @@ describe('여행 모임장이 아닐 때', () => { travelId: 1, participationFlag: true, organizer: { - id: 2, + userId: 2, nickname: '녹차라떼', role: 'string', profileImage: '/string.png', diff --git a/src/components/travel/detail/category/TabTravelDetail.tsx b/src/components/travel/detail/category/TabTravelDetail.tsx index d47e1e47..b83159b4 100644 --- a/src/components/travel/detail/category/TabTravelDetail.tsx +++ b/src/components/travel/detail/category/TabTravelDetail.tsx @@ -39,7 +39,7 @@ const TabTravelDetail = ({ travelId={travelId} bookmarkFlag={bookmarkFlag} userId={user.userId} - organizerId={organizer.id} + organizerId={organizer.userId} /> )}
diff --git a/src/components/travel/detail/category/TabTravelItinerary.test.tsx b/src/components/travel/detail/category/TabTravelItinerary.test.tsx index 4a10b7dd..07585d28 100644 --- a/src/components/travel/detail/category/TabTravelItinerary.test.tsx +++ b/src/components/travel/detail/category/TabTravelItinerary.test.tsx @@ -10,21 +10,21 @@ const mockTravelPlan = [ tripOrderNumber: 1, destination: '서울', description: '서울 탐방', - image: '/test1.png', + travelPlanImage: '/test1.png', }, { tripDay: 2, tripOrderNumber: 1, destination: '부산', description: '부산 여행', - image: '/test1.png', + travelPlanImage: '/test1.png', }, { tripDay: 3, tripOrderNumber: 1, destination: '제주도', description: '제주도 여행', - image: '/test1.png', + travelPlanImage: '/test1.png', }, ]; diff --git a/src/components/travel/detail/category/TabTravelItinerary.tsx b/src/components/travel/detail/category/TabTravelItinerary.tsx index 69e2b41e..20ddc0d3 100644 --- a/src/components/travel/detail/category/TabTravelItinerary.tsx +++ b/src/components/travel/detail/category/TabTravelItinerary.tsx @@ -1,12 +1,12 @@ 'use client'; -import { TravelDetail } from '@/@types/travel'; +import { Travel } from '@/@types/travel'; import ArrowDownIcon from '@/assets/arrow_down.svg'; import { useState } from 'react'; import { formatDateToShortWithDay } from '@/utils/dateChangeKr'; import TravelPlanCard from '../../../card/travel/TravelPlanCard'; -type Props = Pick; +type Props = Pick; const TabTravelItinerary = ({ tripDuration, travelPlan, startAt }: Props) => { const [isOpen, setIsOpen] = useState>(new Set()); @@ -69,7 +69,7 @@ const TabTravelItinerary = ({ tripDuration, travelPlan, startAt }: Props) => { diff --git a/src/components/travel/detail/category/TabTravelReview.tsx b/src/components/travel/detail/category/TabTravelReview.tsx index 98baef2e..4b77bc7c 100644 --- a/src/components/travel/detail/category/TabTravelReview.tsx +++ b/src/components/travel/detail/category/TabTravelReview.tsx @@ -42,12 +42,12 @@ const TabTravelReview = ({ travelId }: { travelId: number }) => { {data && data.pages.map((reviewList) => reviewList.data.content.map((review) => ( - + )), diff --git a/src/components/travel/detail/information/RecruitmentBox.test.tsx b/src/components/travel/detail/information/RecruitmentBox.test.tsx index f7681d96..3dd534c5 100644 --- a/src/components/travel/detail/information/RecruitmentBox.test.tsx +++ b/src/components/travel/detail/information/RecruitmentBox.test.tsx @@ -42,8 +42,8 @@ describe('RecruitmentBox', () => { describe('RecruitmentBox, 모집 중인 여행일 때', () => { const mockParticipants = [ - { id: 1, nickname: 'string', role: 'string', profileImage: 'string' }, - { id: 2, nickname: 'string', role: 'string', profileImage: 'string' }, + { userId: 1, nickname: 'string', role: 'string', profileImage: 'string' }, + { userId: 2, nickname: 'string', role: 'string', profileImage: 'string' }, ]; beforeEach(() => diff --git a/src/components/travel/detail/information/TravelInformation.test.tsx b/src/components/travel/detail/information/TravelInformation.test.tsx index 4c0c3f61..0c2c43f7 100644 --- a/src/components/travel/detail/information/TravelInformation.test.tsx +++ b/src/components/travel/detail/information/TravelInformation.test.tsx @@ -13,9 +13,9 @@ describe('TravelInformation', () => { startAt: '2024.12.01', endAt: '2024.12.10', participant: [ - { id: 1, nickname: '1', role: 'string', profileImage: 'string' }, - { id: 2, nickname: '2', role: 'string', profileImage: 'string' }, - { id: 3, nickname: '3', role: 'string', profileImage: 'string' }, + { userId: 1, nickname: '1', role: 'string', profileImage: 'string' }, + { userId: 2, nickname: '2', role: 'string', profileImage: 'string' }, + { userId: 3, nickname: '3', role: 'string', profileImage: 'string' }, ], registrationEnd: '2024.11.30', }; diff --git a/src/components/travel/detail/information/TravelInformation.tsx b/src/components/travel/detail/information/TravelInformation.tsx index f43ed31b..42f9c565 100644 --- a/src/components/travel/detail/information/TravelInformation.tsx +++ b/src/components/travel/detail/information/TravelInformation.tsx @@ -2,14 +2,14 @@ import Profile from '@/assets/profile.svg'; import Timeline from '@/assets/timeline.svg'; import DateIcon from '@/assets/date.svg'; import LocationIcon from '@/assets/location.svg'; -import { TravelDetail } from '@/@types/travel'; +import { Travel } from '@/@types/travel'; import dayjs from 'dayjs'; import { formatDateToShortWithDay } from '@/utils/dateChangeKr'; import DomesticTag from '../../../common/tag/DomesticTag'; import RecruitmentBox from './RecruitmentBox'; type Props = Pick< - TravelDetail, + Travel, | 'travelName' | 'isDomestic' | 'minTravelMateCount' diff --git a/src/components/travel/detail/reviewRate/ReviewRate.tsx b/src/components/travel/detail/reviewRate/ReviewRate.tsx index a0a88dd3..a1266fb3 100644 --- a/src/components/travel/detail/reviewRate/ReviewRate.tsx +++ b/src/components/travel/detail/reviewRate/ReviewRate.tsx @@ -20,7 +20,7 @@ const ReviewRate = ({ travelId }: { travelId: number }) => { return ( ); } diff --git a/src/components/travel/detail/reviewRate/ScoreBox.tsx b/src/components/travel/detail/reviewRate/ScoreBox.tsx index 939fdbb1..e7ed02e0 100644 --- a/src/components/travel/detail/reviewRate/ScoreBox.tsx +++ b/src/components/travel/detail/reviewRate/ScoreBox.tsx @@ -1,6 +1,6 @@ import VerticalProgressBar from '@/components/common/progressbar/VerticalProgressBar'; import BlueStart from '@/assets/blue_star.svg'; -import { TravelReviewRateScore } from '@/@types/travel'; +import { ReviewRatings } from '@/@types/review'; import StarRate from './StarRate'; const ScoreBox = ({ @@ -8,7 +8,7 @@ const ScoreBox = ({ scoreFrequency, }: { averageScore: number; - scoreFrequency: TravelReviewRateScore; + scoreFrequency: ReviewRatings; }) => { const fullScoreFrequency = { 1: scoreFrequency.oneStarReviews, @@ -17,7 +17,7 @@ const ScoreBox = ({ 4: scoreFrequency.fourStarReviews, 5: scoreFrequency.fiveStarReviews, }; - + const scores = Object.keys(fullScoreFrequency).map(Number); return (
@@ -28,7 +28,7 @@ const ScoreBox = ({
- {[1, 2, 3, 4, 5].map((v) => ( + {scores.map((v) => ( ))}
@@ -41,7 +41,9 @@ const ScoreBox = ({
{count}
{score}점
diff --git a/src/components/travel/list/TravelList.tsx b/src/components/travel/list/TravelList.tsx index f5a10858..a8aac49c 100644 --- a/src/components/travel/list/TravelList.tsx +++ b/src/components/travel/list/TravelList.tsx @@ -2,17 +2,19 @@ import NoResult from '@/components/common/NoResult'; import { useTravelListStore } from '@/store/useTravelListStore'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useInView } from 'react-intersection-observer'; -import { checkTomorrow } from '@/utils/dateChangeKr'; + import useGetTravelsList from '@/queries/travel/useGetTravelsList'; import { InitialFilters } from '@/@types/travel'; import TravelCardBig from '@/components/card/travel/TravelCardBig'; import SkeletonTravelList from '@/components/common/skeleton/travelList/SkeletonTravelList'; import SkeletonTravelCardBig from '@/components/common/skeleton/card/SkeletonTravelCardBig'; +import { ListSkip } from '@/components/a11y/ListSkip'; const TravelList = () => { const { ref, inView } = useInView(); + const [skipMode, setSkipMode] = useState(false); const filters = useTravelListStore((state) => state.filters); const { @@ -24,6 +26,11 @@ const TravelList = () => { fetchNextPage, } = useGetTravelsList(filters); + const handleSkipClick = () => { + setSkipMode(true); + setTimeout(() => setSkipMode(false), 8000); + }; + useEffect(() => { if (filters !== InitialFilters) { fetchNextPage(); @@ -31,10 +38,10 @@ const TravelList = () => { }, [filters, fetchNextPage]); useEffect(() => { - if (inView && hasNextPage) { + if (!skipMode && inView && hasNextPage) { fetchNextPage(); } - }, [inView, hasNextPage, fetchNextPage]); + }, [inView, hasNextPage, fetchNextPage, skipMode]); if (isLoading) { return ( @@ -44,7 +51,7 @@ const TravelList = () => { ); } - if (isError) return
에러{error?.message}
; + if (isError) return
에러가 발생했습니다. {error?.message}
; if (travelListData?.pages[0].data.content.length === 0) { return ( @@ -57,43 +64,54 @@ const TravelList = () => { } return ( -
- {travelListData && - travelListData.pages.map((page) => - page.data.content.map((travel) => ( - - )), + <> +
    + {travelListData && + travelListData.pages.map((page, pageIndex) => + page.data.content.map((travel, travelIndex) => ( +
  • + + +
  • + )), + )} + + {hasNextPage && !skipMode && ( + <> +
  • + +
  • + {Array.from({ length: 3 }).map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  • + +
  • + ))} + )} - {hasNextPage ? ( - <> -
    - -
    - {[1, 2, 3].map((v) => ( - - ))} - - ) : ( -
    - )} -
    +
+ + ); }; diff --git a/src/components/travel/list/filter/FilterDomestic.tsx b/src/components/travel/list/filter/FilterDomestic.tsx index 223c28bb..ab2544e4 100644 --- a/src/components/travel/list/filter/FilterDomestic.tsx +++ b/src/components/travel/list/filter/FilterDomestic.tsx @@ -31,7 +31,6 @@ const FilterDomestic = () => { { { profileImage: File | null; - nickname: string; - description: string; } const EditForm = () => { @@ -25,7 +24,7 @@ const EditForm = () => { nickname: '', description: '', }); - const [previewImage, setPreviewImage] = useState(user?.image || ''); + const [previewImage, setPreviewImage] = useState(user?.profileImage || ''); const { mutate: editProfile } = useEditProfile(); diff --git a/src/components/user/userProfileModal/UserProfileModal.tsx b/src/components/user/userProfileModal/UserProfileModal.tsx index 94e6a554..665ae24c 100644 --- a/src/components/user/userProfileModal/UserProfileModal.tsx +++ b/src/components/user/userProfileModal/UserProfileModal.tsx @@ -35,11 +35,11 @@ const ProfileModal = ({ isOpen, onClose, participant }: Props) => {

- {participant.user} + {participant.nickname}

{participant.email} diff --git a/src/constants/auth.ts b/src/constants/auth.ts index bbe9640d..689dd693 100644 --- a/src/constants/auth.ts +++ b/src/constants/auth.ts @@ -55,6 +55,7 @@ export const AUTH_MAX_LENGTH = { email: 25, password: 20, passwordConfirm: 20, + currentPassword: 20, newPassword: 20, name: 10, nickname: 10, diff --git a/src/hooks/useAuthInput.ts b/src/hooks/useAuthInput.ts index c9fde065..8a3aea66 100644 --- a/src/hooks/useAuthInput.ts +++ b/src/hooks/useAuthInput.ts @@ -1,10 +1,10 @@ import { useState, useCallback, useEffect, useMemo } from 'react'; import validate from '@/utils/validateAuthInput'; import { debounce } from 'lodash'; -import { PasswordInput, TextInput } from '@/@types/auth'; +import { AuthInput } from '@/@types/auth'; interface Props { - name: keyof TextInput | keyof PasswordInput; + name: keyof AuthInput; password?: string; } diff --git a/src/hooks/useChat.ts b/src/hooks/useChat.ts index 8c0b43f7..48f23554 100644 --- a/src/hooks/useChat.ts +++ b/src/hooks/useChat.ts @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useGetChat } from '@/queries/chat/useGetChat'; import { useState, useEffect, useRef } from 'react'; -import { ChattingResponse } from '@/@types/chat'; +import { Chat } from '@/@types/chat'; import { useWebSocketStore } from '@/store/useWebSocketStore'; export const useChat = (chatId: string) => { @@ -27,7 +27,7 @@ export const useChat = (chatId: string) => { }; }, [connected, subscribeToChat, unsubscribeFromChat, chatId]); - const [chatInfo, setChatInfo] = useState(null); + const [chatInfo, setChatInfo] = useState(null); const isFetchingPreviousRef = useRef(false); const { diff --git a/src/hooks/useChatOverview.ts b/src/hooks/useChatOverview.ts index e95a1e2e..27124f21 100644 --- a/src/hooks/useChatOverview.ts +++ b/src/hooks/useChatOverview.ts @@ -1,16 +1,8 @@ import { useGetChatOverview } from '@/queries/chat/useGetChat'; import { useState, useEffect } from 'react'; -import { - ImageInfo, - ChatOverview, - ChatMessage, - ChattingResponse, -} from '@/@types/chat'; - -export const useChatOverview = ( - chatId: string, - chatInfo?: ChattingResponse | null, -) => { +import { ImageInfo, ChatOverview, ChatMessage, Chat } from '@/@types/chat'; + +export const useChatOverview = (chatId: string, chatInfo?: Chat | null) => { const { data } = useGetChatOverview(chatId); const [chatOverview, setChatOverview] = useState(null); diff --git a/src/hooks/useChatRooms.ts b/src/hooks/useChatRooms.ts index a691b91c..73edd506 100644 --- a/src/hooks/useChatRooms.ts +++ b/src/hooks/useChatRooms.ts @@ -1,11 +1,11 @@ import { useEffect, useState } from 'react'; import useGetChatRooms from '@/queries/chat/useGetChatRooms'; -import { RoomResponse, SortType } from '@/@types/chat'; +import { ChatRoom, SortType } from '@/@types/chat'; import { sortRooms } from '@/utils/chat'; export const useChatRooms = () => { const [currentSort, setCurrentSort] = useState('RECENT'); - const [sortedRooms, setSortedRooms] = useState(null); + const [sortedRooms, setSortedRooms] = useState(null); const [isLoading, setIsLoading] = useState(true); const { data, isFetching, error } = useGetChatRooms(currentSort); diff --git a/src/hooks/useHandleChatError.ts b/src/hooks/useHandleChatError.ts new file mode 100644 index 00000000..28ec681d --- /dev/null +++ b/src/hooks/useHandleChatError.ts @@ -0,0 +1,29 @@ +import { useRouter } from 'next/navigation'; +import { QueryError } from '@/@types/query'; + +const useHandleChatError = () => { + const router = useRouter(); + + return (error: QueryError) => { + switch (error.status) { + case 400: + console.error('잘못된 요청입니다.'); + break; + case 401: + console.error('로그인이 필요합니다.'); + router.push('/login'); + break; + case 403: + console.error('접근 권한이 없습니다.'); + router.push('/'); + break; + case 500: + console.error('네트워크를 확인해주세요.'); + break; + default: + console.error('알 수 없는 오류 발생했습니다.'); + } + }; +}; + +export default useHandleChatError; diff --git a/src/hooks/useTravelForm.ts b/src/hooks/useTravelForm.ts index a8e0e69e..287f20de 100644 --- a/src/hooks/useTravelForm.ts +++ b/src/hooks/useTravelForm.ts @@ -13,6 +13,7 @@ import { import useCreateTravel from '@/queries/travel/useCreateTravel'; import useToast from '@/hooks/useToast'; import { usePathname, useRouter } from 'next/navigation'; +import { createTravelFormData } from '@/utils/createTravelFormData'; const defaultInitialData: FormTravelData = { travelName: '', @@ -126,7 +127,7 @@ const useTravelForm = () => { } catch (error) { console.error('IndexedDB 데이터 삭제 중 오류 발생:', error); } finally { - createTravel(formData); + createTravel(createTravelFormData(formData)); } }; diff --git a/src/mocks/data/travel/travelDetail.json b/src/mocks/data/travel/travelDetail.json index 91d571f1..ee2854a3 100644 --- a/src/mocks/data/travel/travelDetail.json +++ b/src/mocks/data/travel/travelDetail.json @@ -2,7 +2,7 @@ "travelId": 1, "travelName": "12월에 떠나는 겨울여행", "description": "12월의 겨울 낭만을 즐기고 싶은 분들 계신가요?함께 국내 겨울 여행지를 돌아다니고 싶습니다!춥지만, 마음만은 따듯한 겨울 여행 함께해요 :)다양한 사람들이 모여서 함께 하고 싶어요!", - "image": "/test3.png", + "travelImage": "/test3.png", "expectedTripCost": 250000, "currentTravelMateCount": 6, "minTravelMateCount": 2, @@ -23,64 +23,64 @@ "tripOrderNumber": 1, "destination": "대관령 양떼목장", "description": "강원도의 상징 눈의...강원도의 상징 눈의 계절이면 생각나는대관령 양떼목장 풍경만 봐도 힐링 ", - "image": "/test4.jpg" + "travelPlanImage": "/test4.jpg" }, { "tripDay": 1, "tripOrderNumber": 2, "destination": "강원도 감자빵", "description": "강원도의 상징 눈의...", - "image": "/test3.png" + "travelPlanImage": "/test3.png" }, { "tripDay": 2, "tripOrderNumber": 1, "destination": "안목해변", "description": "낭만있는 겨울바다를 즐겨요!", - "image": "/test.png" + "travelPlanImage": "/test.png" }, { "tripDay": 2, "tripOrderNumber": 2, "destination": "어부횟집", "description": "바다를 보면 회를 먹는건...", - "image": "/test2.png" + "travelPlanImage": "/test2.png" }, { "tripDay": 3, "tripOrderNumber": 1, "destination": "어부횟집", "description": "바다를 보면 회를 먹는건...", - "image": "/test2.png" + "travelPlanImage": "/test2.png" } ], "participant": [ { - "id": 3, + "userId": 3, "nickname": "녹차라떼 아이스", "role": "ORGANIZER", "profileImage": "/user.jpg" }, { - "id": 4, + "userId": 4, "nickname": "참가중인 다른 유저 1", "role": "ATTENDEE", "profileImage": "" }, { - "id": 5, + "userId": 5, "nickname": "아이스", "role": "ATTENDEE", "profileImage": "/user.jpg" }, { - "id": 6, + "userId": 6, "nickname": "아이스", "role": "ATTENDEE", "profileImage": "/test4.jpg" }, { - "id": 7, + "userId": 7, "nickname": "아이스", "role": "ATTENDEE", "profileImage": "/test4.jpg" diff --git a/src/mocks/data/travel/travelList.json b/src/mocks/data/travel/travelList.json index e4a10571..7d5c40c6 100644 --- a/src/mocks/data/travel/travelList.json +++ b/src/mocks/data/travel/travelList.json @@ -3,60 +3,60 @@ "travelId": 13, "isDomestic": false, "travelName": "부여로 떠나는 다함께 시골투어", - "location": "충남 부여", + "travelLocation": "충남 부여", "maxTravelMateCount": 6, "currentTravelMateCount": 5, "startAt": "2024.12.11", "endAt": "2025.01.12", - "image": "/test/travel/test1.jpg", - "isBookmark": false + "travelImage": "/test/travel/test1.jpg", + "bookmarkFlag": false }, { "travelId": 14, "isDomestic": true, "travelName": "도쿄에서 즐기는 미식여행", - "location": "도쿄시", + "travelLocation": "도쿄시", "maxTravelMateCount": 12, "currentTravelMateCount": 11, "startAt": "2024.12.06", "endAt": "2025.01.12", - "image": "/test/travel/test2.jpg", - "isBookmark": false + "travelImage": "/test/travel/test2.jpg", + "bookmarkFlag": false }, { "travelId": 15, "isDomestic": false, "travelName": "당일치기 남산타워 보러가기", - "location": "용산구", + "travelLocation": "용산구", "maxTravelMateCount": 10, "currentTravelMateCount": 10, "startAt": "2024.01.03", "endAt": "2025.01.12", - "image": "/test/travel/test3.jpg", - "isBookmark": false + "travelImage": "/test/travel/test3.jpg", + "bookmarkFlag": false }, { "travelId": 16, "isDomestic": true, "travelName": "지하철로 떠나는 일본 도심 투어", - "location": "오사카", + "travelLocation": "오사카", "maxTravelMateCount": 6, "currentTravelMateCount": 3, "startAt": "2024.12.31", "endAt": "2025.01.12", - "image": "/test/travel/test4.jpg", - "isBookmark": false + "travelImage": "/test/travel/test4.jpg", + "bookmarkFlag": false }, { "travelId": 17, "isDomestic": false, "travelName": "겨울에만 즐길 수 있는 고즈넉한 한옥 스테이", - "location": "북촌", + "travelLocation": "북촌", "maxTravelMateCount": 6, "currentTravelMateCount": 5, "startAt": "2024.12.24", "endAt": "2025.01.12", - "image": "/test/travel/test5.jpg", - "isBookmark": false + "travelImage": "/test/travel/test5.jpg", + "bookmarkFlag": false } ] diff --git a/src/mocks/data/user/userList.json b/src/mocks/data/user/userList.json index 8eb87fae..d348ca70 100644 --- a/src/mocks/data/user/userList.json +++ b/src/mocks/data/user/userList.json @@ -2,7 +2,7 @@ { "userId": 1, "profileImage": "", - "nickName": "녹차라떼", + "nickname": "녹차라떼", "openTravelCount": 11, "reviewCount": 25, "hashTags": "#친절해요#상세해요" @@ -10,7 +10,7 @@ { "userId": 2, "profileImage": "/user.jpg", - "nickName": "사과가 좋다", + "nickname": "사과가 좋다", "openTravelCount": 2, "reviewCount": 12, "hashTags": "#다양한활동#액티비티" @@ -18,7 +18,7 @@ { "userId": 3, "profileImage": "", - "nickName": "길동", + "nickname": "길동", "openTravelCount": 11, "reviewCount": 2, "hashTags": "#상세해요" @@ -26,7 +26,7 @@ { "userId": 4, "profileImage": "/test3.png", - "nickName": "사과가 좋다", + "nickname": "사과가 좋다", "openTravelCount": 2, "reviewCount": 12, "hashTags": "#다양한활동#액티비티" diff --git a/src/queries/auth/useCheckCode.ts b/src/queries/auth/useCheckCode.ts index c8f281d2..acc34031 100644 --- a/src/queries/auth/useCheckCode.ts +++ b/src/queries/auth/useCheckCode.ts @@ -12,7 +12,7 @@ const useCheckCode = ( return useMutation({ mutationFn: checkCode, onSuccess: (data) => { - onSuccessCallback(data.verifiedToken); + onSuccessCallback(data.data.emailVerifyToken); showToast('이메일 인증이 완료되었습니다.', 'success'); }, onError: (error: QueryError) => { diff --git a/src/queries/chat/useGetChat.ts b/src/queries/chat/useGetChat.ts index 87e1ad19..2c3d77f1 100644 --- a/src/queries/chat/useGetChat.ts +++ b/src/queries/chat/useGetChat.ts @@ -5,8 +5,10 @@ import { } from '@tanstack/react-query'; import { getChat, getChatOverview } from '@/api/chat/chatApi'; import { useEffect } from 'react'; +import useHandleChatError from '@/hooks/useHandleChatError'; export const useGetChat = (chatId: string) => { + const handleChatError = useHandleChatError(); const queryClient = useQueryClient(); useEffect(() => { @@ -17,8 +19,13 @@ export const useGetChat = (chatId: string) => { return useInfiniteQuery({ queryKey: ['chat', chatId], - queryFn: ({ pageParam }: { pageParam: number }) => { - return getChat(chatId, pageParam as number); + queryFn: async ({ pageParam }: { pageParam: number }) => { + try { + return await getChat(chatId, pageParam); + } catch (error: any) { + handleChatError(error); + throw error; + } }, initialPageParam: -1, getNextPageParam: (lastPage) => { diff --git a/src/queries/chat/useGetChatRooms.tsx b/src/queries/chat/useGetChatRooms.tsx index e9bd4c36..c9041d0e 100644 --- a/src/queries/chat/useGetChatRooms.tsx +++ b/src/queries/chat/useGetChatRooms.tsx @@ -1,13 +1,21 @@ import { getChatRooms } from '@/api/chat/chatRoomsApi'; import { useQuery } from '@tanstack/react-query'; import { SortType } from '@/@types/chat'; +import useHandleChatError from '@/hooks/useHandleChatError'; const useGetChatRooms = (sortBy: SortType) => { + const handleChatError = useHandleChatError(); + return useQuery({ queryKey: ['chatRooms', sortBy], - queryFn: ({ queryKey }) => { - const [, sort] = queryKey as [string, SortType]; - return getChatRooms(sort); + queryFn: async ({ queryKey }) => { + try { + const [, sort] = queryKey as [string, SortType]; + return await getChatRooms(sort); + } catch (error: any) { + handleChatError(error); + throw error; + } }, staleTime: Infinity, gcTime: Infinity, diff --git a/src/queries/chat/useSetChat.ts b/src/queries/chat/useSetChat.ts index cf87bdac..3462bb81 100644 --- a/src/queries/chat/useSetChat.ts +++ b/src/queries/chat/useSetChat.ts @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { setIsJoined, uploadChatImages } from '@/api/chat/chatApi'; import { leaveChat } from '@/api/chat/chatRoomsApi'; import { QueryError } from '@/@types/query'; +import useHandleChatError from '@/hooks/useHandleChatError'; interface Props { chatId: string; @@ -9,11 +10,12 @@ interface Props { export const useSetIsJoined = () => { const queryClient = useQueryClient(); + const handleChatError = useHandleChatError(); return useMutation({ mutationFn: ({ chatId }: Props) => setIsJoined(chatId), onError: (error: QueryError) => { - console.error(error); + handleChatError(error); }, onSuccess: () => { queryClient.invalidateQueries({ @@ -47,11 +49,12 @@ interface LeaveChatProps { export const useLeaveChat = () => { const queryClient = useQueryClient(); + const handleChatError = useHandleChatError(); return useMutation({ mutationFn: ({ chatId }: LeaveChatProps) => leaveChat(chatId), onError: (error: QueryError) => { - console.error(error); + handleChatError(error); }, onSuccess: () => { queryClient.invalidateQueries({ diff --git a/src/queries/review/useCreateReviewSelectTravel.ts b/src/queries/review/useCreateReviewSelectTravel.ts index 211a7ef7..0e664d79 100644 --- a/src/queries/review/useCreateReviewSelectTravel.ts +++ b/src/queries/review/useCreateReviewSelectTravel.ts @@ -9,7 +9,7 @@ const useCreateReviewSelectTravel = () => { queryFn: ({ pageParam = 0 }) => getWritableTravelReview(size, pageParam), initialPageParam: 0, getNextPageParam: (lastPage, pages) => { - return lastPage.hasNext ? pages.length + 1 : undefined; + return lastPage.data.hasNext ? pages.length : undefined; }, staleTime: Infinity, }); diff --git a/src/queries/review/useReview.ts b/src/queries/review/useReview.ts index 84dd0186..965916e0 100644 --- a/src/queries/review/useReview.ts +++ b/src/queries/review/useReview.ts @@ -2,13 +2,13 @@ import { ReviewListFilters } from '@/@types/review'; import { getReview } from '@/api/review/review'; import { useInfiniteQuery } from '@tanstack/react-query'; -const useReview = ({ sortOrder }: ReviewListFilters) => { +const useReview = (sortOrder: ReviewListFilters['sortOrder']) => { return useInfiniteQuery({ queryKey: ['review', 'listPage', sortOrder], queryFn: ({ pageParam }) => getReview({ pageParam, sortOrder }), initialPageParam: 0, getNextPageParam: (lastPage, pages) => { - return lastPage.data.hasNext ? pages.length + 1 : undefined; + return lastPage.data.hasNext ? pages.length : undefined; }, }); }; diff --git a/src/queries/review/useReviewLikes.ts b/src/queries/review/useReviewLikes.ts index 3de00054..ca56e562 100644 --- a/src/queries/review/useReviewLikes.ts +++ b/src/queries/review/useReviewLikes.ts @@ -27,7 +27,7 @@ export const useReviewLikes = ({ }, onError: (error: QueryError) => { switch (error.status) { - case 401: + case 400: showModal('로그인이 필요합니다.', '로그인 후 이용해주세요.', { icon: ModalErrorIcon, confirmText: '돌아가기', diff --git a/src/queries/travel/useCreateTravel.ts b/src/queries/travel/useCreateTravel.ts index 4000dd67..28f9281d 100644 --- a/src/queries/travel/useCreateTravel.ts +++ b/src/queries/travel/useCreateTravel.ts @@ -18,16 +18,6 @@ const useCreateTravel = () => { mutationFn: createTravel, onError: (error: QueryError) => { switch (error.status) { - case 401: - showModal('토큰이 만료되었습니다.', '여행 만들기에 실패했습니다.', { - icon: ModalErrorIcon, - type: 'error', - confirmText: '돌아가기', - onConfirm: () => { - router.push('/login'); - }, - }); - break; case 400: showModal( '잘못된 형식의 입력입니다.', @@ -41,13 +31,21 @@ const useCreateTravel = () => { }, ); break; - default: + case 500: showModal('네트워크를 확인해주세요.', `${error.message}`, { icon: ModalErrorIcon, onConfirm: () => { router.push('/'); }, }); + break; + default: + showModal('알 수 없는 오류 발생했습니다.', `${error.message}`, { + icon: ModalErrorIcon, + onConfirm: () => { + router.push('/'); + }, + }); } }, onSuccess: () => { diff --git a/src/queries/travel/useGetTravelReview.ts b/src/queries/travel/useGetTravelReview.ts index a1129f01..59f2c700 100644 --- a/src/queries/travel/useGetTravelReview.ts +++ b/src/queries/travel/useGetTravelReview.ts @@ -5,10 +5,10 @@ import { useInfiniteQuery } from '@tanstack/react-query'; const useGetTravelReview = ({ travelId }: { travelId: number }) => { return useInfiniteQuery({ queryKey: QUERY_KEYS.TRAVEL.TRAVEL_DETAIL_REVIEW(travelId), - queryFn: ({ pageParam = 1 }) => getTravelReview({ travelId, pageParam }), - initialPageParam: 1, + queryFn: ({ pageParam = 0 }) => getTravelReview({ travelId, pageParam }), + initialPageParam: 0, getNextPageParam: (lastPage, pages) => { - return lastPage.data.hasNext ? pages.length + 1 : undefined; + return lastPage.data.hasNext ? pages.length : undefined; }, staleTime: Infinity, }); diff --git a/src/queries/travel/useGetTravelsList.ts b/src/queries/travel/useGetTravelsList.ts index d5d0296d..cd98b6e6 100644 --- a/src/queries/travel/useGetTravelsList.ts +++ b/src/queries/travel/useGetTravelsList.ts @@ -6,10 +6,10 @@ import { useInfiniteQuery } from '@tanstack/react-query'; const useGetTravelsList = (filters: Filters) => { return useInfiniteQuery({ queryKey: QUERY_KEYS.TRAVEL.TRAVEL_LIST(filters), - queryFn: ({ pageParam = 1 }) => getTravels({ ...filters, pageParam }), - initialPageParam: 1, + queryFn: ({ pageParam = 0 }) => getTravels({ ...filters, pageParam }), + initialPageParam: 0, getNextPageParam: (lastPage, pages) => { - return lastPage.data.hasNext ? pages.length + 1 : undefined; + return lastPage.data.hasNext ? pages.length : undefined; }, }); }; diff --git a/src/queries/user/useGetUser.tsx b/src/queries/user/useGetUser.tsx index 4f7525d7..017065b0 100644 --- a/src/queries/user/useGetUser.tsx +++ b/src/queries/user/useGetUser.tsx @@ -1,16 +1,16 @@ -import { getUserInfo, UserInfoResponse } from '@/api/user/userInfo'; +import { getUserInfo } from '@/api/user/userInfo'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; -import { FetcherError } from '@/@types/api'; -import { MyPageProfile } from '@/@types/user'; +import { FetcherError, BaseResponse } from '@/@types/api'; +import { User } from '@/@types/user'; const useGetUser = () => { const queryClient = useQueryClient(); const { data, isLoading, error } = useQuery< - UserInfoResponse, + BaseResponse, FetcherError, - MyPageProfile + User >({ queryKey: ['user'], queryFn: getUserInfo, diff --git a/src/store/useReviewStore.ts b/src/store/useReviewStore.ts index 0e9614ff..ce423e92 100644 --- a/src/store/useReviewStore.ts +++ b/src/store/useReviewStore.ts @@ -9,6 +9,7 @@ interface ReviewStore { export const useReviewStore = create((set) => ({ filters: { sortOrder: 'LATEST', + pageParam: 0, size: 12, }, setFilters: (filters) => diff --git a/src/store/useWebSocketStore.ts b/src/store/useWebSocketStore.ts index feeb3813..2d8b0a47 100644 --- a/src/store/useWebSocketStore.ts +++ b/src/store/useWebSocketStore.ts @@ -21,7 +21,7 @@ interface WebSocketState { sendAt: string; status: 'JOIN' | 'LEAVE' | 'MESSAGE'; participant?: { - user: string; + nickname: string; email: string; description: string; profileImage: string; diff --git a/src/utils/chat.ts b/src/utils/chat.ts index 3eed7579..a5bdde4a 100644 --- a/src/utils/chat.ts +++ b/src/utils/chat.ts @@ -1,9 +1,9 @@ -import { RoomResponse, SortType } from '@/@types/chat'; +import { ChatRoom, SortType } from '@/@types/chat'; export const sortRooms = ( - rooms: RoomResponse[], + rooms: ChatRoom[], sortType: SortType, -): RoomResponse[] => { +): ChatRoom[] => { return [...rooms].sort((a, b) => { if (sortType === 'RECENT') { return ( @@ -47,5 +47,3 @@ export const formatTimeToKorean = (dateString: string) => { return `${period} ${hour}:${minute}`; }; - -// 값 변경으로 인한 수정 diff --git a/src/utils/createTravelFormData.ts b/src/utils/createTravelFormData.ts new file mode 100644 index 00000000..f16efa1c --- /dev/null +++ b/src/utils/createTravelFormData.ts @@ -0,0 +1,72 @@ +import { FormTravelData } from '@/@types/travelForm'; + +export const createTravelFormData = (data: FormTravelData): FormData => { + const formData = new FormData(); + + if (data.registrationEnd.startDate && data.startAt) { + const registrationEndDate = new Date( + data.registrationEnd.startDate.getTime() - + data.registrationEnd.startDate.getTimezoneOffset() * 60000, + ); + registrationEndDate.setHours(23, 59, 59, 999); + + const registrationEnd = registrationEndDate + .toLocaleString('sv-SE', { hour12: false }) + .replace(' ', 'T') + .slice(0, 16); + + const startAt = `${new Date( + data.startAt.getTime() - data.startAt.getTimezoneOffset() * 60000, + ) + .toISOString() + .slice(0, 10)}T${data.startTime.hour}:${data.startTime.minute}`; + + const endAt = data.endAt + ? `${new Date( + data.endAt.getTime() - data.endAt.getTimezoneOffset() * 60000, + ) + .toISOString() + .slice(0, 10)}T${data.endTime.hour}:${data.endTime.minute}` + : `${new Date( + data.startAt.getTime() - data.startAt.getTimezoneOffset() * 60000, + ) + .toISOString() + .slice(0, 10)}T${data.endTime.hour}:${data.endTime.minute}`; + + formData.append('registrationEnd', registrationEnd); + formData.append('startAt', startAt); + formData.append('endAt', endAt); + } + + formData.append('travelName', data.travelName); + formData.append('expectedTripCost', data.expectedTripCost); + formData.append('minTravelMateCount', data.minTravelMateCount); + formData.append('maxTravelMateCount', data.maxTravelMateCount); + formData.append('travelDescription', data.travelDescription); + formData.append('hashTags', `#${data.hashTags.join('#')}`); + formData.append('travelLocation', data.travelLocation); + formData.append('departureLocation', data.departureLocation); + formData.append('isDomestic', `${data.isDomestic}`); + + if (data.travelImage) { + formData.append('travelImage', data.travelImage); + } + + data.detailTravel.forEach((detail, index) => { + formData.append(`detailTravel[${index}].tripDay`, `${detail.tripDay}`); + formData.append( + `detailTravel[${index}].tripOrderNumber`, + `${detail.tripOrderNumber}`, + ); + formData.append(`detailTravel[${index}].destination`, detail.destination); + formData.append(`detailTravel[${index}].description`, detail.description); + if (detail.destinationImage) { + formData.append( + `detailTravel[${index}].destinationImage`, + detail.destinationImage, + ); + } + }); + + return formData; +};