astro-tractstack 2.3.4 → 2.4.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 (64) hide show
  1. package/bin/create-tractstack.js +38 -59
  2. package/dist/index.js +74 -40
  3. package/package.json +46 -9
  4. package/templates/custom/customHelpers.ts +45 -0
  5. package/templates/custom/minimal/codehooks.ts +13 -0
  6. package/templates/custom/shopify/Cart.tsx +2 -2
  7. package/templates/custom/shopify/CartIcon.tsx +1 -1
  8. package/templates/custom/shopify/CheckoutModal.tsx +3 -3
  9. package/templates/custom/shopify/ShopifyCartManager.tsx +3 -3
  10. package/templates/custom/shopify/ShopifyCheckout.tsx +1 -1
  11. package/templates/custom/shopify/ShopifyProductGrid.tsx +5 -5
  12. package/templates/custom/shopify/ShopifyServiceList.tsx +5 -5
  13. package/templates/custom/shopify/shopifyCustomHelper.ts +10 -0
  14. package/templates/{src/utils/customHelpers.ts → custom/shopify/shopifyHelpers.ts} +0 -74
  15. package/templates/custom/with-examples/codehooks.ts +15 -0
  16. package/templates/src/components/Header.astro +1 -1
  17. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +38 -23
  18. package/templates/src/components/codehooks/EpinetTableView.tsx +5 -2
  19. package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -5
  20. package/templates/src/components/codehooks/FeaturedArticle.astro +3 -3
  21. package/templates/src/components/codehooks/ListContent.astro +3 -3
  22. package/templates/src/components/codehooks/SearchWidget.tsx +1 -1
  23. package/templates/src/components/compositor/Node.tsx +13 -2
  24. package/templates/src/components/compositor/nodes/Pane.tsx +2 -14
  25. package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -2
  26. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +35 -14
  27. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  28. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +2 -2
  29. package/templates/src/components/search/SearchResults.tsx +1 -1
  30. package/templates/src/components/search/SearchWrapper.tsx +1 -1
  31. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +8 -4
  32. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +5 -2
  33. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +1 -1
  34. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Sales.tsx +1 -1
  35. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +8 -4
  36. package/templates/src/components/storykeep/widgets/Wizard.tsx +4 -2
  37. package/templates/src/lib/codeHookHelper.ts +156 -0
  38. package/templates/src/lib/resources.ts +41 -0
  39. package/templates/src/lib/storyData.ts +1 -2
  40. package/templates/src/pages/[...slug]/edit.astro +3 -3
  41. package/templates/src/pages/[...slug].astro +76 -70
  42. package/templates/src/pages/codehooks/[...hookId].astro +18 -0
  43. package/templates/src/pages/codehooks/bunny-video.astro +9 -0
  44. package/templates/src/pages/codehooks/custom-hero.astro +6 -0
  45. package/templates/src/pages/codehooks/epinet.astro +15 -0
  46. package/templates/src/pages/codehooks/featured-article.astro +13 -0
  47. package/templates/src/pages/codehooks/get-crafting.astro +8 -0
  48. package/templates/src/pages/codehooks/list-content.astro +13 -0
  49. package/templates/src/pages/codehooks/search-widget.astro +13 -0
  50. package/templates/src/pages/codehooks/shopify-product-grid.astro +25 -0
  51. package/templates/src/pages/codehooks/shopify-service-list.astro +25 -0
  52. package/templates/src/pages/context/[...contextSlug]/edit.astro +3 -3
  53. package/templates/src/pages/context/[...contextSlug].astro +47 -10
  54. package/templates/src/pages/sandbox.astro +3 -14
  55. package/templates/src/stores/analytics.ts +77 -107
  56. package/utils/inject-files.ts +76 -41
  57. package/templates/custom/minimal/CodeHook.astro +0 -72
  58. package/templates/custom/with-examples/CodeHook.astro +0 -81
  59. package/templates/custom/with-examples/ProductCard.astro +0 -29
  60. package/templates/custom/with-examples/ProductCardWrapper.astro +0 -43
  61. package/templates/custom/with-examples/ProductGrid.astro +0 -64
  62. package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -157
  63. package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -279
  64. /package/templates/{src/utils/booking → custom/shopify}/appointmentMode.ts +0 -0
