feat: differentiate personal/team pricing table with two-stage team workspace flow (#9901)

## Summary

Differentiates the subscription pricing dialog between personal and team
workspaces with distinct visual treatments and a two-stage team
workspace upgrade flow.

### Changes

- **Personal pricing dialog**: Shows "P" avatar badge, "Plans for
Personal Workspace" header, and "Solo use only – Need team workspace?"
banner on each tier card
- **Team pricing dialog**: Shows workspace avatar, "Plans for Team
Workspace" header (emerald), green "Invite up to X members" badge, and
emerald border on Creator card
- **Two-stage upgrade flow**: "Need team workspace?" → closes pricing →
opens CreateWorkspaceDialog → sessionStorage flag → page reload →
WorkspaceAuthGate auto-opens team pricing dialog
- **Spacing**: Reduced vertical gaps/padding/font sizes so the table
fits without scrolling

### Key decisions

- sessionStorage key `comfy:resume-team-pricing` bridges the page reload
during workspace creation
- `onChooseTeam` prop is conditionally passed only to the personal
variant
- `resumePendingPricingFlow()` is called from WorkspaceAuthGate after
workspace initialization

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9901-feat-differentiate-personal-team-pricing-table-with-two-stage-team-workspace-flow-3226d73d365081e7af60dcca86e83673)
by [Unito](https://www.unito.io)
This commit is contained in:
Hunter
2026-03-23 09:17:19 -07:00
committed by GitHub
parent bd322314bc
commit cd45efa983
32 changed files with 1426 additions and 154 deletions

View File

@@ -51,6 +51,20 @@ vi.mock('@/platform/distribution/types', () => ({
}
}))
const mockResumePendingPricingFlow = vi.fn()
vi.mock(
'@/platform/cloud/subscription/composables/useSubscriptionDialog',
() => ({
useSubscriptionDialog: () => ({
show: vi.fn(),
showPricingTable: vi.fn(),
hide: vi.fn(),
startTeamWorkspaceUpgradeFlow: vi.fn(),
resumePendingPricingFlow: mockResumePendingPricingFlow
})
})
)
describe('WorkspaceAuthGate', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -142,6 +156,16 @@ describe('WorkspaceAuthGate', () => {
expect(wrapper.find('[data-testid="slot-content"]').exists()).toBe(true)
})
it('calls resumePendingPricingFlow after successful workspace init', async () => {
mockTeamWorkspacesEnabled.value = true
mockWorkspaceStoreInitState.value = 'ready'
mountComponent()
await flushPromises()
expect(mockResumePendingPricingFlow).toHaveBeenCalled()
})
it('skips workspace init when store is already initialized', async () => {
mockTeamWorkspacesEnabled.value = true
mockWorkspaceStoreInitState.value = 'ready'