@kelpie/ui 0.11.0 → 0.13.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 (333) hide show
  1. package/dist/api/resources/attendances.d.ts +13 -0
  2. package/dist/api/resources/attendances.d.ts.map +1 -0
  3. package/dist/api/resources/attendances.js +34 -0
  4. package/dist/api/resources/attendances.js.map +1 -0
  5. package/dist/api/resources/eventAssociations.d.ts +12 -0
  6. package/dist/api/resources/eventAssociations.d.ts.map +1 -0
  7. package/dist/api/resources/eventAssociations.js +25 -0
  8. package/dist/api/resources/eventAssociations.js.map +1 -0
  9. package/dist/api/resources/events.d.ts +17 -0
  10. package/dist/api/resources/events.d.ts.map +1 -0
  11. package/dist/api/resources/events.js +40 -0
  12. package/dist/api/resources/events.js.map +1 -0
  13. package/dist/api/resources/handbookSeed.d.ts +11 -0
  14. package/dist/api/resources/handbookSeed.d.ts.map +1 -0
  15. package/dist/api/resources/handbookSeed.js +43 -0
  16. package/dist/api/resources/handbookSeed.js.map +1 -0
  17. package/dist/api/resources/moduleSettings.d.ts +10 -0
  18. package/dist/api/resources/moduleSettings.d.ts.map +1 -1
  19. package/dist/api/resources/moduleSettings.js +21 -0
  20. package/dist/api/resources/moduleSettings.js.map +1 -1
  21. package/dist/api/resources/planItems.d.ts +4 -4
  22. package/dist/api/resources/planItems.d.ts.map +1 -1
  23. package/dist/api/resources/planItems.js +1 -1
  24. package/dist/api/resources/planItems.js.map +1 -1
  25. package/dist/api/resources/publicConfig.js +3 -3
  26. package/dist/api/resources/sampleData.d.ts.map +1 -1
  27. package/dist/api/resources/sampleData.js +2 -0
  28. package/dist/api/resources/sampleData.js.map +1 -1
  29. package/dist/api/resources/session.d.ts +22 -1
  30. package/dist/api/resources/session.d.ts.map +1 -1
  31. package/dist/api/resources/session.js +43 -1
  32. package/dist/api/resources/session.js.map +1 -1
  33. package/dist/api/resources/workspace.d.ts.map +1 -1
  34. package/dist/api/resources/workspace.js +2 -3
  35. package/dist/api/resources/workspace.js.map +1 -1
  36. package/dist/app/KelpieApp.d.ts.map +1 -1
  37. package/dist/app/KelpieApp.js +5 -1
  38. package/dist/app/KelpieApp.js.map +1 -1
  39. package/dist/components/ActivitiesPanel.d.ts.map +1 -1
  40. package/dist/components/ActivitiesPanel.js +2 -0
  41. package/dist/components/ActivitiesPanel.js.map +1 -1
  42. package/dist/components/AddressesField.d.ts +23 -0
  43. package/dist/components/AddressesField.d.ts.map +1 -0
  44. package/dist/components/AddressesField.js +215 -0
  45. package/dist/components/AddressesField.js.map +1 -0
  46. package/dist/components/CountrySearch.d.ts +16 -0
  47. package/dist/components/CountrySearch.d.ts.map +1 -0
  48. package/dist/components/CountrySearch.js +30 -0
  49. package/dist/components/CountrySearch.js.map +1 -0
  50. package/dist/components/EnvironmentBanner.d.ts +0 -12
  51. package/dist/components/EnvironmentBanner.d.ts.map +1 -1
  52. package/dist/components/EnvironmentBanner.js +21 -6
  53. package/dist/components/EnvironmentBanner.js.map +1 -1
  54. package/dist/components/InlineEdit.d.ts +7 -1
  55. package/dist/components/InlineEdit.d.ts.map +1 -1
  56. package/dist/components/InlineEdit.js +8 -1
  57. package/dist/components/InlineEdit.js.map +1 -1
  58. package/dist/components/LinkedEventsPanel.d.ts +11 -0
  59. package/dist/components/LinkedEventsPanel.d.ts.map +1 -0
  60. package/dist/components/LinkedEventsPanel.js +18 -0
  61. package/dist/components/LinkedEventsPanel.js.map +1 -0
  62. package/dist/components/NotesPanel.d.ts +4 -6
  63. package/dist/components/NotesPanel.d.ts.map +1 -1
  64. package/dist/components/NotesPanel.js +39 -3
  65. package/dist/components/NotesPanel.js.map +1 -1
  66. package/dist/components/PlanAttention.d.ts.map +1 -1
  67. package/dist/components/PlanAttention.js +3 -2
  68. package/dist/components/PlanAttention.js.map +1 -1
  69. package/dist/components/PlanPanel.d.ts +3 -3
  70. package/dist/components/PlanPanel.d.ts.map +1 -1
  71. package/dist/components/Shell.d.ts.map +1 -1
  72. package/dist/components/Shell.js +8 -2
  73. package/dist/components/Shell.js.map +1 -1
  74. package/dist/components/SocialNetworkIcon.d.ts +17 -0
  75. package/dist/components/SocialNetworkIcon.d.ts.map +1 -0
  76. package/dist/components/SocialNetworkIcon.js +39 -0
  77. package/dist/components/SocialNetworkIcon.js.map +1 -0
  78. package/dist/components/SocialProfilesField.d.ts +8 -0
  79. package/dist/components/SocialProfilesField.d.ts.map +1 -1
  80. package/dist/components/SocialProfilesField.js +19 -5
  81. package/dist/components/SocialProfilesField.js.map +1 -1
  82. package/dist/components/TimezoneSearch.d.ts +21 -0
  83. package/dist/components/TimezoneSearch.d.ts.map +1 -0
  84. package/dist/components/TimezoneSearch.js +27 -0
  85. package/dist/components/TimezoneSearch.js.map +1 -0
  86. package/dist/components/WorkspaceSwitcher.d.ts +8 -0
  87. package/dist/components/WorkspaceSwitcher.d.ts.map +1 -0
  88. package/dist/components/WorkspaceSwitcher.js +67 -0
  89. package/dist/components/WorkspaceSwitcher.js.map +1 -0
  90. package/dist/components/environmentTone.d.ts +10 -0
  91. package/dist/components/environmentTone.d.ts.map +1 -0
  92. package/dist/components/environmentTone.js +35 -0
  93. package/dist/components/environmentTone.js.map +1 -0
  94. package/dist/index.d.ts +25 -6
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +17 -3
  97. package/dist/index.js.map +1 -1
  98. package/dist/lib/timezones.d.ts +17 -0
  99. package/dist/lib/timezones.d.ts.map +1 -0
  100. package/dist/lib/timezones.js +39 -0
  101. package/dist/lib/timezones.js.map +1 -0
  102. package/dist/pages/CompaniesPage.d.ts.map +1 -1
  103. package/dist/pages/CompaniesPage.js +12 -11
  104. package/dist/pages/CompaniesPage.js.map +1 -1
  105. package/dist/pages/CompanyDetail.d.ts.map +1 -1
  106. package/dist/pages/CompanyDetail.js +23 -12
  107. package/dist/pages/CompanyDetail.js.map +1 -1
  108. package/dist/pages/DealDetail.d.ts.map +1 -1
  109. package/dist/pages/DealDetail.js +5 -1
  110. package/dist/pages/DealDetail.js.map +1 -1
  111. package/dist/pages/DecisionsPage.d.ts.map +1 -1
  112. package/dist/pages/DecisionsPage.js +7 -0
  113. package/dist/pages/DecisionsPage.js.map +1 -1
  114. package/dist/pages/EnquiryDetail.d.ts.map +1 -1
  115. package/dist/pages/EnquiryDetail.js +5 -1
  116. package/dist/pages/EnquiryDetail.js.map +1 -1
  117. package/dist/pages/EventDetail.d.ts +2 -0
  118. package/dist/pages/EventDetail.d.ts.map +1 -0
  119. package/dist/pages/EventDetail.js +332 -0
  120. package/dist/pages/EventDetail.js.map +1 -0
  121. package/dist/pages/EventsPage.d.ts +2 -0
  122. package/dist/pages/EventsPage.d.ts.map +1 -0
  123. package/dist/pages/EventsPage.js +176 -0
  124. package/dist/pages/EventsPage.js.map +1 -0
  125. package/dist/pages/ListDetail.d.ts.map +1 -1
  126. package/dist/pages/ListDetail.js +17 -0
  127. package/dist/pages/ListDetail.js.map +1 -1
  128. package/dist/pages/OpportunityDetail.d.ts.map +1 -1
  129. package/dist/pages/OpportunityDetail.js +5 -1
  130. package/dist/pages/OpportunityDetail.js.map +1 -1
  131. package/dist/pages/PartnershipDetail.d.ts.map +1 -1
  132. package/dist/pages/PartnershipDetail.js +5 -1
  133. package/dist/pages/PartnershipDetail.js.map +1 -1
  134. package/dist/pages/PeoplePage.d.ts.map +1 -1
  135. package/dist/pages/PeoplePage.js +13 -5
  136. package/dist/pages/PeoplePage.js.map +1 -1
  137. package/dist/pages/PersonDetail.d.ts.map +1 -1
  138. package/dist/pages/PersonDetail.js +94 -28
  139. package/dist/pages/PersonDetail.js.map +1 -1
  140. package/dist/pages/PlanningPage.d.ts.map +1 -1
  141. package/dist/pages/PlanningPage.js +6 -3
  142. package/dist/pages/PlanningPage.js.map +1 -1
  143. package/dist/pages/RaiseDetail.d.ts.map +1 -1
  144. package/dist/pages/RaiseDetail.js +5 -1
  145. package/dist/pages/RaiseDetail.js.map +1 -1
  146. package/dist/pages/RoleDetail.d.ts.map +1 -1
  147. package/dist/pages/RoleDetail.js +4 -1
  148. package/dist/pages/RoleDetail.js.map +1 -1
  149. package/dist/pages/SearchPage.d.ts.map +1 -1
  150. package/dist/pages/SearchPage.js +2 -1
  151. package/dist/pages/SearchPage.js.map +1 -1
  152. package/dist/pages/account/PreferencesPage.d.ts.map +1 -1
  153. package/dist/pages/account/PreferencesPage.js +2 -28
  154. package/dist/pages/account/PreferencesPage.js.map +1 -1
  155. package/dist/pages/account/SecurityPage.js +2 -2
  156. package/dist/pages/account/SecurityPage.js.map +1 -1
  157. package/dist/pages/admin/DataPage.js +10 -9
  158. package/dist/pages/admin/DataPage.js.map +1 -1
  159. package/dist/pages/admin/FieldsPage.js +1 -1
  160. package/dist/pages/admin/FieldsPage.js.map +1 -1
  161. package/dist/pages/admin/ModulesPage.d.ts.map +1 -1
  162. package/dist/pages/admin/ModulesPage.js +1 -0
  163. package/dist/pages/admin/ModulesPage.js.map +1 -1
  164. package/dist/pages/admin/WorkspaceSettingsPage.d.ts +12 -0
  165. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
  166. package/dist/pages/admin/WorkspaceSettingsPage.js +2 -21
  167. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
  168. package/dist/pages/auth/AuthForm.d.ts +18 -0
  169. package/dist/pages/auth/AuthForm.d.ts.map +1 -1
  170. package/dist/pages/auth/AuthForm.js +17 -0
  171. package/dist/pages/auth/AuthForm.js.map +1 -1
  172. package/dist/pages/auth/AuthLayout.d.ts +5 -4
  173. package/dist/pages/auth/AuthLayout.d.ts.map +1 -1
  174. package/dist/pages/auth/AuthLayout.js +7 -5
  175. package/dist/pages/auth/AuthLayout.js.map +1 -1
  176. package/dist/pages/auth/AuthMethods.d.ts +12 -0
  177. package/dist/pages/auth/AuthMethods.d.ts.map +1 -0
  178. package/dist/pages/auth/AuthMethods.js +20 -0
  179. package/dist/pages/auth/AuthMethods.js.map +1 -0
  180. package/dist/pages/auth/RegionSwitcher.d.ts +10 -0
  181. package/dist/pages/auth/RegionSwitcher.d.ts.map +1 -0
  182. package/dist/pages/auth/RegionSwitcher.js +28 -0
  183. package/dist/pages/auth/RegionSwitcher.js.map +1 -0
  184. package/dist/pages/auth/SignInPage.d.ts +7 -0
  185. package/dist/pages/auth/SignInPage.d.ts.map +1 -1
  186. package/dist/pages/auth/SignInPage.js +5 -8
  187. package/dist/pages/auth/SignInPage.js.map +1 -1
  188. package/dist/pages/auth/SignUpPage.d.ts.map +1 -1
  189. package/dist/pages/auth/SignUpPage.js +9 -1
  190. package/dist/pages/auth/SignUpPage.js.map +1 -1
  191. package/dist/pages/auth/nextPath.d.ts +15 -0
  192. package/dist/pages/auth/nextPath.d.ts.map +1 -0
  193. package/dist/pages/auth/nextPath.js +17 -0
  194. package/dist/pages/auth/nextPath.js.map +1 -0
  195. package/dist/pages/auth/regionHref.d.ts +6 -0
  196. package/dist/pages/auth/regionHref.d.ts.map +1 -0
  197. package/dist/pages/auth/regionHref.js +8 -0
  198. package/dist/pages/auth/regionHref.js.map +1 -0
  199. package/dist/pages/dashboard/DashboardPage.d.ts +8 -0
  200. package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -1
  201. package/dist/pages/dashboard/DashboardPage.js +5 -13
  202. package/dist/pages/dashboard/DashboardPage.js.map +1 -1
  203. package/dist/pages/dashboard/attention.d.ts +1 -8
  204. package/dist/pages/dashboard/attention.d.ts.map +1 -1
  205. package/dist/pages/dashboard/attention.js +21 -54
  206. package/dist/pages/dashboard/attention.js.map +1 -1
  207. package/dist/pages/dashboard/sections.d.ts.map +1 -1
  208. package/dist/pages/dashboard/sections.js +3 -2
  209. package/dist/pages/dashboard/sections.js.map +1 -1
  210. package/dist/pages/forms/FormActions.d.ts.map +1 -1
  211. package/dist/pages/forms/FormActions.js +7 -3
  212. package/dist/pages/forms/FormActions.js.map +1 -1
  213. package/dist/pages/onboarding/HandbookStepPage.d.ts +4 -8
  214. package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -1
  215. package/dist/pages/onboarding/HandbookStepPage.js +13 -13
  216. package/dist/pages/onboarding/HandbookStepPage.js.map +1 -1
  217. package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -1
  218. package/dist/pages/onboarding/InvitesStepPage.js +13 -7
  219. package/dist/pages/onboarding/InvitesStepPage.js.map +1 -1
  220. package/dist/pages/onboarding/ModulesStepPage.d.ts +11 -0
  221. package/dist/pages/onboarding/ModulesStepPage.d.ts.map +1 -0
  222. package/dist/pages/onboarding/ModulesStepPage.js +80 -0
  223. package/dist/pages/onboarding/ModulesStepPage.js.map +1 -0
  224. package/dist/pages/onboarding/OnboardingNav.d.ts +17 -0
  225. package/dist/pages/onboarding/OnboardingNav.d.ts.map +1 -0
  226. package/dist/pages/onboarding/OnboardingNav.js +37 -0
  227. package/dist/pages/onboarding/OnboardingNav.js.map +1 -0
  228. package/dist/pages/onboarding/OrganisationStepPage.d.ts +13 -0
  229. package/dist/pages/onboarding/OrganisationStepPage.d.ts.map +1 -0
  230. package/dist/pages/onboarding/OrganisationStepPage.js +59 -0
  231. package/dist/pages/onboarding/OrganisationStepPage.js.map +1 -0
  232. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
  233. package/dist/pages/onboarding/WorkspaceStepPage.js +25 -20
  234. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
  235. package/dist/pages/onboarding/handbookTemplates.d.ts +19 -0
  236. package/dist/pages/onboarding/handbookTemplates.d.ts.map +1 -0
  237. package/dist/pages/onboarding/handbookTemplates.js +57 -0
  238. package/dist/pages/onboarding/handbookTemplates.js.map +1 -0
  239. package/dist/pages/onboarding/onboardingModules.d.ts +23 -0
  240. package/dist/pages/onboarding/onboardingModules.d.ts.map +1 -0
  241. package/dist/pages/onboarding/onboardingModules.js +153 -0
  242. package/dist/pages/onboarding/onboardingModules.js.map +1 -0
  243. package/dist/pages/onboarding/onboardingRerun.d.ts +21 -0
  244. package/dist/pages/onboarding/onboardingRerun.d.ts.map +1 -0
  245. package/dist/pages/onboarding/onboardingRerun.js +33 -0
  246. package/dist/pages/onboarding/onboardingRerun.js.map +1 -0
  247. package/dist/registry/context.d.ts +3 -1
  248. package/dist/registry/context.d.ts.map +1 -1
  249. package/dist/registry/context.js +4 -0
  250. package/dist/registry/context.js.map +1 -1
  251. package/dist/registry/contributions.d.ts +24 -0
  252. package/dist/registry/contributions.d.ts.map +1 -1
  253. package/dist/registry/contributions.js.map +1 -1
  254. package/dist/registry/registry.d.ts +4 -1
  255. package/dist/registry/registry.d.ts.map +1 -1
  256. package/dist/registry/registry.js +6 -0
  257. package/dist/registry/registry.js.map +1 -1
  258. package/package.json +4 -3
  259. package/src/api/resources/attendances.ts +54 -0
  260. package/src/api/resources/eventAssociations.ts +43 -0
  261. package/src/api/resources/events.ts +70 -0
  262. package/src/api/resources/handbookSeed.ts +63 -0
  263. package/src/api/resources/moduleSettings.ts +25 -0
  264. package/src/api/resources/planItems.ts +5 -5
  265. package/src/api/resources/publicConfig.ts +3 -3
  266. package/src/api/resources/sampleData.ts +2 -0
  267. package/src/api/resources/session.ts +59 -0
  268. package/src/api/resources/workspace.ts +2 -3
  269. package/src/app/KelpieApp.tsx +11 -3
  270. package/src/components/ActivitiesPanel.tsx +2 -0
  271. package/src/components/AddressesField.tsx +502 -0
  272. package/src/components/CountrySearch.tsx +71 -0
  273. package/src/components/EnvironmentBanner.tsx +23 -6
  274. package/src/components/InlineEdit.tsx +35 -1
  275. package/src/components/LinkedEventsPanel.tsx +50 -0
  276. package/src/components/NotesPanel.tsx +132 -15
  277. package/src/components/PlanAttention.tsx +5 -4
  278. package/src/components/PlanPanel.tsx +3 -3
  279. package/src/components/Shell.tsx +16 -1
  280. package/src/components/SocialNetworkIcon.tsx +88 -0
  281. package/src/components/SocialProfilesField.tsx +57 -10
  282. package/src/components/TimezoneSearch.tsx +67 -0
  283. package/src/components/WorkspaceSwitcher.tsx +136 -0
  284. package/src/components/environmentTone.ts +40 -0
  285. package/src/index.ts +49 -3
  286. package/src/lib/timezones.ts +43 -0
  287. package/src/pages/CompaniesPage.tsx +14 -13
  288. package/src/pages/CompanyDetail.tsx +59 -27
  289. package/src/pages/DealDetail.tsx +5 -0
  290. package/src/pages/DecisionsPage.tsx +7 -0
  291. package/src/pages/EnquiryDetail.tsx +7 -0
  292. package/src/pages/EventDetail.tsx +794 -0
  293. package/src/pages/EventsPage.tsx +382 -0
  294. package/src/pages/ListDetail.tsx +24 -0
  295. package/src/pages/OpportunityDetail.tsx +7 -0
  296. package/src/pages/PartnershipDetail.tsx +7 -0
  297. package/src/pages/PeoplePage.tsx +18 -5
  298. package/src/pages/PersonDetail.tsx +300 -100
  299. package/src/pages/PlanningPage.tsx +17 -14
  300. package/src/pages/RaiseDetail.tsx +5 -0
  301. package/src/pages/RoleDetail.tsx +8 -0
  302. package/src/pages/SearchPage.tsx +2 -1
  303. package/src/pages/account/PreferencesPage.tsx +2 -37
  304. package/src/pages/account/SecurityPage.tsx +6 -1
  305. package/src/pages/admin/DataPage.tsx +63 -21
  306. package/src/pages/admin/FieldsPage.tsx +1 -1
  307. package/src/pages/admin/ModulesPage.tsx +1 -0
  308. package/src/pages/admin/WorkspaceSettingsPage.tsx +10 -33
  309. package/src/pages/auth/AuthForm.tsx +38 -0
  310. package/src/pages/auth/AuthLayout.tsx +8 -4
  311. package/src/pages/auth/AuthMethods.tsx +29 -0
  312. package/src/pages/auth/RegionSwitcher.tsx +56 -0
  313. package/src/pages/auth/SignInPage.tsx +13 -14
  314. package/src/pages/auth/SignUpPage.tsx +32 -0
  315. package/src/pages/auth/nextPath.ts +17 -0
  316. package/src/pages/auth/regionHref.ts +10 -0
  317. package/src/pages/dashboard/DashboardPage.tsx +16 -48
  318. package/src/pages/dashboard/attention.ts +24 -78
  319. package/src/pages/dashboard/sections.tsx +8 -2
  320. package/src/pages/forms/FormActions.tsx +14 -8
  321. package/src/pages/onboarding/HandbookStepPage.tsx +21 -24
  322. package/src/pages/onboarding/InvitesStepPage.tsx +27 -19
  323. package/src/pages/onboarding/ModulesStepPage.tsx +145 -0
  324. package/src/pages/onboarding/OnboardingNav.tsx +97 -0
  325. package/src/pages/onboarding/OrganisationStepPage.tsx +130 -0
  326. package/src/pages/onboarding/WorkspaceStepPage.tsx +53 -38
  327. package/src/pages/onboarding/handbookTemplates.ts +77 -0
  328. package/src/pages/onboarding/onboardingModules.ts +202 -0
  329. package/src/pages/onboarding/onboardingRerun.ts +41 -0
  330. package/src/registry/context.ts +6 -0
  331. package/src/registry/contributions.ts +26 -0
  332. package/src/registry/registry.ts +12 -0
  333. package/src/styles.css +30 -0
