astro-tractstack 2.0.0-rc.8 → 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.
Files changed (141) hide show
  1. package/LICENSE +8 -97
  2. package/README.md +7 -5
  3. package/bin/create-tractstack.js +35 -11
  4. package/dist/index.js +106 -29
  5. package/package.json +10 -5
  6. package/templates/css/frontend.css +1 -1
  7. package/templates/custom/minimal/CodeHook.astro +13 -12
  8. package/templates/custom/minimal/CustomRoutes.astro +25 -31
  9. package/templates/custom/with-examples/CodeHook.astro +22 -11
  10. package/templates/custom/with-examples/CustomRoutes.astro +4 -8
  11. package/templates/custom/with-examples/ProductCard.astro +29 -0
  12. package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
  13. package/templates/custom/with-examples/ProductGrid.astro +64 -0
  14. package/templates/custom/with-examples/pages/Collections.astro +58 -98
  15. package/templates/gitignore +42 -0
  16. package/templates/prettierignore +5 -0
  17. package/templates/prettierrc +19 -0
  18. package/templates/src/client/app.js +127 -0
  19. package/templates/src/client/htmx.min.js +3519 -0
  20. package/templates/src/client/view.js +429 -0
  21. package/templates/src/components/Footer.astro +4 -9
  22. package/templates/src/components/Header.astro +67 -60
  23. package/templates/src/components/Menu.tsx +188 -52
  24. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  25. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
  26. package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
  27. package/templates/src/components/codehooks/EpinetWrapper.tsx +1 -0
  28. package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
  29. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
  30. package/templates/src/components/codehooks/ListContent.astro +32 -162
  31. package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
  32. package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
  33. package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
  34. package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
  35. package/templates/src/components/compositor/Node.tsx +3 -6
  36. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
  37. package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
  38. package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
  39. package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
  40. package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
  41. package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
  42. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
  43. package/templates/src/components/edit/Header.tsx +10 -4
  44. package/templates/src/components/edit/PanelSwitch.tsx +11 -7
  45. package/templates/src/components/edit/SettingsPanel.tsx +29 -18
  46. package/templates/src/components/edit/ToolBar.tsx +1 -28
  47. package/templates/src/components/edit/ToolMode.tsx +45 -32
  48. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
  49. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
  50. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
  51. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
  52. package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
  53. package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
  54. package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
  55. package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
  56. package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
  57. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
  58. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
  59. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
  60. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
  61. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
  62. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
  63. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
  64. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
  65. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
  66. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
  67. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
  68. package/templates/src/components/edit/state/SaveModal.tsx +316 -169
  69. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
  70. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
  71. package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
  72. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
  73. package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
  74. package/templates/src/components/fields/ArtpackImage.tsx +4 -1
  75. package/templates/src/components/fields/BackgroundImage.tsx +1 -1
  76. package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
  77. package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
  78. package/templates/src/components/fields/ImageUpload.tsx +1 -1
  79. package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
  80. package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
  81. package/templates/src/components/form/ActionBuilderField.tsx +306 -87
  82. package/templates/src/components/search/SearchModal.tsx +420 -0
  83. package/templates/src/components/search/SearchResults.tsx +367 -0
  84. package/templates/src/components/search/SearchWrapper.tsx +46 -0
  85. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
  86. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
  87. package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
  88. package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
  89. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +37 -33
  90. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +55 -7
  91. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +17 -2
  92. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
  93. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
  94. package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
  95. package/templates/src/components/tenant/RegistrationForm.tsx +1 -1
  96. package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
  97. package/templates/src/constants/shapes.ts +9 -0
  98. package/templates/src/constants.ts +2121 -16
  99. package/templates/src/hooks/useSearch.ts +228 -0
  100. package/templates/src/layouts/Layout.astro +213 -104
  101. package/templates/src/lib/storyData.ts +4 -1
  102. package/templates/src/pages/[...slug]/edit.astro +14 -14
  103. package/templates/src/pages/[...slug].astro +82 -21
  104. package/templates/src/pages/api/orphan-analysis.ts +0 -1
  105. package/templates/src/pages/api/tailwind.ts +23 -21
  106. package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
  107. package/templates/src/pages/context/[...contextSlug].astro +7 -2
  108. package/templates/src/pages/storykeep/advanced.astro +5 -4
  109. package/templates/src/pages/storykeep/branding.astro +5 -4
  110. package/templates/src/pages/storykeep/content.astro +5 -4
  111. package/templates/src/pages/storykeep/init.astro +40 -1
  112. package/templates/src/pages/storykeep/login.astro +1 -1
  113. package/templates/src/pages/storykeep.astro +5 -4
  114. package/templates/src/stores/nodes.ts +59 -88
  115. package/templates/src/stores/orphanAnalysis.ts +19 -21
  116. package/templates/src/stores/storykeep.ts +7 -0
  117. package/templates/src/types/compositorTypes.ts +6 -0
  118. package/templates/src/types/tractstack.ts +17 -0
  119. package/templates/src/utils/actions/lispLexer.ts +2 -2
  120. package/templates/src/utils/actions/preParse_Action.ts +3 -0
  121. package/templates/src/utils/api/beliefHelpers.ts +12 -36
  122. package/templates/src/utils/api/menuHelpers.ts +2 -2
  123. package/templates/src/utils/api.ts +26 -0
  124. package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
  125. package/templates/src/utils/compositor/allowInsert.ts +5 -3
  126. package/templates/src/utils/compositor/nodesHelper.ts +4 -0
  127. package/templates/src/utils/compositor/processMarkdown.ts +16 -2
  128. package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
  129. package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
  130. package/templates/src/utils/compositor/typeGuards.ts +1 -0
  131. package/templates/src/utils/customHelpers.ts +38 -0
  132. package/templates/src/utils/helpers.ts +2 -2
  133. package/templates/src/utils/layout.ts +65 -144
  134. package/utils/inject-files.ts +95 -18
  135. package/templates/src/client/analytics-events.js +0 -207
  136. package/templates/src/client/belief-events.js +0 -191
  137. package/templates/src/client/sse.js +0 -613
  138. package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
  139. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
  140. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
  141. package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
