astro-tractstack 2.3.0 → 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/README.md +1 -1
- package/bin/create-tractstack.js +2 -2
- package/dist/index.js +130 -19
- package/package.json +2 -2
- package/templates/custom/minimal/CodeHook.astro +10 -2
- package/templates/custom/shopify/Cart.tsx +115 -77
- package/templates/custom/shopify/CheckoutModal.tsx +509 -120
- package/templates/custom/shopify/NativeBookingCalendar.tsx +375 -0
- package/templates/custom/shopify/ShopifyCartManager.tsx +91 -45
- package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
- package/templates/custom/shopify/ShopifyProductGrid.tsx +170 -176
- package/templates/custom/shopify/ShopifyServiceList.tsx +112 -51
- package/templates/custom/with-examples/CodeHook.astro +10 -2
- package/templates/src/components/Footer.astro +6 -6
- package/templates/src/components/Header.astro +23 -11
- 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_new.tsx +3 -3
- package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
- 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/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 +208 -2
- package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
- 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 +252 -110
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
- 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 +88 -56
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +14 -4
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +14 -5
- 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 +104 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +419 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -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/layouts/Layout.astro +8 -5
- 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 +97 -25
- package/templates/src/types/formTypes.ts +4 -2
- package/templates/src/types/tractstack.ts +59 -2
- 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/brandConfig.ts +2 -0
- package/templates/src/utils/api/brandHelpers.ts +26 -0
- package/templates/src/utils/api/emailHelpers.ts +105 -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 +3 -2
- package/utils/inject-files.ts +116 -5
- package/templates/custom/shopify/CalDotComBooking.tsx +0 -44
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
parseAiPane,
|
|
5
5
|
createDefaultShell,
|
|
6
6
|
} from '@/utils/compositor/aiPaneParser';
|
|
7
|
-
import {
|
|
7
|
+
import { callAaiAPI } from '@/utils/compositor/aiGeneration';
|
|
8
8
|
import { markdownToHtml } from '@/utils/compositor/htmlAst';
|
|
9
9
|
import { CopyInputStep, type CopyMode } from './CopyInputStep';
|
|
10
10
|
import { AiDesignStep, type AiDesignConfig } from './AiDesignStep';
|
|
@@ -148,7 +148,7 @@ export const AiStandardDesignStep = ({
|
|
|
148
148
|
let shellResult = '';
|
|
149
149
|
|
|
150
150
|
if (isAiStyling) {
|
|
151
|
-
// AI Path: Generate Shell via
|
|
151
|
+
// AI Path: Generate Shell via Aai
|
|
152
152
|
let designInput = `Generate a design using a **${aiDesignConfig.harmony.toLowerCase()}** color scheme with a **${aiDesignConfig.theme.toLowerCase()}** theme.`;
|
|
153
153
|
if (aiDesignConfig.baseColor)
|
|
154
154
|
designInput += ` Base the colors around **${aiDesignConfig.baseColor}**.`;
|
|
@@ -173,7 +173,7 @@ export const AiStandardDesignStep = ({
|
|
|
173
173
|
);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
shellResult = await
|
|
176
|
+
shellResult = await callAaiAPI({
|
|
177
177
|
prompt: formattedShellPrompt,
|
|
178
178
|
context: masterShellSystem,
|
|
179
179
|
expectJson: true,
|
|
@@ -204,7 +204,7 @@ export const AiStandardDesignStep = ({
|
|
|
204
204
|
.replace('{{LAYOUT_TYPE}}', layoutType)
|
|
205
205
|
.replace('{{SHELL_JSON}}', shellResult);
|
|
206
206
|
|
|
207
|
-
finalHtml = await
|
|
207
|
+
finalHtml = await callAaiAPI({
|
|
208
208
|
prompt: formattedCopyPrompt,
|
|
209
209
|
context: masterCopySystem,
|
|
210
210
|
expectJson: false,
|
|
@@ -218,7 +218,7 @@ export const AiStandardDesignStep = ({
|
|
|
218
218
|
.replace('{{SHELL_JSON}}', shellResult)
|
|
219
219
|
.replace('{{COPY_INPUT}}', copyValue);
|
|
220
220
|
|
|
221
|
-
finalHtml = await
|
|
221
|
+
finalHtml = await callAaiAPI({
|
|
222
222
|
prompt: formattedStylePrompt,
|
|
223
223
|
context: masterStyleSystem,
|
|
224
224
|
expectJson: false,
|
|
@@ -246,7 +246,7 @@ export const AiStandardDesignStep = ({
|
|
|
246
246
|
.replace('{{SHELL_JSON}}', shellResult)
|
|
247
247
|
.replace('{{COPY_INPUT}}', content);
|
|
248
248
|
|
|
249
|
-
const styledHtml = await
|
|
249
|
+
const styledHtml = await callAaiAPI({
|
|
250
250
|
prompt: formattedStylePrompt,
|
|
251
251
|
context: masterStyleSystem,
|
|
252
252
|
expectJson: false,
|
|
@@ -289,7 +289,7 @@ export const AiStandardDesignStep = ({
|
|
|
289
289
|
.replace('{{LAYOUT_TYPE}}', layoutType)
|
|
290
290
|
.replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
|
|
291
291
|
|
|
292
|
-
const copyResult = await
|
|
292
|
+
const copyResult = await callAaiAPI({
|
|
293
293
|
prompt: formattedCopyPrompt,
|
|
294
294
|
context: masterCopySystem,
|
|
295
295
|
expectJson: false,
|
|
@@ -71,7 +71,12 @@ const IconSelector = ({
|
|
|
71
71
|
() => createListCollection({ items: filteredIcons }),
|
|
72
72
|
[filteredIcons]
|
|
73
73
|
);
|
|
74
|
-
const iconSelectorStyles =
|
|
74
|
+
const iconSelectorStyles = `
|
|
75
|
+
.icon-item .icon-indicator { display: none; }
|
|
76
|
+
.icon-item[data-state="checked"] .icon-indicator { display: flex; }
|
|
77
|
+
.icon-item[data-highlighted] { background-color: #0891b2; color: #fff; }
|
|
78
|
+
.icon-item[data-highlighted] .icon-indicator { color: #fff; }
|
|
79
|
+
`;
|
|
75
80
|
return (
|
|
76
81
|
<div>
|
|
77
82
|
<style>{iconSelectorStyles}</style>
|
|
@@ -100,12 +105,12 @@ const IconSelector = ({
|
|
|
100
105
|
<Combobox.Item
|
|
101
106
|
key={icon}
|
|
102
107
|
item={icon}
|
|
103
|
-
className="icon-item relative cursor-pointer select-none py-2 pl-10 pr-4 text-gray-900
|
|
108
|
+
className="icon-item relative cursor-pointer select-none py-2 pl-10 pr-4 text-gray-900"
|
|
104
109
|
>
|
|
105
110
|
<Combobox.ItemText>
|
|
106
111
|
<i className={`bi bi-${icon} mr-2 text-lg`}></i> {icon}
|
|
107
112
|
</Combobox.ItemText>
|
|
108
|
-
<Combobox.ItemIndicator className="icon-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600
|
|
113
|
+
<Combobox.ItemIndicator className="icon-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
|
|
109
114
|
<CheckIcon className="h-5 w-5" />
|
|
110
115
|
</Combobox.ItemIndicator>
|
|
111
116
|
</Combobox.Item>
|
|
@@ -40,6 +40,12 @@ const ampmOptions = [
|
|
|
40
40
|
{ value: 'PM', label: 'PM' },
|
|
41
41
|
];
|
|
42
42
|
|
|
43
|
+
const datetimeSelectItemHighlightStyles = `
|
|
44
|
+
.datetime-select-item[data-highlighted] {
|
|
45
|
+
background-color: #eff6ff;
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
|
|
43
49
|
const DateTimeInput = ({
|
|
44
50
|
value,
|
|
45
51
|
onChange,
|
|
@@ -450,6 +456,7 @@ const DateTimeInput = ({
|
|
|
450
456
|
{/* Time Picker Section (only show if withTime is true and not date-only) */}
|
|
451
457
|
{withTime && displayFormat !== 'date' && (
|
|
452
458
|
<div className="space-y-2">
|
|
459
|
+
<style>{datetimeSelectItemHighlightStyles}</style>
|
|
453
460
|
<div className="text-xs font-bold text-gray-600">
|
|
454
461
|
Time (Local Timezone)
|
|
455
462
|
</div>
|
|
@@ -489,7 +496,7 @@ const DateTimeInput = ({
|
|
|
489
496
|
<Select.Item
|
|
490
497
|
key={option.value}
|
|
491
498
|
item={option}
|
|
492
|
-
className="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50
|
|
499
|
+
className="datetime-select-item flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50"
|
|
493
500
|
>
|
|
494
501
|
<Select.ItemText>{option.label}</Select.ItemText>
|
|
495
502
|
<Select.ItemIndicator>
|
|
@@ -540,7 +547,7 @@ const DateTimeInput = ({
|
|
|
540
547
|
<Select.Item
|
|
541
548
|
key={option.value}
|
|
542
549
|
item={option}
|
|
543
|
-
className="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50
|
|
550
|
+
className="datetime-select-item flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50"
|
|
544
551
|
>
|
|
545
552
|
<Select.ItemText>{option.label}</Select.ItemText>
|
|
546
553
|
<Select.ItemIndicator>
|
|
@@ -589,7 +596,7 @@ const DateTimeInput = ({
|
|
|
589
596
|
<Select.Item
|
|
590
597
|
key={option.value}
|
|
591
598
|
item={option}
|
|
592
|
-
className="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50
|
|
599
|
+
className="datetime-select-item flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50"
|
|
593
600
|
>
|
|
594
601
|
<Select.ItemText>{option.label}</Select.ItemText>
|
|
595
602
|
<Select.ItemIndicator>
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useRef,
|
|
3
|
+
useState,
|
|
4
|
+
useId,
|
|
5
|
+
type ChangeEvent,
|
|
6
|
+
type DragEvent,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { classNames } from '@/utils/helpers';
|
|
3
9
|
import {
|
|
4
10
|
validateFile,
|
|
@@ -151,14 +157,14 @@ const FileUpload = ({
|
|
|
151
157
|
}
|
|
152
158
|
};
|
|
153
159
|
|
|
154
|
-
const handleFileSelect = (e:
|
|
160
|
+
const handleFileSelect = (e: ChangeEvent<HTMLInputElement>) => {
|
|
155
161
|
const file = e.target.files?.[0];
|
|
156
162
|
if (file) {
|
|
157
163
|
processFile(file);
|
|
158
164
|
}
|
|
159
165
|
};
|
|
160
166
|
|
|
161
|
-
const handleDrop = (e:
|
|
167
|
+
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
|
162
168
|
e.preventDefault();
|
|
163
169
|
setDragOver(false);
|
|
164
170
|
|
|
@@ -170,14 +176,14 @@ const FileUpload = ({
|
|
|
170
176
|
}
|
|
171
177
|
};
|
|
172
178
|
|
|
173
|
-
const handleDragOver = (e:
|
|
179
|
+
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
|
|
174
180
|
e.preventDefault();
|
|
175
181
|
if (!disabled) {
|
|
176
182
|
setDragOver(true);
|
|
177
183
|
}
|
|
178
184
|
};
|
|
179
185
|
|
|
180
|
-
const handleDragLeave = (e:
|
|
186
|
+
const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {
|
|
181
187
|
e.preventDefault();
|
|
182
188
|
setDragOver(false);
|
|
183
189
|
};
|
|
@@ -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:
|
|
45
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
46
46
|
const newValue = parseFloat(e.target.value);
|
|
47
47
|
if (!isNaN(newValue)) {
|
|
48
48
|
onChange(newValue);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Dialog, Portal } from '@ark-ui/react';
|
|
1
3
|
import StringInput from '../StringInput';
|
|
4
|
+
import BooleanToggle from '../BooleanToggle';
|
|
2
5
|
import type { FormStateReturn } from '@/hooks/useFormState';
|
|
3
6
|
import type {
|
|
4
7
|
AdvancedConfigState,
|
|
@@ -16,13 +19,18 @@ export default function APIConfigSection({
|
|
|
16
19
|
}: APIConfigSectionProps) {
|
|
17
20
|
const { state, updateField, errors } = formState;
|
|
18
21
|
|
|
22
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
23
|
+
const goBackend =
|
|
24
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
25
|
+
|
|
19
26
|
// Status flags
|
|
20
27
|
const aaiConfigured = status?.aaiAPIKeySet;
|
|
21
28
|
const shopifyStorefrontConfigured = status?.shopifyStorefrontTokenSet;
|
|
22
29
|
const shopifySecretConfigured = status?.shopifyApiSecretSet;
|
|
23
30
|
const shopifyDomainConfigured = status?.shopifyStoreDomainSet;
|
|
24
31
|
const shopifyVersionConfigured = Boolean(status?.shopifyApiVersion);
|
|
25
|
-
|
|
32
|
+
const shopifyAdminSlugConfigured = status?.shopifyAdminSlugSet;
|
|
33
|
+
const shopifyWebhooksConfigured = status?.userSetupWebhooks;
|
|
26
34
|
const resendConfigured = status?.resendApiKeySet;
|
|
27
35
|
|
|
28
36
|
const renderStatusBadge = (isConfigured: boolean | undefined) => {
|
|
@@ -92,7 +100,9 @@ export default function APIConfigSection({
|
|
|
92
100
|
shopifyStorefrontConfigured &&
|
|
93
101
|
shopifySecretConfigured &&
|
|
94
102
|
shopifyDomainConfigured &&
|
|
95
|
-
shopifyVersionConfigured
|
|
103
|
+
shopifyVersionConfigured &&
|
|
104
|
+
shopifyAdminSlugConfigured &&
|
|
105
|
+
shopifyWebhooksConfigured
|
|
96
106
|
)}
|
|
97
107
|
</div>
|
|
98
108
|
<div className="space-y-4">
|
|
@@ -113,6 +123,20 @@ export default function APIConfigSection({
|
|
|
113
123
|
</p>
|
|
114
124
|
</div>
|
|
115
125
|
|
|
126
|
+
<div>
|
|
127
|
+
<StringInput
|
|
128
|
+
label="Shopify Admin Slug"
|
|
129
|
+
value={state.shopifyAdminSlug}
|
|
130
|
+
onChange={(value) => updateField('shopifyAdminSlug', value)}
|
|
131
|
+
placeholder="your-store-slug"
|
|
132
|
+
error={errors.shopifyAdminSlug}
|
|
133
|
+
/>
|
|
134
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
135
|
+
The internal Shopify slug (found in
|
|
136
|
+
admin.shopify.com/store/SLUG).
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
116
140
|
<div>
|
|
117
141
|
<StringInput
|
|
118
142
|
label="API Version"
|
|
@@ -165,6 +189,37 @@ export default function APIConfigSection({
|
|
|
165
189
|
{shopifySecretConfigured && ' Leave blank to keep existing.'}
|
|
166
190
|
</p>
|
|
167
191
|
</div>
|
|
192
|
+
|
|
193
|
+
{!shopifyWebhooksConfigured && (
|
|
194
|
+
<div className="mt-6 border-t border-gray-100 pt-6">
|
|
195
|
+
<div className="mb-4 rounded-md border border-amber-200 bg-amber-50 p-4">
|
|
196
|
+
<h5 className="text-sm font-bold text-amber-800">
|
|
197
|
+
Webhook Configuration Required
|
|
198
|
+
</h5>
|
|
199
|
+
<p className="mb-3 mt-1 text-xs text-amber-700">
|
|
200
|
+
To ensure synchronization between Shopify and the
|
|
201
|
+
TractStack native booking system, you must configure
|
|
202
|
+
webhook subscriptions within your Shopify Admin panel.
|
|
203
|
+
</p>
|
|
204
|
+
<button
|
|
205
|
+
onClick={() => setIsModalOpen(true)}
|
|
206
|
+
className="text-xs font-bold text-amber-900 underline hover:text-amber-700"
|
|
207
|
+
type="button"
|
|
208
|
+
>
|
|
209
|
+
[ Detailed Instructions ]
|
|
210
|
+
</button>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<BooleanToggle
|
|
214
|
+
label="Webhooks Manually Configured"
|
|
215
|
+
description="I have manually created the required webhooks in my Shopify Admin."
|
|
216
|
+
value={state.userSetupWebhooks}
|
|
217
|
+
onChange={(value) =>
|
|
218
|
+
updateField('userSetupWebhooks', value)
|
|
219
|
+
}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
168
223
|
</div>
|
|
169
224
|
</div>
|
|
170
225
|
|
|
@@ -191,6 +246,157 @@ export default function APIConfigSection({
|
|
|
191
246
|
</div>
|
|
192
247
|
</div>
|
|
193
248
|
</div>
|
|
249
|
+
|
|
250
|
+
<Dialog.Root
|
|
251
|
+
open={isModalOpen}
|
|
252
|
+
onOpenChange={(details) => setIsModalOpen(details.open)}
|
|
253
|
+
preventScroll={true}
|
|
254
|
+
lazyMount
|
|
255
|
+
unmountOnExit
|
|
256
|
+
>
|
|
257
|
+
<Portal>
|
|
258
|
+
<Dialog.Backdrop className="fixed inset-0 z-50 bg-black bg-opacity-75" />
|
|
259
|
+
<Dialog.Positioner className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
260
|
+
<Dialog.Content
|
|
261
|
+
className="relative flex w-full max-w-2xl flex-col overflow-hidden rounded-lg bg-white shadow-xl"
|
|
262
|
+
style={{ maxHeight: '90vh' }}
|
|
263
|
+
>
|
|
264
|
+
{/* Header - Fixed height */}
|
|
265
|
+
<div className="flex items-center justify-between border-b border-gray-200 px-6 py-4">
|
|
266
|
+
<Dialog.Title className="text-xl font-bold text-gray-900">
|
|
267
|
+
How to Configure Webhooks in Shopify
|
|
268
|
+
</Dialog.Title>
|
|
269
|
+
<Dialog.CloseTrigger asChild>
|
|
270
|
+
<button
|
|
271
|
+
className="text-gray-400 hover:text-gray-600"
|
|
272
|
+
type="button"
|
|
273
|
+
>
|
|
274
|
+
<span className="text-2xl">×</span>
|
|
275
|
+
</button>
|
|
276
|
+
</Dialog.CloseTrigger>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
{/* Body - Scrollable via flex-1 */}
|
|
280
|
+
<div className="flex-1 overflow-y-auto p-6 text-sm text-gray-700">
|
|
281
|
+
<ol className="mb-6 list-decimal space-y-2 pl-5">
|
|
282
|
+
<li>Log in to your Shopify Admin dashboard.</li>
|
|
283
|
+
<li>
|
|
284
|
+
Click on <strong>Settings</strong> (the gear icon) in the
|
|
285
|
+
bottom left corner.
|
|
286
|
+
</li>
|
|
287
|
+
<li>
|
|
288
|
+
In the left sidebar, select <strong>Notifications</strong>.
|
|
289
|
+
</li>
|
|
290
|
+
<li>
|
|
291
|
+
Scroll all the way to the bottom to the{' '}
|
|
292
|
+
<strong>Webhooks</strong> section.
|
|
293
|
+
</li>
|
|
294
|
+
<li>
|
|
295
|
+
Click the <strong>Create webhook</strong> button.
|
|
296
|
+
</li>
|
|
297
|
+
<li>
|
|
298
|
+
For each required webhook (listed below), configure the
|
|
299
|
+
following settings:
|
|
300
|
+
<ul className="mt-2 list-disc space-y-1 pl-5">
|
|
301
|
+
<li>
|
|
302
|
+
<strong>Event:</strong> Select the specific event (e.g.,
|
|
303
|
+
Order payment, Product update).
|
|
304
|
+
</li>
|
|
305
|
+
<li>
|
|
306
|
+
<strong>Format:</strong> Select JSON (TractStack relies
|
|
307
|
+
on JSON unmarshaling).
|
|
308
|
+
</li>
|
|
309
|
+
<li>
|
|
310
|
+
<strong>URL:</strong> Enter your backend webhook URL:{' '}
|
|
311
|
+
<code className="rounded bg-gray-100 px-1 py-0.5 text-xs">
|
|
312
|
+
{goBackend}/api/v1/hooks/shopify
|
|
313
|
+
</code>
|
|
314
|
+
</li>
|
|
315
|
+
<li>
|
|
316
|
+
<strong>Webhook API version:</strong> Select the version
|
|
317
|
+
that matches your ShopifyAPIVersion configured in
|
|
318
|
+
TractStack.
|
|
319
|
+
</li>
|
|
320
|
+
</ul>
|
|
321
|
+
</li>
|
|
322
|
+
<li>
|
|
323
|
+
Click <strong>Save</strong>.
|
|
324
|
+
</li>
|
|
325
|
+
</ol>
|
|
326
|
+
|
|
327
|
+
<div className="mb-6 border-l-4 border-red-500 bg-red-50 p-4">
|
|
328
|
+
<p className="font-bold text-red-800">CRITICAL:</p>
|
|
329
|
+
<p className="mt-1 text-red-700">
|
|
330
|
+
After saving your first webhook, Shopify will display a{' '}
|
|
331
|
+
<strong>Webhook signing secret</strong> at the bottom of the
|
|
332
|
+
Webhooks section. You must copy this secret and add it to
|
|
333
|
+
your TractStack API Config as the Shopify API Secret. The
|
|
334
|
+
backend uses this to verify the HMAC signature of all
|
|
335
|
+
incoming payloads.
|
|
336
|
+
</p>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<h4 className="mb-3 text-lg font-bold">
|
|
340
|
+
Required Webhooks Breakdown
|
|
341
|
+
</h4>
|
|
342
|
+
<p className="mb-4">
|
|
343
|
+
You must create a separate webhook subscription for each of
|
|
344
|
+
the following four topics. All of them should point to the
|
|
345
|
+
exact same URL:{' '}
|
|
346
|
+
<code className="rounded bg-gray-100 px-1 py-0.5 text-xs">
|
|
347
|
+
{goBackend}/api/v1/hooks/shopify
|
|
348
|
+
</code>
|
|
349
|
+
</p>
|
|
350
|
+
|
|
351
|
+
<div className="space-y-4">
|
|
352
|
+
<div className="rounded border border-gray-200 bg-gray-50 p-4">
|
|
353
|
+
<h5 className="font-bold">1. Order Paid</h5>
|
|
354
|
+
<ul className="mt-2 space-y-1 text-xs">
|
|
355
|
+
<li>
|
|
356
|
+
<span className="font-bold text-gray-900">
|
|
357
|
+
Shopify Event Name:
|
|
358
|
+
</span>{' '}
|
|
359
|
+
Order payment
|
|
360
|
+
</li>
|
|
361
|
+
<li>
|
|
362
|
+
<span className="font-bold text-gray-900">
|
|
363
|
+
Purpose:
|
|
364
|
+
</span>{' '}
|
|
365
|
+
Transitions the corresponding hold in the bookings
|
|
366
|
+
database table from PENDING to CONFIRMED.
|
|
367
|
+
</li>
|
|
368
|
+
</ul>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<div className="rounded border border-gray-200 bg-gray-50 p-4">
|
|
372
|
+
<h5 className="font-bold">3. Product Update</h5>
|
|
373
|
+
<ul className="mt-2 space-y-1 text-xs">
|
|
374
|
+
<li>
|
|
375
|
+
<span className="font-bold text-gray-900">
|
|
376
|
+
Shopify Event Name:
|
|
377
|
+
</span>{' '}
|
|
378
|
+
Product update
|
|
379
|
+
</li>
|
|
380
|
+
</ul>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<div className="rounded border border-gray-200 bg-gray-50 p-4">
|
|
384
|
+
<h5 className="font-bold">4. Product Deletion</h5>
|
|
385
|
+
<ul className="mt-2 space-y-1 text-xs">
|
|
386
|
+
<li>
|
|
387
|
+
<span className="font-bold text-gray-900">
|
|
388
|
+
Shopify Event Name:
|
|
389
|
+
</span>{' '}
|
|
390
|
+
Product deletion
|
|
391
|
+
</li>
|
|
392
|
+
</ul>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
</Dialog.Content>
|
|
397
|
+
</Dialog.Positioner>
|
|
398
|
+
</Portal>
|
|
399
|
+
</Dialog.Root>
|
|
194
400
|
</div>
|
|
195
401
|
);
|
|
196
402
|
}
|
|
@@ -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)}
|