@kelpie/ui 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/dist/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 +21 -4
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +12 -2
  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/admin/DataPage.js +10 -9
  156. package/dist/pages/admin/DataPage.js.map +1 -1
  157. package/dist/pages/admin/FieldsPage.js +1 -1
  158. package/dist/pages/admin/FieldsPage.js.map +1 -1
  159. package/dist/pages/admin/ModulesPage.d.ts.map +1 -1
  160. package/dist/pages/admin/ModulesPage.js +1 -0
  161. package/dist/pages/admin/ModulesPage.js.map +1 -1
  162. package/dist/pages/admin/WorkspaceSettingsPage.d.ts +12 -0
  163. package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
  164. package/dist/pages/admin/WorkspaceSettingsPage.js +2 -21
  165. package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
  166. package/dist/pages/auth/AuthLayout.d.ts +5 -4
  167. package/dist/pages/auth/AuthLayout.d.ts.map +1 -1
  168. package/dist/pages/auth/AuthLayout.js +7 -5
  169. package/dist/pages/auth/AuthLayout.js.map +1 -1
  170. package/dist/pages/auth/RegionSwitcher.d.ts +10 -0
  171. package/dist/pages/auth/RegionSwitcher.d.ts.map +1 -0
  172. package/dist/pages/auth/RegionSwitcher.js +28 -0
  173. package/dist/pages/auth/RegionSwitcher.js.map +1 -0
  174. package/dist/pages/auth/SignInPage.d.ts.map +1 -1
  175. package/dist/pages/auth/SignInPage.js +3 -1
  176. package/dist/pages/auth/SignInPage.js.map +1 -1
  177. package/dist/pages/auth/SignUpPage.d.ts.map +1 -1
  178. package/dist/pages/auth/SignUpPage.js +7 -0
  179. package/dist/pages/auth/SignUpPage.js.map +1 -1
  180. package/dist/pages/auth/regionHref.d.ts +6 -0
  181. package/dist/pages/auth/regionHref.d.ts.map +1 -0
  182. package/dist/pages/auth/regionHref.js +8 -0
  183. package/dist/pages/auth/regionHref.js.map +1 -0
  184. package/dist/pages/dashboard/DashboardPage.d.ts +8 -0
  185. package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -1
  186. package/dist/pages/dashboard/DashboardPage.js +5 -13
  187. package/dist/pages/dashboard/DashboardPage.js.map +1 -1
  188. package/dist/pages/dashboard/attention.d.ts +1 -8
  189. package/dist/pages/dashboard/attention.d.ts.map +1 -1
  190. package/dist/pages/dashboard/attention.js +21 -54
  191. package/dist/pages/dashboard/attention.js.map +1 -1
  192. package/dist/pages/dashboard/sections.d.ts.map +1 -1
  193. package/dist/pages/dashboard/sections.js +3 -2
  194. package/dist/pages/dashboard/sections.js.map +1 -1
  195. package/dist/pages/forms/FormActions.d.ts.map +1 -1
  196. package/dist/pages/forms/FormActions.js +7 -3
  197. package/dist/pages/forms/FormActions.js.map +1 -1
  198. package/dist/pages/onboarding/HandbookStepPage.d.ts +4 -8
  199. package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -1
  200. package/dist/pages/onboarding/HandbookStepPage.js +13 -13
  201. package/dist/pages/onboarding/HandbookStepPage.js.map +1 -1
  202. package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -1
  203. package/dist/pages/onboarding/InvitesStepPage.js +13 -7
  204. package/dist/pages/onboarding/InvitesStepPage.js.map +1 -1
  205. package/dist/pages/onboarding/ModulesStepPage.d.ts +11 -0
  206. package/dist/pages/onboarding/ModulesStepPage.d.ts.map +1 -0
  207. package/dist/pages/onboarding/ModulesStepPage.js +80 -0
  208. package/dist/pages/onboarding/ModulesStepPage.js.map +1 -0
  209. package/dist/pages/onboarding/OnboardingNav.d.ts +17 -0
  210. package/dist/pages/onboarding/OnboardingNav.d.ts.map +1 -0
  211. package/dist/pages/onboarding/OnboardingNav.js +37 -0
  212. package/dist/pages/onboarding/OnboardingNav.js.map +1 -0
  213. package/dist/pages/onboarding/OrganisationStepPage.d.ts +13 -0
  214. package/dist/pages/onboarding/OrganisationStepPage.d.ts.map +1 -0
  215. package/dist/pages/onboarding/OrganisationStepPage.js +59 -0
  216. package/dist/pages/onboarding/OrganisationStepPage.js.map +1 -0
  217. package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
  218. package/dist/pages/onboarding/WorkspaceStepPage.js +25 -20
  219. package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
  220. package/dist/pages/onboarding/handbookTemplates.d.ts +19 -0
  221. package/dist/pages/onboarding/handbookTemplates.d.ts.map +1 -0
  222. package/dist/pages/onboarding/handbookTemplates.js +57 -0
  223. package/dist/pages/onboarding/handbookTemplates.js.map +1 -0
  224. package/dist/pages/onboarding/onboardingModules.d.ts +23 -0
  225. package/dist/pages/onboarding/onboardingModules.d.ts.map +1 -0
  226. package/dist/pages/onboarding/onboardingModules.js +153 -0
  227. package/dist/pages/onboarding/onboardingModules.js.map +1 -0
  228. package/dist/pages/onboarding/onboardingRerun.d.ts +21 -0
  229. package/dist/pages/onboarding/onboardingRerun.d.ts.map +1 -0
  230. package/dist/pages/onboarding/onboardingRerun.js +33 -0
  231. package/dist/pages/onboarding/onboardingRerun.js.map +1 -0
  232. package/package.json +4 -3
  233. package/src/api/resources/attendances.ts +54 -0
  234. package/src/api/resources/eventAssociations.ts +43 -0
  235. package/src/api/resources/events.ts +70 -0
  236. package/src/api/resources/handbookSeed.ts +63 -0
  237. package/src/api/resources/moduleSettings.ts +25 -0
  238. package/src/api/resources/planItems.ts +5 -5
  239. package/src/api/resources/publicConfig.ts +3 -3
  240. package/src/api/resources/sampleData.ts +2 -0
  241. package/src/api/resources/session.ts +59 -0
  242. package/src/api/resources/workspace.ts +2 -3
  243. package/src/app/KelpieApp.tsx +11 -3
  244. package/src/components/ActivitiesPanel.tsx +2 -0
  245. package/src/components/AddressesField.tsx +502 -0
  246. package/src/components/CountrySearch.tsx +71 -0
  247. package/src/components/EnvironmentBanner.tsx +23 -6
  248. package/src/components/InlineEdit.tsx +35 -1
  249. package/src/components/LinkedEventsPanel.tsx +50 -0
  250. package/src/components/NotesPanel.tsx +132 -15
  251. package/src/components/PlanAttention.tsx +5 -4
  252. package/src/components/PlanPanel.tsx +3 -3
  253. package/src/components/Shell.tsx +16 -1
  254. package/src/components/SocialNetworkIcon.tsx +88 -0
  255. package/src/components/SocialProfilesField.tsx +57 -10
  256. package/src/components/TimezoneSearch.tsx +67 -0
  257. package/src/components/WorkspaceSwitcher.tsx +136 -0
  258. package/src/components/environmentTone.ts +40 -0
  259. package/src/index.ts +42 -3
  260. package/src/lib/timezones.ts +43 -0
  261. package/src/pages/CompaniesPage.tsx +14 -13
  262. package/src/pages/CompanyDetail.tsx +59 -27
  263. package/src/pages/DealDetail.tsx +5 -0
  264. package/src/pages/DecisionsPage.tsx +7 -0
  265. package/src/pages/EnquiryDetail.tsx +7 -0
  266. package/src/pages/EventDetail.tsx +794 -0
  267. package/src/pages/EventsPage.tsx +382 -0
  268. package/src/pages/ListDetail.tsx +24 -0
  269. package/src/pages/OpportunityDetail.tsx +7 -0
  270. package/src/pages/PartnershipDetail.tsx +7 -0
  271. package/src/pages/PeoplePage.tsx +18 -5
  272. package/src/pages/PersonDetail.tsx +300 -100
  273. package/src/pages/PlanningPage.tsx +17 -14
  274. package/src/pages/RaiseDetail.tsx +5 -0
  275. package/src/pages/RoleDetail.tsx +8 -0
  276. package/src/pages/SearchPage.tsx +2 -1
  277. package/src/pages/account/PreferencesPage.tsx +2 -37
  278. package/src/pages/admin/DataPage.tsx +63 -21
  279. package/src/pages/admin/FieldsPage.tsx +1 -1
  280. package/src/pages/admin/ModulesPage.tsx +1 -0
  281. package/src/pages/admin/WorkspaceSettingsPage.tsx +10 -33
  282. package/src/pages/auth/AuthLayout.tsx +8 -4
  283. package/src/pages/auth/RegionSwitcher.tsx +56 -0
  284. package/src/pages/auth/SignInPage.tsx +10 -6
  285. package/src/pages/auth/SignUpPage.tsx +25 -0
  286. package/src/pages/auth/regionHref.ts +10 -0
  287. package/src/pages/dashboard/DashboardPage.tsx +16 -48
  288. package/src/pages/dashboard/attention.ts +24 -78
  289. package/src/pages/dashboard/sections.tsx +8 -2
  290. package/src/pages/forms/FormActions.tsx +14 -8
  291. package/src/pages/onboarding/HandbookStepPage.tsx +21 -24
  292. package/src/pages/onboarding/InvitesStepPage.tsx +27 -19
  293. package/src/pages/onboarding/ModulesStepPage.tsx +145 -0
  294. package/src/pages/onboarding/OnboardingNav.tsx +97 -0
  295. package/src/pages/onboarding/OrganisationStepPage.tsx +130 -0
  296. package/src/pages/onboarding/WorkspaceStepPage.tsx +53 -38
  297. package/src/pages/onboarding/handbookTemplates.ts +77 -0
  298. package/src/pages/onboarding/onboardingModules.ts +202 -0
  299. package/src/pages/onboarding/onboardingRerun.ts +41 -0
  300. package/src/styles.css +30 -0
