feat: V2-7 Admin 콘솔 리디자인 + 3단계 권한 + SaaS 전환 + 코드 정리

- Admin CRM: D3RO Console 스타일 전체 적용 (panelSx/tableSx/filterBtnSx)
- 3단계 권한: manager/admin/super_admin (DB + Edge Functions + Frontend)
- 랜딩 페이지: 1회 결제 → 월간/연간 구독 SaaS 모델 (10개 언어)
- SSE 스트리밍: VoiceConversation Premium LLM 라우팅 + fallback
- Supabase 클라이언트: packages/api-client 공통 추출 (browser+server)
- RPC 함수 타입: 9개 정의 (admin_usage_by_feature 등)
- callAdminApi 401 버그 수정 (getUser() 선행 토큰 갱신)
This commit is contained in:
윤찬 2026-04-13 01:28:10 +09:00
parent d0e854c255
commit 8af75a0a1e
50 changed files with 2185 additions and 950 deletions

View file

@ -1,26 +1,6 @@
// apps/web/src/lib/supabase-browser.ts
// 브라우저 컴포넌트용 Supabase 클라이언트.
'use client'
// apps/web/src/lib/supabase-browser.ts
// Database 제네릭 주입 (@supabase/ssr 0.10 + supabase-js 2.103 정합)
// re-export from shared package
import { createBrowserClient } from '@supabase/ssr'
import type { Database } from '@d3ro/api-client'
let cachedClient: ReturnType<typeof createBrowserClient<Database>> | null = null
export function getSupabaseBrowserClient(): ReturnType<typeof createBrowserClient<Database>> {
if (cachedClient) return cachedClient
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'https://placeholder.supabase.co'
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? 'placeholder-anon-key'
cachedClient = createBrowserClient<Database>(url, key)
return cachedClient
}
export function isSupabaseConfigured(): boolean {
return Boolean(process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)
}
export { getSupabaseBrowserClient, isSupabaseConfigured } from '@d3ro/api-client/supabase-browser'

View file

@ -1,35 +1,12 @@
// apps/web/src/lib/supabase-server.ts
// RSC/route handler용 Supabase 클라이언트 (쿠키 기반 세션).
// Database 제네릭 주입 (@supabase/ssr 0.10 + supabase-js 2.103 정합).
// Next.js cookies() 주입 래퍼 — 실제 로직은 @d3ro/api-client
import { cookies } from 'next/headers'
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import type { Database } from '@d3ro/api-client'
import { createSupabaseServerClient, isSupabaseConfiguredServer } from '@d3ro/api-client/supabase-server'
export async function getSupabaseServerClient(): Promise<ReturnType<typeof createServerClient<Database>>> {
export { isSupabaseConfiguredServer }
export async function getSupabaseServerClient() {
const cookieStore = await cookies()
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'https://placeholder.supabase.co'
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? 'placeholder-anon-key'
return createServerClient<Database>(url, key, {
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet: Array<{ name: string; value: string; options: CookieOptions }>) {
try {
cookiesToSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options)
})
} catch {
// RSC에서 set은 실패하지만 route handler에서는 성공
}
}
}
})
}
export function isSupabaseConfiguredServer(): boolean {
return Boolean(process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)
return createSupabaseServerClient(cookieStore)
}