@wildo-ai/presets-components 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/.native.tsbuildinfo +1 -0
- package/dist/esm/components/actions/action-bar.d.ts.map +1 -1
- package/dist/esm/components/actions/action-bar.js +5 -2
- package/dist/esm/components/actions/action-bar.js.map +1 -1
- package/dist/esm/components/ai-elements/chat/suggestion.d.ts.map +1 -1
- package/dist/esm/components/ai-elements/chat/suggestion.js +8 -1
- package/dist/esm/components/ai-elements/chat/suggestion.js.map +1 -1
- package/dist/esm/components/ai-elements/decision/approval.d.ts +108 -13
- package/dist/esm/components/ai-elements/decision/approval.d.ts.map +1 -1
- package/dist/esm/components/ai-elements/decision/approval.js +60 -10
- package/dist/esm/components/ai-elements/decision/approval.js.map +1 -1
- package/dist/esm/components/ai-elements/graph/graph-runtime.d.ts +3 -1
- package/dist/esm/components/ai-elements/graph/graph-runtime.d.ts.map +1 -1
- package/dist/esm/components/ai-elements/graph/graph-runtime.js +3 -1
- package/dist/esm/components/ai-elements/graph/graph-runtime.js.map +1 -1
- package/dist/esm/components/app/app-layout-main-container.d.ts +9 -0
- package/dist/esm/components/app/app-layout-main-container.d.ts.map +1 -1
- package/dist/esm/components/app/app-layout-main-container.js +17 -23
- package/dist/esm/components/app/app-layout-main-container.js.map +1 -1
- package/dist/esm/components/app/app-layout-main-container.labels.d.ts.map +1 -1
- package/dist/esm/components/app/app-layout-main-container.labels.js +27 -0
- package/dist/esm/components/app/app-layout-main-container.labels.js.map +1 -1
- package/dist/esm/components/billing/billing-plan-change-preview.d.ts.map +1 -1
- package/dist/esm/components/billing/billing-plan-change-preview.js +5 -2
- package/dist/esm/components/billing/billing-plan-change-preview.js.map +1 -1
- package/dist/esm/components/billing/billing-plan-change-preview.labels.d.ts.map +1 -1
- package/dist/esm/components/billing/billing-plan-change-preview.labels.js +3 -0
- package/dist/esm/components/billing/billing-plan-change-preview.labels.js.map +1 -1
- package/dist/esm/components/billing/billing-pricing-table.d.ts +1 -1
- package/dist/esm/components/billing/billing-pricing-table.d.ts.map +1 -1
- package/dist/esm/components/billing/billing-pricing-table.js +8 -4
- package/dist/esm/components/billing/billing-pricing-table.js.map +1 -1
- package/dist/esm/components/billing/billing-pricing-table.labels.d.ts.map +1 -1
- package/dist/esm/components/billing/billing-pricing-table.labels.js +7 -1
- package/dist/esm/components/billing/billing-pricing-table.labels.js.map +1 -1
- package/dist/esm/components/billing/billing-usage-dashboard.d.ts.map +1 -1
- package/dist/esm/components/billing/billing-usage-dashboard.js +6 -3
- package/dist/esm/components/billing/billing-usage-dashboard.js.map +1 -1
- package/dist/esm/components/billing/billing-usage-dashboard.labels.d.ts.map +1 -1
- package/dist/esm/components/billing/billing-usage-dashboard.labels.js +2 -1
- package/dist/esm/components/billing/billing-usage-dashboard.labels.js.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-enum.d.ts.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-enum.js +18 -4
- package/dist/esm/components/data-display/formatters/display-field-enum.js.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-file.d.ts.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-file.js +13 -14
- package/dist/esm/components/data-display/formatters/display-field-file.js.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-file.labels.d.ts.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-file.labels.js +1 -0
- package/dist/esm/components/data-display/formatters/display-field-file.labels.js.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-number.d.ts.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-number.js +6 -1
- package/dist/esm/components/data-display/formatters/display-field-number.js.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-number.labels.d.ts.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-number.labels.js +5 -2
- package/dist/esm/components/data-display/formatters/display-field-number.labels.js.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-string.d.ts +1 -1
- package/dist/esm/components/data-display/formatters/display-field-string.d.ts.map +1 -1
- package/dist/esm/components/data-display/formatters/display-field-string.js +51 -12
- package/dist/esm/components/data-display/formatters/display-field-string.js.map +1 -1
- package/dist/esm/components/data-display/untrusted-html-content.d.ts +55 -0
- package/dist/esm/components/data-display/untrusted-html-content.d.ts.map +1 -0
- package/dist/esm/components/data-display/untrusted-html-content.js +67 -0
- package/dist/esm/components/data-display/untrusted-html-content.js.map +1 -0
- package/dist/esm/components/form/chromes/form-primitive.labels.d.ts.map +1 -1
- package/dist/esm/components/form/chromes/form-primitive.labels.js +9 -0
- package/dist/esm/components/form/chromes/form-primitive.labels.js.map +1 -1
- package/dist/esm/components/form/combobox.d.ts.map +1 -1
- package/dist/esm/components/form/combobox.js +10 -10
- package/dist/esm/components/form/combobox.js.map +1 -1
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.adapter.d.ts +42 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.adapter.d.ts.map +1 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.adapter.js +120 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.adapter.js.map +1 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.d.ts +16 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.d.ts.map +1 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.js +8 -0
- package/dist/esm/components/form/controls/address/providers/google-places-address-lookup.js.map +1 -0
- package/dist/esm/components/navigation/app-level/activity-bar.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/activity-bar.js +2 -2
- package/dist/esm/components/navigation/app-level/activity-bar.js.map +1 -1
- package/dist/esm/components/navigation/app-level/menu-user-org.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/menu-user-org.js +10 -4
- package/dist/esm/components/navigation/app-level/menu-user-org.js.map +1 -1
- package/dist/esm/components/navigation/app-level/menu-user-org.labels.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/menu-user-org.labels.js +7 -0
- package/dist/esm/components/navigation/app-level/menu-user-org.labels.js.map +1 -1
- package/dist/esm/components/navigation/app-level/menubar.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/menubar.js +6 -6
- package/dist/esm/components/navigation/app-level/menubar.js.map +1 -1
- package/dist/esm/components/navigation/app-level/navigation-back.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/navigation-back.js +27 -9
- package/dist/esm/components/navigation/app-level/navigation-back.js.map +1 -1
- package/dist/esm/components/navigation/app-level/sidebar.js +3 -3
- package/dist/esm/components/navigation/app-level/sidebar.js.map +1 -1
- package/dist/esm/components/navigation/app-level/status-bar.js +2 -2
- package/dist/esm/components/navigation/app-level/status-bar.js.map +1 -1
- package/dist/esm/components/navigation/app-level/toolbar.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/toolbar.js +2 -2
- package/dist/esm/components/navigation/app-level/toolbar.js.map +1 -1
- package/dist/esm/components/navigation/app-level/utility-panel.js +2 -2
- package/dist/esm/components/navigation/app-level/utility-panel.js.map +1 -1
- package/dist/esm/components/navigation/breadcrumb.js +1 -1
- package/dist/esm/components/navigation/breadcrumb.js.map +1 -1
- package/dist/esm/components/sections/stepper.d.ts.map +1 -1
- package/dist/esm/components/sections/stepper.js +5 -3
- package/dist/esm/components/sections/stepper.js.map +1 -1
- package/dist/esm/config/preset-navigation.d.ts.map +1 -1
- package/dist/esm/config/preset-navigation.js +23 -2
- package/dist/esm/config/preset-navigation.js.map +1 -1
- package/dist/esm/config/resources/agent-work.resource-presets.d.ts +4 -0
- package/dist/esm/config/resources/agent-work.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/agent-work.resource-presets.js +115 -0
- package/dist/esm/config/resources/agent-work.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/api-key-rotation-window.d.ts +22 -0
- package/dist/esm/config/resources/api-key-rotation-window.d.ts.map +1 -0
- package/dist/esm/config/resources/api-key-rotation-window.js +45 -0
- package/dist/esm/config/resources/api-key-rotation-window.js.map +1 -0
- package/dist/esm/config/resources/application.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/application.resource-presets.js +147 -61
- package/dist/esm/config/resources/application.resource-presets.js.map +1 -1
- package/dist/esm/config/resources/billing.resource-presets.d.ts +4 -3
- package/dist/esm/config/resources/billing.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/billing.resource-presets.js +240 -111
- package/dist/esm/config/resources/billing.resource-presets.js.map +1 -1
- package/dist/esm/config/resources/conversation-threads.resource-presets.d.ts +4 -0
- package/dist/esm/config/resources/conversation-threads.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/conversation-threads.resource-presets.js +105 -0
- package/dist/esm/config/resources/conversation-threads.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/guidance.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/guidance.resource-presets.js +18 -11
- package/dist/esm/config/resources/guidance.resource-presets.js.map +1 -1
- package/dist/esm/config/resources/identity.resource-presets.d.ts +0 -13
- package/dist/esm/config/resources/identity.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/identity.resource-presets.js +48 -16
- package/dist/esm/config/resources/identity.resource-presets.js.map +1 -1
- package/dist/esm/config/resources/inbound-deliveries.resource-presets.d.ts +6 -0
- package/dist/esm/config/resources/inbound-deliveries.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/inbound-deliveries.resource-presets.js +112 -0
- package/dist/esm/config/resources/inbound-deliveries.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/inbound-email.resource-presets.d.ts +4 -0
- package/dist/esm/config/resources/inbound-email.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/inbound-email.resource-presets.js +182 -0
- package/dist/esm/config/resources/inbound-email.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/index.d.ts.map +1 -1
- package/dist/esm/config/resources/index.js +9 -2
- package/dist/esm/config/resources/index.js.map +1 -1
- package/dist/esm/config/resources/legal-holds.resource-presets.d.ts +4 -0
- package/dist/esm/config/resources/legal-holds.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/legal-holds.resource-presets.js +76 -0
- package/dist/esm/config/resources/legal-holds.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/organizations.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/organizations.resource-presets.js +139 -36
- package/dist/esm/config/resources/organizations.resource-presets.js.map +1 -1
- package/dist/esm/config/resources/resource.config.utils.d.ts +20 -0
- package/dist/esm/config/resources/resource.config.utils.d.ts.map +1 -1
- package/dist/esm/config/resources/resource.config.utils.js +20 -0
- package/dist/esm/config/resources/resource.config.utils.js.map +1 -1
- package/dist/esm/config/resources/retention-enforcement.resource-presets.d.ts +5 -0
- package/dist/esm/config/resources/retention-enforcement.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/retention-enforcement.resource-presets.js +74 -0
- package/dist/esm/config/resources/retention-enforcement.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/security.resource-presets.d.ts +3 -1
- package/dist/esm/config/resources/security.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/security.resource-presets.js +563 -133
- package/dist/esm/config/resources/security.resource-presets.js.map +1 -1
- package/dist/esm/config/resources/service-providers.resource-presets.d.ts +4 -0
- package/dist/esm/config/resources/service-providers.resource-presets.d.ts.map +1 -0
- package/dist/esm/config/resources/service-providers.resource-presets.js +321 -0
- package/dist/esm/config/resources/service-providers.resource-presets.js.map +1 -0
- package/dist/esm/config/resources/webhooks.resource-presets.d.ts +4 -3
- package/dist/esm/config/resources/webhooks.resource-presets.d.ts.map +1 -1
- package/dist/esm/config/resources/webhooks.resource-presets.js +76 -25
- package/dist/esm/config/resources/webhooks.resource-presets.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/registration/register-common-enums.d.ts.map +1 -1
- package/dist/esm/registration/register-common-enums.js +15 -0
- package/dist/esm/registration/register-common-enums.js.map +1 -1
- package/dist/esm/registration/register-provider-components.d.ts.map +1 -1
- package/dist/esm/registration/register-provider-components.js +6 -0
- package/dist/esm/registration/register-provider-components.js.map +1 -1
- package/dist/esm/themes/dense.theme.d.ts.map +1 -1
- package/dist/esm/themes/dense.theme.js +22 -2
- package/dist/esm/themes/dense.theme.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -6
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { RelatedResourceSections, resourceUIBehavior, resourceUIBehaviorFrom, polymorphicResourceUIBehavior, StandardIconReference, SummaryFlow, SummaryMeta, SummaryTitle, } from '@wildo-ai/saas-frontend-lib';
|
|
3
|
-
import {
|
|
2
|
+
import { RelatedResourceSections, ResourceHeaderTitle, resourceUIBehavior, resourceUIBehaviorFrom, polymorphicResourceUIBehavior, StandardIconReference, SummaryFlow, SummaryMeta, SummaryTitle, unresolvedLabel, } from '@wildo-ai/saas-frontend-lib';
|
|
3
|
+
import { useDisplayTemplateContext, DisplayField, DisplayLayout, FormField, FormLayout, ObjectScope, Section, SummaryLayout, } from '@wildo-ai/saas-frontend-lib';
|
|
4
4
|
import { BadgeSemanticVariant, BooleanDisplayMode, BooleanEditMode, BooleanLabellingValue, SectionMode, DisplayChromeMode, EditChromeMode, EnumDisplayMode, FormFieldComponent, NumberDisplayMode, StringDisplayMode, } from '@wildo-ai/zod-decorators';
|
|
5
|
-
import { Package, Layers, Puzzle, Gauge, CircleDot, Coins, ShoppingBag, Hash, Layers2, TrendingUp, Package as PackageIcon, Calendar, CalendarDays, Ticket, KeyRound, Gift, Handshake, Percent, Banknote, Clock, UserCheck, CheckCircle2, TimerOff, CircleX, Building2, User, Ban, AlertTriangle, Wallet, Loader, OctagonX, PauseCircle, Sparkles, Square, Repeat, Infinity, FileText, BarChart3, BadgeCheck, PowerOff, ShoppingCart, ExternalLink, Info, CirclePlay, ArrowRightLeft, PackagePlus, PackageMinus, CirclePlus, } from 'lucide-react';
|
|
6
|
-
import { CoreResourceType as R, CoreResourceOperation as Op, Promotion_Operations, BillingAccount_Operations, BillingSubscription_Operations, CreditPool_Operations, productsResourceConfiguration_InitializationFactory as productsFactory, pricesResourceConfiguration_InitializationFactory as pricesFactory, referralsResourceConfiguration_InitializationFactory as referralsFactory, promotionsResourceConfiguration_InitializationFactory as promotionsFactory, billingAccountsPolymorphicFactories, billingSubscriptionsPolymorphicFactories, creditPoolsPolymorphicFactories, invoiceRefsPolymorphicFactories, usageRecordsPolymorphicFactories, ProductSchema, PriceSchema, ReferralSchema, PromotionSchema, BillingAccountBaseSchema, BillingSubscriptionBaseSchema, CreditPoolSchema, InvoiceRefSchema, UsageRecordSchema, ReferralStatus, SubscriptionStatus, InvoiceStatus, BillingScopeType, PromotionDuration, ProductType, PricingModel, BillingInterval, PromotionType, DiscountType, } from '@wildo-ai/saas-models';
|
|
5
|
+
import { Package, Layers, Puzzle, Gauge, CircleDot, Coins, ShoppingBag, Hash, Layers2, TrendingUp, Package as PackageIcon, Calendar, CalendarDays, Ticket, KeyRound, Gift, Handshake, Percent, Banknote, Clock, UserCheck, CheckCircle2, TimerOff, CircleX, Building2, User, Ban, AlertTriangle, Wallet, Loader, OctagonX, PauseCircle, Sparkles, Square, Repeat, Infinity, FileText, BarChart3, BadgeCheck, PowerOff, ShoppingCart, ExternalLink, Info, CirclePlay, CalendarX2, ArrowRightLeft, PackagePlus, PackageMinus, CirclePlus, } from 'lucide-react';
|
|
6
|
+
import { CoreResourceType as R, CoreResourceOperation as Op, Promotion_Operations, BillingAccount_Operations, Referral_Operations, BillingSubscription_Operations, CreditPool_Operations, productsResourceConfiguration_InitializationFactory as productsFactory, pricesResourceConfiguration_InitializationFactory as pricesFactory, referralsResourceConfiguration_InitializationFactory as referralsFactory, promotionsResourceConfiguration_InitializationFactory as promotionsFactory, billingAccountsPolymorphicFactories, billingSubscriptionsPolymorphicFactories, creditPoolsPolymorphicFactories, invoiceRefsPolymorphicFactories, usageRecordsPolymorphicFactories, ProductSchema, PriceSchema, ReferralSchema, PromotionSchema, BillingAccountBaseSchema, BillingSubscriptionBaseSchema, CreditPoolSchema, InvoiceRefSchema, UsageRecordSchema, ReferralStatus, SubscriptionStatus, InvoiceStatus, BillingScopeType, PromotionDuration, ProductType, ProductAvailability, PricingModel, BillingInterval, PromotionType, DiscountType, } from '@wildo-ai/saas-models';
|
|
7
|
+
import { LabellingTypes } from '@wildo-ai/presets-components-models';
|
|
7
8
|
import { notAddressable, apiOnly } from './resource.config.utils.js';
|
|
8
9
|
export function buildBillingMainPresets() {
|
|
9
10
|
/**
|
|
@@ -25,38 +26,61 @@ export function buildBillingMainPresets() {
|
|
|
25
26
|
return {
|
|
26
27
|
[R.PRODUCTS]: resourceUIBehavior(productsFactory, ProductSchema, ({ schema, schemaShape, opsVariantResolver }) => ({
|
|
27
28
|
icon: Package,
|
|
28
|
-
emptyState: { primaryAction: Op.CREATE },
|
|
29
29
|
layout: {
|
|
30
|
+
// No `tax` section any more: it was a heading over one field. The tax display mode is a commercial
|
|
31
|
+
// term of the product, so it sits in `overview` with the other terms.
|
|
30
32
|
refs: {
|
|
31
|
-
sections: ['overview', '
|
|
33
|
+
sections: ['overview', 'purchase', 'behavior', 'visibility'],
|
|
32
34
|
},
|
|
35
|
+
// A catalogue entry is configuration: settings rows on read and edit, bare values on a card.
|
|
33
36
|
defaultChromeMode: {
|
|
34
|
-
display: DisplayChromeMode.
|
|
35
|
-
summary: DisplayChromeMode.
|
|
37
|
+
display: DisplayChromeMode.SETTINGS_ROW,
|
|
38
|
+
summary: DisplayChromeMode.HIDDEN,
|
|
36
39
|
edit: EditChromeMode.SETTINGS_ROW,
|
|
37
40
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
// The identity line: the product's name (the record's label, its word — #1851), what KIND of product it is, and whether new
|
|
42
|
+
// checkouts can buy it. None of the three is repeated below.
|
|
43
|
+
header: () => (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-field", children: [_jsx(ResourceHeaderTitle, {}), _jsx(DisplayField, { name: "type", hideLabel: true, hideDescription: true, className: "w-auto" }), _jsx(DisplayField, { name: "isActive", hideLabel: true, hideDescription: true, className: "w-auto" })] })),
|
|
44
|
+
/*
|
|
45
|
+
* The form carries what can be CHANGED. `key`, `type`, `grantedFeatures` and `grantedLimits` are
|
|
46
|
+
* `isNotEditable` — they are set when the product is materialized from its definition — so on a form
|
|
47
|
+
* they were four read-only rows restating the read page. The first editable term takes the focus.
|
|
48
|
+
*/
|
|
49
|
+
edit: () => (_jsxs(FormLayout, { children: [_jsxs(Section, { name: "overview", appearance: SectionMode.CARD, children: [_jsx(FormField, { name: "planTier", autoFocus: true }), _jsx(FormField, { name: "planTierOrder" }), _jsx(FormField, { name: "requiredPlanTiers" }), _jsx(FormField, { name: "targetScopes" }), _jsx(FormField, { name: "taxDisplayMode" })] }), _jsx(Section, { name: "purchase", appearance: SectionMode.CARD, children: _jsxs(ObjectScope, { name: "purchaseConfig", children: [_jsx(FormField, { name: "maxQuantityPerPurchase" }), _jsx(FormField, { name: "creditsPerUnit" }), _jsx(FormField, { name: "creditPoolKey" })] }) }), _jsx(Section, { name: "behavior", children: _jsx(FormField, { name: "behaviorPolicy" }) }), _jsxs(Section, { name: "visibility", children: [_jsx(FormField, { name: "isPublic" }), _jsx(FormField, { name: "isActive" }), _jsx(FormField, { name: "sortOrder" }), _jsx(FormField, { name: "metadata" })] })] })),
|
|
50
|
+
/*
|
|
51
|
+
* Read: the header names the product, its type and its availability, so the body starts at the terms.
|
|
52
|
+
* No read section carries a description — "Does not set legal tax rates", "Backend enforcement lives in
|
|
53
|
+
* services" were notes to an engineer, on a page an operator reads. The purchase and behaviour blocks only
|
|
54
|
+
* exist for some product types, so they render only when the product carries them; an add-on used to show
|
|
55
|
+
* a "Purchase settings" card of three dashes.
|
|
56
|
+
*/
|
|
42
57
|
display: () => {
|
|
43
|
-
const { data
|
|
44
|
-
|
|
58
|
+
const { data } = useDisplayTemplateContext();
|
|
59
|
+
const purchase = data?.purchaseConfig;
|
|
60
|
+
const purchaseHas = (key) => purchase?.[key] !== undefined && purchase?.[key] !== null;
|
|
61
|
+
const hasPurchase = purchaseHas('maxQuantityPerPurchase') || purchaseHas('creditsPerUnit') || purchaseHas('creditPoolKey');
|
|
62
|
+
const behavior = data?.behaviorPolicy;
|
|
63
|
+
const hasBehavior = !!behavior && Object.keys(behavior).length > 0;
|
|
64
|
+
return (_jsxs(DisplayLayout, { children: [_jsxs(Section, { name: "overview", appearance: SectionMode.CARD, hideDescription: true, children: [_jsx(DisplayField, { name: "key" }), _jsx(DisplayField, { name: "planTier" }), _jsx(DisplayField, { name: "planTierOrder" }), _jsx(DisplayField, { name: "requiredPlanTiers" }), _jsx(DisplayField, { name: "targetScopes" }), _jsx(DisplayField, { name: "grantedFeatures" }), _jsx(DisplayField, { name: "grantedLimits" }), _jsx(DisplayField, { name: "taxDisplayMode" })] }), hasPurchase && (_jsx(Section, { name: "purchase", appearance: SectionMode.CARD, hideDescription: true, children: _jsxs(ObjectScope, { name: "purchaseConfig", children: [purchaseHas('maxQuantityPerPurchase') && _jsx(DisplayField, { name: "maxQuantityPerPurchase" }), purchaseHas('creditsPerUnit') && _jsx(DisplayField, { name: "creditsPerUnit" }), purchaseHas('creditPoolKey') && _jsx(DisplayField, { name: "creditPoolKey" })] }) })), hasBehavior && (_jsx(Section, { name: "behavior", chromeMode: DisplayChromeMode.HIDDEN, hideDescription: true, children: _jsx(DisplayField, { name: "behaviorPolicy" }) })), _jsxs(Section, { name: "visibility", hideDescription: true, children: [_jsx(DisplayField, { name: "isPublic" }), _jsx(DisplayField, { name: "sortOrder" }), _jsx(DisplayField, { name: "isRecommended" }), _jsx(DisplayField, { name: "priceDisclosure" }), _jsx(DisplayField, { name: "availability" }), _jsx(DisplayField, { name: "metadata" })] }), _jsx(RelatedResourceSections, { config: { mode: SectionMode.TABS }, targets: [R.PRICES] })] }));
|
|
45
65
|
},
|
|
66
|
+
/*
|
|
67
|
+
* A catalogue card: the product's name, its key, the type badge, the tier. The title is the host-resolved name
|
|
68
|
+
* (the application's word for the product, #1851) — `name="key"` rendered the raw key where the word
|
|
69
|
+
* belonged. The key stays on the card as a meta value because an operator also identifies a product by it.
|
|
70
|
+
* Availability shows only when it is the EXCEPTION — nearly every product is active and public, and a
|
|
71
|
+
* checkmark on every card was a column of noise that hid the one product that is not. The record id is gone.
|
|
72
|
+
*/
|
|
46
73
|
summary: () => {
|
|
47
|
-
const { data
|
|
48
|
-
|
|
74
|
+
const { data } = useDisplayTemplateContext();
|
|
75
|
+
const product = data;
|
|
76
|
+
return (_jsxs(SummaryLayout, { children: [_jsx(SummaryTitle, {}), _jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "key" }), _jsx(DisplayField, { name: "type" }), _jsx(DisplayField, { name: "planTier" }), product?.isActive === false && _jsx(DisplayField, { name: "isActive" }), product?.isPublic === false && _jsx(DisplayField, { name: "isPublic" }), product?.availability === ProductAvailability.UPCOMING && _jsx(DisplayField, { name: "availability" }), product?.isRecommended === true && _jsx(DisplayField, { name: "isRecommended" })] })] }));
|
|
49
77
|
},
|
|
50
78
|
},
|
|
51
79
|
fields: {
|
|
52
80
|
_id: schemaShape._id.isHidden(),
|
|
53
|
-
key: schemaShape.key.stringUI({
|
|
54
|
-
display: { displayMode: StringDisplayMode.COPY, showDescription: true },
|
|
55
|
-
edit: { showDescription: true },
|
|
56
|
-
}),
|
|
81
|
+
key: schemaShape.key.stringUI({ display: { displayMode: StringDisplayMode.COPY } }),
|
|
57
82
|
type: schemaShape.type.enumUI({
|
|
58
|
-
display: { displayMode: EnumDisplayMode.BADGE
|
|
59
|
-
edit: { showDescription: true },
|
|
83
|
+
display: { displayMode: EnumDisplayMode.BADGE },
|
|
60
84
|
values: {
|
|
61
85
|
[ProductType.PLAN]: { color: BadgeSemanticVariant.INFO, icon: Layers },
|
|
62
86
|
[ProductType.ADDON]: { color: BadgeSemanticVariant.SECONDARY, icon: Puzzle },
|
|
@@ -66,57 +90,94 @@ export function buildBillingMainPresets() {
|
|
|
66
90
|
[ProductType.ITEM]: { color: BadgeSemanticVariant.DEFAULT, icon: ShoppingBag },
|
|
67
91
|
},
|
|
68
92
|
}),
|
|
93
|
+
// Keeps its description on the FORM only: what a tier is for (plan comparison) is not obvious from the
|
|
94
|
+
// word. On a card the bare tier ("premium") beside the type badge read as a second product name, so the
|
|
95
|
+
// card keeps its label.
|
|
69
96
|
planTier: schemaShape.planTier.stringUI({
|
|
70
|
-
display: { displayMode: StringDisplayMode.TRUNCATE, truncateLength: 48,
|
|
71
|
-
|
|
72
|
-
}),
|
|
73
|
-
sortOrder: schemaShape.sortOrder.numberUI({
|
|
74
|
-
display: { displayMode: NumberDisplayMode.FORMATTED, showDescription: true },
|
|
97
|
+
display: { displayMode: StringDisplayMode.TRUNCATE, truncateLength: 48, hideWhenEmpty: true },
|
|
98
|
+
summaryOverride: { chromeMode: DisplayChromeMode.INLINE },
|
|
75
99
|
edit: { showDescription: true },
|
|
76
100
|
}),
|
|
101
|
+
planTierOrder: schemaShape.planTierOrder.numberUI({ display: { hideWhenEmpty: true } }),
|
|
102
|
+
requiredPlanTiers: schemaShape.requiredPlanTiers.arrayUI({ display: { hideWhenEmpty: true } }),
|
|
103
|
+
grantedFeatures: schemaShape.grantedFeatures.arrayUI({ display: { hideWhenEmpty: true } }),
|
|
104
|
+
grantedLimits: schemaShape.grantedLimits.objectUI({ display: { hideWhenEmpty: true } }),
|
|
105
|
+
sortOrder: schemaShape.sortOrder.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED } }),
|
|
106
|
+
// Read as a word ("Active" / "Inactive") because it stands alone in the header and, when inactive, on a
|
|
107
|
+
// card: a bare checkmark there says nothing about what is checked. The form keeps its description — what
|
|
108
|
+
// "active" governs (new checkouts only) is not obvious.
|
|
77
109
|
isActive: schemaShape.isActive.booleanUI({
|
|
78
110
|
labellingValue: BooleanLabellingValue.ACTIVE_INACTIVE,
|
|
79
|
-
display: { displayMode: BooleanDisplayMode.
|
|
111
|
+
display: { displayMode: BooleanDisplayMode.RAW },
|
|
80
112
|
edit: { editMode: BooleanEditMode.SWITCH, showDescription: true },
|
|
81
113
|
}),
|
|
114
|
+
// A bare "No" on a card would not say what is not; the card keeps the label. The form keeps the description
|
|
115
|
+
// (public = listed in self-serve catalogues).
|
|
82
116
|
isPublic: schemaShape.isPublic.booleanUI({
|
|
83
117
|
labellingValue: BooleanLabellingValue.YES_NO,
|
|
84
|
-
display: { displayMode: BooleanDisplayMode.CHECKMARK
|
|
118
|
+
display: { displayMode: BooleanDisplayMode.CHECKMARK },
|
|
119
|
+
summaryOverride: { chromeMode: DisplayChromeMode.INLINE },
|
|
85
120
|
edit: { editMode: BooleanEditMode.SWITCH, showDescription: true },
|
|
86
121
|
}),
|
|
87
|
-
|
|
122
|
+
// On a card only when true (see the summary above), and for the same reason as `isPublic` the card keeps the
|
|
123
|
+
// label: a bare checkmark beside the tier would not say WHAT is checked. Set by the catalogue sync, so there is
|
|
124
|
+
// no edit configuration.
|
|
125
|
+
isRecommended: schemaShape.isRecommended.booleanUI({
|
|
126
|
+
labellingValue: BooleanLabellingValue.YES_NO,
|
|
127
|
+
display: { displayMode: BooleanDisplayMode.CHECKMARK },
|
|
128
|
+
summaryOverride: { chromeMode: DisplayChromeMode.INLINE },
|
|
129
|
+
}),
|
|
130
|
+
taxDisplayMode: schemaShape.taxDisplayMode.enumUI({ display: { hideWhenEmpty: true }, edit: { tooltip: true } }),
|
|
131
|
+
metadata: schemaShape.metadata.objectUI({ display: { hideWhenEmpty: true } }),
|
|
132
|
+
},
|
|
133
|
+
/*
|
|
134
|
+
* A product is named by the application's word for it (`billing_products.<key>.name`), never by its key (#1851).
|
|
135
|
+
* The key stays on the page as a copyable row. A product with no authored word answers its key, which LOOKS
|
|
136
|
+
* missing and gets authored. The section is declared so the registry loads it for every surface that names a
|
|
137
|
+
* product — a list row, the header, a subscription line's product reference, the breadcrumb of a price.
|
|
138
|
+
*/
|
|
139
|
+
getFrontendResourceLabel: ({ currentObject, t }) => {
|
|
140
|
+
const nameKey = `${LabellingTypes.BILLING_PRODUCTS}.${currentObject.key}.name`;
|
|
141
|
+
const name = t(nameKey);
|
|
142
|
+
return name === nameKey ? unresolvedLabel(currentObject.key) : name;
|
|
88
143
|
},
|
|
89
|
-
|
|
144
|
+
instanceLabelSections: [LabellingTypes.BILLING_PRODUCTS],
|
|
90
145
|
views: [],
|
|
91
146
|
})),
|
|
92
147
|
[R.PRICES]: resourceUIBehavior(pricesFactory, PriceSchema, ({ schema, schemaShape, opsVariantResolver }) => ({
|
|
93
148
|
icon: StandardIconReference.RECEIPT,
|
|
94
|
-
emptyState: { primaryAction: Op.CREATE },
|
|
95
149
|
layout: {
|
|
96
150
|
refs: { sections: ['pricing', 'tiers', 'tax', 'availability'] },
|
|
97
151
|
defaultChromeMode: {
|
|
98
|
-
display: DisplayChromeMode.
|
|
99
|
-
summary: DisplayChromeMode.
|
|
152
|
+
display: DisplayChromeMode.SETTINGS_ROW,
|
|
153
|
+
summary: DisplayChromeMode.HIDDEN,
|
|
100
154
|
edit: EditChromeMode.SETTINGS_ROW,
|
|
101
155
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
},
|
|
156
|
+
// The name of a price is its model and cadence (see `getFrontendResourceLabel`); the header adds whether it
|
|
157
|
+
// is the product's default and whether it is still sold, so neither is a body row.
|
|
158
|
+
header: () => (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-field", children: [_jsx(ResourceHeaderTitle, {}), _jsx(DisplayField, { name: "isActive", hideLabel: true, hideDescription: true, className: "w-auto" })] })),
|
|
159
|
+
edit: () => (_jsxs(FormLayout, { children: [_jsxs(Section, { name: "pricing", appearance: SectionMode.CARD, children: [_jsx(FormField, { name: "productId" }), _jsx(FormField, { name: "model", autoFocus: true }), _jsx(FormField, { name: "unitPrice" }), _jsx(FormField, { name: "interval" }), _jsx(FormField, { name: "intervalCount" })] }), _jsxs(Section, { name: "tiers", children: [_jsx(FormField, { name: "tiers" }), _jsx(FormField, { name: "packageSize" }), _jsx(FormField, { name: "usageAggregation" })] }), _jsx(Section, { name: "tax", children: _jsx(FormField, { name: "taxBehavior" }) }), _jsxs(Section, { name: "availability", chromeMode: EditChromeMode.INLINE, children: [_jsx(FormField, { name: "isDefault" }), _jsx(FormField, { name: "isActive" })] })] })),
|
|
160
|
+
/*
|
|
161
|
+
* Read: the amount first, then cadence. The product is not repeated — a price is read inside its product's
|
|
162
|
+
* page — and the model is the header. Tiers and tax only exist for some prices, so those rows hide when
|
|
163
|
+
* empty and their sections render only when they hold something; a flat monthly price used to show two
|
|
164
|
+
* headed blocks of dashes. No read section carries a description.
|
|
165
|
+
*/
|
|
106
166
|
display: () => {
|
|
107
|
-
const { data
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const { data, isLoading } = useDisplayTemplateContext();
|
|
112
|
-
return (_jsxs(SummaryLayout, { children: [_jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "model" }), _jsx(DisplayField, { name: "unitPrice" }), _jsx(DisplayField, { name: "interval" })] }), _jsx(DisplayField, { name: "_id", hideLabel: true, className: "text-muted-foreground text-xs font-mono truncate max-w-full" })] }));
|
|
167
|
+
const { data } = useDisplayTemplateContext();
|
|
168
|
+
const price = data;
|
|
169
|
+
const hasTiers = (price?.tiers?.length ?? 0) > 0 || price?.packageSize != null || price?.usageAggregation != null;
|
|
170
|
+
return (_jsxs(DisplayLayout, { children: [_jsxs(Section, { name: "pricing", appearance: SectionMode.CARD, hideTitle: true, hideDescription: true, children: [_jsx(DisplayField, { name: "unitPrice" }), _jsx(DisplayField, { name: "interval" }), _jsx(DisplayField, { name: "intervalCount" }), _jsx(DisplayField, { name: "isDefault" })] }), hasTiers && (_jsxs(Section, { name: "tiers", hideDescription: true, children: [_jsx(DisplayField, { name: "tiers" }), _jsx(DisplayField, { name: "packageSize" }), _jsx(DisplayField, { name: "usageAggregation" })] })), price?.taxBehavior != null && (_jsx(Section, { name: "tax", hideDescription: true, children: _jsx(DisplayField, { name: "taxBehavior" }) }))] }));
|
|
113
171
|
},
|
|
172
|
+
// A card inside the product's Prices tab: the amount, the cadence, the model — bare, each speaks for itself.
|
|
173
|
+
// The record id is gone.
|
|
174
|
+
summary: () => (_jsx(SummaryLayout, { children: _jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "unitPrice" }), _jsx(DisplayField, { name: "interval" }), _jsx(DisplayField, { name: "model" })] }) })),
|
|
114
175
|
},
|
|
115
176
|
fields: {
|
|
116
177
|
_id: schemaShape._id.isHidden(),
|
|
117
178
|
model: schemaShape.model.enumUI({
|
|
118
179
|
defaultColor: BadgeSemanticVariant.SECONDARY,
|
|
119
|
-
display: { displayMode: EnumDisplayMode.BADGE
|
|
180
|
+
display: { displayMode: EnumDisplayMode.BADGE },
|
|
120
181
|
edit: { showDescription: true },
|
|
121
182
|
values: {
|
|
122
183
|
[PricingModel.FLAT]: { color: BadgeSemanticVariant.SECONDARY, icon: Square },
|
|
@@ -126,48 +187,77 @@ export function buildBillingMainPresets() {
|
|
|
126
187
|
[PricingModel.PACKAGE]: { color: BadgeSemanticVariant.OUTLINE, icon: PackageIcon },
|
|
127
188
|
},
|
|
128
189
|
}),
|
|
190
|
+
// Absent on a one-time price, which is exactly what its absence means; the header and the card say it by
|
|
191
|
+
// not showing a cadence.
|
|
129
192
|
interval: schemaShape.interval.enumUI({
|
|
130
|
-
display: { displayMode: EnumDisplayMode.BADGE,
|
|
131
|
-
edit: { showDescription: true },
|
|
193
|
+
display: { displayMode: EnumDisplayMode.BADGE, hideWhenEmpty: true },
|
|
132
194
|
values: {
|
|
133
195
|
[BillingInterval.MONTH]: { color: BadgeSemanticVariant.SECONDARY, icon: Calendar },
|
|
134
196
|
[BillingInterval.YEAR]: { color: BadgeSemanticVariant.INFO, icon: CalendarDays },
|
|
135
197
|
},
|
|
136
198
|
}),
|
|
137
199
|
intervalCount: schemaShape.intervalCount.numberUI({
|
|
138
|
-
display: { displayMode: NumberDisplayMode.FORMATTED,
|
|
200
|
+
display: { displayMode: NumberDisplayMode.FORMATTED, hideWhenEmpty: true },
|
|
139
201
|
edit: { showDescription: true },
|
|
140
202
|
}),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
203
|
+
tiers: schemaShape.tiers.arrayUI({ display: { hideWhenEmpty: true } }),
|
|
204
|
+
packageSize: schemaShape.packageSize.numberUI({ display: { hideWhenEmpty: true }, edit: { tooltip: true } }),
|
|
205
|
+
usageAggregation: schemaShape.usageAggregation.enumUI({ display: { hideWhenEmpty: true }, edit: { tooltip: true } }),
|
|
206
|
+
taxBehavior: schemaShape.taxBehavior.enumUI({ display: { hideWhenEmpty: true }, edit: { tooltip: true } }),
|
|
207
|
+
// A default price is the exception worth reading; "not the default" is not information.
|
|
144
208
|
isDefault: schemaShape.isDefault.booleanUI({
|
|
145
209
|
labellingValue: BooleanLabellingValue.YES_NO,
|
|
146
|
-
display: { displayMode: BooleanDisplayMode.CHECKMARK
|
|
210
|
+
display: { displayMode: BooleanDisplayMode.CHECKMARK },
|
|
147
211
|
edit: { editMode: BooleanEditMode.SWITCH, showDescription: true },
|
|
148
212
|
}),
|
|
213
|
+
// A word in the header, for the same reason as a product's.
|
|
149
214
|
isActive: schemaShape.isActive.booleanUI({
|
|
150
215
|
labellingValue: BooleanLabellingValue.ACTIVE_INACTIVE,
|
|
151
|
-
display: { displayMode: BooleanDisplayMode.
|
|
216
|
+
display: { displayMode: BooleanDisplayMode.RAW },
|
|
152
217
|
edit: { editMode: BooleanEditMode.SWITCH, showDescription: true },
|
|
153
218
|
}),
|
|
154
219
|
},
|
|
155
|
-
|
|
220
|
+
/*
|
|
221
|
+
* A price's name is its model and, when it recurs, its cadence — both in the application's own words. It used
|
|
222
|
+
* to join the raw wire values with an English "one-time", which no translator could reach
|
|
223
|
+
* (`display-text-comes-from-i18n.md`). A missing word answers the raw value, which LOOKS missing and gets
|
|
224
|
+
* authored. A one-time price is named by its model alone.
|
|
225
|
+
*/
|
|
226
|
+
getFrontendResourceLabel: ({ currentObject, t }) => {
|
|
227
|
+
const word = (key, raw) => {
|
|
228
|
+
const resolved = t(key);
|
|
229
|
+
return resolved === key ? raw : resolved;
|
|
230
|
+
};
|
|
231
|
+
const model = word(`${LabellingTypes.SYSTEM_ENUMS}.PricingModel.${currentObject.model}`, String(currentObject.model));
|
|
232
|
+
if (!currentObject.interval)
|
|
233
|
+
return model;
|
|
234
|
+
const interval = word(`${LabellingTypes.RESOURCES}.${R.PRICES}.fields.interval.enum.${currentObject.interval}`, String(currentObject.interval));
|
|
235
|
+
return `${model} · ${interval}`;
|
|
236
|
+
},
|
|
156
237
|
views: [],
|
|
157
238
|
})),
|
|
158
239
|
[R.REFERRALS]: resourceUIBehavior(referralsFactory, ReferralSchema, ({ schema, schemaShape, opsVariantResolver }) => ({
|
|
159
240
|
icon: StandardIconReference.SHARE,
|
|
160
|
-
emptyState: { primaryAction: Op.CREATE },
|
|
161
241
|
layout: {
|
|
242
|
+
// No sections: a referral is one code, its state, the two sides and what each earned. It is an immutable
|
|
243
|
+
// ledger row (no edit form), read in a list.
|
|
162
244
|
defaultChromeMode: {
|
|
163
|
-
display: DisplayChromeMode.
|
|
164
|
-
summary: DisplayChromeMode.
|
|
245
|
+
display: DisplayChromeMode.SETTINGS_ROW,
|
|
246
|
+
summary: DisplayChromeMode.HIDDEN,
|
|
165
247
|
edit: EditChromeMode.SETTINGS_ROW,
|
|
166
248
|
},
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
249
|
+
// The code is the record's name and the status its state; neither is repeated in the body.
|
|
250
|
+
header: () => (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-field", children: [_jsx(ResourceHeaderTitle, {}), _jsx(DisplayField, { name: "status", hideLabel: true, hideDescription: true, className: "w-auto" })] })),
|
|
251
|
+
// The two sides, when each moved, and what each earned. Every optional row hides when empty — a referral
|
|
252
|
+
// that has not been accepted yet has no referred side, and a column of dashes said nothing.
|
|
253
|
+
display: () => (_jsxs(DisplayLayout, { children: [_jsx(DisplayField, { name: "referrerScopeType" }), _jsx(DisplayField, { name: "referredScopeType" }), _jsx(DisplayField, { name: "expiresAt" }), _jsx(DisplayField, { name: "acceptedAt" }), _jsx(DisplayField, { name: "convertedAt" }), _jsx(DisplayField, { name: "referrerReward" }), _jsx(DisplayField, { name: "referrerRewardApplied" }), _jsx(DisplayField, { name: "referredReward" }), _jsx(DisplayField, { name: "referredRewardApplied" })] })),
|
|
254
|
+
/*
|
|
255
|
+
* A card: the code and its status badge. The two scope badges and the two dates keep their labels — a bare
|
|
256
|
+
* "Organization" beside a bare "User" does not say which side referred, and a bare date does not say what
|
|
257
|
+
* happened on it. The referrer's billing-account id is gone: in an organization's own referrals it is that
|
|
258
|
+
* organization, and an opaque id told the reader nothing.
|
|
259
|
+
*/
|
|
260
|
+
summary: () => (_jsxs(SummaryLayout, { children: [_jsx(SummaryTitle, { name: "referralCode" }), _jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "status" }), _jsx(DisplayField, { name: "referrerScopeType" }), _jsx(DisplayField, { name: "referredScopeType" })] }), _jsxs(SummaryMeta, { className: "text-muted-foreground text-xs", children: [_jsx(DisplayField, { name: "expiresAt" }), _jsx(DisplayField, { name: "convertedAt" })] })] })),
|
|
171
261
|
},
|
|
172
262
|
fields: {
|
|
173
263
|
_id: schemaShape._id.isHidden(),
|
|
@@ -184,18 +274,25 @@ export function buildBillingMainPresets() {
|
|
|
184
274
|
referralCode: schemaShape.referralCode.stringUI({ display: { displayMode: StringDisplayMode.COPY } }),
|
|
185
275
|
referrerScopeType: schemaShape.referrerScopeType.enumUI({
|
|
186
276
|
display: { displayMode: EnumDisplayMode.BADGE },
|
|
277
|
+
summaryOverride: { chromeMode: DisplayChromeMode.INLINE },
|
|
187
278
|
values: {
|
|
188
279
|
[BillingScopeType.ORGANIZATIONS]: { color: BadgeSemanticVariant.SECONDARY, icon: Building2 },
|
|
189
280
|
[BillingScopeType.USER_SELF]: { color: BadgeSemanticVariant.OUTLINE, icon: User },
|
|
190
281
|
},
|
|
191
282
|
}),
|
|
192
283
|
referredScopeType: schemaShape.referredScopeType.enumUI({
|
|
193
|
-
display: { displayMode: EnumDisplayMode.BADGE },
|
|
284
|
+
display: { displayMode: EnumDisplayMode.BADGE, hideWhenEmpty: true },
|
|
285
|
+
summaryOverride: { chromeMode: DisplayChromeMode.INLINE },
|
|
194
286
|
values: {
|
|
195
287
|
[BillingScopeType.ORGANIZATIONS]: { color: BadgeSemanticVariant.SECONDARY, icon: Building2 },
|
|
196
288
|
[BillingScopeType.USER_SELF]: { color: BadgeSemanticVariant.OUTLINE, icon: User },
|
|
197
289
|
},
|
|
198
290
|
}),
|
|
291
|
+
expiresAt: schemaShape.expiresAt.dateUI({ display: { hideWhenEmpty: true }, summaryOverride: { chromeMode: DisplayChromeMode.INLINE } }),
|
|
292
|
+
acceptedAt: schemaShape.acceptedAt.dateUI({ display: { hideWhenEmpty: true } }),
|
|
293
|
+
convertedAt: schemaShape.convertedAt.dateUI({ display: { hideWhenEmpty: true }, summaryOverride: { chromeMode: DisplayChromeMode.INLINE } }),
|
|
294
|
+
referrerReward: schemaShape.referrerReward.objectUI({ display: { hideWhenEmpty: true } }),
|
|
295
|
+
referredReward: schemaShape.referredReward.objectUI({ display: { hideWhenEmpty: true } }),
|
|
199
296
|
referrerRewardApplied: schemaShape.referrerRewardApplied.booleanUI({
|
|
200
297
|
labellingValue: BooleanLabellingValue.YES_NO,
|
|
201
298
|
display: { displayMode: BooleanDisplayMode.CHECKMARK },
|
|
@@ -206,45 +303,58 @@ export function buildBillingMainPresets() {
|
|
|
206
303
|
}),
|
|
207
304
|
},
|
|
208
305
|
getFrontendResourceLabel: ({ currentObject }) => currentObject.referralCode,
|
|
209
|
-
|
|
306
|
+
// #1802: the organization owner issues a code from the list, and withdraws one that has not converted.
|
|
307
|
+
views: [
|
|
308
|
+
[Referral_Operations.ISSUE, notAddressable({ iconOverride: CirclePlus })],
|
|
309
|
+
[Referral_Operations.CANCEL, notAddressable({ iconOverride: CircleX })],
|
|
310
|
+
],
|
|
210
311
|
})),
|
|
211
312
|
[R.PROMOTIONS]: resourceUIBehavior(promotionsFactory, PromotionSchema, ({ schema, schemaShape, opsVariantResolver }) => ({
|
|
212
313
|
icon: StandardIconReference.PROMOTION,
|
|
213
|
-
emptyState: { primaryAction: Op.CREATE },
|
|
214
314
|
layout: {
|
|
215
315
|
refs: { sections: ['overview', 'discount', 'duration', 'applicability', 'validity'] },
|
|
316
|
+
// An operator AUTHORS a promotion (a name, a description), so the form stacks; the read page is rows.
|
|
216
317
|
defaultChromeMode: {
|
|
217
|
-
display: DisplayChromeMode.
|
|
218
|
-
summary: DisplayChromeMode.
|
|
219
|
-
edit: EditChromeMode.
|
|
220
|
-
},
|
|
221
|
-
edit: () => (_jsxs(FormLayout, { children: [_jsxs(Section, { name: "overview", appearance: SectionMode.CARD, children: [_jsx(FormField, { name: "name" }), _jsx(FormField, { name: "description" }), _jsx(FormField, { name: "type" }), _jsx(FormField, { name: "code" })] }), _jsxs(Section, { name: "discount", children: [_jsx(FormField, { name: "discountType" }), _jsx(FormField, { name: "discountPercentage" }), _jsx(FormField, { name: "discountFixedAmount" })] }), _jsxs(Section, { name: "duration", children: [_jsx(FormField, { name: "duration" }), _jsx(FormField, { name: "durationInMonths" })] }), _jsxs(Section, { name: "applicability", children: [_jsx(FormField, { name: "applicableProducts" }), _jsx(FormField, { name: "maxRedemptions" }), _jsx(FormField, { name: "timesRedeemed" })] }), _jsxs(Section, { name: "validity", children: [_jsx(FormField, { name: "validFrom" }), _jsx(FormField, { name: "validUntil" }), _jsx(FormField, { name: "isActive" }), _jsx(FormField, { name: "metadata" })] })] })),
|
|
222
|
-
display: () => {
|
|
223
|
-
const { data, isLoading } = useDisplayTemplateContext();
|
|
224
|
-
return (_jsxs(DisplayLayout, { children: [_jsxs(Section, { name: "overview", appearance: SectionMode.CARD, children: [_jsx(DisplayField, { name: "name" }), _jsx(DisplayField, { name: "description" }), _jsx(DisplayField, { name: "type" }), _jsx(DisplayField, { name: "code" })] }), _jsxs(Section, { name: "discount", children: [_jsx(DisplayField, { name: "discountType" }), _jsx(DisplayField, { name: "discountPercentage" }), _jsx(DisplayField, { name: "discountFixedAmount" })] }), _jsxs(Section, { name: "duration", children: [_jsx(DisplayField, { name: "duration" }), _jsx(DisplayField, { name: "durationInMonths" })] }), _jsxs(Section, { name: "applicability", children: [_jsx(DisplayField, { name: "applicableProducts" }), _jsx(DisplayField, { name: "maxRedemptions" }), _jsx(DisplayField, { name: "timesRedeemed" })] }), _jsxs(Section, { name: "validity", children: [_jsx(DisplayField, { name: "validFrom" }), _jsx(DisplayField, { name: "validUntil" }), _jsx(DisplayField, { name: "isActive" }), _jsx(DisplayField, { name: "metadata" })] })] }));
|
|
225
|
-
},
|
|
226
|
-
summary: () => {
|
|
227
|
-
const { data, isLoading } = useDisplayTemplateContext();
|
|
228
|
-
return (_jsxs(SummaryLayout, { children: [_jsx(SummaryTitle, { name: "name" }), _jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "type" }), _jsx(DisplayField, { name: "discountType" })] }), _jsx(DisplayField, { name: "_id", hideLabel: true, className: "text-muted-foreground text-xs font-mono truncate max-w-full" })] }));
|
|
318
|
+
display: DisplayChromeMode.SETTINGS_ROW,
|
|
319
|
+
summary: DisplayChromeMode.HIDDEN,
|
|
320
|
+
edit: EditChromeMode.STACKED,
|
|
229
321
|
},
|
|
322
|
+
// The name, the kind of promotion and whether it still accepts redemptions — the three things the body no
|
|
323
|
+
// longer repeats.
|
|
324
|
+
header: () => (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-field", children: [_jsx(ResourceHeaderTitle, {}), _jsx(DisplayField, { name: "type", hideLabel: true, hideDescription: true, className: "w-auto" }), _jsx(DisplayField, { name: "isActive", hideLabel: true, hideDescription: true, className: "w-auto" })] })),
|
|
325
|
+
/*
|
|
326
|
+
* The name first, focused; the description is prose, so a textarea. `timesRedeemed` is a counter the
|
|
327
|
+
* system advances (`isNotEditable`), so it is read on the record, not shown as a disabled input here. The
|
|
328
|
+
* discount and duration blocks keep their descriptions: which of the two amounts applies, and what
|
|
329
|
+
* "repeating" needs, are not obvious from the fields.
|
|
330
|
+
*/
|
|
331
|
+
edit: () => (_jsxs(FormLayout, { children: [_jsxs(Section, { name: "overview", appearance: SectionMode.CARD, children: [_jsx(FormField, { name: "name", autoFocus: true }), _jsx(FormField, { name: "description" }), _jsx(FormField, { name: "type" }), _jsx(FormField, { name: "code" })] }), _jsxs(Section, { name: "discount", children: [_jsx(FormField, { name: "discountType" }), _jsx(FormField, { name: "discountPercentage" }), _jsx(FormField, { name: "discountFixedAmount" })] }), _jsxs(Section, { name: "duration", children: [_jsx(FormField, { name: "duration" }), _jsx(FormField, { name: "durationInMonths" })] }), _jsxs(Section, { name: "applicability", children: [_jsx(FormField, { name: "applicableProducts" }), _jsx(FormField, { name: "maxRedemptions" })] }), _jsxs(Section, { name: "validity", children: [_jsx(FormField, { name: "validFrom" }), _jsx(FormField, { name: "validUntil" }), _jsx(FormField, { name: "isActive" }), _jsx(FormField, { name: "metadata" })] })] })),
|
|
332
|
+
/*
|
|
333
|
+
* Read: the header is the name, type and state. The first card is the promotion's own words and its code,
|
|
334
|
+
* with no heading — "Overview" over a description said nothing the description does not. No read section
|
|
335
|
+
* carries a description; those were notes about provider sync and API behaviour.
|
|
336
|
+
*/
|
|
337
|
+
display: () => (_jsxs(DisplayLayout, { children: [_jsxs(Section, { name: "overview", appearance: SectionMode.CARD, hideTitle: true, hideDescription: true, children: [_jsx(DisplayField, { name: "description" }), _jsx(DisplayField, { name: "code" })] }), _jsxs(Section, { name: "discount", hideDescription: true, children: [_jsx(DisplayField, { name: "discountType" }), _jsx(DisplayField, { name: "discountPercentage" }), _jsx(DisplayField, { name: "discountFixedAmount" })] }), _jsxs(Section, { name: "duration", hideDescription: true, children: [_jsx(DisplayField, { name: "duration" }), _jsx(DisplayField, { name: "durationInMonths" })] }), _jsxs(Section, { name: "applicability", hideDescription: true, children: [_jsx(DisplayField, { name: "applicableProducts" }), _jsx(DisplayField, { name: "maxRedemptions" }), _jsx(DisplayField, { name: "timesRedeemed" })] }), _jsxs(Section, { name: "validity", hideDescription: true, children: [_jsx(DisplayField, { name: "validFrom" }), _jsx(DisplayField, { name: "validUntil" }), _jsx(DisplayField, { name: "metadata" })] })] })),
|
|
338
|
+
// A card: the name, then the promotion type and the discount type as badges. The record id is gone.
|
|
339
|
+
summary: () => (_jsxs(SummaryLayout, { children: [_jsx(SummaryTitle, { name: "name" }), _jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "type" }), _jsx(DisplayField, { name: "discountType" })] })] })),
|
|
230
340
|
},
|
|
231
341
|
fields: {
|
|
232
342
|
_id: schemaShape._id.isHidden(),
|
|
233
343
|
name: schemaShape.name.stringUI({
|
|
234
|
-
display: { displayMode: StringDisplayMode.TRUNCATE, truncateLength: 64
|
|
235
|
-
edit: { showDescription: true },
|
|
344
|
+
display: { displayMode: StringDisplayMode.TRUNCATE, truncateLength: 64 },
|
|
236
345
|
}),
|
|
237
346
|
description: schemaShape.description.stringUI({
|
|
238
|
-
display: { displayMode: StringDisplayMode.MULTILINE,
|
|
239
|
-
edit: { component: FormFieldComponent.TEXTAREA
|
|
347
|
+
display: { displayMode: StringDisplayMode.MULTILINE, hideWhenEmpty: true },
|
|
348
|
+
edit: { component: FormFieldComponent.TEXTAREA },
|
|
240
349
|
}),
|
|
350
|
+
// Hidden when empty: a deal with no customer-facing code is a real kind of promotion, not a gap. The form
|
|
351
|
+
// keeps the description — what the code is for (typed at checkout) is not obvious.
|
|
241
352
|
code: schemaShape.code.stringUI({
|
|
242
|
-
display: { displayMode: StringDisplayMode.COPY,
|
|
353
|
+
display: { displayMode: StringDisplayMode.COPY, hideWhenEmpty: true },
|
|
243
354
|
edit: { showDescription: true },
|
|
244
355
|
}),
|
|
245
356
|
type: schemaShape.type.enumUI({
|
|
246
|
-
display: { displayMode: EnumDisplayMode.BADGE
|
|
247
|
-
edit: { showDescription: true },
|
|
357
|
+
display: { displayMode: EnumDisplayMode.BADGE },
|
|
248
358
|
values: {
|
|
249
359
|
[PromotionType.COUPON]: { color: BadgeSemanticVariant.SUCCESS, icon: Ticket },
|
|
250
360
|
[PromotionType.PROMOTION_CODE]: { color: BadgeSemanticVariant.INFO, icon: KeyRound },
|
|
@@ -253,44 +363,43 @@ export function buildBillingMainPresets() {
|
|
|
253
363
|
},
|
|
254
364
|
}),
|
|
255
365
|
discountType: schemaShape.discountType.enumUI({
|
|
256
|
-
display: { displayMode: EnumDisplayMode.BADGE
|
|
257
|
-
edit: { showDescription: true },
|
|
366
|
+
display: { displayMode: EnumDisplayMode.BADGE },
|
|
258
367
|
values: {
|
|
259
368
|
[DiscountType.PERCENTAGE]: { color: BadgeSemanticVariant.INFO, icon: Percent },
|
|
260
369
|
[DiscountType.FIXED_AMOUNT]: { color: BadgeSemanticVariant.SECONDARY, icon: Banknote },
|
|
261
370
|
},
|
|
262
371
|
}),
|
|
372
|
+
// Only one of the two amounts applies (per `discountType`); the other is empty and hides.
|
|
263
373
|
discountPercentage: schemaShape.discountPercentage.numberUI({
|
|
264
374
|
suffix: '%',
|
|
265
|
-
display: { displayMode: NumberDisplayMode.FORMATTED,
|
|
266
|
-
edit: { showDescription: true },
|
|
375
|
+
display: { displayMode: NumberDisplayMode.FORMATTED, hideWhenEmpty: true },
|
|
267
376
|
}),
|
|
377
|
+
discountFixedAmount: schemaShape.discountFixedAmount.moneyUI({ display: { hideWhenEmpty: true } }),
|
|
268
378
|
duration: schemaShape.duration.enumUI({
|
|
269
|
-
display: { displayMode: EnumDisplayMode.BADGE
|
|
270
|
-
edit: { showDescription: true },
|
|
379
|
+
display: { displayMode: EnumDisplayMode.BADGE },
|
|
271
380
|
values: {
|
|
272
381
|
[PromotionDuration.ONCE]: { color: BadgeSemanticVariant.SECONDARY, icon: CircleDot },
|
|
273
382
|
[PromotionDuration.REPEATING]: { color: BadgeSemanticVariant.INFO, icon: Repeat },
|
|
274
383
|
[PromotionDuration.FOREVER]: { color: BadgeSemanticVariant.SUCCESS, icon: Infinity },
|
|
275
384
|
},
|
|
276
385
|
}),
|
|
386
|
+
// Only a REPEATING promotion has a month count.
|
|
277
387
|
durationInMonths: schemaShape.durationInMonths.numberUI({
|
|
278
|
-
display: { displayMode: NumberDisplayMode.FORMATTED,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
edit: { showDescription: true },
|
|
288
|
-
}),
|
|
388
|
+
display: { displayMode: NumberDisplayMode.FORMATTED, hideWhenEmpty: true },
|
|
389
|
+
}),
|
|
390
|
+
// NOT hidden when empty, and neither are the redemption cap and the validity bounds: for these four an
|
|
391
|
+
// empty value MEANS something ("every product", "unlimited", "no start", "no end"). The dash is kept until
|
|
392
|
+
// those words are authored, rather than hiding a real answer.
|
|
393
|
+
maxRedemptions: schemaShape.maxRedemptions.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED } }),
|
|
394
|
+
timesRedeemed: schemaShape.timesRedeemed.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED } }),
|
|
395
|
+
// A word in the header ("Active" / "Inactive"); a bare checkmark beside the name said nothing about what
|
|
396
|
+
// was checked. The form keeps the description: deactivating stops NEW redemptions only.
|
|
289
397
|
isActive: schemaShape.isActive.booleanUI({
|
|
290
398
|
labellingValue: BooleanLabellingValue.ACTIVE_INACTIVE,
|
|
291
|
-
display: { displayMode: BooleanDisplayMode.
|
|
399
|
+
display: { displayMode: BooleanDisplayMode.RAW },
|
|
292
400
|
edit: { editMode: BooleanEditMode.SWITCH, showDescription: true },
|
|
293
401
|
}),
|
|
402
|
+
metadata: schemaShape.metadata.objectUI({ display: { hideWhenEmpty: true } }),
|
|
294
403
|
},
|
|
295
404
|
getFrontendResourceLabel: ({ currentObject }) => currentObject.name,
|
|
296
405
|
views: [
|
|
@@ -319,14 +428,16 @@ export function buildBillingMainPresets() {
|
|
|
319
428
|
display: { displayMode: BooleanDisplayMode.CHECKMARK },
|
|
320
429
|
}),
|
|
321
430
|
},
|
|
322
|
-
// A billing account has no user-authored name — it is the singleton
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
getFrontendResourceLabel: ({
|
|
431
|
+
// A billing account has no user-authored name — it is the singleton provider link for its scope — so it is
|
|
432
|
+
// named by its resource's own words (the READ title, "Billing account"), never by its raw `_id`. Whether it
|
|
433
|
+
// is active is its STATE, a badge, not part of the name; the name used to be English written in code
|
|
434
|
+
// ("Billing account (inactive)"), which no translator could reach. A missing word shows its key.
|
|
435
|
+
getFrontendResourceLabel: ({ t, resourceType }) => t(`${LabellingTypes.RESOURCES}.${resourceType}.operations.read.title`),
|
|
327
436
|
views: [
|
|
328
437
|
[BillingAccount_Operations.OPEN_CHECKOUT, notAddressable({ iconOverride: ShoppingCart })],
|
|
329
438
|
[BillingAccount_Operations.OPEN_PORTAL, notAddressable({ iconOverride: ExternalLink })],
|
|
439
|
+
// #1802: a new customer redeems a referral code on its own account.
|
|
440
|
+
[BillingAccount_Operations.REDEEM_REFERRAL, notAddressable({ iconOverride: Handshake })],
|
|
330
441
|
[BillingAccount_Operations.GET_STATE, notAddressable({ iconOverride: Info })],
|
|
331
442
|
// Billing accounts are Stripe-managed (portal/checkout) — generic UPDATE/DELETE
|
|
332
443
|
// are headless (no inline edit/delete UI).
|
|
@@ -416,7 +527,9 @@ export function buildBillingMainPresets() {
|
|
|
416
527
|
edit: { component: FormFieldComponent.TEXTAREA },
|
|
417
528
|
}),
|
|
418
529
|
},
|
|
419
|
-
|
|
530
|
+
// A subscription is named by the billing period it is in, in the viewer's date format. Its status is its state
|
|
531
|
+
// (the badge), and the raw status value was all the name used to say.
|
|
532
|
+
getFrontendResourceLabel: ({ currentObject, formatDate }) => `${formatDate(currentObject.currentPeriodStart)} – ${formatDate(currentObject.currentPeriodEnd)}`,
|
|
420
533
|
views: [
|
|
421
534
|
[BillingSubscription_Operations.CANCEL, notAddressable({ iconOverride: Ban })],
|
|
422
535
|
[BillingSubscription_Operations.RESUME, notAddressable({ iconOverride: CirclePlay })],
|
|
@@ -424,6 +537,8 @@ export function buildBillingMainPresets() {
|
|
|
424
537
|
[BillingSubscription_Operations.PREVIEW_PLAN_CHANGE, apiOnly()],
|
|
425
538
|
[BillingSubscription_Operations.ADD_ADDON, notAddressable({ iconOverride: PackagePlus })],
|
|
426
539
|
[BillingSubscription_Operations.REMOVE_ADDON, notAddressable({ iconOverride: PackageMinus })],
|
|
540
|
+
// Withdraws a plan change pending at the end of the period (#1868).
|
|
541
|
+
[BillingSubscription_Operations.CANCEL_PLAN_CHANGE, notAddressable({ iconOverride: CalendarX2 })],
|
|
427
542
|
// Subscriptions are managed via the custom ops above + Stripe — generic UPDATE
|
|
428
543
|
// is headless (no inline edit UI).
|
|
429
544
|
[Op.UPDATE, apiOnly()],
|
|
@@ -450,16 +565,24 @@ export function buildBillingMainPresets() {
|
|
|
450
565
|
},
|
|
451
566
|
summary: () => {
|
|
452
567
|
const { data, isLoading } = useDisplayTemplateContext();
|
|
453
|
-
return (_jsxs(SummaryLayout, { children: [_jsx(SummaryTitle, { name: "poolKey" }),
|
|
568
|
+
return (_jsxs(SummaryLayout, { children: [_jsx(SummaryTitle, {}), _jsxs(SummaryMeta, { children: [_jsx(DisplayField, { name: "poolKey" }), _jsx(DisplayField, { name: "available" }), _jsx(DisplayField, { name: "totalPurchased" }), _jsx(DisplayField, { name: "totalGranted" }), _jsx(DisplayField, { name: "totalConsumed" })] }), _jsx(DisplayField, { name: "billingAccountId", hideLabel: true, className: "text-muted-foreground text-xs font-mono truncate max-w-full" })] }));
|
|
454
569
|
},
|
|
455
570
|
},
|
|
456
571
|
fields: {
|
|
457
572
|
_id: schema.shape._id.isHidden(),
|
|
458
573
|
available: schema.shape.available.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED } }),
|
|
459
574
|
totalPurchased: schema.shape.totalPurchased.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED } }),
|
|
575
|
+
totalGranted: schema.shape.totalGranted.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED, hideWhenEmpty: true } }),
|
|
460
576
|
totalConsumed: schema.shape.totalConsumed.numberUI({ display: { displayMode: NumberDisplayMode.FORMATTED } }),
|
|
461
577
|
},
|
|
462
|
-
|
|
578
|
+
// Named by the application's word for the pool (`billing_credit_pools.<poolKey>.name`), for the same reason as a
|
|
579
|
+
// product (#1851). An unworded pool answers its key.
|
|
580
|
+
getFrontendResourceLabel: ({ currentObject, t }) => {
|
|
581
|
+
const nameKey = `${LabellingTypes.BILLING_CREDIT_POOLS}.${currentObject.poolKey}.name`;
|
|
582
|
+
const name = t(nameKey);
|
|
583
|
+
return name === nameKey ? unresolvedLabel(currentObject.poolKey) : name;
|
|
584
|
+
},
|
|
585
|
+
instanceLabelSections: [LabellingTypes.BILLING_CREDIT_POOLS],
|
|
463
586
|
views: [
|
|
464
587
|
[CreditPool_Operations.TOP_UP, notAddressable({ iconOverride: CirclePlus })],
|
|
465
588
|
],
|
|
@@ -490,7 +613,9 @@ export function buildBillingMainPresets() {
|
|
|
490
613
|
display: { displayMode: NumberDisplayMode.FORMATTED, showDescription: true },
|
|
491
614
|
}),
|
|
492
615
|
},
|
|
493
|
-
|
|
616
|
+
// An invoice is named by its date, in the viewer's format. Its status is the badge beside it; the name used to be
|
|
617
|
+
// English written in code around the raw status ("Invoice paid").
|
|
618
|
+
getFrontendResourceLabel: ({ currentObject, formatDate }) => formatDate(currentObject.createdAt),
|
|
494
619
|
views: [
|
|
495
620
|
[Op.LIST, notAddressable()],
|
|
496
621
|
[Op.READ, notAddressable()],
|
|
@@ -514,7 +639,9 @@ export function buildBillingMainPresets() {
|
|
|
514
639
|
display: { displayMode: NumberDisplayMode.FORMATTED, showDescription: true },
|
|
515
640
|
}),
|
|
516
641
|
},
|
|
517
|
-
|
|
642
|
+
// A usage record is named by the instant it was recorded. The product key is an identifier and the quantity a
|
|
643
|
+
// value; both are rows, and the name used to be the two raw values joined.
|
|
644
|
+
getFrontendResourceLabel: ({ currentObject, formatDateTime }) => formatDateTime(currentObject.timestamp),
|
|
518
645
|
views: [
|
|
519
646
|
[Op.LIST, notAddressable()],
|
|
520
647
|
[Op.READ, notAddressable()],
|
|
@@ -549,6 +676,8 @@ export function buildBillingAdminPresets(main) {
|
|
|
549
676
|
[Op.READ, notAddressable()],
|
|
550
677
|
// Referrals are an immutable ledger record — UPDATE is headless (no edit UI).
|
|
551
678
|
[Op.UPDATE, apiOnly()],
|
|
679
|
+
[Referral_Operations.ISSUE, notAddressable({ iconOverride: CirclePlus })],
|
|
680
|
+
[Referral_Operations.CANCEL, notAddressable({ iconOverride: CircleX })],
|
|
552
681
|
],
|
|
553
682
|
})),
|
|
554
683
|
[R.PROMOTIONS]: resourceUIBehaviorFrom(promotionsFactory, main[R.PROMOTIONS], ({ base }) => ({
|