astro-tractstack 2.3.1 → 2.3.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.
Files changed (73) hide show
  1. package/bin/create-tractstack.js +3 -3
  2. package/dist/index.js +69 -11
  3. package/package.json +1 -1
  4. package/templates/custom/shopify/Cart.tsx +99 -19
  5. package/templates/custom/shopify/CheckoutModal.tsx +196 -10
  6. package/templates/custom/shopify/ShopifyCartManager.tsx +79 -76
  7. package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
  8. package/templates/custom/shopify/ShopifyProductGrid.tsx +42 -14
  9. package/templates/custom/shopify/ShopifyServiceList.tsx +94 -50
  10. package/templates/custom/shopify/cart.astro +7 -1
  11. package/templates/src/components/Footer.astro +2 -2
  12. package/templates/src/components/Header.astro +17 -9
  13. package/templates/src/components/Menu.tsx +157 -135
  14. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  15. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +27 -6
  16. package/templates/src/components/codehooks/EpinetTableView.tsx +153 -112
  17. package/templates/src/components/codehooks/EpinetWrapper.tsx +4 -1
  18. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +8 -1
  19. package/templates/src/components/codehooks/ProductCardSetup.tsx +9 -1
  20. package/templates/src/components/codehooks/ProductGridSetup.tsx +9 -1
  21. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +2 -1
  22. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
  23. package/templates/src/components/edit/ToolBar.tsx +2 -1
  24. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +2 -2
  25. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +13 -0
  26. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
  27. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  28. package/templates/src/components/edit/state/SaveModal.tsx +1 -1
  29. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +8 -3
  30. package/templates/src/components/form/DateTimeInput.tsx +10 -3
  31. package/templates/src/components/form/FileUpload.tsx +11 -5
  32. package/templates/src/components/form/NumberInput.tsx +2 -2
  33. package/templates/src/components/form/advanced/APIConfigSection.tsx +221 -39
  34. package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
  35. package/templates/src/components/form/shopify/SchedulingSection.tsx +44 -0
  36. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +10 -1
  37. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +16 -8
  38. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
  39. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +1 -0
  40. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +2 -2
  41. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +79 -51
  42. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +80 -0
  43. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -0
  44. package/templates/src/components/storykeep/email-builder/Blocks.tsx +169 -0
  45. package/templates/src/components/storykeep/email-builder/EmailBuilder.tsx +223 -0
  46. package/templates/src/components/storykeep/email-builder/PreviewModal.tsx +136 -0
  47. package/templates/src/components/storykeep/email-builder/PropertyPanel.tsx +154 -0
  48. package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +1 -8
  49. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +118 -14
  50. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -0
  51. package/templates/src/constants.ts +2 -0
  52. package/templates/src/layouts/Layout.astro +8 -5
  53. package/templates/src/pages/api/google/oauth/callback.ts +50 -0
  54. package/templates/src/pages/api/google/oauth/disconnect.ts +32 -0
  55. package/templates/src/pages/api/google/oauth/start.ts +32 -0
  56. package/templates/src/pages/api/google/oauth/status.ts +32 -0
  57. package/templates/src/pages/privacy.astro +84 -0
  58. package/templates/src/pages/terms.astro +47 -0
  59. package/templates/src/stores/shopify.ts +21 -0
  60. package/templates/src/types/formTypes.ts +4 -2
  61. package/templates/src/types/tractstack.ts +35 -2
  62. package/templates/src/utils/api/advancedHelpers.ts +16 -0
  63. package/templates/src/utils/api/bookingHelpers.ts +3 -1
  64. package/templates/src/utils/api/brandConfig.ts +2 -0
  65. package/templates/src/utils/api/brandHelpers.ts +24 -1
  66. package/templates/src/utils/api/emailHelpers.ts +105 -0
  67. package/templates/src/utils/booking/appointmentMode.ts +135 -0
  68. package/templates/src/utils/customHelpers.ts +2 -0
  69. package/templates/src/utils/tenantResolver.ts +1 -1
  70. package/utils/inject-files.ts +63 -5
  71. package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +0 -101
  72. package/templates/src/utils/actions/actionButton.ts +0 -103
  73. package/templates/src/utils/actions/preParse_Clicked.ts +0 -87
