astro-tractstack 2.0.0-rc.9 → 2.0.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.
- package/LICENSE +8 -97
- package/README.md +7 -5
- package/bin/create-tractstack.js +31 -8
- package/dist/index.js +106 -29
- package/package.json +10 -5
- package/templates/css/frontend.css +1 -1
- package/templates/custom/minimal/CodeHook.astro +13 -12
- package/templates/custom/minimal/CustomRoutes.astro +25 -31
- package/templates/custom/with-examples/CodeHook.astro +22 -11
- package/templates/custom/with-examples/CustomRoutes.astro +4 -8
- package/templates/custom/with-examples/ProductCard.astro +29 -0
- package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
- package/templates/custom/with-examples/ProductGrid.astro +64 -0
- package/templates/custom/with-examples/pages/Collections.astro +58 -98
- package/templates/gitignore +42 -0
- package/templates/prettierignore +5 -0
- package/templates/prettierrc +19 -0
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/htmx.min.js +3519 -0
- package/templates/src/client/view.js +429 -0
- package/templates/src/components/Footer.astro +4 -9
- package/templates/src/components/Header.astro +67 -60
- package/templates/src/components/Menu.tsx +188 -52
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
- package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
- package/templates/src/components/codehooks/EpinetWrapper.tsx +1 -0
- package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
- package/templates/src/components/codehooks/ListContent.astro +32 -162
- package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
- package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
- package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
- package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
- package/templates/src/components/compositor/Node.tsx +3 -6
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
- package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
- package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
- package/templates/src/components/edit/Header.tsx +10 -4
- package/templates/src/components/edit/PanelSwitch.tsx +11 -7
- package/templates/src/components/edit/SettingsPanel.tsx +29 -18
- package/templates/src/components/edit/ToolBar.tsx +1 -28
- package/templates/src/components/edit/ToolMode.tsx +45 -32
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
- package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
- package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
- package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
- package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
- package/templates/src/components/edit/state/SaveModal.tsx +316 -169
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
- package/templates/src/components/fields/ArtpackImage.tsx +4 -1
- package/templates/src/components/fields/BackgroundImage.tsx +1 -1
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
- package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
- package/templates/src/components/fields/ImageUpload.tsx +1 -1
- package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
- package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
- package/templates/src/components/form/ActionBuilderField.tsx +306 -87
- package/templates/src/components/search/SearchModal.tsx +420 -0
- package/templates/src/components/search/SearchResults.tsx +367 -0
- package/templates/src/components/search/SearchWrapper.tsx +46 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
- package/templates/src/components/storykeep/Dashboard_Branding.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
- package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +38 -34
- package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +56 -8
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +18 -3
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
- package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
- package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
- package/templates/src/constants/shapes.ts +9 -0
- package/templates/src/constants.ts +2121 -16
- package/templates/src/hooks/useSearch.ts +228 -0
- package/templates/src/layouts/Layout.astro +213 -104
- package/templates/src/lib/storyData.ts +4 -1
- package/templates/src/pages/[...slug]/edit.astro +14 -14
- package/templates/src/pages/[...slug].astro +82 -21
- package/templates/src/pages/api/orphan-analysis.ts +0 -1
- package/templates/src/pages/api/tailwind.ts +23 -21
- package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
- package/templates/src/pages/context/[...contextSlug].astro +7 -2
- package/templates/src/pages/storykeep/advanced.astro +5 -4
- package/templates/src/pages/storykeep/branding.astro +5 -4
- package/templates/src/pages/storykeep/content.astro +5 -4
- package/templates/src/pages/storykeep/init.astro +40 -1
- package/templates/src/pages/storykeep/login.astro +1 -1
- package/templates/src/pages/storykeep.astro +5 -4
- package/templates/src/stores/nodes.ts +59 -88
- package/templates/src/stores/orphanAnalysis.ts +19 -21
- package/templates/src/stores/storykeep.ts +7 -0
- package/templates/src/types/compositorTypes.ts +6 -0
- package/templates/src/types/tractstack.ts +17 -0
- package/templates/src/utils/actions/lispLexer.ts +2 -2
- package/templates/src/utils/actions/preParse_Action.ts +3 -0
- package/templates/src/utils/api/beliefHelpers.ts +12 -36
- package/templates/src/utils/api/menuHelpers.ts +2 -2
- package/templates/src/utils/api.ts +26 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
- package/templates/src/utils/compositor/allowInsert.ts +5 -3
- package/templates/src/utils/compositor/nodesHelper.ts +4 -0
- package/templates/src/utils/compositor/processMarkdown.ts +16 -2
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
- package/templates/src/utils/compositor/typeGuards.ts +1 -0
- package/templates/src/utils/customHelpers.ts +38 -0
- package/templates/src/utils/helpers.ts +2 -2
- package/templates/src/utils/layout.ts +65 -144
- package/utils/inject-files.ts +95 -18
- package/templates/src/client/analytics-events.js +0 -207
- package/templates/src/client/belief-events.js +0 -191
- package/templates/src/client/sse.js +0 -613
- package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
|
@@ -9,24 +9,10 @@ import type { PaneNode } from '@/types/compositorTypes';
|
|
|
9
9
|
|
|
10
10
|
const PER_PAGE = 20;
|
|
11
11
|
|
|
12
|
-
// V2 Analytics Data Structure
|
|
13
|
-
interface StoryfragmentAnalytics {
|
|
14
|
-
id: string;
|
|
15
|
-
total_actions: number;
|
|
16
|
-
unique_visitors: number;
|
|
17
|
-
last_24h_actions: number;
|
|
18
|
-
last_7d_actions: number;
|
|
19
|
-
last_28d_actions: number;
|
|
20
|
-
last_24h_unique_visitors: number;
|
|
21
|
-
last_7d_unique_visitors: number;
|
|
22
|
-
last_28d_unique_visitors: number;
|
|
23
|
-
total_leads: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
12
|
interface ListContentSetupProps {
|
|
27
13
|
params?: Record<string, string>;
|
|
28
14
|
nodeId: string;
|
|
29
|
-
config
|
|
15
|
+
config: BrandConfig;
|
|
30
16
|
}
|
|
31
17
|
|
|
32
18
|
const ListContentSetup = ({
|
|
@@ -34,17 +20,8 @@ const ListContentSetup = ({
|
|
|
34
20
|
nodeId,
|
|
35
21
|
config,
|
|
36
22
|
}: ListContentSetupProps) => {
|
|
37
|
-
const [analyticsData, setAnalyticsData] = useState<
|
|
38
|
-
Record<string, StoryfragmentAnalytics>
|
|
39
|
-
>({});
|
|
40
|
-
const [isAnalyticsLoading, setIsAnalyticsLoading] = useState(true);
|
|
41
23
|
const $contentMap = useStore(fullContentMapStore);
|
|
42
|
-
|
|
43
24
|
const [isPanelOpen, setIsPanelOpen] = useState(false);
|
|
44
|
-
|
|
45
|
-
const [selectedMode, setSelectedMode] = useState(
|
|
46
|
-
params?.defaultMode || 'recent'
|
|
47
|
-
);
|
|
48
25
|
const [excludedIds, setExcludedIds] = useState<string[]>(
|
|
49
26
|
params?.excludedIds ? params.excludedIds.split(',') : []
|
|
50
27
|
);
|
|
@@ -56,6 +33,7 @@ const ListContentSetup = ({
|
|
|
56
33
|
);
|
|
57
34
|
const [currentPage, setCurrentPage] = useState(1);
|
|
58
35
|
const [bgColor, setBgColor] = useState(params?.bgColor || '');
|
|
36
|
+
const [title, setTitle] = useState(params?.title || '');
|
|
59
37
|
|
|
60
38
|
const isInitialMount = useRef(true);
|
|
61
39
|
|
|
@@ -65,15 +43,15 @@ const ListContentSetup = ({
|
|
|
65
43
|
selectedTopics.length > 0 ||
|
|
66
44
|
excludedIds.length > 0 ||
|
|
67
45
|
pageSize !== 10 ||
|
|
68
|
-
bgColor !== ''
|
|
46
|
+
bgColor !== '' ||
|
|
47
|
+
title !== '';
|
|
69
48
|
|
|
70
49
|
const validPages = $contentMap.filter(
|
|
71
50
|
(item) =>
|
|
72
51
|
item.type === 'StoryFragment' &&
|
|
73
52
|
typeof item.description === 'string' &&
|
|
74
53
|
typeof item.thumbSrc === 'string' &&
|
|
75
|
-
typeof item.thumbSrcSet === 'string'
|
|
76
|
-
typeof item.changed === 'string'
|
|
54
|
+
typeof item.thumbSrcSet === 'string'
|
|
77
55
|
);
|
|
78
56
|
|
|
79
57
|
// Build topic map for filtering
|
|
@@ -89,47 +67,6 @@ const ListContentSetup = ({
|
|
|
89
67
|
}
|
|
90
68
|
});
|
|
91
69
|
|
|
92
|
-
const fetchAnalyticsData = async () => {
|
|
93
|
-
try {
|
|
94
|
-
setIsAnalyticsLoading(true);
|
|
95
|
-
// Updated to use V2 API endpoint
|
|
96
|
-
const response = await fetch('/api/v1/analytics/storyfragments', {
|
|
97
|
-
headers: {
|
|
98
|
-
'X-Tenant-ID': window.TRACTSTACK_CONFIG?.tenantId || 'default',
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (!response.ok) {
|
|
103
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const analyticsArray = await response.json();
|
|
107
|
-
|
|
108
|
-
// Transform array to a map keyed by ID for easier lookup
|
|
109
|
-
// V2 API returns array directly, not wrapped in a success/data structure
|
|
110
|
-
const analyticsById = Array.isArray(analyticsArray)
|
|
111
|
-
? analyticsArray.reduce(
|
|
112
|
-
(
|
|
113
|
-
acc: Record<string, StoryfragmentAnalytics>,
|
|
114
|
-
item: StoryfragmentAnalytics
|
|
115
|
-
) => {
|
|
116
|
-
acc[item.id] = item;
|
|
117
|
-
return acc;
|
|
118
|
-
},
|
|
119
|
-
{}
|
|
120
|
-
)
|
|
121
|
-
: {};
|
|
122
|
-
|
|
123
|
-
setAnalyticsData(analyticsById);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error('Error fetching analytics data:', error);
|
|
126
|
-
// Set empty analytics on error to prevent blocking the UI
|
|
127
|
-
setAnalyticsData({});
|
|
128
|
-
} finally {
|
|
129
|
-
setIsAnalyticsLoading(false);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
70
|
const topics = Array.from(topicMap.entries())
|
|
134
71
|
.map(([name, { count, pageIds }]) => ({
|
|
135
72
|
name,
|
|
@@ -139,23 +76,16 @@ const ListContentSetup = ({
|
|
|
139
76
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
140
77
|
|
|
141
78
|
const filteredPages = validPages.filter((page) => {
|
|
142
|
-
if (excludedIds.includes(page.id)) {
|
|
79
|
+
if (excludedIds.includes(page.id) || selectedTopics.length === 0) {
|
|
143
80
|
return false;
|
|
144
81
|
}
|
|
145
|
-
if (selectedTopics.length === 0) {
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
82
|
return (
|
|
149
83
|
page.topics && page.topics.some((topic) => selectedTopics.includes(topic))
|
|
150
84
|
);
|
|
151
85
|
});
|
|
152
86
|
|
|
87
|
+
// Always sort by most recent
|
|
153
88
|
const sortedPages = [...filteredPages].sort((a, b) => {
|
|
154
|
-
if (selectedMode === 'popular') {
|
|
155
|
-
const aViews = analyticsData[a.id]?.total_actions || 0;
|
|
156
|
-
const bViews = analyticsData[b.id]?.total_actions || 0;
|
|
157
|
-
return bViews - aViews;
|
|
158
|
-
}
|
|
159
89
|
const bDate = b.changed ? new Date(b.changed) : new Date(0);
|
|
160
90
|
const aDate = a.changed ? new Date(a.changed) : new Date(0);
|
|
161
91
|
return bDate.getTime() - aDate.getTime();
|
|
@@ -177,11 +107,11 @@ const ListContentSetup = ({
|
|
|
177
107
|
codeHookTarget: 'list-content',
|
|
178
108
|
codeHookPayload: {
|
|
179
109
|
options: JSON.stringify({
|
|
180
|
-
defaultMode: selectedMode,
|
|
181
110
|
excludedIds: excludedIds.join(','),
|
|
182
111
|
topics: selectedTopics.join(','),
|
|
183
112
|
pageSize: pageSize,
|
|
184
113
|
bgColor: bgColor,
|
|
114
|
+
title: title,
|
|
185
115
|
}),
|
|
186
116
|
},
|
|
187
117
|
bgColour: bgColor || undefined,
|
|
@@ -198,10 +128,6 @@ const ListContentSetup = ({
|
|
|
198
128
|
}
|
|
199
129
|
};
|
|
200
130
|
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
fetchAnalyticsData();
|
|
203
|
-
}, []);
|
|
204
|
-
|
|
205
131
|
useEffect(() => {
|
|
206
132
|
if (isInitialMount.current) {
|
|
207
133
|
isInitialMount.current = false;
|
|
@@ -213,7 +139,7 @@ const ListContentSetup = ({
|
|
|
213
139
|
}, 500);
|
|
214
140
|
|
|
215
141
|
return () => clearTimeout(timeoutId);
|
|
216
|
-
}, [
|
|
142
|
+
}, [excludedIds, selectedTopics, pageSize, bgColor, title]);
|
|
217
143
|
|
|
218
144
|
// Toggle a page's exclusion status
|
|
219
145
|
const toggleExclude = (id: string) => {
|
|
@@ -339,7 +265,6 @@ const ListContentSetup = ({
|
|
|
339
265
|
);
|
|
340
266
|
}
|
|
341
267
|
|
|
342
|
-
if (isAnalyticsLoading) return null;
|
|
343
268
|
return (
|
|
344
269
|
<div className="w-full space-y-6 bg-slate-50 p-6">
|
|
345
270
|
<div className="flex items-center justify-between">
|
|
@@ -359,6 +284,23 @@ const ListContentSetup = ({
|
|
|
359
284
|
<h3 className="text-lg font-bold text-gray-900">Content Settings</h3>
|
|
360
285
|
</div>
|
|
361
286
|
<div className="space-y-4 pt-4">
|
|
287
|
+
<div>
|
|
288
|
+
<label
|
|
289
|
+
htmlFor="list-title"
|
|
290
|
+
className="block text-sm font-bold text-gray-700"
|
|
291
|
+
>
|
|
292
|
+
Optional Title
|
|
293
|
+
</label>
|
|
294
|
+
<input
|
|
295
|
+
type="text"
|
|
296
|
+
id="list-title"
|
|
297
|
+
className="mt-1 block w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-600 focus:ring-cyan-600 sm:text-sm"
|
|
298
|
+
value={title}
|
|
299
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
300
|
+
placeholder="e.g., Recent Articles"
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
362
304
|
<div>
|
|
363
305
|
<label
|
|
364
306
|
htmlFor="page-size"
|
|
@@ -380,29 +322,6 @@ const ListContentSetup = ({
|
|
|
380
322
|
</select>
|
|
381
323
|
</div>
|
|
382
324
|
|
|
383
|
-
<div>
|
|
384
|
-
<label
|
|
385
|
-
htmlFor="sort-mode"
|
|
386
|
-
className="block text-sm font-bold text-gray-700"
|
|
387
|
-
>
|
|
388
|
-
Default sort order
|
|
389
|
-
</label>
|
|
390
|
-
<select
|
|
391
|
-
id="sort-mode"
|
|
392
|
-
name="sort-mode"
|
|
393
|
-
className="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-600 focus:outline-none focus:ring-cyan-600 sm:text-sm"
|
|
394
|
-
value={selectedMode}
|
|
395
|
-
onChange={(e) => setSelectedMode(e.target.value)}
|
|
396
|
-
>
|
|
397
|
-
<option value="recent">Most Recent</option>
|
|
398
|
-
<option value="popular">Most Popular</option>
|
|
399
|
-
</select>
|
|
400
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
401
|
-
Note: Users can toggle between views regardless of the default
|
|
402
|
-
setting
|
|
403
|
-
</p>
|
|
404
|
-
</div>
|
|
405
|
-
|
|
406
325
|
<div>
|
|
407
326
|
<ColorPickerCombo
|
|
408
327
|
title="Background Color"
|
|
@@ -536,7 +455,6 @@ const ListContentSetup = ({
|
|
|
536
455
|
<div className="divide-y divide-gray-200">
|
|
537
456
|
{paginatedPages.map((page) => {
|
|
538
457
|
const isExcluded = excludedIds.includes(page.id);
|
|
539
|
-
const analytics = analyticsData[page.id];
|
|
540
458
|
|
|
541
459
|
return (
|
|
542
460
|
<div key={page.id} className="flex items-center p-4">
|
|
@@ -549,23 +467,9 @@ const ListContentSetup = ({
|
|
|
549
467
|
/>
|
|
550
468
|
</div>
|
|
551
469
|
<div className="ml-4 min-w-0 flex-1">
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
</p>
|
|
556
|
-
<div className="flex items-center space-x-4">
|
|
557
|
-
<button
|
|
558
|
-
onClick={() => toggleExclude(page.id)}
|
|
559
|
-
className={`rounded px-2 py-1 text-xs font-bold ${
|
|
560
|
-
isExcluded
|
|
561
|
-
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
562
|
-
: 'bg-red-100 text-red-600 hover:bg-red-200'
|
|
563
|
-
}`}
|
|
564
|
-
>
|
|
565
|
-
{isExcluded ? 'Restore' : 'Exclude'}
|
|
566
|
-
</button>
|
|
567
|
-
</div>
|
|
568
|
-
</div>
|
|
470
|
+
<p className="text-sm font-bold text-gray-900">
|
|
471
|
+
{page.title}
|
|
472
|
+
</p>
|
|
569
473
|
<div className="mt-1">
|
|
570
474
|
<p className="line-clamp-1 text-sm text-gray-500">
|
|
571
475
|
{page.description}
|
|
@@ -586,22 +490,23 @@ const ListContentSetup = ({
|
|
|
586
490
|
{topic}
|
|
587
491
|
</span>
|
|
588
492
|
))}
|
|
493
|
+
<button
|
|
494
|
+
onClick={() => toggleExclude(page.id)}
|
|
495
|
+
className={`rounded px-2 py-1 text-xs font-bold ${
|
|
496
|
+
isExcluded
|
|
497
|
+
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
498
|
+
: 'bg-red-100 text-red-600 hover:bg-red-200'
|
|
499
|
+
}`}
|
|
500
|
+
>
|
|
501
|
+
{isExcluded ? 'Restore' : 'Exclude'}
|
|
502
|
+
</button>
|
|
589
503
|
</div>
|
|
590
504
|
)}
|
|
591
505
|
<div className="mt-1 flex items-center text-xs text-gray-500">
|
|
592
|
-
{
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
<>
|
|
597
|
-
<span className="mx-2">•</span>
|
|
598
|
-
<span>
|
|
599
|
-
Updated{' '}
|
|
600
|
-
{new Date(page.changed).toLocaleDateString()}
|
|
601
|
-
</span>
|
|
602
|
-
</>
|
|
603
|
-
)}
|
|
604
|
-
</>
|
|
506
|
+
{page.changed && (
|
|
507
|
+
<span>
|
|
508
|
+
Updated {new Date(page.changed).toLocaleDateString()}
|
|
509
|
+
</span>
|
|
605
510
|
)}
|
|
606
511
|
</div>
|
|
607
512
|
</div>
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useState, useMemo } from 'react';
|
|
2
|
+
import { Combobox } from '@ark-ui/react/combobox';
|
|
3
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
4
|
+
import { createListCollection } from '@ark-ui/react/collection';
|
|
5
|
+
import { useStore } from '@nanostores/react';
|
|
6
|
+
import { fullContentMapStore } from '@/stores/storykeep';
|
|
7
|
+
import { getCtx } from '@/stores/nodes';
|
|
8
|
+
import type { PaneNode } from '@/types/compositorTypes';
|
|
9
|
+
import type { BrandConfig } from '@/types/tractstack';
|
|
10
|
+
|
|
11
|
+
interface ProductCardSetupProps {
|
|
12
|
+
nodeId: string;
|
|
13
|
+
params: Record<string, any> | null;
|
|
14
|
+
config: BrandConfig;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ProductCardSetup = (props: ProductCardSetupProps) => {
|
|
18
|
+
const { nodeId, params } = props;
|
|
19
|
+
const ctx = getCtx();
|
|
20
|
+
const $contentMap = useStore(fullContentMapStore);
|
|
21
|
+
|
|
22
|
+
const [showSelector, setShowSelector] = useState(false);
|
|
23
|
+
|
|
24
|
+
const products = useMemo(() => {
|
|
25
|
+
return $contentMap
|
|
26
|
+
.filter(
|
|
27
|
+
(item) => item.type === 'Resource' && item.categorySlug === 'product'
|
|
28
|
+
)
|
|
29
|
+
.map((item) => ({ label: item.title, value: item.slug }));
|
|
30
|
+
}, [$contentMap]);
|
|
31
|
+
|
|
32
|
+
const productCollection = useMemo(() => {
|
|
33
|
+
return createListCollection({
|
|
34
|
+
items: products,
|
|
35
|
+
itemToValue: (item) => item.value,
|
|
36
|
+
itemToString: (item) => item.label,
|
|
37
|
+
});
|
|
38
|
+
}, [products]);
|
|
39
|
+
|
|
40
|
+
const [selectedItem, setSelectedItem] = useState<{
|
|
41
|
+
label: string;
|
|
42
|
+
value: string;
|
|
43
|
+
} | null>(() => {
|
|
44
|
+
const currentSlug = params?.slug;
|
|
45
|
+
if (currentSlug) {
|
|
46
|
+
return products.find((p) => p.value === currentSlug) || null;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const updatePayload = (newPayload: Record<string, any>) => {
|
|
52
|
+
const paneNode = ctx.allNodes.get().get(nodeId) as PaneNode;
|
|
53
|
+
if (!paneNode) return;
|
|
54
|
+
|
|
55
|
+
const updatedPaneNode = {
|
|
56
|
+
...paneNode,
|
|
57
|
+
codeHookPayload: {
|
|
58
|
+
target: paneNode.codeHookPayload?.target,
|
|
59
|
+
options: JSON.stringify(newPayload),
|
|
60
|
+
},
|
|
61
|
+
isChanged: true,
|
|
62
|
+
};
|
|
63
|
+
ctx.modifyNodes([updatedPaneNode]);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleSelect = (details: { value: string[] }) => {
|
|
67
|
+
const slug = details.value[0];
|
|
68
|
+
const selected = products.find((p) => p.value === slug);
|
|
69
|
+
if (selected) {
|
|
70
|
+
setSelectedItem(selected);
|
|
71
|
+
updatePayload({ slug: selected.value });
|
|
72
|
+
setShowSelector(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleClear = () => {
|
|
77
|
+
setSelectedItem(null);
|
|
78
|
+
updatePayload({});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="space-y-4 p-2">
|
|
83
|
+
<h3 className="font-bold text-gray-800">Product Card Configuration</h3>
|
|
84
|
+
|
|
85
|
+
<div className="rounded-md border bg-gray-50 p-3">
|
|
86
|
+
<div className="flex items-center justify-between">
|
|
87
|
+
<div>
|
|
88
|
+
<p className="text-sm font-bold text-gray-600">Selected Product</p>
|
|
89
|
+
<p className="truncate font-bold text-gray-900">
|
|
90
|
+
{selectedItem ? selectedItem.label : 'None'}
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex gap-x-2">
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={() => setShowSelector(!showSelector)}
|
|
97
|
+
className="rounded bg-white px-3 py-1 text-sm font-bold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
98
|
+
>
|
|
99
|
+
{showSelector ? 'Cancel' : 'Change'}
|
|
100
|
+
</button>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={handleClear}
|
|
104
|
+
className="rounded bg-white px-3 py-1 text-sm font-bold text-red-600 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
|
105
|
+
disabled={!selectedItem}
|
|
106
|
+
>
|
|
107
|
+
Clear
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{showSelector && (
|
|
114
|
+
<div className="space-y-2 rounded-md border p-3">
|
|
115
|
+
<Combobox.Root
|
|
116
|
+
collection={productCollection}
|
|
117
|
+
onValueChange={handleSelect}
|
|
118
|
+
lazyMount
|
|
119
|
+
unmountOnExit
|
|
120
|
+
>
|
|
121
|
+
<Combobox.Label className="text-sm font-bold text-gray-700">
|
|
122
|
+
Find a product
|
|
123
|
+
</Combobox.Label>
|
|
124
|
+
<Combobox.Control>
|
|
125
|
+
<Combobox.Input
|
|
126
|
+
className="w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 sm:text-sm"
|
|
127
|
+
placeholder="Search products..."
|
|
128
|
+
/>
|
|
129
|
+
</Combobox.Control>
|
|
130
|
+
<Portal>
|
|
131
|
+
<Combobox.Positioner>
|
|
132
|
+
<Combobox.Content className="z-50 max-h-60 overflow-y-auto rounded-md border bg-white shadow-lg">
|
|
133
|
+
{products.map((item) => (
|
|
134
|
+
<Combobox.Item
|
|
135
|
+
key={item.value}
|
|
136
|
+
item={item}
|
|
137
|
+
className="relative cursor-pointer select-none px-4 py-2 text-gray-900 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
|
|
138
|
+
>
|
|
139
|
+
<Combobox.ItemText>{item.label}</Combobox.ItemText>
|
|
140
|
+
</Combobox.Item>
|
|
141
|
+
))}
|
|
142
|
+
</Combobox.Content>
|
|
143
|
+
</Combobox.Positioner>
|
|
144
|
+
</Portal>
|
|
145
|
+
</Combobox.Root>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default ProductCardSetup;
|