astro-tractstack 2.3.2 → 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.
- package/bin/create-tractstack.js +3 -3
- package/dist/index.js +33 -8
- package/package.json +1 -1
- package/templates/custom/shopify/Cart.tsx +83 -14
- package/templates/custom/shopify/CheckoutModal.tsx +192 -6
- package/templates/custom/shopify/ShopifyCartManager.tsx +53 -41
- package/templates/custom/shopify/cart.astro +7 -1
- package/templates/src/components/Header.astro +3 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +219 -1
- package/templates/src/components/form/shopify/SchedulingSection.tsx +44 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +10 -1
- package/templates/src/components/storykeep/Dashboard_Shopify.tsx +9 -0
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +1 -0
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +80 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +86 -8
- package/templates/src/constants.ts +2 -0
- package/templates/src/pages/api/google/oauth/callback.ts +50 -0
- package/templates/src/pages/api/google/oauth/disconnect.ts +32 -0
- package/templates/src/pages/api/google/oauth/start.ts +32 -0
- package/templates/src/pages/api/google/oauth/status.ts +32 -0
- package/templates/src/pages/privacy.astro +84 -0
- package/templates/src/pages/terms.astro +47 -0
- package/templates/src/stores/shopify.ts +5 -0
- package/templates/src/types/tractstack.ts +30 -0
- package/templates/src/utils/api/advancedHelpers.ts +16 -0
- package/templates/src/utils/api/bookingHelpers.ts +3 -1
- package/templates/src/utils/api/brandHelpers.ts +8 -1
- package/templates/src/utils/booking/appointmentMode.ts +135 -0
- package/templates/src/utils/customHelpers.ts +2 -0
- package/utils/inject-files.ts +29 -4
- package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +0 -101
- package/templates/src/utils/actions/actionButton.ts +0 -103
- package/templates/src/utils/actions/preParse_Clicked.ts +0 -87
|
@@ -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
|
-
};
|