diff --git a/apps/web/src/app/tournament/[id]/result/_components/ResultClient.tsx b/apps/web/src/app/tournament/[id]/result/_components/ResultClient.tsx index 812b290a..0f7c0bd5 100644 --- a/apps/web/src/app/tournament/[id]/result/_components/ResultClient.tsx +++ b/apps/web/src/app/tournament/[id]/result/_components/ResultClient.tsx @@ -137,7 +137,6 @@ function ResultClient({ tournamentId, isGuest = false, isApp = false }: ResultCl open={isShareDialogOpen} onOpenChange={setIsShareDialogOpen} tournamentId={tournamentId} - initialPlayLinkExpiresAt={tournamentData.completed.playLinkExpiresAt} /> void; tournamentId: number; - /** 서버 응답의 playLinkExpiresAt — 아직 생성 전이면 undefined */ - initialPlayLinkExpiresAt?: string; }; const buildPlayLinkUrl = (tournamentId: number) => { @@ -27,25 +25,20 @@ const buildPlayLinkUrl = (tournamentId: number) => { return `${window.location.origin}${path}`; }; -function PlateShareDialog({ - open, - onOpenChange, - tournamentId, - initialPlayLinkExpiresAt, -}: PlateShareDialogProps) { +function PlateShareDialog({ open, onOpenChange, tournamentId }: PlateShareDialogProps) { const { postPlayLinkMutation, isPostPlayLinkPending } = usePostPlayLink(tournamentId); - // 이미 만들어진 플레이 링크가 있으면 mutation 건너뛰고 바로 공유한다. - const hasExistingPlayLink = Boolean(initialPlayLinkExpiresAt); - const handleSendPlayLink = async () => { - if (!hasExistingPlayLink) { - try { - await postPlayLinkMutation(); - } catch { - /** 안내는 usePostPlayLink 훅 레벨 onError 가 담당 — 여기선 공유 플로우만 중단 */ - return; - } + /** + * 생성 API 가 멱등하므로 상태를 따지지 않고 항상 호출한다. + * 미생성이면 만들고, 만료됐으면 새로 발급하고, 살아 있으면 기존 만료시각을 그대로 준다. + * 클라가 만료 여부를 판단하려 들면 죽은 링크를 그대로 공유하게 된다. + */ + try { + await postPlayLinkMutation(); + } catch { + /** 안내는 usePostPlayLink 훅 레벨 onError 가 담당 — 여기선 공유 플로우만 중단 */ + return; } const result = await share({ diff --git a/apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts b/apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts index 6f23d276..e29ea0ce 100644 --- a/apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts +++ b/apps/web/src/app/tournament/[id]/result/_hooks/usePostPlayLink.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { isGlobalNetError } from '@/utils/apiError'; @@ -7,16 +7,22 @@ import { getApiErrorMessage } from '@/utils/getApiErrorMessage'; import { postPlayLink } from '../_apis/postPlayLink'; export const usePostPlayLink = (tournamentId: number) => { + const queryClient = useQueryClient(); + const { mutateAsync: postPlayLinkMutation, isPending: isPostPlayLinkPending } = useMutation({ mutationFn: () => postPlayLink(tournamentId), + /** 만료됐다면 새 기한을 받으므로 상세에 들고 있던 만료시각을 갱신한다 */ + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['tournament', tournamentId] }); + }, /** 문구는 훅 레벨에서만 — 호출부(mutateAsync catch)에서 토스트하면 전역 fallback 과 겹친다 */ onError: error => { if (isGlobalNetError(error)) return; /** - * 403: 플레이 링크로 참여한 토너먼트는 공유 링크 생성 불가 + * 403: 플레이 링크로 참여한 토너먼트·주최자가 아닌 경우 * 404: 토너먼트 존재하지 않음 - * 409: COMPLETED 아닌 토너먼트·이미 생성된 플레이 링크 + * 409: COMPLETED 아닌 토너먼트 */ toast.error(getApiErrorMessage(error)); },