astro-tractstack 2.3.2 → 2.3.4

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 (58) hide show
  1. package/bin/create-tractstack.js +7 -4
  2. package/dist/index.js +51 -8
  3. package/package.json +1 -1
  4. package/templates/custom/shopify/Cart.tsx +279 -118
  5. package/templates/custom/shopify/CartIcon.tsx +8 -8
  6. package/templates/custom/shopify/CheckoutModal.tsx +328 -65
  7. package/templates/custom/shopify/ShopifyCartManager.tsx +117 -60
  8. package/templates/custom/shopify/ShopifyCheckout.tsx +2 -26
  9. package/templates/custom/shopify/ShopifyProductGrid.tsx +8 -0
  10. package/templates/custom/shopify/ShopifyServiceList.tsx +25 -37
  11. package/templates/custom/shopify/cart.astro +7 -1
  12. package/templates/src/components/Header.astro +4 -2
  13. package/templates/src/components/compositor/Node.tsx +39 -9
  14. package/templates/src/components/compositor/nodes/CreativePane.tsx +175 -88
  15. package/templates/src/components/edit/pane/AddPanePanel.tsx +2 -2
  16. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +6 -5
  17. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +9 -15
  18. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  19. package/templates/src/components/form/advanced/APIConfigSection.tsx +249 -4
  20. package/templates/src/components/form/brand/SiteConfigSection.tsx +9 -0
  21. package/templates/src/components/form/shopify/SchedulingSection.tsx +44 -0
  22. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +66 -21
  23. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +266 -18
  24. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +1 -0
  25. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +38 -24
  26. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +240 -65
  27. package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +175 -48
  28. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +91 -10
  29. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Sales.tsx +479 -0
  30. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +7 -3
  31. package/templates/src/constants.ts +2 -0
  32. package/templates/src/layouts/Layout.astro +26 -0
  33. package/templates/src/pages/api/auth/logout.ts +35 -2
  34. package/templates/src/pages/api/google/oauth/callback.ts +50 -0
  35. package/templates/src/pages/api/google/oauth/disconnect.ts +32 -0
  36. package/templates/src/pages/api/google/oauth/start.ts +32 -0
  37. package/templates/src/pages/api/google/oauth/status.ts +32 -0
  38. package/templates/src/pages/api/sales/list.ts +66 -0
  39. package/templates/src/pages/api/sales/metrics.ts +60 -0
  40. package/templates/src/pages/context/[...contextSlug].astro +50 -31
  41. package/templates/src/pages/privacy.astro +84 -0
  42. package/templates/src/pages/storykeep/advanced.astro +4 -1
  43. package/templates/src/pages/terms.astro +47 -0
  44. package/templates/src/stores/nodes.ts +8 -0
  45. package/templates/src/stores/shopify.ts +5 -0
  46. package/templates/src/types/tractstack.ts +87 -0
  47. package/templates/src/utils/api/advancedConfig.ts +2 -1
  48. package/templates/src/utils/api/advancedHelpers.ts +20 -0
  49. package/templates/src/utils/api/bookingHelpers.ts +3 -1
  50. package/templates/src/utils/api/brandConfig.ts +2 -0
  51. package/templates/src/utils/api/brandHelpers.ts +14 -1
  52. package/templates/src/utils/api/salesHelpers.ts +21 -0
  53. package/templates/src/utils/booking/appointmentMode.ts +135 -0
  54. package/templates/src/utils/customHelpers.ts +287 -2
  55. package/utils/inject-files.ts +47 -4
  56. package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +0 -101
  57. package/templates/src/utils/actions/actionButton.ts +0 -103
  58. package/templates/src/utils/actions/preParse_Clicked.ts +0 -87
@@ -303,6 +303,27 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
303
303
  <script is:inline is:persist>
304
304
  let navProgressInterval = null;
305
305
  let navSafetyTimeout = null;