@@ -0,0 +1,135 @@
1
+ import type { ResourceNode } from '@/types/compositorTypes';
2
+ import type { CartItemState } from '@/stores/shopify';
3
+
4
+ export type AppointmentMode = 'IN_PERSON' | 'REMOTE';
5
+
6
+ export type AppointmentSchedulingInput = {
7
+ allowRemote?: boolean;
8
+ remoteOnly?: boolean;
9
+ };
10
+
11
+ export type AppointmentModeConstraints = {
12
+ serviceResources: ResourceNode[];
13
+ anyServiceRemoteOnly: boolean;
14
+ someServiceForcesInPerson: boolean;
15
+ allServicesAllowRemote: boolean;
16
+ effectiveRemoteOnly: boolean;
17
+ effectiveAllowRemote: boolean;
18
+ remoteAvailable: boolean;
19
+ inPersonAvailable: boolean;
20
+ hasImpossibleRemoteMix: boolean;
21
+ /** Same as `remoteAvailable`; kept for cart naming parity */
22
+ canRemote: boolean;
23
+ };
24
+
25
+ /**
26
+ * Collects service resources involved in booking (same rules as Cart / CheckoutModal).
27
+ */
28
+ export function collectBookingServiceResources(
29
+ cart: Record<string, CartItemState>,
30
+ resources: ResourceNode[]
31
+ ): ResourceNode[] {
32
+ const dedupe = new Map<string, ResourceNode>();
33
+ for (const item of Object.values(cart)) {
34
+ const resource = resources.find((r) => r.id === item.resourceId);
35
+ if (
36
+ resource &&
37
+ (resource.categorySlug === 'service' ||
38
+ resource.optionsPayload?.bookingLengthMinutes)
39
+ ) {
40
+ dedupe.set(resource.id, resource);
41
+ }
42
+ if (item.boundResourceId) {
43
+ const bound = resources.find((r) => r.id === item.boundResourceId);
44
+ if (bound) {
45
+ dedupe.set(bound.id, bound);
46
+ }
47
+ }
48
+ }
49
+ return Array.from(dedupe.values());
50
+ }
51
+
52
+ /**
53
+ * Returns true if the cart would mix remote-only services with in-person-only services.
54
+ */
55
+ export function wouldCartHaveImpossibleRemoteMix(
56
+ nextCart: Record<string, CartItemState>,
57
+ resources: ResourceNode[]
58
+ ): boolean {
59
+ const svc = collectBookingServiceResources(nextCart, resources);
60
+ const anyRemoteOnly = svc.some((r) => Boolean(r.optionsPayload?.remoteOnly));
61
+ const someInPersonOnly = svc.some(
62
+ (r) =>
63
+ !Boolean(r.optionsPayload?.remoteOnly) &&
64
+ !Boolean(r.optionsPayload?.allowRemote)
65
+ );
66
+ return anyRemoteOnly && someInPersonOnly;
67
+ }
68
+
69
+ /**
70
+ * Single source of truth for tenant + per-service remote eligibility.
71
+ */
72
+ export function deriveAppointmentConstraints(
73
+ cart: Record<string, CartItemState>,
74
+ resources: ResourceNode[],
75
+ tenantScheduling: AppointmentSchedulingInput
76
+ ): AppointmentModeConstraints {
77
+ const serviceResources = collectBookingServiceResources(cart, resources);
78
+ const allowRemote = Boolean(tenantScheduling.allowRemote);
79
+ const tenantRemoteOnly = Boolean(tenantScheduling.remoteOnly);
80
+
81
+ const anyServiceRemoteOnly = serviceResources.some((r) =>
82
+ Boolean(r.optionsPayload?.remoteOnly)
83
+ );
84
+ const someServiceForcesInPerson = serviceResources.some(
85
+ (r) =>
86
+ !Boolean(r.optionsPayload?.remoteOnly) &&
87
+ !Boolean(r.optionsPayload?.allowRemote)
88
+ );
89
+ const allServicesAllowRemote =
90
+ serviceResources.length === 0 ||
91
+ serviceResources.every(
92
+ (r) =>
93
+ Boolean(r.optionsPayload?.remoteOnly) ||
94
+ Boolean(r.optionsPayload?.allowRemote)
95
+ );
96
+
97
+ const effectiveRemoteOnly = tenantRemoteOnly || anyServiceRemoteOnly;
98
+ const effectiveAllowRemote = allowRemote || tenantRemoteOnly;
99
+
100
+ const remoteAvailable =
101
+ effectiveRemoteOnly ||
102
+ (effectiveAllowRemote &&
103
+ serviceResources.length > 0 &&
104
+ allServicesAllowRemote);
105
+
106
+ const inPersonAvailable = !effectiveRemoteOnly;
107
+ const hasImpossibleRemoteMix =
108
+ anyServiceRemoteOnly && someServiceForcesInPerson;
109
+
110
+ return {
111
+ serviceResources,
112
+ anyServiceRemoteOnly,
113
+ someServiceForcesInPerson,
114
+ allServicesAllowRemote,
115
+ effectiveRemoteOnly,
116
+ effectiveAllowRemote,
117
+ remoteAvailable,
118
+ inPersonAvailable,
119
+ hasImpossibleRemoteMix,
120
+ canRemote: remoteAvailable,
121
+ };
122
+ }
123
+
124
+ export function pickInitialAppointmentMode(
125
+ c: AppointmentModeConstraints,
126
+ currentPreferred: AppointmentMode
127
+ ): AppointmentMode {
128
+ if (c.effectiveRemoteOnly) {
129
+ return 'REMOTE';
130
+ }
131
+ if (currentPreferred === 'REMOTE' && c.remoteAvailable) {
132
+ return 'REMOTE';
133
+ }
134
+ return 'IN_PERSON';
135
+ }
@@ -53,6 +53,8 @@ export const RESTRICTION_MESSAGES = {
53
53
  TERMS: 'Please review the terms for this item before adding it to your cart.',
54
54
  MAX_DURATION: (max: number) =>
55
55
  `You cannot book more than ${max} minutes of services in one session.`,
56
+ INCOMPATIBLE_REMOTE:
57
+ 'This service cannot be combined with the services already in your cart. Some require remote-only delivery while others can only be delivered in person.',
56
58
  DEFAULT_ADD: (title: string) => `${title} has been added to your cart.`,
57
59
  };
