5b505bb4a7
The paywall branch in app/(dashboard)/e/[sector]/[slug]/page.tsx resolved the extension icon server-side and passed the resulting forwardRef component into the 'use client' EmptyState. React cannot serialize a component across the server-to-client boundary, so non-payers opening a gated extension (e.g. /e/general/invoice-inbox) got a 500 error page instead of the upgrade CTA (digest 1621801304, 2026-07-08). Fix: new client wrapper components/extensions/ExtensionUpsellState.tsx accepts only plain string props (iconName, title, description, ctaLabel, ctaHref) and resolves the icon client-side via resolveIcon, the same pattern DashboardNav and the command palette already use. The server page now passes definition.icon as a string. The two other resolveIcon call sites in server pages render the icon inside the server component, which is valid RSC, and are left untouched. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { Button } from '@/components/ui/button'
|
|
import { EmptyState } from '@/components/ui/empty-state'
|
|
import { resolveIcon } from '@/lib/extensions/icon-resolver'
|
|
|
|
interface ExtensionUpsellStateProps {
|
|
iconName?: string
|
|
title: string
|
|
description: string
|
|
ctaLabel: string
|
|
ctaHref: string
|
|
}
|
|
|
|
/**
|
|
* Paywall state for a gated extension workspace, rendered when the active
|
|
* company lacks the required capability.
|
|
*
|
|
* This is a client component on purpose: the server page cannot pass a
|
|
* resolved icon component into the 'use client' EmptyState (React cannot
|
|
* serialize a component across the RSC boundary; doing so 500s the page).
|
|
* The page passes the icon NAME as a plain string and this wrapper resolves
|
|
* it client-side, same as DashboardNav and the command palette do.
|
|
* Every prop here must stay plain-serializable (strings only).
|
|
*/
|
|
export function ExtensionUpsellState({
|
|
iconName,
|
|
title,
|
|
description,
|
|
ctaLabel,
|
|
ctaHref,
|
|
}: ExtensionUpsellStateProps) {
|
|
const Icon = iconName ? resolveIcon(iconName) : undefined
|
|
return (
|
|
<EmptyState icon={Icon} title={title} description={description}>
|
|
<Link href={ctaHref}>
|
|
<Button>{ctaLabel}</Button>
|
|
</Link>
|
|
</EmptyState>
|
|
)
|
|
}
|