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
@@ -43,7 +43,8 @@ import { useUnsavedChanges } from '@/lib/hooks/use-unsaved-changes'
|
||||
import CustomerForm from '@/components/customers/CustomerForm'
|
||||
import { BankDetailsSetupDialog } from '@/components/invoices/BankDetailsSetupDialog'
|
||||
import { FirstInvoiceLogoPrompt } from '@/components/invoices/FirstInvoiceLogoPrompt'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import { useCompany, useCapability } from '@/contexts/CompanyContext'
|
||||
import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
import AgentSparkleButton from '@/components/agent/AgentSparkleButton'
|
||||
import {
|
||||
ROT_WORK_TYPES,
|
||||
@@ -109,6 +110,7 @@ export default function InvoiceEditor(props: InvoiceEditorProps = { mode: 'creat
|
||||
const { toast } = useToast()
|
||||
const { canWrite } = useCanWrite()
|
||||
const { company } = useCompany()
|
||||
const hasEmailSend = useCapability(CAPABILITY.email_send)
|
||||
const supabase = createClient()
|
||||
const t = useTranslations('invoice_editor')
|
||||
const ts = useTranslations('self_billing')
|
||||
@@ -929,7 +931,9 @@ export default function InvoiceEditor(props: InvoiceEditorProps = { mode: 'creat
|
||||
function handleLogoPromptClose() {
|
||||
setShowLogoPrompt(false)
|
||||
// Resume the post-create flow that was deferred by the logo prompt.
|
||||
if (selectedCustomer?.email && createdInvoiceId) {
|
||||
// The send-now dialog only emails: skipped without the email_send
|
||||
// capability (the invoice page's SendInvoiceDialog carries the upsell).
|
||||
if (selectedCustomer?.email && createdInvoiceId && hasEmailSend) {
|
||||
setShowSendPrompt(true)
|
||||
} else if (createdInvoiceId) {
|
||||
router.push(`/invoices/${createdInvoiceId}`)
|
||||
@@ -999,7 +1003,7 @@ export default function InvoiceEditor(props: InvoiceEditorProps = { mode: 'creat
|
||||
// prompt closes, handleLogoPromptClose resumes the regular flow.
|
||||
if (hadZeroInvoices === true && !logoUrl) {
|
||||
setShowLogoPrompt(true)
|
||||
} else if (selectedCustomer?.email) {
|
||||
} else if (selectedCustomer?.email && hasEmailSend) {
|
||||
setShowSendPrompt(true)
|
||||
} else {
|
||||
router.push(`/invoices/${result.data.id}`)
|
||||
|
||||
@@ -25,7 +25,9 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import { useCompany, useCapability } from '@/contexts/CompanyContext'
|
||||
import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
import { UpgradeNote } from '@/components/billing/UpgradeNote'
|
||||
import { Plus, Trash2 } from 'lucide-react'
|
||||
import type { Customer, Currency, RecurringInvoiceSchedule } from '@/types'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
@@ -215,15 +217,18 @@ function NewRecurringScheduleForm({
|
||||
const watchCustomerId = watch('customer_id')
|
||||
const selectedCustomer = customers.find((c) => c.id === watchCustomerId)
|
||||
const customerMissingEmail = !!selectedCustomer && !selectedCustomer.email
|
||||
const hasEmailSend = useCapability(CAPABILITY.email_send)
|
||||
const autoSendBlocked = customerMissingEmail || !hasEmailSend
|
||||
|
||||
// The onValueChange guard on the customer select only fires on a manual
|
||||
// change. In edit mode a schedule can load with auto_send=true against a
|
||||
// customer who has since lost their email (customers load async, after the
|
||||
// form's defaultValues). Force auto_send off whenever the effective customer
|
||||
// has no email so a disabled-but-checked box can't PATCH auto_send=true.
|
||||
// has no email (or email sending isn't entitled) so a disabled-but-checked
|
||||
// box can't PATCH auto_send=true.
|
||||
useEffect(() => {
|
||||
if (customerMissingEmail) setValue('auto_send', false)
|
||||
}, [customerMissingEmail, setValue])
|
||||
if (autoSendBlocked) setValue('auto_send', false)
|
||||
}, [autoSendBlocked, setValue])
|
||||
const subtotalRaw = items.reduce(
|
||||
(sum, it) => sum + (it.quantity || 0) * (it.unit_price || 0),
|
||||
0,
|
||||
@@ -371,7 +376,7 @@ function NewRecurringScheduleForm({
|
||||
id="auto_send"
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
disabled={customerMissingEmail}
|
||||
disabled={autoSendBlocked}
|
||||
className="mt-1 h-4 w-4"
|
||||
/>
|
||||
)}
|
||||
@@ -388,6 +393,11 @@ function NewRecurringScheduleForm({
|
||||
{t('auto_send_missing_email')}
|
||||
</p>
|
||||
)}
|
||||
{!hasEmailSend && (
|
||||
<UpgradeNote className="mt-2">
|
||||
{t('auto_send_requires_subscription')}
|
||||
</UpgradeNote>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user