@@ -3,10 +3,12 @@ import { useState } from 'react'
3
3
  import type { FormEvent } from 'react'
4
4
  import { Link, useNavigate } from 'react-router'
5
5
 
6
+ import { usePublicConfig } from '../../api/resources/publicConfig.ts'
6
7
  import { useSignUp } from '../../api/resources/session.ts'
7
8
  import { ErrorPanel } from '../../components/QueryState.tsx'
8
9
  import { SubmitButton, TextField } from './AuthForm.tsx'
9
10
  import { AuthLayout } from './AuthLayout.tsx'
11
+ import { AuthMethods } from './AuthMethods.tsx'
10
12
 
11
13
  /**
12
14
  * Creating an account, against `POST /v1/auth/signup`.
@@ -24,11 +26,35 @@ import { AuthLayout } from './AuthLayout.tsx'
24
26
  export function SignUpPage(): React.JSX.Element {
25
27
  const navigate = useNavigate()
26
28
  const signUp = useSignUp()
29
+ const { config } = usePublicConfig()
27
30
  const [name, setName] = useState('')
28
31
  const [email, setEmail] = useState('')
29
32
  const [password, setPassword] = useState('')
30
33
  const [localError, setLocalError] = useState<string | null>(null)
31
34
 
35
+ // Undefined (still loading) renders the form, same as the server's own
36
+ // default: a caller that has not thought about it sees no change.
37
+ if (config?.signupsEnabled === false) {
38
+ return (
39
+ <AuthLayout
40
+ title="Signups are closed"
41
+ footer={
42
+ <span className="text-ink-muted">
43
+ Already have an account?{' '}
44
+ <Link to="/login" className="font-medium text-accent hover:underline">
45
+ Sign in
46
+ </Link>
47
+ </span>
48
+ }
49
+ >
50
+ <p className="mt-5 text-ink-muted">
51
+ This instance is not accepting new accounts right now. If you were invited to a
52
+ workspace, use the link from your invitation email instead.
53
+ </p>
54
+ </AuthLayout>
55
+ )
56
+ }
57
+
32
58
  function submit(event: FormEvent): void {
33
59
  event.preventDefault()
34
60
 
@@ -94,6 +120,12 @@ export function SignUpPage(): React.JSX.Element {
94
120
  {signUp.error !== null && <ErrorPanel error={signUp.error} />}
95
121
  <SubmitButton label="Continue" pendingLabel="Creating…" isPending={signUp.isPending} />
96
122
  </form>
123
+ {/*
124
+ `/dashboard` rather than the verification screen a password signup goes
125
+ to: an account a module provisions is verified already, and having no
126
+ workspace, `SessionGate` sends it on to the workspace step itself.
127
+ */}
128
+ <AuthMethods intent="signup" next="/dashboard" />
97
129
  </AuthLayout>