@@ -0,0 +1,50 @@
1
+ import type { EventAssociationTargetType } from '@kelpie/schemas'
2
+ import { Link } from 'react-router'
3
+
4
+ import { useEventAssociationsFor } from '../api/resources/eventAssociations.ts'
5
+ import { ErrorPanel } from './QueryState.tsx'
6
+ import { SectionHeader } from './SectionHeader.tsx'
7
+
8
+ /**
9
+ * Events associated with another record (a Deal, Company, Role, …).
10
+ *
11
+ * Registrants are Attendances on the Person, not this list.
12
+ */
13
+
14
+ export function LinkedEventsPanel({
15
+ targetType,
16
+ targetId,
17
+ }: {
18
+ readonly targetType: EventAssociationTargetType
19
+ readonly targetId: string
20
+ }): React.JSX.Element {
21
+ const { associations, isLoading, error } = useEventAssociationsFor(targetType, targetId)
22
+
23
+ if (error !== null) {
24
+ return <ErrorPanel error={error} />
25
+ }
26
+
27
+ return (
28
+ <div>
29
+ <SectionHeader title="Events" />
30
+ {isLoading && <p className="text-[13px] text-ink-faint">Loading events…</p>}
31
+ {!isLoading && associations.length === 0 && (
32
+ <p className="text-[13px] text-ink-faint">No events linked yet.</p>
33
+ )}
34
+ {associations.length > 0 && (
35
+ <ul className="divide-y divide-border">
36
+ {associations.map((row) => (
37
+ <li key={row.eventId} className="py-2">
38
+ <Link
39
+ to={`/events/${row.eventId}`}
40
+ className="text-[13px] font-medium text-accent hover:underline"
41
+ >
42
+ {row.eventName}
43
+ </Link>
44
+ </li>
45
+ ))}
46
+ </ul>
47
+ )}
48
+ </div>
49
+ )
50
+ }
@@ -4,8 +4,9 @@ import type { FormEvent } from 'react'
4
4
 
