astro-tractstack 2.3.5 → 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 (49) hide show
  1. package/bin/create-tractstack.js +38 -59
  2. package/dist/index.js +60 -36
  3. package/package.json +46 -9
  4. package/templates/custom/minimal/codehooks.ts +13 -0
  5. package/templates/custom/shopify/ShopifyProductGrid.tsx +4 -4
  6. package/templates/custom/shopify/ShopifyServiceList.tsx +4 -4
  7. package/templates/custom/with-examples/codehooks.ts +15 -0
  8. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +38 -23
  9. package/templates/src/components/codehooks/EpinetTableView.tsx +5 -2
  10. package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -5
  11. package/templates/src/components/codehooks/FeaturedArticle.astro +3 -3
  12. package/templates/src/components/codehooks/ListContent.astro +3 -3
  13. package/templates/src/components/compositor/Node.tsx +13 -2
  14. package/templates/src/components/compositor/nodes/Pane.tsx +2 -14
  15. package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -2
  16. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +35 -14
  17. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  18. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +2 -2
  19. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +8 -4
  20. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +5 -2
  21. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +8 -4
  22. package/templates/src/components/storykeep/widgets/Wizard.tsx +4 -2
  23. package/templates/src/lib/codeHookHelper.ts +156 -0
  24. package/templates/src/lib/resources.ts +41 -0
  25. package/templates/src/lib/storyData.ts +1 -2
  26. package/templates/src/pages/[...slug]/edit.astro +3 -3
  27. package/templates/src/pages/[...slug].astro +76 -70
  28. package/templates/src/pages/codehooks/[...hookId].astro +18 -0
  29. package/templates/src/pages/codehooks/bunny-video.astro +9 -0
  30. package/templates/src/pages/codehooks/custom-hero.astro +6 -0
  31. package/templates/src/pages/codehooks/epinet.astro +15 -0
  32. package/templates/src/pages/codehooks/featured-article.astro +13 -0
  33. package/templates/src/pages/codehooks/get-crafting.astro +8 -0
  34. package/templates/src/pages/codehooks/list-content.astro +13 -0
  35. package/templates/src/pages/codehooks/search-widget.astro +13 -0
  36. package/templates/src/pages/codehooks/shopify-product-grid.astro +25 -0
  37. package/templates/src/pages/codehooks/shopify-service-list.astro +25 -0
  38. package/templates/src/pages/context/[...contextSlug]/edit.astro +3 -3
  39. package/templates/src/pages/context/[...contextSlug].astro +47 -10
  40. package/templates/src/pages/sandbox.astro +3 -14
  41. package/templates/src/stores/analytics.ts +77 -107
  42. package/utils/inject-files.ts +62 -37
  43. package/templates/custom/minimal/CodeHook.astro +0 -72
  44. package/templates/custom/with-examples/CodeHook.astro +0 -81
  45. package/templates/custom/with-examples/ProductCard.astro +0 -29
  46. package/templates/custom/with-examples/ProductCardWrapper.astro +0 -43
  47. package/templates/custom/with-examples/ProductGrid.astro +0 -64
  48. package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -157
  49. package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -279
@@ -1,4 +1,4 @@
1
- import { atom } from 'nanostores';
1
+ import { atom, computed } from 'nanostores';
2
2
  import { TractStackAPI } from '@/utils/api';
3
3
 
