@aglyn/plugins-crm 1.0.0-beta.218 → 1.0.0-beta.220
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/README.md +2 -0
- package/package.json +14 -14
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.js +12 -4
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/company-detail-page.js +11 -0
- package/src/lib/components/company-detail-page.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +12 -2
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/company-import-drawer.d.ts +1 -1
- package/src/lib/components/company-picker.js +10 -3
- package/src/lib/components/company-picker.js.map +1 -1
- package/src/lib/components/contact-associations-card.js +4 -3
- package/src/lib/components/contact-associations-card.js.map +1 -1
- package/src/lib/components/contact-detail-page.d.ts +4 -4
- package/src/lib/components/contact-detail-page.js +50 -53
- package/src/lib/components/contact-detail-page.js.map +1 -1
- package/src/lib/components/contact-list-columns.d.ts +3 -0
- package/src/lib/components/contact-list-columns.js +21 -5
- package/src/lib/components/contact-list-columns.js.map +1 -1
- package/src/lib/components/contact-merge-dialog.js +2 -1
- package/src/lib/components/contact-merge-dialog.js.map +1 -1
- package/src/lib/components/contact-timeline-card.js +4 -3
- package/src/lib/components/contact-timeline-card.js.map +1 -1
- package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
- package/src/lib/components/contacts-bulk-bar.js +24 -51
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +9 -3
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/crm-booking-zone.d.ts +8 -2
- package/src/lib/components/crm-booking-zone.js.map +1 -1
- package/src/lib/components/crm-export-all-button.d.ts +1 -1
- package/src/lib/components/crm-export-all-button.js.map +1 -1
- package/src/lib/components/crm-glance-card.js +2 -1
- package/src/lib/components/crm-glance-card.js.map +1 -1
- package/src/lib/components/crm-send-email-dialog.js +9 -6
- package/src/lib/components/crm-send-email-dialog.js.map +1 -1
- package/src/lib/components/csv-import-drawer.js +2 -2
- package/src/lib/components/csv-import-drawer.js.map +1 -1
- package/src/lib/components/deal-detail-page.js +18 -2
- package/src/lib/components/deal-detail-page.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +10 -0
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deal-products-card.js +27 -16
- package/src/lib/components/deal-products-card.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.js +13 -5
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.d.ts +4 -4
- package/src/lib/components/lead-campaigns-card.js +15 -8
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/lead-detail-page.js +25 -5
- package/src/lib/components/lead-detail-page.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +10 -0
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-status-chip.js +3 -1
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/lead-surfaces-note.js +4 -5
- package/src/lib/components/lead-surfaces-note.js.map +1 -1
- package/src/lib/components/lead-unqualify-dialog.js +6 -0
- package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.js +34 -11
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +41 -8
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/new-lead-drawer.js +5 -4
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/org-lead-surfaces-note.js +6 -2
- package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
- package/src/lib/components/recipes-card.js +9 -4
- package/src/lib/components/recipes-card.js.map +1 -1
- package/src/lib/components/record-sharing-card.d.ts +57 -0
- package/src/lib/components/record-sharing-card.js +244 -0
- package/src/lib/components/record-sharing-card.js.map +1 -0
- package/src/lib/components/reports/activity-card.js +4 -3
- package/src/lib/components/reports/activity-card.js.map +1 -1
- package/src/lib/components/reports/closed-deals-card.js +4 -3
- package/src/lib/components/reports/closed-deals-card.js.map +1 -1
- package/src/lib/components/reports/contacts-mix-card.js +3 -2
- package/src/lib/components/reports/contacts-mix-card.js.map +1 -1
- package/src/lib/components/reports/contacts-trend-card.js +2 -1
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.js +3 -2
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.js +8 -6
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/pipeline-card.js +4 -3
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-format.d.ts +1 -1
- package/src/lib/components/reports/report-format.js.map +1 -1
- package/src/lib/components/reports/report-scope.d.ts +1 -1
- package/src/lib/components/reports/report-scope.js.map +1 -1
- package/src/lib/components/reports/source-conversion-card.js +2 -1
- package/src/lib/components/reports/source-conversion-card.js.map +1 -1
- package/src/lib/components/reports/tasks-card.js +2 -1
- package/src/lib/components/reports/tasks-card.js.map +1 -1
- package/src/lib/components/reports/won-lost-by-owner-card.js +3 -2
- package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -1
- package/src/lib/components/reports-section.js +4 -4
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/components/settings-section.js +7 -0
- package/src/lib/components/settings-section.js.map +1 -1
- package/src/lib/components/share-records-dialog.d.ts +28 -0
- package/src/lib/components/share-records-dialog.js +184 -0
- package/src/lib/components/share-records-dialog.js.map +1 -0
- package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
- package/src/lib/components/sharing-rule-drawer.js +374 -0
- package/src/lib/components/sharing-rule-drawer.js.map +1 -0
- package/src/lib/components/sharing-rules-card.d.ts +24 -0
- package/src/lib/components/sharing-rules-card.js +287 -0
- package/src/lib/components/sharing-rules-card.js.map +1 -0
- package/src/lib/components/use-crm-api.d.ts +1 -1
- package/src/lib/components/use-crm-api.js.map +1 -1
- package/src/lib/components/use-site-route-context.d.ts +25 -0
- package/src/lib/components/use-site-route-context.js +58 -0
- package/src/lib/components/use-site-route-context.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +31 -0
- package/src/lib/constants/api-routes.js +27 -1
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +2 -0
- package/src/lib/constants/bundle-common.js +1 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +41 -0
- package/src/lib/declarations.console-server.js +115 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.server.d.ts +12 -0
- package/src/lib/declarations.server.js +84 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-contact-remove.d.ts +35 -0
- package/src/lib/hooks/use-contact-remove.js +77 -0
- package/src/lib/hooks/use-contact-remove.js.map +1 -0
- package/src/lib/hooks/use-crm-campaigns.d.ts +2 -2
- package/src/lib/hooks/use-crm-campaigns.js +3 -3
- package/src/lib/hooks/use-crm-campaigns.js.map +1 -1
- package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
- package/src/lib/hooks/use-crm-sharing.js +109 -0
- package/src/lib/hooks/use-crm-sharing.js.map +1 -0
- package/src/lib/model/campaign-filing-activity.d.ts +1 -1
- package/src/lib/model/campaign-filing-activity.js +1 -1
- package/src/lib/model/campaign-filing-activity.js.map +1 -1
- package/src/lib/model/companies-csv.d.ts +1 -1
- package/src/lib/model/companies-csv.js +2 -2
- package/src/lib/model/companies-csv.js.map +1 -1
- package/src/lib/model/contact-holder.d.ts +68 -0
- package/src/lib/model/contact-holder.js +129 -0
- package/src/lib/model/contact-holder.js.map +1 -0
- package/src/lib/model/contact-interaction-links.d.ts +46 -0
- package/src/lib/model/contact-interaction-links.js +65 -0
- package/src/lib/model/contact-interaction-links.js.map +1 -0
- package/src/lib/model/contact-merge.d.ts +136 -0
- package/src/lib/model/contact-merge.js +412 -0
- package/src/lib/model/contact-merge.js.map +1 -0
- package/src/lib/model/contact-record.d.ts +7 -1
- package/src/lib/model/contact-record.js +7 -6
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/contact-remove.d.ts +48 -0
- package/src/lib/model/contact-remove.js +22 -0
- package/src/lib/model/contact-remove.js.map +1 -0
- package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
- package/src/lib/model/contacts-bulk-writes.js +2 -82
- package/src/lib/model/contacts-bulk-writes.js.map +1 -1
- package/src/lib/model/contacts-csv.d.ts +2 -2
- package/src/lib/model/contacts-csv.js +2 -2
- package/src/lib/model/contacts-csv.js.map +1 -1
- package/src/lib/model/crm-csv.d.ts +239 -0
- package/src/lib/model/crm-csv.js +467 -0
- package/src/lib/model/crm-csv.js.map +1 -0
- package/src/lib/model/crm-digest.d.ts +227 -0
- package/src/lib/model/crm-digest.js +336 -0
- package/src/lib/model/crm-digest.js.map +1 -0
- package/src/lib/model/crm-import.d.ts +7 -1
- package/src/lib/model/crm-import.js +2 -1
- package/src/lib/model/crm-import.js.map +1 -1
- package/src/lib/model/crm-inbound.d.ts +162 -0
- package/src/lib/model/crm-inbound.js +208 -0
- package/src/lib/model/crm-inbound.js.map +1 -0
- package/src/lib/model/crm-lead-import.d.ts +1 -1
- package/src/lib/model/crm-lead-import.js +4 -4
- package/src/lib/model/crm-lead-import.js.map +1 -1
- package/src/lib/model/crm-record-routes.d.ts +5 -0
- package/src/lib/model/crm-record-routes.js +7 -0
- package/src/lib/model/crm-record-routes.js.map +1 -1
- package/src/lib/model/crm-reports.d.ts +467 -0
- package/src/lib/model/crm-reports.js +667 -0
- package/src/lib/model/crm-reports.js.map +1 -0
- package/src/lib/model/crm-routes.d.ts +6 -5
- package/src/lib/model/crm-routes.js +7 -7
- package/src/lib/model/crm-routes.js.map +1 -1
- package/src/lib/model/crm-search-sources.d.ts +15 -0
- package/src/lib/model/crm-search-sources.js +162 -0
- package/src/lib/model/crm-search-sources.js.map +1 -0
- package/src/lib/model/crm-sharing-api.d.ts +32 -0
- package/src/lib/model/crm-sharing-api.js +59 -0
- package/src/lib/model/crm-sharing-api.js.map +1 -0
- package/src/lib/model/crm-sharing.d.ts +333 -0
- package/src/lib/model/crm-sharing.js +603 -0
- package/src/lib/model/crm-sharing.js.map +1 -0
- package/src/lib/model/deals-csv.d.ts +1 -1
- package/src/lib/model/deals-csv.js +4 -5
- package/src/lib/model/deals-csv.js.map +1 -1
- package/src/lib/model/lead-filters.d.ts +5 -4
- package/src/lib/model/lead-filters.js +7 -5
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/model/leads-csv.d.ts +1 -1
- package/src/lib/model/leads-csv.js +1 -1
- package/src/lib/model/leads-csv.js.map +1 -1
- package/src/lib/model/tasks-csv.d.ts +1 -1
- package/src/lib/model/tasks-csv.js +1 -1
- package/src/lib/model/tasks-csv.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +75 -0
- package/src/lib/plan-entitlements.js +56 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +39 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
- package/src/lib/server/api-v1/contacts-merge.js +120 -0
- package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
- package/src/lib/server/api-v1/contacts.d.ts +18 -0
- package/src/lib/server/api-v1/contacts.js +1076 -0
- package/src/lib/server/api-v1/contacts.js.map +1 -0
- package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
- package/src/lib/server/api-v1/crm-activities.js +269 -0
- package/src/lib/server/api-v1/crm-activities.js.map +1 -0
- package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
- package/src/lib/server/api-v1/crm-companies.js +391 -0
- package/src/lib/server/api-v1/crm-companies.js.map +1 -0
- package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
- package/src/lib/server/api-v1/crm-deals.js +631 -0
- package/src/lib/server/api-v1/crm-deals.js.map +1 -0
- package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
- package/src/lib/server/api-v1/crm-email-templates.js +336 -0
- package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
- package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
- package/src/lib/server/api-v1/crm-leads.js +833 -0
- package/src/lib/server/api-v1/crm-leads.js.map +1 -0
- package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
- package/src/lib/server/api-v1/crm-pipelines.js +146 -0
- package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
- package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
- package/src/lib/server/api-v1/crm-shared.js +436 -0
- package/src/lib/server/api-v1/crm-shared.js.map +1 -0
- package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
- package/src/lib/server/api-v1/crm-tasks.js +398 -0
- package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
- package/src/lib/server/api-v1/openapi.d.ts +29 -0
- package/src/lib/server/api-v1/openapi.js +985 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -0
- package/src/lib/server/api-v1/usage.d.ts +26 -0
- package/src/lib/server/api-v1/usage.js +73 -0
- package/src/lib/server/api-v1/usage.js.map +1 -0
- package/src/lib/server/campaign-filing-activity.d.ts +1 -1
- package/src/lib/server/campaign-filing-activity.js +7 -11
- package/src/lib/server/campaign-filing-activity.js.map +1 -1
- package/src/lib/server/capture-contact.js +15 -4
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/consent-group-participant.js +1 -1
- package/src/lib/server/consent-group-participant.js.map +1 -1
- package/src/lib/server/contact-email-engagement.d.ts +33 -0
- package/src/lib/server/contact-email-engagement.js +146 -0
- package/src/lib/server/contact-email-engagement.js.map +1 -0
- package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
- package/src/lib/server/contact-lifecycle-floor.js +91 -0
- package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
- package/src/lib/server/contact-remove.d.ts +75 -0
- package/src/lib/server/contact-remove.js +244 -0
- package/src/lib/server/contact-remove.js.map +1 -0
- package/src/lib/server/contact-update.js +4 -3
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/contacts-import.d.ts +47 -11
- package/src/lib/server/contacts-import.js +171 -71
- package/src/lib/server/contacts-import.js.map +1 -1
- package/src/lib/server/contacts-merge.d.ts +1 -1
- package/src/lib/server/contacts-merge.js +3 -2
- package/src/lib/server/contacts-merge.js.map +1 -1
- package/src/lib/server/container-detach.d.ts +31 -0
- package/src/lib/server/container-detach.js +115 -0
- package/src/lib/server/container-detach.js.map +1 -0
- package/src/lib/server/convert-host-lead.d.ts +189 -0
- package/src/lib/server/convert-host-lead.js +518 -0
- package/src/lib/server/convert-host-lead.js.map +1 -0
- package/src/lib/server/crm-inbound-email.d.ts +162 -0
- package/src/lib/server/crm-inbound-email.js +259 -0
- package/src/lib/server/crm-inbound-email.js.map +1 -0
- package/src/lib/server/crm-records-meter.d.ts +50 -0
- package/src/lib/server/crm-records-meter.js +126 -0
- package/src/lib/server/crm-records-meter.js.map +1 -0
- package/src/lib/server/crm-sharing.d.ts +108 -0
- package/src/lib/server/crm-sharing.js +790 -0
- package/src/lib/server/crm-sharing.js.map +1 -0
- package/src/lib/server/daily-digest-route.d.ts +101 -0
- package/src/lib/server/daily-digest-route.js +578 -0
- package/src/lib/server/daily-digest-route.js.map +1 -0
- package/src/lib/server/export-route.d.ts +60 -0
- package/src/lib/server/export-route.js +447 -0
- package/src/lib/server/export-route.js.map +1 -0
- package/src/lib/server/inbound-address.js +5 -3
- package/src/lib/server/inbound-address.js.map +1 -1
- package/src/lib/server/inbound-route.d.ts +74 -0
- package/src/lib/server/inbound-route.js +260 -0
- package/src/lib/server/inbound-route.js.map +1 -0
- package/src/lib/server/lead-campaign-carry.js +3 -3
- package/src/lib/server/lead-campaign-carry.js.map +1 -1
- package/src/lib/server/lead-convert.d.ts +1 -1
- package/src/lib/server/lead-convert.js +2 -2
- package/src/lib/server/lead-convert.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +1 -1
- package/src/lib/server/lead-create.js +10 -15
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +23 -0
- package/src/lib/server/lead-nurturing.js +82 -0
- package/src/lib/server/lead-nurturing.js.map +1 -0
- package/src/lib/server/leads-import.js +7 -7
- package/src/lib/server/leads-import.js.map +1 -1
- package/src/lib/server/merge-contacts.d.ts +55 -0
- package/src/lib/server/merge-contacts.js +274 -0
- package/src/lib/server/merge-contacts.js.map +1 -0
- package/src/lib/server/pipeline-record-index.d.ts +36 -0
- package/src/lib/server/pipeline-record-index.js +87 -0
- package/src/lib/server/pipeline-record-index.js.map +1 -0
- package/src/lib/server/record-email-state.js +23 -0
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-facts.js +2 -1
- package/src/lib/server/record-facts.js.map +1 -1
- package/src/lib/server/record-timeline.d.ts +12 -0
- package/src/lib/server/record-timeline.js +127 -15
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/task-reminders-route.d.ts +82 -0
- package/src/lib/server/task-reminders-route.js +499 -0
- package/src/lib/server/task-reminders-route.js.map +1 -0
- package/src/lib/server/task-routes.d.ts +2 -0
- package/src/lib/server/task-routes.js +22 -3
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/server-deal-stage.js +2 -1
- package/src/lib/server-deal-stage.js.map +1 -1
- package/src/lib/server.d.ts +5 -2
- package/src/lib/server.js +47 -2
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
- package/src/lib/testing/stand-in-site-record-routes.js +55 -0
- package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
- package/src/lib/usage-axes.d.ts +35 -0
- package/src/lib/usage-axes.js +75 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +0 -24
- package/src/lib/components/use-emails-hub-path.js +0 -59
- package/src/lib/components/use-emails-hub-path.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/new-lead-drawer.tsx"],"sourcesContent":["'use client'\n\nimport {\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_TEXT_MAX,\n type CrmCustomValue,\n type CrmLeadStatus,\n campaignMembershipValue,\n crmPicklistDefaultLabel,\n normalizeCrmLeadTags,\n normalizeContactEmail,\n normalizeCrmLeadProfile,\n type AglynPostalAddress,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport CampaignPicker from '@aglyn/shared-ui-email-campaigns/components/campaign-picker.component'\nimport { useEffect, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n ContactAddressFields,\n EMPTY_ADDRESS,\n type AddressDraft,\n} from './contact-address-fields'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n} from '../model/crm-custom-draft'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { LeadSourceSelect } from './lead-source-select'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\n\n/** What the drawer hands back — already normalized where the route would. */\nexport interface NewLeadValues {\n email: string\n name: string\n company: string\n jobTitle: string\n phone: string\n website: string\n leadSource: string\n status: Extract<CrmLeadStatus, 'new' | 'working'>\n ownerUid: string\n tags: string[]\n /** The site's campaigns to file the lead under (AGL-3254), by id. */\n campaignIds: string[]\n address: AglynPostalAddress | null\n notes: string\n /**\n * The org's custom lead fields (AGL-3272), keyed by each definition's\n * `key`. Absent when the org has defined none, or none was filled — the\n * route stores a `custom` map only when there is one.\n */\n custom?: Record<string, CrmCustomValue>\n}\n\nexport interface NewLeadDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the lead is filed under — a lead is private to one site by\n * path. `null` at the organization level, where the drawer asks which\n * site with a picker and holds its submit until one is named.\n */\n hostId: string | null\n org?: Record<string, unknown> | null\n /** The request is in flight — the form holds still and the button says so. */\n busy?: boolean\n /** What the route answered when it refused, shown above the form. */\n error?: string | null\n /** The team, for the owner picker. */\n roster: OrgMemberOptions\n /**\n * The org the custom lead fields belong to (AGL-3272), as the list\n * already resolved it. `null` while it is in flight — the drawer then\n * shows the standard fields and no custom ones, which is what an org\n * with none defined shows anyway.\n */\n orgId?: string | null\n onSubmit: (values: NewLeadValues) => void\n}\n\nconst NOTES_MAX = 4000\n\n/**\n * ADDING ONE LEAD BY HAND, IN A DRAWER (AGL-3231).\n *\n * Salesforce's New Lead, on the Leads list: a person the team has heard of\n * and not yet qualified, entered with what is known — the address, the\n * name, the company as text, a title, a phone, a website, an address, tags\n * and where they came from. It makes a lead and nothing else: no contact,\n * no company record. Those are the conversion's to create once the lead is\n * real, which is what keeps one person from sitting in two lists.\n *\n * A drawer and not a form above the list, for the reason every list in this\n * console gives. The two fields a person can mistype in a way the record\n * cannot hold — the email and the phone, and the website beside them — are\n * checked before the request leaves, with the same normalizer the route\n * runs, so the refusal lands under the field. Everything else is the\n * route's to decide: whether the site already holds the address (it\n * updates that lead), whether the plan carries the suite, whether the site\n * is at the platform ceiling.\n */\nexport function NewLeadDrawer(props: NewLeadDrawerProps) {\n const { open, onClose, hostId, org, busy, error, roster, orgId, onSubmit } = props\n // The site the route files the lead under: the mounted site, or at the\n // organization level the picked one. `null` until it is known.\n const { createHostId } = useCrmScope({ hostId, org: org as never })\n\n const [email, setEmail] = useState('')\n const [name, setName] = useState('')\n const [company, setCompany] = useState('')\n const [jobTitle, setJobTitle] = useState('')\n const [phone, setPhone] = useState('')\n const [website, setWebsite] = useState('')\n const [leadSource, setLeadSource] = useState('')\n /*\n * THE ORG'S LEAD SOURCES (AGL-3298), and its default for a new record.\n * The default is filled in once the list has answered and only until the\n * person picks something themselves — \"None\" included — so a list that\n * arrives after the drawer opened does not overwrite a choice.\n */\n const leadSources = useLeadSourcePicklist(orgId ?? null)\n const [leadSourceTouched, setLeadSourceTouched] = useState(false)\n const defaultLeadSource = crmPicklistDefaultLabel(leadSources.picklist) ?? ''\n useEffect(() => {\n if (open && !leadSourceTouched) setLeadSource(defaultLeadSource)\n }, [open, leadSourceTouched, defaultLeadSource])\n const [status, setStatus] = useState<NewLeadValues['status']>('new')\n const [ownerUid, setOwnerUid] = useState('')\n const [tags, setTags] = useState('')\n const [campaignIds, setCampaignIds] = useState<string[]>([])\n const [address, setAddress] = useState<AddressDraft>(EMPTY_ADDRESS)\n const [notes, setNotes] = useState('')\n /*\n * The org's own lead fields (AGL-3272). A CREATE holds only a draft —\n * there is no stored map to diff against — and `crmCustomDraftDocument`\n * turns it into the `custom` the route stores, or nothing.\n */\n const fields = useContactFieldDefinitions(orgId ?? null, 'lead')\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n const [customError, setCustomError] = useState('')\n const [emailError, setEmailError] = useState('')\n const [phoneError, setPhoneError] = useState('')\n const [websiteError, setWebsiteError] = useState('')\n /*\n * The campaigns, for the picker (AGL-3254): read while the drawer is\n * open. Under a site, the ones placed on it; at the organization level,\n * every campaign in the org — the lead is the org's record whichever\n * site it is captured by, so the site picked above does not narrow them.\n */\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: open })\n\n // A fresh form on every opening: a person typed and then abandoned must\n // not reappear half-filled the next time somebody reaches for New lead.\n useEffect(() => {\n if (!open) return\n setEmail('')\n setName('')\n setCompany('')\n setJobTitle('')\n setPhone('')\n setWebsite('')\n setLeadSource('')\n setLeadSourceTouched(false)\n setStatus('new')\n setOwnerUid('')\n setTags('')\n setCampaignIds([])\n setAddress(EMPTY_ADDRESS)\n setNotes('')\n setCustom({})\n setCustomError('')\n setEmailError('')\n setPhoneError('')\n setWebsiteError('')\n }, [open])\n\n const handleSubmit = () => {\n const normalizedEmail = normalizeContactEmail(email)\n const { patch, errors } = normalizeCrmLeadProfile({\n company,\n jobTitle,\n phone,\n website,\n leadSource,\n address,\n tags: normalizeCrmLeadTags(tags),\n })\n // Every required lead field counts on a create: the record is being\n // made whole, and nothing has been written to come back and fill.\n const missing = crmCustomDraftMissingRequired(fields.active, {}, custom, 'create')\n setEmailError(normalizedEmail ? '' : 'Enter a valid email address.')\n setPhoneError(errors.phone ?? '')\n setWebsiteError(errors.website ?? '')\n setCustomError(\n missing.length\n ? `${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`\n : '',\n )\n if (!normalizedEmail || errors.phone || errors.website || missing.length) return\n onSubmit({\n email: normalizedEmail,\n name: name.trim().replace(/\\s+/g, ' ').slice(0, CRM_LEAD_TEXT_MAX),\n company: patch.company ?? '',\n jobTitle: patch.jobTitle ?? '',\n phone: patch.phone ?? '',\n website: patch.website ?? '',\n leadSource: patch.leadSource ?? '',\n status,\n ownerUid,\n tags: patch.tags ?? [],\n campaignIds: campaignMembershipValue(campaignIds),\n address: patch.address ?? null,\n notes: notes.trim().slice(0, NOTES_MAX),\n custom: crmCustomDraftDocument(custom),\n })\n }\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n sx={{ '& .MuiDrawer-paper': { width: { xs: '100%', sm: 480 } } }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>close drawer</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {'New lead'}\n </Typography>\n </>\n }\n appBarRight={\n <Button variant=\"outlined\" color=\"inherit\" onClick={onClose}>\n {'Cancel'}\n </Button>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n {/* First, because the lead is filed under the answer. Renders nothing under a site. */}\n <CrmSitePicker\n hostId={hostId}\n disabled={Boolean(busy)}\n helperText=\"The site this lead is filed under — a lead is private to one site.\"\n />\n <TextField\n size=\"small\"\n label=\"Email\"\n type=\"email\"\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n error={Boolean(emailError)}\n helperText={\n emailError ||\n 'The one field that is required — a site holds one lead per address.'\n }\n required\n fullWidth\n autoFocus\n />\n <TextField\n size=\"small\"\n label=\"Name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Company\"\n value={company}\n onChange={(event) => setCompany(event.target.value)}\n helperText=\"As text — converting the lead is what makes it a company record.\"\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Job title\"\n value={jobTitle}\n onChange={(event) => setJobTitle(event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n value={phone}\n onChange={(event) => setPhone(event.target.value)}\n error={Boolean(phoneError)}\n helperText={\n phoneError || 'With the country code, like +1 512 555 0107'\n }\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n value={website}\n onChange={(event) => setWebsite(event.target.value)}\n error={Boolean(websiteError)}\n helperText={websiteError || 'Like acme.com'}\n fullWidth\n />\n <LeadSourceSelect\n picklist={leadSources.picklist}\n value={leadSource}\n onChange={(label) => {\n setLeadSource(label)\n setLeadSourceTouched(true)\n }}\n disabled={Boolean(busy)}\n helperText=\"Where this lead came from. The choices are kept under CRM › Fields › Leads.\"\n />\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <TextField\n select\n size=\"small\"\n label=\"Status\"\n value={status}\n onChange={(event) =>\n setStatus(event.target.value as NewLeadValues['status'])\n }\n fullWidth\n >\n <MenuItem value=\"new\">{CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">\n {CRM_LEAD_STATUS_LABELS.working}\n </MenuItem>\n </TextField>\n <LeadOwnerSelect\n value={ownerUid}\n onChange={setOwnerUid}\n roster={roster}\n />\n </Stack>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"icp2, a-list\"\n helperText=\"Comma-separated\"\n value={tags}\n onChange={(event) => setTags(event.target.value)}\n fullWidth\n />\n {/*\n The campaigns to file the lead under (AGL-3254), picked the way a\n form's page picks them. Grouping, not consent: it decides which\n campaign pages list the lead, never whether anything mails them.\n */}\n <CampaignPicker\n options={campaigns.options}\n value={campaignIds}\n onChange={setCampaignIds}\n helperText=\"The campaigns this lead is part of. It does not decide who a campaign mails.\"\n disabled={Boolean(busy)}\n empty={campaigns.ready && !campaigns.options.length}\n emptyText={\n hostId\n ? 'This site has no campaigns yet. Create one from Marketing to file leads under it.'\n : 'There are no campaigns yet. Create one from Marketing to file leads under it.'\n }\n />\n <Typography variant=\"subtitle2\">{'Address'}</Typography>\n <ContactAddressFields value={address} onChange={setAddress} />\n {/* The org's own lead fields (AGL-3272), the same controls the\n lead's page edits them with. */}\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {customError ? <Alert severity=\"warning\">{customError}</Alert> : null}\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={custom[definition.key]}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={Boolean(busy)}\n />\n ))}\n </>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={notes}\n onChange={(event) => setNotes(event.target.value)}\n multiline\n minRows={2}\n slotProps={{ htmlInput: { maxLength: NOTES_MAX } }}\n fullWidth\n />\n <Button\n variant=\"contained\"\n color=\"primary\"\n // Held until the site is known: the route files the lead under it.\n disabled={Boolean(busy) || !createHostId}\n onClick={handleSubmit}\n >\n {busy ? 'Adding…' : 'Add lead'}\n </Button>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\n\nexport default NewLeadDrawer\n"],"names":["CRM_LEAD_STATUS_LABELS","CRM_LEAD_TEXT_MAX","campaignMembershipValue","crmPicklistDefaultLabel","normalizeCrmLeadTags","normalizeContactEmail","normalizeCrmLeadProfile","ICON_VARIANT_CLOSE","Container","MdiIcon","SrOnly","NavigationDrawerComponent","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","CampaignPicker","useEffect","useState","useContactFieldDefinitions","useCrmCampaigns","useCrmScope","ContactAddressFields","EMPTY_ADDRESS","CrmSitePicker","CrmCustomFieldControl","crmCustomDraftDocument","crmCustomDraftMissingRequired","LeadOwnerSelect","LeadSourceSelect","useLeadSourcePicklist","NOTES_MAX","NewLeadDrawer","props","open","onClose","hostId","org","busy","error","roster","orgId","onSubmit","createHostId","email","setEmail","name","setName","company","setCompany","jobTitle","setJobTitle","phone","setPhone","website","setWebsite","leadSource","setLeadSource","leadSources","leadSourceTouched","setLeadSourceTouched","defaultLeadSource","picklist","status","setStatus","ownerUid","setOwnerUid","tags","setTags","campaignIds","setCampaignIds","address","setAddress","notes","setNotes","fields","custom","setCustom","customError","setCustomError","emailError","setEmailError","phoneError","setPhoneError","websiteError","setWebsiteError","campaigns","enabled","handleSubmit","errors","patch","normalizedEmail","missing","active","length","join","trim","replace","slice","anchor","variant","AppBarProps","color","sx","width","xs","sm","appBarLeft","edge","onClick","mr","path","component","appBarRight","gutterY","spacing","severity","disabled","Boolean","helperText","size","label","type","value","onChange","event","target","required","fullWidth","autoFocus","slotProps","htmlInput","maxLength","direction","select","new","working","placeholder","options","empty","ready","emptyText","map","definition","key","current","$id","multiline","minRows"],"mappings":"AAAA;;;AAEA,SACEA,sBAAsB,EACtBC,iBAAiB,EAGjBC,uBAAuB,EACvBC,uBAAuB,EACvBC,oBAAoB,EACpBC,qBAAqB,EACrBC,uBAAuB,QAElB,eAAc;AACrB,SAASC,kBAAkB,QAAQ,2BAA0B;AAC7D,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,QAAQ,uBAAsB;AACjE,SAASC,yBAAyB,QAAQ,8DAA6D;AACvG,SACEC,KAAK,EACLC,MAAM,EACNC,UAAU,EACVC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,OAAOC,oBAAoB,wEAAuE;AAClG,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAC3C,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,SAASC,eAAe,QAAQ,gCAA4B;AAC5D,SAASC,WAAW,QAAQ,4BAAwB;AAEpD,SACEC,oBAAoB,EACpBC,aAAa,QAER,8BAA0B;AACjC,SAASC,aAAa,QAAQ,uBAAmB;AACjD,SAASC,qBAAqB,QAAQ,gCAA4B;AAClE,SAEEC,sBAAsB,EACtBC,6BAA6B,QACxB,+BAA2B;AAClC,SAASC,eAAe,QAAQ,yBAAqB;AACrD,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,SAASC,qBAAqB,QAAQ,uCAAmC;AAoDzE,MAAMC,YAAY;AAElB;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,cAAcC,KAAyB;QAqB3BjC;IApB1B,MAAM,EAAEkC,IAAI,EAAEC,OAAO,EAAEC,MAAM,EAAEC,GAAG,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGT;IAC7E,uEAAuE;IACvE,+DAA+D;IAC/D,MAAM,EAAEU,YAAY,EAAE,GAAGtB,YAAY;QAAEe;QAAQC,KAAKA;IAAa;IAEjE,MAAM,CAACO,OAAOC,SAAS,GAAG3B,SAAS;IACnC,MAAM,CAAC4B,MAAMC,QAAQ,GAAG7B,SAAS;IACjC,MAAM,CAAC8B,SAASC,WAAW,GAAG/B,SAAS;IACvC,MAAM,CAACgC,UAAUC,YAAY,GAAGjC,SAAS;IACzC,MAAM,CAACkC,OAAOC,SAAS,GAAGnC,SAAS;IACnC,MAAM,CAACoC,SAASC,WAAW,GAAGrC,SAAS;IACvC,MAAM,CAACsC,YAAYC,cAAc,GAAGvC,SAAS;IAC7C;;;;;GAKC,GACD,MAAMwC,cAAc5B,sBAAsBW,gBAAAA,QAAS;IACnD,MAAM,CAACkB,mBAAmBC,qBAAqB,GAAG1C,SAAS;IAC3D,MAAM2C,qBAAoB7D,2BAAAA,wBAAwB0D,YAAYI,QAAQ,aAA5C9D,2BAAiD;IAC3EiB,UAAU;QACR,IAAIiB,QAAQ,CAACyB,mBAAmBF,cAAcI;IAChD,GAAG;QAAC3B;QAAMyB;QAAmBE;KAAkB;IAC/C,MAAM,CAACE,QAAQC,UAAU,GAAG9C,SAAkC;IAC9D,MAAM,CAAC+C,UAAUC,YAAY,GAAGhD,SAAS;IACzC,MAAM,CAACiD,MAAMC,QAAQ,GAAGlD,SAAS;IACjC,MAAM,CAACmD,aAAaC,eAAe,GAAGpD,SAAmB,EAAE;IAC3D,MAAM,CAACqD,SAASC,WAAW,GAAGtD,SAAuBK;IACrD,MAAM,CAACkD,OAAOC,SAAS,GAAGxD,SAAS;IACnC;;;;GAIC,GACD,MAAMyD,SAASxD,2BAA2BsB,gBAAAA,QAAS,MAAM;IACzD,MAAM,CAACmC,QAAQC,UAAU,GAAG3D,SAAyB,CAAC;IACtD,MAAM,CAAC4D,aAAaC,eAAe,GAAG7D,SAAS;IAC/C,MAAM,CAAC8D,YAAYC,cAAc,GAAG/D,SAAS;IAC7C,MAAM,CAACgE,YAAYC,cAAc,GAAGjE,SAAS;IAC7C,MAAM,CAACkE,cAAcC,gBAAgB,GAAGnE,SAAS;IACjD;;;;;GAKC,GACD,MAAMoE,YAAYlE,gBAAgB;QAAEgB;QAAQK;IAAM,GAAG;QAAE8C,SAASrD;IAAK;IAErE,wEAAwE;IACxE,wEAAwE;IACxEjB,UAAU;QACR,IAAI,CAACiB,MAAM;QACXW,SAAS;QACTE,QAAQ;QACRE,WAAW;QACXE,YAAY;QACZE,SAAS;QACTE,WAAW;QACXE,cAAc;QACdG,qBAAqB;QACrBI,UAAU;QACVE,YAAY;QACZE,QAAQ;QACRE,eAAe,EAAE;QACjBE,WAAWjD;QACXmD,SAAS;QACTG,UAAU,CAAC;QACXE,eAAe;QACfE,cAAc;QACdE,cAAc;QACdE,gBAAgB;IAClB,GAAG;QAACnD;KAAK;IAET,MAAMsD,eAAe;YAeLC,eACEA,iBAULC,gBACCA,iBACHA,cACEA,gBACGA,mBAGNA,aAEGA;QAlCX,MAAMC,kBAAkBzF,sBAAsB0C;QAC9C,MAAM,EAAE8C,KAAK,EAAED,MAAM,EAAE,GAAGtF,wBAAwB;YAChD6C;YACAE;YACAE;YACAE;YACAE;YACAe;YACAJ,MAAMlE,qBAAqBkE;QAC7B;QACA,oEAAoE;QACpE,kEAAkE;QAClE,MAAMyB,UAAUjE,8BAA8BgD,OAAOkB,MAAM,EAAE,CAAC,GAAGjB,QAAQ;QACzEK,cAAcU,kBAAkB,KAAK;QACrCR,eAAcM,gBAAAA,OAAOrC,KAAK,YAAZqC,gBAAgB;QAC9BJ,iBAAgBI,kBAAAA,OAAOnC,OAAO,YAAdmC,kBAAkB;QAClCV,eACEa,QAAQE,MAAM,GACV,GAAGF,QAAQG,IAAI,CAAC,MAAM,CAAC,EAAEH,QAAQE,MAAM,GAAG,IAAI,QAAQ,KAAK,UAAU,CAAC,GACtE;QAEN,IAAI,CAACH,mBAAmBF,OAAOrC,KAAK,IAAIqC,OAAOnC,OAAO,IAAIsC,QAAQE,MAAM,EAAE;QAC1EpD,SAAS;YACPE,OAAO+C;YACP7C,MAAMA,KAAKkD,IAAI,GAAGC,OAAO,CAAC,QAAQ,KAAKC,KAAK,CAAC,GAAGpG;YAChDkD,OAAO,GAAE0C,iBAAAA,MAAM1C,OAAO,YAAb0C,iBAAiB;YAC1BxC,QAAQ,GAAEwC,kBAAAA,MAAMxC,QAAQ,YAAdwC,kBAAkB;YAC5BtC,KAAK,GAAEsC,eAAAA,MAAMtC,KAAK,YAAXsC,eAAe;YACtBpC,OAAO,GAAEoC,iBAAAA,MAAMpC,OAAO,YAAboC,iBAAiB;YAC1BlC,UAAU,GAAEkC,oBAAAA,MAAMlC,UAAU,YAAhBkC,oBAAoB;YAChC3B;YACAE;YACAE,IAAI,GAAEuB,cAAAA,MAAMvB,IAAI,YAAVuB,cAAc,EAAE;YACtBrB,aAAatE,wBAAwBsE;YACrCE,OAAO,GAAEmB,iBAAAA,MAAMnB,OAAO,YAAbmB,iBAAiB;YAC1BjB,OAAOA,MAAMuB,IAAI,GAAGE,KAAK,CAAC,GAAGnE;YAC7B6C,QAAQlD,uBAAuBkD;QACjC;IACF;IAEA,qBACE,KAACpE;QACC0B,MAAMA;QACNiE,QAAO;QACPC,SAAQ;QACRjE,SAASA;QACTkE,aAAa;YAAEC,OAAO;QAAU;QAChCC,IAAI;YAAE,sBAAsB;gBAAEC,OAAO;oBAAEC,IAAI;oBAAQC,IAAI;gBAAI;YAAE;QAAE;QAC/DC,0BACE;;8BACE,MAAChG;oBACC2F,OAAM;oBACNM,MAAK;oBACLC,SAAS1E;oBACToE,IAAI;wBAAEO,IAAI;oBAAE;;sCAEZ,KAACxG;4BAAQyG,MAAM3G,mBAAmB2G,IAAI;;sCACtC,KAACxG;sCAAO;;;;8BAEV,KAACQ;oBAAWqF,SAAQ;oBAAKY,WAAU;8BAChC;;;;QAIPC,2BACE,KAACvG;YAAO0F,SAAQ;YAAWE,OAAM;YAAUO,SAAS1E;sBACjD;;kBAIL,cAAA,KAAC9B;YAAU6G,OAAO;sBAChB,cAAA,MAACrG;gBAAMsG,SAAS;;oBACb5E,sBAAQ,KAAC9B;wBAAM2G,UAAS;kCAAW7E;yBAAiB;kCAErD,KAACf;wBACCY,QAAQA;wBACRiF,UAAUC,QAAQhF;wBAClBiF,YAAW;;kCAEb,KAACzG;wBACC0G,MAAK;wBACLC,OAAM;wBACNC,MAAK;wBACLC,OAAO/E;wBACPgF,UAAU,CAACC,QAAUhF,SAASgF,MAAMC,MAAM,CAACH,KAAK;wBAChDpF,OAAO+E,QAAQtC;wBACfuC,YACEvC,cACA;wBAEF+C,QAAQ;wBACRC,SAAS;wBACTC,SAAS;;kCAEX,KAACnH;wBACC0G,MAAK;wBACLC,OAAM;wBACNE,OAAO7E;wBACP8E,UAAU,CAACC,QAAU9E,QAAQ8E,MAAMC,MAAM,CAACH,KAAK;wBAC/CO,WAAW;4BAAEC,WAAW;gCAAEC,WAAWtI;4BAAkB;wBAAE;wBACzDkI,SAAS;;kCAEX,KAAClH;wBACC0G,MAAK;wBACLC,OAAM;wBACNE,OAAO3E;wBACP4E,UAAU,CAACC,QAAU5E,WAAW4E,MAAMC,MAAM,CAACH,KAAK;wBAClDJ,YAAW;wBACXW,WAAW;4BAAEC,WAAW;gCAAEC,WAAWtI;4BAAkB;wBAAE;wBACzDkI,SAAS;;kCAEX,KAAClH;wBACC0G,MAAK;wBACLC,OAAM;wBACNE,OAAOzE;wBACP0E,UAAU,CAACC,QAAU1E,YAAY0E,MAAMC,MAAM,CAACH,KAAK;wBACnDO,WAAW;4BAAEC,WAAW;gCAAEC,WAAWtI;4BAAkB;wBAAE;wBACzDkI,SAAS;;kCAEX,KAAClH;wBACC0G,MAAK;wBACLC,OAAM;wBACNE,OAAOvE;wBACPwE,UAAU,CAACC,QAAUxE,SAASwE,MAAMC,MAAM,CAACH,KAAK;wBAChDpF,OAAO+E,QAAQpC;wBACfqC,YACErC,cAAc;wBAEhB8C,SAAS;;kCAEX,KAAClH;wBACC0G,MAAK;wBACLC,OAAM;wBACNE,OAAOrE;wBACPsE,UAAU,CAACC,QAAUtE,WAAWsE,MAAMC,MAAM,CAACH,KAAK;wBAClDpF,OAAO+E,QAAQlC;wBACfmC,YAAYnC,gBAAgB;wBAC5B4C,SAAS;;kCAEX,KAACnG;wBACCiC,UAAUJ,YAAYI,QAAQ;wBAC9B6D,OAAOnE;wBACPoE,UAAU,CAACH;4BACThE,cAAcgE;4BACd7D,qBAAqB;wBACvB;wBACAyD,UAAUC,QAAQhF;wBAClBiF,YAAW;;kCAEb,MAAC1G;wBAAMwH,WAAW;4BAAE5B,IAAI;4BAAUC,IAAI;wBAAM;wBAAGS,SAAS;;0CACtD,MAACrG;gCACCwH,MAAM;gCACNd,MAAK;gCACLC,OAAM;gCACNE,OAAO5D;gCACP6D,UAAU,CAACC,QACT7D,UAAU6D,MAAMC,MAAM,CAACH,KAAK;gCAE9BK,SAAS;;kDAET,KAACpH;wCAAS+G,OAAM;kDAAO9H,uBAAuB0I,GAAG;;kDACjD,KAAC3H;wCAAS+G,OAAM;kDACb9H,uBAAuB2I,OAAO;;;;0CAGnC,KAAC5G;gCACC+F,OAAO1D;gCACP2D,UAAU1D;gCACV1B,QAAQA;;;;kCAGZ,KAAC1B;wBACC0G,MAAK;wBACLC,OAAM;wBACNgB,aAAY;wBACZlB,YAAW;wBACXI,OAAOxD;wBACPyD,UAAU,CAACC,QAAUzD,QAAQyD,MAAMC,MAAM,CAACH,KAAK;wBAC/CK,SAAS;;kCAOX,KAAChH;wBACC0H,SAASpD,UAAUoD,OAAO;wBAC1Bf,OAAOtD;wBACPuD,UAAUtD;wBACViD,YAAW;wBACXF,UAAUC,QAAQhF;wBAClBqG,OAAOrD,UAAUsD,KAAK,IAAI,CAACtD,UAAUoD,OAAO,CAAC5C,MAAM;wBACnD+C,WACEzG,SACI,sFACA;;kCAGR,KAACrB;wBAAWqF,SAAQ;kCAAa;;kCACjC,KAAC9E;wBAAqBqG,OAAOpD;wBAASqD,UAAUpD;;oBAG/CG,OAAOkB,MAAM,CAACC,MAAM,iBACnB;;0CACE,KAAC/E;gCAAWqF,SAAQ;0CAAa;;4BAChCtB,4BAAc,KAACrE;gCAAM2G,UAAS;0CAAWtC;iCAAuB;4BAChEH,OAAOkB,MAAM,CAACiD,GAAG,CAAC,CAACC,2BAClB,KAACtH;oCAECsH,YAAYA;oCACZpB,OAAO/C,MAAM,CAACmE,WAAWC,GAAG,CAAC;oCAC7BpB,UAAU,CAACD,QACT9C,UAAU,CAACoE,UAAa,aAAKA;gDAAS,CAACF,WAAWC,GAAG,CAAC,EAAErB;;oCAE1DN,UAAUC,QAAQhF;mCANbyG,WAAWG,GAAG;;yBAUvB;kCACJ,KAACpI;wBACC0G,MAAK;wBACLC,OAAM;wBACNE,OAAOlD;wBACPmD,UAAU,CAACC,QAAUnD,SAASmD,MAAMC,MAAM,CAACH,KAAK;wBAChDwB,SAAS;wBACTC,SAAS;wBACTlB,WAAW;4BAAEC,WAAW;gCAAEC,WAAWrG;4BAAU;wBAAE;wBACjDiG,SAAS;;kCAEX,KAACtH;wBACC0F,SAAQ;wBACRE,OAAM;wBACN,mEAAmE;wBACnEe,UAAUC,QAAQhF,SAAS,CAACK;wBAC5BkE,SAASrB;kCAERlD,OAAO,YAAY;;;;;;AAMhC;AAEA,eAAeN,cAAa"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/new-lead-drawer.tsx"],"sourcesContent":["'use client'\n\nimport {\n CRM_LEAD_STATUS_LABELS,\n CRM_LEAD_TEXT_MAX,\n type CrmCustomValue,\n type CrmLeadStatus,\n containerMembershipValue,\n crmPicklistDefaultLabel,\n normalizeCrmLeadTags,\n normalizeContactEmail,\n normalizeCrmLeadProfile,\n type AglynPostalAddress,\n} from '@aglyn/aglyn'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport { Container, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { NavigationDrawerComponent } from '@aglyn/shared-ui-jsx/components/navigation-drawer.component'\nimport {\n Alert,\n Button,\n IconButton,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport ContainerPicker from '@aglyn/tenant-feature-instance/components/container-picker'\nimport { useEffect, useState } from 'react'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { useCrmCampaigns } from '../hooks/use-crm-campaigns'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n ContactAddressFields,\n EMPTY_ADDRESS,\n type AddressDraft,\n} from './contact-address-fields'\nimport { CrmSitePicker } from './crm-site-picker'\nimport { CrmCustomFieldControl } from './crm-custom-field-control'\nimport {\n type CrmCustomDraft,\n crmCustomDraftDocument,\n crmCustomDraftMissingRequired,\n} from '../model/crm-custom-draft'\nimport { LeadOwnerSelect } from './lead-owner-select'\nimport { LeadSourceSelect } from './lead-source-select'\nimport { useLeadSourcePicklist } from '../hooks/use-lead-source-picklist'\n\n/** What the drawer hands back — already normalized where the route would. */\nexport interface NewLeadValues {\n email: string\n name: string\n company: string\n jobTitle: string\n phone: string\n website: string\n leadSource: string\n status: Extract<CrmLeadStatus, 'new' | 'working'>\n ownerUid: string\n tags: string[]\n /** The site's campaigns to file the lead under (AGL-3254), by id. */\n campaignIds: string[]\n address: AglynPostalAddress | null\n notes: string\n /**\n * The org's custom lead fields (AGL-3272), keyed by each definition's\n * `key`. Absent when the org has defined none, or none was filled — the\n * route stores a `custom` map only when there is one.\n */\n custom?: Record<string, CrmCustomValue>\n}\n\nexport interface NewLeadDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the lead is filed under — a lead is private to one site by\n * path. `null` at the organization level, where the drawer asks which\n * site with a picker and holds its submit until one is named.\n */\n hostId: string | null\n org?: Record<string, unknown> | null\n /** The request is in flight — the form holds still and the button says so. */\n busy?: boolean\n /** What the route answered when it refused, shown above the form. */\n error?: string | null\n /** The team, for the owner picker. */\n roster: OrgMemberOptions\n /**\n * The org the custom lead fields belong to (AGL-3272), as the list\n * already resolved it. `null` while it is in flight — the drawer then\n * shows the standard fields and no custom ones, which is what an org\n * with none defined shows anyway.\n */\n orgId?: string | null\n onSubmit: (values: NewLeadValues) => void\n}\n\nconst NOTES_MAX = 4000\n\n/**\n * ADDING ONE LEAD BY HAND, IN A DRAWER (AGL-3231).\n *\n * Salesforce's New Lead, on the Leads list: a person the team has heard of\n * and not yet qualified, entered with what is known — the address, the\n * name, the company as text, a title, a phone, a website, an address, tags\n * and where they came from. It makes a lead and nothing else: no contact,\n * no company record. Those are the conversion's to create once the lead is\n * real, which is what keeps one person from sitting in two lists.\n *\n * A drawer and not a form above the list, for the reason every list in this\n * console gives. The two fields a person can mistype in a way the record\n * cannot hold — the email and the phone, and the website beside them — are\n * checked before the request leaves, with the same normalizer the route\n * runs, so the refusal lands under the field. Everything else is the\n * route's to decide: whether the site already holds the address (it\n * updates that lead), whether the plan carries the suite, whether the site\n * is at the platform ceiling.\n */\nexport function NewLeadDrawer(props: NewLeadDrawerProps) {\n const { open, onClose, hostId, org, busy, error, roster, orgId, onSubmit } = props\n // The site the route files the lead under: the mounted site, or at the\n // organization level the picked one. `null` until it is known.\n const { createHostId } = useCrmScope({ hostId, org: org as never })\n\n const [email, setEmail] = useState('')\n const [name, setName] = useState('')\n const [company, setCompany] = useState('')\n const [jobTitle, setJobTitle] = useState('')\n const [phone, setPhone] = useState('')\n const [website, setWebsite] = useState('')\n const [leadSource, setLeadSource] = useState('')\n /*\n * THE ORG'S LEAD SOURCES (AGL-3298), and its default for a new record.\n * The default is filled in once the list has answered and only until the\n * person picks something themselves — \"None\" included — so a list that\n * arrives after the drawer opened does not overwrite a choice.\n */\n const leadSources = useLeadSourcePicklist(orgId ?? null)\n const [leadSourceTouched, setLeadSourceTouched] = useState(false)\n const defaultLeadSource = crmPicklistDefaultLabel(leadSources.picklist) ?? ''\n useEffect(() => {\n if (open && !leadSourceTouched) setLeadSource(defaultLeadSource)\n }, [open, leadSourceTouched, defaultLeadSource])\n const [status, setStatus] = useState<NewLeadValues['status']>('new')\n const [ownerUid, setOwnerUid] = useState('')\n const [tags, setTags] = useState('')\n const [campaignIds, setCampaignIds] = useState<string[]>([])\n const [address, setAddress] = useState<AddressDraft>(EMPTY_ADDRESS)\n const [notes, setNotes] = useState('')\n /*\n * The org's own lead fields (AGL-3272). A CREATE holds only a draft —\n * there is no stored map to diff against — and `crmCustomDraftDocument`\n * turns it into the `custom` the route stores, or nothing.\n */\n const fields = useContactFieldDefinitions(orgId ?? null, 'lead')\n const [custom, setCustom] = useState<CrmCustomDraft>({})\n const [customError, setCustomError] = useState('')\n const [emailError, setEmailError] = useState('')\n const [phoneError, setPhoneError] = useState('')\n const [websiteError, setWebsiteError] = useState('')\n /*\n * The campaigns, for the picker (AGL-3254): read while the drawer is\n * open. Under a site, the ones placed on it; at the organization level,\n * every campaign in the org — the lead is the org's record whichever\n * site it is captured by, so the site picked above does not narrow them.\n */\n const campaigns = useCrmCampaigns({ hostId, orgId }, { enabled: open })\n\n // A fresh form on every opening: a person typed and then abandoned must\n // not reappear half-filled the next time somebody reaches for New lead.\n useEffect(() => {\n if (!open) return\n setEmail('')\n setName('')\n setCompany('')\n setJobTitle('')\n setPhone('')\n setWebsite('')\n setLeadSource('')\n setLeadSourceTouched(false)\n setStatus('new')\n setOwnerUid('')\n setTags('')\n setCampaignIds([])\n setAddress(EMPTY_ADDRESS)\n setNotes('')\n setCustom({})\n setCustomError('')\n setEmailError('')\n setPhoneError('')\n setWebsiteError('')\n }, [open])\n\n const handleSubmit = () => {\n const normalizedEmail = normalizeContactEmail(email)\n const { patch, errors } = normalizeCrmLeadProfile({\n company,\n jobTitle,\n phone,\n website,\n leadSource,\n address,\n tags: normalizeCrmLeadTags(tags),\n })\n // Every required lead field counts on a create: the record is being\n // made whole, and nothing has been written to come back and fill.\n const missing = crmCustomDraftMissingRequired(fields.active, {}, custom, 'create')\n setEmailError(normalizedEmail ? '' : 'Enter a valid email address.')\n setPhoneError(errors.phone ?? '')\n setWebsiteError(errors.website ?? '')\n setCustomError(\n missing.length\n ? `${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required.`\n : '',\n )\n if (!normalizedEmail || errors.phone || errors.website || missing.length) return\n onSubmit({\n email: normalizedEmail,\n name: name.trim().replace(/\\s+/g, ' ').slice(0, CRM_LEAD_TEXT_MAX),\n company: patch.company ?? '',\n jobTitle: patch.jobTitle ?? '',\n phone: patch.phone ?? '',\n website: patch.website ?? '',\n leadSource: patch.leadSource ?? '',\n status,\n ownerUid,\n tags: patch.tags ?? [],\n campaignIds: containerMembershipValue(campaignIds),\n address: patch.address ?? null,\n notes: notes.trim().slice(0, NOTES_MAX),\n custom: crmCustomDraftDocument(custom),\n })\n }\n\n return (\n <NavigationDrawerComponent\n open={open}\n anchor=\"right\"\n variant=\"temporary\"\n onClose={onClose}\n AppBarProps={{ color: 'surface' }}\n sx={{ '& .MuiDrawer-paper': { width: { xs: '100%', sm: 480 } } }}\n appBarLeft={\n <>\n <IconButton\n color=\"inherit\"\n edge=\"start\"\n onClick={onClose}\n sx={{ mr: 2 }}\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n <SrOnly>close drawer</SrOnly>\n </IconButton>\n <Typography variant=\"h6\" component=\"div\">\n {'New lead'}\n </Typography>\n </>\n }\n appBarRight={\n <Button variant=\"outlined\" color=\"inherit\" onClick={onClose}>\n {'Cancel'}\n </Button>\n }\n >\n <Container gutterY>\n <Stack spacing={2}>\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n {/* First, because the lead is filed under the answer. Renders nothing under a site. */}\n <CrmSitePicker\n hostId={hostId}\n disabled={Boolean(busy)}\n helperText=\"The site this lead is filed under — a lead is private to one site.\"\n />\n <TextField\n size=\"small\"\n label=\"Email\"\n type=\"email\"\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n error={Boolean(emailError)}\n helperText={\n emailError ||\n 'The one field that is required — a site holds one lead per address.'\n }\n required\n fullWidth\n autoFocus\n />\n <TextField\n size=\"small\"\n label=\"Name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Company\"\n value={company}\n onChange={(event) => setCompany(event.target.value)}\n helperText=\"As text — converting the lead is what makes it a company record.\"\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Job title\"\n value={jobTitle}\n onChange={(event) => setJobTitle(event.target.value)}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Phone\"\n value={phone}\n onChange={(event) => setPhone(event.target.value)}\n error={Boolean(phoneError)}\n helperText={\n phoneError || 'With the country code, like +1 512 555 0107'\n }\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Website\"\n value={website}\n onChange={(event) => setWebsite(event.target.value)}\n error={Boolean(websiteError)}\n helperText={websiteError || 'Like acme.com'}\n fullWidth\n />\n <LeadSourceSelect\n picklist={leadSources.picklist}\n value={leadSource}\n onChange={(label) => {\n setLeadSource(label)\n setLeadSourceTouched(true)\n }}\n disabled={Boolean(busy)}\n helperText=\"Where this lead came from. The choices are kept under CRM › Fields › Leads.\"\n />\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <TextField\n select\n size=\"small\"\n label=\"Status\"\n value={status}\n onChange={(event) =>\n setStatus(event.target.value as NewLeadValues['status'])\n }\n fullWidth\n >\n <MenuItem value=\"new\">{CRM_LEAD_STATUS_LABELS.new}</MenuItem>\n <MenuItem value=\"working\">\n {CRM_LEAD_STATUS_LABELS.working}\n </MenuItem>\n </TextField>\n <LeadOwnerSelect\n value={ownerUid}\n onChange={setOwnerUid}\n roster={roster}\n />\n </Stack>\n <TextField\n size=\"small\"\n label=\"Tags\"\n placeholder=\"icp2, a-list\"\n helperText=\"Comma-separated\"\n value={tags}\n onChange={(event) => setTags(event.target.value)}\n fullWidth\n />\n {/*\n The campaigns to file the lead under (AGL-3254), picked the way a\n form's page picks them. Grouping, not consent: it decides which\n campaign pages list the lead, never whether anything mails them.\n */}\n <ContainerPicker\n kind=\"campaign\"\n options={campaigns.options}\n value={campaignIds}\n onChange={setCampaignIds}\n helperText=\"The campaigns this lead is part of. It does not decide who a campaign mails.\"\n disabled={Boolean(busy)}\n empty={campaigns.ready && !campaigns.options.length}\n emptyText={\n hostId\n ? 'This site has no campaigns yet. Create one from Marketing to file leads under it.'\n : 'There are no campaigns yet. Create one from Marketing to file leads under it.'\n }\n />\n <Typography variant=\"subtitle2\">{'Address'}</Typography>\n <ContactAddressFields value={address} onChange={setAddress} />\n {/* The org's own lead fields (AGL-3272), the same controls the\n lead's page edits them with. */}\n {fields.active.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Custom fields'}</Typography>\n {customError ? <Alert severity=\"warning\">{customError}</Alert> : null}\n {fields.active.map((definition) => (\n <CrmCustomFieldControl\n key={definition.$id}\n definition={definition}\n value={custom[definition.key]}\n onChange={(value) =>\n setCustom((current) => ({ ...current, [definition.key]: value }))\n }\n disabled={Boolean(busy)}\n />\n ))}\n </>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Notes\"\n value={notes}\n onChange={(event) => setNotes(event.target.value)}\n multiline\n minRows={2}\n slotProps={{ htmlInput: { maxLength: NOTES_MAX } }}\n fullWidth\n />\n <Button\n variant=\"contained\"\n color=\"primary\"\n // Held until the site is known: the route files the lead under it.\n disabled={Boolean(busy) || !createHostId}\n onClick={handleSubmit}\n >\n {busy ? 'Adding…' : 'Add lead'}\n </Button>\n </Stack>\n </Container>\n </NavigationDrawerComponent>\n )\n}\n\nexport default NewLeadDrawer\n"],"names":["CRM_LEAD_STATUS_LABELS","CRM_LEAD_TEXT_MAX","containerMembershipValue","crmPicklistDefaultLabel","normalizeCrmLeadTags","normalizeContactEmail","normalizeCrmLeadProfile","ICON_VARIANT_CLOSE","Container","MdiIcon","SrOnly","NavigationDrawerComponent","Alert","Button","IconButton","MenuItem","Stack","TextField","Typography","ContainerPicker","useEffect","useState","useContactFieldDefinitions","useCrmCampaigns","useCrmScope","ContactAddressFields","EMPTY_ADDRESS","CrmSitePicker","CrmCustomFieldControl","crmCustomDraftDocument","crmCustomDraftMissingRequired","LeadOwnerSelect","LeadSourceSelect","useLeadSourcePicklist","NOTES_MAX","NewLeadDrawer","props","open","onClose","hostId","org","busy","error","roster","orgId","onSubmit","createHostId","email","setEmail","name","setName","company","setCompany","jobTitle","setJobTitle","phone","setPhone","website","setWebsite","leadSource","setLeadSource","leadSources","leadSourceTouched","setLeadSourceTouched","defaultLeadSource","picklist","status","setStatus","ownerUid","setOwnerUid","tags","setTags","campaignIds","setCampaignIds","address","setAddress","notes","setNotes","fields","custom","setCustom","customError","setCustomError","emailError","setEmailError","phoneError","setPhoneError","websiteError","setWebsiteError","campaigns","enabled","handleSubmit","errors","patch","normalizedEmail","missing","active","length","join","trim","replace","slice","anchor","variant","AppBarProps","color","sx","width","xs","sm","appBarLeft","edge","onClick","mr","path","component","appBarRight","gutterY","spacing","severity","disabled","Boolean","helperText","size","label","type","value","onChange","event","target","required","fullWidth","autoFocus","slotProps","htmlInput","maxLength","direction","select","new","working","placeholder","kind","options","empty","ready","emptyText","map","definition","key","current","$id","multiline","minRows"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { pluginRecordHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
|
|
18
19
|
import { AppLink } from "@aglyn/shared-ui-jsx";
|
|
19
20
|
import { Button, Stack, Typography } from "@mui/material";
|
|
20
21
|
import { useCallback, useState } from "react";
|
|
@@ -124,8 +125,11 @@ OrgLeadSurfacesNote.displayName = 'OrgLeadSurfacesNote';
|
|
|
124
125
|
const name = mount.siteName(host.id);
|
|
125
126
|
const hubHref = mount.siteHubHref(host.id);
|
|
126
127
|
const subdomain = mount.siteSubdomain(host.id);
|
|
127
|
-
const formHref = useCallback((formId)=>subdomain ?
|
|
128
|
-
|
|
128
|
+
const formHref = useCallback((formId)=>subdomain ? pluginRecordHref('form', {
|
|
129
|
+
orgSlug: mount.orgSlug,
|
|
130
|
+
host: subdomain
|
|
131
|
+
}, formId) : null, [
|
|
132
|
+
mount.orgSlug,
|
|
129
133
|
subdomain
|
|
130
134
|
]);
|
|
131
135
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/org-lead-surfaces-note.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { ConsolePluginOrgHost } from '@aglyn/aglyn'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport type { LeadSurfaceForm } from '../model/lead-surfaces'\nimport {\n LEAD_SURFACES_CONTACTS_TOO,\n LEAD_SURFACES_INTRO,\n type LeadRoutingTarget,\n LeadSurfaceFormList,\n UnroutedLeadSurfaces,\n useLeadSurfaceForms,\n useTurnOnLeadRouting,\n} from './lead-surfaces-note'\n\n/**\n * How many sites the note opens with their forms showing. The rest sit\n * behind a count until asked for — and, since a site's forms are read by\n * the group that shows them, are not read until then either.\n */\nexport const ORG_LEAD_SURFACES_OPEN_SITES = 3\n\n/**\n * WHAT CREATES A LEAD, ACROSS THE ORGANIZATION (AGL-2638).\n *\n * The org-level Leads section lists every site's leads in one table, and\n * for a while said nothing about why a form's people were in it: the\n * per-site note was drawn only under a site. This is the same note grouped\n * by site — each site's routed forms, the forms that could route with the\n * switch beside them, and the forms that cannot with the reason as the\n * tooltip — over the sites the mount already bounds. The always-on\n * surfaces are named once at the top, because a member sign-up files a\n * lead on every site and saying so three times would suggest otherwise.\n *\n * ## Bounded two ways\n *\n * The sites come from the mount, which the shell already capped; and only\n * the first {@link ORG_LEAD_SURFACES_OPEN_SITES} groups mount their reader\n * until the reader opens the rest, so an org with twenty sites opens three\n * listeners on arrival and not twenty.\n *\n * ## Links\n *\n * A site's name opens its own Leads section, and a form its own page, both\n * built from the mount's `hostsPath` and the site's subdomain the way the\n * mount builds a site's hub; a site whose subdomain never resolved is named\n * and not linked. No host-index read per site: the mount already answered.\n *\n * Rendered only beneath the org mount — under a site the hook answers\n * `null` and so does the note, which keeps the section's one-line choice\n * between the two notes honest.\n */\nexport function OrgLeadSurfacesNote() {\n const mount = useCrmOrgMount()\n const { turningOn, turnOn } = useTurnOnLeadRouting()\n const [opened, setOpened] = useState(false)\n if (!mount) return null\n const { hosts, hostsReady } = mount\n const shown = opened ? hosts : hosts.slice(0, ORG_LEAD_SURFACES_OPEN_SITES)\n const folded = hosts.length - shown.length\n return (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${LEAD_SURFACES_INTRO}, on every site. ${LEAD_SURFACES_CONTACTS_TOO}`}\n </Typography>\n {!hostsReady ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'…'}\n </Typography>\n ) : !hosts.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet.'}\n </Typography>\n ) : (\n <>\n {shown.map((host) => (\n <SiteLeadSurfaces\n key={host.id}\n host={host}\n mount={mount}\n turningOn={turningOn}\n onTurnOn={(form) =>\n void turnOn({ hostId: host.id, form, siteName: mount.siteName(host.id) })\n }\n />\n ))}\n {folded > 0 ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(true)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show ${folded} more ${folded === 1 ? 'site' : 'sites'}`}\n </Button>\n ) : hosts.length > ORG_LEAD_SURFACES_OPEN_SITES ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(false)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show the first ${ORG_LEAD_SURFACES_OPEN_SITES} sites only`}\n </Button>\n ) : null}\n </>\n )}\n </Stack>\n )\n}\nOrgLeadSurfacesNote.displayName = 'OrgLeadSurfacesNote'\n\n/**\n * One site's group: its name, then the same two rows the site note draws,\n * read by the same reader. A site with no forms says so in one line rather\n * than drawing an empty \"no form routes leads yet\" that reads like a\n * verdict on forms that do not exist.\n */\nfunction SiteLeadSurfaces(props: {\n host: ConsolePluginOrgHost\n mount: CrmOrgMount\n turningOn: LeadRoutingTarget | null\n onTurnOn: (form: LeadSurfaceForm) => void\n}) {\n const { host, mount, turningOn, onTurnOn } = props\n const { routed, unrouted, truncated, status } = useLeadSurfaceForms(host.id)\n const name = mount.siteName(host.id)\n const hubHref = mount.siteHubHref(host.id)\n const subdomain = mount.siteSubdomain(host.id)\n const formHref = useCallback(\n (formId: string) =>\n subdomain\n ? `${mount.hostsPath}/${encodeURIComponent(subdomain)}/forms/${encodeURIComponent(formId)}`\n : null,\n [mount.hostsPath, subdomain],\n )\n return (\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle2\" component=\"div\">\n {hubHref ? (\n <AppLink href={`${hubHref}/leads`} underline=\"hover\">\n {name}\n </AppLink>\n ) : (\n name\n )}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"div\">\n {status === 'loading' ? (\n '…'\n ) : status === 'error' ? (\n 'The forms on this site could not be read.'\n ) : !routed.length && !unrouted.length ? (\n 'No forms on this site yet.'\n ) : routed.length ? (\n <>\n {'Routing leads: '}\n <LeadSurfaceFormList forms={routed} truncated={truncated} formHref={formHref} />\n {'.'}\n </>\n ) : (\n 'No form routes leads yet.'\n )}\n </Typography>\n {unrouted.length ? (\n <UnroutedLeadSurfaces\n hostId={host.id}\n forms={unrouted}\n formHref={formHref}\n turningOn={turningOn}\n onTurnOn={onTurnOn}\n />\n ) : null}\n </Stack>\n )\n}\nSiteLeadSurfaces.displayName = 'SiteLeadSurfaces'\n\nexport default OrgLeadSurfacesNote\n"],"names":["AppLink","Button","Stack","Typography","useCallback","useState","useCrmOrgMount","LEAD_SURFACES_CONTACTS_TOO","LEAD_SURFACES_INTRO","LeadSurfaceFormList","UnroutedLeadSurfaces","useLeadSurfaceForms","useTurnOnLeadRouting","ORG_LEAD_SURFACES_OPEN_SITES","OrgLeadSurfacesNote","mount","turningOn","turnOn","opened","setOpened","hosts","hostsReady","shown","slice","folded","length","spacing","variant","color","map","host","SiteLeadSurfaces","onTurnOn","form","hostId","id","siteName","size","onClick","sx","alignSelf","displayName","props","routed","unrouted","truncated","status","name","hubHref","siteHubHref","subdomain","siteSubdomain","formHref","formId","hostsPath","encodeURIComponent","component","href","underline","forms"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAGA,SAASA,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACzD,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAC7C,SAA2BC,cAAc,QAAQ,gCAA4B;AAE7E,SACEC,0BAA0B,EAC1BC,mBAAmB,EAEnBC,mBAAmB,EACnBC,oBAAoB,EACpBC,mBAAmB,EACnBC,oBAAoB,QACf,0BAAsB;AAE7B;;;;CAIC,GACD,OAAO,MAAMC,+BAA+B,EAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASC;IACd,MAAMC,QAAQT;IACd,MAAM,EAAEU,SAAS,EAAEC,MAAM,EAAE,GAAGL;IAC9B,MAAM,CAACM,QAAQC,UAAU,GAAGd,SAAS;IACrC,IAAI,CAACU,OAAO,OAAO;IACnB,MAAM,EAAEK,KAAK,EAAEC,UAAU,EAAE,GAAGN;IAC9B,MAAMO,QAAQJ,SAASE,QAAQA,MAAMG,KAAK,CAAC,GAAGV;IAC9C,MAAMW,SAASJ,MAAMK,MAAM,GAAGH,MAAMG,MAAM;IAC1C,qBACE,MAACvB;QAAMwB,SAAS;;0BACd,KAACvB;gBAAWwB,SAAQ;gBAAQC,OAAM;0BAC/B,GAAGpB,oBAAoB,iBAAiB,EAAED,4BAA4B;;YAExE,CAACc,2BACA,KAAClB;gBAAWwB,SAAQ;gBAAUC,OAAM;0BACjC;iBAED,CAACR,MAAMK,MAAM,iBACf,KAACtB;gBAAWwB,SAAQ;gBAAQC,OAAM;0BAC/B;+BAGH;;oBACGN,MAAMO,GAAG,CAAC,CAACC,qBACV,KAACC;4BAECD,MAAMA;4BACNf,OAAOA;4BACPC,WAAWA;4BACXgB,UAAU,CAACC,OACT,KAAKhB,OAAO;oCAAEiB,QAAQJ,KAAKK,EAAE;oCAAEF;oCAAMG,UAAUrB,MAAMqB,QAAQ,CAACN,KAAKK,EAAE;gCAAE;2BALpEL,KAAKK,EAAE;oBASfX,SAAS,kBACR,KAACvB;wBACCoC,MAAK;wBACLV,SAAQ;wBACRW,SAAS,IAAMnB,UAAU;wBACzBoB,IAAI;4BAAEC,WAAW;wBAAa;kCAE7B,CAAC,KAAK,EAAEhB,OAAO,MAAM,EAAEA,WAAW,IAAI,SAAS,SAAS;yBAEzDJ,MAAMK,MAAM,GAAGZ,6CACjB,KAACZ;wBACCoC,MAAK;wBACLV,SAAQ;wBACRW,SAAS,IAAMnB,UAAU;wBACzBoB,IAAI;4BAAEC,WAAW;wBAAa;kCAE7B,CAAC,eAAe,EAAE3B,6BAA6B,WAAW,CAAC;yBAE5D;;;;;AAKd;AACAC,oBAAoB2B,WAAW,GAAG;AAElC;;;;;CAKC,GACD,SAASV,iBAAiBW,KAKzB;IACC,MAAM,EAAEZ,IAAI,EAAEf,KAAK,EAAEC,SAAS,EAAEgB,QAAQ,EAAE,GAAGU;IAC7C,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,MAAM,EAAE,GAAGnC,oBAAoBmB,KAAKK,EAAE;IAC3E,MAAMY,OAAOhC,MAAMqB,QAAQ,CAACN,KAAKK,EAAE;IACnC,MAAMa,UAAUjC,MAAMkC,WAAW,CAACnB,KAAKK,EAAE;IACzC,MAAMe,YAAYnC,MAAMoC,aAAa,CAACrB,KAAKK,EAAE;IAC7C,MAAMiB,WAAWhD,YACf,CAACiD,SACCH,YACI,GAAGnC,MAAMuC,SAAS,CAAC,CAAC,EAAEC,mBAAmBL,WAAW,OAAO,EAAEK,mBAAmBF,SAAS,GACzF,MACN;QAACtC,MAAMuC,SAAS;QAAEJ;KAAU;IAE9B,qBACE,MAAChD;QAAMwB,SAAS;;0BACd,KAACvB;gBAAWwB,SAAQ;gBAAY6B,WAAU;0BACvCR,wBACC,KAAChD;oBAAQyD,MAAM,GAAGT,QAAQ,MAAM,CAAC;oBAAEU,WAAU;8BAC1CX;qBAGHA;;0BAGJ,KAAC5C;gBAAWwB,SAAQ;gBAAQC,OAAM;gBAAiB4B,WAAU;0BAC1DV,WAAW,YACV,MACEA,WAAW,UACb,8CACE,CAACH,OAAOlB,MAAM,IAAI,CAACmB,SAASnB,MAAM,GACpC,+BACEkB,OAAOlB,MAAM,iBACf;;wBACG;sCACD,KAAChB;4BAAoBkD,OAAOhB;4BAAQE,WAAWA;4BAAWO,UAAUA;;wBACnE;;qBAGH;;YAGHR,SAASnB,MAAM,iBACd,KAACf;gBACCwB,QAAQJ,KAAKK,EAAE;gBACfwB,OAAOf;gBACPQ,UAAUA;gBACVpC,WAAWA;gBACXgB,UAAUA;iBAEV;;;AAGV;AACAD,iBAAiBU,WAAW,GAAG;AAE/B,eAAe3B,oBAAmB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/org-lead-surfaces-note.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { ConsolePluginOrgHost } from '@aglyn/aglyn'\nimport { pluginRecordHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport type { LeadSurfaceForm } from '../model/lead-surfaces'\nimport {\n LEAD_SURFACES_CONTACTS_TOO,\n LEAD_SURFACES_INTRO,\n type LeadRoutingTarget,\n LeadSurfaceFormList,\n UnroutedLeadSurfaces,\n useLeadSurfaceForms,\n useTurnOnLeadRouting,\n} from './lead-surfaces-note'\n\n/**\n * How many sites the note opens with their forms showing. The rest sit\n * behind a count until asked for — and, since a site's forms are read by\n * the group that shows them, are not read until then either.\n */\nexport const ORG_LEAD_SURFACES_OPEN_SITES = 3\n\n/**\n * WHAT CREATES A LEAD, ACROSS THE ORGANIZATION (AGL-2638).\n *\n * The org-level Leads section lists every site's leads in one table, and\n * for a while said nothing about why a form's people were in it: the\n * per-site note was drawn only under a site. This is the same note grouped\n * by site — each site's routed forms, the forms that could route with the\n * switch beside them, and the forms that cannot with the reason as the\n * tooltip — over the sites the mount already bounds. The always-on\n * surfaces are named once at the top, because a member sign-up files a\n * lead on every site and saying so three times would suggest otherwise.\n *\n * ## Bounded two ways\n *\n * The sites come from the mount, which the shell already capped; and only\n * the first {@link ORG_LEAD_SURFACES_OPEN_SITES} groups mount their reader\n * until the reader opens the rest, so an org with twenty sites opens three\n * listeners on arrival and not twenty.\n *\n * ## Links\n *\n * A site's name opens its own Leads section, and a form its own page, both\n * built from the mount's `hostsPath` and the site's subdomain the way the\n * mount builds a site's hub; a site whose subdomain never resolved is named\n * and not linked. No host-index read per site: the mount already answered.\n *\n * Rendered only beneath the org mount — under a site the hook answers\n * `null` and so does the note, which keeps the section's one-line choice\n * between the two notes honest.\n */\nexport function OrgLeadSurfacesNote() {\n const mount = useCrmOrgMount()\n const { turningOn, turnOn } = useTurnOnLeadRouting()\n const [opened, setOpened] = useState(false)\n if (!mount) return null\n const { hosts, hostsReady } = mount\n const shown = opened ? hosts : hosts.slice(0, ORG_LEAD_SURFACES_OPEN_SITES)\n const folded = hosts.length - shown.length\n return (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${LEAD_SURFACES_INTRO}, on every site. ${LEAD_SURFACES_CONTACTS_TOO}`}\n </Typography>\n {!hostsReady ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'…'}\n </Typography>\n ) : !hosts.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet.'}\n </Typography>\n ) : (\n <>\n {shown.map((host) => (\n <SiteLeadSurfaces\n key={host.id}\n host={host}\n mount={mount}\n turningOn={turningOn}\n onTurnOn={(form) =>\n void turnOn({ hostId: host.id, form, siteName: mount.siteName(host.id) })\n }\n />\n ))}\n {folded > 0 ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(true)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show ${folded} more ${folded === 1 ? 'site' : 'sites'}`}\n </Button>\n ) : hosts.length > ORG_LEAD_SURFACES_OPEN_SITES ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => setOpened(false)}\n sx={{ alignSelf: 'flex-start' }}\n >\n {`Show the first ${ORG_LEAD_SURFACES_OPEN_SITES} sites only`}\n </Button>\n ) : null}\n </>\n )}\n </Stack>\n )\n}\nOrgLeadSurfacesNote.displayName = 'OrgLeadSurfacesNote'\n\n/**\n * One site's group: its name, then the same two rows the site note draws,\n * read by the same reader. A site with no forms says so in one line rather\n * than drawing an empty \"no form routes leads yet\" that reads like a\n * verdict on forms that do not exist.\n */\nfunction SiteLeadSurfaces(props: {\n host: ConsolePluginOrgHost\n mount: CrmOrgMount\n turningOn: LeadRoutingTarget | null\n onTurnOn: (form: LeadSurfaceForm) => void\n}) {\n const { host, mount, turningOn, onTurnOn } = props\n const { routed, unrouted, truncated, status } = useLeadSurfaceForms(host.id)\n const name = mount.siteName(host.id)\n const hubHref = mount.siteHubHref(host.id)\n const subdomain = mount.siteSubdomain(host.id)\n const formHref = useCallback(\n (formId: string) =>\n subdomain ? pluginRecordHref('form', { orgSlug: mount.orgSlug, host: subdomain }, formId) : null,\n [mount.orgSlug, subdomain],\n )\n return (\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle2\" component=\"div\">\n {hubHref ? (\n <AppLink href={`${hubHref}/leads`} underline=\"hover\">\n {name}\n </AppLink>\n ) : (\n name\n )}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"div\">\n {status === 'loading' ? (\n '…'\n ) : status === 'error' ? (\n 'The forms on this site could not be read.'\n ) : !routed.length && !unrouted.length ? (\n 'No forms on this site yet.'\n ) : routed.length ? (\n <>\n {'Routing leads: '}\n <LeadSurfaceFormList forms={routed} truncated={truncated} formHref={formHref} />\n {'.'}\n </>\n ) : (\n 'No form routes leads yet.'\n )}\n </Typography>\n {unrouted.length ? (\n <UnroutedLeadSurfaces\n hostId={host.id}\n forms={unrouted}\n formHref={formHref}\n turningOn={turningOn}\n onTurnOn={onTurnOn}\n />\n ) : null}\n </Stack>\n )\n}\nSiteLeadSurfaces.displayName = 'SiteLeadSurfaces'\n\nexport default OrgLeadSurfacesNote\n"],"names":["pluginRecordHref","AppLink","Button","Stack","Typography","useCallback","useState","useCrmOrgMount","LEAD_SURFACES_CONTACTS_TOO","LEAD_SURFACES_INTRO","LeadSurfaceFormList","UnroutedLeadSurfaces","useLeadSurfaceForms","useTurnOnLeadRouting","ORG_LEAD_SURFACES_OPEN_SITES","OrgLeadSurfacesNote","mount","turningOn","turnOn","opened","setOpened","hosts","hostsReady","shown","slice","folded","length","spacing","variant","color","map","host","SiteLeadSurfaces","onTurnOn","form","hostId","id","siteName","size","onClick","sx","alignSelf","displayName","props","routed","unrouted","truncated","status","name","hubHref","siteHubHref","subdomain","siteSubdomain","formHref","formId","orgSlug","component","href","underline","forms"],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import { checkEntitlement, CRM_ACTION_RECIPES, planLabelGrantingFeature, pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
|
+
import { pluginRecordListHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
|
|
20
21
|
import { AppLink, CardDisplay, SrOnly } from "@aglyn/shared-ui-jsx";
|
|
21
22
|
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
22
23
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
@@ -28,12 +29,16 @@ import { useCrmOrgMount } from "../hooks/use-crm-org-mount.js";
|
|
|
28
29
|
import { useCrmApi } from "./use-crm-api.js";
|
|
29
30
|
/** How many of a site's forms the picker offers before it says it stopped. */ const FORM_OPTION_CEILING = 100;
|
|
30
31
|
/**
|
|
31
|
-
* The site's
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* The site's list of actions — where an installed action lives and is
|
|
33
|
+
* edited — at the address the plugin that keeps actions publishes, or `null`
|
|
34
|
+
* for a site whose subdomain the mount could not answer or a workspace where
|
|
35
|
+
* no plugin keeps actions, which is named and not linked.
|
|
34
36
|
*/ function siteActionsHref(mount, hostId) {
|
|
35
37
|
const subdomain = mount.siteSubdomain(hostId);
|
|
36
|
-
return subdomain ?
|
|
38
|
+
return subdomain ? pluginRecordListHref('action', {
|
|
39
|
+
orgSlug: mount.orgSlug,
|
|
40
|
+
host: subdomain
|
|
41
|
+
}) : null;
|
|
37
42
|
}
|
|
38
43
|
function standingOf(status, hostId, recipeId) {
|
|
39
44
|
const site = status == null ? void 0 : status.get(hostId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recipes-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n planLabelGrantingFeature,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Drawer,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type {\n CrmRecipeInstallResult,\n CrmRecipeSiteStatus,\n} from '../constants/api-routes'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmApi } from './use-crm-api'\n\n/** How many of a site's forms the picker offers before it says it stopped. */\nconst FORM_OPTION_CEILING = 100\n\n/**\n * The site's Automation → Actions page — where an installed action lives\n * and is edited — or `null` for a site whose subdomain the mount could not\n * answer, which is named and not linked.\n */\nfunction siteActionsHref(mount: CrmOrgMount, hostId: string): string | null {\n const subdomain = mount.siteSubdomain(hostId)\n return subdomain\n ? `${mount.hostsPath}/${encodeURIComponent(subdomain)}/automation/actions`\n : null\n}\n\n/**\n * Where a site stands with one recipe, read off the stamps the status route\n * answered. `maybe` is the third answer and the honest one: the site\n * carries actions saved before the stamp existed, any of which could have\n * started from the recipe, so the card neither lists the site as carrying\n * it nor offers the install as if to a blank site. `unread` is the answer\n * before the route has answered, or for a site it did not name.\n */\ntype SiteStanding = 'installed' | 'maybe' | 'clear' | 'unread'\n\nfunction standingOf(\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null,\n hostId: string,\n recipeId: CrmActionRecipeId,\n): SiteStanding {\n const site = status?.get(hostId)\n if (!site) return 'unread'\n if (site.installed.includes(recipeId)) return 'installed'\n return site.unstamped > 0 ? 'maybe' : 'clear'\n}\n\n/**\n * The stamps of every site of the org, read once the site list is known\n * and again after each install, so the card reports what the documents\n * say rather than what it last did.\n */\nfunction useRecipeStatus(mount: CrmOrgMount | null) {\n const api = useCrmApi(null)\n const [sites, setSites] = useState<Map<string, CrmRecipeSiteStatus> | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refresh = useCallback(async () => {\n try {\n const { response, payload } = await api('recipe-status', {})\n if (!response.ok) {\n setError(payload?.error ?? 'Which sites carry a recipe could not be read.')\n return\n }\n const answered = (payload.sites ?? []) as CrmRecipeSiteStatus[]\n setSites(new Map(answered.map((site) => [site.hostId, site])))\n setError(null)\n } catch (caught) {\n console.error(caught)\n setError('Which sites carry a recipe could not be read.')\n }\n }, [api])\n const hostsReady = Boolean(mount?.hostsReady)\n useEffect(() => {\n if (hostsReady) void refresh()\n }, [hostsReady, refresh])\n return { sites, error, refresh }\n}\n\n/** A site's name, linked into its Actions page when the link is known. */\nfunction SiteLink(props: { mount: CrmOrgMount; hostId: string }) {\n const { mount, hostId } = props\n const href = siteActionsHref(mount, hostId)\n const name = mount.siteName(hostId)\n return href ? <AppLink href={href}>{name}</AppLink> : <>{name}</>\n}\nSiteLink.displayName = 'SiteLink'\n\n/** One recipe's \"Installed on\" cell: the sites that carry it, and the ones that may. */\nfunction InstalledOn(props: {\n mount: CrmOrgMount\n recipe: CrmActionRecipe\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n}) {\n const { mount, recipe, status } = props\n if (!status) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reading…'}\n </Typography>\n )\n }\n const installed = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'installed',\n )\n const maybe = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'maybe',\n )\n return (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">\n {installed.length === 0\n ? 'Not installed on any site yet.'\n : installed.map((host, index) => (\n <Fragment key={host.id}>\n {index > 0 ? ', ' : null}\n <SiteLink mount={mount} hostId={host.id} />\n </Fragment>\n ))}\n </Typography>\n {maybe.length > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`May already have it: ${maybe.map((host) => host.name).join(', ')} — ` +\n 'actions there were saved before recipes were tracked.'}\n </Typography>\n ) : null}\n </Stack>\n )\n}\nInstalledOn.displayName = 'InstalledOn'\n\ninterface RecipeInstallFormProps {\n recipe: CrmActionRecipe\n mount: CrmOrgMount\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n onClose: () => void\n onInstalled: (result: CrmRecipeInstallResult, hostId: string) => void\n}\n\n/**\n * The install drawer's body: pick the site, pick the form a recipe that\n * needs one is keyed on, confirm. Mounted fresh on every opening — the\n * Drawer unmounts it when closed — so the picks start over each time from\n * the session's site, when that site does not already carry the recipe.\n *\n * The forms are read from the picked site only once a site is picked and\n * only for a recipe that needs one, so a reader installing the other three\n * pays no read. Live forms only: an archived form collects nothing, and a\n * recipe keyed on it would never fire.\n */\nfunction RecipeInstallForm(props: RecipeInstallFormProps) {\n const { recipe, mount, status, onClose, onInstalled } = props\n const firestore = useFirestore()\n const api = useCrmApi(null)\n const needsForm = recipe.needs === 'form'\n const [hostId, setHostId] = useState(() => {\n const pick = mount.createHostId\n return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : ''\n })\n const [formId, setFormId] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const { data: formRead, status: formsStatus } = useFirestoreCollection<any>(\n () =>\n needsForm && hostId\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n FORM_OPTION_CEILING,\n )\n : null,\n [firestore, needsForm, hostId],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n FORM_OPTION_CEILING,\n )\n const formOptions = useMemo(\n () =>\n (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n ),\n [formDocs],\n )\n const formsLoading = needsForm && Boolean(hostId) && formsStatus === 'loading'\n\n const canInstall =\n Boolean(hostId) && (!needsForm || Boolean(formId)) && !busy && !formsLoading\n\n const handleInstall = async () => {\n if (!canInstall) return\n setBusy(true)\n setError(null)\n try {\n const { response, payload } = await api('recipe-install', {\n hostId,\n recipeId: recipe.id,\n ...(formId ? { formId } : {}),\n })\n if (!response.ok) {\n setError(payload?.error ?? 'The recipe could not be installed.')\n return\n }\n onInstalled(payload as CrmRecipeInstallResult, hostId)\n } catch (caught) {\n console.error(caught)\n setError('The recipe could not be installed.')\n } finally {\n setBusy(false)\n }\n }\n\n const siteSuffix = (id: string) => {\n const standing = standingOf(status, id, recipe.id)\n if (standing === 'installed') return ' — installed'\n if (standing === 'maybe') return ' — may already have it'\n return ''\n }\n\n return (\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{`Install “${recipe.title}”`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={hostId}\n onChange={(event) => {\n setHostId(event.target.value)\n setFormId('')\n setError(null)\n }}\n disabled={busy}\n helperText={\n mount.hosts.length === 0\n ? 'This organization has no sites yet.'\n : 'The action is written into this site’s Automation → Actions.'\n }\n fullWidth\n >\n {mount.hosts.map((host) => (\n <MenuItem\n key={host.id}\n value={host.id}\n disabled={standingOf(status, host.id, recipe.id) === 'installed'}\n >\n {`${host.name}${siteSuffix(host.id)}`}\n </MenuItem>\n ))}\n </TextField>\n {needsForm ? (\n <TextField\n select\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n disabled={!hostId || busy || formsLoading}\n helperText={\n !hostId\n ? 'Pick the site first; the recipe is keyed on one of its forms.'\n : formsLoading\n ? 'Loading the site’s forms…'\n : formOptions.length === 0\n ? 'This site has no live forms.'\n : formsTruncated\n ? `Showing the first ${FORM_OPTION_CEILING} forms.`\n : 'Contacts this form makes are tagged with its name.'\n }\n fullWidth\n >\n {formOptions.map((form) => (\n <MenuItem key={form.id} value={form.id}>\n {form.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The action is created enabled, exactly as the recipe defines it. ' +\n 'Change anything afterwards from the site’s Automation → Actions page.'}\n </Typography>\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canInstall}\n onClick={() => void handleInstall()}\n >\n {busy ? 'Installing…' : 'Install'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nRecipeInstallForm.displayName = 'RecipeInstallForm'\n\nexport interface RecipesCardProps {\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Recipes\" — the CRM's ready-made automations, installed on a site from\n * the organization (AGL-2639).\n *\n * ## Why this card exists at the org level and nowhere else\n *\n * Automation is a site feature: a site's Actions page has a Recipes menu\n * that opens the editor prefilled, and the person saves what they see.\n * The org hub mounts over every site with no editor of its own, so it had\n * no recipes at all, and an org running five sites installed \"Welcome a\n * new lead\" five times by hand. This card is the org's door: every recipe,\n * the sites that already carry it, and Install, which writes the action\n * into the site the reader picks through the server — the server stamps\n * the recipe, refuses a site outside the org, and refuses a second copy.\n * Under a site the section does not mount it; the site's own menu is the\n * right door there.\n *\n * ## \"Installed on\" reads the stamp, and says when it cannot\n *\n * The status route answers, per site, the recipes a live action is stamped\n * with and how many live actions carry no stamp. A site with any of the\n * latter is named as one that MAY already have the recipe rather than\n * listed as clear: those actions were saved before the stamp existed and\n * the card cannot tell a hand-built welcome from an installed one.\n *\n * ## Creation opens a drawer\n *\n * Install is a button per row; the picks — the site, and for Tag by form\n * one of that site's forms — live in a drawer, never above the list.\n */\nexport function RecipesCard(props: RecipesCardProps) {\n const { org } = props\n const mount = useCrmOrgMount()\n const { enqueueSnackbar } = useSnackbar()\n const status = useRecipeStatus(mount)\n const refreshStatus = status.refresh\n const [installing, setInstalling] = useState<CrmActionRecipe | null>(null)\n const [lastInstall, setLastInstall] = useState<{\n hostId: string\n name: string\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const canInstall = Boolean(mount?.hostsReady) && entitled\n\n const handleInstalled = useCallback(\n (result: CrmRecipeInstallResult, hostId: string) => {\n setInstalling(null)\n setLastInstall({ hostId, name: result.name })\n enqueueSnackbar(\n `Installed “${result.name}” on ${mount?.siteName(hostId) ?? hostId}`,\n { variant: 'success', persist: false },\n )\n void refreshStatus()\n },\n [enqueueSnackbar, mount, refreshStatus],\n )\n\n // Mounted by the section only beneath the org hub's provider; the hooks\n // above run either way, so the render order never depends on the mount.\n if (!mount) return null\n\n const lastInstallHref = lastInstall ? siteActionsHref(mount, lastInstall.hostId) : null\n return (\n <CardDisplay\n header={'Recipes'}\n help={pluginDocsHelp('crmSettings', { anchor: '#recipes' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Ready-made automations for the CRM. Install one on a site and the ' +\n 'action is written into that site’s Automation → Actions as the ' +\n 'recipe defines it, where it can be edited like any other action.'}\n </Typography>\n {lastInstall ? (\n <Alert\n severity=\"success\"\n onClose={() => setLastInstall(null)}\n action={\n lastInstallHref ? (\n // A link dressed as a button: the anchor carries its own\n // href, so it keeps the link role a navigation door has.\n <Button\n component={AppLink as any}\n {...({\n componentVariant: 'naked',\n nativeButton: false,\n } as any)}\n href={lastInstallHref}\n size=\"small\"\n color=\"inherit\"\n >\n {'Open Automation → Actions'}\n </Button>\n ) : undefined\n }\n >\n {`Installed “${lastInstall.name}” on ${mount.siteName(lastInstall.hostId)}.`}\n </Alert>\n ) : null}\n {status.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {status.error}\n </Typography>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Recipe'}</TableCell>\n <TableCell>{'Installed on'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {CRM_ACTION_RECIPES.map((recipe) => (\n <TableRow key={recipe.id} hover>\n <TableCell sx={{ maxWidth: 360 }}>\n <Typography variant=\"body2\">{recipe.title}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n </TableCell>\n <TableCell>\n <InstalledOn mount={mount} recipe={recipe} status={status.sites} />\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 120 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!canInstall}\n onClick={() => setInstalling(recipe)}\n >\n {'Install'}\n <SrOnly>{` ${recipe.title}`}</SrOnly>\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {!entitled ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`The actions builder requires the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade.'}\n </Typography>\n ) : null}\n {mount.hostsReady && mount.hosts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nowhere to install a recipe.'}\n </Typography>\n ) : null}\n </Stack>\n <Drawer anchor=\"right\" open={Boolean(installing)} onClose={() => setInstalling(null)}>\n {installing ? (\n <RecipeInstallForm\n recipe={installing}\n mount={mount}\n status={status.sites}\n onClose={() => setInstalling(null)}\n onInstalled={handleInstalled}\n />\n ) : null}\n </Drawer>\n </CardDisplay>\n )\n}\nRecipesCard.displayName = 'RecipesCard'\n\nexport default RecipesCard\n"],"names":["checkEntitlement","CRM_ACTION_RECIPES","planLabelGrantingFeature","pluginDocsHelp","AppLink","CardDisplay","SrOnly","ScrollTable","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Alert","Button","Drawer","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","Fragment","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmApi","FORM_OPTION_CEILING","siteActionsHref","mount","hostId","subdomain","siteSubdomain","hostsPath","encodeURIComponent","standingOf","status","recipeId","site","get","installed","includes","unstamped","useRecipeStatus","api","sites","setSites","error","setError","refresh","payload","response","ok","answered","Map","map","caught","console","hostsReady","Boolean","SiteLink","props","href","name","siteName","displayName","InstalledOn","recipe","variant","color","hosts","filter","host","id","maybe","spacing","length","index","join","RecipeInstallForm","onClose","onInstalled","firestore","needsForm","needs","setHostId","pick","createHostId","formId","setFormId","busy","setBusy","data","formRead","formsStatus","idField","rows","formDocs","truncated","formsTruncated","formOptions","form","archivedAt","$id","String","trim","sort","a","b","localeCompare","formsLoading","canInstall","handleInstall","siteSuffix","standing","sx","width","p","title","description","select","size","label","value","onChange","event","target","disabled","helperText","fullWidth","direction","onClick","RecipesCard","org","enqueueSnackbar","refreshStatus","installing","setInstalling","lastInstall","setLastInstall","entitled","handleInstalled","result","persist","lastInstallHref","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","action","component","componentVariant","nativeButton","undefined","align","hover","maxWidth","open"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAEEA,gBAAgB,EAChBC,kBAAkB,EAGlBC,wBAAwB,EACxBC,cAAc,QACT,eAAc;AACrB,SAASC,OAAO,EAAEC,WAAW,EAAEC,MAAM,QAAQ,uBAAsB;AACnE,SAASC,WAAW,QAAQ,yDAAwD;AACpF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,eAAe,EACfC,iBAAiB,EACjBC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SACEC,KAAK,EACLC,MAAM,EACNC,MAAM,EACNC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,UAAU,QAAQ,qBAAoB;AAC/C,SAASC,QAAQ,EAAEC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAK3E,SAA2BC,cAAc,QAAQ,gCAA4B;AAC7E,SAASC,SAAS,QAAQ,mBAAe;AAEzC,4EAA4E,GAC5E,MAAMC,sBAAsB;AAE5B;;;;CAIC,GACD,SAASC,gBAAgBC,KAAkB,EAAEC,MAAc;IACzD,MAAMC,YAAYF,MAAMG,aAAa,CAACF;IACtC,OAAOC,YACH,GAAGF,MAAMI,SAAS,CAAC,CAAC,EAAEC,mBAAmBH,WAAW,mBAAmB,CAAC,GACxE;AACN;AAYA,SAASI,WACPC,MAAuD,EACvDN,MAAc,EACdO,QAA2B;IAE3B,MAAMC,OAAOF,0BAAAA,OAAQG,GAAG,CAACT;IACzB,IAAI,CAACQ,MAAM,OAAO;IAClB,IAAIA,KAAKE,SAAS,CAACC,QAAQ,CAACJ,WAAW,OAAO;IAC9C,OAAOC,KAAKI,SAAS,GAAG,IAAI,UAAU;AACxC;AAEA;;;;CAIC,GACD,SAASC,gBAAgBd,KAAyB;IAChD,MAAMe,MAAMlB,UAAU;IACtB,MAAM,CAACmB,OAAOC,SAAS,GAAGtB,SAAkD;IAC5E,MAAM,CAACuB,OAAOC,SAAS,GAAGxB,SAAwB;IAClD,MAAMyB,UAAU5B,YAAY;QAC1B,IAAI;gBAMgB6B;YALlB,MAAM,EAAEC,QAAQ,EAAED,OAAO,EAAE,GAAG,MAAMN,IAAI,iBAAiB,CAAC;YAC1D,IAAI,CAACO,SAASC,EAAE,EAAE;;gBAChBJ,iBAASE,2BAAAA,QAASH,KAAK,mBAAI;gBAC3B;YACF;YACA,MAAMM,YAAYH,iBAAAA,QAAQL,KAAK,YAAbK,iBAAiB,EAAE;YACrCJ,SAAS,IAAIQ,IAAID,SAASE,GAAG,CAAC,CAACjB,OAAS;oBAACA,KAAKR,MAAM;oBAAEQ;iBAAK;YAC3DU,SAAS;QACX,EAAE,OAAOQ,QAAQ;YACfC,QAAQV,KAAK,CAACS;YACdR,SAAS;QACX;IACF,GAAG;QAACJ;KAAI;IACR,MAAMc,aAAaC,QAAQ9B,yBAAAA,MAAO6B,UAAU;IAC5CpC,UAAU;QACR,IAAIoC,YAAY,KAAKT;IACvB,GAAG;QAACS;QAAYT;KAAQ;IACxB,OAAO;QAAEJ;QAAOE;QAAOE;IAAQ;AACjC;AAEA,wEAAwE,GACxE,SAASW,SAASC,KAA6C;IAC7D,MAAM,EAAEhC,KAAK,EAAEC,MAAM,EAAE,GAAG+B;IAC1B,MAAMC,OAAOlC,gBAAgBC,OAAOC;IACpC,MAAMiC,OAAOlC,MAAMmC,QAAQ,CAAClC;IAC5B,OAAOgC,qBAAO,KAAC/D;QAAQ+D,MAAMA;kBAAOC;uBAAkB;kBAAGA;;AAC3D;AACAH,SAASK,WAAW,GAAG;AAEvB,sFAAsF,GACtF,SAASC,YAAYL,KAIpB;IACC,MAAM,EAAEhC,KAAK,EAAEsC,MAAM,EAAE/B,MAAM,EAAE,GAAGyB;IAClC,IAAI,CAACzB,QAAQ;QACX,qBACE,KAAClB;YAAWkD,SAAQ;YAAUC,OAAM;sBACjC;;IAGP;IACA,MAAM7B,YAAYX,MAAMyC,KAAK,CAACC,MAAM,CAClC,CAACC,OAASrC,WAAWC,QAAQoC,KAAKC,EAAE,EAAEN,OAAOM,EAAE,MAAM;IAEvD,MAAMC,QAAQ7C,MAAMyC,KAAK,CAACC,MAAM,CAC9B,CAACC,OAASrC,WAAWC,QAAQoC,KAAKC,EAAE,EAAEN,OAAOM,EAAE,MAAM;IAEvD,qBACE,MAAC7D;QAAM+D,SAAS;;0BACd,KAACzD;gBAAWkD,SAAQ;0BACjB5B,UAAUoC,MAAM,KAAK,IAClB,mCACApC,UAAUe,GAAG,CAAC,CAACiB,MAAMK,sBACnB,MAACzD;;4BACEyD,QAAQ,IAAI,OAAO;0CACpB,KAACjB;gCAAS/B,OAAOA;gCAAOC,QAAQ0C,KAAKC,EAAE;;;uBAF1BD,KAAKC,EAAE;;YAM7BC,MAAME,MAAM,GAAG,kBACd,KAAC1D;gBAAWkD,SAAQ;gBAAUC,OAAM;0BACjC,CAAC,qBAAqB,EAAEK,MAAMnB,GAAG,CAAC,CAACiB,OAASA,KAAKT,IAAI,EAAEe,IAAI,CAAC,MAAM,GAAG,CAAC,GACrE;iBAEF;;;AAGV;AACAZ,YAAYD,WAAW,GAAG;AAU1B;;;;;;;;;;CAUC,GACD,SAASc,kBAAkBlB,KAA6B;IACtD,MAAM,EAAEM,MAAM,EAAEtC,KAAK,EAAEO,MAAM,EAAE4C,OAAO,EAAEC,WAAW,EAAE,GAAGpB;IACxD,MAAMqB,YAAY5E;IAClB,MAAMsC,MAAMlB,UAAU;IACtB,MAAMyD,YAAYhB,OAAOiB,KAAK,KAAK;IACnC,MAAM,CAACtD,QAAQuD,UAAU,GAAG7D,SAAS;QACnC,MAAM8D,OAAOzD,MAAM0D,YAAY;QAC/B,OAAOD,QAAQnD,WAAWC,QAAQkD,MAAMnB,OAAOM,EAAE,MAAM,cAAca,OAAO;IAC9E;IACA,MAAM,CAACE,QAAQC,UAAU,GAAGjE,SAAS;IACrC,MAAM,CAACkE,MAAMC,QAAQ,GAAGnE,SAAS;IACjC,MAAM,CAACuB,OAAOC,SAAS,GAAGxB,SAAwB;IAElD,MAAM,EAAEoE,MAAMC,QAAQ,EAAEzD,QAAQ0D,WAAW,EAAE,GAAGvF,uBAC9C,IACE4E,aAAarD,SACTzB,kBACEc,WAAW+D,WAAW,SAASpD,QAAQ,UACvCH,uBAEF,MACN;QAACuD;QAAWC;QAAWrD;KAAO,EAC9B;QAAEiE,SAAS;IAAM;IAEnB,MAAM,EAAEC,MAAMC,QAAQ,EAAEC,WAAWC,cAAc,EAAE,GAAG/F,gBACpDyF,UACAlE;IAEF,MAAMyE,cAAc7E,QAClB,IACE,CAAC0E,mBAAAA,WAAY,EAAE,EACZ1B,MAAM,CAAC,CAAC8B,OAAc,CAACA,KAAKC,UAAU,EACtC/C,GAAG,CAAC,CAAC8C;gBAEUA;mBAFK;gBACnB5B,IAAI4B,KAAKE,GAAG;gBACZxC,MAAOyC,QAAOH,oBAAAA,KAAKpC,WAAW,YAAhBoC,oBAAoB,IAAII,IAAI,MAAMJ,KAAKE,GAAG;YAC1D;WACCG,IAAI,CAAC,CAACC,GAAqBC,IAC1BD,EAAE5C,IAAI,CAAC8C,aAAa,CAACD,EAAE7C,IAAI,IAEjC;QAACkC;KAAS;IAEZ,MAAMa,eAAe3B,aAAaxB,QAAQ7B,WAAWgE,gBAAgB;IAErE,MAAMiB,aACJpD,QAAQ7B,WAAY,CAAA,CAACqD,aAAaxB,QAAQ6B,OAAM,KAAM,CAACE,QAAQ,CAACoB;IAElE,MAAME,gBAAgB;QACpB,IAAI,CAACD,YAAY;QACjBpB,QAAQ;QACR3C,SAAS;QACT,IAAI;YACF,MAAM,EAAEG,QAAQ,EAAED,OAAO,EAAE,GAAG,MAAMN,IAAI,kBAAkB;gBACxDd;gBACAO,UAAU8B,OAAOM,EAAE;eACfe,SAAS;gBAAEA;YAAO,IAAI,CAAC;YAE7B,IAAI,CAACrC,SAASC,EAAE,EAAE;;gBAChBJ,iBAASE,2BAAAA,QAASH,KAAK,mBAAI;gBAC3B;YACF;YACAkC,YAAY/B,SAAmCpB;QACjD,EAAE,OAAO0B,QAAQ;YACfC,QAAQV,KAAK,CAACS;YACdR,SAAS;QACX,SAAU;YACR2C,QAAQ;QACV;IACF;IAEA,MAAMsB,aAAa,CAACxC;QAClB,MAAMyC,WAAW/E,WAAWC,QAAQqC,IAAIN,OAAOM,EAAE;QACjD,IAAIyC,aAAa,aAAa,OAAO;QACrC,IAAIA,aAAa,SAAS,OAAO;QACjC,OAAO;IACT;IAEA,qBACE,MAACtG;QAAM+D,SAAS;QAAGwC,IAAI;YAAEC,OAAO;YAAKC,GAAG;QAAE;;0BACxC,KAACnG;gBAAWkD,SAAQ;0BAAM,CAAC,SAAS,EAAED,OAAOmD,KAAK,CAAC,CAAC,CAAC;;0BACrD,KAACpG;gBAAWkD,SAAQ;gBAAQC,OAAM;0BAC/BF,OAAOoD,WAAW;;0BAErB,KAACtG;gBACCuG,MAAM;gBACNC,MAAK;gBACLC,OAAM;gBACNC,OAAO7F;gBACP8F,UAAU,CAACC;oBACTxC,UAAUwC,MAAMC,MAAM,CAACH,KAAK;oBAC5BlC,UAAU;oBACVzC,SAAS;gBACX;gBACA+E,UAAUrC;gBACVsC,YACEnG,MAAMyC,KAAK,CAACM,MAAM,KAAK,IACnB,wCACA;gBAENqD,SAAS;0BAERpG,MAAMyC,KAAK,CAACf,GAAG,CAAC,CAACiB,qBAChB,KAAC7D;wBAECgH,OAAOnD,KAAKC,EAAE;wBACdsD,UAAU5F,WAAWC,QAAQoC,KAAKC,EAAE,EAAEN,OAAOM,EAAE,MAAM;kCAEpD,GAAGD,KAAKT,IAAI,GAAGkD,WAAWzC,KAAKC,EAAE,GAAG;uBAJhCD,KAAKC,EAAE;;YAQjBU,0BACC,KAAClE;gBACCuG,MAAM;gBACNC,MAAK;gBACLC,OAAM;gBACNC,OAAOnC;gBACPoC,UAAU,CAACC,QAAUpC,UAAUoC,MAAMC,MAAM,CAACH,KAAK;gBACjDI,UAAU,CAACjG,UAAU4D,QAAQoB;gBAC7BkB,YACE,CAAClG,SACG,kEACAgF,eACE,8BACAV,YAAYxB,MAAM,KAAK,IACrB,iCACAuB,iBACE,CAAC,kBAAkB,EAAExE,oBAAoB,OAAO,CAAC,GACjD;gBAEZsG,SAAS;0BAER7B,YAAY7C,GAAG,CAAC,CAAC8C,qBAChB,KAAC1F;wBAAuBgH,OAAOtB,KAAK5B,EAAE;kCACnC4B,KAAKtC,IAAI;uBADGsC,KAAK5B,EAAE;iBAKxB;0BACJ,KAACvD;gBAAWkD,SAAQ;gBAAUC,OAAM;0BACjC,sEACC;;YAEHtB,sBACC,KAAC7B;gBAAWkD,SAAQ;gBAAQC,OAAM;0BAC/BtB;iBAED;0BACJ,MAACnC;gBAAMsH,WAAU;gBAAMvD,SAAS;;kCAC9B,KAAClE;wBACC2D,SAAQ;wBACRC,OAAM;wBACN0D,UAAU,CAAChB;wBACXoB,SAAS,IAAM,KAAKnB;kCAEnBtB,OAAO,gBAAgB;;kCAE1B,KAACjF;wBAAO0H,SAASnD;wBAAS+C,UAAUrC;kCACjC;;;;;;AAKX;AACAX,kBAAkBd,WAAW,GAAG;AAMhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASmE,YAAYvE,KAAuB;QAmHFhE;IAlH/C,MAAM,EAAEwI,GAAG,EAAE,GAAGxE;IAChB,MAAMhC,QAAQJ;IACd,MAAM,EAAE6G,eAAe,EAAE,GAAGnI;IAC5B,MAAMiC,SAASO,gBAAgBd;IAC/B,MAAM0G,gBAAgBnG,OAAOa,OAAO;IACpC,MAAM,CAACuF,YAAYC,cAAc,GAAGjH,SAAiC;IACrE,MAAM,CAACkH,aAAaC,eAAe,GAAGnH,SAG5B;IAEV,MAAMoH,WAAWjJ,iBAAiB0I,KAAK;IACvC,MAAMtB,aAAapD,QAAQ9B,yBAAAA,MAAO6B,UAAU,KAAKkF;IAEjD,MAAMC,kBAAkBxH,YACtB,CAACyH,QAAgChH;;QAC/B2G,cAAc;QACdE,eAAe;YAAE7G;YAAQiC,MAAM+E,OAAO/E,IAAI;QAAC;QAC3CuE,gBACE,CAAC,WAAW,EAAEQ,OAAO/E,IAAI,CAAC,KAAK,UAAElC,yBAAAA,MAAOmC,QAAQ,CAAClC,0BAAWA,QAAQ,EACpE;YAAEsC,SAAS;YAAW2E,SAAS;QAAM;QAEvC,KAAKR;IACP,GACA;QAACD;QAAiBzG;QAAO0G;KAAc;IAGzC,wEAAwE;IACxE,wEAAwE;IACxE,IAAI,CAAC1G,OAAO,OAAO;IAEnB,MAAMmH,kBAAkBN,cAAc9G,gBAAgBC,OAAO6G,YAAY5G,MAAM,IAAI;IACnF,qBACE,MAAC9B;QACCiJ,QAAQ;QACRC,MAAMpJ,eAAe,eAAe;YAAEqJ,QAAQ;QAAW;QACzDC,cAAc;QACdC,cAAc;QACdC,iBAAgB;;0BAEhB,MAAC1I;gBAAM+D,SAAS;;kCACd,KAACzD;wBAAWkD,SAAQ;wBAAQC,OAAM;kCAC/B,uEACC,oEACA;;oBAEHqE,4BACC,KAAClI;wBACC+I,UAAS;wBACTvE,SAAS,IAAM2D,eAAe;wBAC9Ba,QACER,kBACE,yDAAyD;wBACzD,yDAAyD;sCACzD,KAACvI;4BACCgJ,WAAW1J;2BACN;4BACH2J,kBAAkB;4BAClBC,cAAc;wBAChB;4BACA7F,MAAMkF;4BACNvB,MAAK;4BACLpD,OAAM;sCAEL;8BAEDuF;kCAGL,CAAC,WAAW,EAAElB,YAAY3E,IAAI,CAAC,KAAK,EAAElC,MAAMmC,QAAQ,CAAC0E,YAAY5G,MAAM,EAAE,CAAC,CAAC;yBAE5E;oBACHM,OAAOW,KAAK,iBACX,KAAC7B;wBAAWkD,SAAQ;wBAAUC,OAAM;kCACjCjC,OAAOW,KAAK;yBAEb;kCACJ,MAAC7C;wBAAYuH,MAAK;;0CAChB,KAAC1G;0CACC,cAAA,MAACC;;sDACC,KAACF;sDAAW;;sDACZ,KAACA;sDAAW;;sDACZ,KAACA;4CAAU+I,OAAM;;;;;0CAGrB,KAAChJ;0CACEjB,mBAAmB2D,GAAG,CAAC,CAACY,uBACvB,MAACnD;wCAAyB8I,KAAK;;0DAC7B,MAAChJ;gDAAUqG,IAAI;oDAAE4C,UAAU;gDAAI;;kEAC7B,KAAC7I;wDAAWkD,SAAQ;kEAASD,OAAOmD,KAAK;;kEACzC,KAACpG;wDAAWkD,SAAQ;wDAAUC,OAAM;kEACjCF,OAAOoD,WAAW;;;;0DAGvB,KAACzG;0DACC,cAAA,KAACoD;oDAAYrC,OAAOA;oDAAOsC,QAAQA;oDAAQ/B,QAAQA,OAAOS,KAAK;;;0DAEjE,KAAC/B;gDAAU+I,OAAM;gDAAQ1C,IAAI;oDAAEC,OAAO;gDAAI;0DACxC,cAAA,MAAC3G;oDACCgH,MAAK;oDACLrD,SAAQ;oDACR2D,UAAU,CAAChB;oDACXoB,SAAS,IAAMM,cAActE;;wDAE5B;sEACD,KAAClE;sEAAQ,CAAC,CAAC,EAAEkE,OAAOmD,KAAK,EAAE;;;;;;uCAlBlBnD,OAAOM,EAAE;;;;oBAyB7B,CAACmE,yBACA,KAAC1H;wBAAWkD,SAAQ;wBAAUC,OAAM;kCACjC,CAAC,iCAAiC,GAAExE,4BAAAA,yBAAyB,sBAAzBA,4BAAuC,MAAM,CAAC,CAAC,GAClF;yBAEF;oBACHgC,MAAM6B,UAAU,IAAI7B,MAAMyC,KAAK,CAACM,MAAM,KAAK,kBAC1C,KAAC1D;wBAAWkD,SAAQ;wBAAQC,OAAM;kCAC/B;yBAED;;;0BAEN,KAAC3D;gBAAOyI,QAAO;gBAAQa,MAAMrG,QAAQ6E;gBAAaxD,SAAS,IAAMyD,cAAc;0BAC5ED,2BACC,KAACzD;oBACCZ,QAAQqE;oBACR3G,OAAOA;oBACPO,QAAQA,OAAOS,KAAK;oBACpBmC,SAAS,IAAMyD,cAAc;oBAC7BxD,aAAa4D;qBAEb;;;;AAIZ;AACAT,YAAYnE,WAAW,GAAG;AAE1B,eAAemE,YAAW"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/recipes-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n planLabelGrantingFeature,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { pluginRecordListHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AppLink, CardDisplay, SrOnly } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Drawer,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection } from 'firebase/firestore'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type {\n CrmRecipeInstallResult,\n CrmRecipeSiteStatus,\n} from '../constants/api-routes'\nimport { type CrmOrgMount, useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmApi } from './use-crm-api'\n\n/** How many of a site's forms the picker offers before it says it stopped. */\nconst FORM_OPTION_CEILING = 100\n\n/**\n * The site's list of actions — where an installed action lives and is\n * edited — at the address the plugin that keeps actions publishes, or `null`\n * for a site whose subdomain the mount could not answer or a workspace where\n * no plugin keeps actions, which is named and not linked.\n */\nfunction siteActionsHref(mount: CrmOrgMount, hostId: string): string | null {\n const subdomain = mount.siteSubdomain(hostId)\n return subdomain\n ? pluginRecordListHref('action', { orgSlug: mount.orgSlug, host: subdomain })\n : null\n}\n\n/**\n * Where a site stands with one recipe, read off the stamps the status route\n * answered. `maybe` is the third answer and the honest one: the site\n * carries actions saved before the stamp existed, any of which could have\n * started from the recipe, so the card neither lists the site as carrying\n * it nor offers the install as if to a blank site. `unread` is the answer\n * before the route has answered, or for a site it did not name.\n */\ntype SiteStanding = 'installed' | 'maybe' | 'clear' | 'unread'\n\nfunction standingOf(\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null,\n hostId: string,\n recipeId: CrmActionRecipeId,\n): SiteStanding {\n const site = status?.get(hostId)\n if (!site) return 'unread'\n if (site.installed.includes(recipeId)) return 'installed'\n return site.unstamped > 0 ? 'maybe' : 'clear'\n}\n\n/**\n * The stamps of every site of the org, read once the site list is known\n * and again after each install, so the card reports what the documents\n * say rather than what it last did.\n */\nfunction useRecipeStatus(mount: CrmOrgMount | null) {\n const api = useCrmApi(null)\n const [sites, setSites] = useState<Map<string, CrmRecipeSiteStatus> | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refresh = useCallback(async () => {\n try {\n const { response, payload } = await api('recipe-status', {})\n if (!response.ok) {\n setError(payload?.error ?? 'Which sites carry a recipe could not be read.')\n return\n }\n const answered = (payload.sites ?? []) as CrmRecipeSiteStatus[]\n setSites(new Map(answered.map((site) => [site.hostId, site])))\n setError(null)\n } catch (caught) {\n console.error(caught)\n setError('Which sites carry a recipe could not be read.')\n }\n }, [api])\n const hostsReady = Boolean(mount?.hostsReady)\n useEffect(() => {\n if (hostsReady) void refresh()\n }, [hostsReady, refresh])\n return { sites, error, refresh }\n}\n\n/** A site's name, linked into its Actions page when the link is known. */\nfunction SiteLink(props: { mount: CrmOrgMount; hostId: string }) {\n const { mount, hostId } = props\n const href = siteActionsHref(mount, hostId)\n const name = mount.siteName(hostId)\n return href ? <AppLink href={href}>{name}</AppLink> : <>{name}</>\n}\nSiteLink.displayName = 'SiteLink'\n\n/** One recipe's \"Installed on\" cell: the sites that carry it, and the ones that may. */\nfunction InstalledOn(props: {\n mount: CrmOrgMount\n recipe: CrmActionRecipe\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n}) {\n const { mount, recipe, status } = props\n if (!status) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reading…'}\n </Typography>\n )\n }\n const installed = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'installed',\n )\n const maybe = mount.hosts.filter(\n (host) => standingOf(status, host.id, recipe.id) === 'maybe',\n )\n return (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\">\n {installed.length === 0\n ? 'Not installed on any site yet.'\n : installed.map((host, index) => (\n <Fragment key={host.id}>\n {index > 0 ? ', ' : null}\n <SiteLink mount={mount} hostId={host.id} />\n </Fragment>\n ))}\n </Typography>\n {maybe.length > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`May already have it: ${maybe.map((host) => host.name).join(', ')} — ` +\n 'actions there were saved before recipes were tracked.'}\n </Typography>\n ) : null}\n </Stack>\n )\n}\nInstalledOn.displayName = 'InstalledOn'\n\ninterface RecipeInstallFormProps {\n recipe: CrmActionRecipe\n mount: CrmOrgMount\n status: ReadonlyMap<string, CrmRecipeSiteStatus> | null\n onClose: () => void\n onInstalled: (result: CrmRecipeInstallResult, hostId: string) => void\n}\n\n/**\n * The install drawer's body: pick the site, pick the form a recipe that\n * needs one is keyed on, confirm. Mounted fresh on every opening — the\n * Drawer unmounts it when closed — so the picks start over each time from\n * the session's site, when that site does not already carry the recipe.\n *\n * The forms are read from the picked site only once a site is picked and\n * only for a recipe that needs one, so a reader installing the other three\n * pays no read. Live forms only: an archived form collects nothing, and a\n * recipe keyed on it would never fire.\n */\nfunction RecipeInstallForm(props: RecipeInstallFormProps) {\n const { recipe, mount, status, onClose, onInstalled } = props\n const firestore = useFirestore()\n const api = useCrmApi(null)\n const needsForm = recipe.needs === 'form'\n const [hostId, setHostId] = useState(() => {\n const pick = mount.createHostId\n return pick && standingOf(status, pick, recipe.id) !== 'installed' ? pick : ''\n })\n const [formId, setFormId] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const { data: formRead, status: formsStatus } = useFirestoreCollection<any>(\n () =>\n needsForm && hostId\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n FORM_OPTION_CEILING,\n )\n : null,\n [firestore, needsForm, hostId],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n FORM_OPTION_CEILING,\n )\n const formOptions = useMemo(\n () =>\n (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n ),\n [formDocs],\n )\n const formsLoading = needsForm && Boolean(hostId) && formsStatus === 'loading'\n\n const canInstall =\n Boolean(hostId) && (!needsForm || Boolean(formId)) && !busy && !formsLoading\n\n const handleInstall = async () => {\n if (!canInstall) return\n setBusy(true)\n setError(null)\n try {\n const { response, payload } = await api('recipe-install', {\n hostId,\n recipeId: recipe.id,\n ...(formId ? { formId } : {}),\n })\n if (!response.ok) {\n setError(payload?.error ?? 'The recipe could not be installed.')\n return\n }\n onInstalled(payload as CrmRecipeInstallResult, hostId)\n } catch (caught) {\n console.error(caught)\n setError('The recipe could not be installed.')\n } finally {\n setBusy(false)\n }\n }\n\n const siteSuffix = (id: string) => {\n const standing = standingOf(status, id, recipe.id)\n if (standing === 'installed') return ' — installed'\n if (standing === 'maybe') return ' — may already have it'\n return ''\n }\n\n return (\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{`Install “${recipe.title}”`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={hostId}\n onChange={(event) => {\n setHostId(event.target.value)\n setFormId('')\n setError(null)\n }}\n disabled={busy}\n helperText={\n mount.hosts.length === 0\n ? 'This organization has no sites yet.'\n : 'The action is written into this site’s Automation → Actions.'\n }\n fullWidth\n >\n {mount.hosts.map((host) => (\n <MenuItem\n key={host.id}\n value={host.id}\n disabled={standingOf(status, host.id, recipe.id) === 'installed'}\n >\n {`${host.name}${siteSuffix(host.id)}`}\n </MenuItem>\n ))}\n </TextField>\n {needsForm ? (\n <TextField\n select\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n disabled={!hostId || busy || formsLoading}\n helperText={\n !hostId\n ? 'Pick the site first; the recipe is keyed on one of its forms.'\n : formsLoading\n ? 'Loading the site’s forms…'\n : formOptions.length === 0\n ? 'This site has no live forms.'\n : formsTruncated\n ? `Showing the first ${FORM_OPTION_CEILING} forms.`\n : 'Contacts this form makes are tagged with its name.'\n }\n fullWidth\n >\n {formOptions.map((form) => (\n <MenuItem key={form.id} value={form.id}>\n {form.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'The action is created enabled, exactly as the recipe defines it. ' +\n 'Change anything afterwards from the site’s Automation → Actions page.'}\n </Typography>\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canInstall}\n onClick={() => void handleInstall()}\n >\n {busy ? 'Installing…' : 'Install'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nRecipeInstallForm.displayName = 'RecipeInstallForm'\n\nexport interface RecipesCardProps {\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Recipes\" — the CRM's ready-made automations, installed on a site from\n * the organization (AGL-2639).\n *\n * ## Why this card exists at the org level and nowhere else\n *\n * Automation is a site feature: a site's Actions page has a Recipes menu\n * that opens the editor prefilled, and the person saves what they see.\n * The org hub mounts over every site with no editor of its own, so it had\n * no recipes at all, and an org running five sites installed \"Welcome a\n * new lead\" five times by hand. This card is the org's door: every recipe,\n * the sites that already carry it, and Install, which writes the action\n * into the site the reader picks through the server — the server stamps\n * the recipe, refuses a site outside the org, and refuses a second copy.\n * Under a site the section does not mount it; the site's own menu is the\n * right door there.\n *\n * ## \"Installed on\" reads the stamp, and says when it cannot\n *\n * The status route answers, per site, the recipes a live action is stamped\n * with and how many live actions carry no stamp. A site with any of the\n * latter is named as one that MAY already have the recipe rather than\n * listed as clear: those actions were saved before the stamp existed and\n * the card cannot tell a hand-built welcome from an installed one.\n *\n * ## Creation opens a drawer\n *\n * Install is a button per row; the picks — the site, and for Tag by form\n * one of that site's forms — live in a drawer, never above the list.\n */\nexport function RecipesCard(props: RecipesCardProps) {\n const { org } = props\n const mount = useCrmOrgMount()\n const { enqueueSnackbar } = useSnackbar()\n const status = useRecipeStatus(mount)\n const refreshStatus = status.refresh\n const [installing, setInstalling] = useState<CrmActionRecipe | null>(null)\n const [lastInstall, setLastInstall] = useState<{\n hostId: string\n name: string\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const canInstall = Boolean(mount?.hostsReady) && entitled\n\n const handleInstalled = useCallback(\n (result: CrmRecipeInstallResult, hostId: string) => {\n setInstalling(null)\n setLastInstall({ hostId, name: result.name })\n enqueueSnackbar(\n `Installed “${result.name}” on ${mount?.siteName(hostId) ?? hostId}`,\n { variant: 'success', persist: false },\n )\n void refreshStatus()\n },\n [enqueueSnackbar, mount, refreshStatus],\n )\n\n // Mounted by the section only beneath the org hub's provider; the hooks\n // above run either way, so the render order never depends on the mount.\n if (!mount) return null\n\n const lastInstallHref = lastInstall ? siteActionsHref(mount, lastInstall.hostId) : null\n return (\n <CardDisplay\n header={'Recipes'}\n help={pluginDocsHelp('crmSettings', { anchor: '#recipes' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Ready-made automations for the CRM. Install one on a site and the ' +\n 'action is written into that site’s Automation → Actions as the ' +\n 'recipe defines it, where it can be edited like any other action.'}\n </Typography>\n {lastInstall ? (\n <Alert\n severity=\"success\"\n onClose={() => setLastInstall(null)}\n action={\n lastInstallHref ? (\n // A link dressed as a button: the anchor carries its own\n // href, so it keeps the link role a navigation door has.\n <Button\n component={AppLink as any}\n {...({\n componentVariant: 'naked',\n nativeButton: false,\n } as any)}\n href={lastInstallHref}\n size=\"small\"\n color=\"inherit\"\n >\n {'Open Automation → Actions'}\n </Button>\n ) : undefined\n }\n >\n {`Installed “${lastInstall.name}” on ${mount.siteName(lastInstall.hostId)}.`}\n </Alert>\n ) : null}\n {status.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {status.error}\n </Typography>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Recipe'}</TableCell>\n <TableCell>{'Installed on'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {CRM_ACTION_RECIPES.map((recipe) => (\n <TableRow key={recipe.id} hover>\n <TableCell sx={{ maxWidth: 360 }}>\n <Typography variant=\"body2\">{recipe.title}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {recipe.description}\n </Typography>\n </TableCell>\n <TableCell>\n <InstalledOn mount={mount} recipe={recipe} status={status.sites} />\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 120 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!canInstall}\n onClick={() => setInstalling(recipe)}\n >\n {'Install'}\n <SrOnly>{` ${recipe.title}`}</SrOnly>\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n {!entitled ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`The actions builder requires the ${planLabelGrantingFeature('actions') ?? 'Pro'} ` +\n 'plan — see Billing to upgrade.'}\n </Typography>\n ) : null}\n {mount.hostsReady && mount.hosts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nowhere to install a recipe.'}\n </Typography>\n ) : null}\n </Stack>\n <Drawer anchor=\"right\" open={Boolean(installing)} onClose={() => setInstalling(null)}>\n {installing ? (\n <RecipeInstallForm\n recipe={installing}\n mount={mount}\n status={status.sites}\n onClose={() => setInstalling(null)}\n onInstalled={handleInstalled}\n />\n ) : null}\n </Drawer>\n </CardDisplay>\n )\n}\nRecipesCard.displayName = 'RecipesCard'\n\nexport default RecipesCard\n"],"names":["checkEntitlement","CRM_ACTION_RECIPES","planLabelGrantingFeature","pluginDocsHelp","pluginRecordListHref","AppLink","CardDisplay","SrOnly","ScrollTable","useSnackbar","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","Alert","Button","Drawer","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","collection","Fragment","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmApi","FORM_OPTION_CEILING","siteActionsHref","mount","hostId","subdomain","siteSubdomain","orgSlug","host","standingOf","status","recipeId","site","get","installed","includes","unstamped","useRecipeStatus","api","sites","setSites","error","setError","refresh","payload","response","ok","answered","Map","map","caught","console","hostsReady","Boolean","SiteLink","props","href","name","siteName","displayName","InstalledOn","recipe","variant","color","hosts","filter","id","maybe","spacing","length","index","join","RecipeInstallForm","onClose","onInstalled","firestore","needsForm","needs","setHostId","pick","createHostId","formId","setFormId","busy","setBusy","data","formRead","formsStatus","idField","rows","formDocs","truncated","formsTruncated","formOptions","form","archivedAt","$id","String","trim","sort","a","b","localeCompare","formsLoading","canInstall","handleInstall","siteSuffix","standing","sx","width","p","title","description","select","size","label","value","onChange","event","target","disabled","helperText","fullWidth","direction","onClick","RecipesCard","org","enqueueSnackbar","refreshStatus","installing","setInstalling","lastInstall","setLastInstall","entitled","handleInstalled","result","persist","lastInstallHref","header","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","action","component","componentVariant","nativeButton","undefined","align","hover","maxWidth","open"],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CrmOrgDoc } from '../hooks/use-crm-scope';
|
|
2
|
+
import { type CrmShareChip, type CrmSharingObject } from '../model/crm-sharing';
|
|
3
|
+
/**
|
|
4
|
+
* The chip a list row or a record header shows when this site sees the
|
|
5
|
+
* record only because it was shared with it: "Shared by Dana".
|
|
6
|
+
*/
|
|
7
|
+
export declare function CrmShareChipView(props: {
|
|
8
|
+
chip: CrmShareChip | null;
|
|
9
|
+
org: CrmOrgDoc;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
export declare namespace CrmShareChipView {
|
|
12
|
+
var displayName: string;
|
|
13
|
+
}
|
|
14
|
+
export interface RecordSharingCardProps {
|
|
15
|
+
object: CrmSharingObject;
|
|
16
|
+
id: string;
|
|
17
|
+
record: Readonly<Record<string, unknown>> | null | undefined;
|
|
18
|
+
/** The mounted site, or `null` at the organization level. */
|
|
19
|
+
hostId: string | null;
|
|
20
|
+
orgId: string | null;
|
|
21
|
+
org: CrmOrgDoc;
|
|
22
|
+
/** The viewing site's consent-group sites, for "shared with this site". */
|
|
23
|
+
viewingHostIds?: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* SHARING — where this record is visible, and why (AGL-3336).
|
|
27
|
+
*
|
|
28
|
+
* One line for the sites that hold it (its consent group, or the site that
|
|
29
|
+
* created it), then one per hand share, naming who shared it and when, with
|
|
30
|
+
* the one-click unshare, and one per sharing rule. The held line has no
|
|
31
|
+
* unshare: visibility a site holds is not the sharing's to take away. A
|
|
32
|
+
* rule's line is removed by editing the rule, in Settings.
|
|
33
|
+
*
|
|
34
|
+
* The header's "Share with sites…" is an org manager's, and so is the
|
|
35
|
+
* unshare; every other reader sees the lines and nothing to press. For a
|
|
36
|
+
* reader on a site that sees the record only through a read-only share,
|
|
37
|
+
* the card says so, since the page's edits will be refused.
|
|
38
|
+
*/
|
|
39
|
+
export declare function RecordSharingCard(props: RecordSharingCardProps): import("react").JSX.Element;
|
|
40
|
+
export declare namespace RecordSharingCard {
|
|
41
|
+
var displayName: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* "Share with sites…" on a list's bulk bar (AGL-3336): the same dialog as
|
|
45
|
+
* the record page, over the selection. Drawn for an org manager only.
|
|
46
|
+
*/
|
|
47
|
+
export declare function CrmBulkShareButton(props: {
|
|
48
|
+
object: CrmSharingObject;
|
|
49
|
+
ids: readonly string[];
|
|
50
|
+
hostId: string | null;
|
|
51
|
+
orgId: string | null;
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
}): import("react").JSX.Element;
|
|
54
|
+
export declare namespace CrmBulkShareButton {
|
|
55
|
+
var displayName: string;
|
|
56
|
+
}
|
|
57
|
+
export default RecordSharingCard;
|