5
5
  import { useTimezone } from '../api/resources/account.ts'
6
6
  import { useMembers } from '../api/resources/members.ts'
7
- import { useCreateNote, useNotes } from '../api/resources/notes.ts'
7
+ import { useCreateNote, useDeleteNote, useNotes, useUpdateNote } from '../api/resources/notes.ts'
8
8
  import { formatDateTime } from '../lib/dates.ts'
9
+ import { MarkdownView } from './MarkdownView.tsx'
9
10
  import { Paginator } from './Paginator.tsx'
10
11
  import { ErrorPanel } from './QueryState.tsx'
11
12
  import { SectionHeader } from './SectionHeader.tsx'
@@ -13,12 +14,10 @@ import { SectionHeader } from './SectionHeader.tsx'
13
14
  /**
14
15
  * The notes on one record.
15
16
  *
16
- * Ports the mockup's panel. There is no pin control: the mockup renders the
17
- * badge on a note that carries the flag and offers no way to set it, and the
18
- * mockup decides what a page shows. `PATCH /v1/notes/:id` takes `pinned`, so an
19
- * agent can still pin.
20
- *
21
- * No edit or delete either, for the same reason, though the API has both.
17
+ * Bodies are markdown, rendered with the same component as the handbook.
18
+ * Notes can be created, edited, and deleted here. There is no pin control: the
19
+ * mockup renders the badge on a note that carries the flag and offers no way to
20
+ * set it. `PATCH /v1/notes/:id` takes `pinned`, so an agent can still pin.
22
21
  */
23
22
 
