astro-tractstack 2.2.9 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/bin/create-tractstack.js +2 -2
  2. package/dist/index.js +89 -8
  3. package/package.json +3 -1
  4. package/templates/custom/minimal/CodeHook.astro +14 -5
  5. package/templates/custom/shopify/CalDotComBooking.tsx +44 -0
  6. package/templates/custom/shopify/Cart.tsx +345 -0
  7. package/templates/custom/shopify/CartIcon.tsx +47 -0
  8. package/templates/custom/shopify/CartModal.tsx +63 -0
  9. package/templates/custom/shopify/CheckoutModal.tsx +187 -0
  10. package/templates/custom/shopify/ShopifyCartManager.tsx +145 -0
  11. package/templates/custom/shopify/ShopifyCheckout.tsx +167 -0
  12. package/templates/custom/shopify/ShopifyProductGrid.tsx +281 -0
  13. package/templates/custom/shopify/ShopifyServiceList.tsx +118 -0
  14. package/templates/custom/shopify/cart.astro +23 -0
  15. package/templates/custom/with-examples/CodeHook.astro +9 -1
  16. package/templates/custom/with-examples/ProductGrid.astro +1 -1
  17. package/templates/src/client/app.js +4 -2
  18. package/templates/src/components/Header.astro +37 -11
  19. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +13 -2
  20. package/templates/src/components/form/advanced/APIConfigSection.tsx +165 -38
  21. package/templates/src/components/storykeep/Dashboard.tsx +17 -3
  22. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -0
  23. package/templates/src/components/storykeep/Dashboard_Content.tsx +5 -96
  24. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +525 -0
  25. package/templates/src/components/storykeep/StoryKeepBackdrop.astro +43 -23
  26. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +0 -14
  27. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +36 -13
  28. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +4 -11
  29. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +254 -0
  30. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +108 -8
  31. package/templates/src/lib/resources.ts +11 -21
  32. package/templates/src/pages/api/shopify/createCart.ts +73 -0
  33. package/templates/src/pages/api/shopify/getProducts.ts +64 -0
  34. package/templates/src/pages/storykeep/login.astro +5 -10
  35. package/templates/src/pages/storykeep/logout.astro +1 -10
  36. package/templates/src/pages/storykeep/manage.astro +69 -0
  37. package/templates/src/pages/storykeep/{content.astro → pages.astro} +4 -8
  38. package/templates/src/pages/storykeep/shopify.astro +101 -0
  39. package/templates/src/stores/navigation.ts +3 -42
  40. package/templates/src/stores/nodes.ts +3 -1
  41. package/templates/src/stores/resources.ts +7 -10
  42. package/templates/src/stores/shopify.ts +210 -0
  43. package/templates/src/types/tractstack.ts +21 -0
  44. package/templates/src/utils/api/advancedConfig.ts +5 -1
  45. package/templates/src/utils/api/advancedHelpers.ts +48 -5
  46. package/templates/src/utils/api/brandHelpers.ts +4 -0
  47. package/templates/src/utils/api/resourceConfig.ts +13 -5
  48. package/templates/src/utils/customHelpers.ts +70 -0
  49. package/templates/src/utils/helpers.ts +59 -0
  50. package/utils/inject-files.ts +83 -2
