feat(billing): implement US-3.7 billing and subscription UX with detailed dashboard, real-time invoice history, inline paywall and upgrade confirmation

This commit is contained in:
Antigravity
2026-05-27 22:01:21 +00:00
parent da4b5d18be
commit 529fb7a935
8 changed files with 606 additions and 182 deletions

View File

@@ -23,6 +23,7 @@ import { useLanguage } from '@/lib/i18n'
import { MarkdownContent } from '@/components/markdown-content'
import { toast } from 'sonner'
import { useAiConsent } from '@/components/legal/ai-consent-provider'
import { InlinePaywall } from './settings/inline-paywall'
// ── Custom Toast Helper ──────────────────────────────────────────────────────
const mToast = {
@@ -204,6 +205,11 @@ export function ContextualAIChat({
const [chatScope, setChatScope] = useState<'note' | 'all' | string>('note')
const [webSearch, setWebSearch] = useState(false)
const [expanded, setExpanded] = useState(false)
const [quotaExceededFeature, setQuotaExceededFeature] = useState<string | null>(null)
useEffect(() => {
setQuotaExceededFeature(null)
}, [activeTab])
// Action state
const [actionLoading, setActionLoading] = useState<string | null>(null)
@@ -289,12 +295,16 @@ export function ContextualAIChat({
const consented = await requestAiConsent()
if (!consented) return
setInput('')
try {
await sendMessage({ text }, { body: buildChatBody() })
} catch (error) {
} catch (error: any) {
console.error('Chat send error:', error)
toast.error(t('chat.assistantError') || 'Failed to send message')
const isQuota = error?.status === 402 || (error?.message && error.message.includes('402')) || (error?.message && error.message.includes('quota'));
if (isQuota) {
setQuotaExceededFeature('chat')
} else {
toast.error(t('chat.assistantError') || 'Failed to send message')
}
}
}
@@ -317,6 +327,10 @@ export function ContextualAIChat({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(action.body('', noteImages, language)),
})
if (res.status === 402) {
setQuotaExceededFeature('reformulate')
return
}
const data = await res.json()
if (!res.ok) throw new Error(data.error || t('ai.genericError'))
const descs = data.descriptions || []
@@ -349,6 +363,10 @@ export function ContextualAIChat({
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(action.body(noteContent, undefined, targetLang || language, format)),
})
if (res.status === 402) {
setQuotaExceededFeature('reformulate')
return
}
const data = await res.json()
if (!res.ok) throw new Error(data.error || t('ai.genericError'))
const result = data[action.resultKey] || ''
@@ -671,6 +689,14 @@ export function ContextualAIChat({
</div>
<div className="flex-1 flex flex-col min-h-0 relative">
{quotaExceededFeature && (
<div className="absolute inset-x-4 top-4 z-30 animate-in fade-in slide-in-from-top-4 duration-300">
<InlinePaywall
feature={quotaExceededFeature}
onDismiss={() => setQuotaExceededFeature(null)}
/>
</div>
)}
{actionPreview && (
<div className="absolute inset-0 z-20 flex flex-col bg-[#FDFCFB]/95 dark:bg-[#0D0D0D]/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
<div className="px-6 py-4 border-b border-border flex items-center justify-between shrink-0">