@kelpie/ui 0.7.0 → 0.9.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 (349) hide show
  1. package/dist/api/resource.d.ts +59 -6
  2. package/dist/api/resource.d.ts.map +1 -1
  3. package/dist/api/resource.js +106 -24
  4. package/dist/api/resource.js.map +1 -1
  5. package/dist/api/resources/companies.d.ts +2 -0
  6. package/dist/api/resources/companies.d.ts.map +1 -1
  7. package/dist/api/resources/companies.js +5 -1
  8. package/dist/api/resources/companies.js.map +1 -1
  9. package/dist/api/resources/consentPurposes.d.ts +13 -0
  10. package/dist/api/resources/consentPurposes.d.ts.map +1 -0
  11. package/dist/api/resources/consentPurposes.js +39 -0
  12. package/dist/api/resources/consentPurposes.js.map +1 -0
  13. package/dist/api/resources/customFields.d.ts +15 -0
  14. package/dist/api/resources/customFields.d.ts.map +1 -0
  15. package/dist/api/resources/customFields.js +41 -0
  16. package/dist/api/resources/customFields.js.map +1 -0
  17. package/dist/api/resources/decisions.d.ts +2 -0
  18. package/dist/api/resources/decisions.d.ts.map +1 -1
  19. package/dist/api/resources/decisions.js +1 -0
  20. package/dist/api/resources/decisions.js.map +1 -1
  21. package/dist/api/resources/emailDomainLinker.d.ts +16 -0
  22. package/dist/api/resources/emailDomainLinker.d.ts.map +1 -0
  23. package/dist/api/resources/emailDomainLinker.js +16 -0
  24. package/dist/api/resources/emailDomainLinker.js.map +1 -0
  25. package/dist/api/resources/enquiries.d.ts +39 -0
  26. package/dist/api/resources/enquiries.d.ts.map +1 -0
  27. package/dist/api/resources/enquiries.js +68 -0
  28. package/dist/api/resources/enquiries.js.map +1 -0
  29. package/dist/api/resources/forms.d.ts +21 -1
  30. package/dist/api/resources/forms.d.ts.map +1 -1
  31. package/dist/api/resources/forms.js +44 -17
  32. package/dist/api/resources/forms.js.map +1 -1
  33. package/dist/api/resources/handbookPages.d.ts +1 -1
  34. package/dist/api/resources/handbookPages.js +1 -1
  35. package/dist/api/resources/importJobs.d.ts +5 -0
  36. package/dist/api/resources/importJobs.d.ts.map +1 -1
  37. package/dist/api/resources/importJobs.js +3 -0
  38. package/dist/api/resources/importJobs.js.map +1 -1
  39. package/dist/api/resources/listMembers.d.ts +27 -0
  40. package/dist/api/resources/listMembers.d.ts.map +1 -0
  41. package/dist/api/resources/listMembers.js +108 -0
  42. package/dist/api/resources/listMembers.js.map +1 -0
  43. package/dist/api/resources/lists.d.ts +14 -0
  44. package/dist/api/resources/lists.d.ts.map +1 -0
  45. package/dist/api/resources/lists.js +34 -0
  46. package/dist/api/resources/lists.js.map +1 -0
  47. package/dist/api/resources/opportunities.d.ts +2 -0
  48. package/dist/api/resources/opportunities.d.ts.map +1 -1
  49. package/dist/api/resources/opportunities.js +1 -0
  50. package/dist/api/resources/opportunities.js.map +1 -1
  51. package/dist/api/resources/people.d.ts.map +1 -1
  52. package/dist/api/resources/people.js +4 -1
  53. package/dist/api/resources/people.js.map +1 -1
  54. package/dist/api/resources/roles.d.ts +2 -0
  55. package/dist/api/resources/roles.d.ts.map +1 -1
  56. package/dist/api/resources/roles.js +1 -1
  57. package/dist/api/resources/roles.js.map +1 -1
  58. package/dist/api/resources/sampleData.d.ts +17 -0
  59. package/dist/api/resources/sampleData.d.ts.map +1 -0
  60. package/dist/api/resources/sampleData.js +31 -0
  61. package/dist/api/resources/sampleData.js.map +1 -0
  62. package/dist/api/resources/webhooks.d.ts.map +1 -1
  63. package/dist/api/resources/webhooks.js +7 -22
  64. package/dist/api/resources/webhooks.js.map +1 -1
  65. package/dist/app/KelpieApp.d.ts.map +1 -1
  66. package/dist/app/KelpieApp.js +9 -1
  67. package/dist/app/KelpieApp.js.map +1 -1
  68. package/dist/components/ActivitiesPanel.d.ts.map +1 -1
  69. package/dist/components/ActivitiesPanel.js +3 -1
  70. package/dist/components/ActivitiesPanel.js.map +1 -1
  71. package/dist/components/ColumnPicker.d.ts +24 -0
  72. package/dist/components/ColumnPicker.d.ts.map +1 -0
  73. package/dist/components/ColumnPicker.js +56 -0
  74. package/dist/components/ColumnPicker.js.map +1 -0
  75. package/dist/components/CustomFieldsPanel.d.ts +23 -0
  76. package/dist/components/CustomFieldsPanel.d.ts.map +1 -0
  77. package/dist/components/CustomFieldsPanel.js +116 -0
  78. package/dist/components/CustomFieldsPanel.js.map +1 -0
  79. package/dist/components/DataTable.d.ts +16 -1
  80. package/dist/components/DataTable.d.ts.map +1 -1
  81. package/dist/components/DataTable.js +51 -10
  82. package/dist/components/DataTable.js.map +1 -1
  83. package/dist/components/DecisionsPanel.d.ts.map +1 -1
  84. package/dist/components/DecisionsPanel.js +4 -3
  85. package/dist/components/DecisionsPanel.js.map +1 -1
  86. package/dist/components/EntitySearch.d.ts.map +1 -1
  87. package/dist/components/EntitySearch.js +11 -5
  88. package/dist/components/EntitySearch.js.map +1 -1
  89. package/dist/components/FormsPanel.d.ts +18 -0
  90. package/dist/components/FormsPanel.d.ts.map +1 -0
  91. package/dist/components/FormsPanel.js +37 -0
  92. package/dist/components/FormsPanel.js.map +1 -0
  93. package/dist/components/ListsPanel.d.ts +14 -0
  94. package/dist/components/ListsPanel.d.ts.map +1 -0
  95. package/dist/components/ListsPanel.js +56 -0
  96. package/dist/components/ListsPanel.js.map +1 -0
  97. package/dist/components/NotesPanel.d.ts.map +1 -1
  98. package/dist/components/NotesPanel.js +6 -5
  99. package/dist/components/NotesPanel.js.map +1 -1
  100. package/dist/components/Paginator.d.ts +26 -0
  101. package/dist/components/Paginator.d.ts.map +1 -0
  102. package/dist/components/Paginator.js +16 -0
  103. package/dist/components/Paginator.js.map +1 -0
  104. package/dist/components/PlanAttention.d.ts +3 -2
  105. package/dist/components/PlanAttention.d.ts.map +1 -1
  106. package/dist/components/PlanAttention.js +7 -3
  107. package/dist/components/PlanAttention.js.map +1 -1
  108. package/dist/components/PlanPanel.d.ts.map +1 -1
  109. package/dist/components/PlanPanel.js +2 -1
  110. package/dist/components/PlanPanel.js.map +1 -1
  111. package/dist/components/Shell.d.ts.map +1 -1
  112. package/dist/components/Shell.js +4 -0
  113. package/dist/components/Shell.js.map +1 -1
  114. package/dist/components/useHasCustomFields.d.ts +13 -0
  115. package/dist/components/useHasCustomFields.d.ts.map +1 -0
  116. package/dist/components/useHasCustomFields.js +16 -0
  117. package/dist/components/useHasCustomFields.js.map +1 -0
  118. package/dist/index.d.ts +8 -2
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +4 -1
  121. package/dist/index.js.map +1 -1
  122. package/dist/lib/listView.d.ts +15 -0
  123. package/dist/lib/listView.d.ts.map +1 -0
  124. package/dist/lib/listView.js +66 -0
  125. package/dist/lib/listView.js.map +1 -0
  126. package/dist/lib/sort.d.ts +43 -0
  127. package/dist/lib/sort.d.ts.map +1 -0
  128. package/dist/lib/sort.js +102 -0
  129. package/dist/lib/sort.js.map +1 -0
  130. package/dist/pages/CompaniesPage.d.ts.map +1 -1
  131. package/dist/pages/CompaniesPage.js +88 -3
  132. package/dist/pages/CompaniesPage.js.map +1 -1
  133. package/dist/pages/CompanyDetail.d.ts.map +1 -1
  134. package/dist/pages/CompanyDetail.js +40 -15
  135. package/dist/pages/CompanyDetail.js.map +1 -1
  136. package/dist/pages/DealDetail.d.ts.map +1 -1
  137. package/dist/pages/DealDetail.js +20 -1
  138. package/dist/pages/DealDetail.js.map +1 -1
  139. package/dist/pages/DealsPage.d.ts.map +1 -1
  140. package/dist/pages/DealsPage.js +134 -14
  141. package/dist/pages/DealsPage.js.map +1 -1
  142. package/dist/pages/DecisionsPage.d.ts.map +1 -1
  143. package/dist/pages/DecisionsPage.js +52 -8
  144. package/dist/pages/DecisionsPage.js.map +1 -1
  145. package/dist/pages/EnquiriesPage.d.ts +2 -0
  146. package/dist/pages/EnquiriesPage.d.ts.map +1 -0
  147. package/dist/pages/EnquiriesPage.js +251 -0
  148. package/dist/pages/EnquiriesPage.js.map +1 -0
  149. package/dist/pages/EnquiryDetail.d.ts +2 -0
  150. package/dist/pages/EnquiryDetail.d.ts.map +1 -0
  151. package/dist/pages/EnquiryDetail.js +207 -0
  152. package/dist/pages/EnquiryDetail.js.map +1 -0
  153. package/dist/pages/EnquiryStageSettingsPage.d.ts +3 -0
  154. package/dist/pages/EnquiryStageSettingsPage.d.ts.map +1 -0
  155. package/dist/pages/EnquiryStageSettingsPage.js +34 -0
  156. package/dist/pages/EnquiryStageSettingsPage.js.map +1 -0
  157. package/dist/pages/FormDetail.d.ts.map +1 -1
  158. package/dist/pages/FormDetail.js +19 -1
  159. package/dist/pages/FormDetail.js.map +1 -1
  160. package/dist/pages/FormsPage.d.ts.map +1 -1
  161. package/dist/pages/FormsPage.js +53 -2
  162. package/dist/pages/FormsPage.js.map +1 -1
  163. package/dist/pages/FundraisingPage.d.ts.map +1 -1
  164. package/dist/pages/FundraisingPage.js +116 -14
  165. package/dist/pages/FundraisingPage.js.map +1 -1
  166. package/dist/pages/HiringPage.d.ts.map +1 -1
  167. package/dist/pages/HiringPage.js +27 -4
  168. package/dist/pages/HiringPage.js.map +1 -1
  169. package/dist/pages/ListDetail.d.ts +11 -0
  170. package/dist/pages/ListDetail.d.ts.map +1 -0
  171. package/dist/pages/ListDetail.js +214 -0
  172. package/dist/pages/ListDetail.js.map +1 -0
  173. package/dist/pages/ListsPage.d.ts +9 -0
  174. package/dist/pages/ListsPage.d.ts.map +1 -0
  175. package/dist/pages/ListsPage.js +125 -0
  176. package/dist/pages/ListsPage.js.map +1 -0
  177. package/dist/pages/OpportunitiesPage.d.ts.map +1 -1
  178. package/dist/pages/OpportunitiesPage.js +97 -14
  179. package/dist/pages/OpportunitiesPage.js.map +1 -1
  180. package/dist/pages/OpportunityDetail.d.ts.map +1 -1
  181. package/dist/pages/OpportunityDetail.js +64 -3
  182. package/dist/pages/OpportunityDetail.js.map +1 -1
  183. package/dist/pages/PartnershipDetail.d.ts.map +1 -1
  184. package/dist/pages/PartnershipDetail.js +19 -1
  185. package/dist/pages/PartnershipDetail.js.map +1 -1
  186. package/dist/pages/PartnershipsPage.d.ts.map +1 -1
  187. package/dist/pages/PartnershipsPage.js +111 -14
  188. package/dist/pages/PartnershipsPage.js.map +1 -1
  189. package/dist/pages/PeoplePage.d.ts.map +1 -1
  190. package/dist/pages/PeoplePage.js +119 -3
  191. package/dist/pages/PeoplePage.js.map +1 -1
  192. package/dist/pages/PersonDetail.d.ts.map +1 -1
  193. package/dist/pages/PersonDetail.js +67 -5
  194. package/dist/pages/PersonDetail.js.map +1 -1
  195. package/dist/pages/PlanningPage.d.ts.map +1 -1
  196. package/dist/pages/PlanningPage.js +88 -17
  197. package/dist/pages/PlanningPage.js.map +1 -1
  198. package/dist/pages/RaiseDetail.d.ts.map +1 -1
  199. package/dist/pages/RaiseDetail.js +13 -1
  200. package/dist/pages/RaiseDetail.js.map +1 -1
  201. package/dist/pages/RoleDetail.d.ts.map +1 -1
  202. package/dist/pages/RoleDetail.js +5 -4
  203. package/dist/pages/RoleDetail.js.map +1 -1
  204. package/dist/pages/SearchPage.d.ts.map +1 -1
  205. package/dist/pages/SearchPage.js +1 -0
  206. package/dist/pages/SearchPage.js.map +1 -1
  207. package/dist/pages/admin/DataPage.d.ts.map +1 -1
  208. package/dist/pages/admin/DataPage.js +126 -13
  209. package/dist/pages/admin/DataPage.js.map +1 -1
  210. package/dist/pages/admin/FieldsPage.d.ts +2 -0
  211. package/dist/pages/admin/FieldsPage.d.ts.map +1 -0
  212. package/dist/pages/admin/FieldsPage.js +164 -0
  213. package/dist/pages/admin/FieldsPage.js.map +1 -0
  214. package/dist/pages/admin/PrivacyPage.d.ts +9 -0
  215. package/dist/pages/admin/PrivacyPage.d.ts.map +1 -0
  216. package/dist/pages/admin/PrivacyPage.js +170 -0
  217. package/dist/pages/admin/PrivacyPage.js.map +1 -0
  218. package/dist/pages/admin/WebhookDeliveries.d.ts.map +1 -1
  219. package/dist/pages/admin/WebhookDeliveries.js +3 -2
  220. package/dist/pages/admin/WebhookDeliveries.js.map +1 -1
  221. package/dist/pages/admin/WebhooksPage.d.ts.map +1 -1
  222. package/dist/pages/admin/WebhooksPage.js +3 -2
  223. package/dist/pages/admin/WebhooksPage.js.map +1 -1
  224. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
  225. package/dist/pages/admin/WorkspaceSettingsPage.js +45 -2
  226. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
  227. package/dist/pages/dashboard/attention.d.ts.map +1 -1
  228. package/dist/pages/dashboard/attention.js +3 -0
  229. package/dist/pages/dashboard/attention.js.map +1 -1
  230. package/dist/pages/forms/AddFieldMenu.d.ts +17 -0
  231. package/dist/pages/forms/AddFieldMenu.d.ts.map +1 -0
  232. package/dist/pages/forms/AddFieldMenu.js +43 -0
  233. package/dist/pages/forms/AddFieldMenu.js.map +1 -0
  234. package/dist/pages/forms/EmbedPanel.d.ts +4 -0
  235. package/dist/pages/forms/EmbedPanel.d.ts.map +1 -1
  236. package/dist/pages/forms/EmbedPanel.js +62 -3
  237. package/dist/pages/forms/EmbedPanel.js.map +1 -1
  238. package/dist/pages/forms/FieldsEditor.d.ts.map +1 -1
  239. package/dist/pages/forms/FieldsEditor.js +179 -36
  240. package/dist/pages/forms/FieldsEditor.js.map +1 -1
  241. package/dist/pages/forms/FormActions.d.ts +6 -0
  242. package/dist/pages/forms/FormActions.d.ts.map +1 -0
  243. package/dist/pages/forms/FormActions.js +149 -0
  244. package/dist/pages/forms/FormActions.js.map +1 -0
  245. package/dist/pages/forms/FormSettings.d.ts.map +1 -1
  246. package/dist/pages/forms/FormSettings.js +15 -19
  247. package/dist/pages/forms/FormSettings.js.map +1 -1
  248. package/dist/pages/forms/SubmissionDetail.d.ts +2 -0
  249. package/dist/pages/forms/SubmissionDetail.d.ts.map +1 -0
  250. package/dist/pages/forms/SubmissionDetail.js +76 -0
  251. package/dist/pages/forms/SubmissionDetail.js.map +1 -0
  252. package/dist/pages/forms/SubmissionsTable.d.ts.map +1 -1
  253. package/dist/pages/forms/SubmissionsTable.js +57 -8
  254. package/dist/pages/forms/SubmissionsTable.js.map +1 -1
  255. package/dist/pages/forms/fieldList.d.ts +11 -5
  256. package/dist/pages/forms/fieldList.d.ts.map +1 -1
  257. package/dist/pages/forms/fieldList.js +66 -6
  258. package/dist/pages/forms/fieldList.js.map +1 -1
  259. package/dist/pages/forms/template.d.ts +17 -0
  260. package/dist/pages/forms/template.d.ts.map +1 -1
  261. package/dist/pages/forms/template.js +93 -0
  262. package/dist/pages/forms/template.js.map +1 -1
  263. package/dist/pages/handbook/HandbookPage.js +1 -1
  264. package/dist/pages/hiringDirectory.js +2 -2
  265. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
  266. package/dist/pages/onboarding/WorkspaceStepPage.js +15 -2
  267. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
  268. package/dist/pages/positionDirectory.js +2 -2
  269. package/package.json +2 -2
  270. package/src/api/resource.ts +155 -32
  271. package/src/api/resources/companies.ts +7 -1
  272. package/src/api/resources/consentPurposes.ts +83 -0
  273. package/src/api/resources/customFields.ts +88 -0
  274. package/src/api/resources/decisions.ts +3 -0
  275. package/src/api/resources/emailDomainLinker.ts +43 -0
  276. package/src/api/resources/enquiries.ts +118 -0
  277. package/src/api/resources/forms.ts +59 -19
  278. package/src/api/resources/handbookPages.ts +1 -1
  279. package/src/api/resources/importJobs.ts +9 -0
  280. package/src/api/resources/listMembers.ts +173 -0
  281. package/src/api/resources/lists.ts +61 -0
  282. package/src/api/resources/opportunities.ts +3 -0
  283. package/src/api/resources/people.ts +4 -1
  284. package/src/api/resources/roles.ts +3 -1
  285. package/src/api/resources/sampleData.ts +57 -0
  286. package/src/api/resources/webhooks.ts +8 -24
  287. package/src/app/KelpieApp.tsx +16 -0
  288. package/src/components/ActivitiesPanel.tsx +3 -10
  289. package/src/components/ColumnPicker.tsx +124 -0
  290. package/src/components/CustomFieldsPanel.tsx +330 -0
  291. package/src/components/DataTable.tsx +89 -10
  292. package/src/components/DecisionsPanel.tsx +4 -12
  293. package/src/components/EntitySearch.tsx +12 -5
  294. package/src/components/FormsPanel.tsx +86 -0
  295. package/src/components/ListsPanel.tsx +176 -0
  296. package/src/components/NotesPanel.tsx +6 -14
  297. package/src/components/Paginator.tsx +78 -0
  298. package/src/components/PlanAttention.tsx +20 -3
  299. package/src/components/PlanPanel.tsx +2 -10
  300. package/src/components/Shell.tsx +4 -0
  301. package/src/components/useHasCustomFields.ts +18 -0
  302. package/src/index.ts +10 -1
  303. package/src/lib/listView.ts +149 -0
  304. package/src/lib/sort.ts +143 -0
  305. package/src/pages/CompaniesPage.tsx +125 -11
  306. package/src/pages/CompanyDetail.tsx +98 -63
  307. package/src/pages/DealDetail.tsx +34 -0
  308. package/src/pages/DealsPage.tsx +178 -24
  309. package/src/pages/DecisionsPage.tsx +75 -18
  310. package/src/pages/EnquiriesPage.tsx +434 -0
  311. package/src/pages/EnquiryDetail.tsx +554 -0
  312. package/src/pages/EnquiryStageSettingsPage.tsx +63 -0
  313. package/src/pages/FormDetail.tsx +23 -1
  314. package/src/pages/FormsPage.tsx +81 -17
  315. package/src/pages/FundraisingPage.tsx +152 -24
  316. package/src/pages/HiringPage.tsx +39 -11
  317. package/src/pages/ListDetail.tsx +470 -0
  318. package/src/pages/ListsPage.tsx +281 -0
  319. package/src/pages/OpportunitiesPage.tsx +130 -24
  320. package/src/pages/OpportunityDetail.tsx +168 -2
  321. package/src/pages/PartnershipDetail.tsx +35 -0
  322. package/src/pages/PartnershipsPage.tsx +149 -24
  323. package/src/pages/PeoplePage.tsx +157 -11
  324. package/src/pages/PersonDetail.tsx +157 -6
  325. package/src/pages/PlanningPage.tsx +259 -29
  326. package/src/pages/RaiseDetail.tsx +24 -0
  327. package/src/pages/RoleDetail.tsx +21 -26
  328. package/src/pages/SearchPage.tsx +1 -0
  329. package/src/pages/admin/DataPage.tsx +324 -13
  330. package/src/pages/admin/FieldsPage.tsx +455 -0
  331. package/src/pages/admin/PrivacyPage.tsx +461 -0
  332. package/src/pages/admin/WebhookDeliveries.tsx +10 -15
  333. package/src/pages/admin/WebhooksPage.tsx +14 -19
  334. package/src/pages/admin/WorkspaceSettingsPage.tsx +134 -2
  335. package/src/pages/dashboard/attention.ts +3 -0
  336. package/src/pages/forms/AddFieldMenu.tsx +121 -0
  337. package/src/pages/forms/EmbedPanel.tsx +163 -2
  338. package/src/pages/forms/FieldsEditor.tsx +539 -165
  339. package/src/pages/forms/FormActions.tsx +552 -0
  340. package/src/pages/forms/FormSettings.tsx +29 -70
  341. package/src/pages/forms/SubmissionDetail.tsx +249 -0
  342. package/src/pages/forms/SubmissionsTable.tsx +112 -26
  343. package/src/pages/forms/fieldList.ts +68 -5
  344. package/src/pages/forms/template.ts +103 -0
  345. package/src/pages/handbook/HandbookPage.tsx +1 -1
  346. package/src/pages/hiringDirectory.ts +2 -2
  347. package/src/pages/onboarding/WorkspaceStepPage.tsx +31 -3
  348. package/src/pages/positionDirectory.ts +2 -2
  349. package/src/styles.css +3 -3
