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.
- package/bin/create-tractstack.js +7 -4
- package/dist/index.js +51 -8
- package/package.json +1 -1
- package/templates/custom/shopify/Cart.tsx +279 -118
- package/templates/custom/shopify/CartIcon.tsx +8 -8
- package/templates/custom/shopify/CheckoutModal.tsx +328 -65
- package/templates/custom/shopify/ShopifyCartManager.tsx +117 -60
- package/templates/custom/shopify/ShopifyCheckout.tsx +2 -26
- package/templates/custom/shopify/ShopifyProductGrid.tsx +8 -0
- package/templates/custom/shopify/ShopifyServiceList.tsx +25 -37
- package/templates/custom/shopify/cart.astro +7 -1
- package/templates/src/components/Header.astro +4 -2
- package/templates/src/components/compositor/Node.tsx +39 -9
- package/templates/src/components/compositor/nodes/CreativePane.tsx +175 -88
- package/templates/src/components/edit/pane/AddPanePanel.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +6 -5
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +9 -15
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +249 -4
- package/templates/src/components/form/brand/SiteConfigSection.tsx +9 -0
- package/templates/src/components/form/shopify/SchedulingSection.tsx +44 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +66 -21
- package/templates/src/components/storykeep/Dashboard_Shopify.tsx +266 -18
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +1 -0
- package/templates/src/components/storykeep/controls/content/ProductTable.tsx +38 -24
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +240 -65
- package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +175 -48
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +91 -10
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Sales.tsx +479 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +7 -3
- package/templates/src/constants.ts +2 -0
- package/templates/src/layouts/Layout.astro +26 -0
- package/templates/src/pages/api/auth/logout.ts +35 -2
- 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/api/sales/list.ts +66 -0
- package/templates/src/pages/api/sales/metrics.ts +60 -0
- package/templates/src/pages/context/[...contextSlug].astro +50 -31
- package/templates/src/pages/privacy.astro +84 -0
- package/templates/src/pages/storykeep/advanced.astro +4 -1
- package/templates/src/pages/terms.astro +47 -0
- package/templates/src/stores/nodes.ts +8 -0
- package/templates/src/stores/shopify.ts +5 -0
- package/templates/src/types/tractstack.ts +87 -0
- package/templates/src/utils/api/advancedConfig.ts +2 -1
- package/templates/src/utils/api/advancedHelpers.ts +20 -0
- package/templates/src/utils/api/bookingHelpers.ts +3 -1
- package/templates/src/utils/api/brandConfig.ts +2 -0
- package/templates/src/utils/api/brandHelpers.ts +14 -1
- package/templates/src/utils/api/salesHelpers.ts +21 -0
- package/templates/src/utils/booking/appointmentMode.ts +135 -0
- package/templates/src/utils/customHelpers.ts +287 -2
- package/utils/inject-files.ts +47 -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
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
@@ -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
|
|