@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.
- package/dist/api/resources/attendances.d.ts +13 -0
- package/dist/api/resources/attendances.d.ts.map +1 -0
- package/dist/api/resources/attendances.js +34 -0
- package/dist/api/resources/attendances.js.map +1 -0
- package/dist/api/resources/eventAssociations.d.ts +12 -0
- package/dist/api/resources/eventAssociations.d.ts.map +1 -0
- package/dist/api/resources/eventAssociations.js +25 -0
- package/dist/api/resources/eventAssociations.js.map +1 -0
- package/dist/api/resources/events.d.ts +17 -0
- package/dist/api/resources/events.d.ts.map +1 -0
- package/dist/api/resources/events.js +40 -0
- package/dist/api/resources/events.js.map +1 -0
- package/dist/api/resources/handbookSeed.d.ts +11 -0
- package/dist/api/resources/handbookSeed.d.ts.map +1 -0
- package/dist/api/resources/handbookSeed.js +43 -0
- package/dist/api/resources/handbookSeed.js.map +1 -0
- package/dist/api/resources/moduleSettings.d.ts +10 -0
- package/dist/api/resources/moduleSettings.d.ts.map +1 -1
- package/dist/api/resources/moduleSettings.js +21 -0
- package/dist/api/resources/moduleSettings.js.map +1 -1
- package/dist/api/resources/planItems.d.ts +4 -4
- package/dist/api/resources/planItems.d.ts.map +1 -1
- package/dist/api/resources/planItems.js +1 -1
- package/dist/api/resources/planItems.js.map +1 -1
- package/dist/api/resources/publicConfig.js +3 -3
- package/dist/api/resources/sampleData.d.ts.map +1 -1
- package/dist/api/resources/sampleData.js +2 -0
- package/dist/api/resources/sampleData.js.map +1 -1
- package/dist/api/resources/session.d.ts +22 -1
- package/dist/api/resources/session.d.ts.map +1 -1
- package/dist/api/resources/session.js +43 -1
- package/dist/api/resources/session.js.map +1 -1
- package/dist/api/resources/workspace.d.ts.map +1 -1
- package/dist/api/resources/workspace.js +2 -3
- package/dist/api/resources/workspace.js.map +1 -1
- package/dist/app/KelpieApp.d.ts.map +1 -1
- package/dist/app/KelpieApp.js +5 -1
- package/dist/app/KelpieApp.js.map +1 -1
- package/dist/components/ActivitiesPanel.d.ts.map +1 -1
- package/dist/components/ActivitiesPanel.js +2 -0
- package/dist/components/ActivitiesPanel.js.map +1 -1
- package/dist/components/AddressesField.d.ts +23 -0
- package/dist/components/AddressesField.d.ts.map +1 -0
- package/dist/components/AddressesField.js +215 -0
- package/dist/components/AddressesField.js.map +1 -0
- package/dist/components/CountrySearch.d.ts +16 -0
- package/dist/components/CountrySearch.d.ts.map +1 -0
- package/dist/components/CountrySearch.js +30 -0
- package/dist/components/CountrySearch.js.map +1 -0
- package/dist/components/EnvironmentBanner.d.ts +0 -12
- package/dist/components/EnvironmentBanner.d.ts.map +1 -1
- package/dist/components/EnvironmentBanner.js +21 -6
- package/dist/components/EnvironmentBanner.js.map +1 -1
- package/dist/components/InlineEdit.d.ts +7 -1
- package/dist/components/InlineEdit.d.ts.map +1 -1
- package/dist/components/InlineEdit.js +8 -1
- package/dist/components/InlineEdit.js.map +1 -1
- package/dist/components/LinkedEventsPanel.d.ts +11 -0
- package/dist/components/LinkedEventsPanel.d.ts.map +1 -0
- package/dist/components/LinkedEventsPanel.js +18 -0
- package/dist/components/LinkedEventsPanel.js.map +1 -0
- package/dist/components/NotesPanel.d.ts +4 -6
- package/dist/components/NotesPanel.d.ts.map +1 -1
- package/dist/components/NotesPanel.js +39 -3
- package/dist/components/NotesPanel.js.map +1 -1
- package/dist/components/PlanAttention.d.ts.map +1 -1
- package/dist/components/PlanAttention.js +3 -2
- package/dist/components/PlanAttention.js.map +1 -1
- package/dist/components/PlanPanel.d.ts +3 -3
- package/dist/components/PlanPanel.d.ts.map +1 -1
- package/dist/components/Shell.d.ts.map +1 -1
- package/dist/components/Shell.js +8 -2
- package/dist/components/Shell.js.map +1 -1
- package/dist/components/SocialNetworkIcon.d.ts +17 -0
- package/dist/components/SocialNetworkIcon.d.ts.map +1 -0
- package/dist/components/SocialNetworkIcon.js +39 -0
- package/dist/components/SocialNetworkIcon.js.map +1 -0
- package/dist/components/SocialProfilesField.d.ts +8 -0
- package/dist/components/SocialProfilesField.d.ts.map +1 -1
- package/dist/components/SocialProfilesField.js +19 -5
- package/dist/components/SocialProfilesField.js.map +1 -1
- package/dist/components/TimezoneSearch.d.ts +21 -0
- package/dist/components/TimezoneSearch.d.ts.map +1 -0
- package/dist/components/TimezoneSearch.js +27 -0
- package/dist/components/TimezoneSearch.js.map +1 -0
- package/dist/components/WorkspaceSwitcher.d.ts +8 -0
- package/dist/components/WorkspaceSwitcher.d.ts.map +1 -0
- package/dist/components/WorkspaceSwitcher.js +67 -0
- package/dist/components/WorkspaceSwitcher.js.map +1 -0
- package/dist/components/environmentTone.d.ts +10 -0
- package/dist/components/environmentTone.d.ts.map +1 -0
- package/dist/components/environmentTone.js +35 -0
- package/dist/components/environmentTone.js.map +1 -0
- package/dist/index.d.ts +25 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/timezones.d.ts +17 -0
- package/dist/lib/timezones.d.ts.map +1 -0
- package/dist/lib/timezones.js +39 -0
- package/dist/lib/timezones.js.map +1 -0
- package/dist/pages/CompaniesPage.d.ts.map +1 -1
- package/dist/pages/CompaniesPage.js +12 -11
- package/dist/pages/CompaniesPage.js.map +1 -1
- package/dist/pages/CompanyDetail.d.ts.map +1 -1
- package/dist/pages/CompanyDetail.js +23 -12
- package/dist/pages/CompanyDetail.js.map +1 -1
- package/dist/pages/DealDetail.d.ts.map +1 -1
- package/dist/pages/DealDetail.js +5 -1
- package/dist/pages/DealDetail.js.map +1 -1
- package/dist/pages/DecisionsPage.d.ts.map +1 -1
- package/dist/pages/DecisionsPage.js +7 -0
- package/dist/pages/DecisionsPage.js.map +1 -1
- package/dist/pages/EnquiryDetail.d.ts.map +1 -1
- package/dist/pages/EnquiryDetail.js +5 -1
- package/dist/pages/EnquiryDetail.js.map +1 -1
- package/dist/pages/EventDetail.d.ts +2 -0
- package/dist/pages/EventDetail.d.ts.map +1 -0
- package/dist/pages/EventDetail.js +332 -0
- package/dist/pages/EventDetail.js.map +1 -0
- package/dist/pages/EventsPage.d.ts +2 -0
- package/dist/pages/EventsPage.d.ts.map +1 -0
- package/dist/pages/EventsPage.js +176 -0
- package/dist/pages/EventsPage.js.map +1 -0
- package/dist/pages/ListDetail.d.ts.map +1 -1
- package/dist/pages/ListDetail.js +17 -0
- package/dist/pages/ListDetail.js.map +1 -1
- package/dist/pages/OpportunityDetail.d.ts.map +1 -1
- package/dist/pages/OpportunityDetail.js +5 -1
- package/dist/pages/OpportunityDetail.js.map +1 -1
- package/dist/pages/PartnershipDetail.d.ts.map +1 -1
- package/dist/pages/PartnershipDetail.js +5 -1
- package/dist/pages/PartnershipDetail.js.map +1 -1
- package/dist/pages/PeoplePage.d.ts.map +1 -1
- package/dist/pages/PeoplePage.js +13 -5
- package/dist/pages/PeoplePage.js.map +1 -1
- package/dist/pages/PersonDetail.d.ts.map +1 -1
- package/dist/pages/PersonDetail.js +94 -28
- package/dist/pages/PersonDetail.js.map +1 -1
- package/dist/pages/PlanningPage.d.ts.map +1 -1
- package/dist/pages/PlanningPage.js +6 -3
- package/dist/pages/PlanningPage.js.map +1 -1
- package/dist/pages/RaiseDetail.d.ts.map +1 -1
- package/dist/pages/RaiseDetail.js +5 -1
- package/dist/pages/RaiseDetail.js.map +1 -1
- package/dist/pages/RoleDetail.d.ts.map +1 -1
- package/dist/pages/RoleDetail.js +4 -1
- package/dist/pages/RoleDetail.js.map +1 -1
- package/dist/pages/SearchPage.d.ts.map +1 -1
- package/dist/pages/SearchPage.js +2 -1
- package/dist/pages/SearchPage.js.map +1 -1
- package/dist/pages/account/PreferencesPage.d.ts.map +1 -1
- package/dist/pages/account/PreferencesPage.js +2 -28
- package/dist/pages/account/PreferencesPage.js.map +1 -1
- package/dist/pages/account/SecurityPage.js +2 -2
- package/dist/pages/account/SecurityPage.js.map +1 -1
- package/dist/pages/admin/DataPage.js +10 -9
- package/dist/pages/admin/DataPage.js.map +1 -1
- package/dist/pages/admin/FieldsPage.js +1 -1
- package/dist/pages/admin/FieldsPage.js.map +1 -1
- package/dist/pages/admin/ModulesPage.d.ts.map +1 -1
- package/dist/pages/admin/ModulesPage.js +1 -0
- package/dist/pages/admin/ModulesPage.js.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.d.ts +12 -0
- package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.js +2 -21
- package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/auth/AuthForm.d.ts +18 -0
- package/dist/pages/auth/AuthForm.d.ts.map +1 -1
- package/dist/pages/auth/AuthForm.js +17 -0
- package/dist/pages/auth/AuthForm.js.map +1 -1
- package/dist/pages/auth/AuthLayout.d.ts +5 -4
- package/dist/pages/auth/AuthLayout.d.ts.map +1 -1
- package/dist/pages/auth/AuthLayout.js +7 -5
- package/dist/pages/auth/AuthLayout.js.map +1 -1
- package/dist/pages/auth/AuthMethods.d.ts +12 -0
- package/dist/pages/auth/AuthMethods.d.ts.map +1 -0
- package/dist/pages/auth/AuthMethods.js +20 -0
- package/dist/pages/auth/AuthMethods.js.map +1 -0
- package/dist/pages/auth/RegionSwitcher.d.ts +10 -0
- package/dist/pages/auth/RegionSwitcher.d.ts.map +1 -0
- package/dist/pages/auth/RegionSwitcher.js +28 -0
- package/dist/pages/auth/RegionSwitcher.js.map +1 -0
- package/dist/pages/auth/SignInPage.d.ts +7 -0
- package/dist/pages/auth/SignInPage.d.ts.map +1 -1
- package/dist/pages/auth/SignInPage.js +5 -8
- package/dist/pages/auth/SignInPage.js.map +1 -1
- package/dist/pages/auth/SignUpPage.d.ts.map +1 -1
- package/dist/pages/auth/SignUpPage.js +9 -1
- package/dist/pages/auth/SignUpPage.js.map +1 -1
- package/dist/pages/auth/nextPath.d.ts +15 -0
- package/dist/pages/auth/nextPath.d.ts.map +1 -0
- package/dist/pages/auth/nextPath.js +17 -0
- package/dist/pages/auth/nextPath.js.map +1 -0
- package/dist/pages/auth/regionHref.d.ts +6 -0
- package/dist/pages/auth/regionHref.d.ts.map +1 -0
- package/dist/pages/auth/regionHref.js +8 -0
- package/dist/pages/auth/regionHref.js.map +1 -0
- package/dist/pages/dashboard/DashboardPage.d.ts +8 -0
- package/dist/pages/dashboard/DashboardPage.d.ts.map +1 -1
- package/dist/pages/dashboard/DashboardPage.js +5 -13
- package/dist/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/pages/dashboard/attention.d.ts +1 -8
- package/dist/pages/dashboard/attention.d.ts.map +1 -1
- package/dist/pages/dashboard/attention.js +21 -54
- package/dist/pages/dashboard/attention.js.map +1 -1
- package/dist/pages/dashboard/sections.d.ts.map +1 -1
- package/dist/pages/dashboard/sections.js +3 -2
- package/dist/pages/dashboard/sections.js.map +1 -1
- package/dist/pages/forms/FormActions.d.ts.map +1 -1
- package/dist/pages/forms/FormActions.js +7 -3
- package/dist/pages/forms/FormActions.js.map +1 -1
- package/dist/pages/onboarding/HandbookStepPage.d.ts +4 -8
- package/dist/pages/onboarding/HandbookStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/HandbookStepPage.js +13 -13
- package/dist/pages/onboarding/HandbookStepPage.js.map +1 -1
- package/dist/pages/onboarding/InvitesStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/InvitesStepPage.js +13 -7
- package/dist/pages/onboarding/InvitesStepPage.js.map +1 -1
- package/dist/pages/onboarding/ModulesStepPage.d.ts +11 -0
- package/dist/pages/onboarding/ModulesStepPage.d.ts.map +1 -0
- package/dist/pages/onboarding/ModulesStepPage.js +80 -0
- package/dist/pages/onboarding/ModulesStepPage.js.map +1 -0
- package/dist/pages/onboarding/OnboardingNav.d.ts +17 -0
- package/dist/pages/onboarding/OnboardingNav.d.ts.map +1 -0
- package/dist/pages/onboarding/OnboardingNav.js +37 -0
- package/dist/pages/onboarding/OnboardingNav.js.map +1 -0
- package/dist/pages/onboarding/OrganisationStepPage.d.ts +13 -0
- package/dist/pages/onboarding/OrganisationStepPage.d.ts.map +1 -0
- package/dist/pages/onboarding/OrganisationStepPage.js +59 -0
- package/dist/pages/onboarding/OrganisationStepPage.js.map +1 -0
- package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/WorkspaceStepPage.js +25 -20
- package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
- package/dist/pages/onboarding/handbookTemplates.d.ts +19 -0
- package/dist/pages/onboarding/handbookTemplates.d.ts.map +1 -0
- package/dist/pages/onboarding/handbookTemplates.js +57 -0
- package/dist/pages/onboarding/handbookTemplates.js.map +1 -0
- package/dist/pages/onboarding/onboardingModules.d.ts +23 -0
- package/dist/pages/onboarding/onboardingModules.d.ts.map +1 -0
- package/dist/pages/onboarding/onboardingModules.js +153 -0
- package/dist/pages/onboarding/onboardingModules.js.map +1 -0
- package/dist/pages/onboarding/onboardingRerun.d.ts +21 -0
- package/dist/pages/onboarding/onboardingRerun.d.ts.map +1 -0
- package/dist/pages/onboarding/onboardingRerun.js +33 -0
- package/dist/pages/onboarding/onboardingRerun.js.map +1 -0
- package/dist/registry/context.d.ts +3 -1
- package/dist/registry/context.d.ts.map +1 -1
- package/dist/registry/context.js +4 -0
- package/dist/registry/context.js.map +1 -1
- package/dist/registry/contributions.d.ts +24 -0
- package/dist/registry/contributions.d.ts.map +1 -1
- package/dist/registry/contributions.js.map +1 -1
- package/dist/registry/registry.d.ts +4 -1
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +6 -0
- package/dist/registry/registry.js.map +1 -1
- package/package.json +4 -3
- package/src/api/resources/attendances.ts +54 -0
- package/src/api/resources/eventAssociations.ts +43 -0
- package/src/api/resources/events.ts +70 -0
- package/src/api/resources/handbookSeed.ts +63 -0
- package/src/api/resources/moduleSettings.ts +25 -0
- package/src/api/resources/planItems.ts +5 -5
- package/src/api/resources/publicConfig.ts +3 -3
- package/src/api/resources/sampleData.ts +2 -0
- package/src/api/resources/session.ts +59 -0
- package/src/api/resources/workspace.ts +2 -3
- package/src/app/KelpieApp.tsx +11 -3
- package/src/components/ActivitiesPanel.tsx +2 -0
- package/src/components/AddressesField.tsx +502 -0
- package/src/components/CountrySearch.tsx +71 -0
- package/src/components/EnvironmentBanner.tsx +23 -6
- package/src/components/InlineEdit.tsx +35 -1
- package/src/components/LinkedEventsPanel.tsx +50 -0
- package/src/components/NotesPanel.tsx +132 -15
- package/src/components/PlanAttention.tsx +5 -4
- package/src/components/PlanPanel.tsx +3 -3
- package/src/components/Shell.tsx +16 -1
- package/src/components/SocialNetworkIcon.tsx +88 -0
- package/src/components/SocialProfilesField.tsx +57 -10
- package/src/components/TimezoneSearch.tsx +67 -0
- package/src/components/WorkspaceSwitcher.tsx +136 -0
- package/src/components/environmentTone.ts +40 -0
- package/src/index.ts +49 -3
- package/src/lib/timezones.ts +43 -0
- package/src/pages/CompaniesPage.tsx +14 -13
- package/src/pages/CompanyDetail.tsx +59 -27
- package/src/pages/DealDetail.tsx +5 -0
- package/src/pages/DecisionsPage.tsx +7 -0
- package/src/pages/EnquiryDetail.tsx +7 -0
- package/src/pages/EventDetail.tsx +794 -0
- package/src/pages/EventsPage.tsx +382 -0
- package/src/pages/ListDetail.tsx +24 -0
- package/src/pages/OpportunityDetail.tsx +7 -0
- package/src/pages/PartnershipDetail.tsx +7 -0
- package/src/pages/PeoplePage.tsx +18 -5
- package/src/pages/PersonDetail.tsx +300 -100
- package/src/pages/PlanningPage.tsx +17 -14
- package/src/pages/RaiseDetail.tsx +5 -0
- package/src/pages/RoleDetail.tsx +8 -0
- package/src/pages/SearchPage.tsx +2 -1
- package/src/pages/account/PreferencesPage.tsx +2 -37
- package/src/pages/account/SecurityPage.tsx +6 -1
- package/src/pages/admin/DataPage.tsx +63 -21
- package/src/pages/admin/FieldsPage.tsx +1 -1
- package/src/pages/admin/ModulesPage.tsx +1 -0
- package/src/pages/admin/WorkspaceSettingsPage.tsx +10 -33
- package/src/pages/auth/AuthForm.tsx +38 -0
- package/src/pages/auth/AuthLayout.tsx +8 -4
- package/src/pages/auth/AuthMethods.tsx +29 -0
- package/src/pages/auth/RegionSwitcher.tsx +56 -0
- package/src/pages/auth/SignInPage.tsx +13 -14
- package/src/pages/auth/SignUpPage.tsx +32 -0
- package/src/pages/auth/nextPath.ts +17 -0
- package/src/pages/auth/regionHref.ts +10 -0
- package/src/pages/dashboard/DashboardPage.tsx +16 -48
- package/src/pages/dashboard/attention.ts +24 -78
- package/src/pages/dashboard/sections.tsx +8 -2
- package/src/pages/forms/FormActions.tsx +14 -8
- package/src/pages/onboarding/HandbookStepPage.tsx +21 -24
- package/src/pages/onboarding/InvitesStepPage.tsx +27 -19
- package/src/pages/onboarding/ModulesStepPage.tsx +145 -0
- package/src/pages/onboarding/OnboardingNav.tsx +97 -0
- package/src/pages/onboarding/OrganisationStepPage.tsx +130 -0
- package/src/pages/onboarding/WorkspaceStepPage.tsx +53 -38
- package/src/pages/onboarding/handbookTemplates.ts +77 -0
- package/src/pages/onboarding/onboardingModules.ts +202 -0
- package/src/pages/onboarding/onboardingRerun.ts +41 -0
- package/src/registry/context.ts +6 -0
- package/src/registry/contributions.ts +26 -0
- package/src/registry/registry.ts +12 -0
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
15
|
-
*
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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 {
|
|
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:
|
|
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<
|
|
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
|
-
|
|
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
|
|
505
|
+
setKind(event.target.value as FormAttachTargetType)
|
|
500
506
|
setPick('')
|
|
501
507
|
}}
|
|
502
508
|
>
|
|
503
|
-
{
|
|
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
|
|
10
|
+
* Onboarding step 5: review the starter handbook.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
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={
|
|
34
|
+
step={5}
|
|
33
35
|
title="Your starter handbook"
|
|
34
|
-
description="These markdown pages
|
|
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 &&
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
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
|
|
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
|
-
|
|
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={
|
|
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
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
)
|