astro-tractstack 2.3.0 → 2.3.1
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 +1 -1
- package/bin/create-tractstack.js +2 -2
- package/dist/index.js +94 -16
- package/package.json +2 -2
- package/templates/custom/minimal/CodeHook.astro +10 -2
- package/templates/custom/shopify/Cart.tsx +100 -73
- package/templates/custom/shopify/CheckoutModal.tsx +509 -120
- package/templates/custom/shopify/NativeBookingCalendar.tsx +375 -0
- package/templates/custom/shopify/ShopifyCartManager.tsx +92 -37
- package/templates/custom/shopify/ShopifyProductGrid.tsx +139 -173
- package/templates/custom/shopify/ShopifyServiceList.tsx +20 -3
- package/templates/custom/with-examples/CodeHook.astro +10 -2
- package/templates/src/components/Footer.astro +4 -4
- package/templates/src/components/Header.astro +9 -3
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +3 -3
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +2 -2
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +3 -3
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +2 -2
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +7 -7
- package/templates/src/components/form/advanced/APIConfigSection.tsx +244 -2
- package/templates/src/components/form/shopify/SchedulingSection.tsx +354 -0
- package/templates/src/components/storykeep/Dashboard.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Shopify.tsx +253 -110
- package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +14 -5
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +5 -2
- package/templates/src/components/storykeep/controls/content/MenuTable.tsx +14 -5
- package/templates/src/components/storykeep/controls/content/ProductTable.tsx +180 -101
- package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +9 -5
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +13 -4
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +14 -5
- package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +111 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +393 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Products.tsx +46 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Schedule.tsx +78 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +55 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Services.tsx +47 -0
- package/templates/src/pages/api/auth/lookup-lead.ts +72 -0
- package/templates/src/pages/api/booking/availability.ts +72 -0
- package/templates/src/pages/api/booking/cancel.ts +73 -0
- package/templates/src/pages/api/booking/confirm.ts +82 -0
- package/templates/src/pages/api/booking/hold.ts +75 -0
- package/templates/src/pages/api/booking/list.ts +66 -0
- package/templates/src/pages/api/booking/metrics.ts +60 -0
- package/templates/src/pages/api/booking/release.ts +76 -0
- package/templates/src/pages/api/sandbox.ts +2 -2
- package/templates/src/pages/api/shopify/createCart.ts +4 -8
- package/templates/src/pages/api/shopify/getProducts.ts +15 -15
- package/templates/src/pages/storykeep/login.astro +21 -14
- package/templates/src/stores/shopify.ts +81 -25
- package/templates/src/types/tractstack.ts +54 -0
- package/templates/src/utils/api/advancedConfig.ts +2 -0
- package/templates/src/utils/api/advancedHelpers.ts +40 -3
- package/templates/src/utils/api/bookingHelpers.ts +125 -0
- package/templates/src/utils/api/brandHelpers.ts +10 -0
- package/templates/src/utils/auth.ts +29 -9
- package/templates/src/utils/compositor/aiGeneration.ts +3 -3
- package/templates/src/utils/compositor/aiPaneParser.ts +2 -2
- package/templates/src/utils/customHelpers.ts +0 -21
- package/templates/src/utils/profileStorage.ts +5 -0
- package/templates/src/utils/tenantResolver.ts +2 -1
- package/utils/inject-files.ts +82 -4
- package/templates/custom/shopify/CalDotComBooking.tsx +0 -44
|
@@ -6,6 +6,11 @@ import type { ResourceNode } from '@/types/compositorTypes';
|
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
8
|
resources: Record<string, ResourceNode[]>;
|
|
9
|
+
options?: {
|
|
10
|
+
params?: {
|
|
11
|
+
options?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
interface ShopifyOption {
|
|
@@ -17,6 +22,7 @@ interface ShopifyVariant {
|
|
|
17
22
|
id: string;
|
|
18
23
|
title: string;
|
|
19
24
|
price: { amount: string; currencyCode: string };
|
|
25
|
+
compareAtPrice?: { amount: string; currencyCode: string };
|
|
20
26
|
selectedOptions: { name: string; value: string }[];
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -31,7 +37,6 @@ function ProductCard({ resource, allServices }: ProductCardProps) {
|
|
|
31
37
|
const serviceBoundSlug = resource.optionsPayload?.serviceBound as
|
|
32
38
|
| string
|
|
33
39
|
| undefined;
|
|
34
|
-
|
|
35
40
|
const boundServiceResource = serviceBoundSlug
|
|
36
41
|
? allServices.find((r) => r.slug === serviceBoundSlug)
|
|
37
42
|
: undefined;
|
|
@@ -47,15 +52,12 @@ function ProductCard({ resource, allServices }: ProductCardProps) {
|
|
|
47
52
|
|
|
48
53
|
const options: ShopifyOption[] = product?.options || [];
|
|
49
54
|
const variants: ShopifyVariant[] = product?.variants || [];
|
|
50
|
-
|
|
51
|
-
const isUnconfigured = options.some((o) => o.name === 'Title');
|
|
52
|
-
|
|
53
|
-
if (isUnconfigured) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
55
|
+
const vendor: string = product?.vendor || '';
|
|
56
56
|
|
|
57
57
|
const hasModeOption = options.some((o) => o.name === 'Mode');
|
|
58
|
-
const visibleOptions = options.filter(
|
|
58
|
+
const visibleOptions = options.filter(
|
|
59
|
+
(o) => o.name !== 'Mode' && o.name !== 'Title'
|
|
60
|
+
);
|
|
59
61
|
|
|
60
62
|
const [selections, setSelections] = useState<Record<string, string>>(() => {
|
|
61
63
|
const initial: Record<string, string> = {};
|
|
@@ -66,216 +68,180 @@ function ProductCard({ resource, allServices }: ProductCardProps) {
|
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
const getVariant = (targetMode: 'Shipped' | 'Pickup' | null) => {
|
|
71
|
+
if (targetMode && !hasModeOption) return undefined;
|
|
69
72
|
const found = variants.find((v) => {
|
|
70
73
|
const optionsMatch = visibleOptions.every((opt) => {
|
|
71
74
|
const variantOpt = v.selectedOptions.find((o) => o.name === opt.name);
|
|
72
75
|
return variantOpt?.value === selections[opt.name];
|
|
73
76
|
});
|
|
74
|
-
|
|
75
77
|
if (!optionsMatch) return false;
|
|
76
|
-
|
|
77
78
|
if (hasModeOption && targetMode) {
|
|
78
79
|
const modeOpt = v.selectedOptions.find((o) => o.name === 'Mode');
|
|
79
80
|
return modeOpt?.value === targetMode;
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
-
return true;
|
|
82
|
+
return !(hasModeOption && !targetMode);
|
|
83
83
|
});
|
|
84
|
-
|
|
85
84
|
return found;
|
|
86
85
|
};
|
|
87
86
|
|
|
88
|
-
const variantShipped =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}`;
|
|
93
|
-
const cartItem = cart[cartKey];
|
|
94
|
-
const quantity = cartItem?.quantity || 0;
|
|
87
|
+
const variantShipped = hasModeOption
|
|
88
|
+
? getVariant('Shipped')
|
|
89
|
+
: getVariant(null);
|
|
90
|
+
const variantPickup = hasModeOption ? getVariant('Pickup') : undefined;
|
|
95
91
|
|
|
96
|
-
const currentDisplayVariant =
|
|
97
|
-
getVariant('Shipped') || getVariant('Pickup') || variants[0];
|
|
92
|
+
const currentDisplayVariant = variantShipped || variantPickup || variants[0];
|
|
98
93
|
const price = currentDisplayVariant?.price?.amount;
|
|
94
|
+
const compareAtPrice = currentDisplayVariant?.compareAtPrice?.amount;
|
|
99
95
|
const currency = currentDisplayVariant?.price?.currencyCode || 'USD';
|
|
96
|
+
|
|
97
|
+
// High contrast rose badge calculation
|
|
98
|
+
let discountPercent = 0;
|
|
99
|
+
if (price && compareAtPrice) {
|
|
100
|
+
const p = parseFloat(price);
|
|
101
|
+
const cap = parseFloat(compareAtPrice);
|
|
102
|
+
if (cap > p) {
|
|
103
|
+
discountPercent = Math.round(((cap - p) / cap) * 100);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
const { src, srcSet } = getShopifyImage(
|
|
101
108
|
resource,
|
|
102
109
|
'600',
|
|
103
110
|
currentDisplayVariant?.id
|
|
104
111
|
);
|
|
105
112
|
|
|
106
|
-
const handleAction = (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
queueUpdates.push({
|
|
113
|
+
const handleAction = () => {
|
|
114
|
+
const queueUpdates: CartAction[] = [
|
|
115
|
+
{
|
|
111
116
|
resourceId: resource.id,
|
|
117
|
+
gid: product?.id,
|
|
112
118
|
variantIdShipped: variantShipped?.id,
|
|
113
119
|
variantIdPickup: variantPickup?.id,
|
|
114
|
-
action: 'remove',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (boundServiceResource) {
|
|
118
|
-
queueUpdates.push({
|
|
119
|
-
resourceId: boundServiceResource.id,
|
|
120
|
-
variantId: boundServiceResource.optionsPayload?.shopifyData
|
|
121
|
-
? JSON.parse(boundServiceResource.optionsPayload.shopifyData)
|
|
122
|
-
.variants?.[0]?.id
|
|
123
|
-
: undefined,
|
|
124
|
-
action: 'remove',
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
addQueue.set([...addQueue.get(), ...queueUpdates]);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const queueUpdates: CartAction[] = [];
|
|
133
|
-
|
|
134
|
-
const productAction: CartAction & { boundResourceId?: string } = {
|
|
135
|
-
resourceId: resource.id,
|
|
136
|
-
gid: product?.id,
|
|
137
|
-
variantIdShipped: variantShipped?.id,
|
|
138
|
-
variantIdPickup: variantPickup?.id,
|
|
139
|
-
action: 'add',
|
|
140
|
-
boundResourceId: boundServiceResource?.id,
|
|
141
|
-
};
|
|
142
|
-
queueUpdates.push(productAction);
|
|
143
|
-
|
|
144
|
-
if (boundServiceResource) {
|
|
145
|
-
let serviceVariantId = undefined;
|
|
146
|
-
try {
|
|
147
|
-
if (boundServiceResource.optionsPayload?.shopifyData) {
|
|
148
|
-
const serviceData = JSON.parse(
|
|
149
|
-
boundServiceResource.optionsPayload.shopifyData
|
|
150
|
-
);
|
|
151
|
-
serviceVariantId = serviceData.variants?.[0]?.id;
|
|
152
|
-
}
|
|
153
|
-
} catch (e) {}
|
|
154
|
-
|
|
155
|
-
queueUpdates.push({
|
|
156
|
-
resourceId: boundServiceResource.id,
|
|
157
|
-
variantId: serviceVariantId,
|
|
158
120
|
action: 'add',
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`[Shopify] Service bound to slug '${serviceBoundSlug}' was not found in provided resources.`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
121
|
+
boundResourceId: boundServiceResource?.id,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
166
124
|
addQueue.set([...addQueue.get(), ...queueUpdates]);
|
|
167
125
|
};
|
|
168
126
|
|
|
169
127
|
return (
|
|
170
|
-
<div className="flex flex-col
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
128
|
+
<div className="group flex flex-col text-left font-main">
|
|
129
|
+
{/* Clickable Area: Image and Title */}
|
|
130
|
+
<button
|
|
131
|
+
onClick={handleAction}
|
|
132
|
+
className="text-left focus:outline-none"
|
|
133
|
+
aria-label={`Add ${resource.title} to cart`}
|
|
134
|
+
>
|
|
135
|
+
{/* Rounded-2xl Frame with Top-Left Badge */}
|
|
136
|
+
<div className="relative aspect-square w-full overflow-hidden rounded-2xl bg-brand-8 transition-opacity group-hover:opacity-90">
|
|
137
|
+
<img
|
|
138
|
+
src={src}
|
|
139
|
+
srcSet={srcSet}
|
|
140
|
+
alt={resource.title}
|
|
141
|
+
className="h-full w-full object-cover object-center"
|
|
142
|
+
loading="lazy"
|
|
143
|
+
/>
|
|
144
|
+
{discountPercent > 0 && (
|
|
145
|
+
<div className="absolute left-4 top-4 flex h-12 w-12 items-center justify-center rounded-md bg-rose-600 text-xs font-bold text-white shadow-sm">
|
|
146
|
+
-{discountPercent}%
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
192
150
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
))}
|
|
216
|
-
</select>
|
|
217
|
-
</div>
|
|
218
|
-
))}
|
|
151
|
+
<div className="mt-6 flex flex-col">
|
|
152
|
+
{/* Vendor Label */}
|
|
153
|
+
{vendor && (
|
|
154
|
+
<span className="text-xs font-bold uppercase tracking-widest text-brand-6">
|
|
155
|
+
{vendor}
|
|
156
|
+
</span>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
<h3 className="mt-1 text-2xl font-bold text-brand-1">
|
|
160
|
+
{resource.title}
|
|
161
|
+
</h3>
|
|
162
|
+
|
|
163
|
+
{/* Combined Price Baseline */}
|
|
164
|
+
<div className="mt-1 flex items-baseline space-x-2">
|
|
165
|
+
<span className="text-lg font-bold text-brand-1">
|
|
166
|
+
{price} {currency}
|
|
167
|
+
</span>
|
|
168
|
+
{discountPercent > 0 && (
|
|
169
|
+
<span className="text-sm text-brand-6 line-through">
|
|
170
|
+
{compareAtPrice} {currency}
|
|
171
|
+
</span>
|
|
172
|
+
)}
|
|
219
173
|
</div>
|
|
220
|
-
)}
|
|
221
174
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
>
|
|
244
|
-
+
|
|
245
|
-
</button>
|
|
246
|
-
</>
|
|
247
|
-
) : (
|
|
248
|
-
<button
|
|
249
|
-
onClick={() => handleAction('add')}
|
|
250
|
-
className="rounded-md bg-black px-4 py-2 text-sm font-bold text-white hover:bg-gray-800"
|
|
175
|
+
<p className="mt-3 text-sm text-brand-7">{resource.oneliner}</p>
|
|
176
|
+
</div>
|
|
177
|
+
</button>
|
|
178
|
+
|
|
179
|
+
{/* Interactive Variant Selectors */}
|
|
180
|
+
{visibleOptions.length > 0 && (
|
|
181
|
+
<div className="mt-4 space-y-4">
|
|
182
|
+
{visibleOptions.map((opt) => (
|
|
183
|
+
<div key={opt.name} onClick={(e) => e.stopPropagation()}>
|
|
184
|
+
<label className="mb-1 block text-xs font-bold uppercase text-brand-7">
|
|
185
|
+
{opt.name}
|
|
186
|
+
</label>
|
|
187
|
+
<select
|
|
188
|
+
value={selections[opt.name]}
|
|
189
|
+
onChange={(e) =>
|
|
190
|
+
setSelections((prev) => ({
|
|
191
|
+
...prev,
|
|
192
|
+
[opt.name]: e.target.value,
|
|
193
|
+
}))
|
|
194
|
+
}
|
|
195
|
+
className="block w-full border-b border-brand-8 bg-transparent py-2 text-sm text-brand-1 focus:border-brand-1 focus:outline-none"
|
|
251
196
|
>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
197
|
+
{opt.values.map((val) => (
|
|
198
|
+
<option key={val} value={val}>
|
|
199
|
+
{val}
|
|
200
|
+
</option>
|
|
201
|
+
))}
|
|
202
|
+
</select>
|
|
203
|
+
</div>
|
|
204
|
+
))}
|
|
256
205
|
</div>
|
|
257
|
-
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{boundServiceResource && (
|
|
209
|
+
<div className="bg-brand-4/10 mt-4 w-fit rounded px-2 py-1 text-xs font-bold text-brand-4">
|
|
210
|
+
INCLUDES {boundServiceResource.title.toUpperCase()}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
258
213
|
</div>
|
|
259
214
|
);
|
|
260
215
|
}
|
|
261
216
|
|
|
262
|
-
export default function ShopifyProductGrid({ resources = {} }: Props) {
|
|
263
|
-
|
|
217
|
+
export default function ShopifyProductGrid({ resources = {}, options }: Props) {
|
|
218
|
+
let products = resources['product'] || [];
|
|
264
219
|
const services = resources['service'] || [];
|
|
265
220
|
|
|
266
|
-
|
|
267
|
-
|
|
221
|
+
let group = '';
|
|
222
|
+
try {
|
|
223
|
+
const parsedOptions = JSON.parse(options?.params?.options || '{}');
|
|
224
|
+
group = parsedOptions.group || '';
|
|
225
|
+
} catch (e) {}
|
|
226
|
+
|
|
227
|
+
if (group) {
|
|
228
|
+
products = products.filter((p) => p.optionsPayload?.group === group);
|
|
268
229
|
}
|
|
269
230
|
|
|
231
|
+
if (products.length === 0) return null;
|
|
232
|
+
|
|
233
|
+
// Grid with increased gaps matching the design
|
|
270
234
|
return (
|
|
271
|
-
<div className="
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
235
|
+
<div className="mx-auto max-w-7xl px-4 md:px-8">
|
|
236
|
+
<div className="grid grid-cols-2 gap-x-8 gap-y-16 md:gap-x-12 xl:grid-cols-3">
|
|
237
|
+
{products.map((resource) => (
|
|
238
|
+
<ProductCard
|
|
239
|
+
key={resource.id}
|
|
240
|
+
resource={resource}
|
|
241
|
+
allServices={services}
|
|
242
|
+
/>
|
|
243
|
+
))}
|
|
244
|
+
</div>
|
|
279
245
|
</div>
|
|
280
246
|
);
|
|
281
247
|
}
|
|
@@ -4,13 +4,30 @@ import type { ResourceNode } from '@/types/compositorTypes';
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
resources: Record<string, ResourceNode[]>;
|
|
7
|
+
options?: {
|
|
8
|
+
params?: {
|
|
9
|
+
options?: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
7
12
|
}
|
|
8
13
|
|
|
9
|
-
export default function ShopifyServiceList({ resources = {} }: Props) {
|
|
14
|
+
export default function ShopifyServiceList({ resources = {}, options }: Props) {
|
|
10
15
|
const cart = useStore(cartStore);
|
|
11
16
|
|
|
12
17
|
const products = resources['product'] || [];
|
|
13
|
-
|
|
18
|
+
let services = resources['service'] || [];
|
|
19
|
+
|
|
20
|
+
let group = '';
|
|
21
|
+
try {
|
|
22
|
+
const parsedOptions = JSON.parse(options?.params?.options || '{}');
|
|
23
|
+
group = parsedOptions.group || '';
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Ignore JSON parse errors
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (group) {
|
|
29
|
+
services = services.filter((s) => s.optionsPayload?.group === group);
|
|
30
|
+
}
|
|
14
31
|
|
|
15
32
|
const boundServiceSlugs = new Set(
|
|
16
33
|
products
|
|
@@ -86,7 +103,7 @@ export default function ShopifyServiceList({ resources = {} }: Props) {
|
|
|
86
103
|
<div className="flex items-center gap-2">
|
|
87
104
|
<h3 className="font-bold text-gray-900">{resource.title}</h3>
|
|
88
105
|
{duration && (
|
|
89
|
-
<span className="inline-flex items-center rounded-
|
|
106
|
+
<span className="inline-flex items-center rounded-sm bg-blue-50 px-2 py-0.5 text-xs font-bold text-blue-700">
|
|
90
107
|
{duration} mins
|
|
91
108
|
</span>
|
|
92
109
|
)}
|
|
@@ -62,9 +62,17 @@ export const components = {
|
|
|
62
62
|
) : target === 'epinet' ? (
|
|
63
63
|
<EpinetWrapper fullContentMap={fullContentMap} client:only="react" />
|
|
64
64
|
) : target === 'shopify-product-grid' ? (
|
|
65
|
-
<ShopifyProductGrid
|
|
65
|
+
<ShopifyProductGrid
|
|
66
|
+
options={options}
|
|
67
|
+
resources={resourcesPayload}
|
|
68
|
+
client:only="react"
|
|
69
|
+
/>
|
|
66
70
|
) : target === 'shopify-service-list' ? (
|
|
67
|
-
<ShopifyServiceList
|
|
71
|
+
<ShopifyServiceList
|
|
72
|
+
options={options}
|
|
73
|
+
resources={resourcesPayload}
|
|
74
|
+
client:only="react"
|
|
75
|
+
/>
|
|
68
76
|
) : (
|
|
69
77
|
<div class="rounded-lg bg-gray-50 p-8 text-center">
|
|
70
78
|
<p class="text-gray-600">CodeHook target "{target}" not found</p>
|
|
@@ -161,8 +161,8 @@ const createdDate = created ? new Date(created) : new Date();
|
|
|
161
161
|
<div
|
|
162
162
|
class="my-2 flex flex-row items-center justify-center text-myblue md:flex-col"
|
|
163
163
|
>
|
|
164
|
-
<div class="px-4 text-center text-
|
|
165
|
-
pressed with
|
|
164
|
+
<div class="px-4 text-center text-sm italic md:px-12">
|
|
165
|
+
This website has been pressed with
|
|
166
166
|
<a
|
|
167
167
|
href="https://tractstack.com/?utm_source=tractstack&utm_medium=www&utm_campaign=community"
|
|
168
168
|
class="font-bold underline hover:text-black"
|
|
@@ -171,12 +171,12 @@ const createdDate = created ? new Date(created) : new Date();
|
|
|
171
171
|
>
|
|
172
172
|
Tract Stack</a
|
|
173
173
|
>
|
|
174
|
-
|
|
174
|
+
by{` `}
|
|
175
175
|
<a
|
|
176
176
|
href="https://atriskmedia.com/?utm_source=tractstack&utm_medium=www&utm_campaign=community"
|
|
177
177
|
class="font-bold underline hover:text-black"
|
|
178
178
|
target="_blank">At Risk Media</a
|
|
179
|
-
|
|
179
|
+
>.
|
|
180
180
|
</div>
|
|
181
181
|
<br /><br /><br />
|
|
182
182
|
</div>
|
|
@@ -10,13 +10,13 @@ import ShopifyCartManager from '@/custom/shopify/ShopifyCartManager';
|
|
|
10
10
|
import CartIcon from '@/custom/shopify/CartIcon';
|
|
11
11
|
import CartModal from '@/custom/shopify/CartModal';
|
|
12
12
|
import CheckoutModal from '@/custom/shopify/CheckoutModal';
|
|
13
|
-
import type { MenuNode } from '@/types/tractstack';
|
|
13
|
+
import type { MenuNode, BrandConfig } from '@/types/tractstack';
|
|
14
14
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
15
15
|
|
|
16
16
|
export interface Props {
|
|
17
17
|
title: string;
|
|
18
18
|
slug: string;
|
|
19
|
-
brandConfig:
|
|
19
|
+
brandConfig: BrandConfig;
|
|
20
20
|
isContext?: boolean;
|
|
21
21
|
isStoryKeep?: boolean;
|
|
22
22
|
isEditable?: boolean;
|
|
@@ -79,7 +79,11 @@ if (hasShopify) {
|
|
|
79
79
|
<CartIcon client:only="react" />
|
|
80
80
|
</div>
|
|
81
81
|
) : (
|
|
82
|
-
<CheckoutModal
|
|
82
|
+
<CheckoutModal
|
|
83
|
+
client:only="react"
|
|
84
|
+
resources={shopifyResources}
|
|
85
|
+
maxLength={brandConfig?.scheduling?.maxLengthMinutes || 180}
|
|
86
|
+
/>
|
|
83
87
|
)}
|
|
84
88
|
</>
|
|
85
89
|
) : null
|
|
@@ -94,6 +98,7 @@ if (hasShopify) {
|
|
|
94
98
|
[`default`, `logo`].includes(wordmarkMode) ? (
|
|
95
99
|
<>
|
|
96
100
|
<img
|
|
101
|
+
id="t8k-logo"
|
|
97
102
|
src={logo}
|
|
98
103
|
alt="Logo"
|
|
99
104
|
class="pointer-events-none h-8 w-auto"
|
|
@@ -106,6 +111,7 @@ if (hasShopify) {
|
|
|
106
111
|
{
|
|
107
112
|
[`default`, `wordmark`].includes(wordmarkMode) ? (
|
|
108
113
|
<img
|
|
114
|
+
id="t8k-wordmark"
|
|
109
115
|
src={wordmark}
|
|
110
116
|
alt="Wordmark"
|
|
111
117
|
class="pointer-events-none h-14 w-auto max-w-48 md:max-w-72"
|
|
@@ -276,9 +276,9 @@ const AddPaneNewPanel = ({
|
|
|
276
276
|
{!hasAssemblyAI && (
|
|
277
277
|
<div className="rounded-lg border-l-4 border-blue-400 bg-blue-50 p-4 shadow-sm">
|
|
278
278
|
<p className="text-sm text-blue-800">
|
|
279
|
-
Tract Stack uses AssemblyAI
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
Tract Stack uses AssemblyAI to generate designs, describe content,
|
|
280
|
+
and streamline the management of your site. We strongly recommend
|
|
281
|
+
enabling these features. See{' '}
|
|
282
282
|
<a
|
|
283
283
|
href="https://freewebpress.org"
|
|
284
284
|
target="_blank"
|
|
@@ -11,7 +11,7 @@ import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
|
|
|
11
11
|
import { AiRefineDesignStep } from './steps/AiRefineDesignStep';
|
|
12
12
|
import prompts from '@/constants/prompts.json';
|
|
13
13
|
import { parseAiPane } from '@/utils/compositor/aiPaneParser';
|
|
14
|
-
import {
|
|
14
|
+
import { callAaiAPI } from '@/utils/compositor/aiGeneration';
|
|
15
15
|
import type { PaneNode, TemplatePane } from '@/types/compositorTypes';
|
|
16
16
|
|
|
17
17
|
interface AiRestylePaneModalProps {
|
|
@@ -87,7 +87,7 @@ export const AiRestylePaneModal = ({
|
|
|
87
87
|
.replace('{{COPY_INPUT}}', 'A generic content section')
|
|
88
88
|
.replace('{{LAYOUT_TYPE}}', 'Text Only');
|
|
89
89
|
|
|
90
|
-
const resultStr = await
|
|
90
|
+
const resultStr = await callAaiAPI({
|
|
91
91
|
prompt: formattedPrompt,
|
|
92
92
|
context: shellPromptDetails.system || '',
|
|
93
93
|
expectJson: true,
|
|
@@ -6,7 +6,7 @@ import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRou
|
|
|
6
6
|
import prompts from '@/constants/prompts.json';
|
|
7
7
|
import { htmlToHtmlAst } from '@/utils/compositor/htmlAst';
|
|
8
8
|
import type { TemplatePane } from '@/types/compositorTypes';
|
|
9
|
-
import {
|
|
9
|
+
import { callAaiAPI } from '@/utils/compositor/aiGeneration';
|
|
10
10
|
import BooleanToggle from '@/components/form/BooleanToggle';
|
|
11
11
|
import { AiDesignStep, type AiDesignConfig } from './AiDesignStep';
|
|
12
12
|
|
|
@@ -86,7 +86,7 @@ export const AiCreativeDesignStep = ({
|
|
|
86
86
|
userPrompt = userPrompt.replace('{{DESIGN_NOTES}}', combinedNotes);
|
|
87
87
|
|
|
88
88
|
// Use shared infrastructure utility
|
|
89
|
-
const rawHtml = await
|
|
89
|
+
const rawHtml = await callAaiAPI({
|
|
90
90
|
prompt: userPrompt,
|
|
91
91
|
context: systemPrompt,
|
|
92
92
|
expectJson: false,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
convertTemplateToAIShell,
|
|
7
7
|
} from '@/utils/compositor/designLibraryHelper';
|
|
8
8
|
import { parseAiPane, parseAiCopyHtml } from '@/utils/compositor/aiPaneParser';
|
|
9
|
-
import {
|
|
9
|
+
import { callAaiAPI } from '@/utils/compositor/aiGeneration';
|
|
10
10
|
import { CopyInputStep, type CopyMode } from './CopyInputStep';
|
|
11
11
|
import type { DesignLibraryEntry } from '@/types/tractstack';
|
|
12
12
|
import type { TemplatePane } from '@/types/compositorTypes';
|
|
@@ -159,7 +159,7 @@ export const AiLibraryCopyStep = ({
|
|
|
159
159
|
.replace('{{LAYOUT_TYPE}}', layoutType)
|
|
160
160
|
.replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
|
|
161
161
|
|
|
162
|
-
const copyResult = await
|
|
162
|
+
const copyResult = await callAaiAPI({
|
|
163
163
|
prompt: formattedCopyPrompt,
|
|
164
164
|
context: copyPromptDetails.system || '',
|
|
165
165
|
expectJson: false,
|
|
@@ -201,7 +201,7 @@ export const AiLibraryCopyStep = ({
|
|
|
201
201
|
.replace('{{LAYOUT_TYPE}}', layoutType)
|
|
202
202
|
.replace('{{SHELL_JSON}}', shellResult);
|
|
203
203
|
|
|
204
|
-
const copyResult = await
|
|
204
|
+
const copyResult = await callAaiAPI({
|
|
205
205
|
prompt: formattedCopyPrompt,
|
|
206
206
|
context: copyPromptDetails.system || '',
|
|
207
207
|
expectJson: false,
|
|
@@ -5,7 +5,7 @@ import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
|
5
5
|
import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
|
|
6
6
|
import prompts from '@/constants/prompts.json';
|
|
7
7
|
import { htmlToHtmlAst, cleanHtml } from '@/utils/compositor/htmlAst';
|
|
8
|
-
import {
|
|
8
|
+
import { callAaiAPI } from '@/utils/compositor/aiGeneration';
|
|
9
9
|
import type { TemplatePane } from '@/types/compositorTypes';
|
|
10
10
|
|
|
11
11
|
interface AiRefineDesignStepProps {
|
|
@@ -54,7 +54,7 @@ export const AiRefineDesignStep = ({
|
|
|
54
54
|
userPrompt = userPrompt.replace('{{HTML_INPUT}}', cleanHtml(initialHtml));
|
|
55
55
|
|
|
56
56
|
// 1. Get RAW output from AI
|
|
57
|
-
const resultHtml = await
|
|
57
|
+
const resultHtml = await callAaiAPI({
|
|
58
58
|
prompt: userPrompt,
|
|
59
59
|
context: systemPrompt,
|
|
60
60
|
expectJson: false,
|