@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,236 @@
1
+ import { PageHeader } from '@gntik-ai/blocks';
2
+ import { ChatComposer, ChatMessage } from '@gntik-ai/chat';
3
+ import { MessagesSquare, SquarePen } from '@gntik-ai/icons';
4
+ import {
5
+ Avatar,
6
+ AvatarGroup,
7
+ Badge,
8
+ EmptyState,
9
+ Heading,
10
+ Page,
11
+ SplitLayout,
12
+ Text,
13
+ Thumbnail,
14
+ ThumbnailList,
15
+ Timestamp,
16
+ TooltipProvider,
17
+ cn,
18
+ type BreadcrumbItem,
19
+ } from '@gntik-ai/ui';
20
+ import { useId, useState } from 'react';
21
+ import { ConsoleShell, type ConsoleShellProps } from '../shared/ConsoleShell';
22
+ import {
23
+ conversations as sampleConversations,
24
+ messagingBreadcrumbs,
25
+ type Conversation,
26
+ type MessageAttachment,
27
+ } from './data';
28
+
29
+ export interface MessagingProps {
30
+ conversations: Conversation[];
31
+ defaultConversationId: string | null;
32
+ /** Name shown on the current user's messages. */
33
+ currentUser: string;
34
+ /** Called with the text and attachments; the page appends the message locally. */
35
+ onSend: (conversationId: string, text: string, attachments: MessageAttachment[]) => void;
36
+ onNewConversation: () => void;
37
+ breadcrumbs: BreadcrumbItem[];
38
+ currentHref: string;
39
+ shell: Omit<ConsoleShellProps, 'children'>;
40
+ }
41
+
42
+ /** Team messaging: SplitLayout conversation list → thread of ChatMessages with Thumbnail attachments and a ChatComposer. */
43
+ export default function MessagingPage(props: Partial<MessagingProps>) {
44
+ const {
45
+ conversations: initial = sampleConversations,
46
+ defaultConversationId = null,
47
+ currentUser = 'You',
48
+ onSend,
49
+ onNewConversation,
50
+ breadcrumbs = messagingBreadcrumbs,
51
+ currentHref = '/messages',
52
+ shell,
53
+ } = props;
54
+ const [items, setItems] = useState(initial);
55
+ const [selectedId, setSelectedId] = useState<string | null>(defaultConversationId);
56
+ const [showDetail, setShowDetail] = useState(defaultConversationId != null);
57
+ const [pending, setPending] = useState<MessageAttachment[]>([]);
58
+ const ids = useId();
59
+ const selected = items.find((c) => c.id === selectedId) ?? null;
60
+
61
+ const openConversation = (id: string) => {
62
+ setSelectedId(id);
63
+ setShowDetail(true);
64
+ setPending([]);
65
+ setItems((list) => list.map((c) => (c.id === id ? { ...c, unread: 0 } : c)));
66
+ };
67
+ const send = (text: string) => {
68
+ if (!selected) return;
69
+ const at = new Date();
70
+ const attachments = pending;
71
+ setItems((list) =>
72
+ list.map((c) =>
73
+ c.id === selected.id
74
+ ? {
75
+ ...c,
76
+ messages: [
77
+ ...c.messages,
78
+ { id: `m-${at.getTime()}`, author: currentUser, mine: true, text, at, attachments },
79
+ ],
80
+ }
81
+ : c,
82
+ ),
83
+ );
84
+ setPending([]);
85
+ onSend?.(selected.id, text, attachments);
86
+ };
87
+
88
+ const list = (
89
+ <ul aria-label="Conversations" className="flex flex-col gap-0.5 p-2">
90
+ {items.map((c) => {
91
+ const last = c.messages[c.messages.length - 1];
92
+ return (
93
+ <li key={c.id}>
94
+ <button
95
+ type="button"
96
+ aria-current={c.id === selectedId ? 'true' : undefined}
97
+ onClick={() => openConversation(c.id)}
98
+ className="flex w-full items-start gap-3 rounded-lg px-3 py-2.5 text-start hover:bg-accent/55 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring aria-[current=true]:bg-accent"
99
+ >
100
+ <Avatar size="sm" name={c.title} aria-hidden />
101
+ <span className="min-w-0 flex-1">
102
+ <span className="flex items-baseline gap-2">
103
+ <span
104
+ className={cn(
105
+ 'min-w-0 flex-1 truncate text-[13px] text-foreground',
106
+ c.unread > 0 && 'font-semibold',
107
+ )}
108
+ >
109
+ {c.title}
110
+ </span>
111
+ {last && <Timestamp value={last.at} tooltip={false} className="shrink-0 font-mono text-[11px]" />}
112
+ </span>
113
+ <span className="mt-0.5 flex items-center gap-2">
114
+ <span className="min-w-0 flex-1 truncate text-[12px] text-muted-foreground">
115
+ {last ? `${last.mine ? 'You' : last.author.split(' ')[0]}: ${last.text}` : 'No messages yet'}
116
+ </span>
117
+ {c.unread > 0 && (
118
+ <Badge tone="primary" size="sm">
119
+ {c.unread}
120
+ <span className="sr-only"> unread</span>
121
+ </Badge>
122
+ )}
123
+ </span>
124
+ </span>
125
+ </button>
126
+ </li>
127
+ );
128
+ })}
129
+ </ul>
130
+ );
131
+
132
+ const detail = selected ? (
133
+ <section aria-labelledby={`${ids}-title`} className="flex h-full min-h-0 flex-col">
134
+ <header className="flex items-center gap-3 border-b border-border px-5 py-3">
135
+ <div className="min-w-0 flex-1">
136
+ <Heading id={`${ids}-title`} level={2} size="xs" truncate>
137
+ {selected.title}
138
+ </Heading>
139
+ <Text variant="caption" tone="muted" truncate>
140
+ {selected.participants.join(', ')}
141
+ </Text>
142
+ </div>
143
+ <AvatarGroup size="xs" max={3} label="Participants">
144
+ {selected.participants.map((p) => (
145
+ <Avatar key={p} name={p} />
146
+ ))}
147
+ </AvatarGroup>
148
+ </header>
149
+ <div
150
+ role="log"
151
+ aria-label={`Messages in ${selected.title}`}
152
+ className="flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto px-5 py-5"
153
+ >
154
+ {selected.messages.map((m) => (
155
+ <ChatMessage
156
+ key={m.id}
157
+ role={m.mine ? 'user' : 'assistant'}
158
+ author={m.mine ? currentUser : m.author}
159
+ avatar={m.mine ? undefined : <Avatar size="sm" name={m.author} aria-hidden />}
160
+ timestamp={m.at}
161
+ >
162
+ {m.text}
163
+ {m.attachments && m.attachments.length > 0 && (
164
+ <ThumbnailList aria-label={`Attachments from ${m.author}`} className="mt-2">
165
+ {m.attachments.map((a) => (
166
+ <Thumbnail key={a.id} name={a.name} size={a.size} type={a.type} src={a.src} />
167
+ ))}
168
+ </ThumbnailList>
169
+ )}
170
+ </ChatMessage>
171
+ ))}
172
+ </div>
173
+ <div className="border-t border-border p-3">
174
+ <ChatComposer
175
+ label={`Message ${selected.title}`}
176
+ placeholder={`Message ${selected.title}…`}
177
+ onSubmit={send}
178
+ attachments={pending.map(({ id, name, size }) => ({ id, name, size }))}
179
+ onAttach={(files) =>
180
+ setPending((p) => [
181
+ ...p,
182
+ ...files.map((f, i) => ({
183
+ id: `${f.name}-${Date.now()}-${i}`,
184
+ name: f.name,
185
+ size: f.size,
186
+ type: f.type || undefined,
187
+ })),
188
+ ])
189
+ }
190
+ onRemoveAttachment={(id) => setPending((p) => p.filter((a) => a.id !== id))}
191
+ />
192
+ </div>
193
+ </section>
194
+ ) : (
195
+ <EmptyState
196
+ icon={MessagesSquare}
197
+ size="sm"
198
+ titleAs="h2"
199
+ className="mx-auto mt-16"
200
+ title="Select a conversation"
201
+ description="Messages and files show up here."
202
+ />
203
+ );
204
+
205
+ return (
206
+ <ConsoleShell breadcrumbs={breadcrumbs} currentHref={currentHref} {...shell}>
207
+ <Page
208
+ width="full"
209
+ header={
210
+ <PageHeader
211
+ breadcrumbs={null}
212
+ title="Messages"
213
+ description="Direct and group conversations with your workspace."
214
+ status=""
215
+ meta={[]}
216
+ tabs={null}
217
+ actions={[{ label: 'New message', icon: SquarePen, onClick: onNewConversation }]}
218
+ />
219
+ }
220
+ >
221
+ <TooltipProvider>
222
+ <div className="h-[680px] min-h-0 overflow-hidden rounded-xl border border-border">
223
+ <SplitLayout
224
+ listLabel="Conversations"
225
+ detailLabel="Conversation"
226
+ showDetail={showDetail}
227
+ onShowDetailChange={setShowDetail}
228
+ list={list}
229
+ detail={detail}
230
+ />
231
+ </div>
232
+ </TooltipProvider>
233
+ </Page>
234
+ </ConsoleShell>
235
+ );
236
+ }
@@ -0,0 +1,118 @@
1
+ import type { BreadcrumbItem } from '@gntik-ai/ui';
2
+
3
+ export interface MessageAttachment {
4
+ id: string;
5
+ name: string;
6
+ size?: number;
7
+ type?: string;
8
+ /** Image preview URL. */
9
+ src?: string;
10
+ }
11
+
12
+ export interface DirectMessage {
13
+ id: string;
14
+ author: string;
15
+ /** Sent by the current user (right-aligned bubble). */
16
+ mine?: boolean;
17
+ text: string;
18
+ at: Date;
19
+ attachments?: MessageAttachment[];
20
+ }
21
+
22
+ export interface Conversation {
23
+ id: string;
24
+ title: string;
25
+ /** Other participants' names. */
26
+ participants: string[];
27
+ unread: number;
28
+ messages: DirectMessage[];
29
+ }
30
+
31
+ export const messagingBreadcrumbs: BreadcrumbItem[] = [
32
+ { label: 'Acme Industries', href: '/overview' },
33
+ { label: 'Messages' },
34
+ ];
35
+
36
+ const MIN = 60_000;
37
+ const NOW = Date.now();
38
+
39
+ /** Neutral illustration: shapes at low opacity in the default fill, readable in every theme. */
40
+ const PREVIEW =
41
+ 'data:image/svg+xml;utf8,' +
42
+ encodeURIComponent(
43
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 120"><rect width="160" height="120" fill-opacity="0.08"/><circle cx="118" cy="34" r="14" fill-opacity="0.22"/><path d="M0 120 L52 58 L92 100 L118 76 L160 120 Z" fill-opacity="0.3"/></svg>',
44
+ );
45
+
46
+ export const conversations: Conversation[] = [
47
+ {
48
+ id: 'c-release',
49
+ title: 'Release 2.14',
50
+ participants: ['Priya Natarajan', 'Marcus Chen'],
51
+ unread: 2,
52
+ messages: [
53
+ {
54
+ id: 'm1',
55
+ author: 'Priya Natarajan',
56
+ text: 'Deploy gates are green on staging. Sharing the dashboard before we promote.',
57
+ at: new Date(NOW - 42 * MIN),
58
+ attachments: [{ id: 'a1', name: 'staging-dashboard.png', size: 482_000, type: 'image/png', src: PREVIEW }],
59
+ },
60
+ {
61
+ id: 'm2',
62
+ author: 'You',
63
+ mine: true,
64
+ text: 'Looks good. Any open incidents on the gateway?',
65
+ at: new Date(NOW - 38 * MIN),
66
+ },
67
+ {
68
+ id: 'm3',
69
+ author: 'Marcus Chen',
70
+ text: 'None since Tuesday. Runbook attached in case we need to roll back.',
71
+ at: new Date(NOW - 12 * MIN),
72
+ attachments: [{ id: 'a2', name: 'rollback-runbook.pdf', size: 236_000, type: 'application/pdf' }],
73
+ },
74
+ {
75
+ id: 'm4',
76
+ author: 'Priya Natarajan',
77
+ text: 'Promoting at 15:00 unless someone objects.',
78
+ at: new Date(NOW - 5 * MIN),
79
+ },
80
+ ],
81
+ },
82
+ {
83
+ id: 'c-billing',
84
+ title: 'Ana Souza',
85
+ participants: ['Ana Souza'],
86
+ unread: 0,
87
+ messages: [
88
+ {
89
+ id: 'm1',
90
+ author: 'Ana Souza',
91
+ text: 'Can you check the September invoice? The seat count looks off by two.',
92
+ at: new Date(NOW - 3 * 60 * MIN),
93
+ attachments: [{ id: 'a1', name: 'invoice-2026-09.csv', size: 18_400, type: 'text/csv' }],
94
+ },
95
+ {
96
+ id: 'm2',
97
+ author: 'You',
98
+ mine: true,
99
+ text: 'Two guests were counted as members. I’ll ask billing for a credit.',
100
+ at: new Date(NOW - 170 * MIN),
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ id: 'c-onboarding',
106
+ title: 'Onboarding squad',
107
+ participants: ['Jordan Lee', 'Grace Hall', 'Linus Park'],
108
+ unread: 0,
109
+ messages: [
110
+ {
111
+ id: 'm1',
112
+ author: 'Jordan Lee',
113
+ text: 'New checklist is live. Completion went from 41% to 63% in the first week.',
114
+ at: new Date(NOW - 2 * 24 * 60 * MIN),
115
+ },
116
+ ],
117
+ },
118
+ ];
@@ -0,0 +1,12 @@
1
+ import type { TemplateMeta } from '../meta';
2
+
3
+ export const meta: TemplateMeta = {
4
+ name: 'Messaging',
5
+ family: 'Work',
6
+ priority: 'P3',
7
+ status: 'experimental',
8
+ description:
9
+ 'Team messaging in the console: SplitLayout conversation list (unread counts, last message) → thread of ChatMessages with Thumbnail attachments, participants AvatarGroup, and a ChatComposer with file attachments; Back on small screens.',
10
+ layout: 'SidebarLayout · Page · SplitLayout',
11
+ blocks: ['app-sidebar', 'app-topbar', 'page-header'],
12
+ };
package/src/meta.ts ADDED
@@ -0,0 +1,24 @@
1
+ /** Metadata contract for a page template: packages/templates/src/<id>/template.meta.ts. */
2
+ export interface TemplateMeta {
3
+ /** Display name, e.g. "Home dashboard". */
4
+ name: string;
5
+ family:
6
+ | 'Shells'
7
+ | 'Auth'
8
+ | 'Onboarding'
9
+ | 'Overview'
10
+ | 'Resources'
11
+ | 'Work'
12
+ | 'Settings'
13
+ | 'System'
14
+ | 'AI'
15
+ | 'Builders'
16
+ | 'Help';
17
+ priority: 'P1' | 'P2' | 'P3';
18
+ status: 'stable' | 'beta' | 'experimental';
19
+ description: string;
20
+ /** Layout it is built on, e.g. "SidebarLayout". */
21
+ layout: string;
22
+ /** Blocks it composes (ids from kit-registry.json). */
23
+ blocks: string[];
24
+ }
@@ -0,0 +1,22 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../test/a11y';
4
+ import MfaChallengePage from './Page';
5
+
6
+ describe('MfaChallengePage', () => {
7
+ it('renders the code step in a card', async () => {
8
+ const { container } = render(<MfaChallengePage />);
9
+ expect(screen.getByRole('main')).toBeInTheDocument();
10
+ expect(screen.getByRole('heading', { level: 1, name: 'Enter your verification code' })).toBeInTheDocument();
11
+ expect(screen.getByRole('button', { name: 'Resend code in 30s' })).toBeDisabled();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('auto-submits a complete code', async () => {
16
+ const onVerify = vi.fn();
17
+ render(<MfaChallengePage onVerify={onVerify} />);
18
+ await userEvent.click(screen.getAllByRole('textbox')[0]!);
19
+ await userEvent.keyboard('482913');
20
+ expect(onVerify).toHaveBeenCalledWith('482913', 'code');
21
+ });
22
+ });
@@ -0,0 +1,45 @@
1
+ import { MfaChallenge, type MfaMethod } from '@gntik-ai/blocks';
2
+ import { AuthLayout } from '@gntik-ai/ui';
3
+ import type { ReactNode } from 'react';
4
+ import { mfaChallengeSample } from './data';
5
+
6
+ export interface MfaChallengePageProps {
7
+ /** Verifies the code. A returned promise shows the loading state; a rejection shows in the alert. */
8
+ onVerify: (code: string, method: MfaMethod) => void | Promise<void>;
9
+ /** Sends a new code. */
10
+ onResend: () => void | Promise<void>;
11
+ /** "Back to sign in". */
12
+ onCancel: () => void;
13
+ /** Where the code was sent; `null` for authenticator apps. */
14
+ destination: string | null;
15
+ resendCooldown: number;
16
+ length: 6 | 8;
17
+ error: string | null;
18
+ logo: ReactNode;
19
+ }
20
+
21
+ /** Card second-factor step: AuthLayout `card` + MfaChallenge. */
22
+ export default function MfaChallengePage({
23
+ onVerify,
24
+ onResend = () => {},
25
+ onCancel = () => {},
26
+ destination = mfaChallengeSample.destination,
27
+ resendCooldown = mfaChallengeSample.resendCooldown,
28
+ length = mfaChallengeSample.length,
29
+ error,
30
+ logo,
31
+ }: Partial<MfaChallengePageProps>) {
32
+ return (
33
+ <AuthLayout fullScreen variant="card" logo={logo} skipLinkLabel="Skip to verification form">
34
+ <MfaChallenge
35
+ onVerify={onVerify}
36
+ onResend={onResend}
37
+ onCancel={onCancel}
38
+ destination={destination ?? undefined}
39
+ resendCooldown={resendCooldown}
40
+ length={length}
41
+ error={error}
42
+ />
43
+ </AuthLayout>
44
+ );
45
+ }
@@ -0,0 +1,6 @@
1
+ /** Sample destination and timing for the second-factor step. */
2
+ export const mfaChallengeSample = {
3
+ destination: 'd•••@example.com',
4
+ resendCooldown: 30,
5
+ length: 6 as const,
6
+ };
@@ -0,0 +1,12 @@
1
+ import type { TemplateMeta } from '../meta';
2
+
3
+ export const meta: TemplateMeta = {
4
+ name: 'Two-step verification',
5
+ family: 'Auth',
6
+ priority: 'P1',
7
+ status: 'stable',
8
+ description:
9
+ 'Centred card second-factor step: one-time code that auto-submits, backup-code fallback, resend with countdown and a way back to sign in.',
10
+ layout: 'AuthLayout',
11
+ blocks: ['mfa-challenge'],
12
+ };
@@ -0,0 +1,58 @@
1
+ import { render, screen, waitFor, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../test/a11y';
4
+ import ModelProvidersPage from './Page';
5
+
6
+ describe('ModelProvidersPage', () => {
7
+ it('renders the settings nav item, provider keys and usage limits', { timeout: 15000 }, async () => {
8
+ render(<ModelProvidersPage />);
9
+ expect(screen.getByRole('main')).toBeInTheDocument();
10
+ expect(screen.getByRole('heading', { level: 1, name: 'Model providers' })).toBeInTheDocument();
11
+ expect(
12
+ screen.getAllByRole('button', { name: /Model providers/ }).some((b) => b.getAttribute('aria-current') === 'page'),
13
+ ).toBe(true);
14
+ expect(screen.getByRole('heading', { level: 2, name: 'Provider keys' })).toBeInTheDocument();
15
+ expect(screen.getByRole('meter', { name: 'Spend this month' })).toBeInTheDocument();
16
+ await expectNoAxeViolations();
17
+ });
18
+
19
+ it('tests a connection through onTest', { timeout: 15000 }, async () => {
20
+ const onTest = vi.fn(() => Promise.resolve(false));
21
+ render(<ModelProvidersPage onTest={onTest} />);
22
+ const list = screen.getByRole('region', { name: 'Provider keys' });
23
+ const row = within(list).getByText('Gateway').closest('li')!;
24
+ await userEvent.click(within(row).getByRole('button', { name: /Test/ }));
25
+ expect(onTest).toHaveBeenCalledWith(expect.objectContaining({ id: 'k3' }));
26
+ await waitFor(() => expect(within(row).getByText('Failing')).toBeInTheDocument());
27
+ });
28
+
29
+ it('adds a key from the dialog after validation', { timeout: 15000 }, async () => {
30
+ const onAddKey = vi.fn((input: { name: string; provider: string }) => ({
31
+ id: 'new',
32
+ name: input.name,
33
+ provider: input.provider,
34
+ maskedKey: 'pa-…wxyz',
35
+ status: 'untested' as const,
36
+ }));
37
+ render(<ModelProvidersPage onAddKey={onAddKey} />);
38
+ await userEvent.click(screen.getAllByRole('button', { name: 'Add provider key' })[0]!);
39
+ const dialog = await screen.findByRole('dialog');
40
+ await userEvent.click(within(dialog).getByRole('button', { name: 'Add key' }));
41
+ expect(onAddKey).not.toHaveBeenCalled();
42
+ expect(within(dialog).getByText('Choose a provider.')).toBeInTheDocument();
43
+ await userEvent.type(within(dialog).getByRole('textbox', { name: /Name/ }), 'Staging');
44
+ await userEvent.click(within(dialog).getByRole('combobox', { name: 'Provider' }));
45
+ await userEvent.click(await screen.findByRole('option', { name: 'Provider B' }));
46
+ await userEvent.type(within(dialog).getByLabelText(/API key/), 'pb-secret-1234');
47
+ await userEvent.click(within(dialog).getByRole('button', { name: 'Add key' }));
48
+ await waitFor(() =>
49
+ expect(onAddKey).toHaveBeenCalledWith({
50
+ name: 'Staging',
51
+ provider: 'Provider B',
52
+ endpoint: '',
53
+ secret: 'pb-secret-1234',
54
+ }),
55
+ );
56
+ expect(await screen.findByText('Staging')).toBeInTheDocument();
57
+ });
58
+ });