@endora-commerce/email-components 0.100.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/components/email-branding-preview-context.d.ts +7 -0
- package/dist/components/email-branding-preview-context.d.ts.map +1 -0
- package/dist/components/email-branding-preview-context.js +8 -0
- package/dist/components/email-branding-preview-context.js.map +1 -0
- package/dist/components/email-embeds-context.d.ts +11 -0
- package/dist/components/email-embeds-context.d.ts.map +1 -0
- package/dist/components/email-embeds-context.js +15 -0
- package/dist/components/email-embeds-context.js.map +1 -0
- package/dist/config.d.ts +333 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +900 -0
- package/dist/config.js.map +1 -0
- package/dist/defaults/default-footer.d.ts +13 -0
- package/dist/defaults/default-footer.d.ts.map +1 -0
- package/dist/defaults/default-footer.js +42 -0
- package/dist/defaults/default-footer.js.map +1 -0
- package/dist/defaults/default-header.d.ts +5 -0
- package/dist/defaults/default-header.d.ts.map +1 -0
- package/dist/defaults/default-header.js +34 -0
- package/dist/defaults/default-header.js.map +1 -0
- package/dist/defaults/simple-email-body.d.ts +20 -0
- package/dist/defaults/simple-email-body.d.ts.map +1 -0
- package/dist/defaults/simple-email-body.js +41 -0
- package/dist/defaults/simple-email-body.js.map +1 -0
- package/dist/directives/directive-engine.d.ts +7 -0
- package/dist/directives/directive-engine.d.ts.map +1 -0
- package/dist/directives/directive-engine.js +144 -0
- package/dist/directives/directive-engine.js.map +1 -0
- package/dist/editor/email-row-layout-presets.d.ts +33 -0
- package/dist/editor/email-row-layout-presets.d.ts.map +1 -0
- package/dist/editor/email-row-layout-presets.js +145 -0
- package/dist/editor/email-row-layout-presets.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/render/escape-html.d.ts +4 -0
- package/dist/render/escape-html.d.ts.map +1 -0
- package/dist/render/escape-html.js +21 -0
- package/dist/render/escape-html.js.map +1 -0
- package/dist/render/order-labels.d.ts +23 -0
- package/dist/render/order-labels.d.ts.map +1 -0
- package/dist/render/order-labels.js +46 -0
- package/dist/render/order-labels.js.map +1 -0
- package/dist/render/render-email-html.d.ts +18 -0
- package/dist/render/render-email-html.d.ts.map +1 -0
- package/dist/render/render-email-html.js +659 -0
- package/dist/render/render-email-html.js.map +1 -0
- package/dist/render/render-email-text.d.ts +11 -0
- package/dist/render/render-email-text.d.ts.map +1 -0
- package/dist/render/render-email-text.js +167 -0
- package/dist/render/render-email-text.js.map +1 -0
- package/dist/render/sample-variables.d.ts +15 -0
- package/dist/render/sample-variables.d.ts.map +1 -0
- package/dist/render/sample-variables.js +43 -0
- package/dist/render/sample-variables.js.map +1 -0
- package/dist/render/sanitize-email-html.d.ts +9 -0
- package/dist/render/sanitize-email-html.d.ts.map +1 -0
- package/dist/render/sanitize-email-html.js +122 -0
- package/dist/render/sanitize-email-html.js.map +1 -0
- package/dist/render/social-icon-svg.d.ts +6 -0
- package/dist/render/social-icon-svg.d.ts.map +1 -0
- package/dist/render/social-icon-svg.js +41 -0
- package/dist/render/social-icon-svg.js.map +1 -0
- package/dist/schema/component-types.d.ts +269 -0
- package/dist/schema/component-types.d.ts.map +1 -0
- package/dist/schema/component-types.js +105 -0
- package/dist/schema/component-types.js.map +1 -0
- package/dist/schema/envelope.d.ts +21 -0
- package/dist/schema/envelope.d.ts.map +1 -0
- package/dist/schema/envelope.js +8 -0
- package/dist/schema/envelope.js.map +1 -0
- package/dist/tree/walk-embeds.d.ts +7 -0
- package/dist/tree/walk-embeds.d.ts.map +1 -0
- package/dist/tree/walk-embeds.js +115 -0
- package/dist/tree/walk-embeds.js.map +1 -0
- package/package.json +71 -0
- package/tailwind.css +14 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// Email-safe Puck editor configuration (feature 047 + expansion).
|
|
3
|
+
import { createElement } from 'react';
|
|
4
|
+
import { createColorField, definePageBuilderComponent, } from '@endora-commerce/page-builder-core';
|
|
5
|
+
import { useEmailEmbeds } from './components/email-embeds-context.js';
|
|
6
|
+
import { useEmailBrandingPreview } from './components/email-branding-preview-context.js';
|
|
7
|
+
import { EMAIL_COMPONENT_REQUIRED_VARIABLES, EMAIL_ORDER_BLOCK_MARGIN_DEFAULT, EMAIL_ORDER_LABELED_FIELDS, } from './schema/component-types.js';
|
|
8
|
+
import { EMAIL_SOCIAL_BRAND_COLORS, EMAIL_SOCIAL_LABELS } from './render/social-icon-svg.js';
|
|
9
|
+
const ALIGN_OPTIONS = [
|
|
10
|
+
{ label: 'Left', value: 'left' },
|
|
11
|
+
{ label: 'Center', value: 'center' },
|
|
12
|
+
{ label: 'Right', value: 'right' },
|
|
13
|
+
];
|
|
14
|
+
const SOCIAL_NETWORK_OPTIONS = [
|
|
15
|
+
{ label: 'Facebook', value: 'facebook' },
|
|
16
|
+
{ label: 'Instagram', value: 'instagram' },
|
|
17
|
+
{ label: 'LinkedIn', value: 'linkedin' },
|
|
18
|
+
{ label: 'X', value: 'x' },
|
|
19
|
+
{ label: 'YouTube', value: 'youtube' },
|
|
20
|
+
{ label: 'TikTok', value: 'tiktok' },
|
|
21
|
+
{ label: 'Other', value: 'other' },
|
|
22
|
+
];
|
|
23
|
+
const VERTICAL_ALIGN_OPTIONS = [
|
|
24
|
+
{ label: 'Top', value: 'top' },
|
|
25
|
+
{ label: 'Middle', value: 'middle' },
|
|
26
|
+
{ label: 'Bottom', value: 'bottom' },
|
|
27
|
+
];
|
|
28
|
+
const BRANDING_LOGO_SRC = '{{var branding.logoUrl}}';
|
|
29
|
+
export const EmailHeading = {
|
|
30
|
+
label: 'Heading',
|
|
31
|
+
fields: {
|
|
32
|
+
level: {
|
|
33
|
+
type: 'select',
|
|
34
|
+
label: 'Level',
|
|
35
|
+
options: [
|
|
36
|
+
{ label: 'H1', value: 'h1' },
|
|
37
|
+
{ label: 'H2', value: 'h2' },
|
|
38
|
+
{ label: 'H3', value: 'h3' },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
text: { type: 'textarea', label: 'Text' },
|
|
42
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
43
|
+
},
|
|
44
|
+
defaultProps: { level: 'h2', text: 'Heading', align: 'left' },
|
|
45
|
+
render: ({ level, text, align }) => {
|
|
46
|
+
const Tag = level;
|
|
47
|
+
return _jsx(Tag, { style: { margin: '8px 0', textAlign: align, color: '#15202b' }, children: text });
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export const EmailText = {
|
|
51
|
+
label: 'Text',
|
|
52
|
+
fields: {
|
|
53
|
+
text: { type: 'textarea', label: 'Text' },
|
|
54
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
55
|
+
},
|
|
56
|
+
defaultProps: { text: 'Email text. Use {{var your.variable}} for dynamic values.', align: 'left' },
|
|
57
|
+
render: ({ text, align }) => (_jsx("p", { style: { margin: '6px 0', textAlign: align, color: '#1f2937', whiteSpace: 'pre-wrap' }, children: text })),
|
|
58
|
+
};
|
|
59
|
+
export const EmailButton = {
|
|
60
|
+
label: 'Button',
|
|
61
|
+
fields: {
|
|
62
|
+
label: { type: 'text', label: 'Label' },
|
|
63
|
+
href: { type: 'text', label: 'Link' },
|
|
64
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
65
|
+
backgroundColor: createColorField({ label: 'Background color' }),
|
|
66
|
+
textColor: createColorField({ label: 'Text color' }),
|
|
67
|
+
},
|
|
68
|
+
defaultProps: {
|
|
69
|
+
label: 'Call to action',
|
|
70
|
+
href: '#',
|
|
71
|
+
align: 'left',
|
|
72
|
+
backgroundColor: '#1f2937',
|
|
73
|
+
textColor: '#ffffff',
|
|
74
|
+
},
|
|
75
|
+
render: ({ label, href, align, backgroundColor, textColor }) => (_jsx("div", { style: { textAlign: align, margin: '8px 0' }, children: _jsx("a", { href: href || '#', style: {
|
|
76
|
+
display: 'inline-block',
|
|
77
|
+
padding: '12px 22px',
|
|
78
|
+
backgroundColor: backgroundColor || '#1f2937',
|
|
79
|
+
color: textColor || '#ffffff',
|
|
80
|
+
borderRadius: 6,
|
|
81
|
+
textDecoration: 'none',
|
|
82
|
+
fontWeight: 'bold',
|
|
83
|
+
}, children: label }) })),
|
|
84
|
+
};
|
|
85
|
+
export const EmailImage = {
|
|
86
|
+
label: 'Image',
|
|
87
|
+
fields: {
|
|
88
|
+
imageSource: {
|
|
89
|
+
type: 'select',
|
|
90
|
+
label: 'Image source',
|
|
91
|
+
options: [
|
|
92
|
+
{ label: 'URL', value: 'url' },
|
|
93
|
+
{ label: 'Asset library', value: 'library' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
src: { type: 'text', label: 'Image URL' },
|
|
97
|
+
assetId: { type: 'text', label: 'Image' },
|
|
98
|
+
alt: { type: 'text', label: 'Alt text' },
|
|
99
|
+
href: { type: 'text', label: 'Link (optional)' },
|
|
100
|
+
width: { type: 'number', label: 'Width (px)' },
|
|
101
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
102
|
+
},
|
|
103
|
+
defaultProps: {
|
|
104
|
+
imageSource: 'url',
|
|
105
|
+
src: '',
|
|
106
|
+
assetId: '',
|
|
107
|
+
alt: '',
|
|
108
|
+
href: '',
|
|
109
|
+
width: 200,
|
|
110
|
+
align: 'center',
|
|
111
|
+
},
|
|
112
|
+
resolveFields: (data, { fields }) => {
|
|
113
|
+
const source = data.props.imageSource ?? 'url';
|
|
114
|
+
const next = { ...fields };
|
|
115
|
+
if (source === 'library') {
|
|
116
|
+
delete next['src'];
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
delete next['assetId'];
|
|
120
|
+
}
|
|
121
|
+
return next;
|
|
122
|
+
},
|
|
123
|
+
render: ({ src, alt, width, align }) => {
|
|
124
|
+
const margin = align === 'center' ? '0 auto' : align === 'right' ? '0 0 0 auto' : '0';
|
|
125
|
+
return (_jsx("div", { style: { textAlign: align }, children: src ? (_jsx("img", { src: src, alt: alt, width: width, style: { display: 'block', maxWidth: '100%', margin } })) : (_jsx("span", { style: { color: '#9ca3af' }, children: "[image]" })) }));
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
export const EmailLogo = {
|
|
129
|
+
label: 'Logo',
|
|
130
|
+
fields: {
|
|
131
|
+
alt: { type: 'text', label: 'Alt text' },
|
|
132
|
+
href: { type: 'text', label: 'Link (optional)' },
|
|
133
|
+
width: { type: 'number', label: 'Width (px)' },
|
|
134
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
135
|
+
},
|
|
136
|
+
defaultProps: {
|
|
137
|
+
src: BRANDING_LOGO_SRC,
|
|
138
|
+
alt: 'Logo',
|
|
139
|
+
href: '',
|
|
140
|
+
width: 160,
|
|
141
|
+
align: 'center',
|
|
142
|
+
},
|
|
143
|
+
resolveData: async ({ props }) => ({
|
|
144
|
+
props: {
|
|
145
|
+
...props,
|
|
146
|
+
src: BRANDING_LOGO_SRC,
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
render: ({ alt, width, align }) => {
|
|
150
|
+
// Puck calls `render` as a React component — it is mounted, not invoked —
|
|
151
|
+
// so a hook here obeys the rules of hooks. The linter cannot see that
|
|
152
|
+
// through `ComponentConfig`, and it is the field's name rather than the
|
|
153
|
+
// call that it objects to. Surfaced when feature 091 registered
|
|
154
|
+
// `react-hooks` for every `.tsx` in the repository rather than for the
|
|
155
|
+
// admin application alone; the call has always been correct.
|
|
156
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
157
|
+
const { logoUrl } = useEmailBrandingPreview();
|
|
158
|
+
const margin = align === 'center' ? '0 auto' : align === 'right' ? '0 0 0 auto' : '0';
|
|
159
|
+
return (_jsx("div", { style: { textAlign: align, width: '100%' }, children: logoUrl ? (_jsx("img", { src: logoUrl, alt: alt || 'Logo', width: width, style: { display: 'block', maxWidth: '100%', height: 'auto', margin } })) : (_jsxs("span", { style: { color: '#9ca3af', fontSize: 13, display: 'inline-block' }, children: ["[", alt || 'logo', " \u2014 set in Branding]"] })) }));
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
export const EmailDivider = {
|
|
163
|
+
label: 'Divider',
|
|
164
|
+
fields: {
|
|
165
|
+
thickness: { type: 'number', label: 'Thickness (px)', min: 1, max: 8 },
|
|
166
|
+
color: createColorField({ label: 'Color' }),
|
|
167
|
+
},
|
|
168
|
+
defaultProps: { thickness: 1, color: '#e5e7eb' },
|
|
169
|
+
render: ({ thickness, color }) => (_jsx("hr", { style: {
|
|
170
|
+
border: 0,
|
|
171
|
+
borderTop: `${Math.max(1, thickness || 1)}px solid ${color || '#e5e7eb'}`,
|
|
172
|
+
margin: '8px 0',
|
|
173
|
+
} })),
|
|
174
|
+
};
|
|
175
|
+
export const EmailSpacer = {
|
|
176
|
+
label: 'Spacer',
|
|
177
|
+
fields: { height: { type: 'number', label: 'Height (px)' } },
|
|
178
|
+
defaultProps: { height: 16 },
|
|
179
|
+
render: ({ height }) => _jsx("div", { style: { height } }),
|
|
180
|
+
};
|
|
181
|
+
function padEmailTableRow(row, colCount) {
|
|
182
|
+
const cells = [...(row.cells ?? [])];
|
|
183
|
+
while (cells.length < colCount)
|
|
184
|
+
cells.push({ value: '' });
|
|
185
|
+
return { cells: cells.slice(0, Math.max(colCount, 1)) };
|
|
186
|
+
}
|
|
187
|
+
export const EmailTable = {
|
|
188
|
+
label: 'Table',
|
|
189
|
+
fields: {
|
|
190
|
+
columns: {
|
|
191
|
+
type: 'array',
|
|
192
|
+
label: 'Columns',
|
|
193
|
+
getItemSummary: (item, index) => item.label?.trim() || `Column ${(index ?? 0) + 1}`,
|
|
194
|
+
arrayFields: {
|
|
195
|
+
label: { type: 'text', label: 'Header' },
|
|
196
|
+
},
|
|
197
|
+
defaultItemProps: { label: 'Column' },
|
|
198
|
+
min: 1,
|
|
199
|
+
max: 6,
|
|
200
|
+
},
|
|
201
|
+
tableRows: {
|
|
202
|
+
type: 'array',
|
|
203
|
+
label: 'Rows',
|
|
204
|
+
getItemSummary: (item, index) => {
|
|
205
|
+
const preview = (item.cells ?? [])
|
|
206
|
+
.map((c) => c.value?.trim())
|
|
207
|
+
.filter(Boolean)
|
|
208
|
+
.join(' | ');
|
|
209
|
+
return preview || `Row ${(index ?? 0) + 1}`;
|
|
210
|
+
},
|
|
211
|
+
arrayFields: {
|
|
212
|
+
cells: {
|
|
213
|
+
type: 'array',
|
|
214
|
+
label: 'Cells',
|
|
215
|
+
arrayFields: {
|
|
216
|
+
value: { type: 'text', label: 'Value' },
|
|
217
|
+
},
|
|
218
|
+
getItemSummary: (item, i) => item.value?.trim() || `Cell ${(i ?? 0) + 1}`,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
defaultItemProps: { cells: [{ value: '' }, { value: '' }] },
|
|
222
|
+
},
|
|
223
|
+
striped: {
|
|
224
|
+
type: 'radio',
|
|
225
|
+
label: 'Striped rows',
|
|
226
|
+
options: [
|
|
227
|
+
{ label: 'Yes', value: true },
|
|
228
|
+
{ label: 'No', value: false },
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
defaultProps: {
|
|
233
|
+
columns: [{ label: 'Column 1' }, { label: 'Column 2' }],
|
|
234
|
+
tableRows: [
|
|
235
|
+
{ cells: [{ value: 'A1' }, { value: 'B1' }] },
|
|
236
|
+
{ cells: [{ value: 'A2' }, { value: 'B2' }] },
|
|
237
|
+
],
|
|
238
|
+
striped: true,
|
|
239
|
+
},
|
|
240
|
+
resolveData: async ({ props }) => {
|
|
241
|
+
const columns = Array.isArray(props.columns) && props.columns.length > 0
|
|
242
|
+
? props.columns
|
|
243
|
+
: [{ label: 'Column 1' }];
|
|
244
|
+
const colCount = columns.length;
|
|
245
|
+
const tableRows = (Array.isArray(props.tableRows) ? props.tableRows : []).map((row) => padEmailTableRow(row, colCount));
|
|
246
|
+
return { props: { ...props, columns, tableRows } };
|
|
247
|
+
},
|
|
248
|
+
render: ({ columns, tableRows, striped }) => {
|
|
249
|
+
const cols = columns?.length ? columns : [{ label: 'Column 1' }];
|
|
250
|
+
const rows = (tableRows ?? []).map((row) => padEmailTableRow(row, cols.length));
|
|
251
|
+
return (_jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, children: [_jsx("thead", { children: _jsx("tr", { children: cols.map((c, i) => (_jsx("th", { style: {
|
|
252
|
+
textAlign: 'left',
|
|
253
|
+
borderBottom: '2px solid #e5e7eb',
|
|
254
|
+
padding: '6px 8px',
|
|
255
|
+
color: '#6b7280',
|
|
256
|
+
}, children: c.label }, i))) }) }), _jsx("tbody", { children: rows.map((row, ri) => (_jsx("tr", { style: striped && ri % 2 === 1 ? { background: '#f9fafb' } : undefined, children: (row.cells ?? []).map((cell, ci) => (_jsx("td", { style: { padding: '6px 8px', borderBottom: '1px solid #f3f4f6' }, children: cell.value }, ci))) }, ri))) })] }));
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
export const EmailSection = {
|
|
260
|
+
label: 'Section',
|
|
261
|
+
fields: {
|
|
262
|
+
backgroundColor: createColorField({ label: 'Background color' }),
|
|
263
|
+
paddingY: { type: 'number', label: 'Vertical padding (px)' },
|
|
264
|
+
paddingX: { type: 'number', label: 'Horizontal padding (px)' },
|
|
265
|
+
content: {
|
|
266
|
+
type: 'slot',
|
|
267
|
+
/** Columns only belong inside EmailRow. */
|
|
268
|
+
disallow: ['transactional_emails.EmailColumn'],
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
defaultProps: {
|
|
272
|
+
backgroundColor: '#ffffff',
|
|
273
|
+
paddingY: 16,
|
|
274
|
+
paddingX: 24,
|
|
275
|
+
content: [],
|
|
276
|
+
},
|
|
277
|
+
render: ({ backgroundColor, paddingY, paddingX, content: contentSlot, }) => (_jsx("div", { style: {
|
|
278
|
+
backgroundColor: backgroundColor || '#ffffff',
|
|
279
|
+
padding: `${paddingY ?? 16}px ${paddingX ?? 24}px`,
|
|
280
|
+
minHeight: 48,
|
|
281
|
+
}, children: typeof contentSlot === 'function' ? contentSlot() : null })),
|
|
282
|
+
};
|
|
283
|
+
const EMAIL_ROW_SLOT_EDIT_PROPS = {
|
|
284
|
+
minEmptyHeight: 144,
|
|
285
|
+
collisionAxis: 'dynamic',
|
|
286
|
+
className: 'cmsc-pb-slot cmsc-pb-row-slot',
|
|
287
|
+
};
|
|
288
|
+
const EMAIL_COLUMN_SLOT_EDIT_PROPS = {
|
|
289
|
+
minEmptyHeight: 0,
|
|
290
|
+
collisionAxis: 'y',
|
|
291
|
+
className: 'cmsc-pb-slot cmsc-pb-column-slot',
|
|
292
|
+
};
|
|
293
|
+
export const EmailColumn = {
|
|
294
|
+
label: 'Column',
|
|
295
|
+
/** Grid cell is .cmsc-pb-row-col — Puck must not wrap it in an extra block. */
|
|
296
|
+
inline: true,
|
|
297
|
+
fields: {
|
|
298
|
+
span: {
|
|
299
|
+
type: 'number',
|
|
300
|
+
label: 'Width (1–12)',
|
|
301
|
+
min: 1,
|
|
302
|
+
max: 12,
|
|
303
|
+
},
|
|
304
|
+
content: {
|
|
305
|
+
type: 'slot',
|
|
306
|
+
disallow: ['transactional_emails.EmailColumn'],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
defaultProps: {
|
|
310
|
+
span: 6,
|
|
311
|
+
content: [],
|
|
312
|
+
},
|
|
313
|
+
render: (props) => {
|
|
314
|
+
const { content: contentSlot, span, puck } = props;
|
|
315
|
+
const editing = puck?.isEditing !== false;
|
|
316
|
+
const colSpan = Math.min(12, Math.max(1, span ?? 6));
|
|
317
|
+
const Slot = typeof contentSlot === 'function' ? contentSlot : null;
|
|
318
|
+
return (_jsx("div", { ref: puck?.dragRef ?? undefined, className: "cmsc-pb-row-col cmsc:min-w-0 cmsc:self-stretch", style: { gridColumn: `span ${colSpan}` }, "data-pb-editing": editing ? '1' : '0', children: Slot ? createElement(Slot, EMAIL_COLUMN_SLOT_EDIT_PROPS) : null }));
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
export const EmailRow = {
|
|
322
|
+
label: 'Row',
|
|
323
|
+
fields: {
|
|
324
|
+
gap: { type: 'number', label: 'Column gap (px)' },
|
|
325
|
+
verticalAlign: {
|
|
326
|
+
type: 'select',
|
|
327
|
+
label: 'Vertical align',
|
|
328
|
+
options: VERTICAL_ALIGN_OPTIONS,
|
|
329
|
+
},
|
|
330
|
+
content: {
|
|
331
|
+
type: 'slot',
|
|
332
|
+
allow: ['transactional_emails.EmailColumn'],
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
defaultProps: {
|
|
336
|
+
gap: 16,
|
|
337
|
+
verticalAlign: 'top',
|
|
338
|
+
content: [],
|
|
339
|
+
},
|
|
340
|
+
render: (props) => {
|
|
341
|
+
const { gap, verticalAlign, content: contentSlot, puck } = props;
|
|
342
|
+
const editing = puck?.isEditing !== false;
|
|
343
|
+
const alignItems = verticalAlign === 'middle' ? 'center' : verticalAlign === 'bottom' ? 'flex-end' : 'stretch';
|
|
344
|
+
const Slot = typeof contentSlot === 'function' ? contentSlot : null;
|
|
345
|
+
return (_jsx("div", { className: `cmsc-pb-row-cols cmsc:w-full ${editing ? 'cmsc-pb-row-cols--editing' : ''}`, style: {
|
|
346
|
+
gap: gap ?? 16,
|
|
347
|
+
alignItems,
|
|
348
|
+
width: '100%',
|
|
349
|
+
boxSizing: 'border-box',
|
|
350
|
+
}, children: Slot
|
|
351
|
+
? editing
|
|
352
|
+
? createElement(Slot, EMAIL_ROW_SLOT_EDIT_PROPS)
|
|
353
|
+
: createElement(Slot)
|
|
354
|
+
: null }));
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
export const EmailRichText = {
|
|
358
|
+
label: 'Rich text',
|
|
359
|
+
fields: {
|
|
360
|
+
content: { type: 'textarea', label: 'Content (TipTap JSON — edited in admin)' },
|
|
361
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
362
|
+
},
|
|
363
|
+
defaultProps: {
|
|
364
|
+
content: null,
|
|
365
|
+
html: '<p>Rich text. Use {{var your.variable}} for dynamic values.</p>',
|
|
366
|
+
align: 'left',
|
|
367
|
+
},
|
|
368
|
+
render: ({ html, align }) => (_jsx("div", { style: { textAlign: align, fontSize: 15, lineHeight: 1.5, color: '#1f2937' }, dangerouslySetInnerHTML: { __html: html || '<p></p>' } })),
|
|
369
|
+
};
|
|
370
|
+
export const EmailProductCard = {
|
|
371
|
+
label: 'Product card',
|
|
372
|
+
fields: {
|
|
373
|
+
productSlug: { type: 'text', label: 'Product' },
|
|
374
|
+
showImage: {
|
|
375
|
+
type: 'radio',
|
|
376
|
+
label: 'Show image',
|
|
377
|
+
options: [
|
|
378
|
+
{ label: 'Yes', value: true },
|
|
379
|
+
{ label: 'No', value: false },
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
showSku: {
|
|
383
|
+
type: 'radio',
|
|
384
|
+
label: 'Show SKU',
|
|
385
|
+
options: [
|
|
386
|
+
{ label: 'Yes', value: true },
|
|
387
|
+
{ label: 'No', value: false },
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
showPrice: {
|
|
391
|
+
type: 'radio',
|
|
392
|
+
label: 'Show price',
|
|
393
|
+
options: [
|
|
394
|
+
{ label: 'Yes', value: true },
|
|
395
|
+
{ label: 'No', value: false },
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
price: { type: 'text', label: 'Price (override)' },
|
|
399
|
+
ctaLabel: { type: 'text', label: 'CTA label' },
|
|
400
|
+
href: { type: 'text', label: 'Link (override)' },
|
|
401
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
402
|
+
},
|
|
403
|
+
defaultProps: {
|
|
404
|
+
productSlug: '',
|
|
405
|
+
productId: '',
|
|
406
|
+
imageSrc: '',
|
|
407
|
+
title: 'Select a product',
|
|
408
|
+
sku: '',
|
|
409
|
+
price: '',
|
|
410
|
+
href: '',
|
|
411
|
+
ctaLabel: 'View product',
|
|
412
|
+
showImage: true,
|
|
413
|
+
showPrice: true,
|
|
414
|
+
showSku: false,
|
|
415
|
+
align: 'left',
|
|
416
|
+
},
|
|
417
|
+
render: ({ imageSrc, title, sku, price, ctaLabel, showImage, showPrice, showSku }) => {
|
|
418
|
+
const showImg = showImage !== false && showImage !== 'false';
|
|
419
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 12, padding: 8, border: '1px solid #e5e7eb', borderRadius: 6 }, children: [showImg ? (imageSrc && !String(imageSrc).includes('{{') ? (_jsx("img", { src: imageSrc, alt: "", width: 64, height: 64, style: { objectFit: 'cover', flexShrink: 0 } })) : (_jsx("div", { style: {
|
|
420
|
+
width: 64,
|
|
421
|
+
height: 64,
|
|
422
|
+
background: '#f3f4f6',
|
|
423
|
+
border: '1px dashed #d1d5db',
|
|
424
|
+
flexShrink: 0,
|
|
425
|
+
display: 'flex',
|
|
426
|
+
alignItems: 'center',
|
|
427
|
+
justifyContent: 'center',
|
|
428
|
+
fontSize: 10,
|
|
429
|
+
color: '#9ca3af',
|
|
430
|
+
textAlign: 'center',
|
|
431
|
+
lineHeight: 1.2,
|
|
432
|
+
padding: 4,
|
|
433
|
+
boxSizing: 'border-box',
|
|
434
|
+
}, children: "No image" }))) : null, _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600 }, children: title }), showSku && sku ? _jsx("div", { style: { fontSize: 12, color: '#6b7280' }, children: sku }) : null, showPrice !== false && price ? _jsx("div", { children: price }) : null, _jsx("div", { style: { marginTop: 4, fontSize: 13, color: '#1f2937' }, children: ctaLabel })] })] }));
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
export const EmailProductGrid = {
|
|
438
|
+
label: 'Product grid',
|
|
439
|
+
fields: {
|
|
440
|
+
productSlugs: { type: 'text', label: 'Products' },
|
|
441
|
+
columns: { type: 'number', label: 'Columns', min: 1, max: 3 },
|
|
442
|
+
gap: { type: 'number', label: 'Gap (px)', min: 0, max: 48 },
|
|
443
|
+
showImage: {
|
|
444
|
+
type: 'radio',
|
|
445
|
+
label: 'Show image',
|
|
446
|
+
options: [
|
|
447
|
+
{ label: 'Yes', value: true },
|
|
448
|
+
{ label: 'No', value: false },
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
showSku: {
|
|
452
|
+
type: 'radio',
|
|
453
|
+
label: 'Show SKU',
|
|
454
|
+
options: [
|
|
455
|
+
{ label: 'Yes', value: true },
|
|
456
|
+
{ label: 'No', value: false },
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
showPrice: {
|
|
460
|
+
type: 'radio',
|
|
461
|
+
label: 'Show price',
|
|
462
|
+
options: [
|
|
463
|
+
{ label: 'Yes', value: true },
|
|
464
|
+
{ label: 'No', value: false },
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
ctaLabel: { type: 'text', label: 'CTA label' },
|
|
468
|
+
},
|
|
469
|
+
defaultProps: {
|
|
470
|
+
productSlugs: [],
|
|
471
|
+
columns: 2,
|
|
472
|
+
gap: 16,
|
|
473
|
+
showImage: true,
|
|
474
|
+
showPrice: true,
|
|
475
|
+
showSku: false,
|
|
476
|
+
ctaLabel: 'View',
|
|
477
|
+
items: [],
|
|
478
|
+
},
|
|
479
|
+
render: ({ items, columns, showImage, showPrice, showSku, ctaLabel, }) => {
|
|
480
|
+
const cols = Math.min(3, Math.max(1, columns || 2));
|
|
481
|
+
const list = Array.isArray(items) ? items : [];
|
|
482
|
+
if (list.length === 0) {
|
|
483
|
+
return (_jsx("div", { style: { padding: 12, color: '#9ca3af', fontSize: 13, border: '1px dashed #e5e7eb' }, children: "Select products for the grid" }));
|
|
484
|
+
}
|
|
485
|
+
return (_jsx("div", { style: {
|
|
486
|
+
display: 'grid',
|
|
487
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
488
|
+
gap: 12,
|
|
489
|
+
}, children: list.map((item) => (_jsxs("div", { style: { fontSize: 13 }, children: [showImage !== false ? (item.imageSrc ? (_jsx("img", { src: item.imageSrc, alt: "", style: { width: '100%', height: 80, objectFit: 'cover', marginBottom: 6 } })) : (_jsx("div", { style: {
|
|
490
|
+
width: '100%',
|
|
491
|
+
height: 80,
|
|
492
|
+
background: '#f3f4f6',
|
|
493
|
+
border: '1px dashed #d1d5db',
|
|
494
|
+
marginBottom: 6,
|
|
495
|
+
display: 'flex',
|
|
496
|
+
alignItems: 'center',
|
|
497
|
+
justifyContent: 'center',
|
|
498
|
+
fontSize: 11,
|
|
499
|
+
color: '#9ca3af',
|
|
500
|
+
}, children: "No image" }))) : null, _jsx("div", { style: { fontWeight: 600 }, children: item.title }), showSku && item.sku ? _jsx("div", { style: { fontSize: 11, color: '#6b7280' }, children: item.sku }) : null, showPrice !== false && item.price ? _jsx("div", { children: item.price }) : null, _jsx("div", { style: { marginTop: 4, fontSize: 12 }, children: ctaLabel })] }, item.productId || item.productSlug))) }));
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
export const EmailCategoryGrid = {
|
|
504
|
+
label: 'Category grid',
|
|
505
|
+
fields: {
|
|
506
|
+
categorySlugs: { type: 'text', label: 'Categories' },
|
|
507
|
+
columns: { type: 'number', label: 'Columns', min: 1, max: 3 },
|
|
508
|
+
gap: { type: 'number', label: 'Gap (px)', min: 0, max: 48 },
|
|
509
|
+
showImage: {
|
|
510
|
+
type: 'radio',
|
|
511
|
+
label: 'Show image',
|
|
512
|
+
options: [
|
|
513
|
+
{ label: 'Yes', value: true },
|
|
514
|
+
{ label: 'No', value: false },
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
defaultProps: {
|
|
519
|
+
categorySlugs: [],
|
|
520
|
+
columns: 2,
|
|
521
|
+
gap: 16,
|
|
522
|
+
showImage: true,
|
|
523
|
+
items: [],
|
|
524
|
+
},
|
|
525
|
+
render: ({ items, columns, showImage }) => {
|
|
526
|
+
const cols = Math.min(3, Math.max(1, columns || 2));
|
|
527
|
+
const list = Array.isArray(items) ? items : [];
|
|
528
|
+
if (list.length === 0) {
|
|
529
|
+
return (_jsx("div", { style: { padding: 12, color: '#9ca3af', fontSize: 13, border: '1px dashed #e5e7eb' }, children: "Select categories for the grid" }));
|
|
530
|
+
}
|
|
531
|
+
return (_jsx("div", { style: {
|
|
532
|
+
display: 'grid',
|
|
533
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
534
|
+
gap: 12,
|
|
535
|
+
}, children: list.map((item) => (_jsxs("div", { style: { fontSize: 13 }, children: [showImage !== false ? (item.imageSrc ? (_jsx("img", { src: item.imageSrc, alt: "", style: { width: '100%', height: 64, objectFit: 'cover', marginBottom: 6 } })) : (_jsx("div", { style: {
|
|
536
|
+
width: '100%',
|
|
537
|
+
height: 64,
|
|
538
|
+
background: '#f3f4f6',
|
|
539
|
+
border: '1px dashed #d1d5db',
|
|
540
|
+
marginBottom: 6,
|
|
541
|
+
} }))) : null, _jsx("div", { style: { fontWeight: 600 }, children: item.title })] }, item.categoryId || item.categorySlug))) }));
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
export const EmailOrderSummary = {
|
|
545
|
+
label: 'Order summary',
|
|
546
|
+
fields: {
|
|
547
|
+
title: { type: 'text', label: 'Title' },
|
|
548
|
+
showSku: {
|
|
549
|
+
type: 'radio',
|
|
550
|
+
label: 'Show SKU column',
|
|
551
|
+
options: [
|
|
552
|
+
{ label: 'Yes', value: true },
|
|
553
|
+
{ label: 'No', value: false },
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
showName: {
|
|
557
|
+
type: 'radio',
|
|
558
|
+
label: 'Show name column',
|
|
559
|
+
options: [
|
|
560
|
+
{ label: 'Yes', value: true },
|
|
561
|
+
{ label: 'No', value: false },
|
|
562
|
+
],
|
|
563
|
+
},
|
|
564
|
+
showQuantity: {
|
|
565
|
+
type: 'radio',
|
|
566
|
+
label: 'Show quantity column',
|
|
567
|
+
options: [
|
|
568
|
+
{ label: 'Yes', value: true },
|
|
569
|
+
{ label: 'No', value: false },
|
|
570
|
+
],
|
|
571
|
+
},
|
|
572
|
+
showPrice: {
|
|
573
|
+
type: 'radio',
|
|
574
|
+
label: 'Show price column',
|
|
575
|
+
options: [
|
|
576
|
+
{ label: 'Yes', value: true },
|
|
577
|
+
{ label: 'No', value: false },
|
|
578
|
+
],
|
|
579
|
+
},
|
|
580
|
+
showTotals: {
|
|
581
|
+
type: 'radio',
|
|
582
|
+
label: 'Show totals',
|
|
583
|
+
options: [
|
|
584
|
+
{ label: 'Yes', value: true },
|
|
585
|
+
{ label: 'No', value: false },
|
|
586
|
+
],
|
|
587
|
+
},
|
|
588
|
+
marginTop: { type: 'number', label: 'Margin top (px)' },
|
|
589
|
+
marginBottom: { type: 'number', label: 'Margin bottom (px)' },
|
|
590
|
+
},
|
|
591
|
+
defaultProps: {
|
|
592
|
+
title: 'Order summary',
|
|
593
|
+
showSku: false,
|
|
594
|
+
showName: true,
|
|
595
|
+
showQuantity: true,
|
|
596
|
+
showPrice: true,
|
|
597
|
+
showTotals: true,
|
|
598
|
+
marginTop: EMAIL_ORDER_BLOCK_MARGIN_DEFAULT,
|
|
599
|
+
marginBottom: EMAIL_ORDER_BLOCK_MARGIN_DEFAULT,
|
|
600
|
+
},
|
|
601
|
+
render: ({ title, showSku, showName, showQuantity, showPrice, showTotals, marginTop, marginBottom, }) => {
|
|
602
|
+
const cols = [
|
|
603
|
+
showSku ? 'SKU' : null,
|
|
604
|
+
showName !== false ? 'Item' : null,
|
|
605
|
+
showQuantity !== false ? 'Qty' : null,
|
|
606
|
+
showPrice !== false ? 'Price' : null,
|
|
607
|
+
].filter(Boolean);
|
|
608
|
+
const mt = marginTop ?? EMAIL_ORDER_BLOCK_MARGIN_DEFAULT;
|
|
609
|
+
const mb = marginBottom ?? EMAIL_ORDER_BLOCK_MARGIN_DEFAULT;
|
|
610
|
+
return (_jsxs("div", { style: { padding: 8, marginTop: mt, marginBottom: mb }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 8 }, children: title }), _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, children: [_jsx("thead", { children: _jsx("tr", { children: cols.map((c) => (_jsx("th", { style: { textAlign: 'left', borderBottom: '1px solid #e5e7eb', padding: 4 }, children: c }, c))) }) }), _jsx("tbody", { children: _jsx("tr", { children: cols.map((c) => (_jsx("td", { style: { padding: 4, color: '#9ca3af' }, children: "\u2026" }, c))) }) })] }), showTotals ? (_jsx("div", { style: { marginTop: 8, fontSize: 13, color: '#6b7280' }, children: "Totals (order.summaryText)" })) : null] }));
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
function makeOrderLabeledVarConfig(name) {
|
|
614
|
+
const meta = EMAIL_ORDER_LABELED_FIELDS[name];
|
|
615
|
+
return {
|
|
616
|
+
label: meta.label,
|
|
617
|
+
fields: {
|
|
618
|
+
title: { type: 'text', label: 'Title' },
|
|
619
|
+
marginTop: { type: 'number', label: 'Margin top (px)' },
|
|
620
|
+
marginBottom: { type: 'number', label: 'Margin bottom (px)' },
|
|
621
|
+
},
|
|
622
|
+
defaultProps: {
|
|
623
|
+
title: meta.defaultTitle,
|
|
624
|
+
marginTop: EMAIL_ORDER_BLOCK_MARGIN_DEFAULT,
|
|
625
|
+
marginBottom: EMAIL_ORDER_BLOCK_MARGIN_DEFAULT,
|
|
626
|
+
},
|
|
627
|
+
render: ({ title, marginTop, marginBottom }) => {
|
|
628
|
+
const mt = marginTop ?? EMAIL_ORDER_BLOCK_MARGIN_DEFAULT;
|
|
629
|
+
const mb = marginBottom ?? EMAIL_ORDER_BLOCK_MARGIN_DEFAULT;
|
|
630
|
+
return (_jsxs("div", { style: { padding: 8, marginTop: mt, marginBottom: mb }, children: [title ? _jsx("div", { style: { fontWeight: 600, marginBottom: 6 }, children: title }) : null, _jsx("div", { style: { fontSize: 13, color: '#6b7280', whiteSpace: 'pre-wrap' }, children: meta.previewHint })] }));
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
export const EmailOrderId = makeOrderLabeledVarConfig('orders.EmailOrderId');
|
|
635
|
+
export const EmailBillingAddress = makeOrderLabeledVarConfig('orders.EmailBillingAddress');
|
|
636
|
+
export const EmailShippingAddress = makeOrderLabeledVarConfig('orders.EmailShippingAddress');
|
|
637
|
+
export const EmailOrderTotals = makeOrderLabeledVarConfig('orders.EmailOrderTotals');
|
|
638
|
+
export const EmailAppliedDiscounts = makeOrderLabeledVarConfig('orders.EmailAppliedDiscounts');
|
|
639
|
+
export const EmailDeliveryMethod = makeOrderLabeledVarConfig('orders.EmailDeliveryMethod');
|
|
640
|
+
export const EmailPaymentMethod = makeOrderLabeledVarConfig('orders.EmailPaymentMethod');
|
|
641
|
+
export const EmailSocial = {
|
|
642
|
+
label: 'Social links',
|
|
643
|
+
fields: {
|
|
644
|
+
links: {
|
|
645
|
+
type: 'array',
|
|
646
|
+
label: 'Links',
|
|
647
|
+
arrayFields: {
|
|
648
|
+
network: { type: 'select', label: 'Network', options: SOCIAL_NETWORK_OPTIONS },
|
|
649
|
+
href: { type: 'text', label: 'URL' },
|
|
650
|
+
label: { type: 'text', label: 'Label (optional)' },
|
|
651
|
+
enabled: {
|
|
652
|
+
type: 'radio',
|
|
653
|
+
label: 'Enabled',
|
|
654
|
+
options: [
|
|
655
|
+
{ label: 'Yes', value: true },
|
|
656
|
+
{ label: 'No', value: false },
|
|
657
|
+
],
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
getItemSummary: (item) => {
|
|
661
|
+
const on = item.enabled !== false ? '' : ' (off)';
|
|
662
|
+
return `${item.label || item.network || 'Link'}${on}`;
|
|
663
|
+
},
|
|
664
|
+
defaultItemProps: { network: 'facebook', href: '', label: '', enabled: true },
|
|
665
|
+
},
|
|
666
|
+
showIcons: {
|
|
667
|
+
type: 'radio',
|
|
668
|
+
label: 'Show icons',
|
|
669
|
+
options: [
|
|
670
|
+
{ label: 'Yes', value: true },
|
|
671
|
+
{ label: 'No', value: false },
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
showLabels: {
|
|
675
|
+
type: 'radio',
|
|
676
|
+
label: 'Show labels',
|
|
677
|
+
options: [
|
|
678
|
+
{ label: 'Yes', value: true },
|
|
679
|
+
{ label: 'No', value: false },
|
|
680
|
+
],
|
|
681
|
+
},
|
|
682
|
+
iconSize: { type: 'number', label: 'Icon size (px)', min: 16, max: 48 },
|
|
683
|
+
useBrandColors: {
|
|
684
|
+
type: 'radio',
|
|
685
|
+
label: 'Brand colors',
|
|
686
|
+
options: [
|
|
687
|
+
{ label: 'Yes', value: true },
|
|
688
|
+
{ label: 'No', value: false },
|
|
689
|
+
],
|
|
690
|
+
},
|
|
691
|
+
color: createColorField({ label: 'Icon / text color' }),
|
|
692
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
693
|
+
},
|
|
694
|
+
defaultProps: {
|
|
695
|
+
links: [
|
|
696
|
+
{ network: 'facebook', href: 'https://facebook.com/', enabled: true },
|
|
697
|
+
{ network: 'linkedin', href: 'https://linkedin.com/', enabled: true },
|
|
698
|
+
],
|
|
699
|
+
align: 'center',
|
|
700
|
+
showIcons: true,
|
|
701
|
+
showLabels: false,
|
|
702
|
+
iconSize: 28,
|
|
703
|
+
useBrandColors: true,
|
|
704
|
+
color: '#1f2937',
|
|
705
|
+
},
|
|
706
|
+
render: ({ links, align, showIcons, showLabels, iconSize, useBrandColors, color, }) => (_jsx("div", { style: { textAlign: align, padding: 8 }, children: (links ?? [])
|
|
707
|
+
.filter((l) => l?.enabled !== false && l?.href)
|
|
708
|
+
.map((l, i) => {
|
|
709
|
+
const network = l.network in EMAIL_SOCIAL_LABELS ? l.network : 'other';
|
|
710
|
+
const label = l.label || EMAIL_SOCIAL_LABELS[network];
|
|
711
|
+
const iconColor = useBrandColors ? EMAIL_SOCIAL_BRAND_COLORS[network] : color || '#1f2937';
|
|
712
|
+
const size = Math.max(16, iconSize || 28);
|
|
713
|
+
return (_jsxs("a", { href: l.href || '#', style: {
|
|
714
|
+
display: 'inline-flex',
|
|
715
|
+
alignItems: 'center',
|
|
716
|
+
gap: 6,
|
|
717
|
+
margin: '0 8px',
|
|
718
|
+
color: iconColor,
|
|
719
|
+
textDecoration: 'none',
|
|
720
|
+
verticalAlign: 'middle',
|
|
721
|
+
}, children: [showIcons !== false ? (_jsx("span", { style: {
|
|
722
|
+
display: 'inline-flex',
|
|
723
|
+
width: size,
|
|
724
|
+
height: size,
|
|
725
|
+
borderRadius: '50%',
|
|
726
|
+
background: iconColor,
|
|
727
|
+
color: '#fff',
|
|
728
|
+
fontSize: Math.round(size * 0.4),
|
|
729
|
+
fontWeight: 700,
|
|
730
|
+
alignItems: 'center',
|
|
731
|
+
justifyContent: 'center',
|
|
732
|
+
lineHeight: 1,
|
|
733
|
+
}, children: network === 'facebook'
|
|
734
|
+
? 'f'
|
|
735
|
+
: network === 'instagram'
|
|
736
|
+
? 'ig'
|
|
737
|
+
: network === 'linkedin'
|
|
738
|
+
? 'in'
|
|
739
|
+
: network === 'x'
|
|
740
|
+
? 'X'
|
|
741
|
+
: network === 'youtube'
|
|
742
|
+
? 'yt'
|
|
743
|
+
: network === 'tiktok'
|
|
744
|
+
? 'tt'
|
|
745
|
+
: '·' })) : null, showLabels ? _jsx("span", { style: { fontSize: 13 }, children: label }) : null] }, i));
|
|
746
|
+
}) })),
|
|
747
|
+
};
|
|
748
|
+
export const EmailCallout = {
|
|
749
|
+
label: 'Callout',
|
|
750
|
+
fields: {
|
|
751
|
+
text: { type: 'textarea', label: 'Text' },
|
|
752
|
+
backgroundColor: createColorField({ label: 'Background' }),
|
|
753
|
+
borderColor: createColorField({ label: 'Border' }),
|
|
754
|
+
textColor: createColorField({ label: 'Text color' }),
|
|
755
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
756
|
+
},
|
|
757
|
+
defaultProps: {
|
|
758
|
+
text: 'Important notice',
|
|
759
|
+
backgroundColor: '#f3f4f6',
|
|
760
|
+
borderColor: '#d1d5db',
|
|
761
|
+
textColor: '#1f2937',
|
|
762
|
+
align: 'left',
|
|
763
|
+
},
|
|
764
|
+
render: ({ text, backgroundColor, borderColor, textColor, align }) => (_jsx("div", { style: {
|
|
765
|
+
backgroundColor,
|
|
766
|
+
border: `1px solid ${borderColor}`,
|
|
767
|
+
borderRadius: 6,
|
|
768
|
+
padding: 14,
|
|
769
|
+
textAlign: align,
|
|
770
|
+
color: textColor || '#1f2937',
|
|
771
|
+
}, children: text })),
|
|
772
|
+
};
|
|
773
|
+
export const EmailFooterLegal = {
|
|
774
|
+
label: 'Footer / legal',
|
|
775
|
+
fields: {
|
|
776
|
+
text: { type: 'textarea', label: 'Text' },
|
|
777
|
+
align: { type: 'select', label: 'Align', options: ALIGN_OPTIONS },
|
|
778
|
+
},
|
|
779
|
+
defaultProps: {
|
|
780
|
+
text: 'You received this email because you are subscribed.\n<a href="{{var unsubscribeUrl}}">Unsubscribe</a>',
|
|
781
|
+
align: 'center',
|
|
782
|
+
},
|
|
783
|
+
render: ({ text, align }) => (_jsx("p", { style: { margin: 0, fontSize: 12, color: '#6b7280', textAlign: align }, dangerouslySetInnerHTML: {
|
|
784
|
+
__html: (text || '').replace(/\r?\n/g, '<br />'),
|
|
785
|
+
} })),
|
|
786
|
+
};
|
|
787
|
+
export const EmailInsertBlock = {
|
|
788
|
+
label: 'Insert block',
|
|
789
|
+
fields: { code: { type: 'text', label: 'Block code' } },
|
|
790
|
+
defaultProps: { code: '' },
|
|
791
|
+
render: ({ code }) => {
|
|
792
|
+
// Puck mounts `render` as a component — see `EmailLogo` above.
|
|
793
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
794
|
+
const { blocks } = useEmailEmbeds();
|
|
795
|
+
const preview = code ? blocks[code] : null;
|
|
796
|
+
if (preview)
|
|
797
|
+
return _jsx(_Fragment, { children: preview });
|
|
798
|
+
return (_jsx("div", { style: { padding: 8, border: '1px dashed #cbd5e1', color: '#64748b' }, children: code ? `Block "${code}"` : 'Choose a block' }));
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
/**
|
|
802
|
+
* Every entry in this map declares `['email']` and nothing else (feature 096,
|
|
803
|
+
* D-10).
|
|
804
|
+
*
|
|
805
|
+
* It wrote `['email', 'newsletter']` until 2026-09-03, through a constant named
|
|
806
|
+
* `emailContexts`, and that is the spelling that diverged from the manifests:
|
|
807
|
+
* **no block in this repository declares `newsletter`**, and that member of
|
|
808
|
+
* `PageBuilderContext` has never had a block of its own. `contexts` states what
|
|
809
|
+
* a block is *authored for*; which palette it appears in is derived, by
|
|
810
|
+
* `contextAdmits` — one implementation in
|
|
811
|
+
* `@endora-commerce/page-builder-core`, imported by `filterConfigByContext`,
|
|
812
|
+
* `getDisallowedComponentNames` and every other reader.
|
|
813
|
+
*/
|
|
814
|
+
function emailComponent(config) {
|
|
815
|
+
return definePageBuilderComponent({
|
|
816
|
+
...config,
|
|
817
|
+
contexts: ['email'],
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Hide palette entries that require variables not declared on the current email
|
|
822
|
+
* (e.g. Order summary needs `order.items`).
|
|
823
|
+
*
|
|
824
|
+
* Hidden names are removed from both `categories` and `components` so Puck does
|
|
825
|
+
* not dump them into the uncategorized "Other" drawer group.
|
|
826
|
+
*/
|
|
827
|
+
export function filterEmailPaletteByVariables(config, variableKeys) {
|
|
828
|
+
const available = new Set(variableKeys);
|
|
829
|
+
const hide = new Set(Object.entries(EMAIL_COMPONENT_REQUIRED_VARIABLES)
|
|
830
|
+
.filter(([, required]) => !required.every((key) => available.has(key)))
|
|
831
|
+
.map(([name]) => name));
|
|
832
|
+
if (hide.size === 0)
|
|
833
|
+
return config;
|
|
834
|
+
const components = {};
|
|
835
|
+
for (const [name, component] of Object.entries(config.components ?? {})) {
|
|
836
|
+
if (!hide.has(name))
|
|
837
|
+
components[name] = component;
|
|
838
|
+
}
|
|
839
|
+
const categories = {};
|
|
840
|
+
for (const [key, category] of Object.entries(config.categories ?? {})) {
|
|
841
|
+
const list = (category.components ?? []).filter((name) => !hide.has(name) && name in components);
|
|
842
|
+
if (list.length > 0) {
|
|
843
|
+
categories[key] = { ...category, components: list };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return { ...config, components, categories };
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* The e-mail renderer map — **the React half only**, keyed by namespaced block
|
|
850
|
+
* name (feature 096, T302; `contracts/block-definition.md` §4.3).
|
|
851
|
+
*
|
|
852
|
+
* **The four `categories` are gone**, with `defaultExpanded` (D-11: on Puck
|
|
853
|
+
* `0.20.2` `defaultExpanded: true` is indistinguishable from omitting the
|
|
854
|
+
* field, so deleting it is behaviour-preserving). Sections are declared by the
|
|
855
|
+
* modules whose blocks occupy them — `content`, `layout`, `embeds` and the
|
|
856
|
+
* hidden `internal` by `transactional_emails`, `order` by `orders`, and
|
|
857
|
+
* `content` jointly by `catalog` — and served merged by
|
|
858
|
+
* `GET /api/v1/admin/cms/page-builder/config`.
|
|
859
|
+
*
|
|
860
|
+
* **`emailContexts` is gone too** (D-10). It wrote `['email', 'newsletter']`
|
|
861
|
+
* onto every entry, which is what diverged from the declarations: no block in
|
|
862
|
+
* this repository declares `newsletter`, that member of `PageBuilderContext`
|
|
863
|
+
* has never had a block of its own, and the newsletter palette **is** the
|
|
864
|
+
* e-mail palette. The relation that makes it so — `email` admits into
|
|
865
|
+
* `newsletter` — is `contextAdmits` in `@endora-commerce/page-builder-core`,
|
|
866
|
+
* one implementation, imported by every reader. Widening the declarations
|
|
867
|
+
* instead buys no behaviour and leaves two mechanisms answering one question.
|
|
868
|
+
*/
|
|
869
|
+
export const defaultEmailBuilderConfig = {
|
|
870
|
+
components: {
|
|
871
|
+
'transactional_emails.EmailHeading': emailComponent(EmailHeading),
|
|
872
|
+
'transactional_emails.EmailText': emailComponent(EmailText),
|
|
873
|
+
'transactional_emails.EmailRichText': emailComponent(EmailRichText),
|
|
874
|
+
'transactional_emails.EmailButton': emailComponent(EmailButton),
|
|
875
|
+
'transactional_emails.EmailImage': emailComponent(EmailImage),
|
|
876
|
+
'transactional_emails.EmailLogo': emailComponent(EmailLogo),
|
|
877
|
+
'catalog.EmailProductCard': emailComponent(EmailProductCard),
|
|
878
|
+
'catalog.EmailProductGrid': emailComponent(EmailProductGrid),
|
|
879
|
+
'catalog.EmailCategoryGrid': emailComponent(EmailCategoryGrid),
|
|
880
|
+
'orders.EmailOrderSummary': emailComponent(EmailOrderSummary),
|
|
881
|
+
'orders.EmailOrderId': emailComponent(EmailOrderId),
|
|
882
|
+
'orders.EmailBillingAddress': emailComponent(EmailBillingAddress),
|
|
883
|
+
'orders.EmailShippingAddress': emailComponent(EmailShippingAddress),
|
|
884
|
+
'orders.EmailOrderTotals': emailComponent(EmailOrderTotals),
|
|
885
|
+
'orders.EmailAppliedDiscounts': emailComponent(EmailAppliedDiscounts),
|
|
886
|
+
'orders.EmailDeliveryMethod': emailComponent(EmailDeliveryMethod),
|
|
887
|
+
'orders.EmailPaymentMethod': emailComponent(EmailPaymentMethod),
|
|
888
|
+
'transactional_emails.EmailSocial': emailComponent(EmailSocial),
|
|
889
|
+
'transactional_emails.EmailCallout': emailComponent(EmailCallout),
|
|
890
|
+
'transactional_emails.EmailFooterLegal': emailComponent(EmailFooterLegal),
|
|
891
|
+
'transactional_emails.EmailSection': emailComponent(EmailSection),
|
|
892
|
+
'transactional_emails.EmailRow': emailComponent(EmailRow),
|
|
893
|
+
'transactional_emails.EmailColumn': emailComponent(EmailColumn),
|
|
894
|
+
'transactional_emails.EmailDivider': emailComponent(EmailDivider),
|
|
895
|
+
'transactional_emails.EmailSpacer': emailComponent(EmailSpacer),
|
|
896
|
+
'transactional_emails.EmailTable': emailComponent(EmailTable),
|
|
897
|
+
'transactional_emails.EmailInsertBlock': emailComponent(EmailInsertBlock),
|
|
898
|
+
},
|
|
899
|
+
};
|
|
900
|
+
//# sourceMappingURL=config.js.map
|