@gntik-ai/templates 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2420 -0
- package/dist/index.js +14520 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -3
- package/src/accept-invitation/Page.test.tsx +40 -0
- package/src/accept-invitation/Page.tsx +153 -0
- package/src/accept-invitation/data.ts +22 -0
- package/src/accept-invitation/template.meta.ts +12 -0
- package/src/assistant-chat/Page.test.tsx +51 -0
- package/src/assistant-chat/Page.tsx +273 -0
- package/src/assistant-chat/data.ts +71 -0
- package/src/assistant-chat/template.meta.ts +12 -0
- package/src/audit-log/Page.test.tsx +58 -0
- package/src/audit-log/Page.tsx +284 -0
- package/src/audit-log/data.ts +133 -0
- package/src/audit-log/template.meta.ts +12 -0
- package/src/board/Lane.tsx +117 -0
- package/src/board/Page.test.tsx +68 -0
- package/src/board/Page.tsx +178 -0
- package/src/board/data.ts +127 -0
- package/src/board/template.meta.ts +12 -0
- package/src/board/useBoard.ts +146 -0
- package/src/changelog/Page.test.tsx +31 -0
- package/src/changelog/Page.tsx +174 -0
- package/src/changelog/data.ts +105 -0
- package/src/changelog/template.meta.ts +12 -0
- package/src/code-editor/Page.test.tsx +83 -0
- package/src/code-editor/Page.tsx +207 -0
- package/src/code-editor/data.ts +130 -0
- package/src/code-editor/template.meta.ts +12 -0
- package/src/contact/Page.test.tsx +33 -0
- package/src/contact/Page.tsx +272 -0
- package/src/contact/PublicFrame.tsx +84 -0
- package/src/contact/data.ts +129 -0
- package/src/contact/template.meta.ts +12 -0
- package/src/create-wizard/Overlay.test.tsx +68 -0
- package/src/create-wizard/Page.test.tsx +57 -0
- package/src/create-wizard/Page.tsx +246 -0
- package/src/create-wizard/data.ts +139 -0
- package/src/create-wizard/examples/Overlay.tsx +60 -0
- package/src/create-wizard/template.meta.ts +12 -0
- package/src/create-workspace/Page.test.tsx +41 -0
- package/src/create-workspace/Page.tsx +227 -0
- package/src/create-workspace/data.ts +61 -0
- package/src/create-workspace/template.meta.ts +12 -0
- package/src/data-explorer/Page.test.tsx +45 -0
- package/src/data-explorer/Page.tsx +247 -0
- package/src/data-explorer/data.ts +94 -0
- package/src/data-explorer/template.meta.ts +12 -0
- package/src/docs-article/ArticleFeedback.tsx +75 -0
- package/src/docs-article/Page.test.tsx +34 -0
- package/src/docs-article/Page.tsx +200 -0
- package/src/docs-article/data.ts +65 -0
- package/src/docs-article/template.meta.ts +12 -0
- package/src/evaluations/Page.test.tsx +40 -0
- package/src/evaluations/Page.tsx +238 -0
- package/src/evaluations/data.ts +194 -0
- package/src/evaluations/template.meta.ts +12 -0
- package/src/forgot-password/Page.test.tsx +199 -0
- package/src/forgot-password/Page.tsx +66 -0
- package/src/forgot-password/data.ts +5 -0
- package/src/forgot-password/examples/UsernameOrEmail.tsx +25 -0
- package/src/forgot-password/template.meta.ts +12 -0
- package/src/getting-started/Page.test.tsx +27 -0
- package/src/getting-started/Page.tsx +125 -0
- package/src/getting-started/data.ts +67 -0
- package/src/getting-started/template.meta.ts +12 -0
- package/src/help-home/Page.test.tsx +33 -0
- package/src/help-home/Page.tsx +159 -0
- package/src/help-home/data.ts +151 -0
- package/src/help-home/template.meta.ts +12 -0
- package/src/home-dashboard/Page.test.tsx +57 -0
- package/src/home-dashboard/Page.tsx +152 -0
- package/src/home-dashboard/data.ts +66 -0
- package/src/home-dashboard/template.meta.ts +12 -0
- package/src/incident-board/Page.test.tsx +77 -0
- package/src/incident-board/Page.tsx +263 -0
- package/src/incident-board/data.ts +220 -0
- package/src/incident-board/template.meta.ts +12 -0
- package/src/incident-console/Page.test.tsx +49 -0
- package/src/incident-console/Page.tsx +274 -0
- package/src/incident-console/data.ts +176 -0
- package/src/incident-console/template.meta.ts +12 -0
- package/src/index.ts +250 -0
- package/src/keyboard-shortcuts/Page.test.tsx +45 -0
- package/src/keyboard-shortcuts/Page.tsx +94 -0
- package/src/keyboard-shortcuts/ShortcutSheet.tsx +80 -0
- package/src/keyboard-shortcuts/data.ts +84 -0
- package/src/keyboard-shortcuts/template.meta.ts +12 -0
- package/src/landing/Page.test.tsx +27 -0
- package/src/landing/Page.tsx +110 -0
- package/src/landing/data.ts +17 -0
- package/src/landing/template.meta.ts +11 -0
- package/src/live-event-stream/Page.test.tsx +251 -0
- package/src/live-event-stream/Page.tsx +307 -0
- package/src/live-event-stream/data.ts +44 -0
- package/src/live-event-stream/examples/Connecting.tsx +5 -0
- package/src/live-event-stream/examples/Error.tsx +5 -0
- package/src/live-event-stream/examples/LiveEmpty.tsx +5 -0
- package/src/live-event-stream/examples/LiveWithEvents.tsx +6 -0
- package/src/live-event-stream/examples/Stopped.tsx +6 -0
- package/src/live-event-stream/template.meta.ts +74 -0
- package/src/messaging/Page.test.tsx +34 -0
- package/src/messaging/Page.tsx +236 -0
- package/src/messaging/data.ts +118 -0
- package/src/messaging/template.meta.ts +12 -0
- package/src/meta.ts +24 -0
- package/src/mfa/Page.test.tsx +22 -0
- package/src/mfa/Page.tsx +45 -0
- package/src/mfa/data.ts +6 -0
- package/src/mfa/template.meta.ts +12 -0
- package/src/model-providers/Page.test.tsx +58 -0
- package/src/model-providers/Page.tsx +198 -0
- package/src/model-providers/data.ts +101 -0
- package/src/model-providers/template.meta.ts +12 -0
- package/src/notification-inbox/Page.test.tsx +49 -0
- package/src/notification-inbox/Page.tsx +237 -0
- package/src/notification-inbox/data.ts +129 -0
- package/src/notification-inbox/template.meta.ts +12 -0
- package/src/pending-activation/Page.test.tsx +390 -0
- package/src/pending-activation/Page.tsx +145 -0
- package/src/pending-activation/data.ts +25 -0
- package/src/pending-activation/template.meta.ts +12 -0
- package/src/pricing/ComparisonTable.tsx +75 -0
- package/src/pricing/Page.test.tsx +29 -0
- package/src/pricing/Page.tsx +83 -0
- package/src/pricing/PublicFrame.tsx +84 -0
- package/src/pricing/data.ts +104 -0
- package/src/pricing/template.meta.ts +12 -0
- package/src/prompt-playground/Page.test.tsx +72 -0
- package/src/prompt-playground/Page.tsx +254 -0
- package/src/prompt-playground/data.ts +68 -0
- package/src/prompt-playground/template.meta.ts +12 -0
- package/src/public-hub/Page.test.tsx +176 -0
- package/src/public-hub/Page.tsx +125 -0
- package/src/public-hub/Source.test.ts +54 -0
- package/src/public-hub/data.ts +24 -0
- package/src/public-hub/examples/Dark.tsx +11 -0
- package/src/public-hub/examples/Full.tsx +6 -0
- package/src/public-hub/examples/HighContrast.tsx +11 -0
- package/src/public-hub/examples/Light.tsx +11 -0
- package/src/public-hub/examples/Loading.tsx +6 -0
- package/src/public-hub/examples/Minimal.tsx +5 -0
- package/src/public-hub/examples/MinimalLoading.tsx +5 -0
- package/src/public-hub/template.meta.ts +60 -0
- package/src/reset-password/Page.test.tsx +27 -0
- package/src/reset-password/Page.tsx +46 -0
- package/src/reset-password/data.ts +5 -0
- package/src/reset-password/template.meta.ts +12 -0
- package/src/resource-detail/Page.test.tsx +260 -0
- package/src/resource-detail/Page.tsx +262 -0
- package/src/resource-detail/data.ts +55 -0
- package/src/resource-detail/examples/postgres-table-detail.tsx +28 -0
- package/src/resource-detail/examples/realtime-configuration.tsx +26 -0
- package/src/resource-detail/examples/runtime-with-transition.tsx +30 -0
- package/src/resource-detail/examples/runtime-without-transition.tsx +33 -0
- package/src/resource-detail/examples/shell.ts +12 -0
- package/src/resource-detail/template.meta.ts +21 -0
- package/src/resource-form/Page.test.tsx +40 -0
- package/src/resource-form/Page.tsx +186 -0
- package/src/resource-form/data.ts +63 -0
- package/src/resource-form/template.meta.ts +12 -0
- package/src/resource-gallery/Page.test.tsx +42 -0
- package/src/resource-gallery/Page.tsx +125 -0
- package/src/resource-gallery/data.ts +44 -0
- package/src/resource-gallery/template.meta.ts +20 -0
- package/src/resource-index/Page.test.tsx +120 -0
- package/src/resource-index/Page.tsx +216 -0
- package/src/resource-index/data.ts +84 -0
- package/src/resource-index/template.meta.ts +21 -0
- package/src/resource-tree/Page.test.tsx +31 -0
- package/src/resource-tree/Page.tsx +232 -0
- package/src/resource-tree/data.ts +180 -0
- package/src/resource-tree/template.meta.ts +12 -0
- package/src/run-trace/Page.test.tsx +56 -0
- package/src/run-trace/Page.tsx +242 -0
- package/src/run-trace/data.ts +171 -0
- package/src/run-trace/template.meta.ts +12 -0
- package/src/scorecard/Page.test.tsx +38 -0
- package/src/scorecard/Page.tsx +160 -0
- package/src/scorecard/TargetsTable.tsx +92 -0
- package/src/scorecard/data.ts +118 -0
- package/src/scorecard/template.meta.ts +12 -0
- package/src/search-results/Page.test.tsx +43 -0
- package/src/search-results/Page.tsx +243 -0
- package/src/search-results/data.ts +130 -0
- package/src/search-results/template.meta.ts +12 -0
- package/src/settings-api-keys/Page.test.tsx +39 -0
- package/src/settings-api-keys/Page.tsx +171 -0
- package/src/settings-api-keys/data.ts +58 -0
- package/src/settings-api-keys/template.meta.ts +12 -0
- package/src/settings-appearance/Page.test.tsx +44 -0
- package/src/settings-appearance/Page.tsx +92 -0
- package/src/settings-appearance/data.ts +21 -0
- package/src/settings-appearance/template.meta.ts +11 -0
- package/src/settings-billing/Page.test.tsx +31 -0
- package/src/settings-billing/Page.tsx +71 -0
- package/src/settings-billing/data.ts +15 -0
- package/src/settings-billing/template.meta.ts +19 -0
- package/src/settings-integrations/Page.test.tsx +31 -0
- package/src/settings-integrations/Page.tsx +185 -0
- package/src/settings-integrations/data.ts +116 -0
- package/src/settings-integrations/template.meta.ts +11 -0
- package/src/settings-members/Page.test.tsx +32 -0
- package/src/settings-members/Page.tsx +140 -0
- package/src/settings-members/data.ts +28 -0
- package/src/settings-members/template.meta.ts +12 -0
- package/src/settings-notifications/Page.test.tsx +31 -0
- package/src/settings-notifications/Page.tsx +71 -0
- package/src/settings-notifications/data.ts +12 -0
- package/src/settings-notifications/template.meta.ts +11 -0
- package/src/settings-profile/Baseline.test.tsx +9 -0
- package/src/settings-profile/Editable.test.tsx +21 -0
- package/src/settings-profile/Page.test.tsx +41 -0
- package/src/settings-profile/Page.tsx +133 -0
- package/src/settings-profile/ReadOnly.test.tsx +143 -0
- package/src/settings-profile/ReadOnlyProfile.tsx +85 -0
- package/src/settings-profile/SettingsFrame.test.tsx +55 -0
- package/src/settings-profile/SettingsFrame.tsx +88 -0
- package/src/settings-profile/__tests__/dom.ts +8 -0
- package/src/settings-profile/data.ts +41 -0
- package/src/settings-profile/examples/ReadOnly.tsx +31 -0
- package/src/settings-profile/fixtures/editable.snap +1 -0
- package/src/settings-profile/settings-nav.ts +98 -0
- package/src/settings-profile/template.meta.ts +21 -0
- package/src/settings-security/MfaSetupDialog.tsx +96 -0
- package/src/settings-security/Page.test.tsx +44 -0
- package/src/settings-security/Page.tsx +158 -0
- package/src/settings-security/data.ts +11 -0
- package/src/settings-security/template.meta.ts +11 -0
- package/src/settings-workspace/Page.test.tsx +44 -0
- package/src/settings-workspace/Page.tsx +120 -0
- package/src/settings-workspace/data.ts +44 -0
- package/src/settings-workspace/template.meta.ts +19 -0
- package/src/shared/ConsoleShell.test.tsx +81 -0
- package/src/shared/ConsoleShell.tsx +98 -0
- package/src/shell-sidebar/Page.test.tsx +21 -0
- package/src/shell-sidebar/Page.tsx +85 -0
- package/src/shell-sidebar/data.ts +24 -0
- package/src/shell-sidebar/template.meta.ts +12 -0
- package/src/shell-topnav/Page.test.tsx +25 -0
- package/src/shell-topnav/Page.tsx +94 -0
- package/src/shell-topnav/data.ts +25 -0
- package/src/shell-topnav/template.meta.ts +12 -0
- package/src/sign-in/BrandPanel.tsx +34 -0
- package/src/sign-in/Page.test.tsx +41 -0
- package/src/sign-in/Page.tsx +73 -0
- package/src/sign-in/data.ts +11 -0
- package/src/sign-in/template.meta.ts +12 -0
- package/src/sign-in-card/Page.test.tsx +182 -0
- package/src/sign-in-card/Page.tsx +82 -0
- package/src/sign-in-card/data.ts +8 -0
- package/src/sign-in-card/examples/Username.tsx +28 -0
- package/src/sign-in-card/template.meta.ts +12 -0
- package/src/sign-up/Page.test.tsx +33 -0
- package/src/sign-up/Page.tsx +70 -0
- package/src/sign-up/data.ts +15 -0
- package/src/sign-up/template.meta.ts +12 -0
- package/src/sso-login/Page.test.tsx +41 -0
- package/src/sso-login/Page.tsx +151 -0
- package/src/sso-login/data.ts +10 -0
- package/src/sso-login/template.meta.ts +12 -0
- package/src/status-403/Page.test.tsx +23 -0
- package/src/status-403/Page.tsx +92 -0
- package/src/status-403/data.ts +13 -0
- package/src/status-403/template.meta.ts +12 -0
- package/src/status-404/Page.test.tsx +23 -0
- package/src/status-404/Page.tsx +60 -0
- package/src/status-404/PageHeading.test.tsx +52 -0
- package/src/status-404/data.ts +12 -0
- package/src/status-404/examples/PageHeader.tsx +28 -0
- package/src/status-404/template.meta.ts +12 -0
- package/src/status-500/Page.test.tsx +24 -0
- package/src/status-500/Page.tsx +70 -0
- package/src/status-500/data.ts +20 -0
- package/src/status-500/template.meta.ts +12 -0
- package/src/status-maintenance/Page.test.tsx +24 -0
- package/src/status-maintenance/Page.tsx +84 -0
- package/src/status-maintenance/data.ts +14 -0
- package/src/status-maintenance/template.meta.ts +12 -0
- package/src/styles.css +11 -0
- package/src/test/a11y.ts +18 -0
- package/src/test/setup.ts +36 -0
- package/src/upgrade-required/Page.test.tsx +28 -0
- package/src/upgrade-required/Page.tsx +79 -0
- package/src/upgrade-required/data.ts +19 -0
- package/src/upgrade-required/template.meta.ts +12 -0
- package/src/usage-analytics/Page.test.tsx +83 -0
- package/src/usage-analytics/Page.tsx +184 -0
- package/src/usage-analytics/data.ts +161 -0
- package/src/usage-analytics/template.meta.ts +21 -0
- package/src/verify-email/Page.test.tsx +32 -0
- package/src/verify-email/Page.tsx +118 -0
- package/src/verify-email/data.ts +7 -0
- package/src/verify-email/template.meta.ts +12 -0
- package/src/workflow-builder/Page.test.tsx +97 -0
- package/src/workflow-builder/Page.tsx +163 -0
- package/src/workflow-builder/data.ts +13 -0
- package/src/workflow-builder/template.meta.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { ErrorPanel, FirstRunEmpty } from '@gntik-ai/blocks';
|
|
5
|
+
import CreateWizardPage from './Page';
|
|
6
|
+
|
|
7
|
+
it('creates through an overlay of a mounted list, with step-local error and empty surfaces', async () => {
|
|
8
|
+
const user = userEvent.setup();
|
|
9
|
+
const onFinish = vi.fn();
|
|
10
|
+
function List() {
|
|
11
|
+
const [open, setOpen] = useState(false);
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<main>
|
|
15
|
+
<h1>Projects</h1>
|
|
16
|
+
<input aria-label="Filter projects" defaultValue="Keep me" />
|
|
17
|
+
<button onClick={() => setOpen(true)}>New project</button>
|
|
18
|
+
</main>
|
|
19
|
+
<CreateWizardPage
|
|
20
|
+
mode="overlay"
|
|
21
|
+
open={open}
|
|
22
|
+
onOpenChange={setOpen}
|
|
23
|
+
description="Configure a project"
|
|
24
|
+
onFinish={(values) => {
|
|
25
|
+
onFinish(values);
|
|
26
|
+
setOpen(false);
|
|
27
|
+
}}
|
|
28
|
+
stepFeedback={(step) =>
|
|
29
|
+
step === 1 ? (
|
|
30
|
+
<ErrorPanel
|
|
31
|
+
title="Source unavailable"
|
|
32
|
+
message="Try again"
|
|
33
|
+
requestId=""
|
|
34
|
+
details=""
|
|
35
|
+
secondaryAction={null}
|
|
36
|
+
/>
|
|
37
|
+
) : step === 2 ? (
|
|
38
|
+
<FirstRunEmpty
|
|
39
|
+
title="No environments"
|
|
40
|
+
description="Create one later"
|
|
41
|
+
actionLabel="Continue setup"
|
|
42
|
+
secondary={null}
|
|
43
|
+
steps={[]}
|
|
44
|
+
/>
|
|
45
|
+
) : null
|
|
46
|
+
}
|
|
47
|
+
/>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
render(<List />);
|
|
52
|
+
const filter = screen.getByRole('textbox', { name: 'Filter projects' });
|
|
53
|
+
const url = window.location.href;
|
|
54
|
+
await user.click(screen.getByRole('button', { name: 'New project' }));
|
|
55
|
+
await screen.findByRole('dialog', { name: 'New project' });
|
|
56
|
+
await user.type(screen.getByRole('textbox', { name: /Project name/ }), 'orders-api');
|
|
57
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
58
|
+
expect(screen.getByText('Source unavailable')).toBeInTheDocument();
|
|
59
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
60
|
+
expect(screen.getByText('No environments')).toBeInTheDocument();
|
|
61
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
62
|
+
await user.click(screen.getByRole('button', { name: 'Create project' }));
|
|
63
|
+
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
|
64
|
+
expect(onFinish).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(screen.getByRole('textbox', { name: 'Filter projects' })).toBe(filter);
|
|
66
|
+
expect(filter).toHaveValue('Keep me');
|
|
67
|
+
expect(window.location.href).toBe(url);
|
|
68
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
4
|
+
import CreateWizardPage from './Page';
|
|
5
|
+
|
|
6
|
+
describe('CreateWizardPage', () => {
|
|
7
|
+
it('renders the wizard shell on the first step', { timeout: 15000 }, async () => {
|
|
8
|
+
const { container } = render(<CreateWizardPage />);
|
|
9
|
+
expect(screen.getByRole('main')).toBeInTheDocument();
|
|
10
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Project details' })).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByRole('button', { name: /Next/ })).toBeDisabled();
|
|
12
|
+
await expectNoAxeViolations(container);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('walks to the review step, edits, and finishes', { timeout: 15000 }, async () => {
|
|
16
|
+
const onFinish = vi.fn();
|
|
17
|
+
const { container } = render(<CreateWizardPage onFinish={onFinish} />);
|
|
18
|
+
await userEvent.type(screen.getByRole('textbox', { name: /Project name/ }), 'orders-api');
|
|
19
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
20
|
+
await userEvent.click(screen.getByRole('radio', { name: 'Start from a template' }));
|
|
21
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
22
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
23
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Review and create' })).toBeInTheDocument();
|
|
24
|
+
expect(screen.queryByRole('heading', { name: 'Summary' })).toBeNull();
|
|
25
|
+
expect(screen.getAllByRole('heading', { level: 2 }).length).toBeGreaterThanOrEqual(2);
|
|
26
|
+
expect(screen.getByText('Start from a template')).toBeInTheDocument();
|
|
27
|
+
await expectNoAxeViolations(container);
|
|
28
|
+
|
|
29
|
+
await userEvent.click(screen.getByRole('button', { name: 'Edit details' }));
|
|
30
|
+
expect(screen.getByRole('textbox', { name: /Project name/ })).toHaveValue('orders-api');
|
|
31
|
+
for (let i = 0; i < 3; i++) await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
32
|
+
await userEvent.click(screen.getByRole('button', { name: /Create project/ }));
|
|
33
|
+
expect(onFinish).toHaveBeenCalledWith(
|
|
34
|
+
expect.objectContaining({ name: 'orders-api', source: 'template', region: 'eu-west-1' }),
|
|
35
|
+
);
|
|
36
|
+
expect(await screen.findByText('Project “orders-api” created')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('takes its copy from the `copy` prop', { timeout: 15000 }, async () => {
|
|
40
|
+
render(
|
|
41
|
+
<CreateWizardPage
|
|
42
|
+
copy={{
|
|
43
|
+
detailsTitle: 'Agent details',
|
|
44
|
+
nameLabel: 'Agent name',
|
|
45
|
+
submitLabel: 'Create agent',
|
|
46
|
+
reviewTitle: 'Review the agent',
|
|
47
|
+
}}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Agent details' })).toBeInTheDocument();
|
|
51
|
+
await userEvent.type(screen.getByRole('textbox', { name: /Agent name/ }), 'triage-bot');
|
|
52
|
+
for (let i = 0; i < 3; i++) await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
53
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Review the agent' })).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByRole('button', { name: /Create agent/ })).toBeInTheDocument();
|
|
55
|
+
expect(screen.getByText('Agent name')).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { DescriptionListCard } from '@gntik-ai/blocks';
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
Field,
|
|
5
|
+
FieldDescription,
|
|
6
|
+
FieldError,
|
|
7
|
+
FieldLabel,
|
|
8
|
+
Heading,
|
|
9
|
+
Input,
|
|
10
|
+
Logo,
|
|
11
|
+
SelectableCard,
|
|
12
|
+
SelectableCardGroup,
|
|
13
|
+
SimpleSelect,
|
|
14
|
+
Stack,
|
|
15
|
+
Text,
|
|
16
|
+
Textarea,
|
|
17
|
+
WizardLayout,
|
|
18
|
+
type StepItem,
|
|
19
|
+
type WizardLayoutProps,
|
|
20
|
+
} from '@gntik-ai/ui';
|
|
21
|
+
import { useId, useState, type ReactNode } from 'react';
|
|
22
|
+
import {
|
|
23
|
+
PROJECT_NAME_RE,
|
|
24
|
+
wizardCopy,
|
|
25
|
+
wizardInitialValues,
|
|
26
|
+
wizardRegions,
|
|
27
|
+
wizardSources,
|
|
28
|
+
wizardSteps,
|
|
29
|
+
type CreateWizardCopy,
|
|
30
|
+
type SourceOption,
|
|
31
|
+
type WizardRegion,
|
|
32
|
+
type WizardValues,
|
|
33
|
+
} from './data';
|
|
34
|
+
|
|
35
|
+
export interface CreateWizardProps extends Pick<
|
|
36
|
+
WizardLayoutProps,
|
|
37
|
+
| 'mode'
|
|
38
|
+
| 'size'
|
|
39
|
+
| 'open'
|
|
40
|
+
| 'onOpenChange'
|
|
41
|
+
| 'description'
|
|
42
|
+
| 'returnFocusRef'
|
|
43
|
+
| 'closeOnInteractOutside'
|
|
44
|
+
| 'stepAnnouncement'
|
|
45
|
+
| 'pendingLabel'
|
|
46
|
+
> {
|
|
47
|
+
/** Step-local errors or empty states, rendered within the scrollable step body. */
|
|
48
|
+
stepFeedback?: (current: number) => ReactNode;
|
|
49
|
+
/** Flow name in the top bar. */
|
|
50
|
+
title: string;
|
|
51
|
+
steps: StepItem[];
|
|
52
|
+
initialValues: WizardValues;
|
|
53
|
+
sources: readonly SourceOption[];
|
|
54
|
+
regions: readonly WizardRegion[];
|
|
55
|
+
/** Called from the review step; return a promise to show the loading state. */
|
|
56
|
+
onFinish: (values: WizardValues) => void | Promise<void>;
|
|
57
|
+
/** Called after the exit is confirmed. */
|
|
58
|
+
onExit: () => void;
|
|
59
|
+
onStepChange: (index: number) => void;
|
|
60
|
+
/** Step titles, field labels, submit label…; unset keys keep the default (project) wording. */
|
|
61
|
+
copy: Partial<CreateWizardCopy>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function StepIntro({ id, title, children }: { id: string; title: string; children?: ReactNode }) {
|
|
65
|
+
return (
|
|
66
|
+
<Stack gap={1}>
|
|
67
|
+
<Heading level={1} id={id} size="lg">
|
|
68
|
+
{title}
|
|
69
|
+
</Heading>
|
|
70
|
+
{children && <Text variant="supporting">{children}</Text>}
|
|
71
|
+
</Stack>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Multi-step create flow in the focus-mode WizardLayout, ending in a review of every answer. */
|
|
76
|
+
export default function CreateWizardPage({
|
|
77
|
+
title = 'New project',
|
|
78
|
+
steps = wizardSteps,
|
|
79
|
+
initialValues = wizardInitialValues,
|
|
80
|
+
sources = wizardSources,
|
|
81
|
+
regions = wizardRegions,
|
|
82
|
+
onFinish,
|
|
83
|
+
onExit,
|
|
84
|
+
onStepChange,
|
|
85
|
+
copy: copyOverrides,
|
|
86
|
+
stepFeedback,
|
|
87
|
+
...presentation
|
|
88
|
+
}: Partial<CreateWizardProps>) {
|
|
89
|
+
const copy: CreateWizardCopy = { ...wizardCopy, ...copyOverrides };
|
|
90
|
+
const id = useId();
|
|
91
|
+
const [step, setStep] = useState(0);
|
|
92
|
+
const [values, setValues] = useState(initialValues);
|
|
93
|
+
const [touched, setTouched] = useState(false);
|
|
94
|
+
const [finishing, setFinishing] = useState(false);
|
|
95
|
+
const [status, setStatus] = useState('');
|
|
96
|
+
const set = <K extends keyof WizardValues>(key: K, value: WizardValues[K]) =>
|
|
97
|
+
setValues((v) => ({ ...v, [key]: value }));
|
|
98
|
+
|
|
99
|
+
const nameValid = PROJECT_NAME_RE.test(values.name);
|
|
100
|
+
const nameError = touched && !nameValid ? (values.name ? copy.nameInvalid : copy.nameRequired) : undefined;
|
|
101
|
+
const go = (index: number) => {
|
|
102
|
+
setStep(index);
|
|
103
|
+
onStepChange?.(index);
|
|
104
|
+
};
|
|
105
|
+
const sourceLabel = sources.find((s) => s.value === values.source)?.title ?? values.source;
|
|
106
|
+
const regionLabel = regions.find((r) => r.value === values.region)?.label ?? values.region;
|
|
107
|
+
|
|
108
|
+
const finish = async () => {
|
|
109
|
+
setFinishing(true);
|
|
110
|
+
try {
|
|
111
|
+
await onFinish?.(values);
|
|
112
|
+
setStatus(copy.created(values.name));
|
|
113
|
+
} finally {
|
|
114
|
+
setFinishing(false);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<WizardLayout
|
|
120
|
+
{...presentation}
|
|
121
|
+
fullScreen
|
|
122
|
+
logo={<Logo size={22} />}
|
|
123
|
+
title={title}
|
|
124
|
+
steps={steps}
|
|
125
|
+
current={step}
|
|
126
|
+
onStepChange={go}
|
|
127
|
+
nextDisabled={step === 0 && !nameValid}
|
|
128
|
+
nextLoading={finishing}
|
|
129
|
+
finishLabel={copy.submitLabel}
|
|
130
|
+
onFinish={() => void finish()}
|
|
131
|
+
onExit={onExit}
|
|
132
|
+
exitTitle={copy.exitTitle}
|
|
133
|
+
exitDescription={copy.exitDescription}
|
|
134
|
+
footerStart={<span aria-live="polite">{status}</span>}
|
|
135
|
+
>
|
|
136
|
+
{stepFeedback?.(step)}
|
|
137
|
+
{step === 0 && (
|
|
138
|
+
<section aria-labelledby={`${id}-details`}>
|
|
139
|
+
<Stack gap={6}>
|
|
140
|
+
<StepIntro id={`${id}-details`} title={copy.detailsTitle}>
|
|
141
|
+
{copy.detailsIntro}
|
|
142
|
+
</StepIntro>
|
|
143
|
+
<Field invalid={nameError != null}>
|
|
144
|
+
<FieldLabel required>{copy.nameLabel}</FieldLabel>
|
|
145
|
+
<Input
|
|
146
|
+
required
|
|
147
|
+
value={values.name}
|
|
148
|
+
onValueChange={(v) => set('name', v)}
|
|
149
|
+
onBlur={() => setTouched(true)}
|
|
150
|
+
placeholder={copy.namePlaceholder}
|
|
151
|
+
/>
|
|
152
|
+
<FieldDescription>{copy.nameHint}</FieldDescription>
|
|
153
|
+
<FieldError match={nameError != null}>{nameError}</FieldError>
|
|
154
|
+
</Field>
|
|
155
|
+
<Field>
|
|
156
|
+
<FieldLabel>{copy.descriptionLabel}</FieldLabel>
|
|
157
|
+
<Textarea rows={3} value={values.description} onValueChange={(v) => set('description', v)} />
|
|
158
|
+
</Field>
|
|
159
|
+
</Stack>
|
|
160
|
+
</section>
|
|
161
|
+
)}
|
|
162
|
+
{step === 1 && (
|
|
163
|
+
<section aria-labelledby={`${id}-source`}>
|
|
164
|
+
<Stack gap={6}>
|
|
165
|
+
<StepIntro id={`${id}-source`} title={copy.sourceTitle}>
|
|
166
|
+
{copy.sourceIntro}
|
|
167
|
+
</StepIntro>
|
|
168
|
+
<SelectableCardGroup
|
|
169
|
+
aria-labelledby={`${id}-source`}
|
|
170
|
+
value={values.source}
|
|
171
|
+
onValueChange={(v) => set('source', v)}
|
|
172
|
+
columns={1}
|
|
173
|
+
>
|
|
174
|
+
{sources.map((s) => {
|
|
175
|
+
const Icon = s.icon;
|
|
176
|
+
return (
|
|
177
|
+
<SelectableCard
|
|
178
|
+
key={s.value}
|
|
179
|
+
value={s.value}
|
|
180
|
+
title={s.title}
|
|
181
|
+
description={s.description}
|
|
182
|
+
icon={<Icon size={18} aria-hidden />}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
})}
|
|
186
|
+
</SelectableCardGroup>
|
|
187
|
+
</Stack>
|
|
188
|
+
</section>
|
|
189
|
+
)}
|
|
190
|
+
{step === 2 && (
|
|
191
|
+
<section aria-labelledby={`${id}-configure`}>
|
|
192
|
+
<Stack gap={6}>
|
|
193
|
+
<StepIntro id={`${id}-configure`} title={copy.configureTitle}>
|
|
194
|
+
{copy.configureIntro}
|
|
195
|
+
</StepIntro>
|
|
196
|
+
<SimpleSelect
|
|
197
|
+
label={copy.regionLabel}
|
|
198
|
+
items={regions}
|
|
199
|
+
value={values.region}
|
|
200
|
+
onValueChange={(v) => v && set('region', v)}
|
|
201
|
+
className="w-full"
|
|
202
|
+
/>
|
|
203
|
+
<Checkbox
|
|
204
|
+
label={copy.previewsLabel}
|
|
205
|
+
description={copy.previewsDescription}
|
|
206
|
+
checked={values.previews}
|
|
207
|
+
onCheckedChange={(checked) => set('previews', checked)}
|
|
208
|
+
/>
|
|
209
|
+
</Stack>
|
|
210
|
+
</section>
|
|
211
|
+
)}
|
|
212
|
+
{step === 3 && (
|
|
213
|
+
<section aria-labelledby={`${id}-review`}>
|
|
214
|
+
<Stack gap={6}>
|
|
215
|
+
<StepIntro id={`${id}-review`} title={copy.reviewTitle}>
|
|
216
|
+
{copy.reviewIntro}
|
|
217
|
+
</StepIntro>
|
|
218
|
+
<DescriptionListCard
|
|
219
|
+
titleAs="h2"
|
|
220
|
+
title={copy.reviewDetailsTitle}
|
|
221
|
+
description={copy.reviewDetailsDescription}
|
|
222
|
+
items={[
|
|
223
|
+
{ id: 'name', label: copy.nameLabel, value: values.name, mono: true },
|
|
224
|
+
{ id: 'description', label: copy.descriptionLabel, value: values.description || '—' },
|
|
225
|
+
]}
|
|
226
|
+
onEdit={() => go(0)}
|
|
227
|
+
editLabel={copy.editDetailsLabel}
|
|
228
|
+
/>
|
|
229
|
+
<DescriptionListCard
|
|
230
|
+
titleAs="h2"
|
|
231
|
+
title={copy.reviewSourceTitle}
|
|
232
|
+
description={copy.reviewSourceDescription}
|
|
233
|
+
items={[
|
|
234
|
+
{ id: 'source', label: copy.sourceLabel, value: sourceLabel },
|
|
235
|
+
{ id: 'region', label: copy.regionLabel, value: regionLabel },
|
|
236
|
+
{ id: 'previews', label: copy.previewsLabel, value: values.previews ? copy.on : copy.off },
|
|
237
|
+
]}
|
|
238
|
+
onEdit={() => go(1)}
|
|
239
|
+
editLabel={copy.editSourceLabel}
|
|
240
|
+
/>
|
|
241
|
+
</Stack>
|
|
242
|
+
</section>
|
|
243
|
+
)}
|
|
244
|
+
</WizardLayout>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Code2, GitBranch, LayoutTemplate, type LucideIcon } from '@gntik-ai/icons';
|
|
2
|
+
import type { StepItem } from '@gntik-ai/ui';
|
|
3
|
+
|
|
4
|
+
export interface WizardValues {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
source: string;
|
|
8
|
+
region: string;
|
|
9
|
+
previews: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SourceOption {
|
|
13
|
+
value: string;
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
icon: LucideIcon;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WizardRegion {
|
|
20
|
+
value: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const wizardSteps: StepItem[] = [
|
|
25
|
+
{ id: 'details', label: 'Details' },
|
|
26
|
+
{ id: 'source', label: 'Source' },
|
|
27
|
+
{ id: 'configure', label: 'Configure' },
|
|
28
|
+
{ id: 'review', label: 'Review' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const wizardSources: SourceOption[] = [
|
|
32
|
+
{
|
|
33
|
+
value: 'repository',
|
|
34
|
+
title: 'Import a repository',
|
|
35
|
+
description: 'Build and deploy from a Git branch on every push.',
|
|
36
|
+
icon: GitBranch,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
value: 'template',
|
|
40
|
+
title: 'Start from a template',
|
|
41
|
+
description: 'A ready-made service you can change later.',
|
|
42
|
+
icon: LayoutTemplate,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: 'empty',
|
|
46
|
+
title: 'Empty project',
|
|
47
|
+
description: 'No code yet; connect a source when you are ready.',
|
|
48
|
+
icon: Code2,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export const wizardRegions: WizardRegion[] = [
|
|
53
|
+
{ value: 'eu-west-1', label: 'EU West · Ireland' },
|
|
54
|
+
{ value: 'eu-central-1', label: 'EU Central · Frankfurt' },
|
|
55
|
+
{ value: 'us-east-1', label: 'US East · Virginia' },
|
|
56
|
+
{ value: 'ap-south-1', label: 'Asia Pacific · Mumbai' },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export const wizardInitialValues: WizardValues = {
|
|
60
|
+
name: '',
|
|
61
|
+
description: '',
|
|
62
|
+
source: 'repository',
|
|
63
|
+
region: 'eu-west-1',
|
|
64
|
+
previews: true,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const PROJECT_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
68
|
+
|
|
69
|
+
/** Every visible string of the wizard; products override any subset through the `copy` prop. */
|
|
70
|
+
export interface CreateWizardCopy {
|
|
71
|
+
detailsTitle: string;
|
|
72
|
+
detailsIntro: string;
|
|
73
|
+
nameLabel: string;
|
|
74
|
+
namePlaceholder: string;
|
|
75
|
+
nameHint: string;
|
|
76
|
+
nameRequired: string;
|
|
77
|
+
nameInvalid: string;
|
|
78
|
+
descriptionLabel: string;
|
|
79
|
+
sourceTitle: string;
|
|
80
|
+
sourceIntro: string;
|
|
81
|
+
configureTitle: string;
|
|
82
|
+
configureIntro: string;
|
|
83
|
+
regionLabel: string;
|
|
84
|
+
previewsLabel: string;
|
|
85
|
+
previewsDescription: string;
|
|
86
|
+
reviewTitle: string;
|
|
87
|
+
reviewIntro: string;
|
|
88
|
+
/** @deprecated No longer rendered: the review cards are the step's h2 headings. Kept for compatibility. */
|
|
89
|
+
summaryHeading?: string;
|
|
90
|
+
reviewDetailsTitle: string;
|
|
91
|
+
reviewDetailsDescription: string;
|
|
92
|
+
editDetailsLabel: string;
|
|
93
|
+
reviewSourceTitle: string;
|
|
94
|
+
reviewSourceDescription: string;
|
|
95
|
+
editSourceLabel: string;
|
|
96
|
+
sourceLabel: string;
|
|
97
|
+
on: string;
|
|
98
|
+
off: string;
|
|
99
|
+
/** Submit button on the review step. */
|
|
100
|
+
submitLabel: string;
|
|
101
|
+
exitTitle: string;
|
|
102
|
+
exitDescription: string;
|
|
103
|
+
/** Live status after a successful finish. */
|
|
104
|
+
created: (name: string) => string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const wizardCopy: CreateWizardCopy = {
|
|
108
|
+
detailsTitle: 'Project details',
|
|
109
|
+
detailsIntro: 'Name the project; you can change the description later.',
|
|
110
|
+
nameLabel: 'Project name',
|
|
111
|
+
namePlaceholder: 'billing-dashboard',
|
|
112
|
+
nameHint: 'Lowercase letters, numbers and dashes.',
|
|
113
|
+
nameRequired: 'Enter a project name.',
|
|
114
|
+
nameInvalid: 'Use lowercase letters, numbers and dashes.',
|
|
115
|
+
descriptionLabel: 'Description',
|
|
116
|
+
sourceTitle: 'Choose a source',
|
|
117
|
+
sourceIntro: 'Where the project’s code comes from.',
|
|
118
|
+
configureTitle: 'Configure',
|
|
119
|
+
configureIntro: 'Region and deployment defaults.',
|
|
120
|
+
regionLabel: 'Region',
|
|
121
|
+
previewsLabel: 'Preview deployments',
|
|
122
|
+
previewsDescription: 'Deploy every pull request to its own URL.',
|
|
123
|
+
reviewTitle: 'Review and create',
|
|
124
|
+
reviewIntro: 'Check your answers; edit any section before creating the project.',
|
|
125
|
+
summaryHeading: 'Summary',
|
|
126
|
+
reviewDetailsTitle: 'Details',
|
|
127
|
+
reviewDetailsDescription: 'Name and description.',
|
|
128
|
+
editDetailsLabel: 'Edit details',
|
|
129
|
+
reviewSourceTitle: 'Source and configuration',
|
|
130
|
+
reviewSourceDescription: 'Where the code comes from and where it runs.',
|
|
131
|
+
editSourceLabel: 'Edit source',
|
|
132
|
+
sourceLabel: 'Source',
|
|
133
|
+
on: 'On',
|
|
134
|
+
off: 'Off',
|
|
135
|
+
submitLabel: 'Create project',
|
|
136
|
+
exitTitle: 'Leave project setup?',
|
|
137
|
+
exitDescription: 'The project has not been created yet. Your answers will be discarded.',
|
|
138
|
+
created: (name) => `Project “${name}” created`,
|
|
139
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Button, Heading, Stack } from '@gntik-ai/ui';
|
|
2
|
+
import { ErrorPanel, FirstRunEmpty } from '@gntik-ai/blocks';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import CreateWizardPage from '../Page';
|
|
5
|
+
|
|
6
|
+
/** Keep this component mounted: closing preserves the wizard's answers and current step. */
|
|
7
|
+
export default function CreateWizardOverlay() {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [projects, setProjects] = useState<string[]>([]);
|
|
10
|
+
const [sourceUnavailable, setSourceUnavailable] = useState(true);
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<main className="p-6">
|
|
14
|
+
<Stack gap={4}>
|
|
15
|
+
<Heading level={1}>Projects</Heading>
|
|
16
|
+
<Button onClick={() => setOpen(true)}>New project</Button>
|
|
17
|
+
<ul>
|
|
18
|
+
{projects.map((name) => (
|
|
19
|
+
<li key={name}>{name}</li>
|
|
20
|
+
))}
|
|
21
|
+
</ul>
|
|
22
|
+
</Stack>
|
|
23
|
+
</main>
|
|
24
|
+
<CreateWizardPage
|
|
25
|
+
mode="overlay"
|
|
26
|
+
open={open}
|
|
27
|
+
onOpenChange={setOpen}
|
|
28
|
+
description="Create a project over this list."
|
|
29
|
+
pendingLabel="Creating project…"
|
|
30
|
+
onFinish={(values) => {
|
|
31
|
+
setProjects((items) => [...items, values.name]);
|
|
32
|
+
setOpen(false);
|
|
33
|
+
}}
|
|
34
|
+
stepFeedback={(step) =>
|
|
35
|
+
step === 1 && sourceUnavailable ? (
|
|
36
|
+
<ErrorPanel
|
|
37
|
+
title="Sources are unavailable"
|
|
38
|
+
message="Retry or continue with a blank project."
|
|
39
|
+
code={503}
|
|
40
|
+
requestId=""
|
|
41
|
+
details=""
|
|
42
|
+
secondaryAction={null}
|
|
43
|
+
retryLabel="Retry sources"
|
|
44
|
+
onRetry={() => setSourceUnavailable(false)}
|
|
45
|
+
/>
|
|
46
|
+
) : step === 1 ? (
|
|
47
|
+
<FirstRunEmpty
|
|
48
|
+
title="No saved sources"
|
|
49
|
+
description="You can connect a source later."
|
|
50
|
+
steps={[]}
|
|
51
|
+
secondary={null}
|
|
52
|
+
actionLabel="Refresh sources"
|
|
53
|
+
onAction={() => setSourceUnavailable(true)}
|
|
54
|
+
/>
|
|
55
|
+
) : null
|
|
56
|
+
}
|
|
57
|
+
/>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: TemplateMeta = {
|
|
4
|
+
name: 'Create wizard',
|
|
5
|
+
family: 'Resources',
|
|
6
|
+
priority: 'P2',
|
|
7
|
+
status: 'beta',
|
|
8
|
+
description:
|
|
9
|
+
'Focus-mode create flow: details, source, configuration and editable review cards. Page mode is the default; mode="overlay" mounts over an existing list without a new route. Overlay size defaults to fullscreen; size="dialog" uses a popup (fullscreen on mobile). Keep it mounted to preserve answers and the current step on close. The examples/Overlay.tsx demo goes from a list through Finish with step-local ErrorPanel and FirstRunEmpty surfaces. For a consumer-owned step model and validation/review slots, use dialog-wizard (default dialog size).',
|
|
10
|
+
layout: 'WizardLayout',
|
|
11
|
+
blocks: ['description-list-card', 'error-panel', 'empty-states'],
|
|
12
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
4
|
+
import CreateWorkspacePage from './Page';
|
|
5
|
+
|
|
6
|
+
describe('CreateWorkspacePage', () => {
|
|
7
|
+
it('renders the first step with the address preview', { timeout: 15000 }, async () => {
|
|
8
|
+
const { container } = render(<CreateWorkspacePage />);
|
|
9
|
+
expect(screen.getByRole('main')).toBeInTheDocument();
|
|
10
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Name your workspace' })).toBeInTheDocument();
|
|
11
|
+
await userEvent.type(screen.getByRole('textbox', { name: /Workspace name/ }), 'Acme Labs');
|
|
12
|
+
expect(screen.getByText('app.example.com/acme-labs')).toBeInTheDocument();
|
|
13
|
+
await expectNoAxeViolations(container);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('picks region and plan, invites members and finishes', { timeout: 15000 }, async () => {
|
|
17
|
+
const onFinish = vi.fn();
|
|
18
|
+
const { container } = render(<CreateWorkspacePage onFinish={onFinish} />);
|
|
19
|
+
await userEvent.type(screen.getByRole('textbox', { name: /Workspace name/ }), 'Acme Labs');
|
|
20
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
21
|
+
await userEvent.click(screen.getByRole('radio', { name: 'United States' }));
|
|
22
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
23
|
+
expect(screen.getByRole('radio', { name: 'Team' })).toBeChecked();
|
|
24
|
+
await expectNoAxeViolations(container);
|
|
25
|
+
await userEvent.click(screen.getByRole('radio', { name: 'Starter' }));
|
|
26
|
+
await userEvent.click(screen.getByRole('button', { name: /Next/ }));
|
|
27
|
+
await userEvent.type(screen.getByRole('textbox', { name: /Email addresses/ }), 'sam@example.com{Enter}');
|
|
28
|
+
await expectNoAxeViolations(container);
|
|
29
|
+
await userEvent.click(screen.getByRole('button', { name: /Create and invite/ }));
|
|
30
|
+
expect(onFinish).toHaveBeenCalledWith(
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
name: 'Acme Labs',
|
|
33
|
+
slug: 'acme-labs',
|
|
34
|
+
region: 'us',
|
|
35
|
+
plan: 'starter',
|
|
36
|
+
invites: ['sam@example.com'],
|
|
37
|
+
role: 'member',
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|