diff --git a/src/app/group/[groupId]/pending/page.tsx b/src/app/group/[groupId]/pending/page.tsx new file mode 100644 index 00000000..7c8d7ea1 --- /dev/null +++ b/src/app/group/[groupId]/pending/page.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { use } from 'react'; + +import { + GroupPendingHeader, + GroupPendingMembers, + GroupPendingSummary, +} from '@/components/pages/group'; + +interface Props { + params: Promise<{ groupId: string }>; +} + +const GroupPendingMembersPage = ({ params }: Props) => { + const { groupId } = use(params); + + return ( + <> + + + + + ); +}; + +export default GroupPendingMembersPage; diff --git a/src/components/pages/group/group-pending-header/index.tsx b/src/components/pages/group/group-pending-header/index.tsx new file mode 100644 index 00000000..a7d595fc --- /dev/null +++ b/src/components/pages/group/group-pending-header/index.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { useRouter } from 'next/navigation'; + +import { Icon } from '@/components/icon'; + +export const GroupPendingHeader = () => { + const router = useRouter(); + + const handleBackClick = () => { + router.back(); + }; + + return ( +
+ +

참여 신청

+
+ ); +}; diff --git a/src/components/pages/group/group-pending-members/index.tsx b/src/components/pages/group/group-pending-members/index.tsx new file mode 100644 index 00000000..e04a9c4d --- /dev/null +++ b/src/components/pages/group/group-pending-members/index.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { useCallback } from 'react'; + +import { EmptyState } from '@/components/layout/empty-state'; +import { GetPendingMembersResponse } from '@/types/service/group'; + +import { PendingMemberCard } from './pending-member-card'; + +interface Props { + groupId: string; + pendingMembers?: GetPendingMembersResponse['pendingMembers']; +} + +const MOCK_PENDING_MEMBERS: GetPendingMembersResponse['pendingMembers'] = [ + { + userId: 101, + groupUserId: 1, + nickName: 'Hope Lee', + profileImage: null, + profileMessage: 'Here We Go Again :)', + requestMessage: '안녕하세요 함께하고 싶습니다!', + requestedAt: '2025-12-24T13:20:10.123456', + }, + { + userId: 102, + groupUserId: 2, + nickName: '바다소년', + profileImage: null, + profileMessage: '물고기를 좋아합니다.', + requestMessage: '보드게임 정말 좋아해서 신청합니다! 앞으로 잘 부탁드려요', + requestedAt: '2025-12-24T13:25:15.789012', + }, + { + userId: 103, + groupUserId: 3, + nickName: '박디자인', + profileImage: null, + profileMessage: 'UI/UX 디자이너', + requestMessage: null, + requestedAt: '2025-12-24T13:30:20.456789', + }, +]; + +export const GroupPendingMembers = ({ groupId, pendingMembers = MOCK_PENDING_MEMBERS }: Props) => { + const handleReject = useCallback( + (memberId: number) => { + console.log('거절:', groupId, memberId); + }, + [groupId], + ); + + const handleApprove = useCallback( + (memberId: number) => { + console.log('승인:', groupId, memberId); + }, + [groupId], + ); + + if (pendingMembers?.length === 0) { + return ( +
+ 승인 대기 중인 멤버가 없습니다 +
+ ); + } + + return ( +
+
    + {pendingMembers.map((member) => ( +
  • + handleApprove(member.groupUserId)} + onReject={() => handleReject(member.groupUserId)} + /> +
  • + ))} +
+
+ ); +}; diff --git a/src/components/pages/group/group-pending-members/pending-member-card/index.tsx b/src/components/pages/group/group-pending-members/pending-member-card/index.tsx new file mode 100644 index 00000000..dd2b5d7d --- /dev/null +++ b/src/components/pages/group/group-pending-members/pending-member-card/index.tsx @@ -0,0 +1,60 @@ +'use client'; + +import Link from 'next/link'; + +import { Button, ImageWithFallback } from '@/components/ui'; +import { GetPendingMembersResponse } from '@/types/service/group'; + +type PendingMember = GetPendingMembersResponse['pendingMembers'][number]; + +interface Props { + member: PendingMember; + onReject: () => void; + onApprove: () => void; +} + +export const PendingMemberCard = ({ member, onReject, onApprove }: Props) => { + const profileUrl = `/profile/${member.userId}`; + + return ( +
+ + + +
+

{member.nickName}

+ {member.profileMessage && ( +

{member.profileMessage}

+ )} +
+ + + {member.requestMessage && ( +

+ {member.requestMessage} +

+ )} + +
+ + +
+
+ ); +}; diff --git a/src/components/pages/group/group-pending-summary/index.tsx b/src/components/pages/group/group-pending-summary/index.tsx new file mode 100644 index 00000000..b39e0c1f --- /dev/null +++ b/src/components/pages/group/group-pending-summary/index.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { DEFAULT_GROUP_IMAGE } from 'constants/default-images'; + +import { ImageWithFallback } from '@/components/ui'; + +interface Props { + thumbnail?: string | null; + title?: string; + pendingCount?: number; +} + +const MOCK_DATA = { + thumbnail: null, + title: '매우 긴 제목을 가진 모임입니다 이 제목은 너무 길어서 잘려야 합니다', + pendingCount: 5, +}; + +export const GroupPendingSummary = ({ + thumbnail = MOCK_DATA.thumbnail, + title = MOCK_DATA.title, + pendingCount = MOCK_DATA.pendingCount, +}: Props) => { + return ( +
+
+ +
+ +
+

{title}

+ +
+ 신청한 유저 + {pendingCount} + +
+
+
+ ); +}; diff --git a/src/components/pages/group/index.ts b/src/components/pages/group/index.ts index 94287ece..e498876c 100644 --- a/src/components/pages/group/index.ts +++ b/src/components/pages/group/index.ts @@ -2,3 +2,6 @@ export { GroupBannerImages } from './group-banner-images'; export { GroupButtons } from './group-buttons'; export { GroupDescriptions } from './group-descriptions'; export { GroupMembers } from './group-members'; +export { GroupPendingHeader } from './group-pending-header'; +export { GroupPendingMembers } from './group-pending-members'; +export { GroupPendingSummary } from './group-pending-summary'; diff --git a/src/mock/service/group/group-mock.ts b/src/mock/service/group/group-mock.ts deleted file mode 100644 index baf9db90..00000000 --- a/src/mock/service/group/group-mock.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Group } from '@/types/service/group'; - -export const groupMockItem: Group[] = [ - { - id: 1, - title: '동탄 호수공원에서 피크닉하실 분!', - location: '화성시', - locationDetail: '동탄 호수공원', - startTime: '2025-12-07T17:00:00+09:00', - endTime: '2025-12-07T19:00:00+09:00', - images: [ - 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', - ], - tags: ['게임', '피크닉'], - description: '동탄 호수공원에서 어쩌구 저쩌구', - participantCount: 3, - maxParticipants: 12, - createdBy: { - userId: 1, - nickName: '리오넬 메시', - profileImage: - 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', - }, - createdAt: '2025-12-06T17:00:00+09:00', - updatedAt: '2025-12-06T17:00:00+09:00', - joinedCount: 3, - }, -]; diff --git a/src/types/service/group.ts b/src/types/service/group.ts index 66e06114..5b91251e 100644 --- a/src/types/service/group.ts +++ b/src/types/service/group.ts @@ -84,35 +84,6 @@ export interface GetMyGroupsResponse { nextCursor: number | null; } -/** - - * 밑에 타입들은 다른 분들이 아직 다른 파일에서 사용 중이므로 그냥 제거해버리시면 안됩니다(다른 분들도 다 수정 후에 제거 예정) - * 아직 사용 중인 파일: - * - src/types/service/notification.ts (Notification.group) - * - src/mock/service/group/group-mock.ts (groupMockItem) - */ -export interface Group { - id: number; - title: string; - location: string; - locationDetail: string; - startTime: string; - endTime: string; - images: string[]; - tags: string[]; - description: string; - participantCount: number; - maxParticipants: number; - createdBy: { - userId: number; - nickName: string; - profileImage: null | string; - }; - createdAt: string; - updatedAt: string; - joinedCount: number; -} - export interface PreUploadGroupImagePayload { images: File[]; } @@ -185,6 +156,7 @@ export interface CreateGroupResponse { export interface GetGroupDetailsResponse { id: number; title: string; + joinPolicy: GroupV2JoinPolicy; status: GroupV2Status; address: { location: string; @@ -233,3 +205,19 @@ export interface GetGroupDetailsResponse { export interface GroupIdParams { groupId: string; } + +// 승인 대기자 목록 조회 응답 (GET /api/v2/groups/{groupId}/attendance/pending) +export interface GetPendingMembersResponse { + groupId: number; + joinPolicy: GroupV2JoinPolicy; + pendingMembers: { + userId: number; + groupUserId: number; + nickName: string; + profileImage: string | null; + profileMessage: string | null; + requestMessage: string | null; + requestedAt: string; + }[]; + serverTime: string; +}