24
23
  export interface NotesPanelProps {
@@ -82,7 +81,7 @@ export function NotesPanel({ targetType, targetId }: NotesPanelProps): React.JSX
82
81
  onChange={(event) => {
83
82
  setBody(event.target.value)
84
83
  }}
85
- placeholder="Write a note…"
84
+ placeholder="Write a note… (Markdown supported)"
86
85
  rows={3}
87
86
  autoFocus
88
87
  className="w-full resize-y rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent focus:ring-2 focus:ring-accent/20"
@@ -143,22 +142,140 @@ function NoteItem({
143
142
  readonly authorName: string
144
143
  }): React.JSX.Element {
145
144
  const timezone = useTimezone()
145
+ const updateNote = useUpdateNote()
146
+ const deleteNote = useDeleteNote()
147
+ const [editing, setEditing] = useState(false)
148
+ const [confirmingDelete, setConfirmingDelete] = useState(false)
149
+ const [draft, setDraft] = useState(note.body)
150
+
151
+ function cancelEdit(): void {
152
+ setDraft(note.body)
153
+ setEditing(false)
154
+ }
155
+
156
+ function submitEdit(event: FormEvent): void {
157
+ event.preventDefault()
158
+
159
+ const text = draft.trim()
160
+
161
+ if (text.length === 0) {
162
+ return
163
+ }
164
+
165
+ if (text !== note.body) {
166
+ updateNote.run({ id: note.id, changes: { body: text } })
167
+ }
168
+
169
+ setEditing(false)
170
+ }
171
+
172
+ if (editing) {
173
+ return (
174
+ <li className="rounded-md border border-border bg-surface-raised px-3.5 py-3">
175
+ <form onSubmit={submitEdit} className="space-y-2">
176
+ <textarea
177
+ value={draft}
178
+ onChange={(event) => {
179
+ setDraft(event.target.value)
180
+ }}
181
+ rows={3}
182
+ autoFocus
183
+ className="w-full resize-y rounded-md border border-border bg-surface-raised px-3 py-2 text-[13px] outline-none focus:border-accent focus:ring-2 focus:ring-accent/20"
184
+ />
185
+ <div className="flex justify-end gap-2">
186
+ <button
187
+ type="button"
188
+ onClick={cancelEdit}
189
+ className="rounded-md px-2.5 py-1 text-[12px] font-medium text-ink-muted hover:text-ink"
190
+ >
191
+ Cancel
192
+ </button>
193
+ <button
194
+ type="submit"
195
+ className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover"
196
+ >
197
+ Save
198
+ </button>
199
+ </div>
200
+ </form>
201
+ {updateNote.error !== null && (
202
+ <div className="mt-2">
203
+ <ErrorPanel error={updateNote.error} />
204
+ </div>
205
+ )}
206
+ </li>
207
+ )
208
+ }
146
209
 
147
210
  return (
148
- <li className="rounded-md border border-border bg-surface-raised px-3.5 py-3">
211
+ <li className="group rounded-md border border-border bg-surface-raised px-3.5 py-3">
149
212
  <div className="flex items-start justify-between gap-2">
150
- <p className="text-[13px] leading-relaxed whitespace-pre-wrap text-ink">{note.body}</p>
151
- {note.pinned && (
152
- <span className="shrink-0 text-[10px] font-semibold tracking-wide text-accent uppercase">
153
- Pinned
154
- </span>
155
- )}
213
+ <div className="min-w-0 flex-1">
214
+ <MarkdownView source={note.body} />
215
+ </div>
216
+ <div className="flex shrink-0 flex-col items-end gap-1">
217
+ {note.pinned && (
218
+ <span className="text-[10px] font-semibold tracking-wide text-accent uppercase">Pinned</span>
219
+ )}
220
+ {confirmingDelete ? (
221
+ <div className="flex flex-wrap items-center justify-end gap-2">
222
+ <span className="text-[11px] text-ink-muted">Delete this note?</span>
223
+ <button
224
+ type="button"
225
+ onClick={() => {
226
+ setConfirmingDelete(false)
227
+ }}
228
+ className="rounded-md px-2 py-0.5 text-[11px] font-medium text-ink-muted hover:text-ink"
229
+ >
230
+ Cancel
231
+ </button>
232
+ <button
233
+ type="button"
234
+ disabled={deleteNote.isPending}
235
+ onClick={() => {
236
+ deleteNote.run(note.id)
237
+ setConfirmingDelete(false)
238
+ }}
239
+ className="rounded-md bg-danger px-2 py-0.5 text-[11px] font-semibold text-danger-fg transition hover:opacity-90 disabled:opacity-50"
240
+ >
241
+ {deleteNote.isPending ? 'Deleting…' : 'Delete note'}
242
+ </button>
243
+ </div>
244
+ ) : (
245
+ <span className="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100">
246
+ <button
247
+ type="button"
248
+ onClick={() => {
249
+ setDraft(note.body)
250
+ setEditing(true)
251
+ }}
252
+ className="rounded-md px-2 py-0.5 text-[11px] font-medium text-ink-muted hover:bg-surface-sunken hover:text-ink"
253
+ >
254
+ Edit
255
+ </button>
256
+ <button
257
+ type="button"
258
+ onClick={() => {
259
+ setConfirmingDelete(true)
260
+ }}
261
+ className="rounded-md px-2 py-0.5 text-[11px] font-medium text-ink-muted hover:bg-danger-soft hover:text-danger"
262
+ >
263
+ Delete
264
+ </button>
265
+ </span>
266
+ )}
267
+ </div>
156
268
  </div>
157
269
  <div className="mt-2 flex items-center gap-2 text-[11px] text-ink-faint">
158
270
  <span>{authorName}</span>
159
271
  <span>·</span>
160
272
  <span>{formatDateTime(note.createdAt, timezone)}</span>
161
273
  </div>
274
+ {deleteNote.error !== null && (
275
+ <div className="mt-2">
276
+ <ErrorPanel error={deleteNote.error} />
277
+ </div>
278
+ )}
162
279
  </li>
163
280
  )
164
281
  }
@@ -1,11 +1,11 @@
1
- import { PIPELINE_KIND_LABELS, PLAN_ITEM_STATUS_LABELS } from '@kelpie/schemas'
1
+ import { PLAN_ITEM_STATUS_LABELS, PLAN_ITEM_TARGET_TYPE_LABELS } from '@kelpie/schemas'
2
2
  import type {
3
3
  Deal,
4
4
  Enquiry,
5
5
  Opportunity,
6
6
  Partnership,
7
- PipelineKind,
8
7
  PlanItem,
8
+ PlanItemTargetType,
9
9
  Raise,
10
10
  } from '@kelpie/schemas'
11
11
  import { Link } from 'react-router'
@@ -27,12 +27,13 @@ import { SectionHeader } from './SectionHeader.tsx'
27
27
  */
28
28
 
29
29
  /** Where a plan item's record lives. */
30
- const ROUTES: Readonly<Record<PipelineKind, string | undefined>> = {
30
+ const ROUTES: Readonly<Record<PlanItemTargetType, string | undefined>> = {
31
31
  enquiry: '/enquiries',
32
32
  deal: '/deals',
33
33
  opportunity: '/opportunities',
34
34
  raise: '/fundraising',
35
35
  partnership: '/partnerships',
36
+ event: '/events',
36
37
  }
37
38
 
38
39
  export interface PlanAttentionProps {
@@ -247,7 +248,7 @@ export function PlanTargetLink({
247
248
  readonly item: PlanItem
248
249
  readonly targetNames: ReadonlyMap<string, string> | undefined
249
250
  }): React.JSX.Element {
250
- const kind = PIPELINE_KIND_LABELS[item.targetType]
251
+ const kind = PLAN_ITEM_TARGET_TYPE_LABELS[item.targetType]
251
252
  const name = targetNames?.get(item.targetId)
252
253
  const route = ROUTES[item.targetType]
253
254
 
@@ -1,5 +1,5 @@
1
1
  import { PLAN_ITEM_STATUS_LABELS, PLAN_ITEM_STATUSES } from '@kelpie/schemas'
2
- import type { PipelineKind, PlanItem, PlanItemStatus } from '@kelpie/schemas'
2
+ import type { PlanItem, PlanItemStatus, PlanItemTargetType } from '@kelpie/schemas'
3
3
  import { useState } from 'react'
4
4
  import type { FormEvent } from 'react'
5
5
 
@@ -18,7 +18,7 @@ import { ErrorPanel } from './QueryState.tsx'
18
18
  import { SectionHeader } from './SectionHeader.tsx'
19
19
 
20
20
  /**
21
- * The plan for one pipeline record: what happens next, when, and who has it.
21
+ * The plan for one record: what happens next, when, and who has it.
22
22
  *
23
23
  * Ported from the mockup's panel, edit and delete included. Notes have neither,
24
24
  * because the mockup's note panel offers neither; this one offers both, so both
@@ -30,7 +30,7 @@ import { SectionHeader } from './SectionHeader.tsx'
30
30
  */
31
31
 
32
32
  export interface PlanPanelProps {
33
- readonly targetType: PipelineKind
33
+ readonly targetType: PlanItemTargetType
34
34
  readonly targetId: string
35
35
  }
36
36
 
@@ -5,10 +5,12 @@ import { useAccount, useTheme } from '../api/resources/account.ts'
5
5
  import { useLogOut } from '../api/resources/session.ts'
6
6
  import { initialsOf } from '../lib/names.ts'
7
7
  import type { ThemePreference } from '../lib/theme.ts'
8
+ import { ONBOARDING_RERUN_ENTRY } from '../pages/onboarding/onboardingRerun.ts'
8
9
  import { useNavItems } from '../registry/context.ts'
9
10
  import type { NavItem } from '../registry/contributions.ts'
10
11
  import { useVisibleNavItems } from '../registry/visibleNav.ts'
11
12
  import { ErrorBoundary } from './ErrorBoundary.tsx'
13
+ import { WorkspaceSwitcher } from './WorkspaceSwitcher.tsx'
12
14
 
13
15
  /**
14
16
  * Sidebar, header, and the outlet every page renders into.
@@ -40,6 +42,7 @@ const CORE_NAV: readonly NavItem[] = [
40
42
  { id: 'deals', label: 'Deals', to: '/deals', order: 300 },
41
43
  { id: 'opportunities', label: 'Opportunities', to: '/opportunities', order: 350 },
42
44
  { id: 'fundraising', label: 'Fundraising', to: '/fundraising', order: 360 },
45
+ { id: 'events', label: 'Events', to: '/events', order: 370 },
43
46
  { id: 'partnerships', label: 'Partnerships', to: '/partnerships', order: 375 },
44
47
  { id: 'lists', label: 'Lists', to: '/lists', order: 378 },
45
48
  { id: 'forms', label: 'Forms', to: '/forms', order: 380 },
@@ -260,6 +263,7 @@ export function Shell(): React.JSX.Element {
260
263
  >
261
264
  {THEME_LABELS[theme]}
262
265
  </button>
266
+ <WorkspaceSwitcher />
263
267
  <div className="relative" ref={menuRef}>
264
268
  <button
265
269
  type="button"
@@ -302,10 +306,21 @@ export function Shell(): React.JSX.Element {
302
306
  onClick={() => {
303
307
  setMenuOpen(false)
304
308
  }}
305
- className="block border-b border-border px-3 py-1.5 text-left text-[13px] text-ink hover:bg-surface-sunken"
309
+ className="block px-3 py-1.5 text-left text-[13px] text-ink hover:bg-surface-sunken"
306
310
  >
307
311
  Preferences
308
312
  </NavLink>
313
+ <button
314
+ type="button"
315
+ role="menuitem"
316
+ onClick={() => {
317
+ setMenuOpen(false)
318
+ void navigate(ONBOARDING_RERUN_ENTRY)
319
+ }}
320
+ className="block w-full border-b border-border px-3 py-1.5 text-left text-[13px] text-ink hover:bg-surface-sunken"
321
+ >
322
+ Rerun onboarding wizard
323
+ </button>
309
324
  <button
310
325
  type="button"
311
326
  role="menuitem"
@@ -0,0 +1,88 @@
1
+ import { SOCIAL_NETWORK_LABELS } from '@kelpie/schemas'
2
+ import type { SocialNetworkId } from '@kelpie/schemas'
3
+ import {
4
+ siBluesky,
5
+ siCrunchbase,
6
+ siFacebook,
7
+ siGithub,
8
+ siInstagram,
9
+ siMastodon,
10
+ siMedium,
11
+ siSubstack,
12
+ siThreads,
13
+ siTiktok,
14
+ siWellfound,
15
+ siX,
16
+ siYoutube,
17
+ } from 'simple-icons'
18
+
19
+ export interface SocialNetworkIconProps {
20
+ readonly network: SocialNetworkId
21
+ readonly className?: string
22
+ }
23
+
24
+ /**
25
+ * Simple Icons does not ship LinkedIn. This is the standard "in" mark used to
26
+ * identify a LinkedIn profile, not a substitute brand colour lockup.
27
+ */
28
+ const LINKEDIN_PATH =
29
+ 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'
30
+
31
+ /** Generic link glyph for the catch-all network. Heroicons MIT, 24 solid. */
32
+ const OTHER_PATH =
33
+ 'M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3ZM11.768 19.768a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3Z'
34
+
35
+ const ICON_PATHS: Readonly<Record<SocialNetworkId, string>> = {
36
+ angellist: siWellfound.path,
37
+ bluesky: siBluesky.path,
38
+ crunchbase: siCrunchbase.path,
39
+ facebook: siFacebook.path,
40
+ github: siGithub.path,
41
+ instagram: siInstagram.path,
42
+ linkedin: LINKEDIN_PATH,
43
+ mastodon: siMastodon.path,
44
+ medium: siMedium.path,
45
+ substack: siSubstack.path,
46
+ threads: siThreads.path,
47
+ tiktok: siTiktok.path,
48
+ twitter: siX.path,
49
+ youtube: siYoutube.path,
50
+ other: OTHER_PATH,
51
+ }
52
+
53
+ /**
54
+ * Brand mark for a social network. Decorative when the network name sits next
55
+ * to it; fill follows the surrounding text colour so light and dark both work.
56
+ */
57
+ export function SocialNetworkIcon({
58
+ network,
59
+ className,
60
+ }: SocialNetworkIconProps): React.JSX.Element {
61
+ return (
62
+ <svg
63
+ viewBox="0 0 24 24"
64
+ aria-hidden="true"
65
+ className={['shrink-0', className ?? 'size-3.5'].join(' ')}
66
+ >
67
+ <path fill="currentColor" d={ICON_PATHS[network]} />
68
+ </svg>
69
+ )
70
+ }
71
+
72
+ export interface SocialNetworkNameProps {
73
+ readonly network: SocialNetworkId
74
+ readonly className?: string
75
+ }
76
+
77
+ /** Icon plus the display name, for pickers and compact chips. */
78
+ export function SocialNetworkName({
79
+ network,
80
+ className,
81
+ }: SocialNetworkNameProps): React.JSX.Element {
82
+ return (
83
+ <span className={['inline-flex items-center gap-1.5', className ?? ''].join(' ')}>
84
+ <SocialNetworkIcon network={network} />
85
+ <span>{SOCIAL_NETWORK_LABELS[network]}</span>
86
+ </span>
87
+ )
88
+ }
@@ -2,6 +2,8 @@ import { SOCIAL_NETWORK_IDS, SOCIAL_NETWORK_LABELS } from '@kelpie/schemas'
2
2
  import type { SocialNetworkId, SocialProfile } from '@kelpie/schemas'
3
3
  import { useEffect, useId, useMemo, useRef, useState } from 'react'
4
4
 
5
+ import { SocialNetworkIcon, SocialNetworkName } from './SocialNetworkIcon.tsx'
6
+
5
7
  export interface SocialProfilesFieldProps {
6
8
  readonly value: readonly SocialProfile[]
7
9
  readonly onChange: (profiles: readonly SocialProfile[]) => void
@@ -25,6 +27,45 @@ function profileHandle(url: string): string {
25
27
  return path.length > 0 ? path : bare
26
28
  }
27
29
 
30
+ export interface SocialProfileIconsProps {
31
+ readonly profiles: readonly SocialProfile[]
32
+ }
33
+
34
+ /**
35
+ * Compact outbound marks for the person heading. Empty when there are no
36
+ * profiles; the Social tab is the editor.
37
+ */
38
+ export function SocialProfileIcons({
39
+ profiles,
40
+ }: SocialProfileIconsProps): React.JSX.Element | null {
41
+ if (profiles.length === 0) {
42
+ return null
43
+ }
44
+
45
+ return (
46
+ <ul className="flex shrink-0 items-center gap-1.5">
47
+ {profiles.map((profile) => {
48
+ const label = SOCIAL_NETWORK_LABELS[profile.network]
49
+
50
+ return (
51
+ <li key={profile.network}>
52
+ <a
53
+ href={hrefFor(profile.url)}
54
+ target="_blank"
55
+ rel="noopener noreferrer"
56
+ title={`${label} · ${profileHandle(profile.url)}`}
57
+ aria-label={label}
58
+ className="inline-flex text-ink-muted transition hover:text-accent"
59
+ >
60
+ <SocialNetworkIcon network={profile.network} className="size-4" />
61
+ </a>
62
+ </li>
63
+ )
64
+ })}
65
+ </ul>
66
+ )
67
+ }
68
+
28
69
  /** One profile per network, so the network list doubles as the set of things left to add. */
29
70
  export function SocialProfilesField({
30
71
  value,
@@ -150,7 +191,9 @@ export function SocialProfilesField({
150
191
  if (editingNetwork === profile.network) {
151
192
  return (
152
193
  <li key={profile.network} className="py-1">
153
- <div className="mb-1 text-[11px] font-medium text-ink-muted">{label}</div>
194
+ <div className="mb-1">
195
+ <SocialNetworkName network={profile.network} className="text-[11px] font-medium text-ink-muted" />
196
+ </div>
154
197
  <input
155
198
  ref={editInputRef}
156
199
  type="url"
@@ -186,11 +229,14 @@ export function SocialProfilesField({
186
229
  target="_blank"
187
230
  rel="noopener noreferrer"
188
231
  title={profile.url}
189
- className="min-w-0 flex-1 truncate text-[12px] leading-snug text-ink hover:text-accent"
232
+ className="flex min-w-0 flex-1 items-center gap-1.5 text-[12px] leading-snug text-ink hover:text-accent"
190
233
  >
191
- <span className="font-medium">{label}</span>
192
- <span className="text-ink-faint"> · </span>
193
- <span className="text-ink-muted">{profileHandle(profile.url)}</span>
234
+ <SocialNetworkIcon network={profile.network} />
235
+ <span className="min-w-0 truncate">
236
+ <span className="font-medium">{label}</span>
237
+ <span className="text-ink-faint"> · </span>
238
+ <span className="text-ink-muted">{profileHandle(profile.url)}</span>
239
+ </span>
194
240
  </a>
195
241
  <div className="flex shrink-0 items-center">
196
242
  <button
@@ -230,9 +276,10 @@ export function SocialProfilesField({
230
276
 
231
277
  {pendingNetwork !== null ? (
232
278
  <div className="mt-1 space-y-1.5 rounded-md border border-border bg-surface px-2 py-2">
233
- <div className="text-[11px] font-medium text-ink">
234
- {SOCIAL_NETWORK_LABELS[pendingNetwork]}
235
- </div>
279
+ <SocialNetworkName
280
+ network={pendingNetwork}
281
+ className="text-[11px] font-medium text-ink"
282
+ />
236
283
  <input
237
284
  ref={urlInputRef}
238
285
  type="url"
@@ -336,11 +383,11 @@ export function SocialProfilesField({
336
383
  pickNetwork(network)
337
384
  }}
338
385
  className={[
339
- 'flex w-full px-3 py-1.5 text-left text-[12px] text-ink',
386
+ 'flex w-full items-center px-3 py-1.5 text-left text-[12px] text-ink',
340
387
  index === highlight ? 'bg-accent-soft' : 'hover:bg-surface',
341
388
  ].join(' ')}
342
389
  >
343
- {SOCIAL_NETWORK_LABELS[network]}
390
+ <SocialNetworkName network={network} />
344
391
  </button>
345
392
  </li>
346
393
  ))
@@ -0,0 +1,67 @@
1
+ import type { JSX } from 'react'
2
+
3
+ import { timezonesIncluding } from '../lib/timezones.ts'
4
+
5
+ import { EntitySearch } from './EntitySearch.tsx'
6
+
7
+ export interface TimezoneSearchProps {
8
+ readonly value: string
9
+ readonly onChange: (timezone: string) => void
10
+ readonly required?: boolean | undefined
11
+ readonly disabled?: boolean | undefined
12
+ readonly size?: 'sm' | 'md' | undefined
13
+ readonly className?: string | undefined
14
+ }
15
+
16
+ /**
17
+ * Searchable IANA timezone picker.
18
+ *
19
+ * The person sidebar used to be a free-text InlineEdit, which accepted anything
20
+ * and offered no list. Account preferences already searched the full ICU set;
21
+ * this is that control, shared with Events and workspace settings.
22
+ *
23
+ * Clearing a required field is ignored: the stored value stays until a real
24
+ * zone is picked, so an Event or workspace cannot PATCH an empty string.
25
+ */
26
+ export function TimezoneSearch({
27
+ value,
28
+ onChange,
29
+ required,
30
+ disabled,
31
+ size = 'sm',
32
+ className = '',
33
+ }: TimezoneSearchProps): JSX.Element {
34
+ const options = timezonesIncluding(value).map((zone) => ({ id: zone, label: zone }))
35
+
36
+ if (disabled === true) {
37
+ const padding = size === 'md' ? 'px-2.5 py-1.5 text-[13px]' : 'px-2 py-1.5 text-[12px]'
38
+
39
+ return (
40
+ <div
41
+ className={`w-full rounded-md border border-border bg-surface-raised text-ink opacity-60 ${padding} ${className}`}
42
+ >
43
+ {value.length > 0 ? value : '—'}
44
+ </div>
45
+ )
46
+ }
47
+
48
+ return (
49
+ <EntitySearch
50
+ options={options}
51
+ value={value}
52
+ onChange={(next) => {
53
+ if (required === true && next.length === 0) {
54
+ return
55
+ }
56
+
57
+ onChange(next)
58
+ }}
59
+ placeholder="Search time zones…"
60
+ emptyMessage="No matching time zones"
61
+ limit={20}
62
+ required={required}
63
+ size={size}
64
+ className={className}
65
+ />
66
+ )
67
+ }