@@ -0,0 +1,367 @@
1
+ import { useState, useMemo } from 'react';
2
+ import { Pagination } from '@ark-ui/react/pagination';
3
+ import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
4
+ import type { CategorizedResults, FTSResult } from '@/types/tractstack';
5
+ import type { FullContentMapItem } from '@/types/tractstack';
6
+ import {
7
+ getResourceUrl,
8
+ getResourceImage,
9
+ getResourceDescription,
10
+ } from '@/utils/customHelpers';
11
+
12
+ const VERBOSE = false;
13
+
14
+ interface SearchResultsProps {
15
+ results: CategorizedResults;
16
+ contentMap: FullContentMapItem[];
17
+ getTypeColor: (type: string) => string;
18
+ }
19
+
20
+ interface ResultItem {
21
+ id: string;
22
+ type: 'StoryFragment' | 'ContextPane' | 'Resource';
23
+ title: string;
24
+ slug: string;
25
+ description?: string;
26
+ topics?: string[];
27
+ changed?: string;
28
+ thumbSrc?: string;
29
+ categorySlug?: string;
30
+ url: string;
31
+ imageSrc: string;
32
+ }
33
+
34
+ const ITEMS_PER_PAGE = 10;
35
+
36
+ export default function SearchResults({
37
+ results,
38
+ contentMap,
39
+ getTypeColor,
40
+ }: SearchResultsProps) {
41
+ const [currentPage, setCurrentPage] = useState(1);
42
+
43
+ const allResultItems = useMemo(() => {
44
+ const items: ResultItem[] = [];
45
+
46
+ if (VERBOSE)
47
+ console.log('DEBUG SearchResults: Processing results', {
48
+ storyFragmentResults: results.storyFragmentResults.length,
49
+ contextPaneResults: results.contextPaneResults.length,
50
+ resourceResults: results.resourceResults.length,
51
+ contentMapSize: contentMap.length,
52
+ });
53
+
54
+ // Process StoryFragment results
55
+ results.storyFragmentResults.forEach((ftsResult: FTSResult, index) => {
56
+ if (VERBOSE) console.log(`DEBUG StoryFragment ${index}:`, ftsResult);
57
+ const item = contentMap.find(
58
+ (item) => item.id === ftsResult.ID && item.type === 'StoryFragment'
59
+ );
60
+ if (item) {
61
+ if (VERBOSE)
62
+ console.log(
63
+ `DEBUG StoryFragment ${index}: Found in contentMap`,
64
+ item
65
+ );
66
+ items.push({
67
+ id: item.id,
68
+ type: 'StoryFragment',
69
+ title: item.title,
70
+ slug: item.slug,
71
+ description: item.description || undefined,
72
+ topics: item.topics || undefined,
73
+ changed: item.changed || undefined,
74
+ thumbSrc: item.thumbSrc || undefined,
75
+ url: `/${item.slug}`,
76
+ imageSrc: item.thumbSrc || '/static.jpg',
77
+ });
78
+ } else {
79
+ if (VERBOSE)
80
+ console.log(
81
+ `DEBUG StoryFragment ${index}: NOT found in contentMap for ID ${ftsResult.ID}`
82
+ );
83
+ }
84
+ });
85
+
86
+ // Process ContextPane results
87
+ results.contextPaneResults.forEach((ftsResult: FTSResult, index) => {
88
+ if (VERBOSE) console.log(`DEBUG ContextPane ${index}:`, ftsResult);
89
+ const item = contentMap.find(
90
+ (item) => item.id === ftsResult.ID && item.type === 'Pane'
91
+ );
92
+ if (item) {
93
+ if (VERBOSE)
94
+ console.log(`DEBUG ContextPane ${index}: Found in contentMap`, item);
95
+ items.push({
96
+ id: item.id,
97
+ type: 'ContextPane',
98
+ title: item.title,
99
+ slug: item.slug,
100
+ url: `/context/${item.slug}`,
101
+ imageSrc: '/static.jpg',
102
+ });
103
+ } else {
104
+ if (VERBOSE)
105
+ console.log(
106
+ `DEBUG ContextPane ${index}: NOT found in contentMap for ID ${ftsResult.ID}`
107
+ );
108
+ }
109
+ });
110
+
111
+ // Process Resource results
112
+ results.resourceResults.forEach((ftsResult: FTSResult, index) => {
113
+ if (VERBOSE) console.log(`DEBUG Resource ${index}:`, ftsResult);
114
+ const item = contentMap.find(
115
+ (item) => item.id === ftsResult.ID && item.type === 'Resource'
116
+ );
117
+ if (item) {
118
+ if (VERBOSE)
119
+ console.log(`DEBUG Resource ${index}: Found in contentMap`, item);
120
+
121
+ const resourceUrl = getResourceUrl(item.categorySlug || '', item.slug);
122
+ const resourceImage = getResourceImage(
123
+ item.id,
124
+ item.slug,
125
+ item.categorySlug || ''
126
+ );
127
+ const description = getResourceDescription(
128
+ item.id,
129
+ item.slug,
130
+ item.categorySlug || ''
131
+ );
132
+
133
+ if (VERBOSE)
134
+ console.log(`DEBUG Resource ${index}: Helper results`, {
135
+ resourceUrl,
136
+ resourceImage,
137
+ description,
138
+ categorySlug: item.categorySlug,
139
+ slug: item.slug,
140
+ id: item.id,
141
+ });
142
+
143
+ items.push({
144
+ id: item.id,
145
+ type: 'Resource',
146
+ title: item.title,
147
+ slug: item.slug,
148
+ description: description || undefined,
149
+ categorySlug: item.categorySlug || undefined,
150
+ url: resourceUrl,
151
+ imageSrc: resourceImage,
152
+ });
153
+ } else {
154
+ if (VERBOSE)
155
+ console.log(
156
+ `DEBUG Resource ${index}: NOT found in contentMap for ID ${ftsResult.ID}`
157
+ );
158
+ if (VERBOSE)
159
+ console.log(
160
+ 'DEBUG: Available resource IDs in contentMap:',
161
+ contentMap
162
+ .filter((item) => item.type === 'Resource')
163
+ .map((item) => ({ id: item.id, title: item.title }))
164
+ );
165
+ }
166
+ });
167
+
168
+ if (VERBOSE) console.log('DEBUG SearchResults: Final items', items);
169
+
170
+ // Sort by whether they have real images
171
+ return items.sort((a, b) => {
172
+ const aHasRealImage = a.imageSrc !== '/static.jpg';
173
+ const bHasRealImage = b.imageSrc !== '/static.jpg';
174
+
175
+ if (aHasRealImage && !bHasRealImage) return -1;
176
+ if (!aHasRealImage && bHasRealImage) return 1;
177
+ return 0;
178
+ });
179
+ }, [results, contentMap]);
180
+
181
+ const totalResults = allResultItems.length;
182
+ const totalPages = Math.ceil(totalResults / ITEMS_PER_PAGE);
183
+ const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
184
+ const paginatedItems = allResultItems.slice(
185
+ startIndex,
186
+ startIndex + ITEMS_PER_PAGE
187
+ );
188
+
189
+ const handlePageChange = (page: number) => {
190
+ setCurrentPage(page);
191
+ };
192
+
193
+ const getResultBadge = (type: string, categorySlug?: string) => {
194
+ let styleType = type;
195
+ let label = '';
196
+ switch (type) {
197
+ case 'StoryFragment':
198
+ label = 'Page';
199
+ break;
200
+ case 'ContextPane':
201
+ label = 'Context';
202
+ break;
203
+ case 'Resource':
204
+ styleType = 'COLLECTION';
205
+ label = categorySlug || 'Resource';
206
+ break;
207
+ default:
208
+ return null;
209
+ }
210
+
211
+ // Get the color classes but extract just the text and background colors for the inline style
212
+ const colorClasses = getTypeColor(styleType);
213
+
214
+ return (
215
+ <span className={`rounded px-2 py-1 text-xs ${colorClasses}`}>
216
+ {label}
217
+ </span>
218
+ );
219
+ };
220
+
221
+ if (totalResults === 0) {
222
+ return null;
223
+ }
224
+
225
+ return (
226
+ <div className="p-6">
227
+ <div className="mb-6">
228
+ <h2 className="text-mydarkgrey text-lg font-bold">
229
+ {totalResults} result{totalResults !== 1 ? 's' : ''} found
230
+ </h2>
231
+ <p className="mt-1 text-sm text-gray-600">
232
+ Showing {startIndex + 1}-
233
+ {Math.min(startIndex + ITEMS_PER_PAGE, totalResults)} of{' '}
234
+ {totalResults}
235
+ </p>
236
+ </div>
237
+
238
+ <div className="mb-8 space-y-4">
239
+ {paginatedItems.map((item) => (
240
+ <div
241
+ key={item.id}
242
+ className="rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-100"
243
+ >
244
+ <a href={item.url} className="group block">
245
+ <div className="flex flex-col md:flex-row md:items-start md:gap-4">
246
+ {/* Mobile: Full width image without overlay badge */}
247
+ <div
248
+ className="bg-mydarkgrey relative w-full overflow-hidden rounded-lg md:hidden"
249
+ style={{ aspectRatio: '1200/630' }}
250
+ >
251
+ <img
252
+ src={item.imageSrc}
253
+ alt={item.title}
254
+ className="h-full w-full object-contain"
255
+ />
256
+ </div>
257
+
258
+ {/* Desktop: Side image without overlay badge */}
259
+ <div
260
+ className="bg-mydarkgrey relative hidden flex-shrink-0 overflow-hidden rounded-lg md:block"
261
+ style={{ width: '240px', height: '135px' }}
262
+ >
263
+ <img
264
+ src={item.imageSrc}
265
+ alt={item.title}
266
+ className="h-full w-full object-contain"
267
+ />
268
+ </div>
269
+
270
+ <div className="mt-3 min-w-0 flex-1 md:mt-0">
271
+ <div className="flex items-start justify-between gap-4">
272
+ <div className="flex-1">
273
+ <h3 className="text-mydarkgrey group-hover:text-myblue mb-2 text-lg font-bold transition-colors">
274
+ {item.title}
275
+ </h3>
276
+ {item.description && (
277
+ <p className="text-mydarkgrey mb-2 text-sm">
278
+ {item.description}
279
+ </p>
280
+ )}
281
+
282
+ {/* Category badge and topics in same row */}
283
+ <div className="mb-2 flex flex-wrap gap-1">
284
+ {/* Always show the category badge first */}
285
+ {getResultBadge(item.type, item.categorySlug)}
286
+
287
+ {/* Then show topics if they exist */}
288
+ {item.topics && item.topics.length > 0 && (
289
+ <>
290
+ {item.topics.slice(0, 3).map((topic, idx) => (
291
+ <span
292
+ key={idx}
293
+ className="bg-myoffwhite text-mydarkgrey rounded px-2 py-1 text-xs"
294
+ >
295
+ {topic}
296
+ </span>
297
+ ))}
298
+ {item.topics.length > 3 && (
299
+ <span className="text-mydarkgrey text-xs">
300
+ +{item.topics.length - 3} more
301
+ </span>
302
+ )}
303
+ </>
304
+ )}
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </a>
311
+ </div>
312
+ ))}
313
+ </div>
314
+
315
+ {totalPages > 1 && (
316
+ <div className="flex justify-center">
317
+ <Pagination.Root
318
+ count={totalResults}
319
+ pageSize={ITEMS_PER_PAGE}
320
+ page={currentPage}
321
+ onPageChange={(details) => handlePageChange(details.page)}
322
+ >
323
+ <Pagination.PrevTrigger className="text-mydarkgrey hover:text-myblue mr-2 flex items-center gap-1 rounded px-3 py-2 text-sm font-bold transition-colors disabled:opacity-50">
324
+ <ChevronLeftIcon className="h-4 w-4" />
325
+ Previous
326
+ </Pagination.PrevTrigger>
327
+
328
+ <div className="flex items-center gap-1">
329
+ <Pagination.Context>
330
+ {(pagination) =>
331
+ pagination.pages.map((page, index) =>
332
+ page.type === 'page' ? (
333
+ <Pagination.Item
334
+ key={index}
335
+ type="page"
336
+ value={page.value}
337
+ className={`rounded px-3 py-2 text-sm font-bold transition-colors ${
338
+ page.value === currentPage
339
+ ? 'bg-myblue text-white'
340
+ : 'text-mydarkgrey hover:text-myblue'
341
+ }`}
342
+ >
343
+ {page.value}
344
+ </Pagination.Item>
345
+ ) : (
346
+ <span
347
+ key={index}
348
+ className="text-mydarkgrey px-2 text-sm"
349
+ >
350
+ {page.type === 'ellipsis' ? '...' : ''}
351
+ </span>
352
+ )
353
+ )
354
+ }
355
+ </Pagination.Context>
356
+ </div>
357
+
358
+ <Pagination.NextTrigger className="text-mydarkgrey hover:text-myblue ml-2 flex items-center gap-1 rounded px-3 py-2 text-sm font-bold transition-colors disabled:opacity-50">
359
+ Next
360
+ <ChevronRightIcon className="h-4 w-4" />
361
+ </Pagination.NextTrigger>
362
+ </Pagination.Root>
363
+ </div>
364
+ )}
365
+ </div>
366
+ );
367
+ }
@@ -0,0 +1,46 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
3
+ import { initSearch } from '@/utils/customHelpers';
4
+ import SearchModal from './SearchModal';
5
+ import type { FullContentMapItem } from '@/types/tractstack';
6
+
7
+ interface SearchWrapperProps {
8
+ contentMap: FullContentMapItem[];
9
+ }
10
+
11
+ export default function SearchWrapper({ contentMap }: SearchWrapperProps) {
12
+ const [isSearchOpen, setIsSearchOpen] = useState(false);
13
+
14
+ const handleSearchOpen = () => {
15
+ setIsSearchOpen(true);
16
+ };
17
+
18
+ const handleSearchClose = () => {
19
+ setIsSearchOpen(false);
20
+ };
21
+
22
+ useEffect(() => {
23
+ initSearch();
24
+ }, []);
25
+
26
+ return (
27
+ <>
28
+ <button
29
+ onClick={handleSearchOpen}
30
+ className="text-myblue/80 hover:text-myblue hover:rotate-6"
31
+ title="Search content"
32
+ aria-label="Search content"
33
+ >
34
+ <MagnifyingGlassIcon className="h-6 w-6" />
35
+ </button>
36
+
37
+ {isSearchOpen && (
38
+ <SearchModal
39
+ isOpen={isSearchOpen}
40
+ onClose={handleSearchClose}
41
+ contentMap={contentMap}
42
+ />
43
+ )}
44
+ </>
45
+ );
46
+ }
@@ -131,7 +131,7 @@ export default function StoryKeepDashboard_Advanced({
131
131
  };
132
132
 
133
133
  return (
134
- <div className="space-y-6">
134
+ <div className="space-y-8">
135
135
  {initialize && (
136
136
  <div className="rounded-md border border-blue-200 bg-blue-50 p-4">
137
137
  <div className="flex">
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useMemo, Component } from 'react';
1
+ import { useState, useEffect, useCallback, useMemo, Component } from 'react';
2
2
  import type { ReactNode } from 'react';
3
3
  import { useStore } from '@nanostores/react';
4
4
  import { epinetCustomFilters } from '@/stores/analytics';
@@ -238,7 +238,7 @@ export default function StoryKeepDashboard_Analytics({
238
238
  )}
239
239
 
240
240
  {/* Stats Cards Grid */}
241
- <div className="mb-6 grid grid-cols-1 gap-4 md:grid-cols-3">
241
+ <div className="mb-6 grid grid-cols-3 gap-4">
242
242
  {stats.map((item) => {
243
243
  const period = item.period;
244
244
  let firstTimeValue = 0,
@@ -261,11 +261,11 @@ export default function StoryKeepDashboard_Analytics({
261
261
  return (
262
262
  <div
263
263
  key={item.period}
264
- className="rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100"
264
+ className="rounded-lg border border-gray-100 bg-white px-2 py-2.5 shadow-sm transition-colors hover:border-cyan-100 md:px-4 md:py-3"
265
265
  >
266
266
  <dt className="text-sm font-bold text-gray-800">{item.name}</dt>
267
267
 
268
- <dd className="mt-2">
268
+ <dd className="mt-1 md:mt-2">
269
269
  <div className="flex items-end justify-between">
270
270
  <div className="flex-1">
271
271
  <div className="text-sm text-gray-600">Events</div>
@@ -276,10 +276,11 @@ export default function StoryKeepDashboard_Analytics({
276
276
  </div>
277
277
  </dd>
278
278
 
279
- <hr className="my-3.5 border-gray-100" />
279
+ <hr className="my-1.5 border-gray-100 md:my-3.5" />
280
280
 
281
281
  <dd>
282
- <div className="flex items-end justify-between">
282
+ {/* Desktop: side-by-side layout */}
283
+ <div className="hidden items-end justify-between md:flex">
283
284
  <div className="flex-1">
284
285
  <div className="text-sm text-gray-600">
285
286
  Anonymous Visitors
@@ -299,13 +300,35 @@ export default function StoryKeepDashboard_Analytics({
299
300
  </div>
300
301
  </div>
301
302
  </div>
303
+
304
+ {/* Mobile: stacked layout */}
305
+ <div className="md:hidden">
306
+ <div className="mb-1.5">
307
+ <div className="text-sm text-gray-600">
308
+ Anonymous Visitors
309
+ </div>
310
+ <div className="text-2xl font-bold tracking-tight text-cyan-700">
311
+ {firstTimeValue === 0
312
+ ? '-'
313
+ : formatNumber(firstTimeValue)}
314
+ </div>
315
+ </div>
316
+ <div>
317
+ <div className="text-sm text-gray-600">Known Leads</div>
318
+ <div className="text-2xl font-bold tracking-tight text-cyan-700">
319
+ {returningValue === 0
320
+ ? '-'
321
+ : formatNumber(returningValue)}
322
+ </div>
323
+ </div>
324
+ </div>
302
325
  </dd>
303
326
  </div>
304
327
  );
305
328
  })}
306
329
 
307
330
  {/* Total Leads Card */}
308
- <div className="rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100 md:col-span-3">
331
+ <div className="col-span-3 rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100">
309
332
  <div className="flex items-center justify-between">
310
333
  <dt className="text-sm font-bold text-gray-800">Total Leads</dt>
311
334
  <div className="flex items-center gap-2">
@@ -369,7 +392,7 @@ export default function StoryKeepDashboard_Analytics({
369
392
  </div>
370
393
 
371
394
  {/* User Journey Section */}
372
- <div className="mb-6 overflow-hidden">
395
+ <div className="mb-6 overflow-visible">
373
396
  <h3 className="mb-4 text-lg font-bold text-gray-900">
374
397
  User Journey Analytics
375
398
  </h3>
@@ -416,6 +439,7 @@ export default function StoryKeepDashboard_Analytics({
416
439
  isLoading={
417
440
  analytics.isLoading || analytics.status === 'loading'
418
441
  }
442
+ hourlyNodeActivity={analytics.hourlyNodeActivity}
419
443
  />
420
444
  </div>
421
445
  </ErrorBoundary>
@@ -430,6 +454,7 @@ export default function StoryKeepDashboard_Analytics({
430
454
  isLoading={
431
455
  analytics.isLoading || analytics.status === 'loading'
432
456
  }
457
+ hourlyNodeActivity={analytics.hourlyNodeActivity}
433
458
  />
434
459
  </>
435
460
  )
@@ -442,6 +467,7 @@ export default function StoryKeepDashboard_Analytics({
442
467
  <EpinetDurationSelector
443
468
  fullContentMap={fullContentMap}
444
469
  isLoading={analytics.isLoading || analytics.status === 'loading'}
470
+ hourlyNodeActivity={analytics.hourlyNodeActivity}
445
471
  />
446
472
  </>
447
473
  )}
@@ -64,6 +64,12 @@ const StoryKeepDashboard_Content = ({
64
64
  handleContentSubtabChange(tabId as any, setActiveContentTab);
65
65
  };
66
66
 
67
+ useEffect(() => {
68
+ if (createMenu) {
69
+ setActiveContentTab('manage');
70
+ }
71
+ }, [createMenu]);
72
+
67
73
  // Lightweight content summary fetch with retry logic
68
74
  useEffect(() => {
69
75
  let retryCount = 0;
@@ -0,0 +1,87 @@
1
+ ---
2
+ let MODE = 'logo'; // 'wordmark' | 'logo'
3
+
4
+ interface Props {
5
+ brandConfig: {
6
+ LOGO?: string;
7
+ WORDMARK?: string;
8
+ };
9
+ }
10
+
11
+ const { brandConfig } = Astro.props;
12
+
13
+ const getAssetPath = (
14
+ configPath: string | undefined,
15
+ fallback: string
16
+ ): string => {
17
+ if (configPath && configPath !== '') {
18
+ return configPath;
19
+ }
20
+ return fallback;
21
+ };
22
+
23
+ let assetUrl;
24
+ if (MODE === `wordmark`)
25
+ assetUrl = getAssetPath(brandConfig?.WORDMARK, '/brand/wordmark.svg');
26
+ else assetUrl = getAssetPath(brandConfig?.LOGO, '/brand/logo.svg');
27
+
28
+ // Generate positions programmatically for triple density
29
+ const generatePositions = () => {
30
+ const positions = [];
31
+ const rows = 15; // More rows to extend beyond boundaries
32
+ const cols = 12; // More cols to extend beyond boundaries
33
+
34
+ for (let row = 0; row < rows; row++) {
35
+ for (let col = 0; col < cols; col++) {
36
+ // Skip some positions for natural spacing
37
+ if ((row + col) % 3 !== 0) continue;
38
+
39
+ // Allow logos to extend beyond container edges (no margins)
40
+ const top = (row / (rows - 1)) * 120 - 10; // Extend 10% beyond top/bottom
41
+ const left = (col / (cols - 1)) * 120 - 10; // Extend 10% beyond left/right
42
+ const rotation = -45 + Math.random() * 90;
43
+
44
+ positions.push({
45
+ top: `${top}%`,
46
+ left: `${left}%`,
47
+ rotation: `${rotation}deg`,
48
+ });
49
+ }
50
+ }
51
+
52
+ return positions;
53
+ };
54
+
55
+ const logoPositions = generatePositions();
56
+ ---
57
+
58
+ {
59
+ assetUrl && (
60
+ <div
61
+ class="pointer-events-none absolute mr-6 overflow-hidden rounded-2xl p-1.5 md:p-3.5"
62
+ style={{
63
+ top: '2rem',
64
+ left: '64rem',
65
+ right: '0',
66
+ bottom: '3.5rem',
67
+ 'mix-blend-mode': 'multiply',
68
+ opacity: '0.07',
69
+ border: '2px dashed rgba(0, 0, 0, 1)',
70
+ }}
71
+ >
72
+ {logoPositions.map((position) => (
73
+ <img
74
+ src={assetUrl}
75
+ style={{
76
+ position: 'absolute',
77
+ top: position.top,
78
+ left: position.left,
79
+ width: '120px',
80
+ height: 'auto',
81
+ transform: `rotate(${position.rotation})`,
82
+ }}
83
+ />
84
+ ))}
85
+ </div>
86
+ )
87
+ }