58
60
 
@@ -1,4 +1,4 @@
1
- const VERBOSE = false;
1
+ const VERBOSE = true;
2
2
 
3
3
  interface TenantResolution {
4
4
  id: string;
@@ -753,10 +753,6 @@ export async function injectTemplateFiles(
753
753
  src: resolve('../templates/src/utils/actions/preParse_Action.ts'),
754
754
  dest: 'src/utils/actions/preParse_Action.ts',
755
755
  },
756
- {
757
- src: resolve('../templates/src/utils/actions/preParse_Clicked.ts'),
758
- dest: 'src/utils/actions/preParse_Clicked.ts',
759
- },
760
756
  {
761
757
  src: resolve('../templates/src/utils/actions/preParse_Impression.ts'),
762
758
  dest: 'src/utils/actions/preParse_Impression.ts',
@@ -900,6 +896,14 @@ export async function injectTemplateFiles(
900
896
  src: resolve('../templates/custom/shopify/cart.astro'),
901
897
  dest: 'src/pages/cart.astro',
902
898
  },
899
+ {
900
+ src: resolve('../templates/src/pages/privacy.astro'),
901
+ dest: 'src/pages/privacy.astro',
902
+ },
903
+ {
904
+ src: resolve('../templates/src/pages/terms.astro'),
905
+ dest: 'src/pages/terms.astro',
906
+ },
903
907
  {
904
908
  src: resolve('../templates/src/pages/404.astro'),
905
909
  dest: 'src/pages/404.astro',
@@ -1000,6 +1004,22 @@ export async function injectTemplateFiles(
1000
1004
  src: resolve('../templates/src/pages/api/auth/logout.ts'),
1001
1005
  dest: 'src/pages/api/auth/logout.ts',
1002
1006
  },
1007
+ {
1008
+ src: resolve('../templates/src/pages/api/google/oauth/start.ts'),
1009
+ dest: 'src/pages/api/google/oauth/start.ts',
1010
+ },
1011
+ {
1012
+ src: resolve('../templates/src/pages/api/google/oauth/status.ts'),
1013
+ dest: 'src/pages/api/google/oauth/status.ts',
1014
+ },
1015
+ {
1016
+ src: resolve('../templates/src/pages/api/google/oauth/disconnect.ts'),
1017
+ dest: 'src/pages/api/google/oauth/disconnect.ts',
1018
+ },
1019
+ {
1020
+ src: resolve('../templates/src/pages/api/google/oauth/callback.ts'),
1021
+ dest: 'src/pages/api/google/oauth/callback.ts',
1022
+ },
1003
1023
  {
1004
1024
  src: resolve('../templates/src/pages/api/orphan-analysis.ts'),
1005
1025
  dest: 'src/pages/api/orphan-analysis.ts',
@@ -1258,6 +1278,36 @@ export async function injectTemplateFiles(
1258
1278
  ),
1259
1279
  dest: 'src/components/storykeep/shopify/ShopifyDashboard_Services.tsx',
1260
1280
  },
1281
+ {
1282
+ src: resolve(
1283
+ '../templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx'
1284
+ ),
1285
+ dest: 'src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx',
1286
+ },
1287
+ {
1288
+ src: resolve(
1289
+ '../templates/src/components/storykeep/email-builder/EmailBuilder.tsx'
1290
+ ),
1291
+ dest: 'src/components/storykeep/email-builder/EmailBuilder.tsx',
1292
+ },
1293
+ {
1294
+ src: resolve(
1295
+ '../templates/src/components/storykeep/email-builder/Blocks.tsx'
1296
+ ),
1297
+ dest: 'src/components/storykeep/email-builder/Blocks.tsx',
1298
+ },
1299
+ {
1300
+ src: resolve(
1301
+ '../templates/src/components/storykeep/email-builder/PropertyPanel.tsx'
1302
+ ),
1303
+ dest: 'src/components/storykeep/email-builder/PropertyPanel.tsx',
1304
+ },
1305
+ {
1306
+ src: resolve(
1307
+ '../templates/src/components/storykeep/email-builder/PreviewModal.tsx'
1308
+ ),
1309
+ dest: 'src/components/storykeep/email-builder/PreviewModal.tsx',
1310
+ },
1261
1311
  {
1262
1312
  src: resolve(
1263
1313
  '../templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx'
@@ -2267,6 +2317,10 @@ export async function injectTemplateFiles(
2267
2317
  src: resolve('../templates/src/utils/api/setupHelpers.ts'),
2268
2318
  dest: 'src/utils/api/setupHelpers.ts',
2269
2319
  },
2320
+ {
2321
+ src: resolve('../templates/src/utils/api/emailHelpers.ts'),
2322
+ dest: 'src/utils/api/emailHelpers.ts',
2323
+ },
2270
2324
  {
2271
2325
  src: resolve(
2272
2326
  '../templates/src/components/storykeep/widgets/HydrateWizard.tsx'
@@ -2317,6 +2371,11 @@ export async function injectTemplateFiles(
2317
2371
  dest: 'src/utils/customHelpers.ts',
2318
2372
  protected: true,
2319
2373
  },
2374
+ {
2375
+ src: resolve('../templates/src/utils/booking/appointmentMode.ts'),
2376
+ dest: 'src/utils/booking/appointmentMode.ts',
2377
+ protected: true,
2378
+ },
2320
2379
  {
2321
2380
  src: resolve('../templates/custom/shopify/ShopifyProductGrid.tsx'),
2322
2381
  dest: 'src/custom/shopify/ShopifyProductGrid.tsx',
@@ -2465,7 +2524,6 @@ function createPlaceholder(filePath: string): string {
2465
2524
 
2466
2525
  if (filePath.endsWith('.tsx')) {
2467
2526
  return `// TractStack placeholder component
2468
- import React from 'react';
2469
2527
  export default function Placeholder() {
2470
2528
  return <div>TractStack placeholder: ${filePath}</div>;
2471
2529
  }`;
@@ -1,101 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { Dialog } from '@ark-ui/react/dialog';
3
- import { Portal } from '@ark-ui/react/portal';
4
- import XMarkIcon from '@heroicons/react/24/solid/XMarkIcon';
5
- import { ProfileStorage } from '@/utils/profileStorage';
6
- import SandboxRegisterForm from '@/components/codehooks/SandboxRegisterForm';
7
-
8
- interface SandboxAuthWrapperProps {
9
- isServerSideAuthenticated: boolean;
10
- }
11
-
12
- export default function SandboxAuthWrapper({
13
- isServerSideAuthenticated,
14
- }: SandboxAuthWrapperProps) {
15
- const [profileExists, setProfileExists] = useState<boolean | null>(null);
16
-
17
- useEffect(() => {
18
- const hasLocalProfile = ProfileStorage.hasProfile();
19
-
20
- if (hasLocalProfile && !isServerSideAuthenticated) {
21
- const token = localStorage.getItem('tractstack_profile_token');
22
-
23
- if (token) {
24
- ProfileStorage.storeProfileToken(token);
25
- window.location.reload();
26
- return;
27
- } else {
28
- ProfileStorage.clearProfile();
29
- setProfileExists(false);
30
- }
31
- } else {
32
- setProfileExists(hasLocalProfile);
33
- }
34
- }, [isServerSideAuthenticated]);
35
-
36
- const handleRegistrationSuccess = () => {
37
- setProfileExists(true);
38
- window.location.reload();
39
- };
40
-
41
- const handleClose = () => {
42
- window.location.href = '/';
43
- };
44
-
45
- if (profileExists === true && isServerSideAuthenticated) {
46
- return null;
47
- }
48
-
49
- if (profileExists === null) {
50
- return null;
51
- }
52
-
53
- return (
54
- <Dialog.Root open={true} modal={true} trapFocus={false}>
55
- <Portal>
56
- <Dialog.Backdrop
57
- className="fixed inset-0 bg-black bg-opacity-75"
58
- style={{ zIndex: 9005 }}
59
- />
60
- <Dialog.Positioner
61
- className="fixed inset-0 flex items-center justify-center p-4"
62
- style={{ zIndex: 9005 }}
63
- >
64
- <Dialog.Content className="relative grid w-full max-w-6xl grid-cols-1 overflow-hidden rounded-lg bg-white shadow-2xl md:grid-cols-2">
65
- <button
66
- onClick={handleClose}
67
- className="absolute right-4 top-4 z-10 rounded-full bg-gray-100 p-2 text-gray-600 shadow-sm transition-colors hover:bg-gray-200"
68
- title="Close and exit Sandbox"
69
- >
70
- <XMarkIcon className="h-5 w-5" />
71
- </button>
72
-
73
- <div className="flex flex-col justify-center bg-gray-50 p-8 text-right">
74
- <h2 className="text-4xl font-bold text-gray-900 md:text-5xl">
75
- Press <span className="italic text-blue-600">your own</span>{' '}
76
- Tract Stack
77
- </h2>
78
- <p className="mt-4 text-lg text-gray-600">
79
- Create an interactive webpage in a sandbox! No credit card
80
- required.
81
- </p>
82
- <p className="mt-8 text-sm text-gray-500">
83
- Already connected?{' '}
84
- <a
85
- href="/storykeep/profile"
86
- className="font-bold text-blue-600 underline hover:text-blue-500"
87
- >
88
- Unlock your profile
89
- </a>
90
- </p>
91
- </div>
92
-
93
- <div className="flex flex-col justify-center p-8">
94
- <SandboxRegisterForm onSuccess={handleRegistrationSuccess} />
95
- </div>
96
- </Dialog.Content>
97
- </Dialog.Positioner>
98
- </Portal>
99
- </Dialog.Root>
100
- );
101
- }
@@ -1,103 +0,0 @@
1
- import { preParseClicked } from './preParse_Clicked';
2
- import type { BrandConfig } from '@/types/tractstack';
3
-
4
- interface ActionButtonParams {
5
- callbackPayload: any;
6
- targetUrl: string;
7
- paneId: string;
8
- config: BrandConfig;
9
- }
10
-
11
- // Import the sendAnalyticsEvent function to send events to backend
12
- async function sendAnalyticsEvent(event: {
13
- contentId: string;
14
- contentType: 'Pane' | 'StoryFragment';
15
- eventVerb: string;
16
- duration?: number;
17
- }): Promise<void> {
18
- try {
19
- const config = window.TRACTSTACK_CONFIG;
20
- if (!config || !config.sessionId) return;
21
- const backendUrl =
22
- import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
23
-
24
- const sessionId = config.sessionId;
25
- const formData: { [key: string]: string } = {
26
- beliefId: event.contentId,
27
- beliefType: event.contentType,
28
- beliefValue: event.eventVerb,
29
- paneId: '',
30
- };
31
-
32
- if (event.duration !== undefined) {
33
- formData.duration = event.duration.toString();
34
- }
35
-
36
- await fetch(`${backendUrl}/api/v1/state`, {
37
- method: 'POST',
38
- headers: {
39
- 'Content-Type': 'application/x-www-form-urlencoded',
40
- 'X-Tenant-ID': config.tenantId,
41
- 'X-TractStack-Session-ID': sessionId,
42
- 'X-StoryFragment-ID': config.storyfragmentId,
43
- },
44
- body: new URLSearchParams(formData),
45
- });
46
- } catch (error) {
47
- console.error('⛔ API ERROR: Analytics event failed', error, event);
48
- }
49
- }
50
-
51
- export function handleActionButtonClick({
52
- callbackPayload,
53
- targetUrl,
54
- paneId,
55
- config,
56
- }: ActionButtonParams): void {
57
- const event = preParseClicked(paneId, callbackPayload, config);
58
-
59
- if (event) {
60
- console.log(event);
61
- sendAnalyticsEvent({
62
- contentId: event.targetId || event.targetSlug || event.id,
63
- contentType: 'Pane',
64
- eventVerb: event.verb,
65
- });
66
- }
67
-
68
- // Handle URL navigation and scroll
69
- if (targetUrl.startsWith('#') || targetUrl.includes('#')) {
70
- const id = targetUrl.split('#')[1];
71
- const element = document.getElementById(id);
72
-
73
- if (element) {
74
- // Calculate the target position
75
- const elementRect = element.getBoundingClientRect();
76
- const targetPosition = elementRect.top + window.scrollY;
77
-
78
- // Perform smooth scroll
79
- window.scrollTo({
80
- top: targetPosition,
81
- behavior: 'smooth',
82
- });
83
-
84
- // After scrolling, ensure the page layout is preserved
85
- const checkScrollEnd = setInterval(() => {
86
- if (
87
- window.scrollY === targetPosition ||
88
- Math.abs(window.scrollY - targetPosition) < 2
89
- ) {
90
- clearInterval(checkScrollEnd);
91
- document.body.style.minHeight = `${Math.max(
92
- document.body.scrollHeight,
93
- document.documentElement.scrollHeight
94
- )}px`;
95
- }
96
- }, 100);
97
- } else {
98
- window.location.href = targetUrl;
99
- }
100
- } else {
101
- window.location.href = targetUrl;
102
- }
103
- }
@@ -1,87 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { BrandConfig } from '@/types/tractstack';
3
-
4
- export const preParseClicked = (
5
- id: string,
6
- payload: any,
7
- config: BrandConfig
8
- ) => {
9
- const thisPayload = (payload && payload[0]) || false;
10
-
11
- if (!thisPayload || !config?.HOME_SLUG) {
12
- return null;
13
- }
14
-
15
- const command = (thisPayload && thisPayload[0] && thisPayload[0][0]) || null;
16
- const parameters =
17
- (thisPayload && thisPayload[0] && thisPayload[0][1]) || null;
18
-
19
- if (command === 'bunnyMoment' && parameters) {
20
- const videoId = parameters[0];
21
- return {
22
- id: id,
23
- type: `StartVideoMoment`,
24
- verb: `WATCHED`,
25
- targetId: videoId || null,
26
- };
27
- }
28
-
29
- if (command === `goto` && parameters) {
30
- const parameterOne = parameters[0] || null;
31
- const parameterTwo = parameters[1] || null;
32
- //const parameterThree = parameters[2] || null;
33
-
34
- switch (parameterOne) {
35
- case `home`:
36
- return {
37
- id: id,
38
- type: `PaneClicked`,
39
- verb: `CLICKED`,
40
- targetSlug: config?.HOME_SLUG,
41
- };
42
-
43
- case `storyFragment`:
44
- case `storyFragmentPane`:
45
- return {
46
- id: id,
47
- type: `PaneClicked`,
48
- verb: `CLICKED`,
49
- targetSlug: parameterTwo,
50
- };
51
-
52
- case `bunny`:
53
- return {
54
- id: id,
55
- type: `StartVideo`,
56
- verb: `WATCHED`,
57
- targetSlug: parameterTwo,
58
- };
59
-
60
- case `sandbox`:
61
- return {
62
- id: id,
63
- type: `SandboxAction`,
64
- verb: `CLICKED`,
65
- targetSlug: parameterTwo || 'main',
66
- };
67
-
68
- case `storykeep`:
69
- case `context`:
70
- case `concierge`:
71
- case `product`:
72
- case `url`:
73
- // ignore these
74
- break;
75
-
76
- default:
77
- console.log(
78
- `LispActionPayload preParseEvent misfire`,
79
- command,
80
- parameters
81
- );
82
- break;
83
- }
84
- }
85
-
86
- return null;
87
- };