astro-tractstack 2.3.1 → 2.3.2

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.
Files changed (53) hide show
  1. package/dist/index.js +36 -3
  2. package/package.json +1 -1
  3. package/templates/custom/shopify/Cart.tsx +16 -5
  4. package/templates/custom/shopify/CheckoutModal.tsx +4 -4
  5. package/templates/custom/shopify/ShopifyCartManager.tsx +27 -36
  6. package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
  7. package/templates/custom/shopify/ShopifyProductGrid.tsx +42 -14
  8. package/templates/custom/shopify/ShopifyServiceList.tsx +94 -50
  9. package/templates/src/components/Footer.astro +2 -2
  10. package/templates/src/components/Header.astro +14 -8
  11. package/templates/src/components/Menu.tsx +157 -135
  12. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  13. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +27 -6
  14. package/templates/src/components/codehooks/EpinetTableView.tsx +153 -112
  15. package/templates/src/components/codehooks/EpinetWrapper.tsx +4 -1
  16. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +8 -1
  17. package/templates/src/components/codehooks/ProductCardSetup.tsx +9 -1
  18. package/templates/src/components/codehooks/ProductGridSetup.tsx +9 -1
  19. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +2 -1
  20. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
  21. package/templates/src/components/edit/ToolBar.tsx +2 -1
  22. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +2 -2
  23. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +13 -0
  24. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
  25. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  26. package/templates/src/components/edit/state/SaveModal.tsx +1 -1
  27. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +8 -3
  28. package/templates/src/components/form/DateTimeInput.tsx +10 -3
  29. package/templates/src/components/form/FileUpload.tsx +11 -5
  30. package/templates/src/components/form/NumberInput.tsx +2 -2
  31. package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -38
  32. package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
  33. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +7 -8
  34. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
  35. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +2 -2
  36. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +79 -51
  37. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -0
  38. package/templates/src/components/storykeep/email-builder/Blocks.tsx +169 -0
  39. package/templates/src/components/storykeep/email-builder/EmailBuilder.tsx +223 -0
  40. package/templates/src/components/storykeep/email-builder/PreviewModal.tsx +136 -0
  41. package/templates/src/components/storykeep/email-builder/PropertyPanel.tsx +154 -0
  42. package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +1 -8
  43. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +32 -6
  44. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -0
  45. package/templates/src/layouts/Layout.astro +8 -5
  46. package/templates/src/stores/shopify.ts +16 -0
  47. package/templates/src/types/formTypes.ts +4 -2
  48. package/templates/src/types/tractstack.ts +5 -2
  49. package/templates/src/utils/api/brandConfig.ts +2 -0
  50. package/templates/src/utils/api/brandHelpers.ts +16 -0
  51. package/templates/src/utils/api/emailHelpers.ts +105 -0
  52. package/templates/src/utils/tenantResolver.ts +1 -1
  53. package/utils/inject-files.ts +34 -1
@@ -1,4 +1,4 @@
1
- import { forwardRef, useId } from 'react';
1
+ import { forwardRef, useId, type ChangeEvent } from 'react';
2
2
  import { classNames } from '@/utils/helpers';
3
3
 
