feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View file

@ -18,16 +18,20 @@ export function PortalButton(): React.ReactElement {
try {
const supabase = getSupabaseBrowserClient()
const {
data: { session }
data: { session },
error: sessionError
} = await supabase.auth.getSession()
if (!session) {
if (sessionError || !session) {
setError('로그인이 필요합니다')
return
}
const baseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.trim()
if (!baseUrl) throw new Error('결제 서버 설정이 완료되지 않았습니다')
const response = await fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/stripe-portal`,
new URL('/functions/v1/stripe-portal', baseUrl).toString(),
{
method: 'POST',
headers: {
@ -40,15 +44,14 @@ export function PortalButton(): React.ReactElement {
}
)
if (!response.ok) {
const txt = await response.text()
throw new Error(`Portal 열기 실패: ${response.status} ${txt}`)
}
const data = (await response.json()) as { url?: string }
if (data.url) {
window.location.href = data.url
const data = await response.json().catch(() => null) as { url?: unknown } | null
if (!response.ok) throw new Error('Stripe 구독 관리 페이지를 열지 못했습니다')
if (typeof data?.url !== 'string') throw new Error('구독 관리 URL을 받지 못했습니다')
const portalUrl = new URL(data.url)
if (portalUrl.protocol !== 'https:' || portalUrl.hostname !== 'billing.stripe.com') {
throw new Error('구독 관리 URL을 신뢰할 수 없습니다')
}
window.location.assign(portalUrl.toString())
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error')
} finally {
@ -61,6 +64,7 @@ export function PortalButton(): React.ReactElement {
<Button
variant="outlined"
size="small"
data-testid="stripe-portal-open"
startIcon={busy ? <CircularProgress size={14} /> : <SettingsIcon />}
onClick={() => void handleOpenPortal()}
disabled={busy}