@@ -16,53 +16,180 @@ export default function APIConfigSection({
16
16
  }: APIConfigSectionProps) {
17
17
  const { state, updateField, errors } = formState;
18
18
 
19
+ // Status flags
19
20
  const aaiConfigured = status?.aaiAPIKeySet;
21
+ const shopifyStorefrontConfigured = status?.shopifyStorefrontTokenSet;
22
+ const shopifySecretConfigured = status?.shopifyApiSecretSet;
23
+ const shopifyDomainConfigured = status?.shopifyStoreDomainSet;
24
+ const shopifyVersionConfigured = Boolean(status?.shopifyApiVersion);
25
+
26
+ const resendConfigured = status?.resendApiKeySet;
27
+
28
+ const renderStatusBadge = (isConfigured: boolean | undefined) => {
29
+ if (status === null) {
30
+ return (
31
+ <span className="ml-2 inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-bold text-gray-800">
32
+ Loading...
33
+ </span>
34
+ );
35
+ }
36
+ return isConfigured ? (
37
+ <span className="ml-2 inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-bold text-green-800">
38
+ ✓ Set
39
+ </span>
40
+ ) : (
41
+ <span className="ml-2 inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-bold text-yellow-800">
42
+ Not Set
43
+ </span>
44
+ );
45
+ };
20
46
 
21
47
  return (
22
48
  <div className="bg-white shadow md:rounded-lg">
23
49
  <div className="px-4 py-5 md:p-6">
24
- <div className="flex items-center justify-between">
25
- <h3 className="text-base font-bold leading-6 text-gray-900">
26
- API Configuration
27
- </h3>
28
- <div className="flex items-center">
29
- {aaiConfigured ? (
30
- <span className="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-bold text-green-800">
31
- ✓ Configured
32
- </span>
33
- ) : status !== null ? (
34
- <span className="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-bold text-yellow-800">
35
- Optional
36
- </span>
37
- ) : (
38
- <span className="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-bold text-gray-800">
39
- Loading...
40
- </span>
41
- )}
42
- </div>
43
- </div>
50
+ <h3 className="text-base font-bold leading-6 text-gray-900">
51
+ API Integrations
52
+ </h3>
44
53
  <div className="mt-2 max-w-xl text-sm text-gray-500">
45
- <p>Configure external API keys for enhanced functionality.</p>
46
- </div>
47
- <div className="mt-5">
48
- <StringInput
49
- label="AssemblyAI API Key"
50
- value={state.aaiApiKey}
51
- onChange={(value) => updateField('aaiApiKey', value)}
52
- type="password"
53
- placeholder={
54
- aaiConfigured ? '••••••••••••••••' : 'Enter AssemblyAI API key'
55
- }
56
- error={errors.aaiApiKey}
57
- />
58
- </div>
59
- <div className="mt-3 text-xs text-gray-500">
60
54
  <p>
61
- Used for audio transcription and analysis features. Optional but
62
- required for audio processing.
63
- {aaiConfigured && ' Leave blank to keep existing key.'}
55
+ Configure external services to enable advanced features like AI,
56
+ Commerce, and Email.
64
57
  </p>
65
58
  </div>
59
+
60
+ <div className="mt-6 space-y-8">
61
+ {/* AssemblyAI Section */}
62
+ <div className="border-t border-gray-100 pt-6">
63
+ <div className="mb-4 flex items-center justify-between">
64
+ <h4 className="text-sm font-bold text-gray-900">
65
+ AssemblyAI (Audio Intelligence)
66
+ </h4>
67
+ {renderStatusBadge(aaiConfigured)}
68
+ </div>
69
+ <StringInput
70
+ label="API Key"
71
+ value={state.aaiApiKey}
72
+ onChange={(value) => updateField('aaiApiKey', value)}
73
+ type="password"
74
+ placeholder={
75
+ aaiConfigured ? '••••••••••••••••' : 'Enter AssemblyAI API key'
76
+ }
77
+ error={errors.aaiApiKey}
78
+ />
79
+ <p className="mt-2 text-xs text-gray-500">
80
+ Required for audio transcription and analysis.
81
+ {aaiConfigured && ' Leave blank to keep existing key.'}
82
+ </p>
83
+ </div>
84
+
85
+ {/* Shopify Section */}
86
+ <div className="border-t border-gray-100 pt-6">
87
+ <div className="mb-4 flex items-center justify-between">
88
+ <h4 className="text-sm font-bold text-gray-900">
89
+ Shopify (Commerce)
90
+ </h4>
91
+ {renderStatusBadge(
92
+ shopifyStorefrontConfigured &&
93
+ shopifySecretConfigured &&
94
+ shopifyDomainConfigured &&
95
+ shopifyVersionConfigured
96
+ )}
97
+ </div>
98
+ <div className="space-y-4">
99
+ <div>
100
+ <StringInput
101
+ label="Shopify Store Domain"
102
+ value={state.shopifyStoreDomain}
103
+ onChange={(value) => updateField('shopifyStoreDomain', value)}
104
+ placeholder={
105
+ shopifyDomainConfigured
106
+ ? 'your-shop.myshopify.com'
107
+ : 'your-shop.myshopify.com'
108
+ }
109
+ error={errors.shopifyStoreDomain}
110
+ />
111
+ <p className="mt-1 text-xs text-gray-500">
112
+ The primary .myshopify.com domain for your store.
113
+ </p>
114
+ </div>
115
+
116
+ <div>
117
+ <StringInput
118
+ label="API Version"
119
+ value={state.shopifyApiVersion}
120
+ onChange={(value) => updateField('shopifyApiVersion', value)}
121
+ type="text"
122
+ placeholder="2026-01"
123
+ error={errors.shopifyApiVersion}
124
+ />
125
+ <p className="mt-1 text-xs text-gray-500">
126
+ Target specific Shopify API version (YYYY-MM).
127
+ </p>
128
+ </div>
129
+
130
+ <div>
131
+ <StringInput
132
+ label="Headless channel, Private Access Token"
133
+ value={state.shopifyStorefrontToken}
134
+ onChange={(value) =>
135
+ updateField('shopifyStorefrontToken', value)
136
+ }
137
+ type="password"
138
+ placeholder={
139
+ shopifyStorefrontConfigured
140
+ ? '••••••••••••••••'
141
+ : 'shpat_...'
142
+ }
143
+ error={errors.shopifyStorefrontToken}
144
+ />
145
+ <p className="mt-1 text-xs text-gray-500">
146
+ Private access token for fetching products.
147
+ {shopifyStorefrontConfigured &&
148
+ ' Leave blank to keep existing.'}
149
+ </p>
150
+ </div>
151
+
152
+ <div>
153
+ <StringInput
154
+ label="API Secret Key"
155
+ value={state.shopifyApiSecret}
156
+ onChange={(value) => updateField('shopifyApiSecret', value)}
157
+ type="password"
158
+ placeholder={
159
+ shopifySecretConfigured ? '••••••••••••••••' : 'shpss_...'
160
+ }
161
+ error={errors.shopifyApiSecret}
162
+ />
163
+ <p className="mt-1 text-xs text-gray-500">
164
+ Required for Webhook signature verification.
165
+ {shopifySecretConfigured && ' Leave blank to keep existing.'}
166
+ </p>
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ {/* Resend Section */}
172
+ <div className="border-t border-gray-100 pt-6">
173
+ <div className="mb-4 flex items-center justify-between">
174
+ <h4 className="text-sm font-bold text-gray-900">
175
+ Resend (Transactional Email)
176
+ </h4>
177
+ {renderStatusBadge(resendConfigured)}
178
+ </div>
179
+ <StringInput
180
+ label="API Key"
181
+ value={state.resendApiKey}
182
+ onChange={(value) => updateField('resendApiKey', value)}
183
+ type="password"
184
+ placeholder={resendConfigured ? '••••••••••••••••' : 're_...'}
185
+ error={errors.resendApiKey}
186
+ />
187
+ <p className="mt-2 text-xs text-gray-500">
188
+ Required for sending system emails.
189
+ {resendConfigured && ' Leave blank to keep existing key.'}
190
+ </p>
191
+ </div>
192
+ </div>
66
193
  </div>
67
194
  </div>
68
195
  );
@@ -52,9 +52,14 @@ export default function StoryKeepDashboard({
52
52
  current: shouldShowInactiveTabs ? false : activeTab === 'analytics',
53
53
  },
54
54
  {
55
- id: 'content',
56
- name: 'Content',
57
- current: activeTab === 'content',
55
+ id: 'pages',
56
+ name: 'Web Pages',
57
+ current: activeTab === 'pages',
58
+ },
59
+ {
60
+ id: 'manage',
61
+ name: 'Manage Content',
62
+ current: activeTab === 'manage',
58
63
  },
59
64
  {
60
65
  id: 'branding',
@@ -70,6 +75,15 @@ export default function StoryKeepDashboard({
70
75
  },
71
76
  ]
72
77
  : []),
78
+ ...(currentBrandConfig?.HAS_SHOPIFY
79
+ ? [
80
+ {
81
+ id: 'shopify',
82
+ name: 'Shopify',
83
+ current: activeTab === 'shopify',
84
+ },
85
+ ]
86
+ : []),
73
87
  ];
74
88
 
75
89
  useEffect(() => {
@@ -72,6 +72,7 @@ export default function StoryKeepDashboard_Advanced({
72
72
  const newState = convertToLocalState(newStatus);
73
73
  formState.resetToState(newState);
74
74
 
75
+ window.location.reload();
75
76
  return newState;
76
77
  },
77
78
  });
@@ -1,39 +1,17 @@
1
1
  import { useState, useEffect } from 'react';
2
- import { classNames } from '@/utils/helpers';
3
2
  import { TractStackAPI } from '@/utils/api';
4
- import {
5
- handleContentSubtabChange,
6
- restoreTabNavigation,
7
- } from '@/stores/navigation';
8
3
  import ContentBrowser from './controls/content/ContentBrowser';
9
- import ManageContent from './controls/content/ManageContent';
10
4
  import type { FullContentMapItem } from '@/types/tractstack';
11
5
 
12
6
  interface StoryKeepDashboardContentProps {
13
7
  fullContentMap: FullContentMapItem[];
14
8
  homeSlug: string;
15
- createMenu: boolean;
16
9
  }
17
10
 
18
- interface ContentTab {
19
- id: string;
20
- name: string;
21
- }
22
-
23
- const contentTabs: ContentTab[] = [
24
- { id: 'webpages', name: 'Web Pages' },
25
- { id: 'manage', name: 'Manage Content' },
26
- ];
27
-
28
11
  const StoryKeepDashboard_Content = ({
29
12
  fullContentMap,
30
13
  homeSlug,
31
- createMenu,
32
14
  }: StoryKeepDashboardContentProps) => {
33
- const [activeContentTab, setActiveContentTab] = useState('webpages');
34
- const [navigationRestored, setNavigationRestored] = useState(false);
35
-
36
- // Lightweight analytics data state - only for hotContent
37
15
  const [analytics, setAnalytics] = useState<{
38
16
  dashboard: {
39
17
  hotContent?: Array<{ id: string; totalEvents: number }>;
@@ -48,29 +26,6 @@ const StoryKeepDashboard_Content = ({
48
26
  error: null,
49
27
  });
50
28
 
51
- // Restore navigation state when component mounts or when returning to Content tab
52
- useEffect(() => {
53
- if (!navigationRestored) {
54
- const contentNavigation = restoreTabNavigation();
55
- if (contentNavigation) {
56
- setActiveContentTab(contentNavigation.subtab);
57
- }
58
- setNavigationRestored(true);
59
- }
60
- }, [navigationRestored]);
61
-
62
- // Enhanced content tab change with navigation tracking
63
- const handleContentTabChange = (tabId: string) => {
64
- handleContentSubtabChange(tabId as any, setActiveContentTab);
65
- };
66
-
67
- useEffect(() => {
68
- if (createMenu) {
69
- setActiveContentTab('manage');
70
- }
71
- }, [createMenu]);
72
-
73
- // Lightweight content summary fetch with retry logic
74
29
  useEffect(() => {
75
30
  let retryCount = 0;
76
31
  const maxRetries = 2;
@@ -79,7 +34,6 @@ const StoryKeepDashboard_Content = ({
79
34
  try {
80
35
  setAnalytics((prev) => ({ ...prev, isLoading: true, error: null }));
81
36
 
82
- // Use TractStackAPI like FetchAnalytics does
83
37
  const api = new TractStackAPI(
84
38
  window.TRACTSTACK_CONFIG?.tenantId || 'default'
85
39
  );
@@ -91,8 +45,6 @@ const StoryKeepDashboard_Content = ({
91
45
  }
92
46
 
93
47
  const data = response.data;
94
-
95
- // Check if we got actual data
96
48
  const hasData = data.hotContent && data.hotContent.length > 0;
97
49
 
98
50
  setAnalytics({
@@ -140,56 +92,13 @@ const StoryKeepDashboard_Content = ({
140
92
  fetchContentSummary();
141
93
  }, []);
142
94
 
143
- const renderContentTabContent = () => {
144
- switch (activeContentTab) {
145
- case 'webpages':
146
- return (
147
- <ContentBrowser
148
- analytics={analytics}
149
- fullContentMap={fullContentMap}
150
- homeSlug={homeSlug}
151
- />
152
- );
153
- case 'manage':
154
- return (
155
- <ManageContent
156
- fullContentMap={fullContentMap}
157
- homeSlug={homeSlug}
158
- createMenu={createMenu}
159
- />
160
- );
161
- default:
162
- return null;
163
- }
164
- };
165
-
166
95
  return (
167
96
  <div className="w-full">
168
- {/* Content Sub-Navigation */}
169
- <div className="mb-6">
170
- <div className="border-b border-gray-200">
171
- <nav className="-mb-px flex gap-x-6" aria-label="Content tabs">
172
- {contentTabs.map((tab) => (
173
- <button
174
- key={tab.id}
175
- onClick={() => handleContentTabChange(tab.id)}
176
- className={classNames(
177
- activeContentTab === tab.id
178
- ? 'border-cyan-500 text-cyan-600'
179
- : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700',
180
- 'whitespace-nowrap border-b-2 px-1 py-3 text-sm font-bold'
181
- )}
182
- aria-current={activeContentTab === tab.id ? 'page' : undefined}
183
- >
184
- {tab.name}
185
- </button>
186
- ))}
187
- </nav>
188
- </div>
189
- </div>
190
-
191
- {/* Content Tab Content */}
192
- {renderContentTabContent()}
97
+ <ContentBrowser
98
+ analytics={analytics}
99
+ fullContentMap={fullContentMap}
100
+ homeSlug={homeSlug}
101
+ />
193
102
  </div>
194
103
  );
195
104
  };