4
4
  interface NumberInputProps {
@@ -42,7 +42,7 @@ const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
42
42
  const errorId = `${inputId}-error`;
43
43
  const inputName = customName || inputId;
44
44
 
45
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
45
+ const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
46
46
  const newValue = parseFloat(e.target.value);
47
47
  if (!isNaN(newValue)) {
48
48
  onChange(newValue);
@@ -212,7 +212,7 @@ export default function APIConfigSection({
212
212
 
213
213
  <BooleanToggle
214
214
  label="Webhooks Manually Configured"
215
- description="I have manually created the required webhooks (orders/paid, products/*) in my Shopify Admin."
215
+ description="I have manually created the required webhooks in my Shopify Admin."
216
216
  value={state.userSetupWebhooks}
217
217
  onChange={(value) =>
218
218
  updateField('userSetupWebhooks', value)
@@ -300,7 +300,7 @@ export default function APIConfigSection({
300
300
  <ul className="mt-2 list-disc space-y-1 pl-5">
301
301
  <li>
302
302
  <strong>Event:</strong> Select the specific event (e.g.,
303
- Order payment, Product creation).
303
+ Order payment, Product update).
304
304
  </li>
305
305
  <li>
306
306
  <strong>Format:</strong> Select JSON (TractStack relies
@@ -358,12 +358,6 @@ export default function APIConfigSection({
358
358
  </span>{' '}
359
359
  Order payment
360
360
  </li>
361
- <li>
362
- <span className="font-bold text-gray-900">
363
- Topic Header:
364
- </span>{' '}
365
- orders/paid
366
- </li>
367
361
  <li>
368
362
  <span className="font-bold text-gray-900">
369
363
  Purpose:
@@ -374,24 +368,6 @@ export default function APIConfigSection({
374
368
  </ul>
375
369
  </div>
376
370
 
377
- <div className="rounded border border-gray-200 bg-gray-50 p-4">
378
- <h5 className="font-bold">2. Product Creation</h5>
379
- <ul className="mt-2 space-y-1 text-xs">
380
- <li>
381
- <span className="font-bold text-gray-900">
382
- Shopify Event Name:
383
- </span>{' '}
384
- Product creation
385
- </li>
386
- <li>
387
- <span className="font-bold text-gray-900">
388
- Topic Header:
389
- </span>{' '}
390
- products/create
391
- </li>
392
- </ul>
393
- </div>
394
-
395
371
  <div className="rounded border border-gray-200 bg-gray-50 p-4">
396
372
  <h5 className="font-bold">3. Product Update</h5>
397
373
  <ul className="mt-2 space-y-1 text-xs">
@@ -401,12 +377,6 @@ export default function APIConfigSection({
401
377
  </span>{' '}
402
378
  Product update
403
379
  </li>
404
- <li>
405
- <span className="font-bold text-gray-900">
406
- Topic Header:
407
- </span>{' '}
408
- products/update
409
- </li>
410
380
  </ul>
411
381
  </div>
412
382
 
@@ -419,12 +389,6 @@ export default function APIConfigSection({
419
389
  </span>{' '}
420
390
  Product deletion
421
391
  </li>
422
- <li>
423
- <span className="font-bold text-gray-900">
424
- Topic Header:
425
- </span>{' '}
426
- products/delete
427
- </li>
428
392
  </ul>
429
393
  </div>
430
394
  </div>
@@ -48,6 +48,16 @@ export default function SiteConfigSection({
48
48
  error={errors.footer}
49
49
  />
50
50
 
51
+ <StringInput
52
+ value={state.adminEmail}
53
+ onChange={(value) => updateField('adminEmail', value)}
54
+ label="Admin Email"
55
+ type="email"
56
+ placeholder="admin@example.com"
57
+ required={true}
58
+ error={errors.adminEmail}
59
+ />
60
+
51
61
  <StringInput
52
62
  value={state.gtag}
53
63
  onChange={(value) => updateField('gtag', value)}
@@ -26,6 +26,7 @@ import ShopifyDashboard_Services from './shopify/ShopifyDashboard_Services';
26
26
  import ShopifyDashboard_Schedule from './shopify/ShopifyDashboard_Schedule';
27
27
  import ShopifyDashboard_Search from './shopify/ShopifyDashboard_Search';
28
28
  import ShopifyDashboard_Bookings from './shopify/ShopifyDashboard_Bookings';
29
+ import ShopifyDashboard_Emails from './shopify/ShopifyDashboard_Emails';
29
30
 
30
31
  interface DashboardShopifyProps {
31
32
  brandConfig: BrandConfig;
@@ -78,7 +79,8 @@ export default function StoryKeepDashboard_Shopify({
78
79
  { id: 'products', name: 'Products' },
79
80
  { id: 'services', name: 'Services' },
80
81
  { id: 'schedule', name: 'Schedule' },
81
- { id: 'search', name: 'Search' },
82
+ { id: 'search', name: 'Import Products' },
83
+ { id: 'emails', name: 'Emails' },
82
84
  ];
83
85
 
84
86
  useEffect(() => {
@@ -171,7 +173,7 @@ export default function StoryKeepDashboard_Shopify({
171
173
 
172
174
  const executePreFlightCheck = (category: string, product: ShopifyProduct) => {
173
175
  const hasMode = product.options.some((opt) => opt.name === 'Mode');
174
- if (hasMode) {
176
+ if (category === 'service' || hasMode) {
175
177
  startCreateFlow(category, product);
176
178
  } else {
177
179
  setPendingImport({ category, product });
@@ -465,15 +467,12 @@ export default function StoryKeepDashboard_Shopify({
465
467
  </div>
466
468
  )}
467
469
 
468
- {activeTab === 'dashboards' && (
469
- <ShopifyDashboard existingResources={resources} />
470
- )}
470
+ {activeTab === 'dashboards' && <ShopifyDashboard />}
471
471
 
472
472
  {activeTab === 'bookings' && (
473
473
  <ShopifyDashboard_Bookings existingResources={resources} />
474
474
  )}
475
475
 
476
- {/* Local Management Tabs */}
477
476
  {activeTab === 'products' && (
478
477
  <ShopifyDashboard_Products
479
478
  resources={resources}
@@ -492,12 +491,10 @@ export default function StoryKeepDashboard_Shopify({
492
491
  />
493
492
  )}
494
493
 
495
- {/* Schedule Tab */}
496
494
  {activeTab === 'schedule' && (
497
495
  <ShopifyDashboard_Schedule brandConfig={brandConfig} />
498
496
  )}
499
497
 
500
- {/* Catalog Discovery Tab */}
501
498
  {activeTab === 'search' && (
502
499
  <ShopifyDashboard_Search
503
500
  linkedResourceMap={linkedResourceMap}
@@ -507,6 +504,8 @@ export default function StoryKeepDashboard_Shopify({
507
504
  onEdit={handleEditFromCatalog}
508
505
  />
509
506
  )}
507
+
508
+ {activeTab === 'emails' && <ShopifyDashboard_Emails />}
510
509
  </div>
511
510
 
512
511
  {/* Shared Modals */}
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, type KeyboardEvent } from 'react';
2
2
  import { useStore } from '@nanostores/react';
3
3
  import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
4
4
  import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
@@ -125,7 +125,7 @@ export default function BeliefForm({
125
125
  }
126
126
  };
127
127
 
128
- const handleKeyDown = (e: React.KeyboardEvent) => {
128
+ const handleKeyDown = (e: KeyboardEvent) => {
129
129
  if (e.key === 'Enter') {
130
130
  e.preventDefault();
131
131
  handleAddCustomValue();
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useEffect } from 'react';
1
+ import { useState, useRef, useEffect, type ChangeEvent } from 'react';
2
2
  import { useStore } from '@nanostores/react';
3
3
  import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon';
4
4
  import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon';
@@ -53,7 +53,7 @@ export default function ProductTable({
53
53
  };
54
54
  }, []);
55
55
 
56
- const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
56
+ const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
57
57
  const val = e.target.value;
58
58
  setInputValue(val);
59
59
  setIsDebouncing(true);
@@ -7,6 +7,8 @@ interface ResourceBulkIngestProps {
7
7
  onClose: (saved: boolean) => void;
8
8
  onRefresh: () => void;
9
9
  fullContentMap: FullContentMapItem[];
10
+ /** When set, placeholder shows one example for this category only (validation still accepts all types). */
11
+ exampleCategorySlug?: string;
10
12
  }
11
13
 
12
14
  interface ParsedResource {
@@ -40,10 +42,63 @@ interface FieldDefinition {
40
42
  maxNumber?: number;
41
43
  }
42
44
 
45
+ function buildExampleObjectForCategory(
46
+ categorySlug: string,
47
+ index: number,
48
+ knownResources: Record<string, Record<string, FieldDefinition>>,
49
+ categoryKeys: string[]
50
+ ): Record<string, unknown> {
51
+ const schema = knownResources[categorySlug];
52
+ const example: Record<string, unknown> = {
53
+ title: `Example ${categorySlug.charAt(0).toUpperCase() + categorySlug.slice(1)} ${index + 1}`,
54
+ slug: `${categorySlug}-example-${index + 1}`,
55
+ category: categorySlug,
56
+ oneliner: `A brief description of this ${categorySlug}`,
57
+ };
58
+
59
+ Object.entries(schema).forEach(([key, def]: [string, FieldDefinition]) => {
60
+ switch (def.type) {
61
+ case 'string':
62
+ if (
63
+ def.belongsToCategory &&
64
+ categoryKeys.includes(def.belongsToCategory)
65
+ ) {
66
+ example[key] = `${def.belongsToCategory}-example-slug`;
67
+ } else {
68
+ example[key] = def.defaultValue || `example ${key}`;
69
+ }
70
+ break;
71
+ case 'number':
72
+ example[key] = def.defaultValue ?? (def.minNumber || 0);
73
+ break;
74
+ case 'boolean':
75
+ example[key] = def.defaultValue ?? true;
76
+ break;
77
+ case 'multi':
78
+ example[key] = def.defaultValue || [
79
+ `example ${key} 1`,
80
+ `example ${key} 2`,
81
+ ];
82
+ break;
83
+ case 'date':
84
+ example[key] = new Date().toISOString();
85
+ break;
86
+ case 'image':
87
+ example[key] = 'file-id-placeholder';
88
+ break;
89
+ default:
90
+ example[key] = def.defaultValue || `example ${key}`;
91
+ }
92
+ });
93
+
94
+ return example;
95
+ }
96
+
43
97
  export default function ResourceBulkIngest({
44
98
  onClose,
45
99
  onRefresh,
46
100
  fullContentMap,
101
+ exampleCategorySlug,
47
102
  }: ResourceBulkIngestProps) {
48
103
  const [brandConfig, setBrandConfig] = useState<BrandConfig | null>(null);
49
104
  const [loading, setLoading] = useState(false);
@@ -418,7 +473,7 @@ export default function ResourceBulkIngest({
418
473
  };
419
474
  }, [jsonInput, knownResources, fullContentMap]);
420
475
 
421
- // Generate example JSON based on available categories - ENHANCED VERSION
476
+ // Placeholder JSON: one category when exampleCategorySlug is set, else one object per known category
422
477
  const exampleJson = useMemo(() => {
423
478
  const categories = Object.keys(knownResources);
424
479
  if (categories.length === 0) {
@@ -436,59 +491,32 @@ export default function ResourceBulkIngest({
436
491
  );
437
492
  }
438
493
 
439
- // Create examples for ALL categories, not just the first one
440
- const examples = categories.map((categorySlug, index) => {
441
- const schema = knownResources[categorySlug];
442
- const example: any = {
443
- title: `Example ${categorySlug.charAt(0).toUpperCase() + categorySlug.slice(1)} ${index + 1}`,
444
- slug: `${categorySlug}-example-${index + 1}`,
445
- category: categorySlug,
446
- oneliner: `A brief description of this ${categorySlug}`,
447
- };
448
-
449
- // Add example values for schema fields
450
- Object.entries(schema).forEach(
451
- ([key, def]: [string, FieldDefinition]) => {
452
- switch (def.type) {
453
- case 'string':
454
- if (
455
- def.belongsToCategory &&
456
- categories.includes(def.belongsToCategory)
457
- ) {
458
- example[key] = `${def.belongsToCategory}-example-slug`;
459
- } else {
460
- example[key] = def.defaultValue || `example ${key}`;
461
- }
462
- break;
463
- case 'number':
464
- example[key] = def.defaultValue ?? (def.minNumber || 0);
465
- break;
466
- case 'boolean':
467
- example[key] = def.defaultValue ?? true;
468
- break;
469
- case 'multi':
470
- example[key] = def.defaultValue || [
471
- `example ${key} 1`,
472
- `example ${key} 2`,
473
- ];
474
- break;
475
- case 'date':
476
- example[key] = new Date().toISOString();
477
- break;
478
- case 'image':
479
- example[key] = 'file-id-placeholder';
480
- break;
481
- default:
482
- example[key] = def.defaultValue || `example ${key}`;
483
- }
484
- }
485
- );
494
+ if (
495
+ exampleCategorySlug &&
496
+ knownResources[exampleCategorySlug] !== undefined
497
+ ) {
498
+ const examples = [
499
+ buildExampleObjectForCategory(
500
+ exampleCategorySlug,
501
+ 0,
502
+ knownResources,
503
+ categories
504
+ ),
505
+ ];
506
+ return JSON.stringify(examples, null, 2);
507
+ }
486
508
 
487
- return example;
488
- });
509
+ const examples = categories.map((categorySlug, index) =>
510
+ buildExampleObjectForCategory(
511
+ categorySlug,
512
+ index,
513
+ knownResources,
514
+ categories
515
+ )
516
+ );
489
517
 
490
518
  return JSON.stringify(examples, null, 2);
491
- }, [knownResources]);
519
+ }, [knownResources, exampleCategorySlug]);
492
520
 
493
521
  const handleSave = useCallback(async () => {
494
522
  if (validationResult.validResources.length === 0 || isProcessing) return;
@@ -216,6 +216,7 @@ export default function ResourceTable({
216
216
  </div>
217
217
  {showBulkIngest && (
218
218
  <ResourceBulkIngest
219
+ exampleCategorySlug={categorySlug}
219
220
  fullContentMap={fullContentMap}
220
221
  onClose={(saved) => {
221
222
  setShowBulkIngest(false);
@@ -0,0 +1,169 @@
1
+ import { useLayoutEffect, useRef, type ClipboardEvent } from 'react';
2
+ import type { EmailBlock } from '@/utils/api/emailHelpers';
3
+
4
+ interface BlocksProps {
5
+ blocks: EmailBlock[];
6
+ selectedIdx: number | null;
7
+ onSelect: (index: number) => void;
8
+ onChange: (index: number, block: EmailBlock) => void;
9
+ }
10
+
11
+ function TextBlockEditor({
12
+ idx,
13
+ block,
14
+ isSelected,
15
+ onSelect,
16
+ onChange,
17
+ handlePaste,
18
+ }: {
19
+ idx: number;
20
+ block: Extract<EmailBlock, { type: 'text' }>;
21
+ isSelected: boolean;
22
+ onSelect: (index: number) => void;
23
+ onChange: (index: number, block: EmailBlock) => void;
24
+ handlePaste: (
25
+ e: ClipboardEvent<HTMLTextAreaElement>,
26
+ idx: number,
27
+ block: Extract<EmailBlock, { type: 'text' }>
28
+ ) => void;
29
+ }) {
30
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
31
+
32
+ useLayoutEffect(() => {
33
+ const el = textareaRef.current;
34
+ if (!el) return;
35
+ el.style.height = 'auto';
36
+ el.style.height = `${el.scrollHeight}px`;
37
+ }, [block.content]);
38
+
39
+ const containerStyle = {
40
+ border: isSelected ? '2px solid #0867ec' : '2px solid transparent',
41
+ padding: '8px',
42
+ cursor: 'pointer',
43
+ marginBottom: '8px',
44
+ };
45
+
46
+ return (
47
+ <div style={containerStyle} onClick={() => onSelect(idx)}>
48
+ <textarea
49
+ ref={textareaRef}
50
+ value={block.content}
51
+ className="box-border w-full py-1 leading-relaxed"
52
+ onChange={(e) => onChange(idx, { ...block, content: e.target.value })}
53
+ onPaste={(e) => handlePaste(e, idx, block)}
54
+ onFocus={() => onSelect(idx)}
55
+ onClick={(e) => e.stopPropagation()}
56
+ style={{
57
+ minHeight: '2.5rem',
58
+ fontFamily: 'Helvetica, sans-serif',
59
+ fontSize: '16px',
60
+ color: block.color,
61
+ textAlign: block.align,
62
+ fontWeight: block.isBold ? 'bold' : 'normal',
63
+ border: 'none',
64
+ background: 'transparent',
65
+ resize: 'none',
66
+ overflow: 'hidden',
67
+ }}
68
+ rows={1}
69
+ />
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export default function Blocks({
75
+ blocks,
76
+ selectedIdx,
77
+ onSelect,
78
+ onChange,
79
+ }: BlocksProps) {
80
+ const handlePaste = (
81
+ e: ClipboardEvent<HTMLTextAreaElement>,
82
+ idx: number,
83
+ block: Extract<EmailBlock, { type: 'text' }>
84
+ ) => {
85
+ e.preventDefault();
86
+ const pastedText = e.clipboardData.getData('text/plain');
87
+ const target = e.currentTarget;
88
+
89
+ const start = target.selectionStart;
90
+ const end = target.selectionEnd;
91
+
92
+ const newContent =
93
+ block.content.substring(0, start) +
94
+ pastedText +
95
+ block.content.substring(end);
96
+
97
+ onChange(idx, { ...block, content: newContent });
98
+
99
+ window.requestAnimationFrame(() => {
100
+ target.setSelectionRange(
101
+ start + pastedText.length,
102
+ start + pastedText.length
103
+ );
104
+ });
105
+ };
106
+
107
+ return (
108
+ <div className="flex flex-col p-8">
109
+ {blocks.map((block, idx) => {
110
+ const isSelected = selectedIdx === idx;
111
+
112
+ if (block.type === 'text') {
113
+ return (
114
+ <TextBlockEditor
115
+ key={idx}
116
+ idx={idx}
117
+ block={block}
118
+ isSelected={isSelected}
119
+ onSelect={onSelect}
120
+ onChange={onChange}
121
+ handlePaste={handlePaste}
122
+ />
123
+ );
124
+ }
125
+
126
+ const containerStyle = {
127
+ border: isSelected ? '2px solid #0867ec' : '2px solid transparent',
128
+ padding: '8px',
129
+ cursor: 'pointer',
130
+ marginBottom: '8px',
131
+ };
132
+
133
+ return (
134
+ <div key={idx} style={containerStyle} onClick={() => onSelect(idx)}>
135
+ {block.type === 'button' && (
136
+ <div style={{ textAlign: 'center' }}>
137
+ <span
138
+ style={{
139
+ display: 'inline-block',
140
+ padding: '12px 24px',
141
+ backgroundColor: block.bgColor,
142
+ color: block.textColor,
143
+ borderRadius: '4px',
144
+ fontFamily: 'Helvetica, sans-serif',
145
+ fontSize: '16px',
146
+ fontWeight: 'bold',
147
+ }}
148
+ >
149
+ {block.label}
150
+ </span>
151
+ </div>
152
+ )}
153
+
154
+ {block.type === 'divider' && (
155
+ <div style={{ width: '100%', padding: '12px 0' }}>
156
+ <div
157
+ style={{
158
+ borderTop: `1px solid ${block.color}`,
159
+ width: '100%',
160
+ }}
161
+ />
162
+ </div>
163
+ )}
164
+ </div>
165
+ );
166
+ })}
167
+ </div>
168
+ );
169
+ }