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.
- package/dist/index.js +36 -3
- package/package.json +1 -1
- package/templates/custom/shopify/Cart.tsx +16 -5
- package/templates/custom/shopify/CheckoutModal.tsx +4 -4
- package/templates/custom/shopify/ShopifyCartManager.tsx +27 -36
- package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
- package/templates/custom/shopify/ShopifyProductGrid.tsx +42 -14
- package/templates/custom/shopify/ShopifyServiceList.tsx +94 -50
- package/templates/src/components/Footer.astro +2 -2
- package/templates/src/components/Header.astro +14 -8
- package/templates/src/components/Menu.tsx +157 -135
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +27 -6
- package/templates/src/components/codehooks/EpinetTableView.tsx +153 -112
- package/templates/src/components/codehooks/EpinetWrapper.tsx +4 -1
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +8 -1
- package/templates/src/components/codehooks/ProductCardSetup.tsx +9 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +9 -1
- package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +2 -1
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
- package/templates/src/components/edit/ToolBar.tsx +2 -1
- package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +13 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +1 -1
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +8 -3
- package/templates/src/components/form/DateTimeInput.tsx +10 -3
- package/templates/src/components/form/FileUpload.tsx +11 -5
- package/templates/src/components/form/NumberInput.tsx +2 -2
- package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -38
- package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
- package/templates/src/components/storykeep/Dashboard_Shopify.tsx +7 -8
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/ProductTable.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +79 -51
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -0
- package/templates/src/components/storykeep/email-builder/Blocks.tsx +169 -0
- package/templates/src/components/storykeep/email-builder/EmailBuilder.tsx +223 -0
- package/templates/src/components/storykeep/email-builder/PreviewModal.tsx +136 -0
- package/templates/src/components/storykeep/email-builder/PropertyPanel.tsx +154 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +1 -8
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +32 -6
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -0
- package/templates/src/layouts/Layout.astro +8 -5
- package/templates/src/stores/shopify.ts +16 -0
- package/templates/src/types/formTypes.ts +4 -2
- package/templates/src/types/tractstack.ts +5 -2
- package/templates/src/utils/api/brandConfig.ts +2 -0
- package/templates/src/utils/api/brandHelpers.ts +16 -0
- package/templates/src/utils/api/emailHelpers.ts +105 -0
- package/templates/src/utils/tenantResolver.ts +1 -1
- package/utils/inject-files.ts +34 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
emailHelpers,
|
|
4
|
+
type EmailTemplateListEntry,
|
|
5
|
+
} from '@/utils/api/emailHelpers';
|
|
6
|
+
import EmailBuilder from '../email-builder/EmailBuilder';
|
|
7
|
+
|
|
8
|
+
export default function ShopifyDashboard_Emails() {
|
|
9
|
+
const [templates, setTemplates] = useState<
|
|
10
|
+
Record<string, EmailTemplateListEntry[]>
|
|
11
|
+
>({});
|
|
12
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
13
|
+
const [error, setError] = useState<string | null>(null);
|
|
14
|
+
const [editingTemplate, setEditingTemplate] = useState<{
|
|
15
|
+
category: string;
|
|
16
|
+
name: string;
|
|
17
|
+
} | null>(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
loadTemplates();
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const loadTemplates = async () => {
|
|
24
|
+
try {
|
|
25
|
+
setIsLoading(true);
|
|
26
|
+
setError(null);
|
|
27
|
+
const data = await emailHelpers.getTemplates();
|
|
28
|
+
setTemplates(data);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
setError(err instanceof Error ? err.message : 'Failed to load templates');
|
|
31
|
+
} finally {
|
|
32
|
+
setIsLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (editingTemplate) {
|
|
37
|
+
return (
|
|
38
|
+
<EmailBuilder
|
|
39
|
+
category={editingTemplate.category}
|
|
40
|
+
templateName={editingTemplate.name}
|
|
41
|
+
onClose={() => setEditingTemplate(null)}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (isLoading) {
|
|
47
|
+
return (
|
|
48
|
+
<div className="flex h-48 items-center justify-center">
|
|
49
|
+
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-cyan-600" />
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (error) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="rounded-md bg-red-50 p-4 text-sm text-red-700">
|
|
57
|
+
{error}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="space-y-8">
|
|
64
|
+
{Object.entries(templates).map(([category, entries]) => (
|
|
65
|
+
<div
|
|
66
|
+
key={category}
|
|
67
|
+
className="rounded-lg border border-gray-200 bg-white"
|
|
68
|
+
>
|
|
69
|
+
<div className="border-b border-gray-200 bg-gray-50 px-4 py-3">
|
|
70
|
+
<h3 className="text-sm font-bold capitalize text-gray-900">
|
|
71
|
+
{category}
|
|
72
|
+
</h3>
|
|
73
|
+
</div>
|
|
74
|
+
<ul className="divide-y divide-gray-200">
|
|
75
|
+
{entries.map((entry) => (
|
|
76
|
+
<li
|
|
77
|
+
key={entry.name}
|
|
78
|
+
className="flex items-center justify-between px-4 py-4 md:px-6"
|
|
79
|
+
>
|
|
80
|
+
<div className="flex min-w-0 flex-col">
|
|
81
|
+
<p className="truncate text-sm font-bold text-gray-900">
|
|
82
|
+
{entry.adminTitle}
|
|
83
|
+
</p>
|
|
84
|
+
<p className="truncate text-xs text-gray-500">
|
|
85
|
+
{entry.name}.json
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="ml-4 flex flex-shrink-0">
|
|
89
|
+
<button
|
|
90
|
+
onClick={() =>
|
|
91
|
+
setEditingTemplate({ category, name: entry.name })
|
|
92
|
+
}
|
|
93
|
+
className="rounded-md bg-white font-bold text-cyan-600 hover:text-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2"
|
|
94
|
+
>
|
|
95
|
+
Edit
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
</li>
|
|
99
|
+
))}
|
|
100
|
+
</ul>
|
|
101
|
+
</div>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -50,15 +50,18 @@ const {
|
|
|
50
50
|
|
|
51
51
|
const isInitialized = !freshInstallStore.get().needsSetup;
|
|
52
52
|
const goBackend = import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
53
|
+
const isMultiTenant = import.meta.env.PUBLIC_ENABLE_MULTI_TENANT === 'true';
|
|
54
|
+
const tenantId = isMultiTenant
|
|
55
|
+
? (await resolveTenantId(Astro.request)).id
|
|
56
|
+
: import.meta.env.PUBLIC_TENANTID || 'default';
|
|
57
|
+
|
|
56
58
|
if (!Astro.locals.tenant) {
|
|
57
59
|
Astro.locals.tenant = {
|
|
58
60
|
id: tenantId,
|
|
59
61
|
domain: Astro.url.hostname,
|
|
60
|
-
isMultiTenant:
|
|
61
|
-
isLocalhost:
|
|
62
|
+
isMultiTenant: isMultiTenant,
|
|
63
|
+
isLocalhost:
|
|
64
|
+
Astro.url.hostname === 'localhost' || Astro.url.hostname === '127.0.0.1',
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
67
|
const brandConfig = propBrandConfig || (await getBrandConfig(tenantId));
|
|
@@ -264,3 +264,19 @@ export function clearCommerceState() {
|
|
|
264
264
|
transactionTraceId.set('');
|
|
265
265
|
cartState.set(CART_STATES.READY);
|
|
266
266
|
}
|
|
267
|
+
|
|
268
|
+
export interface CartKeyParams {
|
|
269
|
+
resourceId: string;
|
|
270
|
+
variantId?: string;
|
|
271
|
+
variantIdShipped?: string;
|
|
272
|
+
variantIdPickup?: string;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function getCartItemKey(params: CartKeyParams): string {
|
|
276
|
+
if (params.variantId) {
|
|
277
|
+
return params.variantId;
|
|
278
|
+
}
|
|
279
|
+
return `${params.resourceId}_${params.variantIdShipped || 'null'}_${
|
|
280
|
+
params.variantIdPickup || 'null'
|
|
281
|
+
}`;
|
|
282
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
1
3
|
// Base props interface for all atomic form components
|
|
2
4
|
export interface BaseFormComponentProps<T> {
|
|
3
5
|
value: T;
|
|
@@ -71,7 +73,7 @@ export interface NumberInputProps extends BaseFormComponentProps<number> {
|
|
|
71
73
|
export interface FormSectionProps {
|
|
72
74
|
title: string;
|
|
73
75
|
description?: string;
|
|
74
|
-
children:
|
|
76
|
+
children: ReactNode;
|
|
75
77
|
collapsible?: boolean;
|
|
76
78
|
defaultExpanded?: boolean;
|
|
77
79
|
}
|
|
@@ -216,7 +218,7 @@ export interface NumberInputProps extends BaseFormComponentProps<number> {
|
|
|
216
218
|
export interface FormSectionProps {
|
|
217
219
|
title: string;
|
|
218
220
|
description?: string;
|
|
219
|
-
children:
|
|
221
|
+
children: ReactNode;
|
|
220
222
|
collapsible?: boolean;
|
|
221
223
|
defaultExpanded?: boolean;
|
|
222
224
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
1
2
|
import type { StoragePane } from './compositorTypes';
|
|
2
3
|
|
|
3
4
|
export type DesignLibraryEntry = {
|
|
@@ -13,7 +14,7 @@ export type DesignLibraryConfig = DesignLibraryEntry[];
|
|
|
13
14
|
|
|
14
15
|
export interface BaseComponentProps {
|
|
15
16
|
class?: string;
|
|
16
|
-
style?:
|
|
17
|
+
style?: CSSProperties | string;
|
|
17
18
|
id?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -74,7 +75,7 @@ export interface FragmentProps
|
|
|
74
75
|
eager?: boolean;
|
|
75
76
|
|
|
76
77
|
/** Fallback content while loading */
|
|
77
|
-
fallback?:
|
|
78
|
+
fallback?: ReactNode;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
// Utility type for extracting props from Astro components
|
|
@@ -203,6 +204,7 @@ export interface BrandConfig {
|
|
|
203
204
|
HAS_RESEND?: boolean;
|
|
204
205
|
HAS_HYDRATION_TOKEN?: boolean;
|
|
205
206
|
SCHEDULING?: SchedulingConfig;
|
|
207
|
+
ADMIN_EMAIL?: string;
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
export interface BrandConfigState {
|
|
@@ -241,6 +243,7 @@ export interface BrandConfigState {
|
|
|
241
243
|
hasResend: boolean;
|
|
242
244
|
hasHydrationToken: boolean;
|
|
243
245
|
scheduling: SchedulingConfig;
|
|
246
|
+
adminEmail: string;
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
// Form validation types
|
|
@@ -66,6 +66,7 @@ export async function getBrandConfig(tenantId: string): Promise<BrandConfig> {
|
|
|
66
66
|
KNOWN_RESOURCES: {},
|
|
67
67
|
DESIGN_LIBRARY: [],
|
|
68
68
|
HAS_AAI: false,
|
|
69
|
+
ADMIN_EMAIL: '',
|
|
69
70
|
} as BrandConfig;
|
|
70
71
|
}
|
|
71
72
|
throw new Error(response.error || 'Failed to get brand configuration');
|
|
@@ -100,6 +101,7 @@ export async function getBrandConfig(tenantId: string): Promise<BrandConfig> {
|
|
|
100
101
|
KNOWN_RESOURCES: {},
|
|
101
102
|
DESIGN_LIBRARY: [],
|
|
102
103
|
HAS_AAI: false,
|
|
104
|
+
ADMIN_EMAIL: '',
|
|
103
105
|
} as BrandConfig;
|
|
104
106
|
}
|
|
105
107
|
throw error;
|
|
@@ -44,6 +44,7 @@ export function convertToLocalState(
|
|
|
44
44
|
showShopifyHelper: brandConfig.SHOW_SHOPIFY_HELPER ?? false,
|
|
45
45
|
hasResend: brandConfig.HAS_RESEND ?? false,
|
|
46
46
|
hasHydrationToken: brandConfig.HAS_HYDRATION_TOKEN ?? false,
|
|
47
|
+
adminEmail: brandConfig.ADMIN_EMAIL ?? '',
|
|
47
48
|
scheduling: brandConfig.SCHEDULING ?? {
|
|
48
49
|
timezone: 'UTC',
|
|
49
50
|
bufferGapsMinutes: 15,
|
|
@@ -86,6 +87,7 @@ export function convertToBackendFormat(
|
|
|
86
87
|
SHOW_SHOPIFY_HELPER: localState.showShopifyHelper,
|
|
87
88
|
HAS_RESEND: localState.hasResend,
|
|
88
89
|
SCHEDULING: localState.scheduling,
|
|
90
|
+
ADMIN_EMAIL: localState.adminEmail,
|
|
89
91
|
|
|
90
92
|
// ALWAYS send asset paths (current state)
|
|
91
93
|
LOGO: localState.logo,
|
|
@@ -124,6 +126,12 @@ export function validateBrandConfig(state: BrandConfigState): FieldErrors {
|
|
|
124
126
|
errors.footer = 'Site footer is required';
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
if (!state.adminEmail?.trim()) {
|
|
130
|
+
errors.adminEmail = 'Admin Email is required';
|
|
131
|
+
} else if (!isValidEmail(state.adminEmail)) {
|
|
132
|
+
errors.adminEmail = 'Please enter a valid email address';
|
|
133
|
+
}
|
|
134
|
+
|
|
127
135
|
// Validate brand colors (must have exactly 8)
|
|
128
136
|
if (!state.brandColours || state.brandColours.length !== 8) {
|
|
129
137
|
errors.brandColours = 'Must have exactly 8 brand colors';
|
|
@@ -172,3 +180,11 @@ function isValidHexColor(color: string): boolean {
|
|
|
172
180
|
const hex = color.startsWith('#') ? color.slice(1) : color;
|
|
173
181
|
return /^([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(hex);
|
|
174
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Helper function to validate email addresses
|
|
186
|
+
*/
|
|
187
|
+
function isValidEmail(email: string): boolean {
|
|
188
|
+
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
189
|
+
return re.test(email);
|
|
190
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { TractStackAPI } from '../api';
|
|
2
|
+
|
|
3
|
+
export interface TextBlock {
|
|
4
|
+
type: 'text';
|
|
5
|
+
content: string;
|
|
6
|
+
align: 'left' | 'center' | 'right';
|
|
7
|
+
color: string;
|
|
8
|
+
isBold: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ButtonBlock {
|
|
12
|
+
type: 'button';
|
|
13
|
+
label: string;
|
|
14
|
+
url: string;
|
|
15
|
+
bgColor: string;
|
|
16
|
+
textColor: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DividerBlock {
|
|
20
|
+
type: 'divider';
|
|
21
|
+
color: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type EmailBlock = TextBlock | ButtonBlock | DividerBlock;
|
|
25
|
+
|
|
26
|
+
export interface EmailTemplate {
|
|
27
|
+
subject: string;
|
|
28
|
+
blocks: EmailBlock[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PreviewResponse {
|
|
32
|
+
subject: string;
|
|
33
|
+
html: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One row from GET /api/v1/emails/templates (merged manifests). */
|
|
37
|
+
export interface EmailTemplateListEntry {
|
|
38
|
+
name: string;
|
|
39
|
+
adminTitle: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getApi = () => {
|
|
43
|
+
return new TractStackAPI(
|
|
44
|
+
typeof window !== 'undefined'
|
|
45
|
+
? (window as any).TRACTSTACK_CONFIG?.tenantId || 'default'
|
|
46
|
+
: 'default'
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const emailHelpers = {
|
|
51
|
+
getTemplates: async (): Promise<Record<string, EmailTemplateListEntry[]>> => {
|
|
52
|
+
const api = getApi();
|
|
53
|
+
const response = await api.get<Record<string, EmailTemplateListEntry[]>>(
|
|
54
|
+
'/api/v1/emails/templates'
|
|
55
|
+
);
|
|
56
|
+
if (!response.success || !response.data) {
|
|
57
|
+
throw new Error(response.error || 'Failed to fetch templates');
|
|
58
|
+
}
|
|
59
|
+
return response.data;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
getTemplate: async (
|
|
63
|
+
category: string,
|
|
64
|
+
template: string
|
|
65
|
+
): Promise<EmailTemplate> => {
|
|
66
|
+
const api = getApi();
|
|
67
|
+
const response = await api.get<EmailTemplate>(
|
|
68
|
+
`/api/v1/emails/templates/${category}/${template}`
|
|
69
|
+
);
|
|
70
|
+
if (!response.success || !response.data) {
|
|
71
|
+
throw new Error(response.error || 'Failed to fetch template');
|
|
72
|
+
}
|
|
73
|
+
return response.data;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
saveTemplate: async (
|
|
77
|
+
category: string,
|
|
78
|
+
template: string,
|
|
79
|
+
data: EmailTemplate
|
|
80
|
+
): Promise<void> => {
|
|
81
|
+
const api = getApi();
|
|
82
|
+
const response = await api.post(
|
|
83
|
+
`/api/v1/emails/templates/${category}/${template}`,
|
|
84
|
+
data
|
|
85
|
+
);
|
|
86
|
+
if (!response.success) {
|
|
87
|
+
throw new Error(response.error || 'Failed to save template');
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
previewTemplate: async (
|
|
92
|
+
template: EmailTemplate,
|
|
93
|
+
mockData: Record<string, any>
|
|
94
|
+
): Promise<PreviewResponse> => {
|
|
95
|
+
const api = getApi();
|
|
96
|
+
const response = await api.post<PreviewResponse>('/api/v1/emails/preview', {
|
|
97
|
+
template,
|
|
98
|
+
data: mockData,
|
|
99
|
+
});
|
|
100
|
+
if (!response.success || !response.data) {
|
|
101
|
+
throw new Error(response.error || 'Failed to generate preview');
|
|
102
|
+
}
|
|
103
|
+
return response.data;
|
|
104
|
+
},
|
|
105
|
+
};
|
package/utils/inject-files.ts
CHANGED
|
@@ -1258,6 +1258,36 @@ export async function injectTemplateFiles(
|
|
|
1258
1258
|
),
|
|
1259
1259
|
dest: 'src/components/storykeep/shopify/ShopifyDashboard_Services.tsx',
|
|
1260
1260
|
},
|
|
1261
|
+
{
|
|
1262
|
+
src: resolve(
|
|
1263
|
+
'../templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx'
|
|
1264
|
+
),
|
|
1265
|
+
dest: 'src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx',
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
src: resolve(
|
|
1269
|
+
'../templates/src/components/storykeep/email-builder/EmailBuilder.tsx'
|
|
1270
|
+
),
|
|
1271
|
+
dest: 'src/components/storykeep/email-builder/EmailBuilder.tsx',
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
src: resolve(
|
|
1275
|
+
'../templates/src/components/storykeep/email-builder/Blocks.tsx'
|
|
1276
|
+
),
|
|
1277
|
+
dest: 'src/components/storykeep/email-builder/Blocks.tsx',
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
src: resolve(
|
|
1281
|
+
'../templates/src/components/storykeep/email-builder/PropertyPanel.tsx'
|
|
1282
|
+
),
|
|
1283
|
+
dest: 'src/components/storykeep/email-builder/PropertyPanel.tsx',
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
src: resolve(
|
|
1287
|
+
'../templates/src/components/storykeep/email-builder/PreviewModal.tsx'
|
|
1288
|
+
),
|
|
1289
|
+
dest: 'src/components/storykeep/email-builder/PreviewModal.tsx',
|
|
1290
|
+
},
|
|
1261
1291
|
{
|
|
1262
1292
|
src: resolve(
|
|
1263
1293
|
'../templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx'
|
|
@@ -2267,6 +2297,10 @@ export async function injectTemplateFiles(
|
|
|
2267
2297
|
src: resolve('../templates/src/utils/api/setupHelpers.ts'),
|
|
2268
2298
|
dest: 'src/utils/api/setupHelpers.ts',
|
|
2269
2299
|
},
|
|
2300
|
+
{
|
|
2301
|
+
src: resolve('../templates/src/utils/api/emailHelpers.ts'),
|
|
2302
|
+
dest: 'src/utils/api/emailHelpers.ts',
|
|
2303
|
+
},
|
|
2270
2304
|
{
|
|
2271
2305
|
src: resolve(
|
|
2272
2306
|
'../templates/src/components/storykeep/widgets/HydrateWizard.tsx'
|
|
@@ -2465,7 +2499,6 @@ function createPlaceholder(filePath: string): string {
|
|
|
2465
2499
|
|
|
2466
2500
|
if (filePath.endsWith('.tsx')) {
|
|
2467
2501
|
return `// TractStack placeholder component
|
|
2468
|
-
import React from 'react';
|
|
2469
2502
|
export default function Placeholder() {
|
|
2470
2503
|
return <div>TractStack placeholder: ${filePath}</div>;
|
|
2471
2504
|
}`;
|