@kelpie/ui 0.1.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 (704) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +44 -0
  3. package/dist/ServiceStatus.d.ts +6 -0
  4. package/dist/ServiceStatus.d.ts.map +1 -0
  5. package/dist/ServiceStatus.js +35 -0
  6. package/dist/ServiceStatus.js.map +1 -0
  7. package/dist/api/ApiProvider.d.ts +19 -0
  8. package/dist/api/ApiProvider.d.ts.map +1 -0
  9. package/dist/api/ApiProvider.js +18 -0
  10. package/dist/api/ApiProvider.js.map +1 -0
  11. package/dist/api/client.d.ts +83 -0
  12. package/dist/api/client.d.ts.map +1 -0
  13. package/dist/api/client.js +162 -0
  14. package/dist/api/client.js.map +1 -0
  15. package/dist/api/context.d.ts +14 -0
  16. package/dist/api/context.d.ts.map +1 -0
  17. package/dist/api/context.js +20 -0
  18. package/dist/api/context.js.map +1 -0
  19. package/dist/api/errors.d.ts +9 -0
  20. package/dist/api/errors.d.ts.map +1 -0
  21. package/dist/api/errors.js +14 -0
  22. package/dist/api/errors.js.map +1 -0
  23. package/dist/api/health.d.ts +15 -0
  24. package/dist/api/health.d.ts.map +1 -0
  25. package/dist/api/health.js +22 -0
  26. package/dist/api/health.js.map +1 -0
  27. package/dist/api/json.d.ts +3 -0
  28. package/dist/api/json.d.ts.map +1 -0
  29. package/dist/api/json.js +5 -0
  30. package/dist/api/json.js.map +1 -0
  31. package/dist/api/queryClient.d.ts +17 -0
  32. package/dist/api/queryClient.d.ts.map +1 -0
  33. package/dist/api/queryClient.js +33 -0
  34. package/dist/api/queryClient.js.map +1 -0
  35. package/dist/api/resource.d.ts +110 -0
  36. package/dist/api/resource.d.ts.map +1 -0
  37. package/dist/api/resource.js +238 -0
  38. package/dist/api/resource.js.map +1 -0
  39. package/dist/api/resources/account.d.ts +49 -0
  40. package/dist/api/resources/account.d.ts.map +1 -0
  41. package/dist/api/resources/account.js +146 -0
  42. package/dist/api/resources/account.js.map +1 -0
  43. package/dist/api/resources/activities.d.ts +8 -0
  44. package/dist/api/resources/activities.d.ts.map +1 -0
  45. package/dist/api/resources/activities.js +21 -0
  46. package/dist/api/resources/activities.js.map +1 -0
  47. package/dist/api/resources/agentTasks.d.ts +31 -0
  48. package/dist/api/resources/agentTasks.d.ts.map +1 -0
  49. package/dist/api/resources/agentTasks.js +103 -0
  50. package/dist/api/resources/agentTasks.js.map +1 -0
  51. package/dist/api/resources/candidates.d.ts +17 -0
  52. package/dist/api/resources/candidates.d.ts.map +1 -0
  53. package/dist/api/resources/candidates.js +40 -0
  54. package/dist/api/resources/candidates.js.map +1 -0
  55. package/dist/api/resources/companies.d.ts +22 -0
  56. package/dist/api/resources/companies.d.ts.map +1 -0
  57. package/dist/api/resources/companies.js +36 -0
  58. package/dist/api/resources/companies.js.map +1 -0
  59. package/dist/api/resources/dashboard.d.ts +8 -0
  60. package/dist/api/resources/dashboard.d.ts.map +1 -0
  61. package/dist/api/resources/dashboard.js +27 -0
  62. package/dist/api/resources/dashboard.js.map +1 -0
  63. package/dist/api/resources/deals.d.ts +30 -0
  64. package/dist/api/resources/deals.d.ts.map +1 -0
  65. package/dist/api/resources/deals.js +38 -0
  66. package/dist/api/resources/deals.js.map +1 -0
  67. package/dist/api/resources/decisions.d.ts +13 -0
  68. package/dist/api/resources/decisions.d.ts.map +1 -0
  69. package/dist/api/resources/decisions.js +38 -0
  70. package/dist/api/resources/decisions.js.map +1 -0
  71. package/dist/api/resources/forms.d.ts +82 -0
  72. package/dist/api/resources/forms.d.ts.map +1 -0
  73. package/dist/api/resources/forms.js +129 -0
  74. package/dist/api/resources/forms.js.map +1 -0
  75. package/dist/api/resources/handbookPages.d.ts +33 -0
  76. package/dist/api/resources/handbookPages.d.ts.map +1 -0
  77. package/dist/api/resources/handbookPages.js +49 -0
  78. package/dist/api/resources/handbookPages.js.map +1 -0
  79. package/dist/api/resources/importJobs.d.ts +72 -0
  80. package/dist/api/resources/importJobs.d.ts.map +1 -0
  81. package/dist/api/resources/importJobs.js +145 -0
  82. package/dist/api/resources/importJobs.js.map +1 -0
  83. package/dist/api/resources/invites.d.ts +19 -0
  84. package/dist/api/resources/invites.d.ts.map +1 -0
  85. package/dist/api/resources/invites.js +78 -0
  86. package/dist/api/resources/invites.js.map +1 -0
  87. package/dist/api/resources/mcpTools.d.ts +8 -0
  88. package/dist/api/resources/mcpTools.d.ts.map +1 -0
  89. package/dist/api/resources/mcpTools.js +23 -0
  90. package/dist/api/resources/mcpTools.js.map +1 -0
  91. package/dist/api/resources/members.d.ts +40 -0
  92. package/dist/api/resources/members.d.ts.map +1 -0
  93. package/dist/api/resources/members.js +67 -0
  94. package/dist/api/resources/members.js.map +1 -0
  95. package/dist/api/resources/moduleSettings.d.ts +14 -0
  96. package/dist/api/resources/moduleSettings.d.ts.map +1 -0
  97. package/dist/api/resources/moduleSettings.js +43 -0
  98. package/dist/api/resources/moduleSettings.js.map +1 -0
  99. package/dist/api/resources/mutation.d.ts +18 -0
  100. package/dist/api/resources/mutation.d.ts.map +1 -0
  101. package/dist/api/resources/mutation.js +12 -0
  102. package/dist/api/resources/mutation.js.map +1 -0
  103. package/dist/api/resources/notes.d.ts +14 -0
  104. package/dist/api/resources/notes.d.ts.map +1 -0
  105. package/dist/api/resources/notes.js +39 -0
  106. package/dist/api/resources/notes.js.map +1 -0
  107. package/dist/api/resources/opportunities.d.ts +29 -0
  108. package/dist/api/resources/opportunities.d.ts.map +1 -0
  109. package/dist/api/resources/opportunities.js +38 -0
  110. package/dist/api/resources/opportunities.js.map +1 -0
  111. package/dist/api/resources/partnerships.d.ts +33 -0
  112. package/dist/api/resources/partnerships.d.ts.map +1 -0
  113. package/dist/api/resources/partnerships.js +39 -0
  114. package/dist/api/resources/partnerships.js.map +1 -0
  115. package/dist/api/resources/people.d.ts +22 -0
  116. package/dist/api/resources/people.d.ts.map +1 -0
  117. package/dist/api/resources/people.js +36 -0
  118. package/dist/api/resources/people.js.map +1 -0
  119. package/dist/api/resources/pipelineStages.d.ts +22 -0
  120. package/dist/api/resources/pipelineStages.d.ts.map +1 -0
  121. package/dist/api/resources/pipelineStages.js +61 -0
  122. package/dist/api/resources/pipelineStages.js.map +1 -0
  123. package/dist/api/resources/planItems.d.ts +31 -0
  124. package/dist/api/resources/planItems.d.ts.map +1 -0
  125. package/dist/api/resources/planItems.js +60 -0
  126. package/dist/api/resources/planItems.js.map +1 -0
  127. package/dist/api/resources/positions.d.ts +23 -0
  128. package/dist/api/resources/positions.d.ts.map +1 -0
  129. package/dist/api/resources/positions.js +31 -0
  130. package/dist/api/resources/positions.js.map +1 -0
  131. package/dist/api/resources/raises.d.ts +30 -0
  132. package/dist/api/resources/raises.d.ts.map +1 -0
  133. package/dist/api/resources/raises.js +38 -0
  134. package/dist/api/resources/raises.js.map +1 -0
  135. package/dist/api/resources/roles.d.ts +20 -0
  136. package/dist/api/resources/roles.d.ts.map +1 -0
  137. package/dist/api/resources/roles.js +31 -0
  138. package/dist/api/resources/roles.js.map +1 -0
  139. package/dist/api/resources/search.d.ts +12 -0
  140. package/dist/api/resources/search.d.ts.map +1 -0
  141. package/dist/api/resources/search.js +37 -0
  142. package/dist/api/resources/search.js.map +1 -0
  143. package/dist/api/resources/session.d.ts +58 -0
  144. package/dist/api/resources/session.d.ts.map +1 -0
  145. package/dist/api/resources/session.js +157 -0
  146. package/dist/api/resources/session.js.map +1 -0
  147. package/dist/api/resources/webhooks.d.ts +45 -0
  148. package/dist/api/resources/webhooks.d.ts.map +1 -0
  149. package/dist/api/resources/webhooks.js +106 -0
  150. package/dist/api/resources/webhooks.js.map +1 -0
  151. package/dist/api/resources/workspace.d.ts +20 -0
  152. package/dist/api/resources/workspace.d.ts.map +1 -0
  153. package/dist/api/resources/workspace.js +63 -0
  154. package/dist/api/resources/workspace.js.map +1 -0
  155. package/dist/app/KelpieApp.d.ts +18 -0
  156. package/dist/app/KelpieApp.d.ts.map +1 -0
  157. package/dist/app/KelpieApp.js +59 -0
  158. package/dist/app/KelpieApp.js.map +1 -0
  159. package/dist/app/SessionGate.d.ts +14 -0
  160. package/dist/app/SessionGate.d.ts.map +1 -0
  161. package/dist/app/SessionGate.js +33 -0
  162. package/dist/app/SessionGate.js.map +1 -0
  163. package/dist/components/ActivitiesPanel.d.ts +28 -0
  164. package/dist/components/ActivitiesPanel.d.ts.map +1 -0
  165. package/dist/components/ActivitiesPanel.js +75 -0
  166. package/dist/components/ActivitiesPanel.js.map +1 -0
  167. package/dist/components/AgentTasks.d.ts +16 -0
  168. package/dist/components/AgentTasks.d.ts.map +1 -0
  169. package/dist/components/AgentTasks.js +166 -0
  170. package/dist/components/AgentTasks.js.map +1 -0
  171. package/dist/components/Chip.d.ts +10 -0
  172. package/dist/components/Chip.d.ts.map +1 -0
  173. package/dist/components/Chip.js +21 -0
  174. package/dist/components/Chip.js.map +1 -0
  175. package/dist/components/CopyButton.d.ts +7 -0
  176. package/dist/components/CopyButton.d.ts.map +1 -0
  177. package/dist/components/CopyButton.js +30 -0
  178. package/dist/components/CopyButton.js.map +1 -0
  179. package/dist/components/DataTable.d.ts +36 -0
  180. package/dist/components/DataTable.d.ts.map +1 -0
  181. package/dist/components/DataTable.js +43 -0
  182. package/dist/components/DataTable.js.map +1 -0
  183. package/dist/components/DecisionsPanel.d.ts +14 -0
  184. package/dist/components/DecisionsPanel.d.ts.map +1 -0
  185. package/dist/components/DecisionsPanel.js +73 -0
  186. package/dist/components/DecisionsPanel.js.map +1 -0
  187. package/dist/components/DeleteRecord.d.ts +19 -0
  188. package/dist/components/DeleteRecord.d.ts.map +1 -0
  189. package/dist/components/DeleteRecord.js +22 -0
  190. package/dist/components/DeleteRecord.js.map +1 -0
  191. package/dist/components/EntitySearch.d.ts +33 -0
  192. package/dist/components/EntitySearch.d.ts.map +1 -0
  193. package/dist/components/EntitySearch.js +111 -0
  194. package/dist/components/EntitySearch.js.map +1 -0
  195. package/dist/components/ErrorBoundary.d.ts +21 -0
  196. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  197. package/dist/components/ErrorBoundary.js +26 -0
  198. package/dist/components/ErrorBoundary.js.map +1 -0
  199. package/dist/components/InlineEdit.d.ts +37 -0
  200. package/dist/components/InlineEdit.d.ts.map +1 -0
  201. package/dist/components/InlineEdit.js +77 -0
  202. package/dist/components/InlineEdit.js.map +1 -0
  203. package/dist/components/KanbanBoard.d.ts +24 -0
  204. package/dist/components/KanbanBoard.d.ts.map +1 -0
  205. package/dist/components/KanbanBoard.js +92 -0
  206. package/dist/components/KanbanBoard.js.map +1 -0
  207. package/dist/components/MarkdownView.d.ts +4 -0
  208. package/dist/components/MarkdownView.d.ts.map +1 -0
  209. package/dist/components/MarkdownView.js +45 -0
  210. package/dist/components/MarkdownView.js.map +1 -0
  211. package/dist/components/NotesPanel.d.ts +17 -0
  212. package/dist/components/NotesPanel.d.ts.map +1 -0
  213. package/dist/components/NotesPanel.js +53 -0
  214. package/dist/components/NotesPanel.js.map +1 -0
  215. package/dist/components/PageHeader.d.ts +20 -0
  216. package/dist/components/PageHeader.d.ts.map +1 -0
  217. package/dist/components/PageHeader.js +15 -0
  218. package/dist/components/PageHeader.js.map +1 -0
  219. package/dist/components/PhonesField.d.ts +7 -0
  220. package/dist/components/PhonesField.d.ts.map +1 -0
  221. package/dist/components/PhonesField.js +92 -0
  222. package/dist/components/PhonesField.js.map +1 -0
  223. package/dist/components/PlanAttention.d.ts +40 -0
  224. package/dist/components/PlanAttention.d.ts.map +1 -0
  225. package/dist/components/PlanAttention.js +86 -0
  226. package/dist/components/PlanAttention.js.map +1 -0
  227. package/dist/components/PlanPanel.d.ts +18 -0
  228. package/dist/components/PlanPanel.d.ts.map +1 -0
  229. package/dist/components/PlanPanel.js +102 -0
  230. package/dist/components/PlanPanel.js.map +1 -0
  231. package/dist/components/QueryState.d.ts +24 -0
  232. package/dist/components/QueryState.d.ts.map +1 -0
  233. package/dist/components/QueryState.js +34 -0
  234. package/dist/components/QueryState.js.map +1 -0
  235. package/dist/components/RecordTabs.d.ts +16 -0
  236. package/dist/components/RecordTabs.d.ts.map +1 -0
  237. package/dist/components/RecordTabs.js +15 -0
  238. package/dist/components/RecordTabs.js.map +1 -0
  239. package/dist/components/SectionHeader.d.ts +15 -0
  240. package/dist/components/SectionHeader.d.ts.map +1 -0
  241. package/dist/components/SectionHeader.js +14 -0
  242. package/dist/components/SectionHeader.js.map +1 -0
  243. package/dist/components/SegmentedControl.d.ts +13 -0
  244. package/dist/components/SegmentedControl.d.ts.map +1 -0
  245. package/dist/components/SegmentedControl.js +14 -0
  246. package/dist/components/SegmentedControl.js.map +1 -0
  247. package/dist/components/Shell.d.ts +2 -0
  248. package/dist/components/Shell.d.ts.map +1 -0
  249. package/dist/components/Shell.js +143 -0
  250. package/dist/components/Shell.js.map +1 -0
  251. package/dist/components/SidebarField.d.ts +8 -0
  252. package/dist/components/SidebarField.d.ts.map +1 -0
  253. package/dist/components/SidebarField.js +6 -0
  254. package/dist/components/SidebarField.js.map +1 -0
  255. package/dist/components/SocialProfilesField.d.ts +8 -0
  256. package/dist/components/SocialProfilesField.d.ts.map +1 -0
  257. package/dist/components/SocialProfilesField.js +176 -0
  258. package/dist/components/SocialProfilesField.js.map +1 -0
  259. package/dist/components/StageSettings.d.ts +16 -0
  260. package/dist/components/StageSettings.d.ts.map +1 -0
  261. package/dist/components/StageSettings.js +107 -0
  262. package/dist/components/StageSettings.js.map +1 -0
  263. package/dist/components/SummaryBlock.d.ts +12 -0
  264. package/dist/components/SummaryBlock.d.ts.map +1 -0
  265. package/dist/components/SummaryBlock.js +12 -0
  266. package/dist/components/SummaryBlock.js.map +1 -0
  267. package/dist/index.d.ts +90 -0
  268. package/dist/index.d.ts.map +1 -0
  269. package/dist/index.js +55 -0
  270. package/dist/index.js.map +1 -0
  271. package/dist/lib/dates.d.ts +26 -0
  272. package/dist/lib/dates.d.ts.map +1 -0
  273. package/dist/lib/dates.js +71 -0
  274. package/dist/lib/dates.js.map +1 -0
  275. package/dist/lib/money.d.ts +7 -0
  276. package/dist/lib/money.d.ts.map +1 -0
  277. package/dist/lib/money.js +13 -0
  278. package/dist/lib/money.js.map +1 -0
  279. package/dist/lib/names.d.ts +9 -0
  280. package/dist/lib/names.d.ts.map +1 -0
  281. package/dist/lib/names.js +13 -0
  282. package/dist/lib/names.js.map +1 -0
  283. package/dist/lib/plan.d.ts +67 -0
  284. package/dist/lib/plan.d.ts.map +1 -0
  285. package/dist/lib/plan.js +96 -0
  286. package/dist/lib/plan.js.map +1 -0
  287. package/dist/lib/theme.d.ts +19 -0
  288. package/dist/lib/theme.d.ts.map +1 -0
  289. package/dist/lib/theme.js +30 -0
  290. package/dist/lib/theme.js.map +1 -0
  291. package/dist/pages/CompaniesPage.d.ts +2 -0
  292. package/dist/pages/CompaniesPage.d.ts.map +1 -0
  293. package/dist/pages/CompaniesPage.js +77 -0
  294. package/dist/pages/CompaniesPage.js.map +1 -0
  295. package/dist/pages/CompanyDetail.d.ts +2 -0
  296. package/dist/pages/CompanyDetail.d.ts.map +1 -0
  297. package/dist/pages/CompanyDetail.js +177 -0
  298. package/dist/pages/CompanyDetail.js.map +1 -0
  299. package/dist/pages/DealDetail.d.ts +2 -0
  300. package/dist/pages/DealDetail.d.ts.map +1 -0
  301. package/dist/pages/DealDetail.js +192 -0
  302. package/dist/pages/DealDetail.js.map +1 -0
  303. package/dist/pages/DealStageSettingsPage.d.ts +7 -0
  304. package/dist/pages/DealStageSettingsPage.d.ts.map +1 -0
  305. package/dist/pages/DealStageSettingsPage.js +38 -0
  306. package/dist/pages/DealStageSettingsPage.js.map +1 -0
  307. package/dist/pages/DealsPage.d.ts +2 -0
  308. package/dist/pages/DealsPage.d.ts.map +1 -0
  309. package/dist/pages/DealsPage.js +177 -0
  310. package/dist/pages/DealsPage.js.map +1 -0
  311. package/dist/pages/DecisionsPage.d.ts +2 -0
  312. package/dist/pages/DecisionsPage.d.ts.map +1 -0
  313. package/dist/pages/DecisionsPage.js +121 -0
  314. package/dist/pages/DecisionsPage.js.map +1 -0
  315. package/dist/pages/FormDetail.d.ts +2 -0
  316. package/dist/pages/FormDetail.d.ts.map +1 -0
  317. package/dist/pages/FormDetail.js +56 -0
  318. package/dist/pages/FormDetail.js.map +1 -0
  319. package/dist/pages/FormsPage.d.ts +2 -0
  320. package/dist/pages/FormsPage.d.ts.map +1 -0
  321. package/dist/pages/FormsPage.js +67 -0
  322. package/dist/pages/FormsPage.js.map +1 -0
  323. package/dist/pages/FundraisingPage.d.ts +2 -0
  324. package/dist/pages/FundraisingPage.d.ts.map +1 -0
  325. package/dist/pages/FundraisingPage.js +187 -0
  326. package/dist/pages/FundraisingPage.js.map +1 -0
  327. package/dist/pages/HiringPage.d.ts +9 -0
  328. package/dist/pages/HiringPage.d.ts.map +1 -0
  329. package/dist/pages/HiringPage.js +73 -0
  330. package/dist/pages/HiringPage.js.map +1 -0
  331. package/dist/pages/NotFoundPage.d.ts +10 -0
  332. package/dist/pages/NotFoundPage.d.ts.map +1 -0
  333. package/dist/pages/NotFoundPage.js +14 -0
  334. package/dist/pages/NotFoundPage.js.map +1 -0
  335. package/dist/pages/OpportunitiesPage.d.ts +2 -0
  336. package/dist/pages/OpportunitiesPage.d.ts.map +1 -0
  337. package/dist/pages/OpportunitiesPage.js +184 -0
  338. package/dist/pages/OpportunitiesPage.js.map +1 -0
  339. package/dist/pages/OpportunityDetail.d.ts +2 -0
  340. package/dist/pages/OpportunityDetail.d.ts.map +1 -0
  341. package/dist/pages/OpportunityDetail.js +132 -0
  342. package/dist/pages/OpportunityDetail.js.map +1 -0
  343. package/dist/pages/OpportunityStageSettingsPage.d.ts +3 -0
  344. package/dist/pages/OpportunityStageSettingsPage.d.ts.map +1 -0
  345. package/dist/pages/OpportunityStageSettingsPage.js +34 -0
  346. package/dist/pages/OpportunityStageSettingsPage.js.map +1 -0
  347. package/dist/pages/PartnershipDetail.d.ts +2 -0
  348. package/dist/pages/PartnershipDetail.d.ts.map +1 -0
  349. package/dist/pages/PartnershipDetail.js +179 -0
  350. package/dist/pages/PartnershipDetail.js.map +1 -0
  351. package/dist/pages/PartnershipStageSettingsPage.d.ts +3 -0
  352. package/dist/pages/PartnershipStageSettingsPage.d.ts.map +1 -0
  353. package/dist/pages/PartnershipStageSettingsPage.js +34 -0
  354. package/dist/pages/PartnershipStageSettingsPage.js.map +1 -0
  355. package/dist/pages/PartnershipsPage.d.ts +2 -0
  356. package/dist/pages/PartnershipsPage.d.ts.map +1 -0
  357. package/dist/pages/PartnershipsPage.js +182 -0
  358. package/dist/pages/PartnershipsPage.js.map +1 -0
  359. package/dist/pages/PeoplePage.d.ts +10 -0
  360. package/dist/pages/PeoplePage.d.ts.map +1 -0
  361. package/dist/pages/PeoplePage.js +71 -0
  362. package/dist/pages/PeoplePage.js.map +1 -0
  363. package/dist/pages/PersonDetail.d.ts +2 -0
  364. package/dist/pages/PersonDetail.d.ts.map +1 -0
  365. package/dist/pages/PersonDetail.js +193 -0
  366. package/dist/pages/PersonDetail.js.map +1 -0
  367. package/dist/pages/PlanningPage.d.ts +2 -0
  368. package/dist/pages/PlanningPage.d.ts.map +1 -0
  369. package/dist/pages/PlanningPage.js +112 -0
  370. package/dist/pages/PlanningPage.js.map +1 -0
  371. package/dist/pages/RaiseDetail.d.ts +2 -0
  372. package/dist/pages/RaiseDetail.d.ts.map +1 -0
  373. package/dist/pages/RaiseDetail.js +198 -0
  374. package/dist/pages/RaiseDetail.js.map +1 -0
  375. package/dist/pages/RaiseStageSettingsPage.d.ts +3 -0
  376. package/dist/pages/RaiseStageSettingsPage.d.ts.map +1 -0
  377. package/dist/pages/RaiseStageSettingsPage.js +34 -0
  378. package/dist/pages/RaiseStageSettingsPage.js.map +1 -0
  379. package/dist/pages/RoleDetail.d.ts +2 -0
  380. package/dist/pages/RoleDetail.d.ts.map +1 -0
  381. package/dist/pages/RoleDetail.js +177 -0
  382. package/dist/pages/RoleDetail.js.map +1 -0
  383. package/dist/pages/SearchPage.d.ts +2 -0
  384. package/dist/pages/SearchPage.d.ts.map +1 -0
  385. package/dist/pages/SearchPage.js +51 -0
  386. package/dist/pages/SearchPage.js.map +1 -0
  387. package/dist/pages/account/AccountLayout.d.ts +2 -0
  388. package/dist/pages/account/AccountLayout.d.ts.map +1 -0
  389. package/dist/pages/account/AccountLayout.js +25 -0
  390. package/dist/pages/account/AccountLayout.js.map +1 -0
  391. package/dist/pages/account/Field.d.ts +8 -0
  392. package/dist/pages/account/Field.d.ts.map +1 -0
  393. package/dist/pages/account/Field.js +6 -0
  394. package/dist/pages/account/Field.js.map +1 -0
  395. package/dist/pages/account/PreferencesPage.d.ts +2 -0
  396. package/dist/pages/account/PreferencesPage.d.ts.map +1 -0
  397. package/dist/pages/account/PreferencesPage.js +92 -0
  398. package/dist/pages/account/PreferencesPage.js.map +1 -0
  399. package/dist/pages/account/ProfilePage.d.ts +14 -0
  400. package/dist/pages/account/ProfilePage.d.ts.map +1 -0
  401. package/dist/pages/account/ProfilePage.js +71 -0
  402. package/dist/pages/account/ProfilePage.js.map +1 -0
  403. package/dist/pages/account/SecurityPage.d.ts +11 -0
  404. package/dist/pages/account/SecurityPage.d.ts.map +1 -0
  405. package/dist/pages/account/SecurityPage.js +78 -0
  406. package/dist/pages/account/SecurityPage.js.map +1 -0
  407. package/dist/pages/admin/DataPage.d.ts +3 -0
  408. package/dist/pages/admin/DataPage.d.ts.map +1 -0
  409. package/dist/pages/admin/DataPage.js +210 -0
  410. package/dist/pages/admin/DataPage.js.map +1 -0
  411. package/dist/pages/admin/McpPage.d.ts +2 -0
  412. package/dist/pages/admin/McpPage.d.ts.map +1 -0
  413. package/dist/pages/admin/McpPage.js +128 -0
  414. package/dist/pages/admin/McpPage.js.map +1 -0
  415. package/dist/pages/admin/ModulesPage.d.ts +2 -0
  416. package/dist/pages/admin/ModulesPage.d.ts.map +1 -0
  417. package/dist/pages/admin/ModulesPage.js +53 -0
  418. package/dist/pages/admin/ModulesPage.js.map +1 -0
  419. package/dist/pages/admin/TeamPage.d.ts +2 -0
  420. package/dist/pages/admin/TeamPage.d.ts.map +1 -0
  421. package/dist/pages/admin/TeamPage.js +99 -0
  422. package/dist/pages/admin/TeamPage.js.map +1 -0
  423. package/dist/pages/admin/WebhookDeliveries.d.ts +4 -0
  424. package/dist/pages/admin/WebhookDeliveries.d.ts.map +1 -0
  425. package/dist/pages/admin/WebhookDeliveries.js +88 -0
  426. package/dist/pages/admin/WebhookDeliveries.js.map +1 -0
  427. package/dist/pages/admin/WebhooksPage.d.ts +2 -0
  428. package/dist/pages/admin/WebhooksPage.d.ts.map +1 -0
  429. package/dist/pages/admin/WebhooksPage.js +152 -0
  430. package/dist/pages/admin/WebhooksPage.js.map +1 -0
  431. package/dist/pages/admin/WorkspaceSettingsPage.d.ts +2 -0
  432. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -0
  433. package/dist/pages/admin/WorkspaceSettingsPage.js +117 -0
  434. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -0
  435. package/dist/pages/auth/AuthForm.d.ts +46 -0
  436. package/dist/pages/auth/AuthForm.d.ts.map +1 -0
  437. package/dist/pages/auth/AuthForm.js +32 -0
  438. package/dist/pages/auth/AuthForm.js.map +1 -0
  439. package/dist/pages/auth/AuthLayout.d.ts +24 -0
  440. package/dist/pages/auth/AuthLayout.d.ts.map +1 -0
  441. package/dist/pages/auth/AuthLayout.js +19 -0
  442. package/dist/pages/auth/AuthLayout.js.map +1 -0
  443. package/dist/pages/auth/ForgotPasswordPage.d.ts +13 -0
  444. package/dist/pages/auth/ForgotPasswordPage.d.ts.map +1 -0
  445. package/dist/pages/auth/ForgotPasswordPage.js +39 -0
  446. package/dist/pages/auth/ForgotPasswordPage.js.map +1 -0
  447. package/dist/pages/auth/JoinPage.d.ts +12 -0
  448. package/dist/pages/auth/JoinPage.d.ts.map +1 -0
  449. package/dist/pages/auth/JoinPage.js +43 -0
  450. package/dist/pages/auth/JoinPage.js.map +1 -0
  451. package/dist/pages/auth/ResetPasswordPage.d.ts +13 -0
  452. package/dist/pages/auth/ResetPasswordPage.d.ts.map +1 -0
  453. package/dist/pages/auth/ResetPasswordPage.js +58 -0
  454. package/dist/pages/auth/ResetPasswordPage.js.map +1 -0
  455. package/dist/pages/auth/SignInPage.d.ts +2 -0
  456. package/dist/pages/auth/SignInPage.d.ts.map +1 -0
  457. package/dist/pages/auth/SignInPage.js +38 -0
  458. package/dist/pages/auth/SignInPage.js.map +1 -0
  459. package/dist/pages/auth/SignUpPage.d.ts +13 -0
  460. package/dist/pages/auth/SignUpPage.d.ts.map +1 -0
  461. package/dist/pages/auth/SignUpPage.js +43 -0
  462. package/dist/pages/auth/SignUpPage.js.map +1 -0
  463. package/dist/pages/candidateFields.d.ts +22 -0
  464. package/dist/pages/candidateFields.d.ts.map +1 -0
  465. package/dist/pages/candidateFields.js +82 -0
  466. package/dist/pages/candidateFields.js.map +1 -0
  467. package/dist/pages/dashboard/DashboardPage.d.ts +2 -0
  468. package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -0
  469. package/dist/pages/dashboard/DashboardPage.js +40 -0
  470. package/dist/pages/dashboard/DashboardPage.js.map +1 -0
  471. package/dist/pages/dashboard/attention.d.ts +41 -0
  472. package/dist/pages/dashboard/attention.d.ts.map +1 -0
  473. package/dist/pages/dashboard/attention.js +146 -0
  474. package/dist/pages/dashboard/attention.js.map +1 -0
  475. package/dist/pages/dashboard/sections.d.ts +20 -0
  476. package/dist/pages/dashboard/sections.d.ts.map +1 -0
  477. package/dist/pages/dashboard/sections.js +60 -0
  478. package/dist/pages/dashboard/sections.js.map +1 -0
  479. package/dist/pages/fields.d.ts +11 -0
  480. package/dist/pages/fields.d.ts.map +1 -0
  481. package/dist/pages/fields.js +17 -0
  482. package/dist/pages/fields.js.map +1 -0
  483. package/dist/pages/forms/EmbedPanel.d.ts +14 -0
  484. package/dist/pages/forms/EmbedPanel.d.ts.map +1 -0
  485. package/dist/pages/forms/EmbedPanel.js +19 -0
  486. package/dist/pages/forms/EmbedPanel.js.map +1 -0
  487. package/dist/pages/forms/FieldsEditor.d.ts +6 -0
  488. package/dist/pages/forms/FieldsEditor.d.ts.map +1 -0
  489. package/dist/pages/forms/FieldsEditor.js +115 -0
  490. package/dist/pages/forms/FieldsEditor.js.map +1 -0
  491. package/dist/pages/forms/FormSettings.d.ts +6 -0
  492. package/dist/pages/forms/FormSettings.d.ts.map +1 -0
  493. package/dist/pages/forms/FormSettings.js +47 -0
  494. package/dist/pages/forms/FormSettings.js.map +1 -0
  495. package/dist/pages/forms/SubmissionsTable.d.ts +8 -0
  496. package/dist/pages/forms/SubmissionsTable.d.ts.map +1 -0
  497. package/dist/pages/forms/SubmissionsTable.js +91 -0
  498. package/dist/pages/forms/SubmissionsTable.js.map +1 -0
  499. package/dist/pages/forms/fieldList.d.ts +70 -0
  500. package/dist/pages/forms/fieldList.d.ts.map +1 -0
  501. package/dist/pages/forms/fieldList.js +126 -0
  502. package/dist/pages/forms/fieldList.js.map +1 -0
  503. package/dist/pages/forms/template.d.ts +18 -0
  504. package/dist/pages/forms/template.d.ts.map +1 -0
  505. package/dist/pages/forms/template.js +34 -0
  506. package/dist/pages/forms/template.js.map +1 -0
  507. package/dist/pages/handbook/HandbookPage.d.ts +2 -0
  508. package/dist/pages/handbook/HandbookPage.d.ts.map +1 -0
  509. package/dist/pages/handbook/HandbookPage.js +197 -0
  510. package/dist/pages/handbook/HandbookPage.js.map +1 -0
  511. package/dist/pages/handbook/tree.d.ts +62 -0
  512. package/dist/pages/handbook/tree.d.ts.map +1 -0
  513. package/dist/pages/handbook/tree.js +222 -0
  514. package/dist/pages/handbook/tree.js.map +1 -0
  515. package/dist/pages/hiringDirectory.d.ts +60 -0
  516. package/dist/pages/hiringDirectory.d.ts.map +1 -0
  517. package/dist/pages/hiringDirectory.js +81 -0
  518. package/dist/pages/hiringDirectory.js.map +1 -0
  519. package/dist/pages/onboarding/HandbookStepPage.d.ts +14 -0
  520. package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -0
  521. package/dist/pages/onboarding/HandbookStepPage.js +29 -0
  522. package/dist/pages/onboarding/HandbookStepPage.js.map +1 -0
  523. package/dist/pages/onboarding/InvitesStepPage.d.ts +2 -0
  524. package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -0
  525. package/dist/pages/onboarding/InvitesStepPage.js +77 -0
  526. package/dist/pages/onboarding/InvitesStepPage.js.map +1 -0
  527. package/dist/pages/onboarding/WorkspaceStepPage.d.ts +2 -0
  528. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -0
  529. package/dist/pages/onboarding/WorkspaceStepPage.js +72 -0
  530. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -0
  531. package/dist/pages/positionDirectory.d.ts +20 -0
  532. package/dist/pages/positionDirectory.d.ts.map +1 -0
  533. package/dist/pages/positionDirectory.js +58 -0
  534. package/dist/pages/positionDirectory.js.map +1 -0
  535. package/dist/registry/UiExtensionProvider.d.ts +12 -0
  536. package/dist/registry/UiExtensionProvider.d.ts.map +1 -0
  537. package/dist/registry/UiExtensionProvider.js +10 -0
  538. package/dist/registry/UiExtensionProvider.js.map +1 -0
  539. package/dist/registry/context.d.ts +31 -0
  540. package/dist/registry/context.d.ts.map +1 -0
  541. package/dist/registry/context.js +45 -0
  542. package/dist/registry/context.js.map +1 -0
  543. package/dist/registry/contributions.d.ts +71 -0
  544. package/dist/registry/contributions.d.ts.map +1 -0
  545. package/dist/registry/contributions.js +25 -0
  546. package/dist/registry/contributions.js.map +1 -0
  547. package/dist/registry/overridable.d.ts +27 -0
  548. package/dist/registry/overridable.d.ts.map +1 -0
  549. package/dist/registry/overridable.js +29 -0
  550. package/dist/registry/overridable.js.map +1 -0
  551. package/dist/registry/registry.d.ts +68 -0
  552. package/dist/registry/registry.d.ts.map +1 -0
  553. package/dist/registry/registry.js +112 -0
  554. package/dist/registry/registry.js.map +1 -0
  555. package/dist/testing/inputs.d.ts +13 -0
  556. package/dist/testing/inputs.d.ts.map +1 -0
  557. package/dist/testing/inputs.js +16 -0
  558. package/dist/testing/inputs.js.map +1 -0
  559. package/dist/testing/stubClient.d.ts +42 -0
  560. package/dist/testing/stubClient.d.ts.map +1 -0
  561. package/dist/testing/stubClient.js +29 -0
  562. package/dist/testing/stubClient.js.map +1 -0
  563. package/package.json +73 -0
  564. package/src/ServiceStatus.tsx +61 -0
  565. package/src/api/ApiProvider.tsx +40 -0
  566. package/src/api/client.ts +267 -0
  567. package/src/api/context.ts +25 -0
  568. package/src/api/errors.ts +14 -0
  569. package/src/api/health.ts +38 -0
  570. package/src/api/json.ts +4 -0
  571. package/src/api/queryClient.ts +35 -0
  572. package/src/api/resource.ts +412 -0
  573. package/src/api/resources/account.ts +220 -0
  574. package/src/api/resources/activities.ts +31 -0
  575. package/src/api/resources/agentTasks.ts +172 -0
  576. package/src/api/resources/candidates.ts +69 -0
  577. package/src/api/resources/companies.ts +72 -0
  578. package/src/api/resources/dashboard.ts +39 -0
  579. package/src/api/resources/deals.ts +82 -0
  580. package/src/api/resources/decisions.ts +60 -0
  581. package/src/api/resources/forms.ts +218 -0
  582. package/src/api/resources/handbookPages.ts +77 -0
  583. package/src/api/resources/importJobs.ts +229 -0
  584. package/src/api/resources/invites.ts +112 -0
  585. package/src/api/resources/mcpTools.ts +28 -0
  586. package/src/api/resources/members.ts +105 -0
  587. package/src/api/resources/moduleSettings.ts +67 -0
  588. package/src/api/resources/mutation.ts +31 -0
  589. package/src/api/resources/notes.ts +62 -0
  590. package/src/api/resources/opportunities.ts +84 -0
  591. package/src/api/resources/partnerships.ts +89 -0
  592. package/src/api/resources/people.ts +72 -0
  593. package/src/api/resources/pipelineStages.ts +93 -0
  594. package/src/api/resources/planItems.ts +103 -0
  595. package/src/api/resources/positions.ts +70 -0
  596. package/src/api/resources/raises.ts +82 -0
  597. package/src/api/resources/roles.ts +65 -0
  598. package/src/api/resources/search.ts +49 -0
  599. package/src/api/resources/session.ts +220 -0
  600. package/src/api/resources/webhooks.ts +157 -0
  601. package/src/api/resources/workspace.ts +81 -0
  602. package/src/app/KelpieApp.tsx +162 -0
  603. package/src/app/SessionGate.tsx +42 -0
  604. package/src/components/ActivitiesPanel.tsx +235 -0
  605. package/src/components/AgentTasks.tsx +473 -0
  606. package/src/components/Chip.tsx +36 -0
  607. package/src/components/CopyButton.tsx +48 -0
  608. package/src/components/DataTable.tsx +209 -0
  609. package/src/components/DecisionsPanel.tsx +233 -0
  610. package/src/components/DeleteRecord.tsx +73 -0
  611. package/src/components/EntitySearch.tsx +268 -0
  612. package/src/components/ErrorBoundary.tsx +58 -0
  613. package/src/components/InlineEdit.tsx +179 -0
  614. package/src/components/KanbanBoard.tsx +234 -0
  615. package/src/components/MarkdownView.tsx +73 -0
  616. package/src/components/NotesPanel.tsx +169 -0
  617. package/src/components/PageHeader.tsx +61 -0
  618. package/src/components/PhonesField.tsx +204 -0
  619. package/src/components/PlanAttention.tsx +249 -0
  620. package/src/components/PlanPanel.tsx +317 -0
  621. package/src/components/QueryState.tsx +83 -0
  622. package/src/components/RecordTabs.tsx +69 -0
  623. package/src/components/SectionHeader.tsx +63 -0
  624. package/src/components/SegmentedControl.tsx +48 -0
  625. package/src/components/Shell.tsx +300 -0
  626. package/src/components/SidebarField.tsx +18 -0
  627. package/src/components/SocialProfilesField.tsx +356 -0
  628. package/src/components/StageSettings.tsx +319 -0
  629. package/src/components/SummaryBlock.tsx +27 -0
  630. package/src/index.ts +190 -0
  631. package/src/lib/dates.ts +90 -0
  632. package/src/lib/money.ts +12 -0
  633. package/src/lib/names.ts +13 -0
  634. package/src/lib/plan.ts +147 -0
  635. package/src/lib/theme.ts +53 -0
  636. package/src/pages/CompaniesPage.tsx +143 -0
  637. package/src/pages/CompanyDetail.tsx +502 -0
  638. package/src/pages/DealDetail.tsx +512 -0
  639. package/src/pages/DealStageSettingsPage.tsx +67 -0
  640. package/src/pages/DealsPage.tsx +342 -0
  641. package/src/pages/DecisionsPage.tsx +211 -0
  642. package/src/pages/FormDetail.tsx +130 -0
  643. package/src/pages/FormsPage.tsx +151 -0
  644. package/src/pages/FundraisingPage.tsx +354 -0
  645. package/src/pages/HiringPage.tsx +169 -0
  646. package/src/pages/NotFoundPage.tsx +27 -0
  647. package/src/pages/OpportunitiesPage.tsx +349 -0
  648. package/src/pages/OpportunityDetail.tsx +337 -0
  649. package/src/pages/OpportunityStageSettingsPage.tsx +63 -0
  650. package/src/pages/PartnershipDetail.tsx +490 -0
  651. package/src/pages/PartnershipStageSettingsPage.tsx +63 -0
  652. package/src/pages/PartnershipsPage.tsx +353 -0
  653. package/src/pages/PeoplePage.tsx +145 -0
  654. package/src/pages/PersonDetail.tsx +526 -0
  655. package/src/pages/PlanningPage.tsx +318 -0
  656. package/src/pages/RaiseDetail.tsx +498 -0
  657. package/src/pages/RaiseStageSettingsPage.tsx +63 -0
  658. package/src/pages/RoleDetail.tsx +455 -0
  659. package/src/pages/SearchPage.tsx +135 -0
  660. package/src/pages/account/AccountLayout.tsx +38 -0
  661. package/src/pages/account/Field.tsx +20 -0
  662. package/src/pages/account/PreferencesPage.tsx +224 -0
  663. package/src/pages/account/ProfilePage.tsx +147 -0
  664. package/src/pages/account/SecurityPage.tsx +237 -0
  665. package/src/pages/admin/DataPage.tsx +772 -0
  666. package/src/pages/admin/McpPage.tsx +404 -0
  667. package/src/pages/admin/ModulesPage.tsx +113 -0
  668. package/src/pages/admin/TeamPage.tsx +386 -0
  669. package/src/pages/admin/WebhookDeliveries.tsx +184 -0
  670. package/src/pages/admin/WebhooksPage.tsx +443 -0
  671. package/src/pages/admin/WorkspaceSettingsPage.tsx +274 -0
  672. package/src/pages/auth/AuthForm.tsx +118 -0
  673. package/src/pages/auth/AuthLayout.tsx +74 -0
  674. package/src/pages/auth/ForgotPasswordPage.tsx +80 -0
  675. package/src/pages/auth/JoinPage.tsx +85 -0
  676. package/src/pages/auth/ResetPasswordPage.tsx +124 -0
  677. package/src/pages/auth/SignInPage.tsx +83 -0
  678. package/src/pages/auth/SignUpPage.tsx +93 -0
  679. package/src/pages/candidateFields.tsx +175 -0
  680. package/src/pages/dashboard/DashboardPage.tsx +108 -0
  681. package/src/pages/dashboard/attention.ts +206 -0
  682. package/src/pages/dashboard/sections.tsx +269 -0
  683. package/src/pages/fields.ts +21 -0
  684. package/src/pages/forms/EmbedPanel.tsx +95 -0
  685. package/src/pages/forms/FieldsEditor.tsx +412 -0
  686. package/src/pages/forms/FormSettings.tsx +159 -0
  687. package/src/pages/forms/SubmissionsTable.tsx +171 -0
  688. package/src/pages/forms/fieldList.ts +209 -0
  689. package/src/pages/forms/template.ts +37 -0
  690. package/src/pages/handbook/HandbookPage.tsx +500 -0
  691. package/src/pages/handbook/tree.ts +327 -0
  692. package/src/pages/hiringDirectory.ts +146 -0
  693. package/src/pages/onboarding/HandbookStepPage.tsx +75 -0
  694. package/src/pages/onboarding/InvitesStepPage.tsx +180 -0
  695. package/src/pages/onboarding/WorkspaceStepPage.tsx +122 -0
  696. package/src/pages/positionDirectory.ts +98 -0
  697. package/src/registry/UiExtensionProvider.tsx +20 -0
  698. package/src/registry/context.ts +67 -0
  699. package/src/registry/contributions.ts +89 -0
  700. package/src/registry/overridable.ts +62 -0
  701. package/src/registry/registry.ts +204 -0
  702. package/src/styles.css +175 -0
  703. package/src/testing/inputs.ts +18 -0
  704. package/src/testing/stubClient.ts +78 -0
