feat(billing): plan taxonomy reconciliation + Stripe sync + internal-tester allowlist (#164)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com> Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
This commit was merged in pull request #164.
This commit is contained in:
@@ -418,10 +418,10 @@ export function AccountSettingsPage() {
|
||||
<p className="text-sm text-muted-foreground">Plan limits unavailable.</p>
|
||||
)}
|
||||
|
||||
{plan !== 'team' && (
|
||||
{plan !== 'enterprise' && (
|
||||
<div className="flex flex-wrap justify-end gap-2 pt-2">
|
||||
{plan === 'free' && <CheckoutButton plan="pro" />}
|
||||
<CheckoutButton plan="team" />
|
||||
<CheckoutButton plan="enterprise" />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -15,7 +15,7 @@ const FAQ_ITEMS = [
|
||||
},
|
||||
{
|
||||
q: 'What PSA tools do you integrate with?',
|
||||
a: 'Launching with ConnectWise PSA \u2014 session documentation exports directly as internal ticket notes. Atera and Syncro integrations are next. During beta, you can copy formatted notes into any PSA.',
|
||||
a: 'Launching with ConnectWise PSA — session documentation exports directly as internal ticket notes. Atera and Syncro integrations are next. During beta, you can copy formatted notes into any PSA.',
|
||||
},
|
||||
{
|
||||
q: 'What counts as a \u201csession\u201d?',
|
||||
@@ -23,7 +23,7 @@ const FAQ_ITEMS = [
|
||||
},
|
||||
{
|
||||
q: 'What if FlowPilot gets it wrong?',
|
||||
a: 'FlowPilot is a copilot, not autopilot. Every suggestion is a recommendation \u2014 you decide what to act on. And because every step is documented, you always have a full audit trail of what was tried and why.',
|
||||
a: 'FlowPilot is a copilot, not autopilot. Every suggestion is a recommendation — you decide what to act on. And because every step is documented, you always have a full audit trail of what was tried and why.',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -75,8 +75,8 @@ export default function LandingPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta
|
||||
title="ResolutionFlow \u2014 From Issue to Resolution, Documented"
|
||||
description="Your AI troubleshooting copilot. Describe the issue, get help fixing it, and get clean ticket notes \u2014 automatically."
|
||||
title="ResolutionFlow — From Issue to Resolution, Documented"
|
||||
description="Your AI troubleshooting copilot. Describe the issue, get help fixing it, and get clean ticket notes — automatically."
|
||||
/>
|
||||
|
||||
<div className="landing-page">
|
||||
|
||||
@@ -88,7 +88,7 @@ export function UsersPage() {
|
||||
})
|
||||
const [inviteLoading, setInviteLoading] = useState(false)
|
||||
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
|
||||
const [createAccountForm, setCreateAccountForm] = useState({ name: '', plan: 'free' as 'free' | 'pro' | 'team', owner_email: '' })
|
||||
const [createAccountForm, setCreateAccountForm] = useState({ name: '', plan: 'free' as 'free' | 'pro' | 'starter' | 'enterprise', owner_email: '' })
|
||||
const [createAccountLoading, setCreateAccountLoading] = useState(false)
|
||||
|
||||
const fetchAccounts = useCallback(async () => {
|
||||
@@ -469,7 +469,8 @@ export function UsersPage() {
|
||||
<option value="all">All plans</option>
|
||||
<option value="free">Free</option>
|
||||
<option value="pro">Pro</option>
|
||||
<option value="team">Team</option>
|
||||
<option value="enterprise">Enterprise</option>
|
||||
<option value="starter">Starter</option>
|
||||
</select>
|
||||
<select
|
||||
value={statusFilter}
|
||||
@@ -629,7 +630,7 @@ export function UsersPage() {
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Initial Plan</label>
|
||||
<select
|
||||
value={createAccountForm.plan}
|
||||
onChange={(e) => setCreateAccountForm((form) => ({ ...form, plan: e.target.value as 'free' | 'pro' | 'team' }))}
|
||||
onChange={(e) => setCreateAccountForm((form) => ({ ...form, plan: e.target.value as 'free' | 'pro' | 'starter' | 'enterprise' }))}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
@@ -637,7 +638,8 @@ export function UsersPage() {
|
||||
>
|
||||
<option value="free">Free</option>
|
||||
<option value="pro">Pro</option>
|
||||
<option value="team">Team</option>
|
||||
<option value="enterprise">Enterprise</option>
|
||||
<option value="starter">Starter</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -12,8 +12,9 @@ import type { InviteCodeResponse, InviteCodeCreateRequest } from '@/types/admin'
|
||||
|
||||
const PLAN_OPTIONS = [
|
||||
{ value: 'free', label: 'Free' },
|
||||
{ value: 'starter', label: 'Starter' },
|
||||
{ value: 'pro', label: 'Pro' },
|
||||
{ value: 'team', label: 'Team' },
|
||||
{ value: 'enterprise', label: 'Enterprise' },
|
||||
] as const
|
||||
|
||||
const planBadgeVariant = (plan: string): 'success' | 'destructive' | 'warning' | 'default' => {
|
||||
@@ -33,7 +34,7 @@ export function InviteCodesPage() {
|
||||
// Form state
|
||||
const [email, setEmail] = useState('')
|
||||
const [expiresInDays, setExpiresInDays] = useState('')
|
||||
const [assignedPlan, setAssignedPlan] = useState<'free' | 'pro' | 'team'>('free')
|
||||
const [assignedPlan, setAssignedPlan] = useState<'free' | 'pro' | 'starter' | 'enterprise'>('free')
|
||||
const [trialDays, setTrialDays] = useState('')
|
||||
const [note, setNote] = useState('')
|
||||
|
||||
@@ -269,7 +270,7 @@ export function InviteCodesPage() {
|
||||
aria-label="Plan"
|
||||
value={assignedPlan}
|
||||
onChange={(e) => {
|
||||
const plan = e.target.value as 'free' | 'pro' | 'team'
|
||||
const plan = e.target.value as 'free' | 'pro' | 'starter' | 'enterprise'
|
||||
setAssignedPlan(plan)
|
||||
if (plan === 'free') setTrialDays('')
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user