@@ -1,14 +1,21 @@
1
- import type { Workspace } from '@kelpie/schemas'
2
- import { useState } from 'react'
1
+ import type { Company, Workspace } from '@kelpie/schemas'
2
+ import { useMemo, useState } from 'react'
3
3
  import type { FormEvent, ReactNode } from 'react'
4
4
  import { useNavigate } from 'react-router'
5
5
 
6
+ import {
7
+ useCompanies,
8
+ useCreateCompany,
9
+ useUpdateCompany,
10
+ } from '../../api/resources/companies.ts'
6
11
  import { useSession } from '../../api/resources/session.ts'
7
12
  import {
8
13
  useDeleteWorkspace,
9
14
  useUpdateWorkspace,
10
15
  useWorkspace,
11
16
  } from '../../api/resources/workspace.ts'
17
+ import { EntitySearch } from '../../components/EntitySearch.tsx'
18
+ import type { SearchOption } from '../../components/EntitySearch.tsx'
12
19
  import { PageHeader } from '../../components/PageHeader.tsx'
13
20
  import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
14
21
 
@@ -190,11 +197,136 @@ function WorkspaceSettingsForm({
190
197
  )}
191
198
  </form>
192
199
 
200
+ <OwnCompanySection canEdit={canEdit} />
201
+
193
202
  {session?.role === 'owner' && <DangerZone slug={workspace.slug} />}
