Add max_public_pages feature and site builder access control
- Add max_public_pages billing feature (Free=0, Starter=1, Growth=5, Pro=10) - Gate site builder access based on max_public_pages entitlement - Auto-create Site with default booking page for new tenants - Update PageEditor to use useEntitlements hook for permission checks - Replace hardcoded limits in BusinessEditModal with DynamicFeaturesEditor - Add force update functionality for superusers in PlanEditorWizard - Add comprehensive filters to all safe scripting get_* methods - Update plugin documentation with full filter reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,23 @@ vi.mock('../../../hooks/useBillingAdmin', () => ({
|
||||
mutateAsync: vi.fn().mockResolvedValue({ id: 1 }),
|
||||
isPending: false,
|
||||
}),
|
||||
useForceUpdatePlanVersion: () => ({
|
||||
mutateAsync: vi.fn().mockResolvedValue({ version: { id: 1 }, affected_count: 5 }),
|
||||
isPending: false,
|
||||
}),
|
||||
isForceUpdateConfirmRequired: (response: unknown) =>
|
||||
response !== null &&
|
||||
typeof response === 'object' &&
|
||||
'requires_confirm' in response &&
|
||||
(response as { requires_confirm: boolean }).requires_confirm === true,
|
||||
}));
|
||||
|
||||
// Mock useCurrentUser from useAuth
|
||||
vi.mock('../../../hooks/useAuth', () => ({
|
||||
useCurrentUser: () => ({
|
||||
data: { id: 1, role: 'superuser', email: 'admin@test.com' },
|
||||
isLoading: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('PlanEditorWizard', () => {
|
||||
@@ -409,4 +426,135 @@ describe('PlanEditorWizard', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Force Update (Superuser)', () => {
|
||||
it('shows "Update Without Versioning" button for superuser editing plan with subscribers', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<PlanEditorWizard
|
||||
{...defaultProps}
|
||||
mode="edit"
|
||||
initialData={{
|
||||
id: 1,
|
||||
code: 'pro',
|
||||
name: 'Pro',
|
||||
version: {
|
||||
id: 1,
|
||||
subscriber_count: 5,
|
||||
name: 'Pro v1',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
{ wrapper: createWrapper() }
|
||||
);
|
||||
|
||||
// Navigate to last step
|
||||
await user.click(screen.getByRole('button', { name: /pricing/i }));
|
||||
await user.click(screen.getByRole('button', { name: /features/i }));
|
||||
await user.click(screen.getByRole('button', { name: /display/i }));
|
||||
|
||||
// Should show "Update Without Versioning" button
|
||||
expect(screen.getByRole('button', { name: /update without versioning/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows confirmation dialog when clicking "Update Without Versioning"', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<PlanEditorWizard
|
||||
{...defaultProps}
|
||||
mode="edit"
|
||||
initialData={{
|
||||
id: 1,
|
||||
code: 'pro',
|
||||
name: 'Pro',
|
||||
version: {
|
||||
id: 1,
|
||||
subscriber_count: 5,
|
||||
name: 'Pro v1',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
{ wrapper: createWrapper() }
|
||||
);
|
||||
|
||||
// Navigate to last step
|
||||
await user.click(screen.getByRole('button', { name: /pricing/i }));
|
||||
await user.click(screen.getByRole('button', { name: /features/i }));
|
||||
await user.click(screen.getByRole('button', { name: /display/i }));
|
||||
|
||||
// Click the force update button
|
||||
await user.click(screen.getByRole('button', { name: /update without versioning/i }));
|
||||
|
||||
// Should show confirmation dialog with warning
|
||||
expect(screen.getByText(/warning: this will affect existing customers/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/5/)).toBeInTheDocument(); // subscriber count
|
||||
expect(screen.getByRole('button', { name: /yes, update all subscribers/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can cancel force update confirmation', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<PlanEditorWizard
|
||||
{...defaultProps}
|
||||
mode="edit"
|
||||
initialData={{
|
||||
id: 1,
|
||||
code: 'pro',
|
||||
name: 'Pro',
|
||||
version: {
|
||||
id: 1,
|
||||
subscriber_count: 5,
|
||||
name: 'Pro v1',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
{ wrapper: createWrapper() }
|
||||
);
|
||||
|
||||
// Navigate to last step
|
||||
await user.click(screen.getByRole('button', { name: /pricing/i }));
|
||||
await user.click(screen.getByRole('button', { name: /features/i }));
|
||||
await user.click(screen.getByRole('button', { name: /display/i }));
|
||||
|
||||
// Click the force update button
|
||||
await user.click(screen.getByRole('button', { name: /update without versioning/i }));
|
||||
|
||||
// Click Cancel
|
||||
const cancelButtons = screen.getAllByRole('button', { name: /cancel/i });
|
||||
await user.click(cancelButtons[0]); // First cancel is in the confirmation dialog
|
||||
|
||||
// Confirmation dialog should be hidden, back to normal footer
|
||||
expect(screen.queryByText(/warning: this will affect existing customers/i)).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /update without versioning/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show "Update Without Versioning" for plans without subscribers', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<PlanEditorWizard
|
||||
{...defaultProps}
|
||||
mode="edit"
|
||||
initialData={{
|
||||
id: 1,
|
||||
code: 'pro',
|
||||
name: 'Pro',
|
||||
version: {
|
||||
id: 1,
|
||||
subscriber_count: 0, // No subscribers
|
||||
name: 'Pro v1',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
{ wrapper: createWrapper() }
|
||||
);
|
||||
|
||||
// Navigate to last step
|
||||
await user.click(screen.getByRole('button', { name: /pricing/i }));
|
||||
await user.click(screen.getByRole('button', { name: /features/i }));
|
||||
await user.click(screen.getByRole('button', { name: /display/i }));
|
||||
|
||||
// Should NOT show "Update Without Versioning" button
|
||||
expect(screen.queryByRole('button', { name: /update without versioning/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user