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

@ -27,7 +27,7 @@ export function CTA() {
{t.cta.subtitle2}
</p>
<GlowButton href="https://github.com/user/D3ROVoice/releases" variant="primary" size="lg">
<GlowButton href="#" variant="primary" size="lg">
{t.cta.downloadBtn}
</GlowButton>

View file

@ -48,7 +48,7 @@ export function Hero() {
<div className="flex flex-col sm:flex-row items-start gap-4 mb-16">
<a
href="https://github.com/user/D3ROVoice/releases"
href="#"
className="glow-btn inline-flex items-center gap-2 px-8 py-4 font-mono text-sm font-medium uppercase tracking-wider text-white bg-brand-amber rounded-panel"
>
<span className="relative z-10 flex items-center gap-2">

View file

@ -1,3 +1,4 @@
import { useState } from 'react'
import { SectionHeader } from '../components/SectionHeader'
import { Container } from '../components/Container'
import { GlowButton } from '../components/GlowButton'
@ -21,8 +22,18 @@ function CellValue({ value }: { value: string | boolean }) {
return <span className="font-mono text-xs text-neutral-300">{value}</span>
}
const PRICES = {
pro: { monthly: 9.9, annual: 99 },
proPlus: { monthly: 19.9, annual: 199 },
}
export function Pricing() {
const { t } = useI18n()
const [isAnnual, setIsAnnual] = useState(true)
const proPrice = isAnnual ? PRICES.pro.annual : PRICES.pro.monthly
const proPlusPrice = isAnnual ? PRICES.proPlus.annual : PRICES.proPlus.monthly
const periodLabel = isAnnual ? t.pricing.annual : t.pricing.monthly
return (
<section id="pricing" className="relative py-24 md:py-32">
@ -33,6 +44,35 @@ export function Pricing() {
subtitle={t.pricing.subtitle}
/>
{/* Billing toggle */}
<div className="flex items-center justify-center gap-3 mb-10">
<button
onClick={() => setIsAnnual(false)}
className={`font-mono text-xs uppercase tracking-widest px-4 py-2 rounded-panel transition-all ${
!isAnnual
? 'bg-surface-500 text-neutral-100 border border-white/[0.12]'
: 'text-neutral-500 hover:text-neutral-300'
}`}
>
{t.pricing.billingToggleMonthly}
</button>
<button
onClick={() => setIsAnnual(true)}
className={`font-mono text-xs uppercase tracking-widest px-4 py-2 rounded-panel transition-all relative ${
isAnnual
? 'bg-surface-500 text-neutral-100 border border-brand-amber/30'
: 'text-neutral-500 hover:text-neutral-300'
}`}
>
{t.pricing.billingToggleAnnual}
{isAnnual && (
<span className="absolute -top-2.5 -right-2 px-1.5 py-0.5 rounded-full bg-brand-amber text-white text-[9px] font-mono font-semibold uppercase tracking-wider">
{t.pricing.savePercent}
</span>
)}
</button>
</div>
{/* Pricing table */}
<div className="overflow-x-auto -mx-5 md:mx-0">
<table className="w-full min-w-[640px] border-collapse">
@ -54,13 +94,27 @@ export function Pricing() {
</span>
</div>
<div className="font-mono text-nano uppercase tracking-widest text-brand-amber mt-4 mb-1">{t.pricing.pro}</div>
<div className="font-display text-xl font-bold text-neutral-100">$29</div>
<div className="font-mono text-nano text-neutral-600">{t.pricing.oneTime}</div>
<div className="font-display text-xl font-bold text-neutral-100">
${proPrice}
<span className="text-sm font-normal text-neutral-500">{periodLabel}</span>
</div>
{isAnnual && (
<div className="font-mono text-nano text-neutral-600">
${(PRICES.pro.annual / 12).toFixed(1)}{t.pricing.perMonth}
</div>
)}
</th>
<th className="text-center py-4 px-4 w-[20%]">
<div className="font-mono text-nano uppercase tracking-widest text-neutral-500 mb-1">{t.pricing.proPlus}</div>
<div className="font-display text-xl font-bold text-neutral-300">$49</div>
<div className="font-mono text-nano text-neutral-600">{t.pricing.oneTime}</div>
<div className="font-display text-xl font-bold text-neutral-300">
${proPlusPrice}
<span className="text-sm font-normal text-neutral-500">{periodLabel}</span>
</div>
{isAnnual && (
<div className="font-mono text-nano text-neutral-600">
${(PRICES.proPlus.annual / 12).toFixed(1)}{t.pricing.perMonth}
</div>
)}
</th>
</tr>
</thead>
@ -95,7 +149,7 @@ export function Pricing() {
{/* CTA row */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-8">
<div className="flex justify-center">
<GlowButton href="https://github.com/user/D3ROVoice/releases" variant="secondary" size="md">
<GlowButton href="#" variant="secondary" size="md">
{t.pricing.downloadFree}
</GlowButton>
</div>