@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
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
useConvertPipelineRecord,
|
|
12
12
|
} from '../api/resources/conversions.ts'
|
|
13
13
|
import { useMembers } from '../api/resources/members.ts'
|
|
14
|
+
import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
|
|
14
15
|
import { usePeople } from '../api/resources/people.ts'
|
|
15
16
|
import { usePipelineStages } from '../api/resources/pipelineStages.ts'
|
|
16
17
|
import { useRecordPlanItems } from '../api/resources/planItems.ts'
|
|
@@ -27,6 +28,7 @@ import { CustomFieldsPanel } from '../components/CustomFieldsPanel.tsx'
|
|
|
27
28
|
import { useHasCustomFields } from '../components/useHasCustomFields.ts'
|
|
28
29
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
29
30
|
import { ListsPanel } from '../components/ListsPanel.tsx'
|
|
31
|
+
import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
|
|
30
32
|
import { NotesPanel } from '../components/NotesPanel.tsx'
|
|
31
33
|
import { PlanAttention } from '../components/PlanAttention.tsx'
|
|
32
34
|
import { PlanPanel } from '../components/PlanPanel.tsx'
|
|
@@ -69,6 +71,7 @@ export function RaiseDetail(): React.JSX.Element {
|
|
|
69
71
|
const deleteRaise = useDeleteRaise()
|
|
70
72
|
const moduleTabs = inSlotOrder(useRecordTabs('raise'))
|
|
71
73
|
const hasCustomFields = useHasCustomFields('raise')
|
|
74
|
+
const eventsEnabled = useModuleEnabled('events')
|
|
72
75
|
const [activeTab, setActiveTab] = useState('overview')
|
|
73
76
|
const [showConvert, setShowConvert] = useState(false)
|
|
74
77
|
|
|
@@ -92,6 +95,7 @@ export function RaiseDetail(): React.JSX.Element {
|
|
|
92
95
|
{ id: 'notes', label: 'Notes' },
|
|
93
96
|
{ id: 'decisions', label: 'Decisions' },
|
|
94
97
|
{ id: 'lists', label: 'Lists' },
|
|
98
|
+
...(eventsEnabled ? [{ id: 'events', label: 'Events' }] : []),
|
|
95
99
|
...moduleTabs.map((tab) => ({ id: tab.id, label: tab.label })),
|
|
96
100
|
]
|
|
97
101
|
const active = tabs.some((tab) => tab.id === activeTab) ? activeTab : 'overview'
|
|
@@ -162,6 +166,7 @@ export function RaiseDetail(): React.JSX.Element {
|
|
|
162
166
|
{active === 'notes' && <NotesPanel targetType="raise" targetId={record.id} />}
|
|
163
167
|
{active === 'decisions' && <DecisionsPanel targetType="raise" targetId={record.id} />}
|
|
164
168
|
{active === 'lists' && <ListsPanel targetType="raise" targetId={record.id} />}
|
|
169
|
+
{active === 'events' && <LinkedEventsPanel targetType="raise" targetId={record.id} />}
|
|
165
170
|
{moduleTab?.render({ objectType: 'raise', recordId: record.id })}
|
|
166
171
|
</RecordTabs>
|
|
167
172
|
</div>
|
package/src/pages/RoleDetail.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { Link, useNavigate, useParams } from 'react-router'
|
|
|
7
7
|
import { usePatch } from '../api/resource.ts'
|
|
8
8
|
import type { PatchResult } from '../api/resource.ts'
|
|
9
9
|
import { useCandidates, useCreateCandidate, useDeleteCandidate } from '../api/resources/candidates.ts'
|
|
10
|
+
import { useModuleEnabled } from '../api/resources/moduleSettings.ts'
|
|
10
11
|
import { useCreateNote, useDeleteNote, useUpdateNote } from '../api/resources/notes.ts'
|
|
11
12
|
import { useCreatePerson, usePeople } from '../api/resources/people.ts'
|
|
12
13
|
import { useDeleteRole, useRole, useUpdateRole } from '../api/resources/roles.ts'
|
|
@@ -15,6 +16,7 @@ import { Chip } from '../components/Chip.tsx'
|
|
|
15
16
|
import { DeleteRecord } from '../components/DeleteRecord.tsx'
|
|
16
17
|
import { EntitySearch } from '../components/EntitySearch.tsx'
|
|
17
18
|
import { InlineEdit } from '../components/InlineEdit.tsx'
|
|
19
|
+
import { LinkedEventsPanel } from '../components/LinkedEventsPanel.tsx'
|
|
18
20
|
import { Paginator } from '../components/Paginator.tsx'
|
|
19
21
|
import { ErrorPanel, LoadingPanel, NotFoundPanel } from '../components/QueryState.tsx'
|
|
20
22
|
import { SectionHeader } from '../components/SectionHeader.tsx'
|
|
@@ -46,6 +48,7 @@ export function RoleDetail(): React.JSX.Element {
|
|
|
46
48
|
const navigate = useNavigate()
|
|
47
49
|
const { record, isLoading, isNotFound, error } = useRole(id)
|
|
48
50
|
const deleteRole = useDeleteRole()
|
|
51
|
+
const eventsEnabled = useModuleEnabled('events')
|
|
49
52
|
|
|
50
53
|
if (isNotFound) {
|
|
51
54
|
return <NotFoundPanel label="Role" backTo="/hiring" />
|
|
@@ -89,6 +92,11 @@ export function RoleDetail(): React.JSX.Element {
|
|
|
89
92
|
</div>
|
|
90
93
|
|
|
91
94
|
<RoleCandidates role={record} />
|
|
95
|
+
{eventsEnabled && (
|
|
96
|
+
<div className="mt-8">
|
|
97
|
+
<LinkedEventsPanel targetType="role" targetId={record.id} />
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
92
100
|
</div>
|
|
93
101
|
)
|
|
94
102
|
}
|
package/src/pages/SearchPage.tsx
CHANGED
|
@@ -24,6 +24,7 @@ const COLLECTIONS: Readonly<Record<SearchCollection, { label: string; path: (id:
|
|
|
24
24
|
opportunity: { label: 'Opportunities', path: (id) => `/opportunities/${id}` },
|
|
25
25
|
raise: { label: 'Fundraising', path: (id) => `/fundraising/${id}` },
|
|
26
26
|
partnership: { label: 'Partnerships', path: (id) => `/partnerships/${id}` },
|
|
27
|
+
event: { label: 'Events', path: (id) => `/events/${id}` },
|
|
27
28
|
// Decisions have no detail page of their own, in the mockup or here. The global
|
|
28
29
|
// list is where one is read.
|
|
29
30
|
decision: { label: 'Decisions', path: () => '/decisions' },
|
|
@@ -39,7 +40,7 @@ export function SearchPage(): React.JSX.Element {
|
|
|
39
40
|
<div className="animate-fade-in py-16 text-center">
|
|
40
41
|
<p className="text-[15px] font-medium text-ink">Search Kelpie</p>
|
|
41
42
|
<p className="mt-1 text-[13px] text-ink-muted">
|
|
42
|
-
Find people, companies, deals, raises, decisions, and handbook pages.
|
|
43
|
+
Find people, companies, deals, events, raises, decisions, and handbook pages.
|
|
43
44
|
</p>
|
|
44
45
|
</div>
|
|
45
46
|
)
|
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
useTheme,
|
|
9
9
|
useUpdateAccountPreferences,
|
|
10
10
|
} from '../../api/resources/account.ts'
|
|
11
|
-
import { EntitySearch } from '../../components/EntitySearch.tsx'
|
|
12
11
|
import { PageHeader } from '../../components/PageHeader.tsx'
|
|
13
12
|
import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
|
|
13
|
+
import { TimezoneSearch } from '../../components/TimezoneSearch.tsx'
|
|
14
14
|
import { Field } from './Field.tsx'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -27,31 +27,6 @@ const THEME_LABELS: Readonly<Record<ThemePreference, string>> = {
|
|
|
27
27
|
dark: 'Dark',
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Every zone this runtime's ICU knows, matching what the server's
|
|
32
|
-
* `timezoneSchema` will accept. Falls back to the zones the mockup offered if
|
|
33
|
-
* `Intl.supportedValuesOf` is missing, for a browser old enough to lack it.
|
|
34
|
-
*
|
|
35
|
-
* `UTC` is prepended because `supportedValuesOf` omits it in some engines even
|
|
36
|
-
* though ECMA-402 guarantees `Intl.DateTimeFormat` accepts it as a zone name.
|
|
37
|
-
* Without this, a very common choice would be searchable only by accident of
|
|
38
|
-
* already being the stored value.
|
|
39
|
-
*/
|
|
40
|
-
const ALL_TIMEZONES: readonly string[] = (() => {
|
|
41
|
-
try {
|
|
42
|
-
return ['UTC', ...Intl.supportedValuesOf('timeZone')]
|
|
43
|
-
} catch {
|
|
44
|
-
return [
|
|
45
|
-
'UTC',
|
|
46
|
-
'Australia/Sydney',
|
|
47
|
-
'Australia/Melbourne',
|
|
48
|
-
'America/Los_Angeles',
|
|
49
|
-
'America/New_York',
|
|
50
|
-
'Europe/London',
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
})()
|
|
54
|
-
|
|
55
30
|
export function PreferencesPage(): React.JSX.Element {
|
|
56
31
|
const { preferences, isLoading, error } = useAccountPreferences()
|
|
57
32
|
|
|
@@ -79,8 +54,6 @@ function PreferencesForm({
|
|
|
79
54
|
const [productUpdates, setProductUpdates] = useState(preferences.productUpdates)
|
|
80
55
|
const [saved, setSaved] = useState(false)
|
|
81
56
|
|
|
82
|
-
const zones = ALL_TIMEZONES.includes(timezone) ? ALL_TIMEZONES : [timezone, ...ALL_TIMEZONES]
|
|
83
|
-
|
|
84
57
|
function save(event: FormEvent): void {
|
|
85
58
|
event.preventDefault()
|
|
86
59
|
setSaved(false)
|
|
@@ -135,15 +108,7 @@ function PreferencesForm({
|
|
|
135
108
|
label="Timezone"
|
|
136
109
|
hint="Stored on your account. Dates across Kelpie use this zone."
|
|
137
110
|
>
|
|
138
|
-
<
|
|
139
|
-
options={zones.map((zone) => ({ id: zone, label: zone }))}
|
|
140
|
-
value={timezone}
|
|
141
|
-
onChange={setTimezone}
|
|
142
|
-
placeholder="Search time zones…"
|
|
143
|
-
limit={20}
|
|
144
|
-
required
|
|
145
|
-
size="md"
|
|
146
|
-
/>
|
|
111
|
+
<TimezoneSearch value={timezone} onChange={setTimezone} required size="md" />
|
|
147
112
|
</Field>
|
|
148
113
|
</div>
|
|
149
114
|
|
|
@@ -88,7 +88,8 @@ function ChangePasswordForm(): React.JSX.Element {
|
|
|
88
88
|
<form onSubmit={submit} className="space-y-3">
|
|
89
89
|
<h2 className="text-[14px] font-semibold text-ink">Change password</h2>
|
|
90
90
|
<p className="max-w-md text-[12px] text-ink-muted">
|
|
91
|
-
Changing your password signs you out everywhere except this browser.
|
|
91
|
+
Changing your password signs you out everywhere except this browser. If you have never set
|
|
92
|
+
one, use <span className="font-medium text-ink">Forgot password</span> on the sign-in page.
|
|
92
93
|
</p>
|
|
93
94
|
|
|
94
95
|
<label className="block max-w-md">
|
|
@@ -216,6 +217,10 @@ function SessionRow({
|
|
|
216
217
|
<td className="px-4 py-3">
|
|
217
218
|
<div className="font-medium break-all text-ink">{session.device ?? 'Unknown device'}</div>
|
|
218
219
|
{session.current && <span className="text-[11px] font-medium text-success">This device</span>}
|
|
220
|
+
{/* Null for a password sign-in, which is the ordinary case and needs no label. */}
|
|
221
|
+
{session.signedInVia !== null && (
|
|
222
|
+
<div className="text-[11px] text-ink-faint">via {session.signedInVia}</div>
|
|
223
|
+
)}
|
|
219
224
|
</td>
|
|
220
225
|
{/* Always unknown today: the service records the User-Agent and nothing
|
|
221
226
|
derives a location from the request. The column stays because the field
|
|
@@ -1014,17 +1014,17 @@ function messageOf(error: Error | null | undefined): string | null {
|
|
|
1014
1014
|
/**
|
|
1015
1015
|
* One button that fills the workspace with a small demo fixture.
|
|
1016
1016
|
*
|
|
1017
|
-
* The
|
|
1018
|
-
*
|
|
1019
|
-
* a plain error line.
|
|
1017
|
+
* The first click opens a warning. Confirm is what POSTs. Existing records
|
|
1018
|
+
* stay; a sample email or domain that is already present fails the install.
|
|
1020
1019
|
*/
|
|
1021
1020
|
function SampleDataSection(): ReactNode {
|
|
1022
1021
|
const { session } = useSession()
|
|
1023
1022
|
const workspaceId = session?.workspaceId ?? null
|
|
1024
1023
|
const install = useInstallSampleData()
|
|
1025
1024
|
const [counts, setCounts] = useState<SampleDataCounts | null>(null)
|
|
1025
|
+
const [confirming, setConfirming] = useState(false)
|
|
1026
1026
|
|
|
1027
|
-
function
|
|
1027
|
+
function onConfirm(): void {
|
|
1028
1028
|
if (workspaceId === null) {
|
|
1029
1029
|
return
|
|
1030
1030
|
}
|
|
@@ -1033,6 +1033,7 @@ function SampleDataSection(): ReactNode {
|
|
|
1033
1033
|
.runAsync({ workspaceId })
|
|
1034
1034
|
.then((result) => {
|
|
1035
1035
|
setCounts(result)
|
|
1036
|
+
setConfirming(false)
|
|
1036
1037
|
})
|
|
1037
1038
|
.catch(() => undefined)
|
|
1038
1039
|
}
|
|
@@ -1044,32 +1045,73 @@ function SampleDataSection(): ReactNode {
|
|
|
1044
1045
|
<div>
|
|
1045
1046
|
<h2 className="text-[15px] font-semibold text-ink">Install sample data</h2>
|
|
1046
1047
|
<p className="mt-1 text-[13px] text-ink-muted">
|
|
1047
|
-
A small set of companies, people, and
|
|
1048
|
-
|
|
1048
|
+
A small set of companies, people, deals, and events so a workspace has something to look
|
|
1049
|
+
at. Existing records stay. This page asks before it writes.
|
|
1049
1050
|
</p>
|
|
1050
1051
|
</div>
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1052
|
+
{counts !== null ? (
|
|
1053
|
+
<button
|
|
1054
|
+
type="button"
|
|
1055
|
+
disabled
|
|
1056
|
+
className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg opacity-50"
|
|
1057
|
+
>
|
|
1058
|
+
Installed
|
|
1059
|
+
</button>
|
|
1060
|
+
) : confirming ? (
|
|
1061
|
+
<div className="rounded-md border border-warning bg-warning-soft p-3 text-[13px] text-ink">
|
|
1062
|
+
<p>
|
|
1063
|
+
This adds sample companies, people, and other CRM records. Existing records are not
|
|
1064
|
+
removed. If sample emails or domains are already present, the install fails.
|
|
1065
|
+
</p>
|
|
1066
|
+
{failure === null ? null : (
|
|
1067
|
+
<p role="alert" className="mt-2 text-[12px] font-medium text-danger">
|
|
1068
|
+
{failure}
|
|
1069
|
+
</p>
|
|
1070
|
+
)}
|
|
1071
|
+
<div className="mt-2 flex justify-end gap-2">
|
|
1072
|
+
<button
|
|
1073
|
+
type="button"
|
|
1074
|
+
onClick={() => {
|
|
1075
|
+
setConfirming(false)
|
|
1076
|
+
}}
|
|
1077
|
+
disabled={install.isPending}
|
|
1078
|
+
className="rounded-md border border-border px-3 py-1 text-[12px] hover:bg-surface"
|
|
1079
|
+
>
|
|
1080
|
+
Cancel
|
|
1081
|
+
</button>
|
|
1082
|
+
<button
|
|
1083
|
+
type="button"
|
|
1084
|
+
onClick={onConfirm}
|
|
1085
|
+
disabled={install.isPending || workspaceId === null}
|
|
1086
|
+
className="rounded-md bg-accent px-3 py-1 text-[12px] font-semibold text-accent-fg hover:bg-accent-hover disabled:opacity-50"
|
|
1087
|
+
>
|
|
1088
|
+
{install.isPending ? 'Installing…' : 'Install sample data'}
|
|
1089
|
+
</button>
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
) : (
|
|
1093
|
+
<button
|
|
1094
|
+
type="button"
|
|
1095
|
+
onClick={() => {
|
|
1096
|
+
setConfirming(true)
|
|
1097
|
+
}}
|
|
1098
|
+
disabled={workspaceId === null}
|
|
1099
|
+
className="rounded-md bg-accent px-3.5 py-2 text-[12px] font-semibold text-accent-fg transition hover:bg-accent-hover disabled:opacity-50"
|
|
1100
|
+
>
|
|
1101
|
+
Install sample data
|
|
1102
|
+
</button>
|
|
1103
|
+
)}
|
|
1063
1104
|
{counts !== null ? (
|
|
1064
1105
|
<p className="text-[12px] text-ink-muted">
|
|
1065
1106
|
Added {counts.companies} companies, {counts.people} people, {counts.positions}{' '}
|
|
1066
1107
|
positions, {counts.deals} deals, {counts.opportunities} opportunities,{' '}
|
|
1067
1108
|
{counts.raises} raises, {counts.partnerships} partnerships, {counts.enquiries}{' '}
|
|
1068
|
-
enquiries, {counts.roles} roles, {counts.candidates} candidates,{' '}
|
|
1069
|
-
{counts.planItems} plan items, and
|
|
1109
|
+
enquiries, {counts.roles} roles, {counts.candidates} candidates, {counts.events}{' '}
|
|
1110
|
+
events, {counts.attendances} attendances, {counts.planItems} plan items, and{' '}
|
|
1111
|
+
{counts.notes} notes.
|
|
1070
1112
|
</p>
|
|
1071
1113
|
) : null}
|
|
1072
|
-
{failure === null ? null : (
|
|
1114
|
+
{confirming || failure === null ? null : (
|
|
1073
1115
|
<p role="alert" className="text-[12px] font-medium text-danger">
|
|
1074
1116
|
{failure}
|
|
1075
1117
|
</p>
|
|
@@ -52,7 +52,7 @@ export function FieldsPage(): React.JSX.Element {
|
|
|
52
52
|
<div className="animate-slide-in mx-auto max-w-4xl space-y-6">
|
|
53
53
|
<PageHeader
|
|
54
54
|
title="Custom fields"
|
|
55
|
-
description="Add workspace-defined fields to
|
|
55
|
+
description="Add workspace-defined fields to People, Companies, pipeline records, and Events. Values on records go through the same API the UI uses."
|
|
56
56
|
/>
|
|
57
57
|
{isAdmin ? <FieldsAdmin /> : <MemberNotice />}
|
|
58
58
|
</div>
|
|
@@ -18,6 +18,7 @@ import { EntitySearch } from '../../components/EntitySearch.tsx'
|
|
|
18
18
|
import type { SearchOption } from '../../components/EntitySearch.tsx'
|
|
19
19
|
import { PageHeader } from '../../components/PageHeader.tsx'
|
|
20
20
|
import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
|
|
21
|
+
import { TimezoneSearch } from '../../components/TimezoneSearch.tsx'
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Workspace settings, and the one screen that can end a workspace.
|
|
@@ -32,22 +33,6 @@ import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
|
|
|
32
33
|
* would be a stream of half-typed addresses.
|
|
33
34
|
*/
|
|
34
35
|
|
|
35
|
-
/**
|
|
36
|
-
* The timezones the mockup offered, plus whatever this workspace already holds.
|
|
37
|
-
*
|
|
38
|
-
* A full IANA list belongs in a combo box, which is its own piece of work. An
|
|
39
|
-
* unlisted value must still survive a save, so it joins the list rather than
|
|
40
|
-
* being silently rewritten to the first option.
|
|
41
|
-
*/
|
|
42
|
-
const COMMON_TIMEZONES = [
|
|
43
|
-
'Australia/Sydney',
|
|
44
|
-
'Australia/Melbourne',
|
|
45
|
-
'America/Los_Angeles',
|
|
46
|
-
'America/New_York',
|
|
47
|
-
'Europe/London',
|
|
48
|
-
'UTC',
|
|
49
|
-
] as const
|
|
50
|
-
|
|
51
36
|
export function WorkspaceSettingsPage(): React.JSX.Element {
|
|
52
37
|
const { workspace, isLoading, error } = useWorkspace()
|
|
53
38
|
|
|
@@ -77,9 +62,6 @@ function WorkspaceSettingsForm({
|
|
|
77
62
|
const [saved, setSaved] = useState(false)
|
|
78
63
|
|
|
79
64
|
const canEdit = session?.role === 'owner' || session?.role === 'admin'
|
|
80
|
-
const timezones = COMMON_TIMEZONES.includes(timezone as (typeof COMMON_TIMEZONES)[number])
|
|
81
|
-
? [...COMMON_TIMEZONES]
|
|
82
|
-
: [timezone, ...COMMON_TIMEZONES]
|
|
83
65
|
|
|
84
66
|
function save(event: FormEvent): void {
|
|
85
67
|
event.preventDefault()
|
|
@@ -130,20 +112,15 @@ function WorkspaceSettingsForm({
|
|
|
130
112
|
</Field>
|
|
131
113
|
|
|
132
114
|
<Field label="Timezone">
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
setTimezone
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<option key={zone} value={zone}>
|
|
143
|
-
{zone}
|
|
144
|
-
</option>
|
|
145
|
-
))}
|
|
146
|
-
</select>
|
|
115
|
+
<div className="max-w-md">
|
|
116
|
+
<TimezoneSearch
|
|
117
|
+
value={timezone}
|
|
118
|
+
onChange={setTimezone}
|
|
119
|
+
required
|
|
120
|
+
disabled={!canEdit}
|
|
121
|
+
size="md"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
147
124
|
</Field>
|
|
148
125
|
|
|
149
126
|
{update.error !== null && (
|
|
@@ -116,3 +116,41 @@ export function SecondaryButton({
|
|
|
116
116
|
</button>
|
|
117
117
|
)
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A labelled rule, for a module separating its own buttons from the password
|
|
122
|
+
* form above them. Core never renders one itself; see `AuthMethods`.
|
|
123
|
+
*/
|
|
124
|
+
export function AuthDivider({ label = 'or' }: { readonly label?: string }): React.JSX.Element {
|
|
125
|
+
return (
|
|
126
|
+
<div className="flex items-center gap-3 py-1">
|
|
127
|
+
<span className="h-px flex-1 bg-border" />
|
|
128
|
+
<span className="text-[11px] text-ink-faint">{label}</span>
|
|
129
|
+
<span className="h-px flex-1 bg-border" />
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The secondary button as a link.
|
|
136
|
+
*
|
|
137
|
+
* A plain anchor, not a router link: a sign-in method leaves the app for its
|
|
138
|
+
* provider, and the destination is a server route the SPA router knows nothing
|
|
139
|
+
* about.
|
|
140
|
+
*/
|
|
141
|
+
export function AuthLinkButton({
|
|
142
|
+
label,
|
|
143
|
+
href,
|
|
144
|
+
}: {
|
|
145
|
+
readonly label: string
|
|
146
|
+
readonly href: string
|
|
147
|
+
}): React.JSX.Element {
|
|
148
|
+
return (
|
|
149
|
+
<a
|
|
150
|
+
href={href}
|
|
151
|
+
className="block w-full rounded-md border border-border bg-surface px-3.5 py-2 text-center text-[12px] font-medium text-ink-muted transition hover:bg-surface-raised"
|
|
152
|
+
>
|
|
153
|
+
{label}
|
|
154
|
+
</a>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import { Link } from 'react-router'
|
|
3
3
|
|
|
4
|
+
import { RegionSwitcher } from './RegionSwitcher.tsx'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* The centred card every signed-out page sits in: sign in, signup, the two
|
|
6
|
-
* password-reset pages, and the
|
|
8
|
+
* password-reset pages, and the five onboarding steps.
|
|
7
9
|
*
|
|
8
10
|
* `step` is what makes it an onboarding page. Onboarding is a sequence somebody
|
|
9
11
|
* is part-way through, and a card that does not say where they are in it turns
|
|
10
|
-
*
|
|
12
|
+
* five screens into five unrelated forms. The region switcher stays off
|
|
13
|
+
* those steps: a session already exists on this origin.
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
|
-
/** Onboarding, per `onboarding.md`: workspace, invites, handbook. */
|
|
14
|
-
const ONBOARDING_STEPS = [1, 2, 3] as const
|
|
16
|
+
/** Onboarding, per `onboarding.md`: workspace, organisation, modules, invites, handbook. */
|
|
17
|
+
const ONBOARDING_STEPS = [1, 2, 3, 4, 5] as const
|
|
15
18
|
|
|
16
19
|
export type OnboardingStep = (typeof ONBOARDING_STEPS)[number]
|
|
17
20
|
|
|
@@ -50,6 +53,7 @@ export function AuthLayout({
|
|
|
50
53
|
{children}
|
|
51
54
|
{footer !== undefined && <div className="mt-4 text-center text-[12px]">{footer}</div>}
|
|
52
55
|
</div>
|
|
56
|
+
{step === undefined && <RegionSwitcher />}
|
|
53
57
|
</div>
|
|
54
58
|
)
|
|
55
59
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fragment } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useAuthMethods } from '../../registry/context.ts'
|
|
4
|
+
import type { AuthMethodContext } from '../../registry/contributions.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `auth.methods` slot, rendered by the signed-out pages beside their form.
|
|
8
|
+
*
|
|
9
|
+
* Nothing is drawn around the contributions: no divider, no "or" rule. Whether
|
|
10
|
+
* a module has anything to show is decided at run time (a sign-in module with
|
|
11
|
+
* no provider configured renders nothing), so core framing would leave a
|
|
12
|
+
* heading over an empty space. A module that draws buttons draws its own
|
|
13
|
+
* divider with `AuthDivider`.
|
|
14
|
+
*/
|
|
15
|
+
export function AuthMethods({ intent, next }: AuthMethodContext): React.JSX.Element | null {
|
|
16
|
+
const methods = useAuthMethods()
|
|
17
|
+
|
|
18
|
+
if (methods.length === 0) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="mt-4 space-y-2">
|
|
24
|
+
{methods.map((method) => (
|
|
25
|
+
<Fragment key={method.id}>{method.render({ intent, next })}</Fragment>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ChangeEvent } from 'react'
|
|
2
|
+
|
|
3
|
+
import { usePublicConfig } from '../../api/resources/publicConfig.ts'
|
|
4
|
+
import { regionHref } from './regionHref.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A region picker for signed-out pages.
|
|
8
|
+
*
|
|
9
|
+
* Hidden unless public config lists two or more origins. Selecting another
|
|
10
|
+
* region leaves this origin: the session cookie is host-scoped, so the
|
|
11
|
+
* browser has to load the other host's login page. Path, query, and hash
|
|
12
|
+
* stay, so `/login?next=` and invite tokens survive the hop.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export function RegionSwitcher(): React.JSX.Element | null {
|
|
16
|
+
const { config } = usePublicConfig()
|
|
17
|
+
const regions = config?.regions ?? []
|
|
18
|
+
|
|
19
|
+
if (regions.length < 2) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const current = regions.find((region) => region.origin === window.location.origin)
|
|
24
|
+
|
|
25
|
+
function change(event: ChangeEvent<HTMLSelectElement>): void {
|
|
26
|
+
const selected = regions.find((region) => region.id === event.target.value)
|
|
27
|
+
|
|
28
|
+
if (selected === undefined || selected.origin === window.location.origin) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.location.assign(regionHref(selected.origin, window.location))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<label className="mt-6 block w-full max-w-sm">
|
|
37
|
+
<span className="mb-1.5 block text-[12px] font-medium text-ink">Region</span>
|
|
38
|
+
<select
|
|
39
|
+
className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
40
|
+
value={current?.id ?? ''}
|
|
41
|
+
onChange={change}
|
|
42
|
+
>
|
|
43
|
+
{current === undefined && (
|
|
44
|
+
<option value="" disabled>
|
|
45
|
+
Select a region
|
|
46
|
+
</option>
|
|
47
|
+
)}
|
|
48
|
+
{regions.map((region) => (
|
|
49
|
+
<option key={region.id} value={region.id}>
|
|
50
|
+
{region.label}
|
|
51
|
+
</option>
|
|
52
|
+
))}
|
|
53
|
+
</select>
|
|
54
|
+
</label>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -2,10 +2,13 @@ import { useState } from 'react'
|
|
|
2
2
|
import type { FormEvent } from 'react'
|
|
3
3
|
import { Link, useNavigate, useSearchParams } from 'react-router'
|
|
4
4
|
|
|
5
|
+
import { usePublicConfig } from '../../api/resources/publicConfig.ts'
|
|
5
6
|
import { useLogIn } from '../../api/resources/session.ts'
|
|
6
7
|
import { ErrorPanel } from '../../components/QueryState.tsx'
|
|
7
8
|
import { SubmitButton, TextField } from './AuthForm.tsx'
|
|
8
9
|
import { AuthLayout } from './AuthLayout.tsx'
|
|
10
|
+
import { AuthMethods } from './AuthMethods.tsx'
|
|
11
|
+
import { safeNext } from './nextPath.ts'
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Sign in, against `POST /v1/auth/login`.
|
|
@@ -15,18 +18,11 @@ import { AuthLayout } from './AuthLayout.tsx'
|
|
|
15
18
|
* People with the invitation still unaccepted.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
/**
|
|
19
|
-
* Only a path within this app. An absolute URL in `?next=` would turn the
|
|
20
|
-
* sign-in form into an open redirect.
|
|
21
|
-
*/
|
|
22
|
-
function safeNext(value: string | null): string {
|
|
23
|
-
return value !== null && value.startsWith('/') && !value.startsWith('//') ? value : '/dashboard'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
21
|
export function SignInPage(): React.JSX.Element {
|
|
27
22
|
const navigate = useNavigate()
|
|
28
23
|
const [searchParameters] = useSearchParams()
|
|
29
24
|
const logIn = useLogIn()
|
|
25
|
+
const { config } = usePublicConfig()
|
|
30
26
|
const [email, setEmail] = useState('')
|
|
31
27
|
const [password, setPassword] = useState('')
|
|
32
28
|
|
|
@@ -49,12 +45,14 @@ export function SignInPage(): React.JSX.Element {
|
|
|
49
45
|
<Link to="/forgot-password" className="font-medium text-accent hover:underline">
|
|
50
46
|
Forgot password?
|
|
51
47
|
</Link>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
{config?.signupsEnabled !== false && (
|
|
49
|
+
<span className="text-ink-muted">
|
|
50
|
+
No account?{' '}
|
|
51
|
+
<Link to="/signup" className="font-medium text-accent hover:underline">
|
|
52
|
+
Sign up
|
|
53
|
+
</Link>
|
|
54
|
+
</span>
|
|
55
|
+
)}
|
|
58
56
|
</div>
|
|
59
57
|
}
|
|
60
58
|
>
|
|
@@ -78,6 +76,7 @@ export function SignInPage(): React.JSX.Element {
|
|
|
78
76
|
{logIn.error !== null && <ErrorPanel error={logIn.error} />}
|
|
79
77
|
<SubmitButton label="Sign in" pendingLabel="Signing in…" isPending={logIn.isPending} />
|
|
80
78
|
</form>
|
|
79
|
+
<AuthMethods intent="login" next={next} />
|
|
81
80
|
</AuthLayout>
|
|
82
81
|
)
|
|
83
82
|
}
|