fix(entitlements): gate remaining paid-feature UI, drop stale beta pricing copy (#921)
* fix(entitlements): gate remaining paid-feature UI and drop stale beta pricing copy Second post-cutover sweep. Non-payers still hit live controls that 403 at the server gate, plus dead plan names in the inbox guide: - document-inbox guide: remove "Gratis under beta för Open-användare / Pro-planen / Se priser" (plans that no longer exist); inbox stays free, AI extraction is pitched as part of the subscription - payslip send button (RunProgressBar): disabled + tooltip without email_send; the free PDF download next to it is untouched - skattekonto "Synkronisera nu": disabled + tooltip without skatteverket - invoice post-create send-now dialog: skipped without email_send (the invoice page's SendInvoiceDialog carries the upsell) - recurring-invoice auto_send checkbox: disabled + UpgradeNote without email_send, and force-unchecked so edit mode can't PATCH it back on - AgentChat composer: replaced with UpgradeNote without ai, covering deep links to /chat/* and conversations opened before expiry - /onboarding/agent server page: hasCapability(ai) redirect to /settings/billing before the gated composer stream starts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(entitlements): address review findings on the gating sweep - AgentChat: the mount effect auto-fired the first /api/agent/invoke on a fresh start regardless of capability; now returns early without ai, and Regenerate/Correction re-invokes are guarded too (real find by review bot) - recurring auto_send upsell copy moved to i18n (auto_send_requires_subscription, sv+en) matching the rest of the dialog - disabled-button tooltips wrapped in a span (payslip send + skattekonto sync): browsers suppress title on disabled elements Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
ed7a178ac6
commit
a566a42aec
@@ -27,6 +27,8 @@ import {
|
||||
Link2,
|
||||
RefreshCw,
|
||||
} from 'lucide-react'
|
||||
import { useCapability } from '@/contexts/CompanyContext'
|
||||
import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
import type {
|
||||
SkatteverketSaldoResponse,
|
||||
SkattekontoTransactionWithSuggestion,
|
||||
@@ -60,6 +62,7 @@ interface MatchCandidate {
|
||||
|
||||
export default function SkattekontoPage() {
|
||||
const { toast } = useToast()
|
||||
const hasSkvCapability = useCapability(CAPABILITY.skatteverket)
|
||||
const [saldo, setSaldo] = useState<SaldoEnvelope | null>(null)
|
||||
const [tx, setTx] = useState<TransaktionerEnvelope['data'] | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -305,10 +308,13 @@ export default function SkattekontoPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeading
|
||||
right={
|
||||
<Button onClick={syncNow} disabled={syncing}>
|
||||
<RefreshCw className={`mr-2 h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
|
||||
{syncing ? 'Synkroniserar…' : 'Synkronisera nu'}
|
||||
</Button>
|
||||
// The span carries the tooltip: `title` is suppressed on disabled elements.
|
||||
<span title={!hasSkvCapability ? 'Synk mot Skatteverket kräver ett abonnemang' : undefined}>
|
||||
<Button onClick={syncNow} disabled={syncing || !hasSkvCapability}>
|
||||
<RefreshCw className={`mr-2 h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
|
||||
{syncing ? 'Synkroniserar…' : 'Synkronisera nu'}
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createClient } from '@/lib/supabase/server'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { headers } from 'next/headers'
|
||||
import { getActiveCompanyId } from '@/lib/company/context'
|
||||
import { hasCapability } from '@/lib/entitlements/has-capability'
|
||||
import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
import { ensureTicSnapshot } from '@/lib/agent/composer/tic-fetch'
|
||||
import AgentOnboarding from '@/components/onboarding/agent/AgentOnboarding'
|
||||
|
||||
@@ -21,6 +23,13 @@ export default async function AgentOnboardingPage() {
|
||||
const companyId = await getActiveCompanyId(supabase, user.id)
|
||||
if (!companyId) redirect('/onboarding')
|
||||
|
||||
// Paywall: the build flow drives the gated composer stream (ai capability).
|
||||
// Send non-payers to billing where the assistant is pitched, instead of a
|
||||
// build sequence that 403s mid-stream.
|
||||
if (!(await hasCapability(supabase, companyId, CAPABILITY.ai))) {
|
||||
redirect('/settings/billing')
|
||||
}
|
||||
|
||||
// Everything that doesn't depend on the TIC snapshot loads in one batch:
|
||||
// the settings row (carrying the is_sandbox gate + the onboarding-form
|
||||
// data, moms_period, fiscal_year_start_month, f_skatt, city, …, that
|
||||
|
||||
Reference in New Issue
Block a user