'use client'
// apps/web/src/app/accept-invite/page.tsx
// 팀 초대 수락 페이지 — URL의 ?token=을 team-accept Edge Function으로 전달
import { Suspense, useEffect, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { Box, Stack, Alert, CircularProgress } from '@mui/material'
import { MetalCard, PhosphorText } from '@d3ro/ui/components/ds'
import { d3roPalette } from '@d3ro/ui/theme'
import { getSupabaseBrowserClient, isSupabaseConfigured } from '@/lib/supabase-browser'
type AcceptState = 'loading' | 'success' | 'error' | 'need_login'
// useSearchParams는 Suspense boundary 필요 (Next.js 15 prerender 규칙)
export default function AcceptInvitePage(): React.ReactElement {
return (
}
>
)
}
function AcceptInviteInner(): React.ReactElement {
const router = useRouter()
const searchParams = useSearchParams()
const token = searchParams.get('token')
const [state, setState] = useState('loading')
const [message, setMessage] = useState('')
const [teamId, setTeamId] = useState(null)
useEffect(() => {
async function run(): Promise {
if (!token) {
setState('error')
setMessage('유효하지 않은 초대 링크입니다 (토큰 누락).')
return
}
if (!isSupabaseConfigured()) {
setState('error')
setMessage('Supabase가 설정되지 않았습니다.')
return
}
const supabase = getSupabaseBrowserClient()
const {
data: { session }
} = await supabase.auth.getSession()
if (!session) {
setState('need_login')
setMessage('초대를 수락하려면 먼저 로그인해주세요.')
// 토큰을 sessionStorage에 저장해두고 로그인 후 돌아오도록
try {
sessionStorage.setItem('pending_invite_token', token)
} catch {
// storage 차단 시 무시
}
return
}
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/team-accept`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${session.access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ token })
}
)
if (!response.ok) {
const errData = (await response.json()) as { error?: string; message?: string }
setState('error')
setMessage(errData.message ?? errData.error ?? `실패: ${response.status}`)
return
}
const data = (await response.json()) as { team_id: string; role: string }
setTeamId(data.team_id)
setState('success')
setMessage(`팀에 가입되었습니다 (${data.role}). 잠시 후 이동합니다...`)
// 성공 시 3초 후 팀 페이지로
setTimeout(() => {
router.replace(`/teams/${data.team_id}`)
}, 2000)
} catch (e) {
setState('error')
setMessage(e instanceof Error ? e.message : 'Unknown error')
}
}
void run()
}, [token, router])
return (
TEAM INVITE
{state === 'loading' && }
{state === 'success' && (
{message}
)}
{state === 'error' && (
<>
{message}
대시보드로 이동
>
)}
{state === 'need_login' && (
<>
{message}
로그인 페이지로 이동
>
)}
{teamId && (
Team ID: {teamId}
)}
)
}