@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.
Files changed (300) hide show
  1. package/dist/index.d.ts +2420 -0
  2. package/dist/index.js +14520 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +64 -3
  5. package/src/accept-invitation/Page.test.tsx +40 -0
  6. package/src/accept-invitation/Page.tsx +153 -0
  7. package/src/accept-invitation/data.ts +22 -0
  8. package/src/accept-invitation/template.meta.ts +12 -0
  9. package/src/assistant-chat/Page.test.tsx +51 -0
  10. package/src/assistant-chat/Page.tsx +273 -0
  11. package/src/assistant-chat/data.ts +71 -0
  12. package/src/assistant-chat/template.meta.ts +12 -0
  13. package/src/audit-log/Page.test.tsx +58 -0
  14. package/src/audit-log/Page.tsx +284 -0
  15. package/src/audit-log/data.ts +133 -0
  16. package/src/audit-log/template.meta.ts +12 -0
  17. package/src/board/Lane.tsx +117 -0
  18. package/src/board/Page.test.tsx +68 -0
  19. package/src/board/Page.tsx +178 -0
  20. package/src/board/data.ts +127 -0
  21. package/src/board/template.meta.ts +12 -0
  22. package/src/board/useBoard.ts +146 -0
  23. package/src/changelog/Page.test.tsx +31 -0
  24. package/src/changelog/Page.tsx +174 -0
  25. package/src/changelog/data.ts +105 -0
  26. package/src/changelog/template.meta.ts +12 -0
  27. package/src/code-editor/Page.test.tsx +83 -0
  28. package/src/code-editor/Page.tsx +207 -0
  29. package/src/code-editor/data.ts +130 -0
  30. package/src/code-editor/template.meta.ts +12 -0
  31. package/src/contact/Page.test.tsx +33 -0
  32. package/src/contact/Page.tsx +272 -0
  33. package/src/contact/PublicFrame.tsx +84 -0
  34. package/src/contact/data.ts +129 -0
  35. package/src/contact/template.meta.ts +12 -0
  36. package/src/create-wizard/Overlay.test.tsx +68 -0
  37. package/src/create-wizard/Page.test.tsx +57 -0
  38. package/src/create-wizard/Page.tsx +246 -0
  39. package/src/create-wizard/data.ts +139 -0
  40. package/src/create-wizard/examples/Overlay.tsx +60 -0
  41. package/src/create-wizard/template.meta.ts +12 -0
  42. package/src/create-workspace/Page.test.tsx +41 -0
  43. package/src/create-workspace/Page.tsx +227 -0
  44. package/src/create-workspace/data.ts +61 -0
  45. package/src/create-workspace/template.meta.ts +12 -0
  46. package/src/data-explorer/Page.test.tsx +45 -0
  47. package/src/data-explorer/Page.tsx +247 -0
  48. package/src/data-explorer/data.ts +94 -0
  49. package/src/data-explorer/template.meta.ts +12 -0
  50. package/src/docs-article/ArticleFeedback.tsx +75 -0
  51. package/src/docs-article/Page.test.tsx +34 -0
  52. package/src/docs-article/Page.tsx +200 -0
  53. package/src/docs-article/data.ts +65 -0
  54. package/src/docs-article/template.meta.ts +12 -0
  55. package/src/evaluations/Page.test.tsx +40 -0
  56. package/src/evaluations/Page.tsx +238 -0
  57. package/src/evaluations/data.ts +194 -0
  58. package/src/evaluations/template.meta.ts +12 -0
  59. package/src/forgot-password/Page.test.tsx +199 -0
  60. package/src/forgot-password/Page.tsx +66 -0
  61. package/src/forgot-password/data.ts +5 -0
  62. package/src/forgot-password/examples/UsernameOrEmail.tsx +25 -0
  63. package/src/forgot-password/template.meta.ts +12 -0
  64. package/src/getting-started/Page.test.tsx +27 -0
  65. package/src/getting-started/Page.tsx +125 -0
  66. package/src/getting-started/data.ts +67 -0
  67. package/src/getting-started/template.meta.ts +12 -0
  68. package/src/help-home/Page.test.tsx +33 -0
  69. package/src/help-home/Page.tsx +159 -0
  70. package/src/help-home/data.ts +151 -0
  71. package/src/help-home/template.meta.ts +12 -0
  72. package/src/home-dashboard/Page.test.tsx +57 -0
  73. package/src/home-dashboard/Page.tsx +152 -0
  74. package/src/home-dashboard/data.ts +66 -0
  75. package/src/home-dashboard/template.meta.ts +12 -0
  76. package/src/incident-board/Page.test.tsx +77 -0
  77. package/src/incident-board/Page.tsx +263 -0
  78. package/src/incident-board/data.ts +220 -0
  79. package/src/incident-board/template.meta.ts +12 -0
  80. package/src/incident-console/Page.test.tsx +49 -0
  81. package/src/incident-console/Page.tsx +274 -0
  82. package/src/incident-console/data.ts +176 -0
  83. package/src/incident-console/template.meta.ts +12 -0
  84. package/src/index.ts +250 -0
  85. package/src/keyboard-shortcuts/Page.test.tsx +45 -0
  86. package/src/keyboard-shortcuts/Page.tsx +94 -0
  87. package/src/keyboard-shortcuts/ShortcutSheet.tsx +80 -0
  88. package/src/keyboard-shortcuts/data.ts +84 -0
  89. package/src/keyboard-shortcuts/template.meta.ts +12 -0
  90. package/src/landing/Page.test.tsx +27 -0
  91. package/src/landing/Page.tsx +110 -0
  92. package/src/landing/data.ts +17 -0
  93. package/src/landing/template.meta.ts +11 -0
  94. package/src/live-event-stream/Page.test.tsx +251 -0
  95. package/src/live-event-stream/Page.tsx +307 -0
  96. package/src/live-event-stream/data.ts +44 -0
  97. package/src/live-event-stream/examples/Connecting.tsx +5 -0
  98. package/src/live-event-stream/examples/Error.tsx +5 -0
  99. package/src/live-event-stream/examples/LiveEmpty.tsx +5 -0
  100. package/src/live-event-stream/examples/LiveWithEvents.tsx +6 -0
  101. package/src/live-event-stream/examples/Stopped.tsx +6 -0
  102. package/src/live-event-stream/template.meta.ts +74 -0
  103. package/src/messaging/Page.test.tsx +34 -0
  104. package/src/messaging/Page.tsx +236 -0
  105. package/src/messaging/data.ts +118 -0
  106. package/src/messaging/template.meta.ts +12 -0
  107. package/src/meta.ts +24 -0
  108. package/src/mfa/Page.test.tsx +22 -0
  109. package/src/mfa/Page.tsx +45 -0
  110. package/src/mfa/data.ts +6 -0
  111. package/src/mfa/template.meta.ts +12 -0
  112. package/src/model-providers/Page.test.tsx +58 -0
  113. package/src/model-providers/Page.tsx +198 -0
  114. package/src/model-providers/data.ts +101 -0
  115. package/src/model-providers/template.meta.ts +12 -0
  116. package/src/notification-inbox/Page.test.tsx +49 -0
  117. package/src/notification-inbox/Page.tsx +237 -0
  118. package/src/notification-inbox/data.ts +129 -0
  119. package/src/notification-inbox/template.meta.ts +12 -0
  120. package/src/pending-activation/Page.test.tsx +390 -0
  121. package/src/pending-activation/Page.tsx +145 -0
  122. package/src/pending-activation/data.ts +25 -0
  123. package/src/pending-activation/template.meta.ts +12 -0
  124. package/src/pricing/ComparisonTable.tsx +75 -0
  125. package/src/pricing/Page.test.tsx +29 -0
  126. package/src/pricing/Page.tsx +83 -0
  127. package/src/pricing/PublicFrame.tsx +84 -0
  128. package/src/pricing/data.ts +104 -0
  129. package/src/pricing/template.meta.ts +12 -0
  130. package/src/prompt-playground/Page.test.tsx +72 -0
  131. package/src/prompt-playground/Page.tsx +254 -0
  132. package/src/prompt-playground/data.ts +68 -0
  133. package/src/prompt-playground/template.meta.ts +12 -0
  134. package/src/public-hub/Page.test.tsx +176 -0
  135. package/src/public-hub/Page.tsx +125 -0
  136. package/src/public-hub/Source.test.ts +54 -0
  137. package/src/public-hub/data.ts +24 -0
  138. package/src/public-hub/examples/Dark.tsx +11 -0
  139. package/src/public-hub/examples/Full.tsx +6 -0
  140. package/src/public-hub/examples/HighContrast.tsx +11 -0
  141. package/src/public-hub/examples/Light.tsx +11 -0
  142. package/src/public-hub/examples/Loading.tsx +6 -0
  143. package/src/public-hub/examples/Minimal.tsx +5 -0
  144. package/src/public-hub/examples/MinimalLoading.tsx +5 -0
  145. package/src/public-hub/template.meta.ts +60 -0
  146. package/src/reset-password/Page.test.tsx +27 -0
  147. package/src/reset-password/Page.tsx +46 -0
  148. package/src/reset-password/data.ts +5 -0
  149. package/src/reset-password/template.meta.ts +12 -0
  150. package/src/resource-detail/Page.test.tsx +260 -0
  151. package/src/resource-detail/Page.tsx +262 -0
  152. package/src/resource-detail/data.ts +55 -0
  153. package/src/resource-detail/examples/postgres-table-detail.tsx +28 -0
  154. package/src/resource-detail/examples/realtime-configuration.tsx +26 -0
  155. package/src/resource-detail/examples/runtime-with-transition.tsx +30 -0
  156. package/src/resource-detail/examples/runtime-without-transition.tsx +33 -0
  157. package/src/resource-detail/examples/shell.ts +12 -0
  158. package/src/resource-detail/template.meta.ts +21 -0
  159. package/src/resource-form/Page.test.tsx +40 -0
  160. package/src/resource-form/Page.tsx +186 -0
  161. package/src/resource-form/data.ts +63 -0
  162. package/src/resource-form/template.meta.ts +12 -0
  163. package/src/resource-gallery/Page.test.tsx +42 -0
  164. package/src/resource-gallery/Page.tsx +125 -0
  165. package/src/resource-gallery/data.ts +44 -0
  166. package/src/resource-gallery/template.meta.ts +20 -0
  167. package/src/resource-index/Page.test.tsx +120 -0
  168. package/src/resource-index/Page.tsx +216 -0
  169. package/src/resource-index/data.ts +84 -0
  170. package/src/resource-index/template.meta.ts +21 -0
  171. package/src/resource-tree/Page.test.tsx +31 -0
  172. package/src/resource-tree/Page.tsx +232 -0
  173. package/src/resource-tree/data.ts +180 -0
  174. package/src/resource-tree/template.meta.ts +12 -0
  175. package/src/run-trace/Page.test.tsx +56 -0
  176. package/src/run-trace/Page.tsx +242 -0
  177. package/src/run-trace/data.ts +171 -0
  178. package/src/run-trace/template.meta.ts +12 -0
  179. package/src/scorecard/Page.test.tsx +38 -0
  180. package/src/scorecard/Page.tsx +160 -0
  181. package/src/scorecard/TargetsTable.tsx +92 -0
  182. package/src/scorecard/data.ts +118 -0
  183. package/src/scorecard/template.meta.ts +12 -0
  184. package/src/search-results/Page.test.tsx +43 -0
  185. package/src/search-results/Page.tsx +243 -0
  186. package/src/search-results/data.ts +130 -0
  187. package/src/search-results/template.meta.ts +12 -0
  188. package/src/settings-api-keys/Page.test.tsx +39 -0
  189. package/src/settings-api-keys/Page.tsx +171 -0
  190. package/src/settings-api-keys/data.ts +58 -0
  191. package/src/settings-api-keys/template.meta.ts +12 -0
  192. package/src/settings-appearance/Page.test.tsx +44 -0
  193. package/src/settings-appearance/Page.tsx +92 -0
  194. package/src/settings-appearance/data.ts +21 -0
  195. package/src/settings-appearance/template.meta.ts +11 -0
  196. package/src/settings-billing/Page.test.tsx +31 -0
  197. package/src/settings-billing/Page.tsx +71 -0
  198. package/src/settings-billing/data.ts +15 -0
  199. package/src/settings-billing/template.meta.ts +19 -0
  200. package/src/settings-integrations/Page.test.tsx +31 -0
  201. package/src/settings-integrations/Page.tsx +185 -0
  202. package/src/settings-integrations/data.ts +116 -0
  203. package/src/settings-integrations/template.meta.ts +11 -0
  204. package/src/settings-members/Page.test.tsx +32 -0
  205. package/src/settings-members/Page.tsx +140 -0
  206. package/src/settings-members/data.ts +28 -0
  207. package/src/settings-members/template.meta.ts +12 -0
  208. package/src/settings-notifications/Page.test.tsx +31 -0
  209. package/src/settings-notifications/Page.tsx +71 -0
  210. package/src/settings-notifications/data.ts +12 -0
  211. package/src/settings-notifications/template.meta.ts +11 -0
  212. package/src/settings-profile/Baseline.test.tsx +9 -0
  213. package/src/settings-profile/Editable.test.tsx +21 -0
  214. package/src/settings-profile/Page.test.tsx +41 -0
  215. package/src/settings-profile/Page.tsx +133 -0
  216. package/src/settings-profile/ReadOnly.test.tsx +143 -0
  217. package/src/settings-profile/ReadOnlyProfile.tsx +85 -0
  218. package/src/settings-profile/SettingsFrame.test.tsx +55 -0
  219. package/src/settings-profile/SettingsFrame.tsx +88 -0
  220. package/src/settings-profile/__tests__/dom.ts +8 -0
  221. package/src/settings-profile/data.ts +41 -0
  222. package/src/settings-profile/examples/ReadOnly.tsx +31 -0
  223. package/src/settings-profile/fixtures/editable.snap +1 -0
  224. package/src/settings-profile/settings-nav.ts +98 -0
  225. package/src/settings-profile/template.meta.ts +21 -0
  226. package/src/settings-security/MfaSetupDialog.tsx +96 -0
  227. package/src/settings-security/Page.test.tsx +44 -0
  228. package/src/settings-security/Page.tsx +158 -0
  229. package/src/settings-security/data.ts +11 -0
  230. package/src/settings-security/template.meta.ts +11 -0
  231. package/src/settings-workspace/Page.test.tsx +44 -0
  232. package/src/settings-workspace/Page.tsx +120 -0
  233. package/src/settings-workspace/data.ts +44 -0
  234. package/src/settings-workspace/template.meta.ts +19 -0
  235. package/src/shared/ConsoleShell.test.tsx +81 -0
  236. package/src/shared/ConsoleShell.tsx +98 -0
  237. package/src/shell-sidebar/Page.test.tsx +21 -0
  238. package/src/shell-sidebar/Page.tsx +85 -0
  239. package/src/shell-sidebar/data.ts +24 -0
  240. package/src/shell-sidebar/template.meta.ts +12 -0
  241. package/src/shell-topnav/Page.test.tsx +25 -0
  242. package/src/shell-topnav/Page.tsx +94 -0
  243. package/src/shell-topnav/data.ts +25 -0
  244. package/src/shell-topnav/template.meta.ts +12 -0
  245. package/src/sign-in/BrandPanel.tsx +34 -0
  246. package/src/sign-in/Page.test.tsx +41 -0
  247. package/src/sign-in/Page.tsx +73 -0
  248. package/src/sign-in/data.ts +11 -0
  249. package/src/sign-in/template.meta.ts +12 -0
  250. package/src/sign-in-card/Page.test.tsx +182 -0
  251. package/src/sign-in-card/Page.tsx +82 -0
  252. package/src/sign-in-card/data.ts +8 -0
  253. package/src/sign-in-card/examples/Username.tsx +28 -0
  254. package/src/sign-in-card/template.meta.ts +12 -0
  255. package/src/sign-up/Page.test.tsx +33 -0
  256. package/src/sign-up/Page.tsx +70 -0
  257. package/src/sign-up/data.ts +15 -0
  258. package/src/sign-up/template.meta.ts +12 -0
  259. package/src/sso-login/Page.test.tsx +41 -0
  260. package/src/sso-login/Page.tsx +151 -0
  261. package/src/sso-login/data.ts +10 -0
  262. package/src/sso-login/template.meta.ts +12 -0
  263. package/src/status-403/Page.test.tsx +23 -0
  264. package/src/status-403/Page.tsx +92 -0
  265. package/src/status-403/data.ts +13 -0
  266. package/src/status-403/template.meta.ts +12 -0
  267. package/src/status-404/Page.test.tsx +23 -0
  268. package/src/status-404/Page.tsx +60 -0
  269. package/src/status-404/PageHeading.test.tsx +52 -0
  270. package/src/status-404/data.ts +12 -0
  271. package/src/status-404/examples/PageHeader.tsx +28 -0
  272. package/src/status-404/template.meta.ts +12 -0
  273. package/src/status-500/Page.test.tsx +24 -0
  274. package/src/status-500/Page.tsx +70 -0
  275. package/src/status-500/data.ts +20 -0
  276. package/src/status-500/template.meta.ts +12 -0
  277. package/src/status-maintenance/Page.test.tsx +24 -0
  278. package/src/status-maintenance/Page.tsx +84 -0
  279. package/src/status-maintenance/data.ts +14 -0
  280. package/src/status-maintenance/template.meta.ts +12 -0
  281. package/src/styles.css +11 -0
  282. package/src/test/a11y.ts +18 -0
  283. package/src/test/setup.ts +36 -0
  284. package/src/upgrade-required/Page.test.tsx +28 -0
  285. package/src/upgrade-required/Page.tsx +79 -0
  286. package/src/upgrade-required/data.ts +19 -0
  287. package/src/upgrade-required/template.meta.ts +12 -0
  288. package/src/usage-analytics/Page.test.tsx +83 -0
  289. package/src/usage-analytics/Page.tsx +184 -0
  290. package/src/usage-analytics/data.ts +161 -0
  291. package/src/usage-analytics/template.meta.ts +21 -0
  292. package/src/verify-email/Page.test.tsx +32 -0
  293. package/src/verify-email/Page.tsx +118 -0
  294. package/src/verify-email/data.ts +7 -0
  295. package/src/verify-email/template.meta.ts +12 -0
  296. package/src/workflow-builder/Page.test.tsx +97 -0
  297. package/src/workflow-builder/Page.tsx +163 -0
  298. package/src/workflow-builder/data.ts +13 -0
  299. package/src/workflow-builder/template.meta.ts +12 -0
  300. 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
+ });