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:
parent
d0e854c255
commit
8af75a0a1e
50 changed files with 2185 additions and 950 deletions
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue