diff --git a/apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx b/apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx index fcd3302a..050d7da0 100644 --- a/apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx +++ b/apps/web/src/app/tournament/[id]/create/_components/invite-friends/InviteFriendsDialog.tsx @@ -4,16 +4,16 @@ import { ERROR_CODE, ERROR_MESSAGE_MAP } from '@piki/core'; import { useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; -import { CheckIconFill, StopwatchIconFill } from '@/assets/icons/fill'; +import { CheckIconFill, TimerIconFill } from '@/assets/icons/fill'; import Button from '@/components/button'; import { Drawer, DrawerContent, DrawerDescription, DrawerTitle } from '@/components/drawer'; import { ANALYTICS_EVENT } from '@/consts/analytics'; import { logAnalyticsEvent } from '@/utils/analytics'; import { parseServerLocalDateTime } from '@/utils/formatDate'; -import { share } from '@/utils/share'; +import { markInviteSent } from '@/utils/inviteSentSession'; +import { copyToClipboard, share } from '@/utils/share'; import { usePatchInviteExpiry } from '../../_hooks/usePatchInviteExpiry'; -import { markInviteSent } from '@/utils/inviteSentSession'; import InviteExpiresPicker from './InviteExpiresPicker'; type InviteFriendsDialogProps = { @@ -22,6 +22,8 @@ type InviteFriendsDialogProps = { /** 마감 시각 변경 API 호출용 */ tournamentId: number; inviteUrl?: string; + /** 초대 코드 — 링크 대신 코드로 참여할 때 쓴다 */ + inviteCode?: string; /** ISO 8601 — 초대 코드 만료 시각 */ inviteExpiresAt?: string; }; @@ -68,6 +70,7 @@ function InviteFriendsDialog({ onOpenChange, tournamentId, inviteUrl, + inviteCode, inviteExpiresAt, }: InviteFriendsDialogProps) { const [isPickerOpen, setIsPickerOpen] = useState(false); @@ -91,6 +94,17 @@ function InviteFriendsDialog({ const handleOpenPicker = () => setIsPickerOpen(true); + const handleCopyInviteCode = async () => { + if (!inviteCode) return; + + try { + await copyToClipboard(inviteCode); + toast.success('초대 코드를 복사했어요.'); + } catch { + toast.warning('이 환경에서는 복사를 지원하지 않아요.'); + } + }; + const handleConfirmExpires = (newExpiresAt: string) => { patchInviteExpiryMutation( { newExpiresAt }, @@ -133,49 +147,72 @@ function InviteFriendsDialog({
- + 친구 초대하기 - - 초대 링크를 보내 친구와 함께 담을 수 있어요. + + 링크를 보내 친구와 함께 담을 수 있어요.
- {expiresInfo && ( -
-
-
- +
+ {expiresInfo && ( +
+

담기 기한

+
+
+
+ +
+
+

+ {expiresInfo.remainingLabel} +

+

+ {expiresInfo.absoluteLabel} +

+
+
+
-
-

{expiresInfo.remainingLabel}

-

- {expiresInfo.absoluteLabel} -

+
+ )} + + {inviteCode && ( +
+

초대 코드

+
+

{inviteCode}

+
- -
- )} - -
-
- -

- 최대 7명까지 초대할 수 있어요. -

-
-
- -

- 설정한 기한이 지나면 후보를 담을 수 없어요. -

+ )} + +
+
+ +

+ 최대 7명까지 초대할 수 있어요. +

+
+
+ +

+ 설정한 기한이 지나면 후보를 담을 수 없어요. +

+
diff --git a/apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsx b/apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsx index 7f131884..9a26276d 100644 --- a/apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsx +++ b/apps/web/src/app/tournament/[id]/create/_components/participant-panel/ParticipantPanel.tsx @@ -158,6 +158,7 @@ function ParticipantPanel({ onOpenChange={setIsInviteDialogOpen} tournamentId={Number(tournamentId)} inviteUrl={inviteUrl} + inviteCode={inviteCode} inviteExpiresAt={inviteExpiresAt} /> diff --git a/apps/web/src/utils/share.ts b/apps/web/src/utils/share.ts index 451fb222..32c27f49 100644 --- a/apps/web/src/utils/share.ts +++ b/apps/web/src/utils/share.ts @@ -11,7 +11,7 @@ const canUseWebShare = (data: ShareDataT) => { return true; }; -const copyToClipboard = async (text: string) => { +export const copyToClipboard = async (text: string) => { if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return;