194
203
  </div>
195
204
  )
196
205
  }
197
206
 
207
+ /**
208
+ * Mark one or more Companies as "us" — the workspace's own organisation.
209
+ *
210
+ * Backed by `companies.is_own`. Zero or many rows may carry it (a parent and a
211
+ * subsidiary is the intended case), so the surface is a list plus an add
212
+ * picker rather than a single-select. Picking a name that does not exist
213
+ * creates the Company with `is_own: true` in one call.
214
+ */
215
+ function OwnCompanySection({ canEdit }: { readonly canEdit: boolean }): React.JSX.Element {
216
+ const own = useCompanies({ isOwn: true })
217
+ const [search, setSearch] = useState('')
218
+ // The picker's options: workspace companies that are NOT already marked.
219
+ // Server-side search on the term the user types.
220
+ const searchable = useCompanies({
221
+ isOwn: false,
222
+ term: search.trim().length > 0 ? search.trim() : undefined,
223
+ })
224
+ const update = useUpdateCompany()
225
+ const create = useCreateCompany()
226
+
227
+ const options: SearchOption[] = useMemo(
228
+ () =>
229
+ searchable.records.map((company) => ({
230
+ id: company.id,
231
+ label: company.name,
232
+ meta: company.domain ?? undefined,
233
+ })),
234
+ [searchable.records],
235
+ )
236
+
237
+ function mark(companyId: string): void {
238
+ update.run({ id: companyId, changes: { isOwn: true } })
239
+ }
240
+
241
+ function unmark(companyId: string): void {
242
+ update.run({ id: companyId, changes: { isOwn: false } })
243
+ }
244
+
245
+ function createAndMark(name: string): void {
246
+ // The API defaults every other field; `is_own: true` lands the row in the
247
+ // list this section shows and nowhere else.
248
+ create.run({ name, isOwn: true })
249
+ setSearch('')
250
+ }
251
+
252
+ const busy = update.isPending || create.isPending
253
+
254
+ return (
255
+ <section className="space-y-3 rounded-lg border border-border bg-surface p-4">
256
+ <div>
257
+ <h2 className="text-[13px] font-semibold text-ink">This company</h2>
258
+ <p className="mt-1 max-w-xl text-[12px] text-ink-muted">
259
+ Mark the company (or companies) in the CRM that represent this workspace itself. Used
260
+ later to separate own records from prospects.
261
+ </p>
262
+ </div>
263
+
264
+ {own.isLoading ? (
265
+ <p className="text-[12px] text-ink-faint">Loading…</p>
266
+ ) : own.records.length === 0 ? (
267
+ <p className="text-[12px] text-ink-faint">Nothing marked yet.</p>
268
+ ) : (
269
+ <ul className="divide-y divide-border rounded-md border border-border">
270
+ {own.records.map((company: Company) => (
271
+ <li
272
+ key={company.id}
273
+ className="flex items-center justify-between gap-3 px-3 py-2.5"
274
+ >
275
+ <div className="min-w-0">
276
+ <div className="truncate text-[13px] font-medium text-ink">{company.name}</div>
277
+ {company.domain !== null && company.domain.length > 0 && (
278
+ <div className="truncate text-[11px] text-ink-faint">{company.domain}</div>
279
+ )}
280
+ </div>
281
+ {canEdit && (
282
+ <button
283
+ type="button"
284
+ onClick={() => {
285
+ unmark(company.id)
286
+ }}
287
+ disabled={busy}
288
+ className="shrink-0 rounded-md border border-border bg-surface-raised px-2.5 py-1 text-[11px] font-medium text-ink hover:border-danger hover:text-danger disabled:opacity-50"
289
+ >
290
+ Unmark
291
+ </button>
292
+ )}
293
+ </li>
294
+ ))}
295
+ </ul>
296
+ )}
297
+
298
+ {canEdit && (
299
+ <div className="max-w-md">
300
+ <label className="mb-1.5 block text-[12px] font-medium text-ink">
301
+ {own.records.length === 0 ? 'Pick or create' : 'Add another'}
302
+ </label>
303
+ <EntitySearch
304
+ options={options}
305
+ value=""
306
+ onChange={mark}
307
+ onQueryChange={setSearch}
308
+ onCreate={createAndMark}
309
+ createLabel={(query) => `Create “${query}” and mark it`}
310
+ placeholder="Search companies…"
311
+ emptyMessage="No matches — type a new name to create one"
312
+ />
313
+ </div>
314
+ )}
315
+
316
+ {update.error !== null && (
317
+ <div className="max-w-xl">
318
+ <ErrorPanel error={update.error} />
319
+ </div>
320
+ )}
321
+ {create.error !== null && (
322
+ <div className="max-w-xl">
323
+ <ErrorPanel error={create.error} />
324
+ </div>
325
+ )}
326
+ </section>
327
+ )
328
+ }
329
+
198
330
  /**
199
331
  * Deleting the workspace, behind the workspace's own name.
200
332
  *
@@ -28,6 +28,7 @@ const ROUTES: Readonly<Record<RecordTargetType, string | undefined>> = {
28
28
  opportunity: '/opportunities',
29
29
  partnership: '/partnerships',
30
30
  raise: '/fundraising',
31
+ enquiry: '/enquiries',
31
32
  candidate: undefined,
32
33
  }
33
34
 
@@ -38,6 +39,7 @@ const TARGET_TYPE_LABELS: Readonly<Record<RecordTargetType, string>> = {
38
39
  opportunity: 'Opportunity',
39
40
  partnership: 'Partnership',
40
41
  raise: 'Fundraising',
42
+ enquiry: 'Enquiry',
41
43
  candidate: 'Candidate',
42
44
  }
43
45
 
@@ -147,6 +149,7 @@ function plural(count: number, singular: string, pluralForm = `${singular}s`): s
147
149
  const OPEN_PIPELINE_NOUNS: Readonly<
148
150
  Record<PipelineKind, { readonly one: string; readonly many: string }>
149
151
  > = {
152
+ enquiry: { one: 'enquiry', many: 'enquiries' },
150
153
  deal: { one: 'deal', many: 'deals' },
151
154
  opportunity: { one: 'opportunity', many: 'opportunities' },
152
155
  raise: { one: 'raise', many: 'raises' },
@@ -0,0 +1,121 @@
1
+ import { FORM_FIELD_MAP_TARGET_LABELS } from '@kelpie/schemas'
2
+ import type { FormFieldInput } from '@kelpie/schemas'
3
+ import { useEffect, useRef, useState } from 'react'
4
+
5
+ import { unusedCrmPresets } from './fieldList.ts'
6
+ import type { EditableField } from './fieldList.ts'
7
+ import { SUBMISSION_FIELD_PRESETS } from './template.ts'
8
+
9
+ /**
10
+ * The "Add field" button and its menu of ready-made fields.
11
+ *
12
+ * Two groups. CRM presets carry a mapping and appear only while that mapping is
13
+ * free, because a form may carry each CRM target once. Submission-only fields
14
+ * always appear and may repeat. The menu owns nothing but its own open state:
15
+ * the field list lives with the builder, which is what `onAdd` hands the chosen
16
+ * preset back to.
17
+ */
18
+ export interface AddFieldMenuProps {
19
+ readonly fields: readonly EditableField[]
20
+ readonly onAdd: (field: FormFieldInput) => void
21
+ }
22
+
23
+ export function AddFieldMenu({ fields, onAdd }: AddFieldMenuProps): React.JSX.Element {
24
+ const [open, setOpen] = useState(false)
25
+ const containerRef = useRef<HTMLDivElement>(null)
26
+ const crmPresets = unusedCrmPresets(fields)
27
+
28
+ useEffect(() => {
29
+ if (!open) {
30
+ return
31
+ }
32
+
33
+ function onPointerDown(event: MouseEvent): void {
34
+ if (containerRef.current?.contains(event.target as Node) !== true) {
35
+ setOpen(false)
36
+ }
37
+ }
38
+
39
+ function onKeyDown(event: KeyboardEvent): void {
40
+ if (event.key === 'Escape') {
41
+ setOpen(false)
42
+ }
43
+ }
44
+
45
+ document.addEventListener('mousedown', onPointerDown)
46
+ document.addEventListener('keydown', onKeyDown)
47
+
48
+ return () => {
49
+ document.removeEventListener('mousedown', onPointerDown)
50
+ document.removeEventListener('keydown', onKeyDown)
51
+ }
52
+ }, [open])
53
+
54
+ function add(field: FormFieldInput): void {
55
+ onAdd(field)
56
+ setOpen(false)
57
+ }
58
+
59
+ return (
60
+ <div className="relative" ref={containerRef}>
61
+ <button
62
+ type="button"
63
+ onClick={() => {
64
+ setOpen((current) => !current)
65
+ }}
66
+ aria-expanded={open}
67
+ aria-haspopup="menu"
68
+ className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover"
69
+ >
70
+ + Add field
71
+ </button>
72
+ {open && (
73
+ <div
74
+ role="menu"
75
+ aria-label="Field to add"
76
+ className="absolute right-0 z-20 mt-1.5 max-h-80 w-64 overflow-y-auto rounded-md border border-border bg-surface-raised py-1"
77
+ >
78
+ {crmPresets.length > 0 && (
79
+ <>
80
+ <p className="px-3 pb-1 pt-1.5 text-[10px] font-semibold uppercase tracking-wide text-ink-faint">
81
+ Writes to the CRM
82
+ </p>
83
+ {crmPresets.map((preset) => (
84
+ <button
85
+ key={preset.mapTo}
86
+ type="button"
87
+ role="menuitem"
88
+ onClick={() => {
89
+ add(preset)
90
+ }}
91
+ className="flex w-full items-center justify-between gap-2 px-3 py-1.5 text-left text-[13px] text-ink transition hover:bg-surface-sunken"
92
+ >
93
+ <span>{preset.label}</span>
94
+ <span className="shrink-0 text-[11px] text-ink-faint">
95
+ {FORM_FIELD_MAP_TARGET_LABELS[preset.mapTo]}
96
+ </span>
97
+ </button>
98
+ ))}
99
+ </>
100
+ )}
101
+ <p className="px-3 pb-1 pt-1.5 text-[10px] font-semibold uppercase tracking-wide text-ink-faint">
102
+ Submission only
103
+ </p>
104
+ {SUBMISSION_FIELD_PRESETS.map((preset) => (
105
+ <button
106
+ key={preset.menuLabel}
107
+ type="button"
108
+ role="menuitem"
109
+ onClick={() => {
110
+ add(preset.field)
111
+ }}
112
+ className="w-full px-3 py-1.5 text-left text-[13px] text-ink transition hover:bg-surface-sunken"
113
+ >
114
+ {preset.menuLabel}
115
+ </button>
116
+ ))}
117
+ </div>
118
+ )}
119
+ </div>
120
+ )
121
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Form } from '@kelpie/schemas'
2
+ import { useEffect, useId, useState } from 'react'
2
3
 