@@ -0,0 +1,772 @@
1
+ import {
2
+ CONFLICT_MODE_LABELS,
3
+ IMPORT_OBJECTS,
4
+ IMPORT_SOURCES,
5
+ MATCH_KEYS,
6
+ OBJECT_COLUMNS,
7
+ OBJECT_LABELS,
8
+ SOURCE_LABELS,
9
+ defaultMatchKeyId,
10
+ findMatchKey,
11
+ isImportJobSettled,
12
+ requiredColumns,
13
+ } from '@kelpie/schemas'
14
+ import type {
15
+ ImportColumnMap,
16
+ ImportConflictMode,
17
+ ImportJob,
18
+ ImportObject,
19
+ ImportPreviewRow,
20
+ ImportRowAction,
21
+ ImportSource,
22
+ } from '@kelpie/schemas'
23
+ import { useState } from 'react'
24
+ import type { ChangeEvent, FormEvent, ReactNode } from 'react'
25
+
26
+ import {
27
+ saveCsv,
28
+ useCommitImportJob,
29
+ useCreateImportJob,
30
+ useDeleteImportJob,
31
+ useExportCsv,
32
+ useImportJob,
33
+ } from '../../api/resources/importJobs.ts'
34
+ import { PageHeader } from '../../components/PageHeader.tsx'
35
+
36
+ /**
37
+ * Import and export, ported from the mockup's `/admin/data`.
38
+ *
39
+ * The wizard is the mockup's four steps, with the parsing moved to the server.
40
+ * The mockup read the CSV in the browser to fill its mapping table; here the
41
+ * upload is the first dry run, and the job comes back carrying the file's own
42
+ * headers and the map the server derived. Correcting a mapping re-uploads the
43
+ * same file, which is why the `File` stays in state after the first request.
44
+ *
45
+ * Narrowed from the mockup: no "Load sample" button. It fetched fixtures out of
46
+ * `public/fixtures`, and none of those are part of the application.
47
+ */
48
+
49
+ type WizardStep = 'source' | 'upload' | 'map' | 'result'
50
+
51
+ const STEPS: readonly { readonly id: WizardStep; readonly label: string }[] = [
52
+ { id: 'source', label: 'Source' },
53
+ { id: 'upload', label: 'Upload' },
54
+ { id: 'map', label: 'Map' },
55
+ { id: 'result', label: 'Dry-run' },
56
+ ]
57
+
58
+ export function DataPage(): ReactNode {
59
+ const [exportObject, setExportObject] = useState<ImportObject>('companies')
60
+
61
+ const [step, setStep] = useState<WizardStep>('source')
62
+ const [source, setSource] = useState<ImportSource>('custom')
63
+ const [object, setObject] = useState<ImportObject>('companies')
64
+ const [file, setFile] = useState<File | null>(null)
65
+ const [columnMap, setColumnMap] = useState<ImportColumnMap>({})
66
+ const [conflictMode, setConflictMode] = useState<ImportConflictMode>('skip')
67
+ const [matchKeyId, setMatchKeyId] = useState<string>(() => defaultMatchKeyId('companies'))
68
+ const [jobId, setJobId] = useState<string | undefined>(undefined)
69
+ const [problem, setProblem] = useState<string | null>(null)
70
+
71
+ const exportCsv = useExportCsv()
72
+ const createJob = useCreateImportJob()
73
+ const commitJob = useCommitImportJob()
74
+ const deleteJob = useDeleteImportJob()
75
+ const watched = useImportJob(jobId)
76
+ const job = watched.record
77
+
78
+ const failure = problem ?? messageOf(exportCsv.error ?? createJob.error ?? commitJob.error ?? watched.error)
79
+
80
+ /**
81
+ * Drops a job the wizard is walking away from, so the file stored against it
82
+ * goes too.
83
+ *
84
+ * Every path out of a job leads here, because each one is the same thing: a
85
+ * correction that uploads the file again, a step back, or starting over. None
86
+ * of them can reach the old job afterwards.
87
+ *
88
+ * Fire and forget, and `deleteJob.error` is deliberately left out of `failure`
89
+ * above. The caller has already got what they asked for, and tidying up is not
90
+ * theirs to fix. The one refusal the server makes is a job still committing,
91
+ * and that one settles on its own.
92
+ */
93
+ function discardJob(id: string | undefined): void {
94
+ if (id !== undefined) {
95
+ deleteJob.run(id)
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Starts a step that talks to the API and drops its rejection.
101
+ *
102
+ * These steps use `runAsync` because they chain: a refused mapping must not
103
+ * advance the wizard, and only a rejection stops the lines after the await.
104
+ * The failure is already on screen through `failure` above, so the rejection
105
+ * has nothing left to say, and discarding it with a bare `void` would leave an
106
+ * unhandled rejection behind every refused upload.
107
+ */
108
+ function runStep(work: Promise<unknown>): void {
109
+ void work.catch(() => undefined)
110
+ }
111
+
112
+ function resetWizard(): void {
113
+ discardJob(jobId)
114
+ setStep('source')
115
+ setFile(null)
116
+ setColumnMap({})
117
+ setConflictMode('skip')
118
+ setMatchKeyId(defaultMatchKeyId(object))
119
+ setJobId(undefined)
120
+ setProblem(null)
121
+ }
122
+
123
+ async function onDownload(target: ImportObject, template: boolean): Promise<void> {
124
+ setProblem(null)
125
+ saveCsv(await exportCsv.runAsync({ object: target, template }))
126
+ }
127
+
128
+ /** Uploads the file and reads back the headers, the derived map, and the forecast. */
129
+ async function runDryRun(next: File, map: ImportColumnMap | undefined): Promise<ImportJob> {
130
+ const superseded = jobId
131
+ const created = await createJob.runAsync({
132
+ file: next,
133
+ source,
134
+ object,
135
+ conflictMode,
136
+ matchKeyId,
137
+ ...(map === undefined ? {} : { columnMap: map }),
138
+ })
139
+
140
+ setJobId(created.id)
141
+
142
+ // After the create rather than before it. A map the server rejects throws
143
+ // out of `runAsync`, and the caller is still reading the old job's headers
144
+ // in the mapping table it has to correct.
145
+ discardJob(superseded)
146
+
147
+ return created
148
+ }
149
+
150
+ async function onFileChange(event: ChangeEvent<HTMLInputElement>): Promise<void> {
151
+ const chosen = event.target.files?.[0]
152
+
153
+ // Cleared so choosing the same file twice fires the event again, which is
154
+ // what a caller who fixed the file on disk expects.
155
+ event.target.value = ''
156
+
157
+ if (chosen === undefined) {
158
+ return
159
+ }
160
+
161
+ setProblem(null)
162
+ setFile(chosen)
163
+
164
+ const created = await runDryRun(chosen, undefined)
165
+
166
+ // The first upload takes the server's own reading of the file, which is what
167
+ // the mapping table is then built from.
168
+ setColumnMap(created.columnMap)
169
+ setMatchKeyId(created.matchKey)
170
+ setStep('map')
171
+ }
172
+
173
+ async function onRerun(event: FormEvent): Promise<void> {
174
+ event.preventDefault()
175
+
176
+ if (file === null) {
177
+ return
178
+ }
179
+
180
+ const matchKey = findMatchKey(object, matchKeyId)
181
+ const missing =
182
+ matchKey === undefined
183
+ ? []
184
+ : requiredColumns(object, matchKey).filter((column) => (columnMap[column] ?? null) === null)
185
+
186
+ if (missing.length > 0) {
187
+ setProblem(`Map these columns first: ${missing.join(', ')}`)
188
+ return
189
+ }
190
+
191
+ setProblem(null)
192
+ await runDryRun(file, columnMap)
193
+ setStep('result')
194
+ }
195
+
196
+ function onCommit(): void {
197
+ // The file goes back with the commit: the server kept its digest, not its
198
+ // bytes. This is the same `File` every dry run was run against.
199
+ if (jobId === undefined || file === null) {
200
+ return
201
+ }
202
+
203
+ setProblem(null)
204
+ commitJob.run({ id: jobId, file })
205
+ }
206
+
207
+ return (
208
+ <div className="animate-slide-in space-y-10">
209
+ <PageHeader
210
+ title="Import & export"
211
+ description="CSV for People, Companies, Positions, and Deals. Custom files or HubSpot / Salesforce packs."
212
+ />
213
+
214
+ {failure === null ? null : (
215
+ <p role="alert" className="text-[12px] font-medium text-danger">
216
+ {failure}
217
+ </p>
218
+ )}
219
+
220
+ <section className="space-y-4">
221
+ <div>
222
+ <h2 className="text-[15px] font-semibold text-ink">Export</h2>
223
+ <p className="mt-1 text-[13px] text-ink-muted">
224
+ Download this workspace as CSV. A Kelpie export reads straight back in with no column
225
+ mapping.
226
+ </p>
227
+ </div>
228
+ <div className="flex flex-wrap items-end gap-3">
229
+ <Field label="Object">
230
+ <select
231
+ value={exportObject}
232
+ onChange={(event) => setExportObject(event.target.value as ImportObject)}
233
+ className="w-48 rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent"
234
+ >
235
+ {IMPORT_OBJECTS.map((item) => (
236
+ <option key={item} value={item}>
237
+ {OBJECT_LABELS[item]}
238
+ </option>
239
+ ))}
240
+ </select>
241
+ </Field>
242
+ <button
243
+ type="button"
244
+ onClick={() => runStep(onDownload(exportObject, false))}
245
+ disabled={exportCsv.isPending}
246
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover disabled:opacity-50"
247
+ >
248
+ {exportCsv.isPending ? 'Preparing…' : 'Download CSV'}
249
+ </button>
250
+ </div>
251
+ </section>
252
+
253
+ <section className="space-y-4 border-t border-border pt-8">
254
+ <div className="flex items-start justify-between gap-3">
255
+ <div>
256
+ <h2 className="text-[15px] font-semibold text-ink">Import</h2>
257
+ <p className="mt-1 text-[13px] text-ink-muted">
258
+ Custom CSV or HubSpot / Salesforce export packs. Every job is dry-run first; nothing is
259
+ written until you commit.
260
+ </p>
261
+ </div>
262
+ {step === 'source' ? null : (
263
+ <button
264
+ type="button"
265
+ onClick={resetWizard}
266
+ className="shrink-0 text-[12px] font-medium text-ink-muted hover:text-ink"
267
+ >
268
+ Start over
269
+ </button>
270
+ )}
271
+ </div>
272
+
273
+ <StepIndicator step={step} />
274
+
275
+ {step === 'source' ? (
276
+ <form
277
+ className="space-y-4"
278
+ onSubmit={(event) => {
279
+ event.preventDefault()
280
+ setStep('upload')
281
+ }}
282
+ >
283
+ <Field label="Source">
284
+ <select
285
+ value={source}
286
+ onChange={(event) => setSource(event.target.value as ImportSource)}
287
+ className="w-full max-w-md rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent"
288
+ >
289
+ {IMPORT_SOURCES.map((item) => (
290
+ <option key={item} value={item}>
291
+ {SOURCE_LABELS[item]}
292
+ </option>
293
+ ))}
294
+ </select>
295
+ </Field>
296
+ <Field label="Object">
297
+ <select
298
+ value={object}
299
+ onChange={(event) => {
300
+ const next = event.target.value as ImportObject
301
+
302
+ setObject(next)
303
+ setMatchKeyId(defaultMatchKeyId(next))
304
+ }}
305
+ className="w-full max-w-md rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent"
306
+ >
307
+ {IMPORT_OBJECTS.map((item) => (
308
+ <option key={item} value={item}>
309
+ {OBJECT_LABELS[item]}
310
+ </option>
311
+ ))}
312
+ </select>
313
+ </Field>
314
+ <div className="rounded-md border border-border px-4 py-3">
315
+ <div className="text-[13px] font-medium text-ink">Need a blank file?</div>
316
+ <p className="mt-0.5 text-[12px] text-ink-muted">
317
+ Download a Kelpie CSV template for {OBJECT_LABELS[object].toLowerCase()}, fill it in,
318
+ then continue.
319
+ </p>
320
+ <button
321
+ type="button"
322
+ onClick={() => runStep(onDownload(object, true))}
323
+ className="mt-3 rounded-md border border-border bg-surface px-3.5 py-2 text-[12px] font-semibold text-ink transition hover:border-accent"
324
+ >
325
+ Download template
326
+ </button>
327
+ </div>
328
+ <p className="text-[12px] text-ink-muted">
329
+ Import order for linked data: Companies → People → Positions → Deals.
330
+ </p>
331
+ <button
332
+ type="submit"
333
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover"
334
+ >
335
+ Continue
336
+ </button>
337
+ </form>
338
+ ) : null}
339
+
340
+ {step === 'upload' ? (
341
+ <div className="space-y-4">
342
+ <p className="text-[13px] text-ink-muted">
343
+ {SOURCE_LABELS[source]} → {OBJECT_LABELS[object]}
344
+ </p>
345
+ <label className="flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-border bg-surface-raised px-6 py-10 transition hover:border-accent">
346
+ <span className="text-[13px] font-medium text-ink">
347
+ {createJob.isPending ? 'Reading the file…' : 'Drop a CSV here or click to browse'}
348
+ </span>
349
+ <span className="mt-1 text-[12px] text-ink-muted">
350
+ UTF-8 CSV · max 10 MB · max 10,000 rows
351
+ </span>
352
+ <input
353
+ type="file"
354
+ accept=".csv,text/csv"
355
+ className="sr-only"
356
+ disabled={createJob.isPending}
357
+ onChange={(event) => runStep(onFileChange(event))}
358
+ />
359
+ </label>
360
+ <button
361
+ type="button"
362
+ onClick={() => setStep('source')}
363
+ className="rounded-md px-3 py-2 text-[12px] font-medium text-ink-muted hover:text-ink"
364
+ >
365
+ Back
366
+ </button>
367
+ </div>
368
+ ) : null}
369
+
370
+ {step === 'map' && job !== undefined ? (
371
+ <MappingForm
372
+ job={job}
373
+ object={object}
374
+ columnMap={columnMap}
375
+ conflictMode={conflictMode}
376
+ matchKeyId={matchKeyId}
377
+ isPending={createJob.isPending}
378
+ onColumnMapChange={setColumnMap}
379
+ onConflictModeChange={setConflictMode}
380
+ onMatchKeyChange={setMatchKeyId}
381
+ onSubmit={(event) => runStep(onRerun(event))}
382
+ onBack={() => {
383
+ discardJob(jobId)
384
+ setFile(null)
385
+ setJobId(undefined)
386
+ setStep('upload')
387
+ }}
388
+ />
389
+ ) : null}
390
+
391
+ {step === 'result' && job !== undefined ? (
392
+ <ResultPanel
393
+ job={job}
394
+ object={object}
395
+ isCommitting={commitJob.isPending}
396
+ onCommit={onCommit}
397
+ onBack={() => setStep('map')}
398
+ />
399
+ ) : null}
400
+ </section>
401
+ </div>
402
+ )
403
+ }
404
+
405
+ interface MappingFormProps {
406
+ readonly job: ImportJob
407
+ readonly object: ImportObject
408
+ readonly columnMap: ImportColumnMap
409
+ readonly conflictMode: ImportConflictMode
410
+ readonly matchKeyId: string
411
+ readonly isPending: boolean
412
+ readonly onColumnMapChange: (map: ImportColumnMap) => void
413
+ readonly onConflictModeChange: (mode: ImportConflictMode) => void
414
+ readonly onMatchKeyChange: (id: string) => void
415
+ readonly onSubmit: (event: FormEvent) => void
416
+ readonly onBack: () => void
417
+ }
418
+
419
+ function MappingForm(props: MappingFormProps): ReactNode {
420
+ const matchKey = findMatchKey(props.object, props.matchKeyId)
421
+ const keyColumns = new Set(matchKey?.columns ?? [])
422
+
423
+ return (
424
+ <form className="space-y-4" onSubmit={props.onSubmit}>
425
+ <p className="text-[13px] text-ink-muted">
426
+ Mapping <span className="font-medium text-ink">{props.job.fileName ?? 'the file'}</span> (
427
+ {props.job.counts.total} rows). Match Kelpie fields to CSV columns.
428
+ </p>
429
+ <div className="grid gap-3 sm:grid-cols-2">
430
+ <Field label="On match">
431
+ <select
432
+ value={props.conflictMode}
433
+ onChange={(event) => props.onConflictModeChange(event.target.value as ImportConflictMode)}
434
+ className="w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent"
435
+ >
436
+ {Object.entries(CONFLICT_MODE_LABELS).map(([mode, label]) => (
437
+ <option key={mode} value={mode}>
438
+ {label}
439
+ </option>
440
+ ))}
441
+ </select>
442
+ </Field>
443
+ <Field label="Key column">
444
+ <select
445
+ value={props.matchKeyId}
446
+ onChange={(event) => props.onMatchKeyChange(event.target.value)}
447
+ className="w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent"
448
+ >
449
+ {MATCH_KEYS[props.object].map((key) => (
450
+ <option key={key.id} value={key.id}>
451
+ {key.label}
452
+ </option>
453
+ ))}
454
+ </select>
455
+ </Field>
456
+ </div>
457
+ <p className="text-[12px] text-ink-muted">
458
+ {props.conflictMode === 'update'
459
+ ? 'Rows matching an existing record on the key column overwrite the fields you map below. A blank cell changes nothing.'
460
+ : 'Rows matching an existing record on the key column are skipped.'}{' '}
461
+ Key columns must be mapped.
462
+ </p>
463
+ <div className="overflow-hidden rounded-md border border-border">
464
+ <table className="w-full text-left text-[13px]">
465
+ <thead>
466
+ <tr className="border-b border-border bg-surface text-[11px] font-semibold tracking-wide text-ink-muted uppercase">
467
+ <th className="px-4 py-2.5">Kelpie field</th>
468
+ <th className="px-4 py-2.5">CSV column</th>
469
+ </tr>
470
+ </thead>
471
+ <tbody>
472
+ {OBJECT_COLUMNS[props.object].map((column) => {
473
+ const isKey = keyColumns.has(column.key)
474
+
475
+ return (
476
+ <tr key={column.key} className="border-b border-border last:border-0">
477
+ <td className="px-4 py-2.5">
478
+ <span className="font-medium">{column.label}</span>
479
+ <span className="ml-2 font-mono text-[11px] text-ink-faint">{column.key}</span>
480
+ {column.required || isKey ? (
481
+ <span className="ml-2 text-[11px] text-danger">
482
+ {isKey ? 'key' : 'required'}
483
+ </span>
484
+ ) : null}
485
+ </td>
486
+ <td className="px-4 py-2.5">
487
+ <select
488
+ value={props.columnMap[column.key] ?? ''}
489
+ onChange={(event) =>
490
+ props.onColumnMapChange({
491
+ ...props.columnMap,
492
+ [column.key]: event.target.value === '' ? null : event.target.value,
493
+ })
494
+ }
495
+ className="w-full max-w-xs rounded-md border border-border bg-surface px-2 py-1.5 text-[13px] outline-none focus:border-accent"
496
+ >
497
+ <option value="">— ignore —</option>
498
+ {props.job.sourceHeaders.map((header) => (
499
+ <option key={header} value={header}>
500
+ {header}
501
+ </option>
502
+ ))}
503
+ </select>
504
+ </td>
505
+ </tr>
506
+ )
507
+ })}
508
+ </tbody>
509
+ </table>
510
+ </div>
511
+ <div className="flex flex-wrap gap-3">
512
+ <button
513
+ type="submit"
514
+ disabled={props.isPending}
515
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover disabled:opacity-50"
516
+ >
517
+ {props.isPending ? 'Running…' : 'Run dry-run'}
518
+ </button>
519
+ <button
520
+ type="button"
521
+ onClick={props.onBack}
522
+ className="rounded-md px-3 py-2 text-[12px] font-medium text-ink-muted hover:text-ink"
523
+ >
524
+ Back
525
+ </button>
526
+ </div>
527
+ </form>
528
+ )
529
+ }
530
+
531
+ interface ResultPanelProps {
532
+ readonly job: ImportJob
533
+ readonly object: ImportObject
534
+ readonly isCommitting: boolean
535
+ readonly onCommit: () => void
536
+ readonly onBack: () => void
537
+ }
538
+
539
+ function ResultPanel({ job, object, isCommitting, onCommit, onBack }: ResultPanelProps): ReactNode {
540
+ const working = !isImportJobSettled(job.status)
541
+ const done = job.status === 'completed'
542
+ const nothingToDo = job.counts.create === 0 && job.counts.update === 0
543
+
544
+ return (
545
+ <div className="space-y-4">
546
+ <p className="text-[13px] text-ink-muted">
547
+ Job <span className="font-mono text-[12px] text-ink">{job.id}</span> · status {job.status}
548
+ {working ? ' · working…' : null}
549
+ </p>
550
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-5">
551
+ <Stat label="Total" value={job.counts.total} />
552
+ <Stat label="Create" value={job.counts.create} />
553
+ <Stat label="Update" value={job.counts.update} />
554
+ <Stat label="Skip" value={job.counts.skip} />
555
+ <Stat label="Error" value={job.counts.error} danger />
556
+ </div>
557
+ <p className="text-[12px] text-ink-muted">
558
+ On match: {CONFLICT_MODE_LABELS[job.conflictMode].toLowerCase()}. Key column:{' '}
559
+ <span className="font-mono text-[11px] text-ink">
560
+ {findMatchKey(object, job.matchKey)?.label ?? job.matchKey}
561
+ </span>
562
+ </p>
563
+
564
+ {job.preview.length > 0 ? (
565
+ <div className="space-y-2">
566
+ <div>
567
+ <h3 className="text-[13px] font-semibold text-ink">Mapping preview</h3>
568
+ <p className="mt-0.5 text-[12px] text-ink-muted">
569
+ The first {job.preview.length} row{job.preview.length === 1 ? '' : 's'} as Kelpie read
570
+ them. Go back to mapping if a column looks wrong.
571
+ </p>
572
+ </div>
573
+ <PreviewTable object={object} rows={job.preview} />
574
+ </div>
575
+ ) : null}
576
+
577
+ {job.errors.length > 0 ? (
578
+ <div className="space-y-2">
579
+ <div className="overflow-hidden rounded-md border border-border">
580
+ <table className="w-full text-left text-[13px]">
581
+ <thead>
582
+ <tr className="border-b border-border bg-surface text-[11px] font-semibold tracking-wide text-ink-muted uppercase">
583
+ <th className="px-4 py-2.5">Row</th>
584
+ <th className="px-4 py-2.5">Field</th>
585
+ <th className="px-4 py-2.5">Message</th>
586
+ </tr>
587
+ </thead>
588
+ <tbody>
589
+ {job.errors.map((error, index) => (
590
+ <tr
591
+ key={`${String(error.row)}-${error.field}-${String(index)}`}
592
+ className="border-b border-border last:border-0"
593
+ >
594
+ <td className="px-4 py-2.5 font-mono text-[12px]">{error.row}</td>
595
+ <td className="px-4 py-2.5 font-mono text-[12px]">{error.field}</td>
596
+ <td className="px-4 py-2.5 text-ink-muted">{error.message}</td>
597
+ </tr>
598
+ ))}
599
+ </tbody>
600
+ </table>
601
+ </div>
602
+ {job.counts.error > job.errors.length ? (
603
+ <p className="text-[12px] text-ink-muted">
604
+ Showing {job.errors.length} of {job.counts.error} failing rows.
605
+ </p>
606
+ ) : null}
607
+ </div>
608
+ ) : (
609
+ <p className="text-[13px] text-ink-muted">No row errors.</p>
610
+ )}
611
+
612
+ <div className="flex flex-wrap gap-3">
613
+ <button
614
+ type="button"
615
+ onClick={onCommit}
616
+ disabled={done || working || isCommitting || nothingToDo}
617
+ className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover disabled:cursor-not-allowed disabled:opacity-50"
618
+ >
619
+ {done ? 'Committed' : isCommitting || working ? 'Working…' : 'Commit import'}
620
+ </button>
621
+ <button
622
+ type="button"
623
+ onClick={onBack}
624
+ className="rounded-md px-3 py-2 text-[12px] font-medium text-ink-muted hover:text-ink"
625
+ >
626
+ Back to mapping
627
+ </button>
628
+ </div>
629
+ </div>
630
+ )
631
+ }
632
+
633
+ function PreviewTable({
634
+ object,
635
+ rows,
636
+ }: {
637
+ readonly object: ImportObject
638
+ readonly rows: readonly ImportPreviewRow[]
639
+ }): ReactNode {
640
+ const keys = OBJECT_COLUMNS[object]
641
+ .map((column) => column.key)
642
+ .filter((key) => rows.some((row) => key in row.values))
643
+
644
+ return (
645
+ <div className="overflow-x-auto rounded-md border border-border">
646
+ <table className="w-full min-w-[36rem] text-left text-[13px]">
647
+ <thead>
648
+ <tr className="border-b border-border bg-surface text-[11px] font-semibold tracking-wide text-ink-muted uppercase">
649
+ <th className="px-3 py-2.5">Row</th>
650
+ <th className="px-3 py-2.5">Action</th>
651
+ {keys.map((key) => (
652
+ <th key={key} className="px-3 py-2.5 font-mono normal-case">
653
+ {key}
654
+ </th>
655
+ ))}
656
+ </tr>
657
+ </thead>
658
+ <tbody>
659
+ {rows.map((row) => (
660
+ <tr key={row.row} className="border-b border-border last:border-0">
661
+ <td className="px-3 py-2.5 font-mono text-[12px] text-ink-muted">{row.row}</td>
662
+ <td className="px-3 py-2.5">
663
+ <ActionChip action={row.action} />
664
+ </td>
665
+ {keys.map((key) => (
666
+ <td
667
+ key={key}
668
+ className="max-w-[12rem] truncate px-3 py-2.5 text-ink"
669
+ title={row.values[key] ?? ''}
670
+ >
671
+ {row.values[key] === undefined || row.values[key] === '' ? (
672
+ <span className="text-ink-faint">—</span>
673
+ ) : (
674
+ row.values[key]
675
+ )}
676
+ </td>
677
+ ))}
678
+ </tr>
679
+ ))}
680
+ </tbody>
681
+ </table>
682
+ </div>
683
+ )
684
+ }
685
+
686
+ const ACTION_STYLES: Readonly<Record<ImportRowAction, string>> = {
687
+ pending: 'bg-surface text-ink-faint',
688
+ create: 'bg-accent/15 text-accent-hover',
689
+ update: 'bg-warning-soft text-warning',
690
+ skip: 'bg-surface text-ink-muted',
691
+ error: 'bg-danger-soft text-danger',
692
+ }
693
+
694
+ function ActionChip({ action }: { readonly action: ImportRowAction }): ReactNode {
695
+ return (
696
+ <span
697
+ className={[
698
+ 'inline-block rounded px-1.5 py-0.5 text-[11px] font-semibold uppercase',
699
+ ACTION_STYLES[action],
700
+ ].join(' ')}
701
+ >
702
+ {action}
703
+ </span>
704
+ )
705
+ }
706
+
707
+ function StepIndicator({ step }: { readonly step: WizardStep }): ReactNode {
708
+ const active = STEPS.findIndex((item) => item.id === step)
709
+
710
+ return (
711
+ <ol className="flex flex-wrap gap-2 text-[12px]">
712
+ {STEPS.map((item, index) => (
713
+ <li
714
+ key={item.id}
715
+ className={[
716
+ 'rounded-md px-2.5 py-1 font-medium',
717
+ index === active
718
+ ? 'bg-accent/15 text-accent-hover'
719
+ : index < active
720
+ ? 'text-ink'
721
+ : 'text-ink-faint',
722
+ ].join(' ')}
723
+ >
724
+ {index + 1}. {item.label}
725
+ </li>
726
+ ))}
727
+ </ol>
728
+ )
729
+ }
730
+
731
+ function Stat({
732
+ label,
733
+ value,
734
+ danger,
735
+ }: {
736
+ readonly label: string
737
+ readonly value: number
738
+ readonly danger?: boolean
739
+ }): ReactNode {
740
+ return (
741
+ <div className="rounded-md border border-border px-3 py-2.5">
742
+ <div className="text-[11px] font-semibold tracking-wide text-ink-muted uppercase">{label}</div>
743
+ <div
744
+ className={[
745
+ 'mt-1 text-[20px] font-semibold tabular-nums',
746
+ danger === true && value > 0 ? 'text-danger' : 'text-ink',
747
+ ].join(' ')}
748
+ >
749
+ {value}
750
+ </div>
751
+ </div>
752
+ )
753
+ }
754
+
755
+ function Field({
756
+ label,
757
+ children,
758
+ }: {
759
+ readonly label: string
760
+ readonly children: ReactNode
761
+ }): ReactNode {
762
+ return (
763
+ <label className="block">
764
+ <span className="mb-1.5 block text-[12px] font-medium text-ink">{label}</span>
765
+ {children}
766
+ </label>
767
+ )
768
+ }
769
+
770
+ function messageOf(error: Error | null | undefined): string | null {
771
+ return error === null || error === undefined ? null : error.message
772
+ }