diff --git a/package.json b/package.json index c0f2bda..06591c2 100644 --- a/package.json +++ b/package.json @@ -52,5 +52,6 @@ "recoil": "^0.7.7", "recoil-persist": "^5.1.0", "typescript": "5.1.6" - } + }, + "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" } diff --git a/pages/party/[id].tsx b/pages/party/[id].tsx index 49d5abc..5d91d0c 100644 --- a/pages/party/[id].tsx +++ b/pages/party/[id].tsx @@ -1,33 +1,30 @@ -import styled from "@emotion/styled"; -import { DefaultHeader } from "@components/common/DefaultHeader"; -import { HeaderBackButton } from "@components/common/HeaderBackButton"; -import { useScroll } from "react-use"; -import { useRef } from "react"; -import PartyDetailContent from "@components/partydetail/PartyDetailContent"; -import QuerySuspenseErrorBoundary from "@components/hoc/QuerySuspenseErrorBoundary"; +import styled from '@emotion/styled'; +import { DefaultHeader } from '@components/common/DefaultHeader'; +import { HeaderBackButton } from '@components/common/HeaderBackButton'; +import { useScroll } from 'react-use'; +import { useRef } from 'react'; +import PartyDetailContent from '@components/partydetail/PartyDetailContent'; +import QuerySuspenseErrorBoundary from '@components/hoc/QuerySuspenseErrorBoundary'; const Container = styled.div` - display: flex; - flex-direction: column; - align-items: center; - margin: 0 auto; - height: 100%; - width: 100%; - max-width: 768px; - overflow-y: scroll; + display: flex; + flex-direction: column; + align-items: center; + margin: 0 auto; + height: 100%; + width: 100%; + max-width: 768px; `; const PartyDetail = () => { - const scrollRef = useRef(null); - const { y } = useScroll(scrollRef); - return ( - - } /> - - - - - ); + return ( + + } /> + + + + + ); }; export default PartyDetail; diff --git a/pages/profile/index.tsx b/pages/profile/index.tsx index 94439d0..bb6ebd8 100644 --- a/pages/profile/index.tsx +++ b/pages/profile/index.tsx @@ -1,4 +1,5 @@ import BackgroundImage from '@components/common/BackgroundImage'; +import { DefaultButton } from '@components/common/DefaultButton'; import { DefaultHeader } from '@components/common/DefaultHeader'; import { HeaderBackButton } from '@components/common/HeaderBackButton'; import QuerySuspenseErrorBoundary from '@components/hoc/QuerySuspenseErrorBoundary'; @@ -10,6 +11,7 @@ import ProfileTab from '@components/profile/ProfileTab'; import styled from '@emotion/styled'; import { GetServerSideProps } from 'next'; import Link from 'next/link'; +import { useRouter } from 'next/router'; import { useRef } from 'react'; import { useScroll } from 'react-use'; @@ -28,18 +30,27 @@ const Container = styled.div` const ProfileInfoContainer = styled.div` display: flex; flex-direction: column; - height: 200px; width: 100%; `; const RightAreaContainer = styled.div` display: flex; height: 100%; + justify-content: flex-end; padding: 0 8px; align-items: center; cursor: pointer; `; +const ProfileLoginButtonWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + height: 120px; + padding: 0 30px; +`; + const RightArea = () => { return ( @@ -51,6 +62,10 @@ const RightArea = () => { const Profile = () => { const scrollRef = useRef(null); const { y } = useScroll(scrollRef); + const { push } = useRouter(); + const onClickLoginButton = () => { + push('/signin'); + }; return ( @@ -58,7 +73,18 @@ const Profile = () => { { + if (error?.response?.status === 401) { + return ( + + + + ); + } + }} suspenseFallback={} > @@ -79,7 +105,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return { redirect: { permanent: false, - destination: '/profile?category=situation&role=HOST', + destination: '/profile?category=situation&role=VOLUNTEER&status=RECRUIT', }, }; } diff --git a/src/api/deleteReview.ts b/src/api/deleteReview.ts new file mode 100644 index 0000000..a3f33be --- /dev/null +++ b/src/api/deleteReview.ts @@ -0,0 +1,15 @@ +import defaultRequest from 'src/lib/axios/defaultRequest'; + +export interface DeleteReviewBody { + reviewId: number; +} + +const deleteReview = async (body: DeleteReviewBody) => { + return defaultRequest.delete('/api/review', { + data: { + body, + }, + }); +}; + +export default deleteReview; diff --git a/src/api/getHostReviewList.ts b/src/api/getHostReviewList.ts new file mode 100644 index 0000000..cfd84f0 --- /dev/null +++ b/src/api/getHostReviewList.ts @@ -0,0 +1,21 @@ +import variableAssignment from '@utils/variableAssignment'; +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { InfinitePaginationDataType } from 'types/common/InfinitePaginationDataType'; +import { GetReviewListResponse } from 'types/review'; + +interface GetHostReviewListParameter { + page: number; + size: number; + hostId: number; +} + +export const API_GET_HOST_REVIEW_LIST = `/api/review/host`; + +export const getHostReviewList = async (params: GetHostReviewListParameter) => { + const { data } = await defaultRequest.get< + InfinitePaginationDataType<'reviewGetResList', GetReviewListResponse> + >(API_GET_HOST_REVIEW_LIST, { + params, + }); + return data; +}; diff --git a/src/api/getPartyCurrentSituation.ts b/src/api/getPartyCurrentSituation.ts new file mode 100644 index 0000000..c837efb --- /dev/null +++ b/src/api/getPartyCurrentSituation.ts @@ -0,0 +1,25 @@ +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { InfinitePaginationDataType } from 'types/common/InfinitePaginationDataType'; +import { GetPartyCurrentSituationResponse } from 'types/party'; + +type GetPartyCurrentSituationRequestRole = 'HOST' | 'VOLUNTEER'; +export type GetPartyCurrentSituationRequestStatus = 'RECRUIT' | 'RECRUIT_FINISH' | 'PARTY_FINISH'; +interface GetPartyCurrentSituationParameter { + page: number; + size: number; + role: GetPartyCurrentSituationRequestRole; + status: GetPartyCurrentSituationRequestStatus; +} + +export const API_GET_PARTY_STATUS_KEY = '/api/party/party-status'; + +const getPartyStatus = async (params: GetPartyCurrentSituationParameter) => { + const { data } = await defaultRequest.get< + InfinitePaginationDataType<'partyList', GetPartyCurrentSituationResponse> + >(API_GET_PARTY_STATUS_KEY, { + params, + }); + return data; +}; + +export default getPartyStatus; diff --git a/src/api/getPartyDetail.ts b/src/api/getPartyDetail.ts index d84ce4c..48a8339 100644 --- a/src/api/getPartyDetail.ts +++ b/src/api/getPartyDetail.ts @@ -1,22 +1,16 @@ -import variableAssignMent from "@utils/variableAssignment"; -import defaultRequest from "src/lib/axios/defaultRequest"; -import { PartyDetailResponse } from "types/party/detail/PartyDetailResponse"; +import variableAssignMent from '@utils/variableAssignment'; +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { PartyDetailResponse } from 'types/party/detail/PartyDetailResponse'; interface GetPartyDetailParameter { - id: string; - userId: string; + id: string; } -export const API_GET_PARTY_DETAIL_KEY = "/api/party/{{id}}?userId={{userId}}"; +export const API_GET_PARTY_DETAIL_KEY = '/api/party/{{id}}'; -const getPartyDetail = async ({ - id, - userId, -}: GetPartyDetailParameter): Promise => { - const { data } = await defaultRequest.get( - variableAssignMent(API_GET_PARTY_DETAIL_KEY, { id, userId }) - ); - return data; +const getPartyDetail = async ({ id }: GetPartyDetailParameter): Promise => { + const { data } = await defaultRequest.get(variableAssignMent(API_GET_PARTY_DETAIL_KEY, { id })); + return data; }; export default getPartyDetail; diff --git a/src/api/getPartyJoin.ts b/src/api/getPartyJoin.ts index 4ebce7b..d7d3aac 100644 --- a/src/api/getPartyJoin.ts +++ b/src/api/getPartyJoin.ts @@ -1,20 +1,24 @@ -import variableAssignMent from "@utils/variableAssignment"; -import defaultRequest from "src/lib/axios/defaultRequest"; -import { PartyJoinResponse } from "types/party/join/PartyJoinResponse"; +import variableAssignMent from '@utils/variableAssignment'; +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { InfinitePaginationDataType } from 'types/common/InfinitePaginationDataType'; +import { PartyJoinResponse } from 'types/party/join/PartyJoinResponse'; -interface getPartyJoinParameter { - role: string; +type GetPartyJoinRequestRole = 'HOST' | 'VOLUNTEER'; +interface GetPartyJoinParameter { + page: number; + size: number; + role: GetPartyJoinRequestRole; } -export const API_GET_PARTY_JOIN_KEY = "/api/party/party-join?role={{role}}"; +export const API_GET_PARTY_JOIN_KEY = '/api/party/party-join'; -const getPartyJoin = async ({ - role, -}: getPartyJoinParameter): Promise => { - const { data } = await defaultRequest.get( - variableAssignMent(API_GET_PARTY_JOIN_KEY, { role: role }) - ); - return data; +const getPartyJoin = async (params: GetPartyJoinParameter) => { + const { data } = await defaultRequest.get< + InfinitePaginationDataType<'partyList', PartyJoinResponse> + >(API_GET_PARTY_JOIN_KEY, { + params, + }); + return data; }; export default getPartyJoin; diff --git a/src/api/getPartyMainPage.ts b/src/api/getPartyMainPage.ts index c735b61..f348c15 100644 --- a/src/api/getPartyMainPage.ts +++ b/src/api/getPartyMainPage.ts @@ -1,28 +1,24 @@ -import variableAssignMent from "@utils/variableAssignment"; -import defaultRequest from "src/lib/axios/defaultRequest"; -import { PartyListResponse } from "types/common/PartyListResponse"; +import variableAssignMent from '@utils/variableAssignment'; +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { InfinitePaginationDataType } from 'types/common/InfinitePaginationDataType'; +import { PartyListResponse } from 'types/common/PartyListResponse'; interface GetMainPageParameter { - longitude: number; - latitude: number; - lastPartyId?: number; - size?: number; + longitude: number; + latitude: number; + page?: number; + size?: number; } -export const API_GET_MAIN_PAGE = - "/api/main"; +export const API_GET_MAIN_PAGE = '/api/main'; const getMainPageData = async (params: GetMainPageParameter) => { - const { data } = await defaultRequest.get< - InfinitePaginationDataType<"partyList", PartyListResponse> - >( - API_GET_MAIN_PAGE, { - params:{ - ...params - } - } - ); - return data; + const { data } = await defaultRequest.get< + InfinitePaginationDataType<'partyList', PartyListResponse> + >(API_GET_MAIN_PAGE, { + params, + }); + return data; }; export default getMainPageData; diff --git a/src/api/getPartyStatus.ts b/src/api/getPartyStatus.ts deleted file mode 100644 index 2bb6691..0000000 --- a/src/api/getPartyStatus.ts +++ /dev/null @@ -1,20 +0,0 @@ -import defaultRequest from "src/lib/axios/defaultRequest"; -import variableAssignMent from "@utils/variableAssignment"; -import { PartyDetailResponse } from "types/party/detail/PartyDetailResponse"; - -interface getPartyStatusParameter { - role: string; -} - -export const API_GET_PARTY_STATUS_KEY = "/api/party/party-status?role={{role}}"; - -const getPartyStatus = async ({ - role, -}: getPartyStatusParameter): Promise => { - const { data } = await defaultRequest.get( - variableAssignMent(API_GET_PARTY_STATUS_KEY, { role: role }) - ); - return data; -}; - -export default getPartyStatus; diff --git a/src/api/getProfileReviewList.ts b/src/api/getProfileReviewList.ts deleted file mode 100644 index 9784aad..0000000 --- a/src/api/getProfileReviewList.ts +++ /dev/null @@ -1,21 +0,0 @@ -import defaultRequest from 'src/lib/axios/defaultRequest'; -import { GetReviewListResponse } from 'types/review'; - -export type ProfileReviewListRequestType = 'SENDER' | 'RECEIVER'; - -interface GetProfileReviewListParameter { - reviewType: ProfileReviewListRequestType; -} - -export const API_GET_REVIEW_LIST_KEY = '/api/review'; - -const getProfileReviewList = async ({ reviewType }: GetProfileReviewListParameter) => { - const { data } = await defaultRequest.get(API_GET_REVIEW_LIST_KEY, { - params: { - reviewType, - }, - }); - return data; -}; - -export default getProfileReviewList; diff --git a/src/api/getReviewDetail.ts b/src/api/getReviewDetail.ts new file mode 100644 index 0000000..39785e9 --- /dev/null +++ b/src/api/getReviewDetail.ts @@ -0,0 +1,20 @@ +import variableAssignment from '@utils/variableAssignment'; +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { GetReviewDetailResponse } from 'types/review'; + +interface GetReviewDetailParameter { + reviewId: string; +} + +export const API_GET_REVIEW_DETAIL = `/api/review/{{reviewId}}`; + +const getReviewDetail = async ({ reviewId }: GetReviewDetailParameter) => { + const { data } = await defaultRequest.get( + variableAssignment(API_GET_REVIEW_DETAIL, { + reviewId, + }), + ); + return data; +}; + +export default getReviewDetail; diff --git a/src/api/getReviewList.ts b/src/api/getReviewList.ts new file mode 100644 index 0000000..a621882 --- /dev/null +++ b/src/api/getReviewList.ts @@ -0,0 +1,24 @@ +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { InfinitePaginationDataType } from 'types/common/InfinitePaginationDataType'; +import { GetReviewListResponse } from 'types/review'; + +export type ReviewListRequestType = 'SENDER' | 'RECEIVER'; + +interface GetReviewListParameter { + reviewType: ReviewListRequestType; + page: number; + size: number; +} + +export const API_GET_REVIEW_LIST_KEY = '/api/review'; + +const getReviewList = async (params: GetReviewListParameter) => { + const { data } = await defaultRequest.get< + InfinitePaginationDataType<'reviewGetResList', GetReviewListResponse> + >(API_GET_REVIEW_LIST_KEY, { + params, + }); + return data; +}; + +export default getReviewList; diff --git a/src/api/getSearchResult.ts b/src/api/getSearchResult.ts index 8479ba1..8da865d 100644 --- a/src/api/getSearchResult.ts +++ b/src/api/getSearchResult.ts @@ -1,26 +1,24 @@ -import variableAssignment from "@utils/variableAssignment"; -import defaultRequest from "src/lib/axios/defaultRequest"; -import { PartyListResponse } from "types/common/PartyListResponse"; +import variableAssignment from '@utils/variableAssignment'; +import defaultRequest from 'src/lib/axios/defaultRequest'; +import { InfinitePaginationDataType } from 'types/common/InfinitePaginationDataType'; +import { PartyListResponse } from 'types/common/PartyListResponse'; interface SearchResultParams { - keyword: string; - lastPartyId: number; - size?: number; + keyword: string; + lastPartyId: number; + size?: number; } -export const API_GET_SEARCH_RESULT = - "/api/search"; +export const API_GET_SEARCH_RESULT = '/api/search'; const getSearchResult = async (params: SearchResultParams) => { - const { data } = await defaultRequest< - InfinitePaginationDataType<"partyList", PartyListResponse> - >( - API_GET_SEARCH_RESULT, { - params:{ - ...params - } - } - ); - return data; + const { data } = await defaultRequest< + InfinitePaginationDataType<'partyList', PartyListResponse> + >(API_GET_SEARCH_RESULT, { + params: { + ...params, + }, + }); + return data; }; export default getSearchResult; diff --git a/src/api/patchReview.ts b/src/api/patchReview.ts new file mode 100644 index 0000000..a4d5e72 --- /dev/null +++ b/src/api/patchReview.ts @@ -0,0 +1,14 @@ +import defaultRequest from 'src/lib/axios/defaultRequest'; + +export interface PatchReviewBody { + reviewId: number; + content: string; + rating: number; + imgUrl?: string[]; +} + +const patchReview = async (body: PatchReviewBody) => { + return defaultRequest.patch('/api/review', body); +}; + +export default patchReview; diff --git a/src/api/postParticipate.ts b/src/api/postParticipate.ts index 9dae0e8..453d1e0 100644 --- a/src/api/postParticipate.ts +++ b/src/api/postParticipate.ts @@ -1,15 +1,17 @@ -import defaultRequest from "src/lib/axios/defaultRequest"; +import defaultRequest from 'src/lib/axios/defaultRequest'; -interface postParticipateParameter { - partyId: number; - leaderId?: number; - status: string; +export type PostParticipateStatus = 'APPLY' | 'CANCEL'; + +interface PostParticipateParameter { + partyId: number; + oneLineIntroduce?: string; + status: PostParticipateStatus; } -export const API_POST_PARTY_PARTICIPATION_KEY = "/api/party/participation"; +export const API_POST_PARTY_PARTICIPATION_KEY = '/api/party/participation'; -const postParticipate = async (body: postParticipateParameter) => { - return defaultRequest.post(API_POST_PARTY_PARTICIPATION_KEY, body); +const postParticipate = async (body: PostParticipateParameter) => { + return defaultRequest.post(API_POST_PARTY_PARTICIPATION_KEY, body); }; export default postParticipate; diff --git a/src/api/postPartyDecision.ts b/src/api/postPartyDecision.ts index 723aa0d..aa6fcb3 100644 --- a/src/api/postPartyDecision.ts +++ b/src/api/postPartyDecision.ts @@ -1,15 +1,17 @@ -import defaultRequest from "src/lib/axios/defaultRequest"; +import defaultRequest from 'src/lib/axios/defaultRequest'; -interface postPartyParticipationParameter { - partyId: number; - nickname: string; - status: string; +export type PostPartyDecisionStatus = 'ACCEPT' | 'REFUSE'; + +interface PostPartyParticipationParameter { + partyId: number; + nickname: string; + status: PostPartyDecisionStatus; } -export const API_POST_PARTY_DECISION_KEY = "/api/party/decision"; +export const API_POST_PARTY_DECISION_KEY = '/api/party/decision'; -const postPartyDecision = async (body: postPartyParticipationParameter) => { - return defaultRequest.post(API_POST_PARTY_DECISION_KEY, body); +const postPartyDecision = async (body: PostPartyParticipationParameter) => { + return defaultRequest.post(API_POST_PARTY_DECISION_KEY, body); }; export default postPartyDecision; diff --git a/src/api/postReview.ts b/src/api/postReview.ts new file mode 100644 index 0000000..e64cb5e --- /dev/null +++ b/src/api/postReview.ts @@ -0,0 +1,14 @@ +import defaultRequest from 'src/lib/axios/defaultRequest'; + +export interface PostReviewBody { + partyId: number; + content: string; + rating: number; + imgUrl?: string[]; +} + +const postReview = async (body: PostReviewBody) => { + return defaultRequest.post('/api/review', body); +}; + +export default postReview; diff --git a/src/api/postUploadFiles.ts b/src/api/postUploadFiles.ts new file mode 100644 index 0000000..dd155b7 --- /dev/null +++ b/src/api/postUploadFiles.ts @@ -0,0 +1,14 @@ +import { PostFileResponse } from 'types/common/PostFileResponse'; +import { postUploadImage } from './postUploadImage'; + +interface PostUploadFiles { + fileList: File[]; +} + +const postUploadFiles = async ({ fileList }: PostUploadFiles): Promise => { + const promiseList = fileList.map((file) => postUploadImage(file)); + const result = await Promise.all(promiseList); + return result; +}; + +export default postUploadFiles; diff --git a/src/api/postUploadImage.ts b/src/api/postUploadImage.ts index 67e4142..57fe20f 100644 --- a/src/api/postUploadImage.ts +++ b/src/api/postUploadImage.ts @@ -1,18 +1,16 @@ -import defaultRequest from "src/lib/axios/defaultRequest"; +import defaultRequest from 'src/lib/axios/defaultRequest'; export interface SetImageResponse { - imgUrl: string; + imgUrl: string; } -export const postUploadImage = async (image: File) => - ( - await defaultRequest.post( - "/api/image", - { image }, - { +export const postUploadImage = async (image: File) => { + const formData = new FormData(); + formData.append('image', image); + const { data } = await defaultRequest.post('/api/image', formData, { headers: { - "Content-Type": "multipart/form-data", + 'Content-Type': 'multipart/form-data', }, - } - ) - ).data; + }); + return data; +}; diff --git a/src/components/common/BackgroundImage.tsx b/src/components/common/BackgroundImage.tsx index af6d005..676f6e3 100644 --- a/src/components/common/BackgroundImage.tsx +++ b/src/components/common/BackgroundImage.tsx @@ -1,42 +1,37 @@ -import React from "react"; -import styled from "@emotion/styled"; -import Image from "next/image"; +import React from 'react'; +import styled from '@emotion/styled'; +import Image from 'next/image'; interface BackgroundImageProps { - scrollY: number; - src?: string; - height?: number; + scrollY: number; + src?: string; + height?: number; } const Container = styled.div` - display: flex; - width: 100%; - min-height: ${({ height }) => `${height}px`}; - justify-content: center; - align-items: center; - z-index: 8; - transform: ${({ scrollY }) => `translateY(${scrollY * 0.4}px)`}; + display: flex; + width: 100%; + min-height: ${({ height }) => `${height}px`}; + justify-content: center; + align-items: center; + transform: ${({ scrollY }) => `translateY(${scrollY * 0.4}px)`}; `; -const DEFAULT_BACKGROUND_IMAGE = "/images/common/defaultBackgroundImage.jpg"; +const DEFAULT_BACKGROUND_IMAGE = '/images/common/defaultBackgroundImage.jpg'; -const BackgroundImage = ({ - scrollY, - src, - height = 400, -}: BackgroundImageProps) => { - return ( - - 배경이미지 - - ); +const BackgroundImage = ({ scrollY, src, height = 400 }: BackgroundImageProps) => { + return ( + + 배경이미지 + + ); }; export default BackgroundImage; diff --git a/src/components/common/DefaultHeader.tsx b/src/components/common/DefaultHeader.tsx index 20cf504..085b587 100644 --- a/src/components/common/DefaultHeader.tsx +++ b/src/components/common/DefaultHeader.tsx @@ -13,9 +13,9 @@ const Wrapper = styled.div` background: ${ColorToken.white}; width: 100%; position: fixed; + z-index: 3; top: 0; left: 0; - z-index: 999; height: 45px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), diff --git a/src/components/common/ReviewStarRating.tsx b/src/components/common/ReviewStarRating.tsx index 541fadb..86b49d3 100644 --- a/src/components/common/ReviewStarRating.tsx +++ b/src/components/common/ReviewStarRating.tsx @@ -6,7 +6,7 @@ interface ReviewStarRatingProps { max?: number; size?: number; color?: string; - onSetRate: (rate: number) => void; + onSetRate?: (rate: number) => void; defaultRate?: number; } diff --git a/src/components/common/Toast.tsx b/src/components/common/Toast.tsx index 2481dd2..409442c 100644 --- a/src/components/common/Toast.tsx +++ b/src/components/common/Toast.tsx @@ -1,60 +1,61 @@ -import styled from "@emotion/styled"; -import { ToastOption } from "types/toast"; -import InfoIcon from "@components/icons/toast/Info.icon"; -import WarnIcon from "@components/icons/toast/Warn.icon"; -import CloseIcon from "@components/icons/common/Close.icon"; +import styled from '@emotion/styled'; +import { ToastOption } from 'types/toast'; +import InfoIcon from '@components/icons/toast/Info.icon'; +import WarnIcon from '@components/icons/toast/Warn.icon'; +import CloseIcon from '@components/icons/common/Close.icon'; interface ToastIconProps { - type?: "info" | "warn"; + type?: 'info' | 'warn'; } interface ToastProps { - message: string; - option: ToastOption; - onToastClose: () => void; + message: string; + option: ToastOption; + onToastClose: () => void; } const Container = styled.div` - display: flex; - justify-content: space-between; - width: 320px; - height: 64px; - padding: 20px 12px; - align-items: center; - border-radius: 4px; - background: var(--neutral-white, #fff); - box-shadow: 0px 12px 24px 0px rgba(0, 0, 0, 0.2); + display: flex; + justify-content: space-between; + width: 320px; + height: 64px; + padding: 20px 12px; + align-items: center; + border-radius: 4px; + background: var(--neutral-white, #fff); + box-shadow: 0px 12px 24px 0px rgba(0, 0, 0, 0.2); + z-index: 5; `; const Content = styled.div` - display: flex; - align-items: center; - gap: 8px; + display: flex; + align-items: center; + gap: 8px; `; const ToastIcon = ({ type }: ToastIconProps) => { - switch (type) { - case "info": - return ; - case "warn": - return ; - default: - return null; - } + switch (type) { + case 'info': + return ; + case 'warn': + return ; + default: + return null; + } }; const Toast = ({ message, option, onToastClose }: ToastProps) => { - const { type } = option; - return ( - - - - {message} - - - - ); + const { type } = option; + return ( + + + + {message} + + + + ); }; export default Toast; diff --git a/src/components/common/card/ReviewCard.tsx b/src/components/common/card/ReviewCard.tsx index 20b2212..1684379 100644 --- a/src/components/common/card/ReviewCard.tsx +++ b/src/components/common/card/ReviewCard.tsx @@ -8,7 +8,7 @@ import styled from '@emotion/styled'; import dayjs from 'dayjs'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import React, { FC, MouseEventHandler, useCallback, useState } from 'react'; +import React, { FC, MouseEventHandler, useCallback, useMemo, useState } from 'react'; import { GetReviewListResponse } from 'types/review'; interface ReviewCardProps { @@ -87,6 +87,10 @@ const ReviewCard: FC = ({ data, onClickEditButton, onClickDelet push(`/review/${data.reviewId}`); }; + const formattedImage = useMemo(() => { + return data.reviewImg.map((image) => ({ id: crypto.randomUUID(), imageUrl: image })); + }, [data.reviewImg]); + return ( @@ -137,9 +141,9 @@ const ReviewCard: FC = ({ data, onClickEditButton, onClickDelet - {data.reviewImg.length > 0 ? ( + {formattedImage.length > 0 ? ( - {data.reviewImg.map((reviewImage, index) => { + {formattedImage.map((reviewImage, index) => { const handler = () => { onClickImage(index); }; @@ -173,7 +177,7 @@ const ReviewCard: FC = ({ data, onClickEditButton, onClickDelet diff --git a/src/components/hoc/QuerySuspenseErrorBoundary.tsx b/src/components/hoc/QuerySuspenseErrorBoundary.tsx index ee2cb12..11a93ce 100644 --- a/src/components/hoc/QuerySuspenseErrorBoundary.tsx +++ b/src/components/hoc/QuerySuspenseErrorBoundary.tsx @@ -1,41 +1,48 @@ -import DefaultError from "@components/common/DefaultError"; -import DefaultLoading from "@components/common/DefaultLoading"; -import { Suspense } from "@suspensive/react"; -import { QueryErrorResetBoundary } from "@tanstack/react-query"; // (*) -import { FC, PropsWithChildren, ReactEventHandler } from "react"; -import { ErrorBoundary } from "react-error-boundary"; +import DefaultError from '@components/common/DefaultError'; +import DefaultLoading from '@components/common/DefaultLoading'; +import { Suspense } from '@suspensive/react'; +import { QueryErrorResetBoundary } from '@tanstack/react-query'; // (*) +import { AxiosError } from 'axios'; +import { FC, PropsWithChildren, ReactEventHandler } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; interface QuerySuspenseErrorBoundaryProps { - children: React.ReactNode; - suspenseFallback?: React.ReactNode | string; - errorFallback?: ( - resetErrorBoundary: ReactEventHandler - ) => React.ReactNode; + children: React.ReactNode; + suspenseFallback?: React.ReactNode | string; + errorFallback?: ({ + resetErrorBoundary, + error, + }: { + resetErrorBoundary: ReactEventHandler; + error?: AxiosError; + }) => React.ReactNode; } -const QuerySuspenseErrorBoundary: FC< - PropsWithChildren -> = ({ children, suspenseFallback, errorFallback }) => { - return ( - - {({ reset }) => ( - - errorFallback ? ( - errorFallback(resetErrorBoundary) - ) : ( - - ) - } - > - }> - {children} - - - )} - - ); +const QuerySuspenseErrorBoundary: FC> = ({ + children, + suspenseFallback, + errorFallback, +}) => { + return ( + + {({ reset }) => ( + + errorFallback ? ( + errorFallback({ resetErrorBoundary, error }) + ) : ( + + ) + } + > + }> + {children} + + + )} + + ); }; export default QuerySuspenseErrorBoundary; diff --git a/src/components/home/HomeList.tsx b/src/components/home/HomeList.tsx index e82afd7..f284876 100644 --- a/src/components/home/HomeList.tsx +++ b/src/components/home/HomeList.tsx @@ -1,6 +1,5 @@ import { DefaultText } from '@components/common/DefaultText'; import NoResult from '@components/common/NoResult'; -import { ObserverTrigger } from '@components/hoc/ObserverTrigger'; import styled from '@emotion/styled'; import { useSuspenseInfiniteQuery } from '@tanstack/react-query'; import { useRouter } from 'next/router'; @@ -10,6 +9,7 @@ import getMainPageData, { API_GET_MAIN_PAGE } from 'src/api/getPartyMainPage'; import { PositionSate } from 'src/recoil-states/positionStates'; import { Color } from 'styles/Color'; import { PartyCard } from './PartyCard'; +import { ObserverTrigger } from '@components/hoc/ObserverTrigger'; const Container = styled.div` display: flex; @@ -36,12 +36,17 @@ export const HomeList: FC = () => { ? getMainPageData({ latitude: position.coords.x, longitude: position.coords.y, - lastPartyId: pageParam, + page: pageParam, size: 5, }) : Promise.resolve(null), initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage?.pageInfo?.lastPartyId, + getNextPageParam: (lastPage) => { + if (!lastPage?.pageInfo.hasNext) { + return undefined; + } + return lastPage.pageInfo.page + 1; + }, }); const onClickPartyCard = (id: number) => { diff --git a/src/components/partydetail/PartyBrief.tsx b/src/components/partydetail/PartyBrief.tsx index d66509b..46f04f9 100644 --- a/src/components/partydetail/PartyBrief.tsx +++ b/src/components/partydetail/PartyBrief.tsx @@ -1,135 +1,111 @@ -import styled from "@emotion/styled"; -import { DefaultText } from "@components/common/DefaultText"; -import ViewcountIcon from "@components/icons/profile/Viewcount.icon"; -import Divider from "@mui/material/Divider"; -import PersonIcon from "@components/icons/profile/Person.icon"; -import InfoIcon from "@components/icons/profile/Info.icon"; -import GenderIcon from "@components/icons/profile/Gender.icon"; -import { Fragment } from "react"; -import RestaurantIcon from "@components/icons/profile/Restaurant.icon"; -import { - PARTY_AGE_LABEL, - PARTY_GENDER_LABEL, - PARTY_CATEGORY_LABEL, -} from "src/constants/options"; -import { PartyDetailResponse } from "types/party/detail/PartyDetailResponse"; +import styled from '@emotion/styled'; +import { DefaultText } from '@components/common/DefaultText'; +import ViewcountIcon from '@components/icons/profile/Viewcount.icon'; +import Divider from '@mui/material/Divider'; +import PersonIcon from '@components/icons/profile/Person.icon'; +import InfoIcon from '@components/icons/profile/Info.icon'; +import GenderIcon from '@components/icons/profile/Gender.icon'; +import { Fragment } from 'react'; +import RestaurantIcon from '@components/icons/profile/Restaurant.icon'; +import { PARTY_AGE_LABEL, PARTY_GENDER_LABEL, PARTY_CATEGORY_LABEL } from 'src/constants/options'; +import { PartyDetailResponse } from 'types/party/detail/PartyDetailResponse'; type PartyBriefProps = Pick< - PartyDetailResponse, - | "partyTitle" - | "hit" - | "totalParticipant" - | "participate" - | "gender" - | "age" - | "category" + PartyDetailResponse, + 'partyTitle' | 'hit' | 'totalParticipant' | 'participate' | 'gender' | 'age' | 'category' >; const Container = styled.div` - display: flex; - flex-direction: column; - gap: 16px; - padding: 20px 20px; - background-color: white; - border-radius: 12px; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 20px; + background-color: white; + border-radius: 12px; `; const PartyTitle = styled.div` - width: 100%; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - z-index: 99; - gap: 10px; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 10px; `; const ViewcountContainer = styled.div` - display: flex; - flex-direction: row; - gap: 16px; + display: flex; + flex-direction: row; + gap: 16px; `; const PartyConditionContainer = styled.div` - display: flex; - flex-direction: row; - gap: 8px; - align-items: center; - justify-content: center; - font-size: 16px; + display: flex; + flex-direction: row; + gap: 8px; + align-items: center; + justify-content: center; + font-size: 16px; `; const PartyConditionBox = styled.div` - width: 80px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - z-index: 99; - gap: 10px; + width: 80px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + gap: 10px; `; const PartyBrief = (data: PartyBriefProps) => { - const { - partyTitle, - hit, - totalParticipant, - participate, - gender, - age, - category, - } = data; + const { partyTitle, hit, totalParticipant, participate, gender, age, category } = data; - const PartyBriefData = [ - { - id: "participant_count", - icon: , - content: `${String(participate)}/${String(totalParticipant)}명`, - }, - { - id: "gender_data", - icon: , - content: `${ - PARTY_GENDER_LABEL.find((item) => item.value === gender)?.name - }`, - }, - { - id: "age_data", - icon: , - content: `${PARTY_AGE_LABEL.find((item) => item.value === age)?.name}`, - }, - { - id: "category_data", - icon: , - content: `${ - PARTY_CATEGORY_LABEL.find((item) => item.value === category)?.name - }`, - }, - ]; + const PartyBriefData = [ + { + id: 'participant_count', + icon: , + content: `${String(participate)}/${String(totalParticipant)}명`, + }, + { + id: 'gender_data', + icon: , + content: `${PARTY_GENDER_LABEL.find((item) => item.value === gender)?.name}`, + }, + { + id: 'age_data', + icon: , + content: `${PARTY_AGE_LABEL.find((item) => item.value === age)?.name}`, + }, + { + id: 'category_data', + icon: , + content: `${PARTY_CATEGORY_LABEL.find((item) => item.value === category)?.name}`, + }, + ]; - return ( - - - - - - - - - - - {PartyBriefData.map(({ id, icon, content }, index) => ( - - {index !== 0 && } - - {icon} - - - - ))} - - - ); + return ( + + + + + + + + + + + {PartyBriefData.map(({ id, icon, content }, index) => ( + + {index !== 0 && } + + {icon} + + + + ))} + + + ); }; export default PartyBrief; diff --git a/src/components/partydetail/PartyDetail.tsx b/src/components/partydetail/PartyDetail.tsx index 799917a..f8cf0e4 100644 --- a/src/components/partydetail/PartyDetail.tsx +++ b/src/components/partydetail/PartyDetail.tsx @@ -1,67 +1,66 @@ -import styled from "@emotion/styled"; -import { DefaultText } from "@components/common/DefaultText"; -import Divider from "@mui/material/Divider"; -import dayjs from "dayjs"; -import { PartyDetailResponse } from "types/party/detail/PartyDetailResponse"; +import styled from '@emotion/styled'; +import { DefaultText } from '@components/common/DefaultText'; +import Divider from '@mui/material/Divider'; +import dayjs from 'dayjs'; +import { PartyDetailResponse } from 'types/party/detail/PartyDetailResponse'; type PartyDetailProps = Pick< - PartyDetailResponse, - "deadline" | "partyTime" | "menu" | "partyContent" + PartyDetailResponse, + 'deadline' | 'partyTime' | 'menu' | 'partyContent' >; const Container = styled.div` - display: flex; - flex-direction: column; - gap: 16px; - padding: 20px 20px; - z-index: 99; - background-color: white; - border-radius: 12px; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 20px; + background-color: white; + border-radius: 12px; `; const PartyDetailBox = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; + display: flex; + flex-direction: row; + justify-content: space-between; `; const PartyIntroduce = styled.div` - display: flex; - flex-direction: column; - gap: 16px; + display: flex; + flex-direction: column; + gap: 16px; `; const PartyDetail = (data: PartyDetailProps) => { - const { deadline, partyTime, menu, partyContent } = data; + const { deadline, partyTime, menu, partyContent } = data; - const partyDetailData = [ - { - title: "모집마감", - content: dayjs(deadline).format("YYYY.MM.MM. HH:MM"), - }, + const partyDetailData = [ + { + title: '모집마감', + content: dayjs(deadline).format('YYYY.MM.MM. HH:MM'), + }, - { - title: "모임시간", - content: dayjs(partyTime).format("YYYY.MM.MM. HH:MM"), - }, - { title: "식사메뉴", content: menu }, - ]; + { + title: '모임시간', + content: dayjs(partyTime).format('YYYY.MM.MM. HH:MM'), + }, + { title: '식사메뉴', content: menu }, + ]; - return ( - - {partyDetailData.map(({ title, content }) => ( - - - - - ))} - - - - - - - ); + return ( + + {partyDetailData.map(({ title, content }) => ( + + + + + ))} + + + + + + + ); }; export default PartyDetail; diff --git a/src/components/partydetail/PartyDetailBottomBar.tsx b/src/components/partydetail/PartyDetailBottomBar.tsx index cf1134a..de2de42 100644 --- a/src/components/partydetail/PartyDetailBottomBar.tsx +++ b/src/components/partydetail/PartyDetailBottomBar.tsx @@ -1,127 +1,190 @@ -import styled from "@emotion/styled"; -import DeleteIcon from "@components/icons/common/Delete.icon"; -import EditIcon from "@components/icons/common/Edit.icon"; -import { DefaultButton } from "@components/common/DefaultButton"; -import { DefaultModalContainer } from "@components/common/DefaultModalContainer"; -import { Transition } from "@mantine/core"; -import { useState } from "react"; -import { MouseEventHandler } from "react"; -import ConfirmPopup from "../popup/ConfirmPopup"; +import { DefaultButton } from '@components/common/DefaultButton'; +import { DefaultModalContainer } from '@components/common/DefaultModalContainer'; +import styled from '@emotion/styled'; +import useToast from '@hooks/useToast'; +import { Transition } from '@mantine/core'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import { useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import deletePartyDetail from 'src/api/deletePartyDetail'; +import { API_GET_PARTY_DETAIL_KEY } from 'src/api/getPartyDetail'; +import { API_GET_MAIN_PAGE } from 'src/api/getPartyMainPage'; +import postParticipate from 'src/api/postParticipate'; +import ConfirmPopup from '../popup/ConfirmPopup'; +import PartyDetailPartyRequestPopup from './PartyDetailPartyRequestPopup'; +import { isAxiosError } from 'axios'; +import { API_GET_PARTY_JOIN_KEY } from 'src/api/getPartyJoin'; +export interface PartyRequestPopupForm { + oneLineIntroduce: string; +} interface PartyDetailBottomBarProps { - participateParty: MouseEventHandler; - partyDetailDelete: MouseEventHandler; - isLeader: boolean; + isLeader: boolean; } const Container = styled.div` - width: 100%; - height: 75px; - display: flex; - justify-content: center; - align-items: center; - position: absolute; - position: fixed; - bottom: 0; - border-top: 1px solid #dddddd; - z-index: 99999999999999; - background-color: white; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); - transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + width: 100%; + height: 75px; + display: flex; + justify-content: center; + align-items: center; + bottom: 0; + border-top: 1px solid #dddddd; + position: fixed; + z-index: 3; + background-color: white; + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.12), + 0 1px 2px rgba(0, 0, 0, 0.24); + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); `; const IconContainer = styled.div` - width: 64px; - height: 64px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 8px; - cursor: pointer; - transition: all 0.1s; - &:hover { - background-color: #dddddd; - } + width: 64px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + cursor: pointer; + transition: all 0.1s; + &:hover { + background-color: #dddddd; + } `; const BottomBarContainer = styled.div` - width: 768px; - display: flex; -`; - -const ParcitipateButtonContainer = styled.div` - display: flex; - width: 100%; - justify-content: center; + width: 768px; + display: flex; `; -const HostPannelContainer = styled.div` - display: flex; - width: 100%; - justify-content: right; +const ButtonContainer = styled.div` + display: flex; + width: 100%; + justify-content: center; + padding: 20px; + gap: 10px; `; -const PartyDetailBottomBar = ({ - participateParty, - partyDetailDelete, - isLeader, -}: PartyDetailBottomBarProps) => { - const [isOpen, setIsOpen] = useState(false); +const PartyDetailBottomBar = ({ isLeader }: PartyDetailBottomBarProps) => { + const { push } = useRouter(); + const [isOpenDeletePopup, setIsOpenDeletePopup] = useState(false); + const [isOpenParticipatePopup, setIsOpenParticipatePopup] = useState(false); + const { showToast } = useToast(); + const router = useRouter(); + const { id } = router.query as { id: string }; + const queryClient = useQueryClient(); + const form = useForm({ + defaultValues: { + oneLineIntroduce: '', + }, + }); - const OpenConfirmPopup = () => { - setIsOpen(true); - }; + const postParticipateMutate = useMutation({ + mutationFn: postParticipate, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: [API_GET_PARTY_DETAIL_KEY, { id }], + }); + await queryClient.invalidateQueries({ + queryKey: [API_GET_PARTY_JOIN_KEY, { role: 'VOLUNTEER' }], + }); + form.reset({ oneLineIntroduce: '' }); + showToast('파티가 신청되었습니다. 방장의 수락을 기다려 주세요.'); + setIsOpenParticipatePopup(false); + }, + onError: (error) => { + if (isAxiosError(error)) { + showToast(error.response?.data.errorMessage); + } + }, + }); - const CloseConfirmPopup = () => { - setIsOpen(false); - }; + const deletePartyDetailMutate = useMutation({ + mutationFn: deletePartyDetail, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: [API_GET_MAIN_PAGE], + }); + showToast('파티가 삭제되었습니다.'); + router.push('/'); + }, + }); - const onClickEditHandler = () => { - //편집페이지 라우팅 및 데이터전달 - }; + const onValid = ({ oneLineIntroduce }: PartyRequestPopupForm) => { + postParticipateMutate.mutate({ + partyId: Number(id), + oneLineIntroduce, + status: 'APPLY', + }); + }; - return ( - - - {isLeader ? ( - - - - - - - - - ) : ( - - - - )} - - - {(styles) => ( - - - - )} - - - ); + return ( + + + {isLeader ? ( + + setIsOpenDeletePopup(true)} + /> + push(`/party/edit/${id}`)} + /> + + ) : ( + + setIsOpenParticipatePopup(true)} + /> + + )} + + + {(styles) => ( + + + { + setIsOpenParticipatePopup(false); + }} + onSubmit={form.handleSubmit(onValid)} + /> + + + )} + + + {(styles) => ( + + setIsOpenDeletePopup(false)} + confirmPopup={() => { + deletePartyDetailMutate.mutate({ id }); + }} + description="정말로 삭제하시겠습니까?" + /> + + )} + + + ); }; export default PartyDetailBottomBar; diff --git a/src/components/partydetail/PartyDetailContent.tsx b/src/components/partydetail/PartyDetailContent.tsx index e11aac9..92c5e07 100644 --- a/src/components/partydetail/PartyDetailContent.tsx +++ b/src/components/partydetail/PartyDetailContent.tsx @@ -1,74 +1,34 @@ -import PartyInfo from "@components/partydetail/PartyInfo"; -import { useRouter } from "next/router"; -import { useQuery } from "@tanstack/react-query"; -import getPartyDetail, { - API_GET_PARTY_DETAIL_KEY, -} from "src/api/getPartyDetail"; -import BackgroundImage from "@components/common/BackgroundImage"; -import PartyDetailBottomBar from "@components/partydetail/PartyDetailBottomBar"; -import postParticipate from "src/api/postParticipate"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import deletePartyDetail from "src/api/deletePartyDetail"; -import { API_GET_MAIN_PAGE } from "src/api/getPartyMainPage"; - -const PartyDetailContent = ({ y }: { y: number }) => { - const router = useRouter(); - const { id } = router.query as { id: string }; - const queryClient = useQueryClient(); - const userId = 11; - - const { data } = useQuery({ - queryKey: [API_GET_PARTY_DETAIL_KEY, { id }], - queryFn: () => getPartyDetail({ id, userId: String(userId) }), - enabled: !!id, - }); - - const postParticipateMutate = useMutation({ - mutationFn: postParticipate, - onSuccess: () => { - queryClient.invalidateQueries({ +import PartyInfo from '@components/partydetail/PartyInfo'; +import { useRouter } from 'next/router'; +import { useQuery, useSuspenseInfiniteQuery, useSuspenseQuery } from '@tanstack/react-query'; +import getPartyDetail, { API_GET_PARTY_DETAIL_KEY } from 'src/api/getPartyDetail'; +import BackgroundImage from '@components/common/BackgroundImage'; +import PartyDetailBottomBar from '@components/partydetail/PartyDetailBottomBar'; +import postParticipate from 'src/api/postParticipate'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import deletePartyDetail from 'src/api/deletePartyDetail'; +import { API_GET_MAIN_PAGE } from 'src/api/getPartyMainPage'; +import useToast from '@hooks/useToast'; +import styled from '@emotion/styled'; +import Image from 'next/image'; +import { API_GET_HOST_REVIEW_LIST, getHostReviewList } from 'src/api/getHostReviewList'; + +const PartyDetailContent = () => { + const router = useRouter(); + const { id } = router.query as { id: string }; + + const { data } = useSuspenseQuery({ queryKey: [API_GET_PARTY_DETAIL_KEY, { id }], - }); - router.push("/"); - }, - }); - - const DeletePartyDetailMutate = useMutation({ - mutationFn: deletePartyDetail, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: [API_GET_PARTY_DETAIL_KEY, { id }], - }); - router.push("/"); - }, - }); - - const participateParty = () => { - postParticipateMutate.mutate({ - partyId: Number(id), - status: "APPLY", + queryFn: () => getPartyDetail({ id }), }); - }; - - const partyDetailDelete = () => { - DeletePartyDetailMutate.mutate({ id }); - }; - return ( - <> - {data && ( + return ( <> - - - + 배경 이미지 + + - )} - - ); + ); }; export default PartyDetailContent; diff --git a/src/components/partydetail/PartyDetailPartyRequestPopup.tsx b/src/components/partydetail/PartyDetailPartyRequestPopup.tsx new file mode 100644 index 0000000..7d352e8 --- /dev/null +++ b/src/components/partydetail/PartyDetailPartyRequestPopup.tsx @@ -0,0 +1,82 @@ +import { DefaultButton } from '@components/common/DefaultButton'; +import { DefaultText } from '@components/common/DefaultText'; +import TextArea from '@components/common/TextArea'; +import styled from '@emotion/styled'; +import { FC, FormEventHandler } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { PartyRequestPopupForm } from './PartyDetailBottomBar'; + +interface PartyDetailPartyRequestPopupProps { + closePopup: VoidFunction; + onSubmit: FormEventHandler; +} + +const Container = styled.form` + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +`; +const TextSection = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 10px; +`; +const PartyDetailContainer = styled.div` + display: flex; + flex-direction: column; + padding: 40px; + width: 400px; + background-color: white; + border-radius: 12px; +`; +const ButtonContainer = styled.div` + display: flex; + width: 100%; + justify-content: space-around; + background-color: white; + gap: 10px; +`; + +const PartyDetailPartyRequestPopup: FC = ({ + closePopup, + onSubmit, +}) => { + const { register, control } = useFormContext(); + const oneLineIntroduce = useWatch({ control, name: 'oneLineIntroduce' }); + + return ( + + + + +