astro-tractstack 2.0.2 → 2.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -353,7 +353,7 @@ const EpinetWrapper = ({
353
353
  }
354
354
  >
355
355
  <div className="space-y-6">
356
- <div className="rounded-lg bg-white p-6 shadow">
356
+ <div className="rounded-lg bg-white p-2 shadow md:p-6">
357
357
  <div className="mb-4 flex items-center justify-between">
358
358
  {(isLoading || status === 'loading') && (
359
359
  <div className="flex items-center space-x-2 text-sm text-gray-500">
@@ -3,7 +3,8 @@ import * as d3 from 'd3';
3
3
  import { sankey, sankeyLinkHorizontal } from 'd3-sankey';
4
4
 
5
5
  const MAX_HEIGHT = 1600;
6
- const COMPRESSED_HEIGHT = 256; // Fixed height for compressed view
6
+ const COMPRESSED_HEIGHT = 256;
7
+ const MIN_DIAGRAM_WIDTH = 800; // Define a minimum width for the diagram
7
8
 
8
9
  const colors = [
9
10
  '#ef4444',
@@ -52,7 +53,7 @@ const SankeyDiagram = ({ data, isLoading = false }: SankeyDiagramProps) => {
52
53
  const containerRef = useRef<HTMLDivElement | null>(null);
53
54
  const hasScrolledRef = useRef(false);
54
55
  const [dimensions, setDimensions] = useState({
55
- width: 800,
56
+ width: MIN_DIAGRAM_WIDTH,
56
57
  height: 500,
57
58
  });
58
59
  const [isExpanded, setIsExpanded] = useState(false);
@@ -60,7 +61,11 @@ const SankeyDiagram = ({ data, isLoading = false }: SankeyDiagramProps) => {
60
61
  useEffect(() => {
61
62
  const updateDimensions = () => {
62
63
  if (containerRef.current) {
63
- const containerWidth = containerRef.current.offsetWidth;
64
+ // Ensure the diagram width is the larger of the container or our defined minimum
65
+ const containerWidth = Math.max(
66
+ MIN_DIAGRAM_WIDTH,
67
+ containerRef.current.offsetWidth
68
+ );
64
69
  const nodeCount = data.nodes.length || 1;
65
70
  const optimalHeight = nodeCount * (40 + 10) + 50;
66
71
  const finalHeight = Math.min(MAX_HEIGHT, optimalHeight);
@@ -245,7 +250,6 @@ const SankeyDiagram = ({ data, isLoading = false }: SankeyDiagramProps) => {
245
250
 
246
251
  return (
247
252
  <div ref={containerRef} className="relative w-full">
248
- {/* Expand/Compress Controls */}
249
253
  <div className="mb-3 flex items-center justify-between">
250
254
  <div className="text-sm text-gray-600">
251
255
  {data.nodes.length} nodes • {data.links.length} connections
@@ -292,23 +296,21 @@ const SankeyDiagram = ({ data, isLoading = false }: SankeyDiagramProps) => {
292
296
  </button>
293
297
  </div>
294
298
 
295
- {/* Compression Warning */}
296
299
  {needsCompression && (
297
300
  <div className="mb-2 rounded bg-amber-50 px-3 py-2 text-sm text-amber-800">
298
301
  <strong>Compressed view</strong> - click anywhere to expand!
299
302
  </div>
300
303
  )}
301
304
 
302
- {/* SVG Container - Clickable when compressed */}
303
305
  <div
304
- className={`transition-all duration-300 ${
306
+ className={`overflow-x-auto transition-all duration-300 md:overflow-visible ${
305
307
  needsCompression
306
308
  ? 'cursor-pointer hover:bg-gray-50 hover:shadow-md'
307
309
  : ''
308
310
  }`}
309
311
  style={{
310
312
  height: `${displayHeight}px`,
311
- overflow: 'hidden',
313
+ overflowY: 'hidden',
312
314
  }}
313
315
  onClick={needsCompression ? handleExpand : undefined}
314
316
  role={needsCompression ? 'button' : undefined}
@@ -333,7 +335,7 @@ const SankeyDiagram = ({ data, isLoading = false }: SankeyDiagramProps) => {
333
335
  height={dimensions.height}
334
336
  style={{
335
337
  display: 'block',
336
- width: '100%',
338
+ minWidth: `${dimensions.width}px`, // Ensure SVG itself doesn't shrink
337
339
  height: `${dimensions.height}px`,
338
340
  transform: needsCompression
339
341
  ? `scaleY(${displayHeight / dimensions.height})`
@@ -345,6 +347,10 @@ const SankeyDiagram = ({ data, isLoading = false }: SankeyDiagramProps) => {
345
347
  ></svg>
346
348
  </div>
347
349
 
350
+ <div className="mt-2 text-center text-xs text-gray-500 md:hidden">
351
+ &larr; Scroll to see full journey map &rarr;
352
+ </div>
353
+
348
354
  {isLoading && (
349
355
  <div className="absolute inset-0 flex items-center justify-center rounded bg-black bg-opacity-80">
350
356
  <div className="flex items-center space-x-2 text-white">
@@ -39,36 +39,25 @@ const FIELD_TYPES = [
39
39
  { value: 'image', label: 'Image' },
40
40
  ];
41
41
 
42
- const KnownResourceForm = ({
42
+ const KnownResourceFormRenderer = ({
43
43
  categorySlug,
44
44
  contentMap,
45
45
  onClose,
46
- }: KnownResourceFormProps) => {
46
+ brandConfig,
47
+ }: KnownResourceFormProps & { brandConfig: BrandConfig }) => {
47
48
  const [newFieldName, setNewFieldName] = useState('');
48
49
  const [showAddField, setShowAddField] = useState(false);
49
- const [brandConfig, setBrandConfig] = useState<BrandConfig | null>(null);
50
- const [loading, setLoading] = useState(false);
51
-
52
- useEffect(() => {
53
- if (!brandConfig && !loading) {
54
- setLoading(true);
55
- getBrandConfig(window.TRACTSTACK_CONFIG?.tenantId || 'default')
56
- .then(setBrandConfig)
57
- .catch(console.error)
58
- .finally(() => setLoading(false));
59
- }
60
- }, [brandConfig, loading]);
61
50
 
62
- const knownResources = brandConfig?.KNOWN_RESOURCES || {};
63
51
  const isCreate = categorySlug === 'new';
64
- const currentCategory = isCreate ? {} : knownResources[categorySlug] || {};
52
+ const knownResources = brandConfig.KNOWN_RESOURCES || {};
53
+ const currentCategory = isCreate ? {} : knownResources[categorySlug];
65
54
 
66
55
  const hasExistingResources =
67
56
  !isCreate && contentMap.some((item) => item.categorySlug === categorySlug);
68
57
 
69
58
  const initialState: KnownResourceState = {
70
59
  categorySlug: isCreate ? '' : categorySlug,
71
- fields: isCreate ? {} : currentCategory,
60
+ fields: currentCategory,
72
61
  };
73
62
 
74
63
  const validator = (state: KnownResourceState): FieldErrors => {
@@ -90,8 +79,6 @@ const KnownResourceForm = ({
90
79
  validator,
91
80
  onSave: async (data) => {
92
81
  try {
93
- // Update known resources in brand config
94
- if (!brandConfig) throw new Error('Brand config not loaded');
95
82
  const brandState = convertToLocalState(brandConfig);
96
83
  const updatedKnownResources = {
97
84
  ...brandState.knownResources,
@@ -108,7 +95,6 @@ const KnownResourceForm = ({
108
95
  updatedBrandState
109
96
  );
110
97
 
111
- // Call success callback after save (original pattern)
112
98
  setTimeout(() => {
113
99
  onClose?.(true);
114
100
  }, 1000);
@@ -179,7 +165,6 @@ const KnownResourceForm = ({
179
165
 
180
166
  return (
181
167
  <div className="space-y-8">
182
- {/* Header */}
183
168
  <div className="border-b border-gray-200 pb-4">
184
169
  <h2 className="text-2xl font-bold text-gray-900">
185
170
  {isCreate ? 'Create Resource Category' : `Edit ${categorySlug}`}
@@ -200,7 +185,6 @@ const KnownResourceForm = ({
200
185
  </div>
201
186
 
202
187
  <div className="space-y-6">
203
- {/* Category Name */}
204
188
  <StringInput
205
189
  label="Category Name"
206
190
  value={formState.state.categorySlug}
@@ -214,7 +198,6 @@ const KnownResourceForm = ({
214
198
  Must be lowercase with hyphens. Cannot be changed after creation.
215
199
  </p>
216
200
 
217
- {/* Fields Section */}
218
201
  <div className="space-y-6">
219
202
  <div className="flex items-center justify-between">
220
203
  <h3 className="text-lg font-bold text-gray-900">Fields</h3>
@@ -228,7 +211,6 @@ const KnownResourceForm = ({
228
211
  </button>
229
212
  </div>
230
213
 
231
- {/* Add Field Form */}
232
214
  {showAddField && (
233
215
  <div className="rounded-lg border border-gray-200 bg-gray-50 p-4">
234
216
  <h4 className="mb-3 text-sm font-bold text-gray-900">
@@ -264,7 +246,6 @@ const KnownResourceForm = ({
264
246
  </div>
265
247
  )}
266
248
 
267
- {/* Existing Fields */}
268
249
  {Object.keys(formState.state.fields).length === 0 ? (
269
250
  <div className="py-6 text-center text-gray-500">
270
251
  No fields defined yet. Click "Add Field" to create your first
@@ -275,7 +256,6 @@ const KnownResourceForm = ({
275
256
  {Object.entries(formState.state.fields).map(
276
257
  ([fieldName, fieldDef]) => {
277
258
  const locked = isFieldLocked(fieldName);
278
-
279
259
  return (
280
260
  <div
281
261
  key={fieldName}
@@ -304,7 +284,6 @@ const KnownResourceForm = ({
304
284
  </button>
305
285
  )}
306
286
  </div>
307
-
308
287
  <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
309
288
  <EnumSelect
310
289
  label="Type"
@@ -315,7 +294,6 @@ const KnownResourceForm = ({
315
294
  options={FIELD_TYPES}
316
295
  disabled={locked}
317
296
  />
318
-
319
297
  <BooleanToggle
320
298
  label="Optional"
321
299
  value={fieldDef.optional || false}
@@ -324,7 +302,6 @@ const KnownResourceForm = ({
324
302
  }
325
303
  disabled={locked}
326
304
  />
327
-
328
305
  {fieldDef.type === 'categoryReference' && (
329
306
  <EnumSelect
330
307
  label="Reference Category"
@@ -341,7 +318,6 @@ const KnownResourceForm = ({
341
318
  disabled={locked}
342
319
  />
343
320
  )}
344
-
345
321
  {fieldDef.type === 'number' && (
346
322
  <>
347
323
  <NumberInput
@@ -372,7 +348,6 @@ const KnownResourceForm = ({
372
348
  </div>
373
349
  </div>
374
350
 
375
- {/* Save/Cancel Bar */}
376
351
  <UnsavedChangesBar
377
352
  formState={formState}
378
353
  message="You have unsaved resource category changes"
@@ -380,7 +355,6 @@ const KnownResourceForm = ({
380
355
  cancelLabel="Discard Changes"
381
356
  />
382
357
 
383
- {/* Cancel Navigation Button */}
384
358
  <div className="flex justify-start">
385
359
  <button
386
360
  type="button"
@@ -394,4 +368,60 @@ const KnownResourceForm = ({
394
368
  );
395
369
  };
396
370
 
371
+ const KnownResourceForm = ({
372
+ categorySlug,
373
+ contentMap,
374
+ onClose,
375
+ }: KnownResourceFormProps) => {
376
+ const [brandConfig, setBrandConfig] = useState<BrandConfig | null>(null);
377
+ const [loading, setLoading] = useState(true);
378
+ const [error, setError] = useState<string | null>(null);
379
+
380
+ useEffect(() => {
381
+ getBrandConfig(window.TRACTSTACK_CONFIG?.tenantId || 'default')
382
+ .then(setBrandConfig)
383
+ .catch((err) => {
384
+ console.error('Failed to load brand configuration:', err);
385
+ setError(
386
+ 'Could not load resource category configuration. Please try again.'
387
+ );
388
+ })
389
+ .finally(() => setLoading(false));
390
+ }, []);
391
+
392
+ if (loading) {
393
+ return (
394
+ <div className="py-12 text-center text-gray-500">
395
+ Loading configuration...
396
+ </div>
397
+ );
398
+ }
399
+
400
+ if (error) {
401
+ return (
402
+ <div className="rounded-md bg-red-50 p-4 text-center text-red-700">
403
+ {error}
404
+ </div>
405
+ );
406
+ }
407
+
408
+ const isCreate = categorySlug === 'new';
409
+ if (!isCreate && !(brandConfig?.KNOWN_RESOURCES || {})[categorySlug]) {
410
+ return (
411
+ <div className="py-12 text-center text-gray-500">
412
+ Resource category "{categorySlug}" not found.
413
+ </div>
414
+ );
415
+ }
416
+
417
+ return (
418
+ <KnownResourceFormRenderer
419
+ categorySlug={categorySlug}
420
+ contentMap={contentMap}
421
+ onClose={onClose}
422
+ brandConfig={brandConfig!}
423
+ />
424
+ );
425
+ };
426
+
397
427
  export default KnownResourceForm;