3
4
  import { useFormEmbed } from '../../api/resources/forms.ts'
4
5
  import { CopyButton } from '../../components/CopyButton.tsx'
@@ -12,14 +13,21 @@ import { SectionHeader } from '../../components/SectionHeader.tsx'
12
13
  * embed page is served by the service, so its address depends on where the
13
14
  * service is reached, which the server knows from the request and the browser
14
15
  * only happens to share while the two are same-origin.
16
+ *
17
+ * Each iframe snippet has a Preview that opens the bare embed document in a
18
+ * modal — fields only, the same document the snippet iframes — fixed height for
19
+ * the plain iframe, auto-resize for the script variant.
15
20
  */
16
21
 
17
22
  export interface EmbedPanelProps {
18
23
  readonly form: Form
19
24
  }
20
25
 
26
+ type PreviewMode = 'fixed' | 'resize'
27
+
21
28
  export function EmbedPanel({ form }: EmbedPanelProps): React.JSX.Element {
22
29
  const { snippets, isLoading, error } = useFormEmbed(form.id)
30
+ const [preview, setPreview] = useState<PreviewMode | null>(null)
23
31
 
24
32
  if (error !== null) {
25
33
  return <ErrorPanel error={error} />
@@ -33,7 +41,7 @@ export function EmbedPanel({ form }: EmbedPanelProps): React.JSX.Element {
33
41
  <div className="max-w-2xl space-y-5">
34
42
  <SectionHeader
35
43
  title="Embed"
36
- description="The page below is served by Kelpie, so a site embedding it loads nothing else."
44
+ description="Hosted page is the standalone URL with Kelpie chrome. The iframe snippets load a bare form fields only — so they sit inside your own site."
37
45
  />
38
46
 
39
47
  <div>
@@ -57,12 +65,31 @@ export function EmbedPanel({ form }: EmbedPanelProps): React.JSX.Element {
57
65
  label="iframe"
58
66
  hint="One tag, no JavaScript, fixed height."
59
67
  value={snippets.iframeSnippet}
68
+ onPreview={() => {
69
+ setPreview('fixed')
70
+ }}
60
71
  />
61
72
  <Snippet
62
73
  label="iframe with auto-resize"
63
74
  hint="Adds a listener that follows the page as fields appear and as the thank-you replaces the form."
64
75
  value={snippets.scriptSnippet}
76
+ onPreview={() => {
77
+ setPreview('resize')
78
+ }}
65
79
  />
80
+
81
+ {preview !== null && (
82
+ <EmbedPreviewModal
83
+ formId={form.id}
84
+ formName={form.name}
85
+ formTitle={form.title}
86
+ url={snippets.embedUrl}
87
+ mode={preview}
88
+ onClose={() => {
89
+ setPreview(null)
90
+ }}
91
+ />
92
+ )}
66
93
  </div>
67
94
  )
68
95
  }
@@ -71,16 +98,27 @@ function Snippet({
71
98
  label,
72
99
  hint,
73
100
  value,
101
+ onPreview,
74
102
  }: {
75
103
  readonly label: string
76
104
  readonly hint: string
77
105
  readonly value: string
106
+ readonly onPreview: () => void
78
107
  }): React.JSX.Element {
79
108
  return (
80
109
  <div>
81
110
  <div className="mb-1.5 flex items-center justify-between gap-2">
82
111
  <span className="text-[12px] font-medium text-ink">{label}</span>
83
- <CopyButton value={value} label={`Copy the ${label} snippet`} />
112
+ <div className="flex items-center gap-2">
113
+ <button
114
+ type="button"
115
+ onClick={onPreview}
116
+ className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover"
117
+ >
118
+ Preview
119
+ </button>
120
+ <CopyButton value={value} label={`Copy the ${label} snippet`} />
121
+ </div>
84
122
  </div>
85
123
  <textarea
86
124
  readOnly
@@ -93,3 +131,126 @@ function Snippet({
93
131
  </div>
94
132
  )
95
133
  }
134
+
135
+ /**
136
+ * Frames the hosted embed the way each snippet would: fixed 720px for the plain
137
+ * iframe, or listening for the height postMessage when previewing auto-resize.
138
+ */
139
+ function EmbedPreviewModal({
140
+ formId,
141
+ formName,
142
+ formTitle,
143
+ url,
144
+ mode,
145
+ onClose,
146
+ }: {
147
+ readonly formId: string
148
+ readonly formName: string
149
+ readonly formTitle: string
150
+ readonly url: string
151
+ readonly mode: PreviewMode
152
+ readonly onClose: () => void
153
+ }): React.JSX.Element {
154
+ const titleId = useId()
155
+ const heading = formTitle.trim().length > 0 ? formTitle : formName
156
+ const [height, setHeight] = useState(720)
157
+
158
+ useEffect(() => {
159
+ if (mode !== 'resize') {
160
+ return
161
+ }
162
+
163
+ function onMessage(event: MessageEvent): void {
164
+ const data = event.data as { kelpie?: string; formId?: string; height?: number } | null
165
+
166
+ if (
167
+ data === null ||
168
+ typeof data !== 'object' ||
169
+ data.kelpie !== 'height' ||
170
+ data.formId !== formId ||
171
+ typeof data.height !== 'number' ||
172
+ !Number.isFinite(data.height)
173
+ ) {
174
+ return
175
+ }
176
+
177
+ setHeight(Math.max(320, Math.ceil(data.height)))
178
+ }
179
+
180
+ window.addEventListener('message', onMessage)
181
+
182
+ return () => {
183
+ window.removeEventListener('message', onMessage)
184
+ }
185
+ }, [formId, mode])
186
+
187
+ useEffect(() => {
188
+ function onKeyDown(event: KeyboardEvent): void {
189
+ if (event.key === 'Escape') {
190
+ onClose()
191
+ }
192
+ }
193
+
194
+ window.addEventListener('keydown', onKeyDown)
195
+
196
+ return () => {
197
+ window.removeEventListener('keydown', onKeyDown)
198
+ }
199
+ }, [onClose])
200
+
201
+ return (
202
+ <div
203
+ className="fixed inset-0 z-50 flex items-end justify-center bg-ink/40 p-4 sm:items-center"
204
+ role="dialog"
205
+ aria-modal="true"
206
+ aria-labelledby={titleId}
207
+ onClick={onClose}
208
+ >
209
+ <div
210
+ className="animate-slide-in flex max-h-[90vh] w-full max-w-lg flex-col overflow-hidden rounded-md border border-border bg-surface-raised shadow-lg"
211
+ onClick={(event) => {
212
+ event.stopPropagation()
213
+ }}
214
+ >
215
+ <div className="flex shrink-0 items-start justify-between gap-3 border-b border-border px-4 py-3">
216
+ <div className="min-w-0">
217
+ <h2 id={titleId} className="truncate text-[14px] font-semibold text-ink">
218
+ Preview · {heading}
219
+ </h2>
220
+ <p className="mt-0.5 text-[11px] text-ink-faint">
221
+ {mode === 'resize'
222
+ ? 'Bare iframe embed with auto-resize — fields only, height follows the form.'
223
+ : 'Bare iframe embed — fields only, fixed height.'}
224
+ </p>
225
+ </div>
226
+ <button
227
+ type="button"
228
+ onClick={onClose}
229
+ className="shrink-0 rounded-md px-2 py-1 text-[12px] font-medium text-ink-muted transition hover:text-ink"
230
+ >
231
+ Close
232
+ </button>
233
+ </div>
234
+
235
+ <div className="min-h-0 flex-1 overflow-y-auto bg-[#f4f4f5] p-4">
236
+ <div className="overflow-hidden rounded-md border border-[#d4d4d8] bg-white shadow-sm">
237
+ <iframe
238
+ src={url}
239
+ title={`${heading} preview`}
240
+ style={{
241
+ width: '100%',
242
+ border: 0,
243
+ height: mode === 'fixed' ? '720px' : `${String(height)}px`,
244
+ colorScheme: 'normal',
245
+ }}
246
+ className="block bg-transparent"
247
+ />
248
+ </div>
249
+ <p className="mt-2 text-center text-[11px] text-ink-faint">
250
+ Simulated host page — the iframe uses its own neutral styles, not Kelpie&apos;s.
251
+ </p>
252
+ </div>
253
+ </div>
254
+ </div>
255
+ )
256
+ }