98
130
  )
99
131
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Where a signed-out page sends the browser once it is signed in.
3
+ *
4
+ * Its own file rather than beside the form controls: a module exporting a
5
+ * component and a function from one file loses Fast Refresh, and this is
6
+ * exported to modules, which is the reason it is not page-local. A module
7
+ * contributing a sign-in method is handed the result and has to be able to
8
+ * rely on what it means.
9
+ */
10
+
11
+ /**
12
+ * Only a path within this app. An absolute URL in `?next=` would turn a
13
+ * sign-in page into an open redirect.
14
+ */
15
+ export function safeNext(value: string | null): string {
16
+ return value !== null && value.startsWith('/') && !value.startsWith('//') ? value : '/dashboard'
17
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The URL a region switcher navigates to: the other origin, plus the path,
3
+ * query, and hash the reader is already on.
4
+ */
5
+ export function regionHref(
6
+ origin: string,
7
+ location: Pick<Location, 'pathname' | 'search' | 'hash'>,
8
+ ): string {
9
+ return `${origin}${location.pathname}${location.search}${location.hash}`
10
+ }
@@ -7,7 +7,7 @@ import { useWorkspace } from '../../api/resources/workspace.ts'
7
7
  import { AgentTasks } from '../../components/AgentTasks.tsx'
8
8
  import { PageHeader } from '../../components/PageHeader.tsx'
9
9
  import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
10
- import { attentionRows, briefLines } from './attention.ts'
10
+ import { attentionRows } from './attention.ts'
11
11
  import { ActivityFeed, AttentionList, DecisionsList, NotesList } from './sections.tsx'
12
12
 
13
13
  /**
@@ -19,37 +19,34 @@ import { ActivityFeed, AttentionList, DecisionsList, NotesList } from './section
19
19
  * for its name, and the team, to turn an author id into a person.
20
20
  */
21
21
 
22
- /** The heading date, written in the workspace's own day rather than the browser's. */
23
- function briefHeading(dashboard: Dashboard): string {
24
- return new Intl.DateTimeFormat(undefined, {
25
- weekday: 'long',
26
- month: 'short',
27
- day: 'numeric',
28
- timeZone: dashboard.timezone,
29
- }).format(dashboard.generatedAt)
30
- }
31
-
32
22
  export function DashboardPage(): React.JSX.Element {
33
23
  const { dashboard, isLoading, error } = useDashboard()
34
24
  const { workspace } = useWorkspace()
35
25
  const { nameById } = useMembers()
36
26
  const timezone = useTimezone()
27
+ const workspaceName = workspace?.name ?? 'This workspace'
37
28
 
38
29
  return (
39
30
  <div className="animate-fade-in">
40
- <PageHeader title="Dashboard" description="What needs attention across this workspace." />
31
+ <PageHeader
32
+ title="Dashboard"
33
+ description="What needs attention across this workspace."
34
+ actions={
35
+ workspace?.id !== undefined ? (
36
+ <AgentTasks
37
+ targetType="workspace"
38
+ targetId={workspace.id}
39
+ targetLabel={workspaceName}
40
+ />
41
+ ) : undefined
42
+ }
43
+ />
41
44
 
42
45
  {error !== null && <ErrorPanel error={error} />}
43
46
  {isLoading && <LoadingPanel label="Loading the workspace…" />}
44
47
 
45
48
  {dashboard !== undefined && (
46
- <DashboardBody
47
- dashboard={dashboard}
48
- workspaceId={workspace?.id}
49
- workspaceName={workspace?.name ?? 'This workspace'}
50
- nameById={nameById}
51
- timezone={timezone}
52
- />
49
+ <DashboardBody dashboard={dashboard} nameById={nameById} timezone={timezone} />
53
50
  )}
54
51
  </div>
55
52
  )
@@ -57,14 +54,10 @@ export function DashboardPage(): React.JSX.Element {
57
54
 
58
55
  function DashboardBody({
59
56
  dashboard,
60
- workspaceId,
61
- workspaceName,
62
57
  nameById,
63
58
  timezone,
64
59
  }: {
65
60
  readonly dashboard: Dashboard
66
- readonly workspaceId: string | undefined
67
- readonly workspaceName: string
68
61
  readonly nameById: ReadonlyMap<string, string>
69
62
  readonly timezone: string
70
63
  }): React.JSX.Element {
@@ -75,31 +68,6 @@ function DashboardBody({
75
68
 
76
69
  return (
77
70
  <div>
78
- <section className="mb-8 border-b border-border pb-6">
79
- <div className="flex flex-wrap items-start justify-between gap-3">
80
- <div>
81
- <p className="text-[11px] text-ink-faint">Daily brief</p>
82
- <h2 className="mt-0.5 text-[15px] font-semibold tracking-tight text-ink">
83
- {workspaceName} · {briefHeading(dashboard)}
84
- </h2>
85
- <p className="mt-1 text-[12px] text-ink-muted">
86
- Assembled from overdue Plans, partnership touchpoints, and contacts going cold.
87
- </p>
88
- </div>
89
- {workspaceId !== undefined && (
90
- <AgentTasks targetType="workspace" targetId={workspaceId} targetLabel={workspaceName} />
91
- )}
92
- </div>
93
- <ul className="mt-4 space-y-1.5">
94
- {briefLines(dashboard).map((line) => (
95
- <li key={line} className="flex gap-2 text-[13px] leading-relaxed text-ink">
96
- <span className="mt-[9px] h-1 w-1 shrink-0 rounded-full bg-accent" />
97
- <span>{line}</span>
98
- </li>
99
- ))}
100
- </ul>
101
- </section>
102
-
103
71
  <div className="mb-8 grid gap-8 lg:grid-cols-2">
104
72
  <AttentionList rows={attentionRows(dashboard)} />
105
73
  <ActivityFeed
@@ -3,16 +3,16 @@ import type {
3
3
  DashboardPlanItem,
4
4
  DashboardStaleContact,
5
5
  DashboardTouchpoint,
6
- PipelineKind,
6
+ DashboardUpcomingEvent,
7
7
  RecordTargetType,
8
8
  } from '@kelpie/schemas'
9
9
 
10
10
  import type { ChipTone } from '../../components/Chip.tsx'
11
- import { formatDay } from '../../lib/dates.ts'
11
+ import { formatDate, formatDay } from '../../lib/dates.ts'
12
12
 
13
13
  /**
14
- * Reading the workspace snapshot as a page: one attention list, and the brief
15
- * above it.
14
+ * Reading the workspace snapshot as a page: one attention list from the
15
+ * signals the service already ranked.
16
16
  *
17
17
  * Pure functions over the decoded response, so the page renders and these are
18
18
  * tested separately. Nothing here recomputes a signal: the service already
@@ -30,6 +30,8 @@ const ROUTES: Readonly<Record<RecordTargetType, string | undefined>> = {
30
30
  raise: '/fundraising',
31
31
  enquiry: '/enquiries',
32
32
  candidate: undefined,
33
+ event: '/events',
34
+ attendance: undefined,
33
35
  }
34
36
 
35
37
  const TARGET_TYPE_LABELS: Readonly<Record<RecordTargetType, string>> = {
@@ -41,6 +43,8 @@ const TARGET_TYPE_LABELS: Readonly<Record<RecordTargetType, string>> = {
41
43
  raise: 'Fundraising',
42
44
  enquiry: 'Enquiry',
43
45
  candidate: 'Candidate',
46
+ event: 'Event',
47
+ attendance: 'Attendance',
44
48
  }
45
49
 
46
50
  export function targetTypeLabel(targetType: RecordTargetType): string {
@@ -115,8 +119,22 @@ function staleContactRow(contact: DashboardStaleContact): AttentionRow {
115
119
  }
116
120
  }
117
121
 
122
+ function upcomingEventRow(event: DashboardUpcomingEvent, timezone: string): AttentionRow {
123
+ const where = event.location.length > 0 ? event.location : event.format
124
+
125
+ return {
126
+ id: `event-${event.id}`,
127
+ label: 'Upcoming event',
128
+ tone: 'accent',
129
+ meta: formatDate(event.startsAt, timezone),
130
+ title: event.name,
131
+ detail: `${String(event.attendeeCount)} registered · ${where}`,
132
+ href: `/events/${event.id}`,
133
+ }
134
+ }
135
+
118
136
  /**
119
- * The signals behind the brief, most urgent first.
137
+ * The attention list, most urgent first.
120
138
  *
121
139
  * The groups are concatenated rather than sorted: each arrives from the API in
122
140
  * its own order — plan items soonest first, contacts coldest first — and a
@@ -131,79 +149,7 @@ export function attentionRows(dashboard: Dashboard): readonly AttentionRow[] {
131
149
  ...touchpoints.filter((touchpoint) => touchpoint.overdue).map(touchpointRow),
132
150
  ...dashboard.dueSoonPlanItems.items.map((item) => planItemRow(item, false)),
133
151
  ...touchpoints.filter((touchpoint) => !touchpoint.overdue).map(touchpointRow),
152
+ ...dashboard.upcomingEvents.items.map((item) => upcomingEventRow(item, dashboard.timezone)),
134
153
  ...dashboard.staleContacts.items.map(staleContactRow),
135
154
  ]
136
155
  }
137
-
138
- function plural(count: number, singular: string, pluralForm = `${singular}s`): string {
139
- return `${String(count)} ${count === 1 ? singular : pluralForm}`
140
- }
141
-
142
- /**
143
- * How a count of open records reads mid-sentence.
144
- *
145
- * Not `PIPELINE_KIND_LABELS`: that one is nav wording, where a Raise is filed
146
- * under "Fundraising", and "3 fundraisings" is not a sentence. Written out
147
- * rather than suffixed, because "opportunitys" is not one either.
148
- */
149
- const OPEN_PIPELINE_NOUNS: Readonly<
150
- Record<PipelineKind, { readonly one: string; readonly many: string }>
151
- > = {
152
- enquiry: { one: 'enquiry', many: 'enquiries' },
153
- deal: { one: 'deal', many: 'deals' },
154
- opportunity: { one: 'opportunity', many: 'opportunities' },
155
- raise: { one: 'raise', many: 'raises' },
156
- partnership: { one: 'partnership', many: 'partnerships' },
157
- }
158
-
159
- /**
160
- * The brief, built from the totals rather than from the rows.
161
- *
162
- * A list is capped by the request's limit and a total is not, so counting the
163
- * rows on screen would understate every workspace with more than a page of them.
164
- */
165
- export function briefLines(dashboard: Dashboard): readonly string[] {
166
- const lines: string[] = []
167
-
168
- if (dashboard.overduePlanItems.total > 0) {
169
- lines.push(
170
- `${plural(dashboard.overduePlanItems.total, 'plan item')} overdue — triage Planning first.`,
171
- )
172
- }
173
-
174
- if (dashboard.dueSoonPlanItems.total > 0) {
175
- lines.push(
176
- `${plural(dashboard.dueSoonPlanItems.total, 'plan item')} due in the next ${plural(dashboard.upcomingDays, 'day')}.`,
177
- )
178
- }
179
-
180
- if (dashboard.partnershipTouchpoints.total > 0) {
181
- lines.push(
182
- `${plural(dashboard.partnershipTouchpoints.total, 'partnership touchpoint')} at hand.`,
183
- )
184
- }
185
-
186
- if (dashboard.staleContacts.total > 0) {
187
- lines.push(
188
- `${plural(dashboard.staleContacts.total, 'contact')} past the ${String(dashboard.staleContactDays)}-day touch threshold.`,
189
- )
190
- }
191
-
192
- const open = dashboard.pipelines.filter((pipeline) => pipeline.open > 0)
193
-
194
- if (open.length > 0) {
195
- lines.push(
196
- `Open: ${open
197
- .map((pipeline) => {
198
- const noun = OPEN_PIPELINE_NOUNS[pipeline.kind]
199
-
200
- return plural(pipeline.open, noun.one, noun.many)
201
- })
202
- .join(', ')}.`,
203
- )
204
- }
205
-
206
- return lines.length === 0
207
- ? ['Nothing urgent. Pipeline and relationships look quiet today.']
208
- : lines
209
- }
@@ -9,6 +9,7 @@ import type { ReactNode } from 'react'
9
9
  import { Link } from 'react-router'
10
10
 
11
11
  import { Chip } from '../../components/Chip.tsx'
12
+ import { MarkdownView } from '../../components/MarkdownView.tsx'
12
13
  import { SectionHeader } from '../../components/SectionHeader.tsx'
13
14
  import { formatDate, formatRelativeTime } from '../../lib/dates.ts'
14
15
  import { targetHref, targetTypeLabel } from './attention.ts'
@@ -72,7 +73,10 @@ function EmptyOr({
72
73
  export function AttentionList({ rows }: { readonly rows: readonly AttentionRow[] }): React.JSX.Element {
73
74
  return (
74
75
  <section>
75
- <SectionHeader title="Needs attention" description="The signals behind the brief." />
76
+ <SectionHeader
77
+ title="Needs attention"
78
+ description="Overdue Plans, partnership touchpoints, and contacts going cold."
79
+ />
76
80
  <EmptyOr isEmpty={rows.length === 0} empty="Nothing urgent right now.">
77
81
  <ul className="divide-y divide-border border-y border-border">
78
82
  {rows.map((row) => (
@@ -201,7 +205,9 @@ export function NotesList({
201
205
  {notes.map((note) => (
202
206
  <li key={note.id} className="py-3">
203
207
  <div className="flex items-start justify-between gap-2">
204
- <p className="line-clamp-3 text-[13px] leading-relaxed text-ink">{note.body}</p>
208
+ <div className="line-clamp-3 min-w-0 flex-1">
209
+ <MarkdownView source={note.body} />
210
+ </div>
205
211
  {note.pinned && (
206
212
  <span className="shrink-0 text-[10px] font-medium tracking-wide text-accent uppercase">
207
213
  Pinned
@@ -1,9 +1,10 @@
1
- import { PIPELINE_KINDS } from '@kelpie/schemas'
2
- import type { Form, FormAttachTarget, PipelineKind } from '@kelpie/schemas'
1
+ import { FORM_ATTACH_TARGET_TYPES, PLAN_ITEM_TARGET_TYPE_LABELS } from '@kelpie/schemas'
2
+ import type { Form, FormAttachTarget, FormAttachTargetType, PipelineKind } from '@kelpie/schemas'
3
3
  import { useMemo, useState } from 'react'
4
4
 
5
5
  import { useDeals } from '../../api/resources/deals.ts'
6
6
  import { useEnquiries } from '../../api/resources/enquiries.ts'
7
+ import { useEvents } from '../../api/resources/events.ts'
7
8
  import { useUpdateForm } from '../../api/resources/forms.ts'
8
9
  import { useLists } from '../../api/resources/lists.ts'
9
10
  import { useMembers } from '../../api/resources/members.ts'
@@ -400,12 +401,13 @@ interface AttachRecord {
400
401
  readonly name: string
401
402
  }
402
403
 
403
- function useAttachOptions(kind: PipelineKind): readonly AttachRecord[] {
404
+ function useAttachOptions(kind: FormAttachTargetType): readonly AttachRecord[] {
404
405
  const deals = useDeals({}, { enabled: kind === 'deal' })
405
406
  const opportunities = useOpportunities({}, { enabled: kind === 'opportunity' })
406
407
  const raises = useRaises({}, { enabled: kind === 'raise' })
407
408
  const partnerships = usePartnerships({}, { enabled: kind === 'partnership' })
408
409
  const enquiries = useEnquiries({}, { enabled: kind === 'enquiry' })
410
+ const events = useEvents({}, { enabled: kind === 'event' })
409
411
 
410
412
  switch (kind) {
411
413
  case 'deal':
@@ -421,6 +423,8 @@ function useAttachOptions(kind: PipelineKind): readonly AttachRecord[] {
421
423
  }))
422
424
  case 'enquiry':
423
425
  return enquiries.records.map((enquiry) => ({ id: enquiry.id, name: enquiry.name }))
426
+ case 'event':
427
+ return events.records.map((record) => ({ id: record.id, name: record.name }))
424
428
  }
425
429
  }
426
430
 
@@ -431,7 +435,7 @@ function AttachTargetsBlock({
431
435
  readonly form: Form
432
436
  readonly onChange: (next: readonly FormAttachTarget[]) => void
433
437
  }): React.JSX.Element {
434
- const [kind, setKind] = useState<PipelineKind>('deal')
438
+ const [kind, setKind] = useState<FormAttachTargetType>('deal')
435
439
  const [pick, setPick] = useState('')
436
440
  const options = useAttachOptions(kind)
437
441
  const chosen = form.attachTargets
@@ -459,7 +463,9 @@ function AttachTargetsBlock({
459
463
  <div className="space-y-2 rounded-md border border-border p-4">
460
464
  <div className="text-[13px] font-medium text-ink">Attach the submitter to a record</div>
461
465
  <p className="text-[11px] text-ink-faint">
462
- Every submitter is linked into these records through <code>person_links</code>.
466
+ Pipeline records receive a <code>person_links</code> row. An Event
467
+ registers Attendance instead: the submitter is marked registered, and a
468
+ second submit of the same person is a no-op.
463
469
  </p>
464
470
  <ul className="divide-y divide-border">
465
471
  {chosen.length === 0 && (
@@ -496,13 +502,13 @@ function AttachTargetsBlock({
496
502
  className={inputClass}
497
503
  value={kind}
498
504
  onChange={(event) => {
499
- setKind(event.target.value as PipelineKind)
505
+ setKind(event.target.value as FormAttachTargetType)
500
506
  setPick('')
501
507
  }}
502
508
  >
503
- {PIPELINE_KINDS.map((option) => (
509
+ {FORM_ATTACH_TARGET_TYPES.map((option) => (
504
510
  <option key={option} value={option}>
505
- {option}
511
+ {PLAN_ITEM_TARGET_TYPE_LABELS[option]}
506
512
  </option>
507
513
  ))}
508
514
  </select>
@@ -1,45 +1,47 @@
1
- import { useNavigate } from 'react-router'
1
+ import { Navigate, useNavigate, useSearchParams } from 'react-router'
2
2
 
3
3
  import { useHandbookPages } from '../../api/resources/handbookPages.ts'
4
4
  import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
5
- import { PrimaryButton } from '../auth/AuthForm.tsx'
6
5
  import { AuthLayout } from '../auth/AuthLayout.tsx'
6
+ import { OnboardingNav } from './OnboardingNav.tsx'
7
+ import { isOnboardingRerun } from './onboardingRerun.ts'
7
8
 
8
9
  /**
9
- * Onboarding step 3: the starter handbook.
10
+ * Onboarding step 5: review the starter handbook.
10
11
  *
11
- * The mockup's button created these pages. Against the real API they already
12
- * exist: `POST /v1/workspaces` seeded them in the same transaction that created
13
- * the workspace, two steps ago. So this reads them back and shows what is there.
12
+ * Pages were seeded on step 2 when the reader picked an organisation type.
13
+ * This step reads them back and shows what agents will learn from.
14
14
  *
15
- * That is the deviation, and it is the honest one. A button labelled "create"
16
- * that creates nothing teaches a reader something false about the product, and
17
- * the alternative — deleting the step — would drop the one screen that tells a
18
- * new workspace its handbook is what agents read.
15
+ * A rerun skips this step: the pages are already in place.
19
16
  */
20
17
  export function HandbookStepPage(): React.JSX.Element {
21
18
  const navigate = useNavigate()
19
+ const [searchParams] = useSearchParams()
22
20
  const { records: pages, isLoading, error } = useHandbookPages()
23
21
 
22
+ if (isOnboardingRerun(searchParams)) {
23
+ return <Navigate to="/dashboard" replace />
24
+ }
25
+
24
26
  function finish(): void {
25
- // The wizard ends on the dashboard, per `onboarding.md`. It is empty for a
26
- // brand new workspace, which is the point: it says what there is to fill in.
27
27
  navigate('/dashboard', { replace: true })
28
28
  }
29
29
 
30
+ const sortedPages = [...pages].sort((left, right) => left.sortOrder - right.sortOrder)
31
+
30
32
  return (
31
33
  <AuthLayout
32
- step={3}
34
+ step={5}
33
35
  title="Your starter handbook"
34
- description="These markdown pages are ready. Agents read them to learn what your company is."
36
+ description="These markdown pages were chosen for your organisation type. Agents read them to learn what your company is."
35
37
  >
36
38
  <div className="mt-5 space-y-4">
37
39
  {error !== null && <ErrorPanel error={error} />}
38
40
  {error === null && isLoading && <LoadingPanel label="Loading your handbook…" />}
39
41
 
40
- {error === null && !isLoading && pages.length > 0 && (
42
+ {error === null && !isLoading && sortedPages.length > 0 && (
41
43
  <ul className="max-h-56 overflow-y-auto rounded-md border border-border bg-surface">
42
- {pages.map((page) => (
44
+ {sortedPages.map((page) => (
43
45
  <li
44
46
  key={page.id}
45
47
  className="border-b border-border px-3 py-2 text-[13px] text-ink last:border-0"
@@ -50,25 +52,20 @@ export function HandbookStepPage(): React.JSX.Element {
50
52
  </ul>
51
53
  )}
52
54
 
53
- {/* An empty box under a heading that says the pages are ready would
54
- assert one thing and show another. The handbook is seeded with the
55
- workspace, so nothing here is the seeding having not happened. */}
56
- {error === null && !isLoading && pages.length === 0 && (
55
+ {error === null && !isLoading && sortedPages.length === 0 && (
57
56
  <p className="rounded-md border border-border bg-surface px-3 py-2 text-[13px] text-ink-muted">
58
57
  This workspace has no handbook pages. That is not what a new workspace should look
59
58
  like — the Handbook tab is where to add them.
60
59
  </p>
61
60
  )}
62
61
 
63
- {pages.length > 0 && (
62
+ {sortedPages.length > 0 && (
64
63
  <p className="text-[12px] text-ink-muted">
65
64
  Every page starts as a stub. Write them from the Handbook tab whenever you are ready.
66
65
  </p>
67
66
  )}
68
67
 
69
- {/* Not a submit: the work behind this step already happened, so the
70
- button only moves the reader on. */}
71
- <PrimaryButton label="Go to Kelpie" onClick={finish} />
68
+ <OnboardingNav step={5} nextLabel="Go to Kelpie" nextType="button" onNext={finish} />
72
69
  </div>
73
70
  </AuthLayout>
74
71
  )
@@ -2,14 +2,16 @@ import { INVITABLE_ROLES } from '@kelpie/schemas'
2
2
  import type { InvitableRole } from '@kelpie/schemas'
3
3
  import { useState } from 'react'
4
4
  import type { FormEvent } from 'react'
5
- import { useNavigate } from 'react-router'
5
+ import { useNavigate, useSearchParams } from 'react-router'
6
6
 
7
7
  import { useSendInvite } from '../../api/resources/invites.ts'
8
- import { AUTH_INPUT_CLASS, SecondaryButton, SubmitButton } from '../auth/AuthForm.tsx'
8
+ import { AUTH_INPUT_CLASS, SecondaryButton } from '../auth/AuthForm.tsx'
9
9
  import { AuthLayout } from '../auth/AuthLayout.tsx'
10
+ import { OnboardingNav } from './OnboardingNav.tsx'
11
+ import { isOnboardingRerun } from './onboardingRerun.ts'
10
12
 
11
13
  /**
12
- * Onboarding step 2: invitations, against `POST /v1/workspaces/:id/invites`.
14
+ * Onboarding step 4: invitations, against `POST /v1/workspaces/:id/invites`.
13
15
  *
14
16
  * One request per address, and they can fail one at a time — an address already
15
17
  * invited answers `409` while its neighbours succeed. The mockup could ignore
@@ -38,9 +40,16 @@ function isOutstanding(row: InviteRow): boolean {
38
40
 
39
41
  export function InvitesStepPage(): React.JSX.Element {
40
42
  const navigate = useNavigate()
43
+ const [searchParams] = useSearchParams()
44
+ const rerun = isOnboardingRerun(searchParams)
41
45
  const sendInvite = useSendInvite()
42
46
  const [rows, setRows] = useState<readonly InviteRow[]>([emptyRow()])
43
47
 
48
+ /** First run reviews the handbook next; a rerun leaves those pages alone. */
49
+ function afterInvites(): void {
50
+ navigate(rerun ? '/dashboard' : '/onboarding/handbook', { replace: true })
51
+ }
52
+
44
53
  function changeRow(index: number, changes: Partial<InviteRow>): void {
45
54
  setRows((previous) =>
46
55
  previous.map((row, at) => (at === index ? { ...row, ...changes } : row)),
@@ -80,7 +89,7 @@ export function InvitesStepPage(): React.JSX.Element {
80
89
  sendOutstanding()
81
90
  .then((allSent) => {
82
91
  if (allSent) {
83
- navigate('/onboarding/handbook', { replace: true })
92
+ afterInvites()
84
93
  }
85
94
  })
86
95
  .catch(() => undefined)
@@ -90,7 +99,7 @@ export function InvitesStepPage(): React.JSX.Element {
90
99
 
91
100
  return (
92
101
  <AuthLayout
93
- step={2}
102
+ step={4}
94
103
  title="Invite teammates"
95
104
  description="Optional. You can always invite people later from Admin → Team."
96
105
  >
@@ -117,20 +126,19 @@ export function InvitesStepPage(): React.JSX.Element {
117
126
  Add another
118
127
  </button>
119
128
 
120
- <div className="flex flex-col gap-2 pt-1">
121
- <SubmitButton
122
- label={outstanding === 0 ? 'Continue' : 'Send invitations'}
123
- pendingLabel="Sending…"
124
- isPending={sendInvite.isPending}
125
- />
126
- <SecondaryButton
127
- label="Skip for now"
128
- disabled={sendInvite.isPending}
129
- onClick={() => {
130
- navigate('/onboarding/handbook', { replace: true })
131
- }}
132
- />
133
- </div>
129
+ <OnboardingNav
130
+ step={4}
131
+ nextLabel={outstanding === 0 ? 'Next' : 'Send invitations'}
132
+ nextPendingLabel="Sending…"
133
+ isPending={sendInvite.isPending}
134
+ extra={
135
+ <SecondaryButton
136
+ label="Skip for now"
137
+ disabled={sendInvite.isPending}
138
+ onClick={afterInvites}
139
+ />
140
+ }
141
+ />
134
142
  </form>
135
143
  </AuthLayout>
136
144
  )