4
4
  interface AvailableFilter {
@@ -11,43 +11,38 @@ export interface AppliedFilter {
11
11
  value: string;
12
12
  }
13
13
 
14
- const tenantEpinetCustomFilters = atom<
15
- Record<
14
+ export interface EpinetFiltersState {
15
+ enabled: boolean;
16
+ visitorType: 'all' | 'anonymous' | 'known';
17
+ selectedUserId: string | null;
18
+ startTimeUTC: string | null;
19
+ endTimeUTC: string | null;
20
+ userCounts: Array<{ id: string; count: number; isKnown: boolean }>;
21
+ hourlyNodeActivity: Record<
16
22
  string,
17
- {
18
- enabled: boolean;
19
- visitorType: 'all' | 'anonymous' | 'known';
20
- selectedUserId: string | null;
21
- startTimeUTC: string | null;
22
- endTimeUTC: string | null;
23
- userCounts: Array<{ id: string; count: number; isKnown: boolean }>;
24
- hourlyNodeActivity: Record<
25
- string,
26
- Record<
27
- string,
28
- {
29
- events: Record<string, number>;
30
- visitorIds: string[];
31
- }
32
- >
33
- >;
34
- availableFilters: AvailableFilter[];
35
- appliedFilters: AppliedFilter[];
36
- }
37
- >
38
- >({});
23
+ Record<
24
+ string,
25
+ {
26
+ events: Record<string, number>;
27
+ visitorIds: string[];
28
+ }
29
+ >
30
+ >;
31
+ availableFilters: AvailableFilter[];
32
+ appliedFilters: AppliedFilter[];
33
+ }
39
34
 
40
- const tenantFullContentMaps = atom<
41
- Record<
42
- string,
43
- {
44
- data: any[];
45
- lastUpdated: number;
46
- }
47
- >
48
- >({});
35
+ export interface TenantFullContentMapState {
36
+ data: any[];
37
+ lastUpdated: number;
38
+ }
39
+
40
+ const tenantEpinetCustomFilters = atom<Record<string, EpinetFiltersState>>({});
41
+
42
+ const tenantFullContentMaps = atom<Record<string, TenantFullContentMapState>>(
43
+ {}
44
+ );
49
45
 
50
- // Helper to get current tenant ID
51
46
  function getCurrentTenantId(): string {
52
47
  const resolvedTenantId =
53
48
  (typeof window !== 'undefined' && window.TRACTSTACK_CONFIG?.tenantId) ||
@@ -56,9 +51,9 @@ function getCurrentTenantId(): string {
56
51
  return resolvedTenantId;
57
52
  }
58
53
 
59
- const defaultEpinetFilters = {
54
+ const defaultEpinetFilters: EpinetFiltersState = {
60
55
  enabled: false,
61
- visitorType: 'all' as 'all' | 'anonymous' | 'known',
56
+ visitorType: 'all',
62
57
  selectedUserId: null,
63
58
  startTimeUTC: null,
64
59
  endTimeUTC: null,
@@ -68,81 +63,56 @@ const defaultEpinetFilters = {
68
63
  appliedFilters: [],
69
64
  };
70
65
 
71
- const createEpinetFiltersStore = () => {
72
- const store = {
73
- get: () => {
74
- const tenantId = getCurrentTenantId();
75
- return tenantEpinetCustomFilters.get()[tenantId] || defaultEpinetFilters;
76
- },
77
-
78
- set: (tenantId: string, updates: any) => {
79
- const currentFilters =
80
- tenantEpinetCustomFilters.get()[tenantId] || defaultEpinetFilters;
81
- tenantEpinetCustomFilters.set({
82
- ...tenantEpinetCustomFilters.get(),
83
- [tenantId]: {
84
- ...currentFilters,
85
- ...updates,
86
- },
87
- });
88
- },
89
-
90
- subscribe: (callback: (value: any) => void) => {
91
- const tenantId = getCurrentTenantId();
92
- return tenantEpinetCustomFilters.subscribe((filters) => {
93
- callback(filters[tenantId] || defaultEpinetFilters);
94
- });
95
- },
96
- lc: 0,
97
- listen: function (callback: any) {
98
- return this.subscribe(callback);
99
- },
100
- notify: function () {},
101
- off: function () {},
102
- get value() {
103
- return this.get();
104
- },
105
- };
106
-
107
- return store;
108
- };
109
-
110
- const createFullContentMapStore = () => {
111
- const store = {
112
- get: () => {
113
- const tenantId = getCurrentTenantId();
114
- return tenantFullContentMaps.get()[tenantId] || null;
115
- },
66
+ export const epinetCustomFilters = computed(
67
+ tenantEpinetCustomFilters,
68
+ (filters) => {
69
+ const tenantId = getCurrentTenantId();
70
+ return filters[tenantId] || defaultEpinetFilters;
71
+ }
72
+ );
116
73
 
117
- set: (tenantId: string, data: { data: any[]; lastUpdated: number }) => {
118
- tenantFullContentMaps.set({
119
- ...tenantFullContentMaps.get(),
120
- [tenantId]: data,
121
- });
122
- },
74
+ export function getEpinetCustomFilters(): EpinetFiltersState {
75
+ return epinetCustomFilters.get();
76
+ }
123
77
 
124
- subscribe: (callback: (value: any) => void) => {
125
- const tenantId = getCurrentTenantId();
126
- return tenantFullContentMaps.subscribe((maps) => {
127
- callback(maps[tenantId] || null);
128
- });
129
- },
130
- lc: 0,
131
- listen: function (callback: any) {
132
- return this.subscribe(callback);
133
- },
134
- notify: function () {},
135
- off: function () {},
136
- get value() {
137
- return this.get();
78
+ export function setEpinetCustomFilters(
79
+ tenantId: string,
80
+ updates: Partial<EpinetFiltersState>
81
+ ): void {
82
+ const currentFilters =
83
+ tenantEpinetCustomFilters.get()[tenantId] || defaultEpinetFilters;
84
+ tenantEpinetCustomFilters.set({
85
+ ...tenantEpinetCustomFilters.get(),
86
+ [tenantId]: {
87
+ ...currentFilters,
88
+ ...updates,
138
89
  },
139
- };
90
+ });
91
+ }
140
92
 
141
- return store;
142
- };
93
+ export const fullContentMapStore = computed(tenantFullContentMaps, (maps) => {
94
+ const tenantId = getCurrentTenantId();
95
+ return maps[tenantId] || null;
96
+ });
97
+
98
+ export function setTenantFullContentMap(
99
+ tenantId: string,
100
+ data: TenantFullContentMapState
101
+ ): void {
102
+ tenantFullContentMaps.set({
103
+ ...tenantFullContentMaps.get(),
104
+ [tenantId]: data,
105
+ });
106
+ }
143
107
 
144
- export const epinetCustomFilters = createEpinetFiltersStore();
145
- export const fullContentMapStore = createFullContentMapStore();
108
+ // Synchronous in-process accessor for the warm content-map atom, keyed on an
109
+ // explicit tenantId (unlike fullContentMapStore.get(), which keys on
110
+ // getCurrentTenantId() and resolves the wrong tenant during SSR sub-requests).
111
+ // Returns [] on a cold miss; codehook blades fall back to the async
112
+ // getFullContentMap(tenantId) when this is empty.
113
+ export function getCachedFullContentMap(tenantId: string): any[] {
114
+ return tenantFullContentMaps.get()[tenantId]?.data ?? [];
115
+ }
146
116
 
147
117
  export async function getFullContentMap(tenantId: string): Promise<any[]> {
148
118
  const api = new TractStackAPI(tenantId);
@@ -1518,18 +1518,6 @@ export async function injectTemplateFiles(
1518
1518
  ),
1519
1519
  dest: 'src/components/codehooks/ListContentSetup.tsx',
1520
1520
  },
1521
- {
1522
- src: resolve(
1523
- '../templates/src/components/codehooks/ProductCardSetup.tsx'
1524
- ),
1525
- dest: 'src/components/codehooks/ProductCardSetup.tsx',
1526
- },
1527
- {
1528
- src: resolve(
1529
- '../templates/src/components/codehooks/ProductGridSetup.tsx'
1530
- ),
1531
- dest: 'src/components/codehooks/ProductGridSetup.tsx',
1532
- },
1533
1521
  {
1534
1522
  src: resolve(
1535
1523
  '../templates/src/components/codehooks/BunnyVideoWrapper.astro'
@@ -1541,6 +1529,32 @@ export async function injectTemplateFiles(
1541
1529
  dest: 'src/components/codehooks/BunnyVideoSetup.tsx',
1542
1530
  },
1543
1531
 
1532
+ // CodeHook Blades (partial routes; core hooks overwritable)
1533
+ {
1534
+ src: resolve('../templates/src/pages/codehooks/featured-article.astro'),
1535
+ dest: 'src/pages/codehooks/featured-article.astro',
1536
+ },
1537
+ {
1538
+ src: resolve('../templates/src/pages/codehooks/list-content.astro'),
1539
+ dest: 'src/pages/codehooks/list-content.astro',
1540
+ },
1541
+ {
1542
+ src: resolve('../templates/src/pages/codehooks/search-widget.astro'),
1543
+ dest: 'src/pages/codehooks/search-widget.astro',
1544
+ },
1545
+ {
1546
+ src: resolve('../templates/src/pages/codehooks/bunny-video.astro'),
1547
+ dest: 'src/pages/codehooks/bunny-video.astro',
1548
+ },
1549
+ {
1550
+ src: resolve('../templates/src/pages/codehooks/epinet.astro'),
1551
+ dest: 'src/pages/codehooks/epinet.astro',
1552
+ },
1553
+ {
1554
+ src: resolve('../templates/src/pages/codehooks/[...hookId].astro'),
1555
+ dest: 'src/pages/codehooks/[...hookId].astro',
1556
+ },
1557
+
1544
1558
  // Widget Components
1545
1559
  {
1546
1560
  src: resolve('../templates/src/components/widgets/Impression.tsx'),
@@ -1568,6 +1582,10 @@ export async function injectTemplateFiles(
1568
1582
  src: resolve('../templates/src/lib/resources.ts'),
1569
1583
  dest: 'src/lib/resources.ts',
1570
1584
  },
1585
+ {
1586
+ src: resolve('../templates/src/lib/codeHookHelper.ts'),
1587
+ dest: 'src/lib/codeHookHelper.ts',
1588
+ },
1571
1589
 
1572
1590
  // Client Scripts
1573
1591
  {
@@ -2357,15 +2375,6 @@ export async function injectTemplateFiles(
2357
2375
  },
2358
2376
 
2359
2377
  // Custom Components (Conditional)
2360
- {
2361
- src: resolve(
2362
- config?.includeExamples
2363
- ? '../templates/custom/with-examples/CodeHook.astro'
2364
- : '../templates/custom/minimal/CodeHook.astro'
2365
- ),
2366
- dest: 'src/custom/CodeHook.astro',
2367
- protected: true,
2368
- },
2369
2378
  {
2370
2379
  src: resolve(
2371
2380
  config?.includeExamples
@@ -2376,11 +2385,7 @@ export async function injectTemplateFiles(
2376
2385
  protected: true,
2377
2386
  },
2378
2387
  {
2379
- src: resolve(
2380
- config?.includeExamples
2381
- ? '../templates/custom/with-examples/HeaderWidget.astro'
2382
- : '../templates/custom/minimal/HeaderWidget.astro'
2383
- ),
2388
+ src: resolve('../templates/custom/minimal/HeaderWidget.astro'),
2384
2389
  dest: 'src/custom/HeaderWidget.astro',
2385
2390
  protected: true,
2386
2391
  },
@@ -2450,6 +2455,33 @@ export async function injectTemplateFiles(
2450
2455
  protected: true,
2451
2456
  },
2452
2457
 
2458
+ // CodeHook manifest (frontend capability list; hand-maintained, protected)
2459
+ {
2460
+ src: resolve(
2461
+ config?.includeExamples
2462
+ ? '../templates/custom/with-examples/codehooks.ts'
2463
+ : '../templates/custom/minimal/codehooks.ts'
2464
+ ),
2465
+ dest: 'src/custom/codehooks.ts',
2466
+ protected: true,
2467
+ },
2468
+
2469
+ // CodeHook Blades (userland hooks; protected once installed)
2470
+ {
2471
+ src: resolve(
2472
+ '../templates/src/pages/codehooks/shopify-product-grid.astro'
2473
+ ),
2474
+ dest: 'src/pages/codehooks/shopify-product-grid.astro',
2475
+ protected: true,
2476
+ },
2477
+ {
2478
+ src: resolve(
2479
+ '../templates/src/pages/codehooks/shopify-service-list.astro'
2480
+ ),
2481
+ dest: 'src/pages/codehooks/shopify-service-list.astro',
2482
+ protected: true,
2483
+ },
2484
+
2453
2485
  // Example Components (Conditional)
2454
2486
  ...(config?.includeExamples
2455
2487
  ? [
@@ -2466,20 +2498,13 @@ export async function injectTemplateFiles(
2466
2498
  protected: true,
2467
2499
  },
2468
2500
  {
2469
- src: resolve('../templates/custom/with-examples/ProductGrid.astro'),
2470
- dest: 'src/custom/ProductGrid.astro',
2471
- protected: true,
2472
- },
2473
- {
2474
- src: resolve(
2475
- '../templates/custom/with-examples/ProductCardWrapper.astro'
2476
- ),
2477
- dest: 'src/custom/ProductCardWrapper.astro',
2501
+ src: resolve('../templates/src/pages/codehooks/custom-hero.astro'),
2502
+ dest: 'src/pages/codehooks/custom-hero.astro',
2478
2503
  protected: true,
2479
2504
  },
2480
2505
  {
2481
- src: resolve('../templates/custom/with-examples/ProductCard.astro'),
2482
- dest: 'src/custom/ProductCard.astro',
2506
+ src: resolve('../templates/src/pages/codehooks/get-crafting.astro'),
2507
+ dest: 'src/pages/codehooks/get-crafting.astro',
2483
2508
  protected: true,
2484
2509
  },
2485
2510
  {
@@ -1,72 +0,0 @@
1
- ---
2
- import FeaturedArticle from '@/components/codehooks/FeaturedArticle.astro';
3
- import ListContent from '@/components/codehooks/ListContent.astro';
4
- import SearchWidget from '@/components/codehooks/SearchWidget.tsx';
5
- import BunnyVideoWrapper from '@/components/codehooks/BunnyVideoWrapper.astro';
6
- import EpinetWrapper from '@/components/codehooks/EpinetWrapper';
7
- import ShopifyProductGrid from '@/custom/shopify/ShopifyProductGrid';
8
- import ShopifyServiceList from '@/custom/shopify/ShopifyServiceList';
9
- import type { FullContentMapItem } from '@/types/tractstack';
10
- import type { ResourceNode } from '@/types/compositorTypes';
11
- // import CustomHero from './CustomHero.astro';
12
- // Uncomment to add custom components
13
-
14
- export interface Props {
15
- target: string;
16
- paneId: string;
17
- fullContentMap: FullContentMapItem[];
18
- resourcesPayload?: Record<string, ResourceNode[]>;
19
- options?: {
20
- params?: {
21
- options?: string;
22
- };
23
- };
24
- }
25
-
26
- const { target, options, fullContentMap, resourcesPayload = {} } = Astro.props;
27
-
28
- export const components = {
29
- 'featured-article': true,
30
- 'featured-content': true,
31
- 'list-content': true,
32
- 'search-widget': true,
33
- 'bunny-video': import.meta.env.PUBLIC_ENABLE_BUNNY === 'true',
34
- epinet: true,
35
- 'shopify-product-grid': true,
36
- 'shopify-service-list': true,
37
- // "custom-hero": true, // Uncomment when you create CustomHero.astro
38
- };
39
- ---
40
-
41
- {
42
- target === 'list-content' ? (
43
- <ListContent options={options} contentMap={fullContentMap} />
44
- ) : target === 'featured-article' ? (
45
- <FeaturedArticle options={options} contentMap={fullContentMap} />
46
- ) : target === 'search-widget' ? (
47
- <SearchWidget fullContentMap={fullContentMap} client:load />
48
- ) : target === 'bunny-video' && import.meta.env.PUBLIC_ENABLE_BUNNY ? (
49
- <BunnyVideoWrapper options={options} />
50
- ) : target === 'epinet' ? (
51
- <EpinetWrapper fullContentMap={fullContentMap} client:only="react" />
52
- ) : target === 'shopify-product-grid' ? (
53
- <ShopifyProductGrid
54
- options={options}
55
- resources={resourcesPayload}
56
- client:only="react"
57
- />
58
- ) : target === 'shopify-service-list' ? (
59
- <ShopifyServiceList
60
- options={options}
61
- resources={resourcesPayload}
62
- client:only="react"
63
- />
64
- ) : (
65
- /* : target === "custom-hero" ? (
66
- <CustomHero />
67
- ) */
68
- <div class="rounded-lg bg-gray-50 p-8 text-center">
69
- <p class="text-gray-600">CodeHook target "{target}" not found</p>
70
- </div>
71
- )
72
- }
@@ -1,81 +0,0 @@
1
- ---
2
- import CustomHero from './CustomHero.astro';
3
- import SearchWidget from '@/components/codehooks/SearchWidget.tsx';
4
- import FeaturedArticle from '@/components/codehooks/FeaturedArticle.astro';
5
- import ListContent from '@/components/codehooks/ListContent.astro';
6
- import BunnyVideoWrapper from '@/components/codehooks/BunnyVideoWrapper.astro';
7
- import EpinetWrapper from '@/components/codehooks/EpinetWrapper';
8
- import ProductCardWrapper from './ProductCardWrapper.astro';
9
- import ProductGrid from './ProductGrid.astro';
10
- import SandboxLauncher from './SandboxLauncher';
11
- import ShopifyProductGrid from '@/custom/shopify/ShopifyProductGrid';
12
- import ShopifyServiceList from '@/custom/shopify/ShopifyServiceList';
13
- import type { FullContentMapItem } from '@/types/tractstack';
14
- import type { ResourceNode } from '@/types/compositorTypes';
15
-
16
- export interface Props {
17
- target: string;
18
- paneId: string;
19
- fullContentMap: FullContentMapItem[];
20
- resourcesPayload?: Record<string, ResourceNode[]>;
21
- options?: {
22
- params?: {
23
- options?: string;
24
- };
25
- };
26
- }
27
-
28
- const { target, options, fullContentMap, resourcesPayload = {} } = Astro.props;
29
-
30
- export const components = {
31
- 'custom-hero': true,
32
- 'featured-article': true,
33
- 'list-content': true,
34
- 'search-widget': true,
35
- 'product-card': true,
36
- 'product-grid': true,
37
- 'get-crafting': true,
38
- 'bunny-video': import.meta.env.PUBLIC_ENABLE_BUNNY === 'true',
39
- epinet: true,
40
- 'shopify-product-grid': true,
41
- 'shopify-service-list': true,
42
- };
43
- ---
44
-
45
- {
46
- target === 'product-card' ? (
47
- <ProductCardWrapper options={options} resourcesPayload={resourcesPayload} />
48
- ) : target === 'product-grid' ? (
49
- <ProductGrid options={options} resourcesPayload={resourcesPayload} />
50
- ) : target === 'list-content' ? (
51
- <ListContent options={options} contentMap={fullContentMap} />
52
- ) : target === 'featured-article' ? (
53
- <FeaturedArticle options={options} contentMap={fullContentMap} />
54
- ) : target === 'search-widget' ? (
55
- <SearchWidget fullContentMap={fullContentMap} client:load />
56
- ) : target === 'bunny-video' && import.meta.env.PUBLIC_ENABLE_BUNNY ? (
57
- <BunnyVideoWrapper options={options} />
58
- ) : target === 'get-crafting' ? (
59
- <SandboxLauncher client:only="react" />
60
- ) : target === 'custom-hero' ? (
61
- <CustomHero />
62
- ) : target === 'epinet' ? (
63
- <EpinetWrapper fullContentMap={fullContentMap} client:only="react" />
64
- ) : target === 'shopify-product-grid' ? (
65
- <ShopifyProductGrid
66
- options={options}
67
- resources={resourcesPayload}
68
- client:only="react"
69
- />
70
- ) : target === 'shopify-service-list' ? (
71
- <ShopifyServiceList
72
- options={options}
73
- resources={resourcesPayload}
74
- client:only="react"
75
- />
76
- ) : (
77
- <div class="rounded-lg bg-gray-50 p-8 text-center">
78
- <p class="text-gray-600">CodeHook target "{target}" not found</p>
79
- </div>
80
- )
81
- }
@@ -1,29 +0,0 @@
1
- ---
2
- import type { ResourceNode } from '@/types/compositorTypes';
3
-
4
- export interface Props {
5
- product?: ResourceNode;
6
- }
7
-
8
- const { product } = Astro.props;
9
- ---
10
-
11
- <div class="rounded-lg border bg-white p-6 shadow-sm">
12
- <h2 class="mb-4 text-xl font-bold text-gray-900">
13
- Product Card (Parameter Dump)
14
- </h2>
15
-
16
- {
17
- product ? (
18
- <pre class="whitespace-pre-wrap rounded-md bg-gray-50 p-4 text-xs text-gray-700">
19
- {JSON.stringify(product, null, 2)}
20
- </pre>
21
- ) : (
22
- <div class="rounded-md bg-red-50 p-4 text-center">
23
- <p class="font-bold text-red-800">
24
- Error: ProductCard component received no product data.
25
- </p>
26
- </div>
27
- )
28
- }
29
- </div>
@@ -1,43 +0,0 @@
1
- ---
2
- import type { ResourceNode } from '@/types/compositorTypes';
3
- import ProductCard from './ProductCard.astro';
4
-
5
- export interface Props {
6
- options?: {
7
- params?: {
8
- options?: string;
9
- };
10
- };
11
- resourcesPayload?: Record<string, ResourceNode[]>;
12
- }
13
-
14
- const { options, resourcesPayload } = Astro.props;
15
-
16
- let product: ResourceNode | undefined = undefined;
17
- let parsedOptions: { slug?: string } = {};
18
-
19
- if (options?.params?.options && typeof options.params.options === 'string') {
20
- try {
21
- parsedOptions = JSON.parse(options.params.options);
22
- } catch (e) {
23
- console.error('Failed to parse ProductCard options JSON:', e);
24
- }
25
- }
26
-
27
- const targetSlug = parsedOptions?.slug;
28
-
29
- if (targetSlug && resourcesPayload) {
30
- for (const key in resourcesPayload) {
31
- const resourceArray = resourcesPayload[key] as ResourceNode[];
32
- const found = resourceArray.find(
33
- (resource) => resource.slug === targetSlug
34
- );
35
- if (found) {
36
- product = found;
37
- break;
38
- }
39
- }
40
- }
41
- ---
42
-
43
- <ProductCard product={product} />
@@ -1,64 +0,0 @@
1
- ---
2
- import type { ResourceNode } from '@/types/compositorTypes';
3
- import ProductCard from './ProductCard.astro';
4
-
5
- export interface Props {
6
- options?: {
7
- params?: {
8
- options?: string;
9
- };
10
- };
11
- resourcesPayload?: Record<string, ResourceNode[]>;
12
- }
13
-
14
- const { options, resourcesPayload } = Astro.props;
15
-
16
- let productsToDisplay: ResourceNode[] = [];
17
- let allFetchedProducts: ResourceNode[] = [];
18
- let parsedOptions: { productType?: string; category?: string; slugs?: string } =
19
- {};
20
-
21
- if (options?.params?.options && typeof options.params.options === 'string') {
22
- try {
23
- parsedOptions = JSON.parse(options.params.options);
24
- } catch (e) {
25
- console.error('Failed to parse ProductGrid options JSON:', e);
26
- }
27
- }
28
-
29
- if (resourcesPayload) {
30
- for (const key in resourcesPayload) {
31
- allFetchedProducts = resourcesPayload[key] as ResourceNode[];
32
- break;
33
- }
34
- }
35
-
36
- if (parsedOptions?.productType) {
37
- productsToDisplay = allFetchedProducts.filter(
38
- (product) =>
39
- product.optionsPayload?.productType === parsedOptions.productType
40
- );
41
- } else {
42
- productsToDisplay = allFetchedProducts;
43
- }
44
- ---
45
-
46
- <div class="space-y-6">
47
- {
48
- productsToDisplay.length > 0 ? (
49
- <div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
50
- {productsToDisplay.map((product) => (
51
- <ProductCard product={product} />
52
- ))}
53
- </div>
54
- ) : (
55
- <div class="rounded-lg border bg-yellow-50 p-6 text-center shadow-sm">
56
- <p class="font-bold text-yellow-800">No products to display.</p>
57
- <p class="mt-1 text-sm text-yellow-700">
58
- Check the grid configuration or ensure products match the specified
59
- filters.
60
- </p>
61
- </div>
62
- )
63
- }
64
- </div>