@@ -1,6 +1,10 @@
1
1
  import { useEffect, useRef } from 'react';
2
2
  import { useStore } from '@nanostores/react';
3
- import { epinetCustomFilters } from '@/stores/analytics';
3
+ import {
4
+ epinetCustomFilters,
5
+ getEpinetCustomFilters,
6
+ setEpinetCustomFilters,
7
+ } from '@/stores/analytics';
4
8
  import { TractStackAPI } from '@/utils/api';
5
9
 
6
10
  const VERBOSE = false;
@@ -252,7 +256,7 @@ class AnalyticsService {
252
256
  this.setCachedResponse(cacheKey, analyticsData);
253
257
  onUpdate(analyticsData);
254
258
 
255
- epinetCustomFilters.set(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
259
+ setEpinetCustomFilters(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
256
260
  ...filters,
257
261
  availableFilters: data.availableFilters || [],
258
262
  });
@@ -298,9 +302,9 @@ class AnalyticsService {
298
302
  if (VERBOSE) console.log('🏁 Initializing analytics filters');
299
303
  const nowUTC = new Date();
300
304
  const oneWeekAgoUTC = new Date(nowUTC.getTime() - 7 * 24 * 60 * 60 * 1000);
301
- const current = epinetCustomFilters.get();
305
+ const current = getEpinetCustomFilters();
302
306
  if (!current.enabled) {
303
- epinetCustomFilters.set(tenantId, {
307
+ setEpinetCustomFilters(tenantId, {
304
308
  enabled: true,
305
309
  visitorType: 'all',
306
310
  selectedUserId: null,
@@ -139,7 +139,7 @@ export default function Wizard({
139
139
  const buildWizardData = async () => {
140
140
  try {
141
141
  const homePage = activeContentMap.find(
142
- (item) => item.slug === homeSlug
142
+ (item: FullContentMapItem) => item.slug === homeSlug
143
143
  );
144
144
 
145
145
  let homeData = null;
@@ -173,7 +173,9 @@ export default function Wizard({
173
173
  hasPanes: !!homePage?.panes?.length,
174
174
  hasSeo: !!homePage?.description,
175
175
  hasMenu: !!homeData?.menuId,
176
- hasAnyMenu: activeContentMap.some((item) => item.type === 'Menu'),
176
+ hasAnyMenu: activeContentMap.some(
177
+ (item: FullContentMapItem) => item.type === 'Menu'
178
+ ),
177
179
  };
178
180
 
179
181
  setWizardData(data);
@@ -0,0 +1,156 @@
1
+ import { getCachedFullContentMap, getFullContentMap } from '@/stores/analytics';
2
+ import { getCodeHookResources } from '@/lib/resources';
3
+ import type { ResourceNode } from '@/types/compositorTypes';
4
+
5
+ export interface CodeHookBladeContext {
6
+ tenantId: string;
7
+ paneId: string;
8
+ optionsStr: string;
9
+ options?: { params: { options: string } };
10
+ }
11
+
12
+ export interface CodeHookResourceFilters {
13
+ categories: string[];
14
+ slugs: string[];
15
+ }
16
+
17
+ export function parseCodeHookBladeContext(
18
+ searchParams: URLSearchParams
19
+ ): CodeHookBladeContext {
20
+ const tenantId = searchParams.get('tenantId') || 'default';
21
+ const paneId = searchParams.get('paneId') || '';
22
+ const optionsStr = searchParams.get('options') || '';
23
+ const options = optionsStr ? { params: { options: optionsStr } } : undefined;
24
+ return { tenantId, paneId, optionsStr, options };
25
+ }
26
+
27
+ export async function resolveCodeHookFullContentMap(
28
+ tenantId: string
29
+ ): Promise<any[]> {
30
+ let fullContentMap = getCachedFullContentMap(tenantId);
31
+ if (!fullContentMap.length) {
32
+ fullContentMap = await getFullContentMap(tenantId);
33
+ }
34
+ return fullContentMap;
35
+ }
36
+
37
+ export function parseCodeHookResourceFilters(
38
+ optionsStr: string,
39
+ logLabel?: string
40
+ ): CodeHookResourceFilters {
41
+ const categories: string[] = [];
42
+ const slugs: string[] = [];
43
+ if (!optionsStr) {
44
+ return { categories, slugs };
45
+ }
46
+ try {
47
+ const parsed = JSON.parse(optionsStr);
48
+ if (typeof parsed.category === 'string' && parsed.category) {
49
+ categories.push(...parsed.category.split('|'));
50
+ }
51
+ if (typeof parsed.slugs === 'string' && parsed.slugs) {
52
+ slugs.push(...parsed.slugs.split(','));
53
+ }
54
+ if (typeof parsed.slug === 'string' && parsed.slug) {
55
+ slugs.push(parsed.slug);
56
+ }
57
+ } catch (e) {
58
+ console.error(`Invalid options for ${logLabel ?? 'codehook resources'}`, e);
59
+ }
60
+ return { categories, slugs };
61
+ }
62
+
63
+ export async function resolveCodeHookResources(
64
+ tenantId: string,
65
+ optionsStr: string,
66
+ logLabel?: string
67
+ ): Promise<ResourceNode[]> {
68
+ const { categories, slugs } = parseCodeHookResourceFilters(
69
+ optionsStr,
70
+ logLabel
71
+ );
72
+ return getCodeHookResources(tenantId, categories, slugs);
73
+ }
74
+
75
+ export function buildCodeHookBladePath(
76
+ hookId: string,
77
+ ctx: Pick<CodeHookBladeContext, 'paneId' | 'tenantId' | 'optionsStr'>
78
+ ): string {
79
+ const params = new URLSearchParams({
80
+ paneId: ctx.paneId,
81
+ tenantId: ctx.tenantId,
82
+ });
83
+ if (ctx.optionsStr) {
84
+ params.set('options', ctx.optionsStr);
85
+ }
86
+ return `/codehooks/${hookId}?${params.toString()}`;
87
+ }
88
+
89
+ export function resolveSSRFetchOrigin(pageUrl: URL, siteUrl?: string): string {
90
+ if (siteUrl) {
91
+ try {
92
+ return new URL(siteUrl).origin;
93
+ } catch {
94
+ // fall through to page origin
95
+ }
96
+ }
97
+ return pageUrl.origin;
98
+ }
99
+
100
+ export async function fetchCodeHookBladeHtml(
101
+ bladePath: string,
102
+ request: Request,
103
+ origin: string
104
+ ): Promise<string> {
105
+ try {
106
+ const url = new URL(bladePath, origin);
107
+ const res = await fetch(url, {
108
+ headers: {
109
+ cookie: request.headers.get('cookie') ?? '',
110
+ accept: 'text/html',
111
+ },
112
+ });
113
+ if (!res.ok) {
114
+ console.error(
115
+ `Failed to SSR-fetch codehook blade ${bladePath}. Status: ${res.status}`
116
+ );
117
+ return '';
118
+ }
119
+ return res.text();
120
+ } catch (error) {
121
+ console.error(`Error SSR-fetching codehook blade ${bladePath}:`, error);
122
+ return '';
123
+ }
124
+ }
125
+
126
+ export async function fetchCodeHookBladesForPanes(options: {
127
+ paneIds: string[];
128
+ codeHookTargets: Record<string, string>;
129
+ tenantId: string;
130
+ request: Request;
131
+ origin: string;
132
+ }): Promise<Record<string, string>> {
133
+ const { paneIds, codeHookTargets, tenantId, request, origin } = options;
134
+ const result: Record<string, string> = {};
135
+
136
+ await Promise.all(
137
+ paneIds
138
+ .filter((paneId) => codeHookTargets[paneId])
139
+ .map(async (paneId) => {
140
+ const hookId = codeHookTargets[paneId];
141
+ const optionsStr = codeHookTargets[`${paneId}-${hookId}`] || '';
142
+ const bladePath = buildCodeHookBladePath(hookId, {
143
+ paneId,
144
+ tenantId,
145
+ optionsStr,
146
+ });
147
+ result[paneId] = await fetchCodeHookBladeHtml(
148
+ bladePath,
149
+ request,
150
+ origin
151
+ );
152
+ })
153
+ );
154
+
155
+ return result;
156
+ }
@@ -1,6 +1,47 @@
1
1
  import { headerResourcesStore, HEADER_RESOURCES_TTL } from '@/stores/resources';
2
2
  import type { ResourceNode } from '@/types/compositorTypes';
3
3
 
4
+ // Stateless resource fetch for codehook blades. POSTs categories/slugs to the
5
+ // nodes/resources endpoint, unwraps the { resources, count } envelope, and
6
+ // returns the FLAT ResourceNode[] (the API shape) unchanged - no grouping.
7
+ // Components that need a keyed view reshape internally. No cache.
8
+ export async function getCodeHookResources(
9
+ tenantId: string,
10
+ categories: string[] = [],
11
+ slugs: string[] = []
12
+ ): Promise<ResourceNode[]> {
13
+ if (categories.length === 0 && slugs.length === 0) {
14
+ return [];
15
+ }
16
+
17
+ const goBackend =
18
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
19
+
20
+ try {
21
+ const response = await fetch(`${goBackend}/api/v1/nodes/resources`, {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ 'X-Tenant-ID': tenantId,
26
+ },
27
+ body: JSON.stringify({ categories, slugs }),
28
+ });
29
+
30
+ if (!response.ok) {
31
+ console.error(
32
+ `Failed to fetch codehook resources. Status: ${response.status}`
33
+ );
34
+ return [];
35
+ }
36
+
37
+ const payload = await response.json();
38
+ return (payload.resources as ResourceNode[]) || [];
39
+ } catch (error) {
40
+ console.error('Error fetching codehook resources:', error);
41
+ return [];
42
+ }
43
+ }
44
+
4
45
  export async function getHeaderResources(
5
46
  tenantId: string,
6
47
  categories: string[],
@@ -1,5 +1,5 @@
1
1
  import { handleFailedResponse } from '@/utils/backend';
2
- import type { ImpressionNode, ResourceNode } from '@/types/compositorTypes';
2
+ import type { ImpressionNode } from '@/types/compositorTypes';
3
3
 
4
4
  export interface StoryData {
5
5
  id: string;
@@ -8,7 +8,6 @@ export interface StoryData {
8
8
  paneIds: string[];
9
9
  codeHookTargets: Record<string, string>;
10
10
  codeHookVisibility: Record<string, boolean | string[]>;
11
- resourcesPayload: Record<string, ResourceNode[]>;
12
11
  impressions: ImpressionNode[];
13
12
  fragments: Record<string, string>;
14
13
  menu: any;
@@ -6,7 +6,7 @@ import { getFullContentMap } from '@/stores/analytics';
6
6
  import { getBrandConfig } from '@/utils/api/brandConfig';
7
7
  import { joinUrlPaths } from '@/utils/helpers';
8
8
  import { handleFailedResponse } from '@/utils/backend';
9
- import { components as codeHookComponents } from '@/custom/CodeHook.astro';
9
+ import { availableCodeHookIds } from '@/custom/codehooks';
10
10
  import StoryKeepHeader from '@/components/edit/Header';
11
11
  import StoryKeepToolBar from '@/components/edit/ToolBar';
12
12
  import StoryKeepToolMode from '@/components/edit/ToolMode';
@@ -188,7 +188,7 @@ for (const [key, value] of Astro.url.searchParams) {
188
188
  fullContentMap={fullContentMap}
189
189
  fullCanonicalURL={fullCanonicalURL}
190
190
  urlParams={urlParams}
191
- availableCodeHooks={Object.keys(codeHookComponents)}
191
+ availableCodeHooks={availableCodeHookIds}
192
192
  client:only="react"
193
193
  />
194
194
  </div>
@@ -213,7 +213,7 @@ for (const [key, value] of Astro.url.searchParams) {
213
213
  }
214
214
  <div class="pointer-events-auto max-h-full">
215
215
  <SettingsPanel
216
- availableCodeHooks={Object.keys(codeHookComponents)}
216
+ availableCodeHooks={availableCodeHookIds}
217
217
  client:only="react"
218
218
  />
219
219
  </div>
@@ -1,10 +1,13 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
- import CodeHook from '@/custom/CodeHook.astro';
4
3
  import { getBrandConfig } from '@/utils/api/brandConfig';
5
4
  import { getFullContentMap } from '@/stores/analytics';
6
5
  import { getOrSetSessionId } from '@/lib/session';
7
6
  import { getStoryData } from '@/lib/storyData';
7
+ import {
8
+ fetchCodeHookBladesForPanes,
9
+ resolveSSRFetchOrigin,
10
+ } from '@/lib/codeHookHelper';
8
11
  import { preHealthCheck } from '@/utils/backend';
9
12
 
10
13
  const tenantId =
@@ -43,7 +46,6 @@ const storyfragmentId = storyData.id;
43
46
  const storyfragmentTitle = storyData.title || 'Untitled Story';
44
47
  const paneIds = storyData.paneIds || [];
45
48
  const codeHookTargets = storyData.codeHookTargets || {};
46
- const resourcesPayload = storyData.resourcesPayload || {};
47
49
 
48
50
  if (paneIds.length === 0) {
49
51
  console.log(`Empty Story Fragment. Redirecting to /storykeep`);
@@ -66,6 +68,14 @@ if (!brandConfig.SITE_INIT) {
66
68
  return Astro.redirect('/storykeep');
67
69
  }
68
70
 
71
+ const codeHookHtml = await fetchCodeHookBladesForPanes({
72
+ paneIds,
73
+ codeHookTargets,
74
+ tenantId,
75
+ request: Astro.request,
76
+ origin: resolveSSRFetchOrigin(Astro.url, brandConfig.SITE_URL),
77
+ });
78
+
69
79
  const ogImage =
70
80
  typeof storyData.socialImagePath === `string`
71
81
  ? storyData.socialImagePath
@@ -95,77 +105,73 @@ paneIds.forEach((paneId: string) => {
95
105
  <main id="main-content" class="w-full">
96
106
  <div class="panes-container">
97
107
  {
98
- paneIds.map((paneId: string) => (
99
- <div id={paneSlugLookup[paneId]}>
100
- <div
101
- id={`pane-${paneId}`}
102
- data-pane-id={paneId}
103
- class="pane-fragment-container"
104
- style={
105
- !codeHookTargets[paneId]
106
- ? undefined
107
- : !storyData.codeHookVisibility?.[paneId]
108
- ? 'display:none;'
109
- : 'display:block;'
110
- }
111
- hx-get={`/api/v1/fragments/panes/${paneId}`}
112
- hx-trigger="refresh"
113
- hx-swap="innerHTML scroll:none"
114
- >
115
- {codeHookTargets[paneId] ? (
116
- <div class="relative overflow-hidden">
117
- <CodeHook
118
- target={codeHookTargets[paneId]}
119
- paneId={paneId}
120
- options={(() => {
121
- const optionsStr =
122
- codeHookTargets[paneId + '-' + codeHookTargets[paneId]];
123
- return optionsStr
124
- ? { params: { options: optionsStr } }
125
- : undefined;
126
- })()}
127
- fullContentMap={fullContentMap}
128
- resourcesPayload={resourcesPayload}
129
- />
130
- <div id={`pane-${paneId}-unset`}>
131
- {Array.isArray(storyData.codeHookVisibility?.[paneId]) && (
132
- <button
133
- type="button"
134
- class="absolute right-2 top-2 z-10 rounded-full bg-white p-1.5 text-mydarkgrey hover:bg-black hover:text-white"
135
- title="Go Back"
136
- hx-post="/api/v1/state"
137
- hx-trigger="click"
138
- hx-swap="none"
139
- hx-vals={JSON.stringify({
140
- unsetBeliefIds:
141
- storyData.codeHookVisibility[paneId].join(','),
142
- paneId: paneId,
143
- })}
144
- hx-preserve="true"
145
- >
146
- <svg
147
- class="h-6 w-6"
148
- fill="none"
149
- viewBox="0 0 24 24"
150
- stroke-width="1.5"
151
- stroke="currentColor"
108
+ paneIds.map((paneId: string) => {
109
+ const hookId = codeHookTargets[paneId];
110
+ const isCodeHook = !!hookId;
111
+ return (
112
+ <div id={paneSlugLookup[paneId]}>
113
+ <div
114
+ id={`pane-${paneId}`}
115
+ data-pane-id={paneId}
116
+ class="pane-fragment-container"
117
+ style={
118
+ !isCodeHook
119
+ ? undefined
120
+ : !storyData.codeHookVisibility?.[paneId]
121
+ ? 'display:none;'
122
+ : 'display:block;'
123
+ }
124
+ hx-get={
125
+ !isCodeHook ? `/api/v1/fragments/panes/${paneId}` : undefined
126
+ }
127
+ hx-trigger={!isCodeHook ? 'refresh' : undefined}
128
+ hx-swap={!isCodeHook ? 'innerHTML scroll:none' : undefined}
129
+ >
130
+ {isCodeHook ? (
131
+ <div class="relative overflow-hidden">
132
+ <Fragment set:html={codeHookHtml[paneId] || ''} />
133
+ <div id={`pane-${paneId}-unset`}>
134
+ {Array.isArray(
135
+ storyData.codeHookVisibility?.[paneId]
136
+ ) && (
137
+ <button
138
+ type="button"
139
+ class="absolute right-2 top-2 z-10 rounded-full bg-white p-1.5 text-mydarkgrey hover:bg-black hover:text-white"
140
+ title="Go Back"
141
+ hx-post="/api/v1/state"
142
+ hx-trigger="click"
143
+ hx-swap="none"
144
+ hx-vals={JSON.stringify({
145
+ unsetBeliefIds:
146
+ storyData.codeHookVisibility[paneId].join(','),
147
+ paneId: paneId,
148
+ })}
149
+ hx-preserve="true"
152
150
  >
153
- <path
154
- stroke-linecap="round"
155
- stroke-linejoin="round"
156
- d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
157
- />
158
- </svg>
159
- </button>
160
- )}
151
+ <svg
152
+ class="h-6 w-6"
153
+ fill="none"
154
+ viewBox="0 0 24 24"
155
+ stroke-width="1.5"
156
+ stroke="currentColor"
157
+ >
158
+ <path
159
+ stroke-linecap="round"
160
+ stroke-linejoin="round"
161
+ d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
162
+ />
163
+ </svg>
164
+ </button>
165
+ )}
166
+ </div>
161
167
  </div>
162
- </div>
163
- ) : (
164
- <Fragment set:html={storyData.fragments[paneId] || ''} />
165
- )}
168
+ ) : (
169
+ <Fragment set:html={storyData.fragments[paneId] || ''} />
170
+ )}
171
+ </div>
166
172
  </div>
167
- </div>
168
- ))
173
+ );
174
+ })
169
175
  }
170
176
  </div>
171
177
  </main>
@@ -0,0 +1,18 @@
1
+ ---
2
+ export const partial = true;
3
+ const hookId = Astro.params.hookId || '';
4
+ ---
5
+
6
+ <div class="my-4 w-full bg-gray-50 p-6">
7
+ <div
8
+ class="mb-4 rounded-lg border-2 border-dashed border-gray-300 bg-slate-50 p-6"
9
+ >
10
+ <h3 class="text-lg text-gray-700">
11
+ Code Hook:{' '}
12
+ <span class="font-action font-bold">{hookId || 'unknown'}</span>
13
+ </h3>
14
+ <p class="mt-2 text-sm text-gray-500">
15
+ CodeHook not activated for this install.
16
+ </p>
17
+ </div>
18
+ </div>
@@ -0,0 +1,9 @@
1
+ ---
2
+ export const partial = true;
3
+ import BunnyVideoWrapper from '@/components/codehooks/BunnyVideoWrapper.astro';
4
+ import { parseCodeHookBladeContext } from '@/lib/codeHookHelper';
5
+
6
+ const { options } = parseCodeHookBladeContext(Astro.url.searchParams);
7
+ ---
8
+
9
+ <BunnyVideoWrapper options={options} />
@@ -0,0 +1,6 @@
1
+ ---
2
+ export const partial = true;
3
+ import CustomHero from '@/custom/CustomHero.astro';
4
+ ---
5
+
6
+ <CustomHero />
@@ -0,0 +1,15 @@
1
+ ---
2
+ export const partial = true;
3
+ import EpinetWrapper from '@/components/codehooks/EpinetWrapper';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <div style="min-height: 400px;">
14
+ <EpinetWrapper fullContentMap={fullContentMap} client:only="react" />
15
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ export const partial = true;
3
+ import FeaturedArticle from '@/components/codehooks/FeaturedArticle.astro';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, options } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <FeaturedArticle options={options} fullContentMap={fullContentMap} />
@@ -0,0 +1,8 @@
1
+ ---
2
+ export const partial = true;
3
+ import SandboxLauncher from '@/custom/SandboxLauncher';
4
+ ---
5
+
6
+ <div style="min-height: 400px;">
7
+ <SandboxLauncher client:only="react" />
8
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ export const partial = true;
3
+ import ListContent from '@/components/codehooks/ListContent.astro';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, options } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <ListContent options={options} fullContentMap={fullContentMap} />
@@ -0,0 +1,13 @@
1
+ ---
2
+ export const partial = true;
3
+ import SearchWidget from '@/components/codehooks/SearchWidget.tsx';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <SearchWidget fullContentMap={fullContentMap} client:load />
@@ -0,0 +1,25 @@
1
+ ---
2
+ export const partial = true;
3
+ import ShopifyProductGrid from '@/custom/shopify/ShopifyProductGrid';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookResources,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, optionsStr, options } = parseCodeHookBladeContext(
10
+ Astro.url.searchParams
11
+ );
12
+ const resources = await resolveCodeHookResources(
13
+ tenantId,
14
+ optionsStr,
15
+ 'shopify-product-grid'
16
+ );
17
+ ---
18
+
19
+ <div style="min-height: 400px;">
20
+ <ShopifyProductGrid
21
+ options={options}
22
+ resources={resources}
23
+ client:only="react"
24
+ />
25
+ </div>
@@ -0,0 +1,25 @@
1
+ ---
2
+ export const partial = true;
3
+ import ShopifyServiceList from '@/custom/shopify/ShopifyServiceList';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookResources,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, optionsStr, options } = parseCodeHookBladeContext(
10
+ Astro.url.searchParams
11
+ );
12
+ const resources = await resolveCodeHookResources(
13
+ tenantId,
14
+ optionsStr,
15
+ 'shopify-service-list'
16
+ );
17
+ ---
18
+
19
+ <div style="min-height: 300px;">
20
+ <ShopifyServiceList
21
+ options={options}
22
+ resources={resources}
23
+ client:only="react"
24
+ />
25
+ </div>
@@ -6,7 +6,7 @@ import { getFullContentMap } from '@/stores/analytics';
6
6
  import { getBrandConfig } from '@/utils/api/brandConfig';
7
7
  import { joinUrlPaths } from '@/utils/helpers';
8
8
  import { handleFailedResponse } from '@/utils/backend';
9
- import { components as codeHookComponents } from '@/custom/CodeHook.astro';
9
+ import { availableCodeHookIds } from '@/custom/codehooks';
10
10
  import StoryKeepHeader from '@/components/edit/Header';
11
11
  import StoryKeepToolBar from '@/components/edit/ToolBar';
12
12
  import StoryKeepToolMode from '@/components/edit/ToolMode';
@@ -175,7 +175,7 @@ for (const [key, value] of Astro.url.searchParams) {
175
175
  fullContentMap={fullContentMap}
176
176
  fullCanonicalURL={canonicalURL}
177
177
  urlParams={urlParams}
178
- availableCodeHooks={Object.keys(codeHookComponents)}
178
+ availableCodeHooks={availableCodeHookIds}
179
179
  client:only="react"
180
180
  />
181
181
  </div>
@@ -201,7 +201,7 @@ for (const [key, value] of Astro.url.searchParams) {
201
201
  <div class="pointer-events-auto max-h-full">
202
202
  <SettingsPanel
203
203
  config={brandConfig}
204
- availableCodeHooks={Object.keys(codeHookComponents)}
204
+ availableCodeHooks={availableCodeHookIds}
205
205
  client:only="react"
206
206
  />
207
207
  </div>