306
+ const IN_SITE_FROM_KEY = 'tractstack:inSiteFrom';
307
+ let isInSiteNavigationTrackingBound = false;
308
+
309
+ const navEntry = performance.getEntriesByType('navigation')[0];
310
+ if (navEntry?.type === 'navigate' && history.length <= 1) {
311
+ sessionStorage.removeItem(IN_SITE_FROM_KEY);
312
+ }
313
+
314
+ function setupInSiteNavigationTracking() {
315
+ if (isInSiteNavigationTrackingBound) {
316
+ return;
317
+ }
318
+
319
+ document.addEventListener('astro:before-preparation', () => {
320
+ sessionStorage.setItem(
321
+ IN_SITE_FROM_KEY,
322
+ location.pathname + location.search + location.hash
323
+ );
324
+ });
325
+ isInSiteNavigationTrackingBound = true;
326
+ }
306
327
 
307
328
  function startNavLoadingAnimation() {
308
329
  const loadingIndicator = document.getElementById('loading-indicator');
@@ -402,8 +423,13 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
402
423
 
403
424
  if (document.readyState === 'loading') {
404
425
  document.addEventListener('DOMContentLoaded', setupNavigationLoading);
426
+ document.addEventListener(
427
+ 'DOMContentLoaded',
428
+ setupInSiteNavigationTracking
429
+ );
405
430
  } else {
406
431
  setupNavigationLoading();
432
+ setupInSiteNavigationTracking();
407
433
  }
408
434
 
409
435
  document.addEventListener('astro:page-load', setupNavigationLoading);
@@ -1,6 +1,9 @@
1
1
  import type { APIRoute } from '@/types/astro';
2
2
 
3
- export const POST: APIRoute = async ({ cookies, url }) => {
3
+ export const POST: APIRoute = async ({ cookies, url, locals }) => {
4
+ const tenantId =
5
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
6
+
4
7
  try {
5
8
  const isLocalhost =
6
9
  url.hostname === 'localhost' || url.hostname === '127.0.0.1';
@@ -15,6 +18,36 @@ export const POST: APIRoute = async ({ cookies, url }) => {
15
18
  cookies.delete('admin_auth', cookieOptions);
16
19
  cookies.delete('editor_auth', cookieOptions);
17
20
 
21
+ const responseHeaders = new Headers({
22
+ 'Content-Type': 'application/json',
23
+ });
24
+
25
+ const backendUrl =
26
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
27
+
28
+ const controller = new AbortController();
29
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
30
+
31
+ try {
32
+ const backendResponse = await fetch(`${backendUrl}/api/v1/auth/logout`, {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ 'X-Tenant-ID': tenantId,
37
+ },
38
+ signal: controller.signal,
39
+ });
40
+
41
+ clearTimeout(timeoutId);
42
+
43
+ for (const setCookie of backendResponse.headers.getSetCookie()) {
44
+ responseHeaders.append('Set-Cookie', setCookie);
45
+ }
46
+ } catch (fetchError) {
47
+ clearTimeout(timeoutId);
48
+ console.error('Logout backend request failed:', fetchError);
49
+ }
50
+
18
51
  return new Response(
19
52
  JSON.stringify({
20
53
  success: true,
@@ -22,7 +55,7 @@ export const POST: APIRoute = async ({ cookies, url }) => {
22
55
  }),
23
56
  {
24
57
  status: 200,
25
- headers: { 'Content-Type': 'application/json' },
58
+ headers: responseHeaders,
26
59
  }
27
60
  );
28
61
  } catch (error) {
@@ -0,0 +1,50 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+
3
+ export const GET: APIRoute = async ({ request, locals, url }) => {
4
+ const GO_BACKEND =
5
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
+ const tenantId =
7
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
8
+
9
+ const forwardedProto =
10
+ request.headers.get('x-forwarded-proto') || url.protocol.replace(':', '');
11
+ const forwardedHost = request.headers.get('x-forwarded-host') || url.host;
12
+
13
+ const response = await fetch(
14
+ `${GO_BACKEND}/api/v1/google/oauth/callback${url.search}`,
15
+ {
16
+ method: 'GET',
17
+ redirect: 'manual',
18
+ headers: {
19
+ 'X-Tenant-ID': tenantId,
20
+ 'X-Forwarded-Proto': forwardedProto,
21
+ 'X-Forwarded-Host': forwardedHost,
22
+ ...(request.headers.get('Authorization') && {
23
+ Authorization: request.headers.get('Authorization')!,
24
+ }),
25
+ ...(request.headers.get('Cookie') && {
26
+ Cookie: request.headers.get('Cookie')!,
27
+ }),
28
+ },
29
+ }
30
+ );
31
+
32
+ const location = response.headers.get('location');
33
+ if (location) {
34
+ const frontendOrigin = `${forwardedProto}://${forwardedHost}`;
35
+ const redirectUrl = new URL(location, frontendOrigin).toString();
36
+ return Response.redirect(
37
+ redirectUrl,
38
+ response.status >= 300 && response.status < 400 ? response.status : 302
39
+ );
40
+ }
41
+
42
+ const body = await response.text();
43
+ return new Response(body, {
44
+ status: response.status,
45
+ headers: {
46
+ 'Content-Type':
47
+ response.headers.get('Content-Type') || 'application/json',
48
+ },
49
+ });
50
+ };
@@ -0,0 +1,32 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+
3
+ export const POST: APIRoute = async ({ request, locals, url }) => {
4
+ const GO_BACKEND =
5
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
+ const tenantId =
7
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
8
+ const forwardedProto =
9
+ request.headers.get('x-forwarded-proto') || url.protocol.replace(':', '');
10
+ const forwardedHost = request.headers.get('x-forwarded-host') || url.host;
11
+
12
+ const response = await fetch(`${GO_BACKEND}/api/v1/google/oauth/disconnect`, {
13
+ method: 'POST',
14
+ headers: {
15
+ 'X-Tenant-ID': tenantId,
16
+ 'X-Forwarded-Proto': forwardedProto,
17
+ 'X-Forwarded-Host': forwardedHost,
18
+ ...(request.headers.get('Authorization') && {
19
+ Authorization: request.headers.get('Authorization')!,
20
+ }),
21
+ ...(request.headers.get('Cookie') && {
22
+ Cookie: request.headers.get('Cookie')!,
23
+ }),
24
+ },
25
+ });
26
+
27
+ const data = await response.text();
28
+ return new Response(data, {
29
+ status: response.status,
30
+ headers: { 'Content-Type': 'application/json' },
31
+ });
32
+ };
@@ -0,0 +1,32 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+
3
+ export const GET: APIRoute = async ({ request, locals, url }) => {
4
+ const GO_BACKEND =
5
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
+ const tenantId =
7
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
8
+ const forwardedProto =
9
+ request.headers.get('x-forwarded-proto') || url.protocol.replace(':', '');
10
+ const forwardedHost = request.headers.get('x-forwarded-host') || url.host;
11
+
12
+ const response = await fetch(`${GO_BACKEND}/api/v1/google/oauth/start`, {
13
+ method: 'GET',
14
+ headers: {
15
+ 'X-Tenant-ID': tenantId,
16
+ 'X-Forwarded-Proto': forwardedProto,
17
+ 'X-Forwarded-Host': forwardedHost,
18
+ ...(request.headers.get('Authorization') && {
19
+ Authorization: request.headers.get('Authorization')!,
20
+ }),
21
+ ...(request.headers.get('Cookie') && {
22
+ Cookie: request.headers.get('Cookie')!,
23
+ }),
24
+ },
25
+ });
26
+
27
+ const data = await response.text();
28
+ return new Response(data, {
29
+ status: response.status,
30
+ headers: { 'Content-Type': 'application/json' },
31
+ });
32
+ };
@@ -0,0 +1,32 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+
3
+ export const GET: APIRoute = async ({ request, locals, url }) => {
4
+ const GO_BACKEND =
5
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
+ const tenantId =
7
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
8
+ const forwardedProto =
9
+ request.headers.get('x-forwarded-proto') || url.protocol.replace(':', '');
10
+ const forwardedHost = request.headers.get('x-forwarded-host') || url.host;
11
+
12
+ const response = await fetch(`${GO_BACKEND}/api/v1/google/oauth/status`, {
13
+ method: 'GET',
14
+ headers: {
15
+ 'X-Tenant-ID': tenantId,
16
+ 'X-Forwarded-Proto': forwardedProto,
17
+ 'X-Forwarded-Host': forwardedHost,
18
+ ...(request.headers.get('Authorization') && {
19
+ Authorization: request.headers.get('Authorization')!,
20
+ }),
21
+ ...(request.headers.get('Cookie') && {
22
+ Cookie: request.headers.get('Cookie')!,
23
+ }),
24
+ },
25
+ });
26
+
27
+ const data = await response.text();
28
+ return new Response(data, {
29
+ status: response.status,
30
+ headers: { 'Content-Type': 'application/json' },
31
+ });
32
+ };
@@ -0,0 +1,66 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+ import { getAdminToken } from '@/utils/auth';
3
+
4
+ export const GET: APIRoute = async (context) => {
5
+ const { request, locals } = context;
6
+ const GO_BACKEND =
7
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
8
+
9
+ try {
10
+ const url = new URL(request.url);
11
+ const searchParams = url.searchParams;
12
+
13
+ const controller = new AbortController();
14
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
15
+ const tenantId =
16
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
17
+ const token = getAdminToken(context);
18
+
19
+ try {
20
+ const response = await fetch(
21
+ `${GO_BACKEND}/api/v1/sales/list?${searchParams.toString()}`,
22
+ {
23
+ method: 'GET',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ 'X-Tenant-ID': tenantId,
27
+ ...(token && { Authorization: `Bearer ${token}` }),
28
+ ...(request.headers.get('Authorization') && {
29
+ Authorization: request.headers.get('Authorization')!,
30
+ }),
31
+ },
32
+ signal: controller.signal,
33
+ }
34
+ );
35
+
36
+ clearTimeout(timeoutId);
37
+ const data = await response.json();
38
+
39
+ return new Response(JSON.stringify(data), {
40
+ status: response.status,
41
+ headers: { 'Content-Type': 'application/json' },
42
+ });
43
+ } catch (fetchError) {
44
+ clearTimeout(timeoutId);
45
+ if (fetchError instanceof Error && fetchError.name === 'AbortError') {
46
+ return new Response(
47
+ JSON.stringify({
48
+ success: false,
49
+ error: 'Sales list lookup timeout',
50
+ }),
51
+ { status: 408, headers: { 'Content-Type': 'application/json' } }
52
+ );
53
+ }
54
+ throw fetchError;
55
+ }
56
+ } catch (error) {
57
+ console.error('Sales list API proxy error:', error);
58
+ return new Response(
59
+ JSON.stringify({
60
+ success: false,
61
+ error: 'Failed to connect to backend service',
62
+ }),
63
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
64
+ );
65
+ }
66
+ };
@@ -0,0 +1,60 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+ import { getAdminToken } from '@/utils/auth';
3
+
4
+ export const GET: APIRoute = async (context) => {
5
+ const { request, locals } = context;
6
+ const GO_BACKEND =
7
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
8
+
9
+ try {
10
+ const controller = new AbortController();
11
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
12
+ const tenantId =
13
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
14
+ const token = getAdminToken(context);
15
+
16
+ try {
17
+ const response = await fetch(`${GO_BACKEND}/api/v1/sales/metrics`, {
18
+ method: 'GET',
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ 'X-Tenant-ID': tenantId,
22
+ ...(token && { Authorization: `Bearer ${token}` }),
23
+ ...(request.headers.get('Authorization') && {
24
+ Authorization: request.headers.get('Authorization')!,
25
+ }),
26
+ },
27
+ signal: controller.signal,
28
+ });
29
+
30
+ clearTimeout(timeoutId);
31
+ const data = await response.json();
32
+
33
+ return new Response(JSON.stringify(data), {
34
+ status: response.status,
35
+ headers: { 'Content-Type': 'application/json' },
36
+ });
37
+ } catch (fetchError) {
38
+ clearTimeout(timeoutId);
39
+ if (fetchError instanceof Error && fetchError.name === 'AbortError') {
40
+ return new Response(
41
+ JSON.stringify({
42
+ success: false,
43
+ error: 'Sales metrics lookup timeout',
44
+ }),
45
+ { status: 408, headers: { 'Content-Type': 'application/json' } }
46
+ );
47
+ }
48
+ throw fetchError;
49
+ }
50
+ } catch (error) {
51
+ console.error('Sales metrics API proxy error:', error);
52
+ return new Response(
53
+ JSON.stringify({
54
+ success: false,
55
+ error: 'Failed to connect to backend service',
56
+ }),
57
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
58
+ );
59
+ }
60
+ };
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
- import CodeHook from '@/custom/CodeHook.astro';
4
3
  import { getBrandConfig } from '@/utils/api/brandConfig';
5
4
  import { handleFailedResponse } from '@/utils/backend';
6
- import { getFullContentMap } from '@/stores/analytics';
7
5
  import { preHealthCheck } from '@/utils/backend';
8
6
 
9
7
  const tenantId =
@@ -58,8 +56,6 @@ try {
58
56
 
59
57
  const paneId = contextPaneData.id;
60
58
  const paneTitle = contextPaneData.title || 'Context';
61
- const codeHookTarget = contextPaneData.codeHookTarget || null;
62
- const resourcesPayload = storyData.resourcesPayload || {};
63
59
 
64
60
  // Get rendered fragment for the context pane
65
61
  let fragmentData = '';
@@ -93,8 +89,6 @@ try {
93
89
  );
94
90
  }
95
91
 
96
- // Get supporting data
97
- const fullContentMap = await getFullContentMap(tenantId);
98
92
  const brandConfig = await getBrandConfig(tenantId);
99
93
 
100
94
  if (!brandConfig.SITE_INIT) {
@@ -122,18 +116,7 @@ if (!brandConfig.SITE_INIT) {
122
116
  hx-trigger="refresh"
123
117
  hx-swap="innerHTML scroll:none"
124
118
  >
125
- {
126
- codeHookTarget ? (
127
- <CodeHook
128
- target={codeHookTarget}
129
- paneId={paneId}
130
- resourcesPayload={resourcesPayload}
131
- fullContentMap={fullContentMap}
132
- />
133
- ) : (
134
- <Fragment set:html={fragmentData} />
135
- )
136
- }
119
+ <Fragment set:html={fragmentData} />
137
120
  </div>
138
121
 
139
122
  <div class="py-12 text-center text-2xl md:text-3xl">
@@ -148,20 +131,56 @@ if (!brandConfig.SITE_INIT) {
148
131
  </Layout>
149
132
 
150
133
  <script>
151
- document.addEventListener('DOMContentLoaded', function () {
134
+ const IN_SITE_FROM_KEY = 'tractstack:inSiteFrom';
135
+
136
+ function getInSiteFrom() {
137
+ const rawFromPath = sessionStorage.getItem(IN_SITE_FROM_KEY);
138
+ if (!rawFromPath) {
139
+ return null;
140
+ }
141
+
142
+ try {
143
+ const fromUrl = new URL(rawFromPath, window.location.origin);
144
+ if (fromUrl.origin !== window.location.origin) {
145
+ return null;
146
+ }
147
+
148
+ const fromPath = fromUrl.pathname + fromUrl.search + fromUrl.hash;
149
+ const currentPath =
150
+ window.location.pathname +
151
+ window.location.search +
152
+ window.location.hash;
153
+
154
+ if (fromPath === currentPath) {
155
+ return null;
156
+ }
157
+
158
+ return fromPath;
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ function setupContextCloseButton() {
152
165
  const closeBtn = document.getElementById('context-close-btn');
153
166
 
154
- if (closeBtn) {
155
- closeBtn.addEventListener('click', function () {
156
- // Check if we have history to go back to
157
- if (window.history.length > 1 && document.referrer) {
158
- // Go back to previous page
159
- window.history.back();
160
- } else {
161
- // Fallback to home page
162
- window.location.href = '/';
163
- }
164
- });
167
+ if (!closeBtn) {
168
+ return;
165
169
  }
166
- });
170
+
171
+ const fromPath = getInSiteFrom();
172
+ closeBtn.textContent = fromPath ? 'Close' : 'Home';
173
+
174
+ closeBtn.onclick = function () {
175
+ if (fromPath) {
176
+ window.location.assign(fromPath);
177
+ return;
178
+ }
179
+
180
+ window.location.assign('/');
181
+ };
182
+ }
183
+
184
+ setupContextCloseButton();
185
+ document.addEventListener('astro:page-load', setupContextCloseButton);
167
186
  </script>
@@ -0,0 +1,84 @@
1
+ ---
2
+ const effectiveDate = '2026-05-03';
3
+ ---
4
+
5
+ <!doctype html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="UTF-8" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <title>Privacy Policy</title>
11
+ </head>
12
+ <body>
13
+ <main
14
+ style="margin: 0 auto; max-width: 48rem; padding: 2rem 1rem; font-family: Inter, Arial, sans-serif; line-height: 1.6;"
15
+ >
16
+ <h1>Privacy Policy</h1>
17
+ <p><strong>Effective date:</strong> {effectiveDate}</p>
18
+ <p>
19
+ This site is built with TractStack and is designed to minimize personal
20
+ data collection while supporting operational analytics.
21
+ </p>
22
+
23
+ <h2>What We Collect</h2>
24
+ <ul>
25
+ <li>
26
+ Pseudonymous usage and activity events for site operation and content
27
+ performance.
28
+ </li>
29
+ <li>
30
+ Session identifiers used for continuity, including session, visit, and
31
+ fingerprint IDs.
32
+ </li>
33
+ <li>
34
+ Optional profile/contact details only when you explicitly provide
35
+ them.
36
+ </li>
37
+ </ul>
38
+
39
+ <h2>How We Use Data</h2>
40
+ <ul>
41
+ <li>To operate site behavior, booking flows, and communications.</li>
42
+ <li>To measure page and interaction performance.</li>
43
+ <li>
44
+ To provide profile-based personalization when a profile is created.
45
+ </li>
46
+ </ul>
47
+
48
+ <h2>Consent and Controls</h2>
49
+ <ul>
50
+ <li>
51
+ Consent controls profile memory and preference retention in your
52
+ browser.
53
+ </li>
54
+ <li>
55
+ Operational session and interaction tracking may occur even when full
56
+ profile consent is not enabled.
57
+ </li>
58
+ <li>
59
+ You can revoke consent from the profile/session controls on this site;
60
+ this clears browser-stored session/profile data for this browser.
61
+ </li>
62
+ </ul>
63
+
64
+ <h2>Retention</h2>
65
+ <p>
66
+ Revoking consent clears local browser storage for session/profile data,
67
+ but previously recorded server-side operational logs and analytics
68
+ events may still exist according to site retention policy.
69
+ </p>
70
+
71
+ <h2>Consent Signal</h2>
72
+ <p>
73
+ The consent signal sent during session handshake can be present, denied,
74
+ or unknown depending on client state.
75
+ </p>
76
+
77
+ <h2>Contact</h2>
78
+ <p>
79
+ For privacy questions, contact the site owner using the contact details
80
+ provided on this website.
81
+ </p>
82
+ </main>
83
+ </body>
84
+ </html>
@@ -64,7 +64,10 @@ try {
64
64
  role={role}
65
65
  initializing={initializing}
66
66
  />
67
- <StoryKeepDashboard_Advanced client:only="react" />
67
+ <StoryKeepDashboard_Advanced
68
+ client:only="react"
69
+ brandConfig={brandConfig}
70
+ />
68
71
  </div>
69
72
  </main>
70
73
  </Layout>
@@ -0,0 +1,47 @@
1
+ ---
2
+ const effectiveDate = '2026-05-03';
3
+ ---
4
+
5
+ <!doctype html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="UTF-8" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <title>Terms of Service</title>
11
+ </head>
12
+ <body>
13
+ <main
14
+ style="margin: 0 auto; max-width: 48rem; padding: 2rem 1rem; font-family: Inter, Arial, sans-serif; line-height: 1.6;"
15
+ >
16
+ <h1>Terms of Service</h1>
17
+ <p><strong>Effective date:</strong> {effectiveDate}</p>
18
+
19
+ <h2>Acceptance of Terms</h2>
20
+ <p>By using this website and its services, you agree to these terms.</p>
21
+
22
+ <h2>Use of Service</h2>
23
+ <p>
24
+ You agree not to misuse the website, attempt unauthorized access, or
25
+ interfere with normal operation.
26
+ </p>
27
+
28
+ <h2>Bookings and Communications</h2>
29
+ <p>
30
+ If this site offers booking services, confirmations and related
31
+ communications are provided as operational notifications.
32
+ </p>
33
+
34
+ <h2>Disclaimer</h2>
35
+ <p>
36
+ Services are provided on an “as is” basis, without warranties except as
37
+ required by law.
38
+ </p>
39
+
40
+ <h2>Contact</h2>
41
+ <p>
42
+ For terms questions, contact the site owner using details provided on
43
+ this website.
44
+ </p>
45
+ </main>
46
+ </body>
47
+ </html>
@@ -1876,6 +1876,14 @@ export class NodesContext {
1876
1876
  if (ownerNode && 'slug' in ownerNode && typeof ownerNode.slug === `string`)
1877
1877
  duplicatedPane.slug = ownerNode.slug;
1878
1878
 
1879
+ this.deleteChildren(ownerId);
1880
+
1881
+ if (pane.htmlAst) {
1882
+ duplicatedPane.htmlAst = pane.htmlAst;
1883
+ } else {
1884
+ delete duplicatedPane.htmlAst;
1885
+ }
1886
+
1879
1887
  // Track all nodes that need to be added
1880
1888
  // Call the new helper to process markdown, gridLayout, and bgPane
1881
1889
  const allNodes: BaseNode[] = this._processPaneTemplate(
@@ -227,6 +227,10 @@ export const transactionTraceId = persistentAtom<string>(
227
227
  'tractstack_shopify_trace_id',
228
228
  ''
229
229
  );
230
+ export const preferredAppointmentMode = persistentAtom<'IN_PERSON' | 'REMOTE'>(
231
+ 'tractstack_shopify_appointment_mode',
232
+ 'IN_PERSON'
233
+ );
230
234
 
231
235
  export interface CustomerDetails {
232
236
  name: string;
@@ -262,6 +266,7 @@ export function clearCommerceState() {
262
266
  leadId: '',
263
267
  });
264
268
  transactionTraceId.set('');
269
+ preferredAppointmentMode.set('IN_PERSON');
265
270
  cartState.set(CART_STATES.READY);
266
271
  }
267
272