@kelpie/ui 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/resource.d.ts +59 -6
- package/dist/api/resource.d.ts.map +1 -1
- package/dist/api/resource.js +106 -24
- package/dist/api/resource.js.map +1 -1
- package/dist/api/resources/companies.d.ts +2 -0
- package/dist/api/resources/companies.d.ts.map +1 -1
- package/dist/api/resources/companies.js +5 -1
- package/dist/api/resources/companies.js.map +1 -1
- package/dist/api/resources/consentPurposes.d.ts +13 -0
- package/dist/api/resources/consentPurposes.d.ts.map +1 -0
- package/dist/api/resources/consentPurposes.js +39 -0
- package/dist/api/resources/consentPurposes.js.map +1 -0
- package/dist/api/resources/customFields.d.ts +15 -0
- package/dist/api/resources/customFields.d.ts.map +1 -0
- package/dist/api/resources/customFields.js +41 -0
- package/dist/api/resources/customFields.js.map +1 -0
- package/dist/api/resources/decisions.d.ts +2 -0
- package/dist/api/resources/decisions.d.ts.map +1 -1
- package/dist/api/resources/decisions.js +1 -0
- package/dist/api/resources/decisions.js.map +1 -1
- package/dist/api/resources/emailDomainLinker.d.ts +16 -0
- package/dist/api/resources/emailDomainLinker.d.ts.map +1 -0
- package/dist/api/resources/emailDomainLinker.js +16 -0
- package/dist/api/resources/emailDomainLinker.js.map +1 -0
- package/dist/api/resources/enquiries.d.ts +39 -0
- package/dist/api/resources/enquiries.d.ts.map +1 -0
- package/dist/api/resources/enquiries.js +68 -0
- package/dist/api/resources/enquiries.js.map +1 -0
- package/dist/api/resources/forms.d.ts +21 -1
- package/dist/api/resources/forms.d.ts.map +1 -1
- package/dist/api/resources/forms.js +44 -17
- package/dist/api/resources/forms.js.map +1 -1
- package/dist/api/resources/handbookPages.d.ts +1 -1
- package/dist/api/resources/handbookPages.js +1 -1
- package/dist/api/resources/importJobs.d.ts +5 -0
- package/dist/api/resources/importJobs.d.ts.map +1 -1
- package/dist/api/resources/importJobs.js +3 -0
- package/dist/api/resources/importJobs.js.map +1 -1
- package/dist/api/resources/listMembers.d.ts +27 -0
- package/dist/api/resources/listMembers.d.ts.map +1 -0
- package/dist/api/resources/listMembers.js +108 -0
- package/dist/api/resources/listMembers.js.map +1 -0
- package/dist/api/resources/lists.d.ts +14 -0
- package/dist/api/resources/lists.d.ts.map +1 -0
- package/dist/api/resources/lists.js +34 -0
- package/dist/api/resources/lists.js.map +1 -0
- package/dist/api/resources/opportunities.d.ts +2 -0
- package/dist/api/resources/opportunities.d.ts.map +1 -1
- package/dist/api/resources/opportunities.js +1 -0
- package/dist/api/resources/opportunities.js.map +1 -1
- package/dist/api/resources/people.d.ts.map +1 -1
- package/dist/api/resources/people.js +4 -1
- package/dist/api/resources/people.js.map +1 -1
- package/dist/api/resources/roles.d.ts +2 -0
- package/dist/api/resources/roles.d.ts.map +1 -1
- package/dist/api/resources/roles.js +1 -1
- package/dist/api/resources/roles.js.map +1 -1
- package/dist/api/resources/sampleData.d.ts +17 -0
- package/dist/api/resources/sampleData.d.ts.map +1 -0
- package/dist/api/resources/sampleData.js +31 -0
- package/dist/api/resources/sampleData.js.map +1 -0
- package/dist/api/resources/webhooks.d.ts.map +1 -1
- package/dist/api/resources/webhooks.js +7 -22
- package/dist/api/resources/webhooks.js.map +1 -1
- package/dist/app/KelpieApp.d.ts.map +1 -1
- package/dist/app/KelpieApp.js +9 -1
- package/dist/app/KelpieApp.js.map +1 -1
- package/dist/components/ActivitiesPanel.d.ts.map +1 -1
- package/dist/components/ActivitiesPanel.js +3 -1
- package/dist/components/ActivitiesPanel.js.map +1 -1
- package/dist/components/ColumnPicker.d.ts +24 -0
- package/dist/components/ColumnPicker.d.ts.map +1 -0
- package/dist/components/ColumnPicker.js +56 -0
- package/dist/components/ColumnPicker.js.map +1 -0
- package/dist/components/CustomFieldsPanel.d.ts +23 -0
- package/dist/components/CustomFieldsPanel.d.ts.map +1 -0
- package/dist/components/CustomFieldsPanel.js +116 -0
- package/dist/components/CustomFieldsPanel.js.map +1 -0
- package/dist/components/DataTable.d.ts +16 -1
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/DataTable.js +51 -10
- package/dist/components/DataTable.js.map +1 -1
- package/dist/components/DecisionsPanel.d.ts.map +1 -1
- package/dist/components/DecisionsPanel.js +4 -3
- package/dist/components/DecisionsPanel.js.map +1 -1
- package/dist/components/EntitySearch.d.ts.map +1 -1
- package/dist/components/EntitySearch.js +11 -5
- package/dist/components/EntitySearch.js.map +1 -1
- package/dist/components/FormsPanel.d.ts +18 -0
- package/dist/components/FormsPanel.d.ts.map +1 -0
- package/dist/components/FormsPanel.js +37 -0
- package/dist/components/FormsPanel.js.map +1 -0
- package/dist/components/ListsPanel.d.ts +14 -0
- package/dist/components/ListsPanel.d.ts.map +1 -0
- package/dist/components/ListsPanel.js +56 -0
- package/dist/components/ListsPanel.js.map +1 -0
- package/dist/components/NotesPanel.d.ts.map +1 -1
- package/dist/components/NotesPanel.js +6 -5
- package/dist/components/NotesPanel.js.map +1 -1
- package/dist/components/Paginator.d.ts +26 -0
- package/dist/components/Paginator.d.ts.map +1 -0
- package/dist/components/Paginator.js +16 -0
- package/dist/components/Paginator.js.map +1 -0
- package/dist/components/PlanAttention.d.ts +3 -2
- package/dist/components/PlanAttention.d.ts.map +1 -1
- package/dist/components/PlanAttention.js +7 -3
- package/dist/components/PlanAttention.js.map +1 -1
- package/dist/components/PlanPanel.d.ts.map +1 -1
- package/dist/components/PlanPanel.js +2 -1
- package/dist/components/PlanPanel.js.map +1 -1
- package/dist/components/Shell.d.ts.map +1 -1
- package/dist/components/Shell.js +4 -0
- package/dist/components/Shell.js.map +1 -1
- package/dist/components/useHasCustomFields.d.ts +13 -0
- package/dist/components/useHasCustomFields.d.ts.map +1 -0
- package/dist/components/useHasCustomFields.js +16 -0
- package/dist/components/useHasCustomFields.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/listView.d.ts +15 -0
- package/dist/lib/listView.d.ts.map +1 -0
- package/dist/lib/listView.js +66 -0
- package/dist/lib/listView.js.map +1 -0
- package/dist/lib/sort.d.ts +43 -0
- package/dist/lib/sort.d.ts.map +1 -0
- package/dist/lib/sort.js +102 -0
- package/dist/lib/sort.js.map +1 -0
- package/dist/pages/CompaniesPage.d.ts.map +1 -1
- package/dist/pages/CompaniesPage.js +88 -3
- package/dist/pages/CompaniesPage.js.map +1 -1
- package/dist/pages/CompanyDetail.d.ts.map +1 -1
- package/dist/pages/CompanyDetail.js +40 -15
- package/dist/pages/CompanyDetail.js.map +1 -1
- package/dist/pages/DealDetail.d.ts.map +1 -1
- package/dist/pages/DealDetail.js +20 -1
- package/dist/pages/DealDetail.js.map +1 -1
- package/dist/pages/DealsPage.d.ts.map +1 -1
- package/dist/pages/DealsPage.js +134 -14
- package/dist/pages/DealsPage.js.map +1 -1
- package/dist/pages/DecisionsPage.d.ts.map +1 -1
- package/dist/pages/DecisionsPage.js +52 -8
- package/dist/pages/DecisionsPage.js.map +1 -1
- package/dist/pages/EnquiriesPage.d.ts +2 -0
- package/dist/pages/EnquiriesPage.d.ts.map +1 -0
- package/dist/pages/EnquiriesPage.js +251 -0
- package/dist/pages/EnquiriesPage.js.map +1 -0
- package/dist/pages/EnquiryDetail.d.ts +2 -0
- package/dist/pages/EnquiryDetail.d.ts.map +1 -0
- package/dist/pages/EnquiryDetail.js +207 -0
- package/dist/pages/EnquiryDetail.js.map +1 -0
- package/dist/pages/EnquiryStageSettingsPage.d.ts +3 -0
- package/dist/pages/EnquiryStageSettingsPage.d.ts.map +1 -0
- package/dist/pages/EnquiryStageSettingsPage.js +34 -0
- package/dist/pages/EnquiryStageSettingsPage.js.map +1 -0
- package/dist/pages/FormDetail.d.ts.map +1 -1
- package/dist/pages/FormDetail.js +19 -1
- package/dist/pages/FormDetail.js.map +1 -1
- package/dist/pages/FormsPage.d.ts.map +1 -1
- package/dist/pages/FormsPage.js +53 -2
- package/dist/pages/FormsPage.js.map +1 -1
- package/dist/pages/FundraisingPage.d.ts.map +1 -1
- package/dist/pages/FundraisingPage.js +116 -14
- package/dist/pages/FundraisingPage.js.map +1 -1
- package/dist/pages/HiringPage.d.ts.map +1 -1
- package/dist/pages/HiringPage.js +27 -4
- package/dist/pages/HiringPage.js.map +1 -1
- package/dist/pages/ListDetail.d.ts +11 -0
- package/dist/pages/ListDetail.d.ts.map +1 -0
- package/dist/pages/ListDetail.js +214 -0
- package/dist/pages/ListDetail.js.map +1 -0
- package/dist/pages/ListsPage.d.ts +9 -0
- package/dist/pages/ListsPage.d.ts.map +1 -0
- package/dist/pages/ListsPage.js +125 -0
- package/dist/pages/ListsPage.js.map +1 -0
- package/dist/pages/OpportunitiesPage.d.ts.map +1 -1
- package/dist/pages/OpportunitiesPage.js +97 -14
- package/dist/pages/OpportunitiesPage.js.map +1 -1
- package/dist/pages/OpportunityDetail.d.ts.map +1 -1
- package/dist/pages/OpportunityDetail.js +64 -3
- package/dist/pages/OpportunityDetail.js.map +1 -1
- package/dist/pages/PartnershipDetail.d.ts.map +1 -1
- package/dist/pages/PartnershipDetail.js +19 -1
- package/dist/pages/PartnershipDetail.js.map +1 -1
- package/dist/pages/PartnershipsPage.d.ts.map +1 -1
- package/dist/pages/PartnershipsPage.js +111 -14
- package/dist/pages/PartnershipsPage.js.map +1 -1
- package/dist/pages/PeoplePage.d.ts.map +1 -1
- package/dist/pages/PeoplePage.js +119 -3
- package/dist/pages/PeoplePage.js.map +1 -1
- package/dist/pages/PersonDetail.d.ts.map +1 -1
- package/dist/pages/PersonDetail.js +67 -5
- package/dist/pages/PersonDetail.js.map +1 -1
- package/dist/pages/PlanningPage.d.ts.map +1 -1
- package/dist/pages/PlanningPage.js +88 -17
- package/dist/pages/PlanningPage.js.map +1 -1
- package/dist/pages/RaiseDetail.d.ts.map +1 -1
- package/dist/pages/RaiseDetail.js +13 -1
- package/dist/pages/RaiseDetail.js.map +1 -1
- package/dist/pages/RoleDetail.d.ts.map +1 -1
- package/dist/pages/RoleDetail.js +5 -4
- package/dist/pages/RoleDetail.js.map +1 -1
- package/dist/pages/SearchPage.d.ts.map +1 -1
- package/dist/pages/SearchPage.js +1 -0
- package/dist/pages/SearchPage.js.map +1 -1
- package/dist/pages/admin/DataPage.d.ts.map +1 -1
- package/dist/pages/admin/DataPage.js +126 -13
- package/dist/pages/admin/DataPage.js.map +1 -1
- package/dist/pages/admin/FieldsPage.d.ts +2 -0
- package/dist/pages/admin/FieldsPage.d.ts.map +1 -0
- package/dist/pages/admin/FieldsPage.js +164 -0
- package/dist/pages/admin/FieldsPage.js.map +1 -0
- package/dist/pages/admin/PrivacyPage.d.ts +9 -0
- package/dist/pages/admin/PrivacyPage.d.ts.map +1 -0
- package/dist/pages/admin/PrivacyPage.js +170 -0
- package/dist/pages/admin/PrivacyPage.js.map +1 -0
- package/dist/pages/admin/WebhookDeliveries.d.ts.map +1 -1
- package/dist/pages/admin/WebhookDeliveries.js +3 -2
- package/dist/pages/admin/WebhookDeliveries.js.map +1 -1
- package/dist/pages/admin/WebhooksPage.d.ts.map +1 -1
- package/dist/pages/admin/WebhooksPage.js +3 -2
- package/dist/pages/admin/WebhooksPage.js.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.d.ts.map +1 -1
- package/dist/pages/admin/WorkspaceSettingsPage.js +45 -2
- package/dist/pages/admin/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/dashboard/attention.d.ts.map +1 -1
- package/dist/pages/dashboard/attention.js +3 -0
- package/dist/pages/dashboard/attention.js.map +1 -1
- package/dist/pages/forms/AddFieldMenu.d.ts +17 -0
- package/dist/pages/forms/AddFieldMenu.d.ts.map +1 -0
- package/dist/pages/forms/AddFieldMenu.js +43 -0
- package/dist/pages/forms/AddFieldMenu.js.map +1 -0
- package/dist/pages/forms/EmbedPanel.d.ts +4 -0
- package/dist/pages/forms/EmbedPanel.d.ts.map +1 -1
- package/dist/pages/forms/EmbedPanel.js +62 -3
- package/dist/pages/forms/EmbedPanel.js.map +1 -1
- package/dist/pages/forms/FieldsEditor.d.ts.map +1 -1
- package/dist/pages/forms/FieldsEditor.js +179 -36
- package/dist/pages/forms/FieldsEditor.js.map +1 -1
- package/dist/pages/forms/FormActions.d.ts +6 -0
- package/dist/pages/forms/FormActions.d.ts.map +1 -0
- package/dist/pages/forms/FormActions.js +149 -0
- package/dist/pages/forms/FormActions.js.map +1 -0
- package/dist/pages/forms/FormSettings.d.ts.map +1 -1
- package/dist/pages/forms/FormSettings.js +15 -19
- package/dist/pages/forms/FormSettings.js.map +1 -1
- package/dist/pages/forms/SubmissionDetail.d.ts +2 -0
- package/dist/pages/forms/SubmissionDetail.d.ts.map +1 -0
- package/dist/pages/forms/SubmissionDetail.js +76 -0
- package/dist/pages/forms/SubmissionDetail.js.map +1 -0
- package/dist/pages/forms/SubmissionsTable.d.ts.map +1 -1
- package/dist/pages/forms/SubmissionsTable.js +57 -8
- package/dist/pages/forms/SubmissionsTable.js.map +1 -1
- package/dist/pages/forms/fieldList.d.ts +11 -5
- package/dist/pages/forms/fieldList.d.ts.map +1 -1
- package/dist/pages/forms/fieldList.js +66 -6
- package/dist/pages/forms/fieldList.js.map +1 -1
- package/dist/pages/forms/template.d.ts +17 -0
- package/dist/pages/forms/template.d.ts.map +1 -1
- package/dist/pages/forms/template.js +93 -0
- package/dist/pages/forms/template.js.map +1 -1
- package/dist/pages/handbook/HandbookPage.js +1 -1
- package/dist/pages/hiringDirectory.js +2 -2
- package/dist/pages/onboarding/WorkspaceStepPage.d.ts.map +1 -1
- package/dist/pages/onboarding/WorkspaceStepPage.js +15 -2
- package/dist/pages/onboarding/WorkspaceStepPage.js.map +1 -1
- package/dist/pages/positionDirectory.js +2 -2
- package/package.json +2 -2
- package/src/api/resource.ts +155 -32
- package/src/api/resources/companies.ts +7 -1
- package/src/api/resources/consentPurposes.ts +83 -0
- package/src/api/resources/customFields.ts +88 -0
- package/src/api/resources/decisions.ts +3 -0
- package/src/api/resources/emailDomainLinker.ts +43 -0
- package/src/api/resources/enquiries.ts +118 -0
- package/src/api/resources/forms.ts +59 -19
- package/src/api/resources/handbookPages.ts +1 -1
- package/src/api/resources/importJobs.ts +9 -0
- package/src/api/resources/listMembers.ts +173 -0
- package/src/api/resources/lists.ts +61 -0
- package/src/api/resources/opportunities.ts +3 -0
- package/src/api/resources/people.ts +4 -1
- package/src/api/resources/roles.ts +3 -1
- package/src/api/resources/sampleData.ts +57 -0
- package/src/api/resources/webhooks.ts +8 -24
- package/src/app/KelpieApp.tsx +16 -0
- package/src/components/ActivitiesPanel.tsx +3 -10
- package/src/components/ColumnPicker.tsx +124 -0
- package/src/components/CustomFieldsPanel.tsx +330 -0
- package/src/components/DataTable.tsx +89 -10
- package/src/components/DecisionsPanel.tsx +4 -12
- package/src/components/EntitySearch.tsx +12 -5
- package/src/components/FormsPanel.tsx +86 -0
- package/src/components/ListsPanel.tsx +176 -0
- package/src/components/NotesPanel.tsx +6 -14
- package/src/components/Paginator.tsx +78 -0
- package/src/components/PlanAttention.tsx +20 -3
- package/src/components/PlanPanel.tsx +2 -10
- package/src/components/Shell.tsx +4 -0
- package/src/components/useHasCustomFields.ts +18 -0
- package/src/index.ts +10 -1
- package/src/lib/listView.ts +149 -0
- package/src/lib/sort.ts +143 -0
- package/src/pages/CompaniesPage.tsx +125 -11
- package/src/pages/CompanyDetail.tsx +98 -63
- package/src/pages/DealDetail.tsx +34 -0
- package/src/pages/DealsPage.tsx +178 -24
- package/src/pages/DecisionsPage.tsx +75 -18
- package/src/pages/EnquiriesPage.tsx +434 -0
- package/src/pages/EnquiryDetail.tsx +554 -0
- package/src/pages/EnquiryStageSettingsPage.tsx +63 -0
- package/src/pages/FormDetail.tsx +23 -1
- package/src/pages/FormsPage.tsx +81 -17
- package/src/pages/FundraisingPage.tsx +152 -24
- package/src/pages/HiringPage.tsx +39 -11
- package/src/pages/ListDetail.tsx +470 -0
- package/src/pages/ListsPage.tsx +281 -0
- package/src/pages/OpportunitiesPage.tsx +130 -24
- package/src/pages/OpportunityDetail.tsx +168 -2
- package/src/pages/PartnershipDetail.tsx +35 -0
- package/src/pages/PartnershipsPage.tsx +149 -24
- package/src/pages/PeoplePage.tsx +157 -11
- package/src/pages/PersonDetail.tsx +157 -6
- package/src/pages/PlanningPage.tsx +259 -29
- package/src/pages/RaiseDetail.tsx +24 -0
- package/src/pages/RoleDetail.tsx +21 -26
- package/src/pages/SearchPage.tsx +1 -0
- package/src/pages/admin/DataPage.tsx +324 -13
- package/src/pages/admin/FieldsPage.tsx +455 -0
- package/src/pages/admin/PrivacyPage.tsx +461 -0
- package/src/pages/admin/WebhookDeliveries.tsx +10 -15
- package/src/pages/admin/WebhooksPage.tsx +14 -19
- package/src/pages/admin/WorkspaceSettingsPage.tsx +134 -2
- package/src/pages/dashboard/attention.ts +3 -0
- package/src/pages/forms/AddFieldMenu.tsx +121 -0
- package/src/pages/forms/EmbedPanel.tsx +163 -2
- package/src/pages/forms/FieldsEditor.tsx +539 -165
- package/src/pages/forms/FormActions.tsx +552 -0
- package/src/pages/forms/FormSettings.tsx +29 -70
- package/src/pages/forms/SubmissionDetail.tsx +249 -0
- package/src/pages/forms/SubmissionsTable.tsx +112 -26
- package/src/pages/forms/fieldList.ts +68 -5
- package/src/pages/forms/template.ts +103 -0
- package/src/pages/handbook/HandbookPage.tsx +1 -1
- package/src/pages/hiringDirectory.ts +2 -2
- package/src/pages/onboarding/WorkspaceStepPage.tsx +31 -3
- package/src/pages/positionDirectory.ts +2 -2
- package/src/styles.css +3 -3
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { CONSENT_PURPOSE_STATUS_LABELS, CONSENT_PURPOSE_STATUSES } from '@kelpie/schemas'
|
|
2
|
+
import type { ConsentPurpose, ConsentPurposeStatus } from '@kelpie/schemas'
|
|
3
|
+
import { useMemo, useState } from 'react'
|
|
4
|
+
import type { FormEvent } from 'react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
useConsentPurposes,
|
|
8
|
+
useCreateConsentPurpose,
|
|
9
|
+
useDeleteConsentPurpose,
|
|
10
|
+
useUpdateConsentPurpose,
|
|
11
|
+
} from '../../api/resources/consentPurposes.ts'
|
|
12
|
+
import { useSession } from '../../api/resources/session.ts'
|
|
13
|
+
import { AddButton } from '../../components/SectionHeader.tsx'
|
|
14
|
+
import { PageHeader } from '../../components/PageHeader.tsx'
|
|
15
|
+
import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Admin surface for workspace consent purposes.
|
|
19
|
+
*
|
|
20
|
+
* Admin-only, matching Custom fields: a purpose change touches every form,
|
|
21
|
+
* list, and import job that names it, and is team-wide config. A purpose has
|
|
22
|
+
* a default status that every Person without an explicit override inherits.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export function PrivacyPage(): React.JSX.Element {
|
|
26
|
+
const { session } = useSession()
|
|
27
|
+
const isAdmin = session?.role === 'owner' || session?.role === 'admin'
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="animate-slide-in mx-auto max-w-4xl space-y-6">
|
|
31
|
+
<PageHeader
|
|
32
|
+
title="Privacy"
|
|
33
|
+
description="Consent purposes for the things you contact people about. A form's consent checkbox, a list you add signups to, and an import can each grant one of these — and every Person's record reflects it."
|
|
34
|
+
/>
|
|
35
|
+
{isAdmin ? <PurposesAdmin /> : <MemberNotice />}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function MemberNotice(): React.JSX.Element {
|
|
41
|
+
return (
|
|
42
|
+
<p className="rounded-md border border-border px-4 py-3 text-[13px] text-ink-muted">
|
|
43
|
+
Consent purposes are managed by workspace admins.
|
|
44
|
+
</p>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function PurposesAdmin(): React.JSX.Element {
|
|
49
|
+
const [adding, setAdding] = useState(false)
|
|
50
|
+
const [seeding, setSeeding] = useState(false)
|
|
51
|
+
const purposes = useConsentPurposes({ sort: 'sort_order', limit: 200 })
|
|
52
|
+
const create = useCreateConsentPurpose()
|
|
53
|
+
|
|
54
|
+
async function seedStarters(): Promise<void> {
|
|
55
|
+
setSeeding(true)
|
|
56
|
+
try {
|
|
57
|
+
await create.runAsync({
|
|
58
|
+
slug: 'contact',
|
|
59
|
+
label: 'Contact',
|
|
60
|
+
description: 'Being contacted by the workspace about our work together.',
|
|
61
|
+
defaultStatus: 'unknown',
|
|
62
|
+
})
|
|
63
|
+
await create.runAsync({
|
|
64
|
+
slug: 'marketing',
|
|
65
|
+
label: 'Marketing',
|
|
66
|
+
description: 'Marketing communications — newsletters, product updates, and campaigns.',
|
|
67
|
+
defaultStatus: 'unknown',
|
|
68
|
+
})
|
|
69
|
+
} catch {
|
|
70
|
+
// Error rendered inline via the ErrorPanel below.
|
|
71
|
+
} finally {
|
|
72
|
+
setSeeding(false)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<div className="flex justify-end">
|
|
79
|
+
<AddButton
|
|
80
|
+
onClick={() => {
|
|
81
|
+
setAdding(true)
|
|
82
|
+
}}
|
|
83
|
+
label="Add a purpose"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{adding && (
|
|
88
|
+
<AddPurposeForm
|
|
89
|
+
onDone={() => {
|
|
90
|
+
setAdding(false)
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{purposes.error !== null && <ErrorPanel error={purposes.error} />}
|
|
96
|
+
{create.error !== null && <ErrorPanel error={create.error} />}
|
|
97
|
+
{purposes.isLoading ? (
|
|
98
|
+
<LoadingPanel label="Loading purposes…" />
|
|
99
|
+
) : purposes.records.length === 0 ? (
|
|
100
|
+
<EmptyState
|
|
101
|
+
onAdd={() => {
|
|
102
|
+
setAdding(true)
|
|
103
|
+
}}
|
|
104
|
+
onSeedStarters={() => {
|
|
105
|
+
void seedStarters()
|
|
106
|
+
}}
|
|
107
|
+
isSeeding={seeding}
|
|
108
|
+
/>
|
|
109
|
+
) : (
|
|
110
|
+
<ul className="space-y-3">
|
|
111
|
+
{purposes.records.map((purpose) => (
|
|
112
|
+
<li key={purpose.id}>
|
|
113
|
+
<PurposeRow purpose={purpose} />
|
|
114
|
+
</li>
|
|
115
|
+
))}
|
|
116
|
+
</ul>
|
|
117
|
+
)}
|
|
118
|
+
</>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface EmptyStateProps {
|
|
123
|
+
readonly onAdd: () => void
|
|
124
|
+
readonly onSeedStarters: () => void
|
|
125
|
+
readonly isSeeding: boolean
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function EmptyState({ onAdd, onSeedStarters, isSeeding }: EmptyStateProps): React.JSX.Element {
|
|
129
|
+
return (
|
|
130
|
+
<div className="rounded-md border border-dashed border-border p-6 text-[13px]">
|
|
131
|
+
<p className="font-medium text-ink">You have no consent purposes yet.</p>
|
|
132
|
+
<p className="mt-1 text-ink-muted">
|
|
133
|
+
A purpose is what a person is agreeing to — being contacted at all, marketing, research.
|
|
134
|
+
Every workspace usually has at least a general <span className="font-medium">Contact</span>{' '}
|
|
135
|
+
purpose. Forms, lists, and imports that don’t need consent don’t need one.
|
|
136
|
+
</p>
|
|
137
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={onSeedStarters}
|
|
141
|
+
disabled={isSeeding}
|
|
142
|
+
className="rounded-md bg-accent px-3 py-1.5 text-[12px] font-medium text-white hover:bg-accent-hover disabled:opacity-50"
|
|
143
|
+
>
|
|
144
|
+
{isSeeding ? 'Adding…' : 'Add Contact & Marketing'}
|
|
145
|
+
</button>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={onAdd}
|
|
149
|
+
className="rounded-md border border-border px-3 py-1.5 text-[12px] font-medium text-ink hover:border-ink-muted"
|
|
150
|
+
>
|
|
151
|
+
Add my own
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface AddPurposeFormProps {
|
|
159
|
+
readonly onDone: () => void
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function AddPurposeForm({ onDone }: AddPurposeFormProps): React.JSX.Element {
|
|
163
|
+
const create = useCreateConsentPurpose()
|
|
164
|
+
const [label, setLabel] = useState('')
|
|
165
|
+
const [slug, setSlug] = useState('')
|
|
166
|
+
const [slugEdited, setSlugEdited] = useState(false)
|
|
167
|
+
const [description, setDescription] = useState('')
|
|
168
|
+
const [defaultStatus, setDefaultStatus] = useState<ConsentPurposeStatus>('unknown')
|
|
169
|
+
|
|
170
|
+
const derivedSlug = useMemo(
|
|
171
|
+
() => (slugEdited ? slug : slugify(label)),
|
|
172
|
+
[slugEdited, slug, label],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
function submit(event: FormEvent): void {
|
|
176
|
+
event.preventDefault()
|
|
177
|
+
const trimmedLabel = label.trim()
|
|
178
|
+
if (trimmedLabel.length === 0) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
create
|
|
182
|
+
.runAsync({
|
|
183
|
+
slug: derivedSlug,
|
|
184
|
+
label: trimmedLabel,
|
|
185
|
+
description: description.trim(),
|
|
186
|
+
defaultStatus,
|
|
187
|
+
})
|
|
188
|
+
.then(() => {
|
|
189
|
+
setLabel('')
|
|
190
|
+
setSlug('')
|
|
191
|
+
setSlugEdited(false)
|
|
192
|
+
setDescription('')
|
|
193
|
+
setDefaultStatus('unknown')
|
|
194
|
+
onDone()
|
|
195
|
+
})
|
|
196
|
+
.catch(() => {
|
|
197
|
+
// Error rendered below.
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<form
|
|
203
|
+
onSubmit={submit}
|
|
204
|
+
className="space-y-3 rounded-md border border-border bg-surface-raised p-4"
|
|
205
|
+
>
|
|
206
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
207
|
+
<Field label="Label">
|
|
208
|
+
<input
|
|
209
|
+
className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
210
|
+
value={label}
|
|
211
|
+
onChange={(event) => {
|
|
212
|
+
setLabel(event.target.value)
|
|
213
|
+
}}
|
|
214
|
+
required
|
|
215
|
+
/>
|
|
216
|
+
</Field>
|
|
217
|
+
<Field label="Slug">
|
|
218
|
+
<input
|
|
219
|
+
className="w-full rounded-md border border-border bg-transparent px-2 py-1 font-mono text-[12px] outline-none focus:border-accent"
|
|
220
|
+
value={derivedSlug}
|
|
221
|
+
onChange={(event) => {
|
|
222
|
+
setSlug(event.target.value)
|
|
223
|
+
setSlugEdited(true)
|
|
224
|
+
}}
|
|
225
|
+
placeholder="lowercase_snake_case"
|
|
226
|
+
pattern="^[a-z][a-z0-9_]*$"
|
|
227
|
+
required
|
|
228
|
+
/>
|
|
229
|
+
</Field>
|
|
230
|
+
<Field label="Default status">
|
|
231
|
+
<select
|
|
232
|
+
className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
233
|
+
value={defaultStatus}
|
|
234
|
+
onChange={(event) => {
|
|
235
|
+
setDefaultStatus(event.target.value as ConsentPurposeStatus)
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{CONSENT_PURPOSE_STATUSES.map((option) => (
|
|
239
|
+
<option key={option} value={option}>
|
|
240
|
+
{CONSENT_PURPOSE_STATUS_LABELS[option]}
|
|
241
|
+
</option>
|
|
242
|
+
))}
|
|
243
|
+
</select>
|
|
244
|
+
</Field>
|
|
245
|
+
</div>
|
|
246
|
+
<Field label="Description (shown on capture surfaces and to agents)">
|
|
247
|
+
<textarea
|
|
248
|
+
className="min-h-[60px] w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
249
|
+
value={description}
|
|
250
|
+
onChange={(event) => {
|
|
251
|
+
setDescription(event.target.value)
|
|
252
|
+
}}
|
|
253
|
+
/>
|
|
254
|
+
</Field>
|
|
255
|
+
|
|
256
|
+
{create.error !== null && <ErrorPanel error={create.error} />}
|
|
257
|
+
|
|
258
|
+
<div className="flex justify-end gap-2">
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
onClick={() => {
|
|
262
|
+
onDone()
|
|
263
|
+
}}
|
|
264
|
+
className="rounded-md border border-border px-3 py-1 text-[12px] text-ink-muted hover:text-ink"
|
|
265
|
+
>
|
|
266
|
+
Cancel
|
|
267
|
+
</button>
|
|
268
|
+
<button
|
|
269
|
+
type="submit"
|
|
270
|
+
disabled={create.isPending}
|
|
271
|
+
className="rounded-md bg-accent px-3 py-1 text-[12px] font-medium text-white hover:bg-accent-hover disabled:opacity-50"
|
|
272
|
+
>
|
|
273
|
+
{create.isPending ? 'Adding…' : 'Add purpose'}
|
|
274
|
+
</button>
|
|
275
|
+
</div>
|
|
276
|
+
</form>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interface PurposeRowProps {
|
|
281
|
+
readonly purpose: ConsentPurpose
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function PurposeRow({ purpose }: PurposeRowProps): React.JSX.Element {
|
|
285
|
+
const update = useUpdateConsentPurpose()
|
|
286
|
+
const remove = useDeleteConsentPurpose()
|
|
287
|
+
const [editing, setEditing] = useState(false)
|
|
288
|
+
const [label, setLabel] = useState(purpose.label)
|
|
289
|
+
const [description, setDescription] = useState(purpose.description)
|
|
290
|
+
const [defaultStatus, setDefaultStatus] = useState<ConsentPurposeStatus>(purpose.defaultStatus)
|
|
291
|
+
const [confirmingDelete, setConfirmingDelete] = useState(false)
|
|
292
|
+
|
|
293
|
+
function save(event: FormEvent): void {
|
|
294
|
+
event.preventDefault()
|
|
295
|
+
update
|
|
296
|
+
.runAsync({
|
|
297
|
+
id: purpose.id,
|
|
298
|
+
changes: {
|
|
299
|
+
label: label.trim(),
|
|
300
|
+
description: description.trim(),
|
|
301
|
+
defaultStatus,
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
.then(() => {
|
|
305
|
+
setEditing(false)
|
|
306
|
+
})
|
|
307
|
+
.catch(() => {
|
|
308
|
+
// Error rendered inline.
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<div className="rounded-md border border-border p-3">
|
|
314
|
+
<div className="flex items-start justify-between gap-3">
|
|
315
|
+
<div className="min-w-0 flex-1">
|
|
316
|
+
<div className="flex flex-wrap items-baseline gap-2">
|
|
317
|
+
<div className="text-[14px] font-semibold text-ink">{purpose.label}</div>
|
|
318
|
+
<code className="rounded bg-surface-raised px-1.5 py-0.5 font-mono text-[11px] text-ink-muted">
|
|
319
|
+
{purpose.slug}
|
|
320
|
+
</code>
|
|
321
|
+
<span className="text-[11px] text-ink-muted">
|
|
322
|
+
Default: {CONSENT_PURPOSE_STATUS_LABELS[purpose.defaultStatus]}
|
|
323
|
+
</span>
|
|
324
|
+
</div>
|
|
325
|
+
{purpose.description.length > 0 && (
|
|
326
|
+
<p className="mt-1 text-[12px] text-ink-muted">{purpose.description}</p>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
<div className="flex shrink-0 gap-2">
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
onClick={() => {
|
|
333
|
+
setEditing((current) => !current)
|
|
334
|
+
}}
|
|
335
|
+
className="text-[12px] text-ink-muted hover:text-ink"
|
|
336
|
+
>
|
|
337
|
+
{editing ? 'Cancel' : 'Edit'}
|
|
338
|
+
</button>
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
onClick={() => {
|
|
342
|
+
setConfirmingDelete(true)
|
|
343
|
+
}}
|
|
344
|
+
className="text-[12px] text-red-600 hover:text-red-700"
|
|
345
|
+
>
|
|
346
|
+
Delete
|
|
347
|
+
</button>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
{editing && (
|
|
352
|
+
<form onSubmit={save} className="mt-3 space-y-2">
|
|
353
|
+
<Field label="Label">
|
|
354
|
+
<input
|
|
355
|
+
className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
356
|
+
value={label}
|
|
357
|
+
onChange={(event) => {
|
|
358
|
+
setLabel(event.target.value)
|
|
359
|
+
}}
|
|
360
|
+
required
|
|
361
|
+
/>
|
|
362
|
+
</Field>
|
|
363
|
+
<Field label="Default status">
|
|
364
|
+
<select
|
|
365
|
+
className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
366
|
+
value={defaultStatus}
|
|
367
|
+
onChange={(event) => {
|
|
368
|
+
setDefaultStatus(event.target.value as ConsentPurposeStatus)
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
{CONSENT_PURPOSE_STATUSES.map((option) => (
|
|
372
|
+
<option key={option} value={option}>
|
|
373
|
+
{CONSENT_PURPOSE_STATUS_LABELS[option]}
|
|
374
|
+
</option>
|
|
375
|
+
))}
|
|
376
|
+
</select>
|
|
377
|
+
</Field>
|
|
378
|
+
<Field label="Description">
|
|
379
|
+
<textarea
|
|
380
|
+
className="min-h-[60px] w-full rounded-md border border-border bg-transparent px-2 py-1 text-[13px] outline-none focus:border-accent"
|
|
381
|
+
value={description}
|
|
382
|
+
onChange={(event) => {
|
|
383
|
+
setDescription(event.target.value)
|
|
384
|
+
}}
|
|
385
|
+
/>
|
|
386
|
+
</Field>
|
|
387
|
+
{update.error !== null && <ErrorPanel error={update.error} />}
|
|
388
|
+
<div className="flex justify-end">
|
|
389
|
+
<button
|
|
390
|
+
type="submit"
|
|
391
|
+
disabled={update.isPending}
|
|
392
|
+
className="rounded-md bg-accent px-3 py-1 text-[12px] font-medium text-white hover:bg-accent-hover disabled:opacity-50"
|
|
393
|
+
>
|
|
394
|
+
{update.isPending ? 'Saving…' : 'Save'}
|
|
395
|
+
</button>
|
|
396
|
+
</div>
|
|
397
|
+
</form>
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
{confirmingDelete && (
|
|
401
|
+
<div className="mt-3 rounded-md border border-red-300 bg-red-50 p-3 text-[13px] text-red-800 dark:border-red-800 dark:bg-red-950/40 dark:text-red-200">
|
|
402
|
+
<p>
|
|
403
|
+
Delete the {purpose.label} purpose? Remove it from every form field and list that
|
|
404
|
+
names it first — the delete refuses otherwise.
|
|
405
|
+
</p>
|
|
406
|
+
{remove.error !== null && <ErrorPanel error={remove.error} />}
|
|
407
|
+
<div className="mt-2 flex justify-end gap-2">
|
|
408
|
+
<button
|
|
409
|
+
type="button"
|
|
410
|
+
onClick={() => {
|
|
411
|
+
setConfirmingDelete(false)
|
|
412
|
+
}}
|
|
413
|
+
className="rounded-md border border-red-300 px-3 py-1 text-[12px] hover:bg-red-100 dark:border-red-800 dark:hover:bg-red-900/40"
|
|
414
|
+
>
|
|
415
|
+
Cancel
|
|
416
|
+
</button>
|
|
417
|
+
<button
|
|
418
|
+
type="button"
|
|
419
|
+
onClick={() => {
|
|
420
|
+
remove
|
|
421
|
+
.runAsync(purpose.id)
|
|
422
|
+
.then(() => {
|
|
423
|
+
setConfirmingDelete(false)
|
|
424
|
+
})
|
|
425
|
+
.catch(() => {
|
|
426
|
+
// Error rendered inline.
|
|
427
|
+
})
|
|
428
|
+
}}
|
|
429
|
+
disabled={remove.isPending}
|
|
430
|
+
className="rounded-md bg-red-600 px-3 py-1 text-[12px] font-medium text-white hover:bg-red-700 disabled:opacity-50"
|
|
431
|
+
>
|
|
432
|
+
{remove.isPending ? 'Deleting…' : 'Delete'}
|
|
433
|
+
</button>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
</div>
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
interface FieldProps {
|
|
442
|
+
readonly label: string
|
|
443
|
+
readonly children: React.ReactNode
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function Field({ label, children }: FieldProps): React.JSX.Element {
|
|
447
|
+
return (
|
|
448
|
+
<label className="block text-[12px]">
|
|
449
|
+
<span className="mb-1 block font-medium text-ink-muted">{label}</span>
|
|
450
|
+
{children}
|
|
451
|
+
</label>
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function slugify(label: string): string {
|
|
456
|
+
return label
|
|
457
|
+
.toLowerCase()
|
|
458
|
+
.replace(/[^a-z0-9]+/gu, '_')
|
|
459
|
+
.replace(/^_+|_+$/gu, '')
|
|
460
|
+
.replace(/^([0-9])/u, '_$1')
|
|
461
|
+
}
|
|
@@ -7,6 +7,7 @@ import { Chip } from '../../components/Chip.tsx'
|
|
|
7
7
|
import type { ChipTone } from '../../components/Chip.tsx'
|
|
8
8
|
import { DataTable } from '../../components/DataTable.tsx'
|
|
9
9
|
import type { Column } from '../../components/DataTable.tsx'
|
|
10
|
+
import { Paginator } from '../../components/Paginator.tsx'
|
|
10
11
|
import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
|
|
11
12
|
import { SegmentedControl } from '../../components/SegmentedControl.tsx'
|
|
12
13
|
import { formatDateTime } from '../../lib/dates.ts'
|
|
@@ -138,25 +139,19 @@ export function WebhookDeliveries({ webhookId }: { readonly webhookId: string })
|
|
|
138
139
|
{status === 'all' ? 'Nothing delivered yet.' : `No ${status} deliveries.`}
|
|
139
140
|
</p>
|
|
140
141
|
) : (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
<>
|
|
143
|
+
<Paginator list={deliveries} placement="top" />
|
|
144
|
+
<DataTable
|
|
145
|
+
columns={columns}
|
|
146
|
+
rows={deliveries.records}
|
|
147
|
+
getRowId={(delivery) => delivery.id}
|
|
148
|
+
/>
|
|
149
|
+
</>
|
|
146
150
|
)}
|
|
147
151
|
|
|
148
152
|
{open !== undefined && <PayloadPanel delivery={open} />}
|
|
149
153
|
|
|
150
|
-
{deliveries
|
|
151
|
-
<button
|
|
152
|
-
type="button"
|
|
153
|
-
onClick={deliveries.loadMore}
|
|
154
|
-
disabled={deliveries.isLoadingMore}
|
|
155
|
-
className="rounded-md border border-border px-3 py-1.5 text-[12px] font-medium text-ink transition hover:border-border-strong hover:bg-surface-sunken disabled:opacity-50"
|
|
156
|
-
>
|
|
157
|
-
{deliveries.isLoadingMore ? 'Loading…' : 'Load more'}
|
|
158
|
-
</button>
|
|
159
|
-
)}
|
|
154
|
+
<Paginator list={deliveries} />
|
|
160
155
|
</div>
|
|
161
156
|
)
|
|
162
157
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { Chip } from '../../components/Chip.tsx'
|
|
20
20
|
import type { ChipTone } from '../../components/Chip.tsx'
|
|
21
21
|
import { PageHeader } from '../../components/PageHeader.tsx'
|
|
22
|
+
import { Paginator } from '../../components/Paginator.tsx'
|
|
22
23
|
import { ErrorPanel, LoadingPanel } from '../../components/QueryState.tsx'
|
|
23
24
|
import { formatDateTime } from '../../lib/dates.ts'
|
|
24
25
|
import { WebhookDeliveries } from './WebhookDeliveries.tsx'
|
|
@@ -68,7 +69,7 @@ function MemberNotice(): React.JSX.Element {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
function WebhookAdmin(): React.JSX.Element {
|
|
71
|
-
const
|
|
72
|
+
const webhooks = useWebhooks()
|
|
72
73
|
const [minted, setMinted] = useState<CreatedWebhook | null>(null)
|
|
73
74
|
|
|
74
75
|
return (
|
|
@@ -88,30 +89,24 @@ function WebhookAdmin(): React.JSX.Element {
|
|
|
88
89
|
/>
|
|
89
90
|
)}
|
|
90
91
|
|
|
91
|
-
{error !== null && <ErrorPanel error={error} />}
|
|
92
|
+
{webhooks.error !== null && <ErrorPanel error={webhooks.error} />}
|
|
92
93
|
|
|
93
|
-
{isLoading ? (
|
|
94
|
+
{webhooks.isLoading ? (
|
|
94
95
|
<LoadingPanel label="Loading webhooks…" />
|
|
95
|
-
) : records.length === 0 ? (
|
|
96
|
+
) : webhooks.records.length === 0 ? (
|
|
96
97
|
<p className="text-[13px] text-ink-muted">No webhooks yet.</p>
|
|
97
98
|
) : (
|
|
98
|
-
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
<>
|
|
100
|
+
<Paginator list={webhooks} placement="top" />
|
|
101
|
+
<ul className="space-y-3">
|
|
102
|
+
{webhooks.records.map((webhook) => (
|
|
103
|
+
<WebhookRow key={webhook.id} webhook={webhook} onRotated={setMinted} />
|
|
104
|
+
))}
|
|
105
|
+
</ul>
|
|
106
|
+
</>
|
|
103
107
|
)}
|
|
104
108
|
|
|
105
|
-
{
|
|
106
|
-
<button
|
|
107
|
-
type="button"
|
|
108
|
-
disabled={isLoadingMore}
|
|
109
|
-
onClick={loadMore}
|
|
110
|
-
className="rounded-md border border-border px-3 py-1.5 text-[12px] font-medium text-ink-muted transition hover:text-ink disabled:opacity-50"
|
|
111
|
-
>
|
|
112
|
-
{isLoadingMore ? 'Loading…' : 'Load more'}
|
|
113
|
-
</button>
|
|
114
|
-
)}
|
|
109
|
+
<Paginator list={webhooks} />
|
|
115
110
|
</>
|
|
116
111
|
)
|
|
117
112
|
}
|