@sixtyninecommerce/admin 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5772 -118
- package/dist/auth/index.d.ts +7 -1
- package/dist/auth/index.js +8 -1
- package/dist/catalog/assets.d.ts +120 -8
- package/dist/catalog/assets.js +98 -12
- package/dist/catalog/collections.d.ts +279 -14
- package/dist/catalog/collections.js +246 -15
- package/dist/catalog/documents.d.ts +142 -0
- package/dist/catalog/documents.js +114 -0
- package/dist/catalog/index.d.ts +54 -6
- package/dist/catalog/index.js +10 -1
- package/dist/catalog/metadata.d.ts +244 -0
- package/dist/catalog/metadata.js +220 -0
- package/dist/catalog/products.d.ts +159 -19
- package/dist/catalog/products.js +88 -13
- package/dist/catalog/properties.d.ts +140 -14
- package/dist/catalog/properties.js +65 -1
- package/dist/catalog/recommendations.d.ts +288 -0
- package/dist/catalog/recommendations.js +237 -0
- package/dist/catalog/reviews.d.ts +381 -0
- package/dist/catalog/reviews.js +325 -0
- package/dist/catalog/types.d.ts +17 -1
- package/dist/catalog/types.js +10 -0
- package/dist/catalog/variant-find.d.ts +136 -0
- package/dist/catalog/variant-find.js +239 -0
- package/dist/catalog/variants.d.ts +293 -26
- package/dist/catalog/variants.js +164 -7
- package/dist/catalog/visibility.d.ts +38 -7
- package/dist/catalog/visibility.js +19 -1
- package/dist/client.d.ts +121 -9
- package/dist/client.js +53 -14
- package/dist/content/assets.d.ts +285 -0
- package/dist/content/assets.js +181 -0
- package/dist/content/forms.d.ts +332 -1
- package/dist/content/forms.js +140 -4
- package/dist/content/items.d.ts +292 -0
- package/dist/content/items.js +170 -0
- package/dist/content/menus.d.ts +254 -0
- package/dist/content/menus.js +70 -0
- package/dist/content/strings.d.ts +137 -0
- package/dist/content/strings.js +82 -0
- package/dist/content/types.d.ts +282 -0
- package/dist/content/types.js +100 -0
- package/dist/customers/age-verification.d.ts +66 -0
- package/dist/customers/age-verification.js +79 -0
- package/dist/customers/credit.d.ts +263 -0
- package/dist/customers/credit.js +244 -0
- package/dist/customers/delivery-preferences.d.ts +120 -0
- package/dist/customers/delivery-preferences.js +97 -0
- package/dist/customers/delivery-terms.d.ts +44 -0
- package/dist/customers/delivery-terms.js +32 -0
- package/dist/customers/groups.d.ts +207 -0
- package/dist/customers/groups.js +438 -0
- package/dist/customers/index.d.ts +462 -37
- package/dist/customers/index.js +317 -40
- package/dist/customers/invitation-status.d.ts +57 -0
- package/dist/customers/invitation-status.js +21 -0
- package/dist/customers/notes.d.ts +67 -0
- package/dist/customers/notes.js +51 -0
- package/dist/customers/notification-recipients.d.ts +97 -0
- package/dist/customers/notification-recipients.js +84 -0
- package/dist/customers/saved-lists.d.ts +42 -0
- package/dist/customers/saved-lists.js +61 -0
- package/dist/customers/vat.d.ts +83 -0
- package/dist/customers/vat.js +46 -0
- package/dist/customers/wire.d.ts +20 -0
- package/dist/customers/wire.js +35 -0
- package/dist/documents/ar.d.ts +307 -0
- package/dist/documents/ar.js +254 -0
- package/dist/documents/emails.d.ts +150 -0
- package/dist/documents/emails.js +220 -0
- package/dist/documents/index.d.ts +744 -21
- package/dist/documents/index.js +438 -24
- package/dist/documents/pdf.d.ts +2 -0
- package/dist/documents/pdf.js +11 -0
- package/dist/fulfillments/create.d.ts +167 -0
- package/dist/fulfillments/create.js +216 -0
- package/dist/fulfillments/index.d.ts +163 -11
- package/dist/fulfillments/index.js +135 -6
- package/dist/fulfillments/payment-policy.d.ts +64 -0
- package/dist/fulfillments/payment-policy.js +140 -0
- package/dist/fulfillments/warehouse.d.ts +724 -0
- package/dist/fulfillments/warehouse.js +914 -0
- package/dist/geo/index.d.ts +119 -0
- package/dist/geo/index.js +107 -0
- package/dist/index.d.ts +147 -39
- package/dist/index.js +51 -12
- package/dist/integrations/afas-actions.d.ts +234 -0
- package/dist/integrations/afas-actions.js +310 -0
- package/dist/integrations/afas-types.d.ts +483 -0
- package/dist/integrations/afas-types.js +5 -0
- package/dist/integrations/afas.d.ts +234 -0
- package/dist/integrations/afas.js +290 -0
- package/dist/integrations/connections.d.ts +103 -0
- package/dist/integrations/connections.js +55 -0
- package/dist/integrations/esp.d.ts +313 -0
- package/dist/integrations/esp.js +169 -0
- package/dist/integrations/exact-online-types.d.ts +965 -0
- package/dist/integrations/exact-online-types.js +6 -0
- package/dist/integrations/exact-online.d.ts +784 -0
- package/dist/integrations/exact-online.js +1089 -0
- package/dist/integrations/index.d.ts +44 -3
- package/dist/integrations/index.js +23 -3
- package/dist/integrations/pipeline-discoveries.d.ts +65 -0
- package/dist/integrations/pipeline-discoveries.js +65 -0
- package/dist/integrations/pipeline-keys.d.ts +13 -0
- package/dist/integrations/pipeline-keys.js +22 -0
- package/dist/integrations/pipeline-runs.d.ts +119 -0
- package/dist/integrations/pipeline-runs.js +87 -0
- package/dist/integrations/pipelines.d.ts +87 -15
- package/dist/integrations/pipelines.js +41 -22
- package/dist/integrations/price-import.d.ts +223 -0
- package/dist/integrations/price-import.js +124 -0
- package/dist/integrations/types.d.ts +367 -3
- package/dist/integrations/types.js +4 -3
- package/dist/locations/index.d.ts +66 -0
- package/dist/locations/index.js +31 -0
- package/dist/meta/index.d.ts +172 -0
- package/dist/metafields/index.d.ts +8 -1
- package/dist/metafields/index.js +10 -0
- package/dist/orders/cancel-with-money.d.ts +237 -0
- package/dist/orders/cancel-with-money.js +155 -0
- package/dist/orders/commit-edit.d.ts +23 -1
- package/dist/orders/commit-edit.js +19 -2
- package/dist/orders/deposit-policy.d.ts +3 -1
- package/dist/orders/drafts.d.ts +800 -0
- package/dist/orders/drafts.js +771 -0
- package/dist/orders/fields.d.ts +245 -0
- package/dist/orders/fields.js +188 -0
- package/dist/orders/get.d.ts +29 -19
- package/dist/orders/get.js +17 -8
- package/dist/orders/index.d.ts +66 -15
- package/dist/orders/index.js +25 -2
- package/dist/orders/lifecycle.d.ts +128 -0
- package/dist/orders/lifecycle.js +158 -0
- package/dist/orders/line-price-preview.d.ts +7 -2
- package/dist/orders/list.d.ts +243 -0
- package/dist/orders/list.js +487 -0
- package/dist/orders/manual-discounts.d.ts +13 -5
- package/dist/orders/manual-pricing.d.ts +5 -0
- package/dist/orders/money-view.d.ts +149 -0
- package/dist/orders/money-view.js +99 -0
- package/dist/orders/order-entry.d.ts +20 -7
- package/dist/orders/plan-edit.d.ts +34 -2
- package/dist/orders/plan-edit.js +112 -11
- package/dist/orders/settings.d.ts +201 -0
- package/dist/orders/settings.js +325 -0
- package/dist/orders/status.d.ts +159 -13
- package/dist/orders/status.js +21 -0
- package/dist/orders/surcharges.d.ts +20 -1
- package/dist/orders/surcharges.js +37 -11
- package/dist/payments/account-credit.d.ts +46 -0
- package/dist/payments/account-credit.js +42 -0
- package/dist/payments/ar.d.ts +292 -0
- package/dist/payments/ar.js +223 -0
- package/dist/payments/connected-account.d.ts +47 -0
- package/dist/payments/connected-account.js +61 -0
- package/dist/payments/credit-noted-fields.d.ts +19 -0
- package/dist/payments/credit-noted-fields.js +69 -0
- package/dist/payments/customer-consent.d.ts +30 -0
- package/dist/payments/customer-consent.js +49 -0
- package/dist/payments/index.d.ts +340 -19
- package/dist/payments/index.js +151 -1
- package/dist/payments/methods.d.ts +303 -8
- package/dist/payments/methods.js +337 -7
- package/dist/payments/mollie-methods.d.ts +159 -0
- package/dist/payments/mollie-methods.js +141 -0
- package/dist/payments/payment-links.d.ts +180 -0
- package/dist/payments/payment-links.js +97 -0
- package/dist/payments/provider-keys.d.ts +150 -0
- package/dist/payments/provider-keys.js +118 -0
- package/dist/payments/providers.d.ts +86 -11
- package/dist/payments/providers.js +29 -0
- package/dist/payments/requests.d.ts +147 -0
- package/dist/payments/requests.js +93 -0
- package/dist/payments/store-credit-fields.d.ts +15 -0
- package/dist/payments/store-credit-fields.js +51 -0
- package/dist/pickups/index.d.ts +106 -0
- package/dist/pickups/index.js +108 -0
- package/dist/platform/channels.d.ts +132 -0
- package/dist/platform/channels.js +128 -0
- package/dist/platform/email-gateways.d.ts +165 -0
- package/dist/platform/email-gateways.js +78 -0
- package/dist/platform/geo.d.ts +123 -0
- package/dist/platform/geo.js +144 -0
- package/dist/platform/tax.d.ts +162 -0
- package/dist/platform/tax.js +169 -0
- package/dist/pos/anomalies.d.ts +158 -0
- package/dist/pos/anomalies.js +64 -0
- package/dist/pos/assisted-selling.d.ts +79 -0
- package/dist/pos/assisted-selling.js +38 -0
- package/dist/pos/exchanges.d.ts +108 -0
- package/dist/pos/exchanges.js +22 -0
- package/dist/pos/gift-cards.d.ts +135 -0
- package/dist/pos/gift-cards.js +47 -0
- package/dist/pos/index.d.ts +2370 -58
- package/dist/pos/index.js +854 -7
- package/dist/pos/no-receipt.d.ts +94 -0
- package/dist/pos/no-receipt.js +25 -0
- package/dist/pos/offline-sales.d.ts +228 -0
- package/dist/pos/offline-sales.js +170 -0
- package/dist/pos/pay-by-link.d.ts +143 -0
- package/dist/pos/pay-by-link.js +57 -0
- package/dist/pos/quick-keys.d.ts +103 -0
- package/dist/pos/quick-keys.js +45 -0
- package/dist/pos/reports.d.ts +360 -0
- package/dist/pos/reports.js +62 -0
- package/dist/pos/returns.d.ts +244 -0
- package/dist/pos/returns.js +83 -0
- package/dist/pricing/bulk-coupons.d.ts +139 -0
- package/dist/pricing/bulk-coupons.js +112 -0
- package/dist/pricing/index.d.ts +174 -4
- package/dist/pricing/index.js +174 -1
- package/dist/pricing/price-lists.d.ts +606 -0
- package/dist/pricing/price-lists.js +600 -0
- package/dist/purchasing/alerts.d.ts +104 -0
- package/dist/purchasing/alerts.js +88 -0
- package/dist/purchasing/backorders.d.ts +61 -0
- package/dist/purchasing/backorders.js +92 -0
- package/dist/purchasing/cost.d.ts +110 -0
- package/dist/purchasing/cost.js +86 -0
- package/dist/purchasing/index.d.ts +54 -0
- package/dist/purchasing/index.js +24 -0
- package/dist/purchasing/links.d.ts +263 -0
- package/dist/purchasing/links.js +192 -0
- package/dist/purchasing/purchase-orders.d.ts +551 -0
- package/dist/purchasing/purchase-orders.js +491 -0
- package/dist/purchasing/receipts.d.ts +396 -0
- package/dist/purchasing/receipts.js +287 -0
- package/dist/purchasing/replenishment.d.ts +281 -0
- package/dist/purchasing/replenishment.js +240 -0
- package/dist/purchasing/returns.d.ts +273 -0
- package/dist/purchasing/returns.js +229 -0
- package/dist/purchasing/settings.d.ts +152 -0
- package/dist/purchasing/settings.js +181 -0
- package/dist/purchasing/suppliers.d.ts +476 -0
- package/dist/purchasing/suppliers.js +438 -0
- package/dist/quotes/index.d.ts +518 -0
- package/dist/quotes/index.js +439 -0
- package/dist/reports/index.d.ts +71 -8
- package/dist/reports/index.js +74 -11
- package/dist/returns/index.d.ts +675 -21
- package/dist/returns/index.js +756 -11
- package/dist/search/index.d.ts +76 -1
- package/dist/search/index.js +63 -0
- package/dist/seo/config.d.ts +128 -0
- package/dist/seo/config.js +95 -0
- package/dist/seo/redirects.d.ts +125 -0
- package/dist/seo/redirects.js +60 -1
- package/dist/seo/settings.d.ts +147 -0
- package/dist/seo/settings.js +209 -0
- package/dist/shipping/definitions.d.ts +29 -0
- package/dist/shipping/definitions.js +47 -0
- package/dist/shipping/fulfillment-profiles.d.ts +174 -0
- package/dist/shipping/fulfillment-profiles.js +104 -0
- package/dist/shipping/hendriks.d.ts +97 -0
- package/dist/shipping/hendriks.js +50 -0
- package/dist/shipping/index.d.ts +54 -0
- package/dist/shipping/index.js +27 -0
- package/dist/shipping/methods.d.ts +205 -0
- package/dist/shipping/methods.js +240 -0
- package/dist/shipping/myparcel.d.ts +668 -0
- package/dist/shipping/myparcel.js +466 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
- package/dist/shipping/parcel-rule.d.ts +6 -5
- package/dist/shipping/parcel-rule.js +21 -23
- package/dist/shipping/rate-tables.d.ts +324 -0
- package/dist/shipping/rate-tables.js +390 -0
- package/dist/shipping/ship-to-countries.d.ts +75 -0
- package/dist/shipping/ship-to-countries.js +47 -0
- package/dist/stock/by-location.d.ts +86 -0
- package/dist/stock/by-location.js +94 -0
- package/dist/stock/index.d.ts +18 -4
- package/dist/stock/index.js +7 -0
- package/dist/stock/locations.d.ts +144 -0
- package/dist/stock/locations.js +168 -0
- package/dist/stock/settings.d.ts +106 -0
- package/dist/stock/settings.js +103 -0
- package/dist/stock/transfers.d.ts +386 -52
- package/dist/stock/transfers.js +222 -17
- package/dist/stored-value/index.d.ts +312 -11
- package/dist/stored-value/index.js +223 -4
- package/dist/subscriptions/index.d.ts +682 -0
- package/dist/subscriptions/index.js +815 -0
- package/dist/tax/display-policy.d.ts +105 -0
- package/dist/tax/display-policy.js +84 -0
- package/dist/tax/index.d.ts +79 -1
- package/dist/tax/index.js +52 -0
- package/dist/tenant/email-style.d.ts +102 -0
- package/dist/tenant/email-style.js +72 -0
- package/dist/tenant/email-templates.d.ts +287 -0
- package/dist/tenant/email-templates.js +271 -0
- package/dist/tenant/notifications.d.ts +23 -17
- package/dist/tenant/settings-sources.d.ts +1 -1
- package/dist/tenant/time-zone.d.ts +17 -0
- package/dist/tenant/time-zone.js +11 -0
- package/dist/types.d.ts +50 -3
- package/dist/users/index.d.ts +3 -0
- package/dist/users/index.js +2 -0
- package/dist/users/warehouses.d.ts +50 -0
- package/dist/users/warehouses.js +51 -0
- package/package.json +2 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
import { type EmailStylePatch } from './email-style.js';
|
|
4
|
+
/** One template rendered as the store would send it, filled from the store's own records. */
|
|
5
|
+
export interface EmailPreview {
|
|
6
|
+
subject: string;
|
|
7
|
+
/** The whole HTML document, ready for an `<iframe srcdoc>`. */
|
|
8
|
+
html: string;
|
|
9
|
+
/** Each recipient field resolved for the sample, as one comma-separated list ('' for none). */
|
|
10
|
+
recipients: {
|
|
11
|
+
to: string;
|
|
12
|
+
cc: string;
|
|
13
|
+
bcc: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* `true` when the credential's customer-data level withheld part of the answer (an app
|
|
17
|
+
* installation below `customerData: full`): `subject` and `html` then read `''`, and so do the
|
|
18
|
+
* recipients below `identity`. Absent otherwise.
|
|
19
|
+
*/
|
|
20
|
+
redacted?: true;
|
|
21
|
+
}
|
|
22
|
+
export interface EmailPreviewOptions extends Call {
|
|
23
|
+
/** The language to render the template's texts and subject in, e.g. `nl`. */
|
|
24
|
+
languageCode: string;
|
|
25
|
+
/**
|
|
26
|
+
* Style fields to preview instead of the stored ones, e.g. a form's unsaved values. Nothing is
|
|
27
|
+
* saved. A value `tenant.emailStyle.set` would refuse is not refused here: the renderer draws it
|
|
28
|
+
* with its own fallback.
|
|
29
|
+
*/
|
|
30
|
+
style?: EmailStylePatch;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One part of a subject line or of a block's text: literal text (`{ type: 'text', value }`), or a
|
|
34
|
+
* variable the email fills in when it is sent (`{ type: 'var', key }`, a key from the template
|
|
35
|
+
* type's `variables` in {@link EmailTemplateCatalog}). A variable key the type does not offer is
|
|
36
|
+
* printed as `[key]`.
|
|
37
|
+
*/
|
|
38
|
+
export interface EmailTokenSegment {
|
|
39
|
+
type: 'text' | 'var';
|
|
40
|
+
/** The text of a `text` part. */
|
|
41
|
+
value?: string;
|
|
42
|
+
/** The variable of a `var` part. */
|
|
43
|
+
key?: string;
|
|
44
|
+
}
|
|
45
|
+
/** A block's texts in one language. */
|
|
46
|
+
export interface EmailTemplateBlockContent {
|
|
47
|
+
languageCode: string;
|
|
48
|
+
/**
|
|
49
|
+
* One entry per text of the block, keyed by the block type's field names (most hold
|
|
50
|
+
* {@link EmailTokenSegment} lists). The renderer falls back to the block's defaults for a
|
|
51
|
+
* missing entry.
|
|
52
|
+
*/
|
|
53
|
+
content: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/** One block (the engine calls them chips) of a template's body, e.g. a heading or the order lines. */
|
|
56
|
+
export interface EmailTemplateBlock {
|
|
57
|
+
id: string;
|
|
58
|
+
/** The block type: one of the catalog's `blockTypes`. */
|
|
59
|
+
type: string;
|
|
60
|
+
/** Its place in the body: the blocks are answered in this order. */
|
|
61
|
+
position: number;
|
|
62
|
+
/** The block's switches and settings (e.g. `{ showImage: true }`), the same in every language. */
|
|
63
|
+
config: Record<string, unknown>;
|
|
64
|
+
/** Its texts, one entry per language the store wrote them in. */
|
|
65
|
+
contents: EmailTemplateBlockContent[];
|
|
66
|
+
}
|
|
67
|
+
/** A template's subject line in one language. */
|
|
68
|
+
export interface EmailTemplateSubject {
|
|
69
|
+
languageCode: string;
|
|
70
|
+
segments: EmailTokenSegment[];
|
|
71
|
+
}
|
|
72
|
+
/** The template of one type, as the store sends it now. */
|
|
73
|
+
export interface EmailTemplate {
|
|
74
|
+
/**
|
|
75
|
+
* The template's id. It changes when the store starts or stops taking `settings.email` from its
|
|
76
|
+
* organization, so the methods here address a template by its `type`; keep the id only to tell
|
|
77
|
+
* that the template a page shows was replaced.
|
|
78
|
+
*/
|
|
79
|
+
id: string;
|
|
80
|
+
type: string;
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
/** Who it is sent to, as recipient variables and fixed addresses. */
|
|
83
|
+
recipients: {
|
|
84
|
+
to: EmailTokenSegment[];
|
|
85
|
+
cc: EmailTokenSegment[];
|
|
86
|
+
bcc: EmailTokenSegment[];
|
|
87
|
+
};
|
|
88
|
+
/** The body, in `position` order. */
|
|
89
|
+
blocks: EmailTemplateBlock[];
|
|
90
|
+
subjects: EmailTemplateSubject[];
|
|
91
|
+
}
|
|
92
|
+
export interface EmailTemplateVariable {
|
|
93
|
+
key: string;
|
|
94
|
+
label: string;
|
|
95
|
+
}
|
|
96
|
+
export interface EmailTemplateTypeInfo {
|
|
97
|
+
type: string;
|
|
98
|
+
label: string;
|
|
99
|
+
description: string;
|
|
100
|
+
/** The variables a subject or block text of this type may use. */
|
|
101
|
+
variables: EmailTemplateVariable[];
|
|
102
|
+
}
|
|
103
|
+
export interface EmailBlockTypeInfo {
|
|
104
|
+
type: string;
|
|
105
|
+
label: string;
|
|
106
|
+
/** A grouping for a picker: `structural`, `content`, `commerce_data`, `action`, `cms_data`, … */
|
|
107
|
+
category: string;
|
|
108
|
+
}
|
|
109
|
+
/** What the template editor can offer: the same for every store. */
|
|
110
|
+
export interface EmailTemplateCatalog {
|
|
111
|
+
templateTypes: EmailTemplateTypeInfo[];
|
|
112
|
+
blockTypes: EmailBlockTypeInfo[];
|
|
113
|
+
/** The variables a recipient field may use. */
|
|
114
|
+
recipientVariables: EmailTemplateVariable[];
|
|
115
|
+
}
|
|
116
|
+
export interface AddEmailTemplateBlockInput {
|
|
117
|
+
/** The block type: one of the catalog's `blockTypes`. */
|
|
118
|
+
type: string;
|
|
119
|
+
/**
|
|
120
|
+
* Where to put it: `0` is first, and the blocks at that place and after it move down one. At the
|
|
121
|
+
* end when omitted.
|
|
122
|
+
*/
|
|
123
|
+
position?: number;
|
|
124
|
+
/** Its switches and settings; `{}` when omitted. */
|
|
125
|
+
config?: Record<string, unknown>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The template writes, by template type or by block id. Every one is refused while the store takes
|
|
129
|
+
* `settings.email` from its organization (see {@link EmailTemplatesModule}).
|
|
130
|
+
*
|
|
131
|
+
* **Coded refusals shared by every write:**
|
|
132
|
+
* - `ORGANIZATION_SETTINGS_INHERITED` → `BusinessRuleError` (422): the store inherits
|
|
133
|
+
* `settings.email`; `tenant.settingsSources.override('settings.email')` first.
|
|
134
|
+
* - `MANAGED_BY_ORGANIZATION` → `BusinessRuleError` (422): the organization locks it.
|
|
135
|
+
* - `FORBIDDEN` → `PermissionDeniedError` (403): the credential lacks `UpdateSettings`.
|
|
136
|
+
*/
|
|
137
|
+
export interface EmailTemplateBlocksModule {
|
|
138
|
+
/**
|
|
139
|
+
* Add a block to the template of `templateType`, and answer it (with no `contents`: a new block
|
|
140
|
+
* has no texts until `setContent`). Two requests: the template the store uses is looked up
|
|
141
|
+
* first.
|
|
142
|
+
*
|
|
143
|
+
* **Coded refusals:** `EMAIL_TEMPLATE_NOT_FOUND` → `NotFoundError` (404), no template of the
|
|
144
|
+
* type; `EMAIL_CHIP_TYPE_INVALID` → `ValidationError` (400), a block type outside the catalog;
|
|
145
|
+
* `EMAIL_CHIP_CONFIG_INVALID` (400), a `config` that is not an object; `ENTITY_NOT_FOUND` →
|
|
146
|
+
* `NotFoundError` (404), the template was replaced between the two requests (ask again).
|
|
147
|
+
*
|
|
148
|
+
* @effect write
|
|
149
|
+
* @permission UpdateSettings
|
|
150
|
+
*/
|
|
151
|
+
add(templateType: string, block: AddEmailTemplateBlockInput, options?: Call): Promise<EmailTemplateBlock>;
|
|
152
|
+
/**
|
|
153
|
+
* Replace a block's `config` as a whole (send every key it keeps), and answer the block without
|
|
154
|
+
* its `contents` (`getByType` reads them).
|
|
155
|
+
*
|
|
156
|
+
* **Coded refusals:** `EMAIL_CHIP_CONFIG_INVALID` → `ValidationError` (400), a config that is not
|
|
157
|
+
* an object; `ENTITY_NOT_FOUND` → `NotFoundError` (404), no block of the store's with that id.
|
|
158
|
+
*
|
|
159
|
+
* @effect write
|
|
160
|
+
* @permission UpdateSettings
|
|
161
|
+
*/
|
|
162
|
+
update(blockId: string, patch: {
|
|
163
|
+
config: Record<string, unknown>;
|
|
164
|
+
}, options?: Call): Promise<Omit<EmailTemplateBlock, 'contents'>>;
|
|
165
|
+
/**
|
|
166
|
+
* Delete a block with its texts in every language. The blocks after it move up one. There is
|
|
167
|
+
* no undo: a block added back starts from its defaults.
|
|
168
|
+
*
|
|
169
|
+
* **Coded refusal:** `ENTITY_NOT_FOUND` → `NotFoundError` (404), no block of the store's with
|
|
170
|
+
* that id.
|
|
171
|
+
*
|
|
172
|
+
* @effect destructive
|
|
173
|
+
* @permission UpdateSettings
|
|
174
|
+
*/
|
|
175
|
+
remove(blockId: string, options?: Call): Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Put the blocks of the template of `templateType` in a new order: `blockIds` lists EVERY block
|
|
178
|
+
* of the template once, first to last. Two requests: the template is looked up first.
|
|
179
|
+
*
|
|
180
|
+
* **Coded refusals:** `EMAIL_CHIP_ORDER_INVALID` → `ValidationError` (400), a block left out,
|
|
181
|
+
* listed twice, or not one of the template's (the message names it); `EMAIL_TEMPLATE_NOT_FOUND`
|
|
182
|
+
* → `NotFoundError` (404), no template of the type; `ENTITY_NOT_FOUND` (404), the template was
|
|
183
|
+
* replaced between the two requests.
|
|
184
|
+
*
|
|
185
|
+
* @effect write
|
|
186
|
+
* @permission UpdateSettings
|
|
187
|
+
*/
|
|
188
|
+
reorder(templateType: string, blockIds: readonly string[], options?: Call): Promise<void>;
|
|
189
|
+
/**
|
|
190
|
+
* Set a block's texts in one language, replacing what it had in that language (the other
|
|
191
|
+
* languages keep theirs). Answers what is stored.
|
|
192
|
+
*
|
|
193
|
+
* **Coded refusals:** `EMAIL_CHIP_CONTENT_INVALID` → `ValidationError` (400), content that is
|
|
194
|
+
* not an object; `EMAIL_LANGUAGE_CODE_INVALID` (400), a language code that is none (`en`, `nl`,
|
|
195
|
+
* `pt_BR`, …); `ENTITY_NOT_FOUND` → `NotFoundError` (404), no block of the store's with that id.
|
|
196
|
+
*
|
|
197
|
+
* @effect write
|
|
198
|
+
* @permission UpdateSettings
|
|
199
|
+
*/
|
|
200
|
+
setContent(blockId: string, languageCode: string, content: Record<string, unknown>, options?: Call): Promise<EmailTemplateBlockContent>;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* `admin.tenant.emailTemplates` — the active store's email templates (part of the `settings.email`
|
|
204
|
+
* group). Always the ACTIVE channel; no channel argument.
|
|
205
|
+
*
|
|
206
|
+
* Templates are addressed by TYPE (`order_confirmation`, `shipping_confirmation`, …): a template's
|
|
207
|
+
* id changes when the store starts or stops taking `settings.email` from its organization, its
|
|
208
|
+
* type never does. While the store inherits, every read answers the organization's template, and
|
|
209
|
+
* every write (`setSubject`, `blocks.*`) is refused (`ORGANIZATION_SETTINGS_INHERITED` or
|
|
210
|
+
* `MANAGED_BY_ORGANIZATION`, 422).
|
|
211
|
+
*
|
|
212
|
+
* A change applies to the next email of its type the store sends; an email already sent keeps
|
|
213
|
+
* what it said.
|
|
214
|
+
*/
|
|
215
|
+
export interface EmailTemplatesModule {
|
|
216
|
+
/**
|
|
217
|
+
* What the editor can offer: each template type with its label, description and variables, the
|
|
218
|
+
* block types, and the recipient variables. The same for every store.
|
|
219
|
+
*
|
|
220
|
+
* @effect read
|
|
221
|
+
* @permission ReadSettings
|
|
222
|
+
*/
|
|
223
|
+
catalog(options?: Call): Promise<EmailTemplateCatalog>;
|
|
224
|
+
/**
|
|
225
|
+
* The template of `type` the store sends, with its blocks (in order) and subjects, or `null`
|
|
226
|
+
* for a type the engine does not know.
|
|
227
|
+
*
|
|
228
|
+
* For a store on its own settings, the first read of a known type the store has no template
|
|
229
|
+
* of yet creates it from the platform's default.
|
|
230
|
+
*
|
|
231
|
+
* @effect read
|
|
232
|
+
* @permission ReadSettings
|
|
233
|
+
*/
|
|
234
|
+
getByType(type: string, options?: Call): Promise<EmailTemplate | null>;
|
|
235
|
+
/**
|
|
236
|
+
* Set the subject of the template of `templateType` in one language, replacing what it had in
|
|
237
|
+
* that language. Two requests: the template the store uses is looked up first. Answers what is
|
|
238
|
+
* stored.
|
|
239
|
+
*
|
|
240
|
+
* **Coded refusals:** `EMAIL_SUBJECT_INVALID` → `ValidationError` (400), a part that is neither
|
|
241
|
+
* text with its `value` nor a variable with its `key` (the message names it);
|
|
242
|
+
* `EMAIL_LANGUAGE_CODE_INVALID` (400), a language code that is none; `EMAIL_TEMPLATE_NOT_FOUND`
|
|
243
|
+
* → `NotFoundError` (404), no template of the type; `ENTITY_NOT_FOUND` (404), the template was
|
|
244
|
+
* replaced between the two requests. And those of {@link EmailTemplateBlocksModule}.
|
|
245
|
+
*
|
|
246
|
+
* @effect write
|
|
247
|
+
* @permission UpdateSettings
|
|
248
|
+
*/
|
|
249
|
+
setSubject(templateType: string, languageCode: string, segments: readonly EmailTokenSegment[], options?: Call): Promise<EmailTemplateSubject>;
|
|
250
|
+
/** The blocks of a template's body. */
|
|
251
|
+
blocks: EmailTemplateBlocksModule;
|
|
252
|
+
/**
|
|
253
|
+
* Render the template of `type` as the store would send it now, optionally with unsaved style
|
|
254
|
+
* changes. Two requests: the template the store uses for `type` is looked up first.
|
|
255
|
+
*
|
|
256
|
+
* **The answer is customer data.** The sample is filled from the store's LATEST REAL order and
|
|
257
|
+
* customer (synthetic values only in a store with none), so `subject`, `html` and the
|
|
258
|
+
* recipients carry a real buyer's name, email and addresses. Do not log or forward them; a
|
|
259
|
+
* credential below `customerData: full` gets them withheld ({@link EmailPreview.redacted}).
|
|
260
|
+
*
|
|
261
|
+
* For a store on its own settings, the first look-up of a known type the store has no template
|
|
262
|
+
* of yet creates it from the platform's default (as the dashboard's editor does).
|
|
263
|
+
*
|
|
264
|
+
* **Coded refusals:**
|
|
265
|
+
* - `EMAIL_TEMPLATE_NOT_FOUND` → `NotFoundError` (404): no template of `type` (an unknown type).
|
|
266
|
+
* - `ENTITY_NOT_FOUND` → `NotFoundError` (404): the template changed between the two requests
|
|
267
|
+
* (the store's `settings.email` source moved); ask again.
|
|
268
|
+
* - `FORBIDDEN` → `PermissionDeniedError` (403): the credential lacks `ReadSettings`.
|
|
269
|
+
*
|
|
270
|
+
* @effect read
|
|
271
|
+
* @permission ReadSettings
|
|
272
|
+
*/
|
|
273
|
+
preview(type: string, options: EmailPreviewOptions): Promise<EmailPreview>;
|
|
274
|
+
}
|
|
275
|
+
export declare const EMAIL_TEMPLATE_CATALOG_QUERY = "\n query EmailTemplateCatalog {\n emailTemplateCatalog {\n templateTypes { type label description variables { key label } }\n chipTypes { type label category }\n recipientVariables { key label }\n }\n }\n";
|
|
276
|
+
export declare const EMAIL_TEMPLATE_BY_TYPE_QUERY = "\n query EmailTemplateByType($type: String!) {\n emailTemplateByType(type: $type) {\n id\n type\n enabled\n recipientTo { type value key }\n recipientCc { type value key }\n recipientBcc { type value key }\n chips { id type position config translations { languageCode content } }\n subjects { languageCode subject { type value key } }\n }\n }\n";
|
|
277
|
+
export declare const EMAIL_TEMPLATE_ID_BY_TYPE_QUERY = "\n query EmailTemplateIdByType($type: String!) {\n emailTemplateByType(type: $type) { id }\n }\n";
|
|
278
|
+
/** The look-up `blocks.add` makes: the id, and where the body ends. */
|
|
279
|
+
export declare const EMAIL_TEMPLATE_END_BY_TYPE_QUERY = "\n query EmailTemplateEndByType($type: String!) {\n emailTemplateByType(type: $type) { id chips { position } }\n }\n";
|
|
280
|
+
export declare const ADD_EMAIL_TEMPLATE_BLOCK_MUTATION = "\n mutation AddEmailTemplateBlock($templateId: ID!, $input: AddChipInput!) {\n addEmailTemplateChip(templateId: $templateId, input: $input) { id type position config }\n }\n";
|
|
281
|
+
export declare const UPDATE_EMAIL_TEMPLATE_BLOCK_MUTATION = "\n mutation UpdateEmailTemplateBlock($chipId: ID!, $input: UpdateChipInput!) {\n updateEmailTemplateChip(chipId: $chipId, input: $input) { id type position config }\n }\n";
|
|
282
|
+
export declare const REMOVE_EMAIL_TEMPLATE_BLOCK_MUTATION = "\n mutation RemoveEmailTemplateBlock($chipId: ID!) {\n removeEmailTemplateChip(chipId: $chipId)\n }\n";
|
|
283
|
+
export declare const REORDER_EMAIL_TEMPLATE_BLOCKS_MUTATION = "\n mutation ReorderEmailTemplateBlocks($templateId: ID!, $input: ReorderChipsInput!) {\n reorderEmailTemplateChips(templateId: $templateId, input: $input)\n }\n";
|
|
284
|
+
export declare const SET_EMAIL_TEMPLATE_BLOCK_CONTENT_MUTATION = "\n mutation SetEmailTemplateBlockContent($chipId: ID!, $input: UpsertChipTranslationInput!) {\n upsertEmailChipTranslation(chipId: $chipId, input: $input) { languageCode content }\n }\n";
|
|
285
|
+
export declare const SET_EMAIL_TEMPLATE_SUBJECT_MUTATION = "\n mutation SetEmailTemplateSubject($templateId: ID!, $input: UpsertSubjectInput!) {\n upsertEmailTemplateSubject(templateId: $templateId, input: $input) {\n languageCode\n subject { type value key }\n }\n }\n";
|
|
286
|
+
export declare const EMAIL_PREVIEW_QUERY = "\n query EmailPreview(\n $templateId: ID!\n $languageCode: String!\n $styleOverrides: UpdateEmailStyleConfigInput\n ) {\n renderEmailPreview(\n templateId: $templateId\n languageCode: $languageCode\n styleOverrides: $styleOverrides\n ) {\n subject\n html\n recipientTo\n recipientCc\n recipientBcc\n }\n }\n";
|
|
287
|
+
export declare function createEmailTemplatesModule(transport: Transport): EmailTemplatesModule;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { customerDataField } from '../customers/wire.js';
|
|
3
|
+
import { emailStyleInputOf } from './email-style.js';
|
|
4
|
+
const SEGMENT_FIELDS = 'type value key';
|
|
5
|
+
const BLOCK_FIELDS = `id type position config translations { languageCode content }`;
|
|
6
|
+
/**
|
|
7
|
+
* A block as the two block WRITES answer it: no `translations`. An engine before 2026-10-15 saves
|
|
8
|
+
* the chip and only then fails to answer that (non-null) relation, so a write selecting it would
|
|
9
|
+
* report a failure for a change that was stored, and a retry would add the block twice.
|
|
10
|
+
*/
|
|
11
|
+
const WRITTEN_BLOCK_FIELDS = 'id type position config';
|
|
12
|
+
export const EMAIL_TEMPLATE_CATALOG_QUERY = /* GraphQL */ `
|
|
13
|
+
query EmailTemplateCatalog {
|
|
14
|
+
emailTemplateCatalog {
|
|
15
|
+
templateTypes { type label description variables { key label } }
|
|
16
|
+
chipTypes { type label category }
|
|
17
|
+
recipientVariables { key label }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
export const EMAIL_TEMPLATE_BY_TYPE_QUERY = /* GraphQL */ `
|
|
22
|
+
query EmailTemplateByType($type: String!) {
|
|
23
|
+
emailTemplateByType(type: $type) {
|
|
24
|
+
id
|
|
25
|
+
type
|
|
26
|
+
enabled
|
|
27
|
+
recipientTo { ${SEGMENT_FIELDS} }
|
|
28
|
+
recipientCc { ${SEGMENT_FIELDS} }
|
|
29
|
+
recipientBcc { ${SEGMENT_FIELDS} }
|
|
30
|
+
chips { ${BLOCK_FIELDS} }
|
|
31
|
+
subjects { languageCode subject { ${SEGMENT_FIELDS} } }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export const EMAIL_TEMPLATE_ID_BY_TYPE_QUERY = /* GraphQL */ `
|
|
36
|
+
query EmailTemplateIdByType($type: String!) {
|
|
37
|
+
emailTemplateByType(type: $type) { id }
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
/** The look-up `blocks.add` makes: the id, and where the body ends. */
|
|
41
|
+
export const EMAIL_TEMPLATE_END_BY_TYPE_QUERY = /* GraphQL */ `
|
|
42
|
+
query EmailTemplateEndByType($type: String!) {
|
|
43
|
+
emailTemplateByType(type: $type) { id chips { position } }
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
export const ADD_EMAIL_TEMPLATE_BLOCK_MUTATION = /* GraphQL */ `
|
|
47
|
+
mutation AddEmailTemplateBlock($templateId: ID!, $input: AddChipInput!) {
|
|
48
|
+
addEmailTemplateChip(templateId: $templateId, input: $input) { ${WRITTEN_BLOCK_FIELDS} }
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
export const UPDATE_EMAIL_TEMPLATE_BLOCK_MUTATION = /* GraphQL */ `
|
|
52
|
+
mutation UpdateEmailTemplateBlock($chipId: ID!, $input: UpdateChipInput!) {
|
|
53
|
+
updateEmailTemplateChip(chipId: $chipId, input: $input) { ${WRITTEN_BLOCK_FIELDS} }
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
export const REMOVE_EMAIL_TEMPLATE_BLOCK_MUTATION = /* GraphQL */ `
|
|
57
|
+
mutation RemoveEmailTemplateBlock($chipId: ID!) {
|
|
58
|
+
removeEmailTemplateChip(chipId: $chipId)
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
export const REORDER_EMAIL_TEMPLATE_BLOCKS_MUTATION = /* GraphQL */ `
|
|
62
|
+
mutation ReorderEmailTemplateBlocks($templateId: ID!, $input: ReorderChipsInput!) {
|
|
63
|
+
reorderEmailTemplateChips(templateId: $templateId, input: $input)
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
export const SET_EMAIL_TEMPLATE_BLOCK_CONTENT_MUTATION = /* GraphQL */ `
|
|
67
|
+
mutation SetEmailTemplateBlockContent($chipId: ID!, $input: UpsertChipTranslationInput!) {
|
|
68
|
+
upsertEmailChipTranslation(chipId: $chipId, input: $input) { languageCode content }
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
export const SET_EMAIL_TEMPLATE_SUBJECT_MUTATION = /* GraphQL */ `
|
|
72
|
+
mutation SetEmailTemplateSubject($templateId: ID!, $input: UpsertSubjectInput!) {
|
|
73
|
+
upsertEmailTemplateSubject(templateId: $templateId, input: $input) {
|
|
74
|
+
languageCode
|
|
75
|
+
subject { ${SEGMENT_FIELDS} }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
export const EMAIL_PREVIEW_QUERY = /* GraphQL */ `
|
|
80
|
+
query EmailPreview(
|
|
81
|
+
$templateId: ID!
|
|
82
|
+
$languageCode: String!
|
|
83
|
+
$styleOverrides: UpdateEmailStyleConfigInput
|
|
84
|
+
) {
|
|
85
|
+
renderEmailPreview(
|
|
86
|
+
templateId: $templateId
|
|
87
|
+
languageCode: $languageCode
|
|
88
|
+
styleOverrides: $styleOverrides
|
|
89
|
+
) {
|
|
90
|
+
subject
|
|
91
|
+
html
|
|
92
|
+
recipientTo
|
|
93
|
+
recipientCc
|
|
94
|
+
recipientBcc
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
/** A part as stored, without the null of the field the other kind uses. */
|
|
99
|
+
function segmentOf(wire) {
|
|
100
|
+
const segment = { type: wire.type };
|
|
101
|
+
if (wire.value != null)
|
|
102
|
+
segment.value = wire.value;
|
|
103
|
+
if (wire.key != null)
|
|
104
|
+
segment.key = wire.key;
|
|
105
|
+
return segment;
|
|
106
|
+
}
|
|
107
|
+
/** A part as the engine takes it: only the fields the caller set. */
|
|
108
|
+
function segmentInputOf(segment) {
|
|
109
|
+
const input = { type: segment.type };
|
|
110
|
+
if (segment.value !== undefined)
|
|
111
|
+
input.value = segment.value;
|
|
112
|
+
if (segment.key !== undefined)
|
|
113
|
+
input.key = segment.key;
|
|
114
|
+
return input;
|
|
115
|
+
}
|
|
116
|
+
function contentOf(wire) {
|
|
117
|
+
return { languageCode: wire.languageCode, content: wire.content ?? {} };
|
|
118
|
+
}
|
|
119
|
+
function writtenBlockOf(wire) {
|
|
120
|
+
return {
|
|
121
|
+
id: String(wire.id),
|
|
122
|
+
type: wire.type,
|
|
123
|
+
position: wire.position,
|
|
124
|
+
config: wire.config ?? {},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function blockOf(wire) {
|
|
128
|
+
return { ...writtenBlockOf(wire), contents: (wire.translations ?? []).map(contentOf) };
|
|
129
|
+
}
|
|
130
|
+
function templateOf(wire) {
|
|
131
|
+
return {
|
|
132
|
+
id: String(wire.id),
|
|
133
|
+
type: wire.type,
|
|
134
|
+
enabled: wire.enabled,
|
|
135
|
+
recipients: {
|
|
136
|
+
to: wire.recipientTo.map(segmentOf),
|
|
137
|
+
cc: wire.recipientCc.map(segmentOf),
|
|
138
|
+
bcc: wire.recipientBcc.map(segmentOf),
|
|
139
|
+
},
|
|
140
|
+
// The engine orders them on its own rows; the inherited read builds them in code. Sorted
|
|
141
|
+
// here so the promise holds either way.
|
|
142
|
+
blocks: wire.chips.map(blockOf).sort((a, b) => a.position - b.position),
|
|
143
|
+
subjects: wire.subjects.map((row) => ({
|
|
144
|
+
languageCode: row.languageCode,
|
|
145
|
+
segments: row.subject.map(segmentOf),
|
|
146
|
+
})),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function templateNotFound(type) {
|
|
150
|
+
return new NotFoundError({
|
|
151
|
+
code: 'EMAIL_TEMPLATE_NOT_FOUND',
|
|
152
|
+
message: `The store has no email template of type "${type}".`,
|
|
153
|
+
status: 404,
|
|
154
|
+
service: 'vendure',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export function createEmailTemplatesModule(transport) {
|
|
158
|
+
/** The id of the template of `type` the store uses, or the coded not-found. */
|
|
159
|
+
const templateIdOf = async (type, call) => {
|
|
160
|
+
const found = await transport.graphql({ api: 'admin', query: EMAIL_TEMPLATE_ID_BY_TYPE_QUERY, variables: { type } }, call);
|
|
161
|
+
const templateId = found.emailTemplateByType?.id;
|
|
162
|
+
if (!templateId)
|
|
163
|
+
throw templateNotFound(type);
|
|
164
|
+
return String(templateId);
|
|
165
|
+
};
|
|
166
|
+
const blocks = {
|
|
167
|
+
async add(templateType, block, options) {
|
|
168
|
+
let templateId;
|
|
169
|
+
let position = block.position;
|
|
170
|
+
if (position === undefined) {
|
|
171
|
+
// "At the end" is after the LAST position, not the count: positions may have gaps.
|
|
172
|
+
const found = await transport.graphql({
|
|
173
|
+
api: 'admin',
|
|
174
|
+
query: EMAIL_TEMPLATE_END_BY_TYPE_QUERY,
|
|
175
|
+
variables: { type: templateType },
|
|
176
|
+
}, options);
|
|
177
|
+
if (!found.emailTemplateByType)
|
|
178
|
+
throw templateNotFound(templateType);
|
|
179
|
+
templateId = String(found.emailTemplateByType.id);
|
|
180
|
+
position = found.emailTemplateByType.chips.reduce((end, chip) => Math.max(end, chip.position + 1), 0);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
templateId = await templateIdOf(templateType, options);
|
|
184
|
+
}
|
|
185
|
+
const data = await transport.graphql({
|
|
186
|
+
api: 'admin',
|
|
187
|
+
query: ADD_EMAIL_TEMPLATE_BLOCK_MUTATION,
|
|
188
|
+
variables: {
|
|
189
|
+
templateId,
|
|
190
|
+
input: {
|
|
191
|
+
type: block.type,
|
|
192
|
+
position,
|
|
193
|
+
...(block.config !== undefined ? { config: block.config } : {}),
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}, options);
|
|
197
|
+
// The engine writes no texts for a new block (`addChip`), so it has none to answer.
|
|
198
|
+
return { ...writtenBlockOf(data.addEmailTemplateChip), contents: [] };
|
|
199
|
+
},
|
|
200
|
+
async update(blockId, patch, options) {
|
|
201
|
+
const data = await transport.graphql({
|
|
202
|
+
api: 'admin',
|
|
203
|
+
query: UPDATE_EMAIL_TEMPLATE_BLOCK_MUTATION,
|
|
204
|
+
variables: { chipId: blockId, input: { config: patch.config } },
|
|
205
|
+
}, options);
|
|
206
|
+
return writtenBlockOf(data.updateEmailTemplateChip);
|
|
207
|
+
},
|
|
208
|
+
async remove(blockId, options) {
|
|
209
|
+
await transport.graphql({
|
|
210
|
+
api: 'admin',
|
|
211
|
+
query: REMOVE_EMAIL_TEMPLATE_BLOCK_MUTATION,
|
|
212
|
+
variables: { chipId: blockId },
|
|
213
|
+
}, options);
|
|
214
|
+
},
|
|
215
|
+
async reorder(templateType, blockIds, options) {
|
|
216
|
+
const templateId = await templateIdOf(templateType, options);
|
|
217
|
+
await transport.graphql({
|
|
218
|
+
api: 'admin',
|
|
219
|
+
query: REORDER_EMAIL_TEMPLATE_BLOCKS_MUTATION,
|
|
220
|
+
variables: { templateId, input: { chipIds: [...blockIds] } },
|
|
221
|
+
}, options);
|
|
222
|
+
},
|
|
223
|
+
async setContent(blockId, languageCode, content, options) {
|
|
224
|
+
const data = await transport.graphql({
|
|
225
|
+
api: 'admin',
|
|
226
|
+
query: SET_EMAIL_TEMPLATE_BLOCK_CONTENT_MUTATION,
|
|
227
|
+
variables: { chipId: blockId, input: { languageCode, content } },
|
|
228
|
+
}, options);
|
|
229
|
+
return contentOf(data.upsertEmailChipTranslation);
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
async catalog(options) {
|
|
234
|
+
const data = await transport.graphql({ api: 'admin', query: EMAIL_TEMPLATE_CATALOG_QUERY }, options);
|
|
235
|
+
const { templateTypes, chipTypes, recipientVariables } = data.emailTemplateCatalog;
|
|
236
|
+
return { templateTypes, blockTypes: chipTypes, recipientVariables };
|
|
237
|
+
},
|
|
238
|
+
async getByType(type, options) {
|
|
239
|
+
const data = await transport.graphql({ api: 'admin', query: EMAIL_TEMPLATE_BY_TYPE_QUERY, variables: { type } }, options);
|
|
240
|
+
return data.emailTemplateByType ? templateOf(data.emailTemplateByType) : null;
|
|
241
|
+
},
|
|
242
|
+
async setSubject(templateType, languageCode, segments, options) {
|
|
243
|
+
const templateId = await templateIdOf(templateType, options);
|
|
244
|
+
const data = await transport.graphql({
|
|
245
|
+
api: 'admin',
|
|
246
|
+
query: SET_EMAIL_TEMPLATE_SUBJECT_MUTATION,
|
|
247
|
+
variables: {
|
|
248
|
+
templateId,
|
|
249
|
+
input: { languageCode, subject: segments.map(segmentInputOf) },
|
|
250
|
+
},
|
|
251
|
+
}, options);
|
|
252
|
+
const saved = data.upsertEmailTemplateSubject;
|
|
253
|
+
return { languageCode: saved.languageCode, segments: saved.subject.map(segmentOf) };
|
|
254
|
+
},
|
|
255
|
+
blocks,
|
|
256
|
+
async preview(type, options) {
|
|
257
|
+
const { languageCode, style, ...call } = options;
|
|
258
|
+
const templateId = await templateIdOf(type, call);
|
|
259
|
+
const { value, mark } = await customerDataField(transport, EMAIL_PREVIEW_QUERY, 'renderEmailPreview', {
|
|
260
|
+
templateId,
|
|
261
|
+
languageCode,
|
|
262
|
+
...(style !== undefined ? { styleOverrides: emailStyleInputOf(style) } : {}),
|
|
263
|
+
}, call);
|
|
264
|
+
return mark({
|
|
265
|
+
subject: value.subject,
|
|
266
|
+
html: value.html,
|
|
267
|
+
recipients: { to: value.recipientTo, cc: value.recipientCc, bcc: value.recipientBcc },
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
@@ -38,8 +38,8 @@ export interface NotificationEventConfig {
|
|
|
38
38
|
event: string;
|
|
39
39
|
/**
|
|
40
40
|
* `false` switches the email off altogether; nothing is logged for it. Always
|
|
41
|
-
* `true` for `password_reset`, `email_change
|
|
42
|
-
* cannot be switched off.
|
|
41
|
+
* `true` for `password_reset`, `email_change`, `account_setup` and
|
|
42
|
+
* `gift_card_issued`, which cannot be switched off.
|
|
43
43
|
*/
|
|
44
44
|
enabled: boolean;
|
|
45
45
|
sender: NotificationSender;
|
|
@@ -60,20 +60,23 @@ export interface NotificationEventConfig {
|
|
|
60
60
|
export interface UpdateNotificationEventConfigInput {
|
|
61
61
|
/**
|
|
62
62
|
* `false` is refused with `EVENT_ALWAYS_ENABLED` ({@link ValidationError}) on
|
|
63
|
-
* `password_reset`, `email_change` and `
|
|
64
|
-
* a customer resets a password (or sets the first one
|
|
65
|
-
* account), confirms a new address,
|
|
66
|
-
*
|
|
67
|
-
*
|
|
63
|
+
* `password_reset`, `email_change`, `account_setup` and `gift_card_issued`:
|
|
64
|
+
* each is the only way a customer resets a password (or sets the first one
|
|
65
|
+
* on an imported account), confirms a new address, finishes a storefront
|
|
66
|
+
* registration, or receives the code of a gift card they paid for (the
|
|
67
|
+
* platform keeps only its hash), so they are always sent. `magic_link_login`
|
|
68
|
+
* and `customer_invite` can be switched off.
|
|
68
69
|
*/
|
|
69
70
|
enabled?: boolean;
|
|
70
71
|
/**
|
|
71
72
|
* `none` is refused on the identity emails — the `account` category:
|
|
72
73
|
* password reset, magic link, invitation, email change, email and chat
|
|
73
|
-
* verification — with `EVENT_ALWAYS_PLATFORM_SENT`
|
|
74
|
-
* Each carries a one-time credential
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* verification — and on `gift_card_issued`, with `EVENT_ALWAYS_PLATFORM_SENT`
|
|
75
|
+
* ({@link ValidationError}). Each carries a one-time credential, or a gift
|
|
76
|
+
* card's code, only the platform can issue, and no platform event carries
|
|
77
|
+
* one (`stored_value.gift_card.issued` names the card, never its code), so
|
|
78
|
+
* there is no event your system could send it from. What you control there
|
|
79
|
+
* is where its link lands (your head's URL).
|
|
77
80
|
*
|
|
78
81
|
* `none` is also refused on an email whose `replacementEventTypes` is `null`,
|
|
79
82
|
* with `NO_REPLACEMENT_EVENT` ({@link ValidationError}): no platform event is
|
|
@@ -87,6 +90,8 @@ export interface UpdateNotificationEventConfigInput {
|
|
|
87
90
|
* links or codes, and one login serves the person in every store, so they go to
|
|
88
91
|
* the account holder alone: `to` is the `customer_email` variable, `cc` and
|
|
89
92
|
* `bcc` are empty, and `list` answers exactly that whatever was stored before.
|
|
93
|
+
* So does `gift_card_issued`: whoever holds a copy of its code can spend the
|
|
94
|
+
* card, so it goes to the buyer alone.
|
|
90
95
|
* Any other list on them is refused with `IDENTITY_EMAIL_RECIPIENTS_FIXED`
|
|
91
96
|
* ({@link ValidationError}); resending the lists `list` answered is accepted.
|
|
92
97
|
*/
|
|
@@ -119,18 +124,19 @@ export interface UpdateNotificationEventConfigInput {
|
|
|
119
124
|
*
|
|
120
125
|
* **Coded refusals**, GraphQL errors mapped by `core`:
|
|
121
126
|
* - `EVENT_ALWAYS_PLATFORM_SENT` → {@link ValidationError}: `sender: 'none'` on
|
|
122
|
-
* an identity email
|
|
123
|
-
* `/sender`.
|
|
127
|
+
* an identity email or `gift_card_issued`. The message names the event;
|
|
128
|
+
* `details[0].field` is `/sender`.
|
|
124
129
|
* - `EVENT_ALWAYS_ENABLED` → {@link ValidationError}: `enabled: false` on
|
|
125
|
-
* `password_reset`, `email_change` or `
|
|
126
|
-
* event and why it cannot be switched off;
|
|
130
|
+
* `password_reset`, `email_change`, `account_setup` or `gift_card_issued`.
|
|
131
|
+
* The message names the event and why it cannot be switched off;
|
|
132
|
+
* `details[0].field` is `/enabled`.
|
|
127
133
|
* - `NO_REPLACEMENT_EVENT` → {@link ValidationError}: `sender: 'none'` on an
|
|
128
134
|
* email no emitted platform event replaces (`replacementEventTypes: null`).
|
|
129
135
|
* The message names the event, and the event planned to replace it when one
|
|
130
136
|
* is; `details[0].field` is `/sender`.
|
|
131
137
|
* - `IDENTITY_EMAIL_RECIPIENTS_FIXED` → {@link ValidationError}: a recipient
|
|
132
|
-
* list on an identity email (or `new_customer`) that
|
|
133
|
-
* account holder. `details[].field` names each offending list
|
|
138
|
+
* list on an identity email (or `new_customer`, or `gift_card_issued`) that
|
|
139
|
+
* names anyone but the account holder. `details[].field` names each offending list
|
|
134
140
|
* (`/recipientTo`, `/recipientCc`, `/recipientBcc`).
|
|
135
141
|
* - `INVALID_NOTIFICATION_SENDER` → {@link ValidationError}.
|
|
136
142
|
* - `NOTIFICATION_EVENT_NOT_FOUND` → {@link NotFoundError}: no such event key.
|
|
@@ -97,7 +97,7 @@ export interface SettingsSourcesModule {
|
|
|
97
97
|
assets(group: SettingsGroup | (string & {}), call?: Call): Promise<AssetFile[]>;
|
|
98
98
|
}
|
|
99
99
|
export declare const CHANNEL_SETTINGS_SOURCES_QUERY = "\n query ChannelSettingsSources {\n channelSettingsSources { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
|
|
100
|
-
export declare const CHANNEL_SETTINGS_ASSETS_QUERY = "\n query ChannelSettingsAssets($group: String!) {\n channelSettingsAssets(group: $group) { \n id\n createdAt\n updatedAt\n name\n type\n mimeType\n fileSize\n width\n height\n source\n preview\n focalPoint {\n x\n y\n }\n tags {\n value\n }\n }\n }\n";
|
|
100
|
+
export declare const CHANNEL_SETTINGS_ASSETS_QUERY = "\n query ChannelSettingsAssets($group: String!) {\n channelSettingsAssets(group: $group) { \n id\n createdAt\n updatedAt\n name\n type\n mimeType\n fileSize\n width\n height\n source\n preview\n focalPoint {\n x\n y\n }\n tags {\n value\n }\n customFields {\n altText\n }\n }\n }\n";
|
|
101
101
|
export declare const OVERRIDE_ORGANIZATION_SETTINGS_MUTATION = "\n mutation OverrideOrganizationSettings($group: String!) {\n overrideOrganizationSettings(group: $group) { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
|
|
102
102
|
export declare const INHERIT_ORGANIZATION_SETTINGS_MUTATION = "\n mutation InheritOrganizationSettings($group: String!) {\n inheritOrganizationSettings(group: $group) { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
|
|
103
103
|
export declare function createSettingsSourcesModule(transport: Transport): SettingsSourcesModule;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `admin.tenant.timeZone(call?)`: the ACTIVE store's IANA time zone (`Europe/Amsterdam`,
|
|
5
|
+
* `America/New_York`, …) as the engine resolves it: the store's own setting, or
|
|
6
|
+
* `Europe/Amsterdam` when it has none or an invalid one. It is the zone the API counts calendar
|
|
7
|
+
* days in (a requested delivery day, "shipped today", an invoice date), so render dates and
|
|
8
|
+
* "today" in it, never in the browser's or the server's own zone, and never re-check the
|
|
9
|
+
* setting yourself: the engine owns the rule.
|
|
10
|
+
*
|
|
11
|
+
* Any signed-in caller of the store may read it. No channel argument: the tenant is the store.
|
|
12
|
+
*
|
|
13
|
+
* @effect read
|
|
14
|
+
*/
|
|
15
|
+
export type TenantTimeZoneReader = (call?: Call) => Promise<string>;
|
|
16
|
+
export declare const STORE_TIME_ZONE_QUERY = "\n query StoreTimeZone {\n storeTimeZone\n }\n";
|
|
17
|
+
export declare function createTenantTimeZoneReader(transport: Transport): TenantTimeZoneReader;
|