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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@tanstack/react-query": "^5.90.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jwt-decode": "^4.0.0",
"ky": "^1.14.2",
"react": "^19.2.0",
"react-dom": "^19.2.0",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 86 additions & 25 deletions src/page/post-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,20 @@ import UploadIcon from '@shared/assets/icon/upload.svg?react';
import { Carousel } from '@widgets/postDetail/carousel/carousel';
import { DetailInfo } from '@widgets/postDetail/detail-info';
import { Comment } from '@widgets/postDetail/comment/comment';
import type { CommentItemProps } from '@widgets/postDetail/comment/comment-item';
import Input from '@shared/ui/input';
import { FloatingActionButton } from '@shared/ui/floatingActionButton';
import SendIcon from '@shared/assets/icon/send.svg?react';
import { Button } from '@shared/ui/button';
import { useQuery } from '@tanstack/react-query';
import { FEED_QUERY_OPTIONS } from '@shared/api/domain/feeds/query';

const mockComments: CommentItemProps[] = [
{
id: 1,
author: '승택',
time: '19시간 전',
value: '제발 저요!!!',
parentId: null,
type: 'user',
},
];
import { useMutation } from '@tanstack/react-query';
import {
PARTICIPATION_MUTATION_OPTIONS,
PARTICIPATION_QUERY_OPTIONS,
} from '@shared/api/domain/participations/query';
import { getMyMemberId } from '@shared/utils/auth';
import { COMMENT_QUERY_OPTIONS } from '@shared/api/domain/comments/query';
import { queryClient } from '@app/providers/query-client';

const handleShare = async () => {
const url = window.location.href;
Expand All @@ -44,6 +40,57 @@ const PostDetailPage = () => {
const navigate = useNavigate();
const { feedId } = useParams();
const numericFeedId = Number(feedId);
const { mutate: applyParticipation } = useMutation({
...PARTICIPATION_MUTATION_OPTIONS.APPLY(),
onSuccess: () => {
console.log('✅ 참가 신청 성공');

queryClient.invalidateQueries({
queryKey: ['participations', numericFeedId],
});

queryClient.invalidateQueries({
queryKey: ['comments', numericFeedId],
});
},

onError: (error) => {
console.error('❌ 참가 신청 실패:', error);
},
});
const { data: participants } = useQuery(
PARTICIPATION_QUERY_OPTIONS.LIST(numericFeedId),
);
const { data: commentsData } = useQuery(
COMMENT_QUERY_OPTIONS.LIST(numericFeedId),
);
const { mutate: approveParticipation } = useMutation({
...PARTICIPATION_MUTATION_OPTIONS.APPROVE(),

onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['participations', numericFeedId],
});

queryClient.invalidateQueries({
queryKey: ['comments', numericFeedId],
});
},
});

const { mutate: rejectParticipation } = useMutation({
...PARTICIPATION_MUTATION_OPTIONS.REJECT(),

onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['participations', numericFeedId],
});

queryClient.invalidateQueries({
queryKey: ['comments', numericFeedId],
});
},
});

