feat(billing): make the expired-trial state visible with a clear upgrade path (#1725)
getCompanyEntitlements now derives an entitlementState (trial / trial_expired / lapsed_subscription / paid / none) plus trialExpiredAt from the grants it already fetches, reading company_subscriptions.status inside the existing Promise.all so churned payers get 'abonnemang' copy instead of 'provperiod'. The state threads through CompanyContext and the dashboard layout. Two new surfaces, both hidden in sandbox: - SubscriptionTouchpoint replaces the sidebar trial pill: countdown while the trial runs, a persistent muted upgrade link to /settings/billing once it lapses (visible even collapsed, icon-only with aria-label), and the first mobile bottom-sheet touchpoint. - TrialExpiredDialog: one-time on-entry notice with 'Se abonnemang' and a ghost dismiss; acknowledgement persists per user+company in user_preferences.ui_state.trial_expired_ack (read server-side, no flash), set on dismiss and click-through alike. Narrows the 2026-07-11 'no trial-expired nag' decision at the founder's direction after a user could not find the upgrade path at all; see DECISIONS.md. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
834cc4d0e8
commit
c402421908
@@ -35,6 +35,12 @@ const BodySchema = z
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
// One-time expired-trial dialog acknowledgement: companyId -> ISO
|
||||
// timestamp of the ack. Merged per key like create_mode, so acking one
|
||||
// company never clears another's.
|
||||
trial_expired_ack: z
|
||||
.record(z.string().uuid(), z.string().datetime())
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
@@ -79,6 +85,9 @@ export async function POST(request: Request) {
|
||||
...(patch.agent_panel
|
||||
? { agent_panel: { ...current.agent_panel, ...patch.agent_panel } }
|
||||
: {}),
|
||||
...(patch.trial_expired_ack
|
||||
? { trial_expired_ack: { ...current.trial_expired_ack, ...patch.trial_expired_ack } }
|
||||
: {}),
|
||||
}
|
||||
|
||||
const { error: upsertError } = await supabase
|
||||
|
||||
Reference in New Issue
Block a user