@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,25 @@
1
+ /** Product-agnostic preview copy for an account awaiting activation. */
2
+ export const pendingActivationSample = {
3
+ title: 'Your account is awaiting activation',
4
+ intro: 'Your registration is complete. You can sign in once your account has been approved.',
5
+ statusTitle: 'Approval pending',
6
+ statusMessage: 'Your account is waiting for review. No further action is needed right now.',
7
+ recovery: {
8
+ 'no-access': {
9
+ title: 'Account access is restricted',
10
+ message: 'This account is not available to you yet. Return to sign in or review your registration.',
11
+ },
12
+ 'not-found': {
13
+ title: 'Account not found',
14
+ message: 'We could not find this registration. Return to sign in or review your sign-up.',
15
+ },
16
+ error: {
17
+ title: 'Account status unavailable',
18
+ message: 'We could not load your account status. Try again in a moment.',
19
+ },
20
+ },
21
+ recoveryLinks: [
22
+ { label: 'Sign in', href: '/sign-in' },
23
+ { label: 'Review sign-up', href: '/sign-up' },
24
+ ],
25
+ };
@@ -0,0 +1,12 @@
1
+ import type { TemplateMeta } from '../meta';
2
+
3
+ export const meta: TemplateMeta = {
4
+ name: 'Pending activation',
5
+ family: 'Auth',
6
+ priority: 'P2',
7
+ status: 'beta',
8
+ description:
9
+ 'Centred card for registered accounts awaiting approval or activation: polite status, optional request reference, allowed actions and recovery links. Use instead of verify-email when activation requires approval rather than an email verification link.',
10
+ layout: 'AuthLayout',
11
+ blocks: ['inline-callout', 'error-panel'],
12
+ };
@@ -0,0 +1,75 @@
1
+ import type { PricingTier } from '@gntik-ai/blocks';
2
+ import { Check, Minus } from '@gntik-ai/icons';
3
+ import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@gntik-ai/ui';
4
+ import { Fragment } from 'react';
5
+ import type { ComparisonSection, ComparisonValue } from './data';
6
+
7
+ function Value({ value }: { value: ComparisonValue | undefined }) {
8
+ if (value === true)
9
+ return (
10
+ <>
11
+ <Check size={16} aria-hidden className="inline text-primary-text" />
12
+ <span className="sr-only">Included</span>
13
+ </>
14
+ );
15
+ if (value === false || value === undefined)
16
+ return (
17
+ <>
18
+ <Minus size={16} aria-hidden className="inline text-muted-foreground" />
19
+ <span className="sr-only">Not included</span>
20
+ </>
21
+ );
22
+ return <span className="text-foreground">{value}</span>;
23
+ }
24
+
25
+ /** Plan comparison: one column per tier, rows grouped by section (column-group headers). */
26
+ export function ComparisonTable({
27
+ tiers,
28
+ sections,
29
+ caption,
30
+ }: {
31
+ tiers: readonly PricingTier[];
32
+ sections: readonly ComparisonSection[];
33
+ caption: string;
34
+ }) {
35
+ return (
36
+ <Table containerLabel={caption}>
37
+ <TableCaption srOnly>{caption}</TableCaption>
38
+ <TableHeader>
39
+ <TableRow>
40
+ <TableHead scope="col" className="w-[34%]">
41
+ Feature
42
+ </TableHead>
43
+ {tiers.map((t) => (
44
+ <TableHead key={t.id} scope="col" align="center">
45
+ {t.name}
46
+ </TableHead>
47
+ ))}
48
+ </TableRow>
49
+ </TableHeader>
50
+ <TableBody>
51
+ {sections.map((s) => (
52
+ <Fragment key={s.title}>
53
+ <TableRow>
54
+ <TableHead scope="colgroup" colSpan={tiers.length + 1} className="bg-secondary/40 text-foreground">
55
+ {s.title}
56
+ </TableHead>
57
+ </TableRow>
58
+ {s.rows.map((r) => (
59
+ <TableRow key={r.feature}>
60
+ <TableHead scope="row" className="font-normal text-foreground">
61
+ {r.feature}
62
+ </TableHead>
63
+ {tiers.map((t) => (
64
+ <TableCell key={t.id} align="center">
65
+ <Value value={r.values[t.id]} />
66
+ </TableCell>
67
+ ))}
68
+ </TableRow>
69
+ ))}
70
+ </Fragment>
71
+ ))}
72
+ </TableBody>
73
+ </Table>
74
+ );
75
+ }
@@ -0,0 +1,29 @@
1
+ import { samplePricingTiers } from '@gntik-ai/blocks';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { expectNoAxeViolations } from '../test/a11y';
5
+ import PricingPage from './Page';
6
+
7
+ describe('PricingPage', () => {
8
+ it('renders the nav, tiers, comparison and FAQ', { timeout: 15000 }, async () => {
9
+ render(<PricingPage />);
10
+ expect(screen.getByRole('main')).toBeInTheDocument();
11
+ expect(screen.getAllByRole('navigation', { name: 'Primary' }).length).toBeGreaterThan(0);
12
+ expect(screen.getByRole('heading', { name: 'Simple pricing that grows with you' })).toBeInTheDocument();
13
+ const table = screen.getByRole('table', { name: 'Plan comparison' });
14
+ const head = within(table).getAllByRole('rowgroup')[0]!;
15
+ expect(within(head).getAllByRole('columnheader')).toHaveLength(samplePricingTiers.length + 1);
16
+ const sso = within(table).getByRole('rowheader', { name: 'Single sign-on' }).closest('tr');
17
+ expect(sso).not.toBeNull();
18
+ expect(within(sso as HTMLElement).getAllByText('Not included')).toHaveLength(2);
19
+ expect(screen.getByRole('heading', { name: /questions/i })).toBeInTheDocument();
20
+ await expectNoAxeViolations();
21
+ });
22
+
23
+ it('selects a tier with the billing cycle', { timeout: 15000 }, async () => {
24
+ const onSelectTier = vi.fn();
25
+ render(<PricingPage onSelectTier={onSelectTier} />);
26
+ await userEvent.click(screen.getByRole('button', { name: /Start free trial/ }));
27
+ expect(onSelectTier).toHaveBeenCalledWith(expect.objectContaining({ id: 'team' }), expect.any(String));
28
+ });
29
+ });
@@ -0,0 +1,83 @@
1
+ import {
2
+ Faq,
3
+ FeatureGrid,
4
+ PricingTable,
5
+ samplePricingTiers,
6
+ type FaqItem,
7
+ type Feature,
8
+ type PricingCycle,
9
+ type PricingTier,
10
+ } from '@gntik-ai/blocks';
11
+ import { Section, Stack, type MegaMenuItem } from '@gntik-ai/ui';
12
+ import type { ReactNode } from 'react';
13
+ import { ComparisonTable } from './ComparisonTable';
14
+ import { comparisonSections, publicCopyright, publicFooterLinks, publicMenu, type ComparisonSection } from './data';
15
+ import { PublicFrame } from './PublicFrame';
16
+
17
+ export interface PricingProps {
18
+ brand: ReactNode;
19
+ /** MegaMenu items (MobileNav below lg). */
20
+ menu: MegaMenuItem[];
21
+ currentHref: string;
22
+ signInHref: string;
23
+ getStartedHref: string;
24
+ title: ReactNode;
25
+ description: ReactNode;
26
+ tiers: PricingTier[];
27
+ defaultCycle: PricingCycle;
28
+ onSelectTier: (tier: PricingTier, cycle: PricingCycle) => void;
29
+ /** Highlights above the comparison (FeatureGrid); omitted = block samples. */
30
+ features: Feature[];
31
+ comparison: ComparisonSection[];
32
+ faqs: FaqItem[];
33
+ footerLinks: Array<{ label: string; href: string }>;
34
+ copyright: ReactNode;
35
+ }
36
+
37
+ /** Public pricing: StackedLayout + MegaMenu, PricingTable, FeatureGrid, plan comparison table and Faq. */
38
+ export default function PricingPage(props: Partial<PricingProps>) {
39
+ const {
40
+ brand,
41
+ menu = publicMenu,
42
+ currentHref = '/pricing',
43
+ signInHref,
44
+ getStartedHref,
45
+ title = 'Simple pricing that grows with you',
46
+ description = 'Start free, upgrade when your team does. Every paid plan has a 14-day trial.',
47
+ tiers = samplePricingTiers,
48
+ defaultCycle,
49
+ onSelectTier,
50
+ features,
51
+ comparison = comparisonSections,
52
+ faqs,
53
+ footerLinks = publicFooterLinks,
54
+ copyright = publicCopyright,
55
+ } = props;
56
+ return (
57
+ <PublicFrame
58
+ brand={brand}
59
+ menu={menu}
60
+ currentHref={currentHref}
61
+ signInHref={signInHref}
62
+ getStartedHref={getStartedHref}
63
+ footerLinks={footerLinks}
64
+ copyright={copyright}
65
+ >
66
+ <Stack gap={12}>
67
+ <PricingTable
68
+ eyebrow="Pricing"
69
+ title={title}
70
+ description={description}
71
+ tiers={tiers}
72
+ defaultCycle={defaultCycle}
73
+ onSelectTier={onSelectTier}
74
+ />
75
+ <FeatureGrid eyebrow="Every plan" title="Included from day one" features={features} columns={3} />
76
+ <Section title="Compare plans" description="Limits and features side by side.">
77
+ <ComparisonTable tiers={tiers} sections={comparison} caption="Plan comparison" />
78
+ </Section>
79
+ <Faq items={faqs} layout="split" />
80
+ </Stack>
81
+ </PublicFrame>
82
+ );
83
+ }
@@ -0,0 +1,84 @@
1
+ import {
2
+ Button,
3
+ Link,
4
+ Logo,
5
+ MegaMenu,
6
+ MobileNav,
7
+ StackedLayout,
8
+ ThemeSwitcher,
9
+ megaMenuNavItems,
10
+ type MegaMenuItem,
11
+ } from '@gntik-ai/ui';
12
+ import type { ReactNode } from 'react';
13
+
14
+ export interface PublicFrameProps {
15
+ brand?: ReactNode;
16
+ menu: MegaMenuItem[];
17
+ currentHref?: string;
18
+ signInHref?: string;
19
+ getStartedHref?: string;
20
+ footerLinks: Array<{ label: string; href: string }>;
21
+ copyright: ReactNode;
22
+ children: ReactNode;
23
+ }
24
+
25
+ /**
26
+ * Public-site chrome: StackedLayout with the MegaMenu as the sub-nav row on large screens and
27
+ * a MobileNav (fed by megaMenuNavItems) below lg.
28
+ */
29
+ export function PublicFrame({
30
+ brand = <Logo size={26} wordmark />,
31
+ menu,
32
+ currentHref,
33
+ signInHref = '/sign-in',
34
+ getStartedHref = '/sign-up',
35
+ footerLinks,
36
+ copyright,
37
+ children,
38
+ }: PublicFrameProps) {
39
+ return (
40
+ <StackedLayout
41
+ fullScreen
42
+ width="wide"
43
+ brand={brand}
44
+ actions={
45
+ <>
46
+ <ThemeSwitcher size="sm" className="hidden md:inline-flex" />
47
+ <Button variant="ghost" size="sm" render={<a href={signInHref} />} nativeButton={false}>
48
+ Sign in
49
+ </Button>
50
+ <Button size="sm" render={<a href={getStartedHref} />} nativeButton={false}>
51
+ Get started
52
+ </Button>
53
+ <MobileNav
54
+ className="lg:hidden"
55
+ groups={[{ items: megaMenuNavItems(menu) }]}
56
+ currentHref={currentHref}
57
+ title="Menu"
58
+ label="Primary"
59
+ side="right"
60
+ />
61
+ </>
62
+ }
63
+ subnav={<MegaMenu items={menu} currentHref={currentHref} label="Primary" className="hidden lg:block" />}
64
+ footer={
65
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
66
+ <span>{copyright}</span>
67
+ <nav aria-label="Footer">
68
+ <ul className="flex flex-wrap gap-x-4 gap-y-1">
69
+ {footerLinks.map((l) => (
70
+ <li key={l.href}>
71
+ <Link href={l.href} tone="muted" underline="hover">
72
+ {l.label}
73
+ </Link>
74
+ </li>
75
+ ))}
76
+ </ul>
77
+ </nav>
78
+ </div>
79
+ }
80
+ >
81
+ {children}
82
+ </StackedLayout>
83
+ );
84
+ }
@@ -0,0 +1,104 @@
1
+ import { BarChart3, BookOpen, FileText, FolderKanban, LifeBuoy, Rocket, Shield } from '@gntik-ai/icons';
2
+ import type { MegaMenuItem } from '@gntik-ai/ui';
3
+
4
+ /** Public navigation: product panels plus plain links. */
5
+ export const publicMenu: MegaMenuItem[] = [
6
+ {
7
+ label: 'Product',
8
+ sections: [
9
+ {
10
+ title: 'Build',
11
+ links: [
12
+ {
13
+ label: 'Projects',
14
+ href: '/product/projects',
15
+ icon: FolderKanban,
16
+ description: 'Every project, its environments and owners.',
17
+ },
18
+ {
19
+ label: 'Deployments',
20
+ href: '/product/deployments',
21
+ icon: Rocket,
22
+ description: 'Previews, rollbacks and deploy gates.',
23
+ },
24
+ ],
25
+ },
26
+ {
27
+ title: 'Operate',
28
+ links: [
29
+ {
30
+ label: 'Analytics',
31
+ href: '/product/analytics',
32
+ icon: BarChart3,
33
+ description: 'Usage, latency and error budgets.',
34
+ },
35
+ { label: 'Security', href: '/product/security', icon: Shield, description: 'SSO, roles and audit logs.' },
36
+ ],
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ label: 'Resources',
42
+ sections: [
43
+ {
44
+ links: [
45
+ { label: 'Documentation', href: '/docs', icon: BookOpen, description: 'Guides and API reference.' },
46
+ { label: 'Changelog', href: '/changelog', icon: FileText, description: 'What shipped this month.' },
47
+ { label: 'Support', href: '/contact', icon: LifeBuoy, description: 'Talk to a person.' },
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ { label: 'Pricing', href: '/pricing' },
53
+ { label: 'Contact', href: '/contact' },
54
+ ];
55
+
56
+ export const publicFooterLinks = [
57
+ { label: 'Status', href: '/status' },
58
+ { label: 'Privacy', href: '/privacy' },
59
+ { label: 'Terms', href: '/terms' },
60
+ ];
61
+
62
+ export const publicCopyright = '© Example, Inc.';
63
+
64
+ /** `true` = included, `false` = not included, a string = the limit. */
65
+ export type ComparisonValue = boolean | string;
66
+
67
+ export interface ComparisonRow {
68
+ feature: string;
69
+ /** Values per tier id. */
70
+ values: Record<string, ComparisonValue>;
71
+ }
72
+
73
+ export interface ComparisonSection {
74
+ title: string;
75
+ rows: ComparisonRow[];
76
+ }
77
+
78
+ /** Plan comparison keyed by the sample tier ids (starter · team · enterprise). */
79
+ export const comparisonSections: ComparisonSection[] = [
80
+ {
81
+ title: 'Usage',
82
+ rows: [
83
+ { feature: 'Projects', values: { starter: '1', team: 'Unlimited', enterprise: 'Unlimited' } },
84
+ { feature: 'Members', values: { starter: '3', team: '25', enterprise: 'Unlimited' } },
85
+ { feature: 'Requests per month', values: { starter: '50,000', team: '500,000', enterprise: 'Custom' } },
86
+ ],
87
+ },
88
+ {
89
+ title: 'Features',
90
+ rows: [
91
+ { feature: 'Preview deployments', values: { starter: false, team: true, enterprise: true } },
92
+ { feature: 'Saved views', values: { starter: true, team: true, enterprise: true } },
93
+ { feature: 'Data residency', values: { starter: false, team: false, enterprise: true } },
94
+ ],
95
+ },
96
+ {
97
+ title: 'Security and support',
98
+ rows: [
99
+ { feature: 'Single sign-on', values: { starter: false, team: false, enterprise: true } },
100
+ { feature: 'Audit log', values: { starter: false, team: '30 days', enterprise: '1 year' } },
101
+ { feature: 'Support', values: { starter: 'Community', team: 'Email', enterprise: 'Dedicated' } },
102
+ ],
103
+ },
104
+ ];
@@ -0,0 +1,12 @@
1
+ import type { TemplateMeta } from '../meta';
2
+
3
+ export const meta: TemplateMeta = {
4
+ name: 'Pricing',
5
+ family: 'Overview',
6
+ priority: 'P3',
7
+ status: 'experimental',
8
+ description:
9
+ 'Public pricing page: StackedLayout with a MegaMenu (MobileNav below lg), PricingTable with the monthly / annual toggle, FeatureGrid highlights, a plan comparison table grouped by section, and Faq.',
10
+ layout: 'StackedLayout',
11
+ blocks: ['pricing-table', 'feature-grid', 'faq'],
12
+ };
@@ -0,0 +1,72 @@
1
+ import { render, screen, waitFor, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import type { MonacoLoader } from '@gntik-ai/editor';
4
+ import { expectNoAxeViolations } from '../test/a11y';
5
+ import PromptPlaygroundPage from './Page';
6
+
7
+ /** Minimal Monaco stand-in (same approach as the CodePanel block tests). */
8
+ function mockMonaco() {
9
+ const model = () => ({
10
+ getValue: () => '',
11
+ setValue: vi.fn(),
12
+ onDidChangeContent: () => ({ dispose: vi.fn() }),
13
+ dispose: vi.fn(),
14
+ });
15
+ const editor = {
16
+ defineTheme: vi.fn(),
17
+ setTheme: vi.fn(),
18
+ setModelLanguage: vi.fn(),
19
+ remeasureFonts: vi.fn(),
20
+ createModel: vi.fn(() => model()),
21
+ create: vi.fn(),
22
+ createDiffEditor: vi.fn(() => ({
23
+ setModel: vi.fn(),
24
+ getModel: () => null,
25
+ updateOptions: vi.fn(),
26
+ layout: vi.fn(),
27
+ dispose: vi.fn(),
28
+ })),
29
+ };
30
+ return { editor, loader: (() => Promise.resolve({ editor })) as unknown as MonacoLoader };
31
+ }
32
+
33
+ describe('PromptPlaygroundPage', () => {
34
+ it('renders the playground chrome, variables, prompt and model columns', { timeout: 15000 }, async () => {
35
+ render(<PromptPlaygroundPage />);
36
+ expect(screen.getByRole('main')).toBeInTheDocument();
37
+ expect(screen.getByRole('heading', { level: 1, name: 'Ticket summary' })).toBeInTheDocument();
38
+ expect(screen.getByRole('complementary', { name: 'Variables' })).toBeInTheDocument();
39
+ expect(screen.getByRole('heading', { level: 2, name: 'Prompt' })).toBeInTheDocument();
40
+ expect(screen.getByRole('heading', { level: 3, name: 'Column 1: general-large' })).toBeInTheDocument();
41
+ expect(screen.getByRole('heading', { level: 3, name: 'Column 2: general-small' })).toBeInTheDocument();
42
+ await expectNoAxeViolations();
43
+ });
44
+
45
+ it('runs every column with the shared variables and diffs against the first', { timeout: 15000 }, async () => {
46
+ const m = mockMonaco();
47
+ const onGenerate = vi.fn((model: string) => ({ text: `answer from ${model}`, latencyMs: 10, outputTokens: 3 }));
48
+ render(<PromptPlaygroundPage onGenerate={onGenerate} loader={m.loader} />);
49
+ await userEvent.click(screen.getByRole('button', { name: 'Run' }));
50
+ await waitFor(() => expect(onGenerate).toHaveBeenCalledTimes(2));
51
+ expect(onGenerate.mock.calls.map((c) => c[0])).toEqual(['general-large', 'general-small']);
52
+ expect(onGenerate).toHaveBeenCalledWith(
53
+ 'general-large',
54
+ expect.objectContaining({ variables: expect.objectContaining({ company: 'Northwind' }) }),
55
+ );
56
+ expect(await screen.findByText('answer from general-small')).toBeInTheDocument();
57
+
58
+ await userEvent.click(screen.getByRole('switch', { name: 'Diff' }));
59
+ await waitFor(() => expect(m.editor.createDiffEditor).toHaveBeenCalledTimes(1));
60
+ expect(screen.getByText('Changes against general-large.')).toBeInTheDocument();
61
+ });
62
+
63
+ it('adds a third model column and removes it', { timeout: 15000 }, async () => {
64
+ render(<PromptPlaygroundPage />);
65
+ const outputs = screen.getByRole('region', { name: 'Outputs' });
66
+ await userEvent.click(within(outputs).getByRole('button', { name: 'Add model' }));
67
+ expect(screen.getByRole('heading', { level: 3, name: 'Column 3: reasoning-medium' })).toBeInTheDocument();
68
+ expect(within(outputs).getByRole('button', { name: 'Add model' })).toBeDisabled();
69
+ await userEvent.click(screen.getByRole('button', { name: 'Remove column 3' }));
70
+ expect(screen.queryByRole('heading', { name: /Column 3/ })).toBeNull();
71
+ });
72
+ });