const { data, isLoading } = useQuery(
FEED_QUERY_OPTIONS.DETAIL(numericFeedId),
Expand All @@ -52,12 +99,14 @@ const PostDetailPage = () => {

if (!data) return <div>no data</div>;

const isOwner = false;
const isApplied = false;
const myId = getMyMemberId();

const isOwner = data.writer?.writerId === myId;
const isApplied = participants?.some((p) => p.applicantId === myId) ?? false;
const isClosed = false;

const canApply = !isOwner && !isApplied && !isClosed;
console.log('feed detail:', data);

return (
<div>
<TopNavigation
Expand Down Expand Up @@ -88,23 +137,35 @@ const PostDetailPage = () => {
<p className="flex typo-body1 py-[2rem] border-b">{data.description}</p>
</div>

{/* 🔥 댓글 (아직 mock) */}
<div className="px-[2.4rem] py-[2rem] border-b">
<Comment comments={mockComments} />
<Comment
comments={commentsData ?? []}
participants={participants}
isOwner={isOwner}
onChangeApproval={(participationId, status) => {
if (status === 'approved') {
approveParticipation(participationId);
} else {
rejectParticipation(participationId);
}
}}
/>
</div>

{canApply && (
<div className="flex flex-col items-center px-[2.4rem] pt-[2rem]">
<Button>참가 신청하기</Button>
<div className="flex w-full gap-[1.6rem] py-[1.4rem]">
<Input inputSize="sm" placeholder="댓글을 입력해주세요" />
<FloatingActionButton
mode="inline"
icon={<SendIcon width={'2rem'} height={'2rem'} />}
/>
</div>
<Button onClick={() => applyParticipation(numericFeedId)}>
참가 신청하기
</Button>
</div>
)}
<div className="flex w-full gap-[1.6rem] py-[1.4rem] px-[2.4rem]">
<Input inputSize="sm" placeholder="댓글을 입력해주세요" />
<FloatingActionButton
mode="inline"
icon={<SendIcon width={'2rem'} height={'2rem'} />}
/>
</div>
</div>
);
};
Expand Down
16 changes: 16 additions & 0 deletions src/shared/api/domain/comments/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '@shared/api/config/instance';
import { END_POINT } from '@shared/api/end-point';
import type { GetCommentsResponse } from '@shared/types/comments/type';

const getComments = async (feedId: number) => {
return api.get(END_POINT.FEED.COMMENTS(feedId)).json<GetCommentsResponse>();
};

export const COMMENT_QUERY_OPTIONS = {
LIST: (feedId: number) =>
queryOptions({
queryKey: ['comments', feedId],
queryFn: () => getComments(feedId),
}),
};
1 change: 1 addition & 0 deletions src/shared/api/domain/feeds/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const FEED_QUERY_OPTIONS = {
queryKey: FEED_QUERY_KEY.DETAIL(feedId),
queryFn: () => getFeedDetail(feedId),
enabled: !!feedId,
staleTime: 0,
}),
};

Expand Down
47 changes: 47 additions & 0 deletions src/shared/api/domain/participations/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { mutationOptions, queryOptions } from '@tanstack/react-query';
import { api } from '@shared/api/config/instance';
import { END_POINT } from '@shared/api/end-point';
import type { GetParticipantsResponse } from '@shared/types/participations/type';
const applyParticipation = async (feedId: number) => {
return api.post(END_POINT.FEED.PARTICIPATION(feedId)).json();
};
const approveParticipation = async (id: number) => {
return api.patch(END_POINT.PARTICIPATION.APPROVE(id)).json();
};

const rejectParticipation = async (id: number) => {
return api.patch(END_POINT.PARTICIPATION.REJECT(id)).json();
};

const getParticipants = async (
feedId: number,
): Promise<GetParticipantsResponse> => {
return api
.get(END_POINT.FEED.PARTICIPATION(feedId))
.json<GetParticipantsResponse>();
};

export const PARTICIPATION_MUTATION_OPTIONS = {
APPLY: () =>
mutationOptions({
mutationFn: applyParticipation,
}),

APPROVE: () =>
mutationOptions({
mutationFn: approveParticipation,
}),

REJECT: () =>
mutationOptions({
mutationFn: rejectParticipation,
}),
};

export const PARTICIPATION_QUERY_OPTIONS = {
LIST: (feedId: number) =>
queryOptions({
queryKey: ['participations', feedId],
queryFn: () => getParticipants(feedId),
}),
};
7 changes: 7 additions & 0 deletions src/shared/api/end-point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ export const END_POINT = {
FEED: {
LIST: 'api/feeds',
DETAIL: (feedId: number) => `api/feeds/${feedId}`,
PARTICIPATION: (feedId: number) => `api/feeds/${feedId}/participations`,
COMMENTS: (feedId: number) => `api/feeds/${feedId}/comments`, // ✅ 추가
},
PARTICIPATION: {
APPROVE: (id: number) => `api/participations/${id}/approve`,
REJECT: (id: number) => `api/participations/${id}/reject`,
},

S3: {
PRESIGNED_UPLOAD: 'api/s3/presigned-upload',
},
Expand Down
4 changes: 4 additions & 0 deletions src/shared/types/comments/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { paths } from '@shared/types/schema';

export type GetCommentsResponse =
paths['/api/feeds/{feedId}/comments']['get']['responses']['200']['content']['*/*'];
4 changes: 4 additions & 0 deletions src/shared/types/participations/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { paths } from '@shared/types/schema';

export type GetParticipantsResponse =
paths['/api/feeds/{feedId}/participations']['get']['responses']['200']['content']['*/*'];
9 changes: 9 additions & 0 deletions src/shared/utils/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { jwtDecode } from 'jwt-decode';

export const getMyMemberId = () => {
const token = localStorage.getItem('accessToken');
if (!token) return null;

const decoded: any = jwtDecode(token);
return decoded.memberId;
};
Loading