astro-tractstack 2.0.41 → 2.0.43

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 (48) hide show
  1. package/dist/index.js +8 -2
  2. package/package.json +1 -1
  3. package/templates/src/components/compositor/Node.tsx +4 -1
  4. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +5 -1
  5. package/templates/src/components/edit/SettingsPanel.tsx +1 -3
  6. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +6 -10
  7. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +6 -2
  8. package/templates/src/components/edit/pane/PanePanel_path.tsx +4 -3
  9. package/templates/src/components/edit/panels/StyleParentPanel.tsx +0 -2
  10. package/templates/src/components/edit/state/SaveModal.tsx +250 -79
  11. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +27 -16
  12. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +5 -7
  13. package/templates/src/components/edit/widgets/BeliefWidget.tsx +4 -1
  14. package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +5 -1
  15. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +5 -1
  16. package/templates/src/components/edit/widgets/ToggleWidget.tsx +4 -1
  17. package/templates/src/components/fields/BackgroundImage.tsx +4 -1
  18. package/templates/src/components/fields/ImageUpload.tsx +4 -1
  19. package/templates/src/components/form/ActionBuilderField.tsx +5 -1
  20. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +4 -2
  21. package/templates/src/components/storykeep/state/BrandingWrapper.tsx +13 -1
  22. package/templates/src/components/storykeep/widgets/HydrateWizard.tsx +84 -0
  23. package/templates/src/components/storykeep/widgets/{SetupWizard.tsx → InitWizard.tsx} +4 -3
  24. package/templates/src/components/widgets/Impression.tsx +3 -1
  25. package/templates/src/hooks/useSearch.ts +5 -3
  26. package/templates/src/layouts/Layout.astro +1 -23
  27. package/templates/src/pages/[...slug]/edit.astro +0 -1
  28. package/templates/src/pages/api/auth/decode.ts +2 -4
  29. package/templates/src/pages/api/auth/login.ts +4 -5
  30. package/templates/src/pages/api/auth/logout.ts +22 -7
  31. package/templates/src/pages/api/auth/profile.ts +4 -2
  32. package/templates/src/pages/api/sandbox.ts +3 -5
  33. package/templates/src/pages/api/tailwind.ts +6 -9
  34. package/templates/src/pages/storykeep/branding.astro +18 -1
  35. package/templates/src/pages/storykeep/init.astro +25 -23
  36. package/templates/src/stores/analytics.ts +5 -14
  37. package/templates/src/stores/nodes.ts +1 -6
  38. package/templates/src/stores/orphanAnalysis.ts +5 -40
  39. package/templates/src/types/compositorTypes.ts +1 -1
  40. package/templates/src/types/tractstack.ts +2 -0
  41. package/templates/src/utils/actions/actionButton.ts +3 -1
  42. package/templates/src/utils/api/brandHelpers.ts +1 -0
  43. package/templates/src/utils/api/setupHelpers.ts +177 -20
  44. package/templates/src/utils/api.ts +14 -26
  45. package/templates/src/utils/compositor/nodesHelper.ts +5 -1
  46. package/templates/src/utils/layout.ts +85 -75
  47. package/templates/src/utils/tenantResolver.ts +1 -1
  48. package/utils/inject-files.ts +8 -2
@@ -39,20 +39,20 @@ const StoryFragmentMenuPanel = ({
39
39
  const [selectedMenu, setSelectedMenu] = useState<MenuNode | null>(null);
40
40
  const [showMenuEditor, setShowMenuEditor] = useState(false);
41
41
  const [contentMap, setContentMap] = useState<FullContentMapItem[]>([]);
42
+ const tenantId =
43
+ window.TRACTSTACK_CONFIG?.tenantId ||
44
+ import.meta.env.PUBLIC_TENANTID ||
45
+ 'default';
42
46
 
43
47
  useEffect(() => {
44
48
  const fetchData = async () => {
45
49
  try {
46
- const api = new TractStackAPI();
50
+ const api = new TractStackAPI(tenantId);
47
51
 
48
52
  // Get current content map first if we haven't already
49
53
  if (!contentMap) {
50
54
  const currentContentMap = await api.getContentMapWithTimestamp();
51
55
  if (currentContentMap.success && currentContentMap.data) {
52
- const tenantId =
53
- window.TRACTSTACK_CONFIG?.tenantId ||
54
- import.meta.env.PUBLIC_TENANTID ||
55
- 'default';
56
56
  fullContentMapStore.set(tenantId, currentContentMap.data);
57
57
  setContentMap(currentContentMap.data.data);
58
58
  }
@@ -240,8 +240,6 @@ const StoryFragmentMenuPanel = ({
240
240
  setShowMenuEditor(false);
241
241
  if (saved) {
242
242
  try {
243
- const tenantId =
244
- import.meta.env.PUBLIC_TENANTID || 'default';
245
243
  const refreshedContentMap =
246
244
  await getFullContentMap(tenantId);
247
245
  setContentMap(refreshedContentMap);
@@ -14,6 +14,10 @@ export default function BeliefWidget({ node, onUpdate }: BeliefWidgetProps) {
14
14
  const [currentScaleType, setCurrentScaleType] = useState<string>('');
15
15
  const [currentPrompt, setCurrentPrompt] = useState<string>('');
16
16
  const [isInitialized, setIsInitialized] = useState(false);
17
+ const tenantId =
18
+ window.TRACTSTACK_CONFIG?.tenantId ||
19
+ import.meta.env.PUBLIC_TENANTID ||
20
+ 'default';
17
21
 
18
22
  // Get parameter metadata from the widgetMeta constant
19
23
  const widgetInfo = widgetMeta.belief;
@@ -43,7 +47,6 @@ export default function BeliefWidget({ node, onUpdate }: BeliefWidgetProps) {
43
47
  try {
44
48
  const goBackend =
45
49
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
46
- const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
47
50
 
48
51
  // Step 1: Get all belief IDs
49
52
  const idsResponse = await fetch(`${goBackend}/api/v1/nodes/beliefs`, {
@@ -16,6 +16,10 @@ export default function IdentifyAsWidget({
16
16
  const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
17
17
  const [targetValues, setTargetValues] = useState<string[]>([]);
18
18
  const [currentPrompt, setCurrentPrompt] = useState<string>('');
19
+ const tenantId =
20
+ window.TRACTSTACK_CONFIG?.tenantId ||
21
+ import.meta.env.PUBLIC_TENANTID ||
22
+ 'default';
19
23
 
20
24
  // Sync state with node.codeHookParams
21
25
  useEffect(() => {
@@ -54,7 +58,7 @@ export default function IdentifyAsWidget({
54
58
  useEffect(() => {
55
59
  const fetchData = async () => {
56
60
  try {
57
- const api = new TractStackAPI();
61
+ const api = new TractStackAPI(tenantId);
58
62
 
59
63
  // Step 1: Get all belief IDs
60
64
  const idsResponse = await api.get('/api/v1/nodes/beliefs');
@@ -253,6 +253,10 @@ export default function InteractiveDisclosureWidget({
253
253
  node,
254
254
  onUpdate,
255
255
  }: InteractiveDisclosureWidgetProps) {
256
+ const tenantId =
257
+ window.TRACTSTACK_CONFIG?.tenantId ||
258
+ import.meta.env.PUBLIC_TENANTID ||
259
+ 'default';
256
260
  const [mode, setMode] = useState<'belief' | 'open'>('belief');
257
261
  const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
258
262
  const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
@@ -367,7 +371,7 @@ export default function InteractiveDisclosureWidget({
367
371
  useEffect(() => {
368
372
  const fetchData = async () => {
369
373
  try {
370
- const api = new TractStackAPI();
374
+ const api = new TractStackAPI(tenantId);
371
375
  const {
372
376
  data: { beliefIds },
373
377
  } = await api.get('/api/v1/nodes/beliefs');
@@ -14,6 +14,10 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
14
14
  const [currentPrompt, setCurrentPrompt] = useState<string>('');
15
15
  const [currentScale, setCurrentScale] = useState<string>('');
16
16
  const [isInitialized, setIsInitialized] = useState(false);
17
+ const tenantId =
18
+ window.TRACTSTACK_CONFIG?.tenantId ||
19
+ import.meta.env.PUBLIC_TENANTID ||
20
+ 'default';
17
21
 
18
22
  const widgetInfo = widgetMeta.toggle;
19
23
 
@@ -38,7 +42,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
38
42
  try {
39
43
  const goBackend =
40
44
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
41
- const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
42
45
 
43
46
  const idsResponse = await fetch(`${goBackend}/api/v1/nodes/beliefs`, {
44
47
  headers: {
@@ -42,13 +42,16 @@ const BackgroundImage = ({ paneId, onUpdate }: BackgroundImageProps) => {
42
42
  desktop: false,
43
43
  });
44
44
  const [localAltDescription, setLocalAltDescription] = useState<string>('');
45
+ const tenantId =
46
+ window.TRACTSTACK_CONFIG?.tenantId ||
47
+ import.meta.env.PUBLIC_TENANTID ||
48
+ 'default';
45
49
 
46
50
  useEffect(() => {
47
51
  const loadFiles = async () => {
48
52
  try {
49
53
  const goBackend =
50
54
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
51
- const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
52
55
 
53
56
  // First, get all file IDs
54
57
  const idsResponse = await fetch(`${goBackend}/api/v1/nodes/files`, {
@@ -48,6 +48,10 @@ export const ImageUpload = ({
48
48
  const fileInputRef = useRef<HTMLInputElement>(null);
49
49
  const comboboxRef = useRef<HTMLDivElement>(null);
50
50
  const { openAbove, maxHeight } = useDropdownDirection(comboboxRef);
51
+ const tenantId =
52
+ window.TRACTSTACK_CONFIG?.tenantId ||
53
+ import.meta.env.PUBLIC_TENANTID ||
54
+ 'default';
51
55
 
52
56
  // Find the current image node
53
57
  const currentImageNode = nodeId
@@ -61,7 +65,6 @@ export const ImageUpload = ({
61
65
  try {
62
66
  const goBackend =
63
67
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
64
- const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
65
68
 
66
69
  // First, get all file IDs
67
70
  const idsResponse = await fetch(`${goBackend}/api/v1/nodes/files`, {
@@ -46,11 +46,15 @@ export default function ActionBuilderField({
46
46
  const [command, setCommand] = useState<ActionCommand>('goto');
47
47
  const [params, setParams] = useState('');
48
48
  const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
49
+ const tenantId =
50
+ window.TRACTSTACK_CONFIG?.tenantId ||
51
+ import.meta.env.PUBLIC_TENANTID ||
52
+ 'default';
49
53
 
50
54
  useEffect(() => {
51
55
  const fetchData = async () => {
52
56
  try {
53
- const api = new TractStackAPI();
57
+ const api = new TractStackAPI(tenantId);
54
58
  const {
55
59
  data: { beliefIds },
56
60
  } = await api.get('/api/v1/nodes/beliefs');
@@ -178,14 +178,16 @@ export default function StoryKeepDashboard_Analytics({
178
178
  setIsDownloading(true);
179
179
 
180
180
  const config = window.TRACTSTACK_CONFIG;
181
+ const backendUrl =
182
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
181
183
  const response = await fetch(
182
- `${config?.backendUrl || ''}/api/v1/admin/leads/download`,
184
+ `${backendUrl}/api/v1/admin/leads/download`,
183
185
  {
184
186
  method: 'GET',
185
187
  headers: {
186
188
  'X-Tenant-ID': config?.tenantId || 'default',
187
189
  },
188
- credentials: 'include', // Include cookies for auth
190
+ credentials: 'include',
189
191
  }
190
192
  );
191
193
 
@@ -1,7 +1,9 @@
1
1
  import { useState } from 'react';
2
2
  import StoryKeepDashboard from '../Dashboard';
3
3
  import StoryKeepDashboard_Branding from '../Dashboard_Branding';
4
+ import HydrateWizard from '../widgets/HydrateWizard';
4
5
  import type { FullContentMapItem, BrandConfig } from '@/types/tractstack';
6
+ import type { LoadData } from '@/types/compositorTypes';
5
7
 
6
8
  interface BrandingPageWrapperProps {
7
9
  fullContentMap: FullContentMapItem[];
@@ -9,6 +11,7 @@ interface BrandingPageWrapperProps {
9
11
  role: string | null;
10
12
  initializing: boolean;
11
13
  initialBrandConfig: BrandConfig;
14
+ initialSuitcase?: LoadData | null;
12
15
  }
13
16
 
14
17
  export default function BrandingPageWrapper({
@@ -17,11 +20,20 @@ export default function BrandingPageWrapper({
17
20
  role,
18
21
  initializing,
19
22
  initialBrandConfig,
23
+ initialSuitcase,
20
24
  }: BrandingPageWrapperProps) {
21
- // Manage shared brandConfig state at this level
22
25
  const [brandConfig, setBrandConfig] =
23
26
  useState<BrandConfig>(initialBrandConfig);
24
27
 
28
+ if (initialBrandConfig.HAS_HYDRATION_TOKEN && initialSuitcase) {
29
+ return (
30
+ <HydrateWizard
31
+ initialSuitcase={initialSuitcase}
32
+ fullContentMap={fullContentMap}
33
+ />
34
+ );
35
+ }
36
+
25
37
  return (
26
38
  <>
27
39
  <StoryKeepDashboard
@@ -0,0 +1,84 @@
1
+ import { useEffect, useState, useRef } from 'react';
2
+ import { prepareHydrationContext } from '@/utils/api/setupHelpers';
3
+ import SaveModal from '@/components/edit/state/SaveModal';
4
+ import type { FullContentMapItem } from '@/types/tractstack';
5
+ import type { LoadData } from '@/types/compositorTypes';
6
+
7
+ interface Props {
8
+ initialSuitcase: LoadData;
9
+ fullContentMap: FullContentMapItem[];
10
+ }
11
+
12
+ export default function HydrateWizard({
13
+ initialSuitcase,
14
+ fullContentMap,
15
+ }: Props) {
16
+ const [status, setStatus] = useState<'preparing' | 'ready' | 'error'>(
17
+ 'preparing'
18
+ );
19
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
20
+ const hasInitialized = useRef(false);
21
+
22
+ useEffect(() => {
23
+ if (hasInitialized.current) return;
24
+ hasInitialized.current = true;
25
+
26
+ const timer = setTimeout(() => {
27
+ try {
28
+ prepareHydrationContext(initialSuitcase, fullContentMap);
29
+ setStatus('ready');
30
+ } catch (err) {
31
+ console.error('Hydration preparation failed:', err);
32
+ setErrorMessage(err instanceof Error ? err.message : String(err));
33
+ setStatus('error');
34
+ }
35
+ }, 100);
36
+
37
+ return () => clearTimeout(timer);
38
+ }, []);
39
+
40
+ if (status === 'error') {
41
+ return (
42
+ <div className="mx-auto max-w-2xl p-6 text-center">
43
+ <div className="rounded-lg bg-white p-12 shadow-lg">
44
+ <h2 className="text-xl font-bold text-red-600">Setup Failed</h2>
45
+ <p className="mt-2 text-gray-600">{errorMessage}</p>
46
+ <div className="mt-6">
47
+ <a
48
+ href="/storykeep"
49
+ className="rounded bg-gray-200 px-4 py-2 text-gray-800 hover:bg-gray-300"
50
+ >
51
+ Return to Dashboard
52
+ </a>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ if (status === 'ready') {
60
+ return (
61
+ <SaveModal
62
+ show={true}
63
+ slug="hello"
64
+ isContext={false}
65
+ onClose={() => {}}
66
+ hydrate={true}
67
+ />
68
+ );
69
+ }
70
+
71
+ return (
72
+ <div className="mx-auto max-w-2xl p-6 text-center">
73
+ <div className="rounded-lg bg-white p-12 shadow-lg">
74
+ <div className="mb-4 flex h-16 justify-center">
75
+ <div className="h-12 w-12 animate-spin rounded-full border-4 border-gray-200 border-t-cyan-600"></div>
76
+ </div>
77
+ <h2 className="text-2xl font-bold text-gray-900">
78
+ Preparing installation...
79
+ </h2>
80
+ <p className="mt-2 text-gray-600">Unpacking suitcase content</p>
81
+ </div>
82
+ </div>
83
+ );
84
+ }
@@ -9,7 +9,7 @@ import {
9
9
  initializeSystem,
10
10
  } from '@/utils/api/setupHelpers';
11
11
 
12
- export default function SetupWizard() {
12
+ export default function InitWizard() {
13
13
  const formState = useFormState({
14
14
  initialData: initialSetupState,
15
15
  validator: validateSetup,
@@ -17,8 +17,9 @@ export default function SetupWizard() {
17
17
  onSave: async (data) => {
18
18
  try {
19
19
  await initializeSystem(data);
20
- // Hard redirect to break out of any potential state/cache issues
21
- window.location.href = '/storykeep';
20
+ setTimeout(() => {
21
+ window.location.href = '/storykeep';
22
+ }, 1000);
22
23
  return data;
23
24
  } catch (error) {
24
25
  console.error('Installation failed:', error);
@@ -47,8 +47,10 @@ const Impression = ({ payload, currentPage, config }: ImpressionProps) => {
47
47
  beliefValue: 'CLICKED',
48
48
  paneId: payload.parentId || '',
49
49
  };
50
+ const backendUrl =
51
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
50
52
 
51
- await fetch(`${tractStackConfig.backendUrl}/api/v1/state`, {
53
+ await fetch(`${backendUrl}/api/v1/state`, {
52
54
  method: 'POST',
53
55
  headers: {
54
56
  'Content-Type': 'application/x-www-form-urlencoded',
@@ -31,20 +31,22 @@ export function useSearch(): UseSearchReturn {
31
31
  const [suggestions, setSuggestions] = useState<DiscoverySuggestion[]>([]);
32
32
  const [isDiscovering, setIsDiscovering] = useState(false);
33
33
  const [discoverError, setDiscoverError] = useState<string | null>(null);
34
+ const tenantId =
35
+ (typeof window !== 'undefined' && window.TRACTSTACK_CONFIG?.tenantId) ||
36
+ import.meta.env.PUBLIC_TENANTID ||
37
+ 'default';
34
38
 
35
- // Retrieve state
36
39
  const [searchResults, setSearchResults] = useState<CategorizedResults | null>(
37
40
  null
38
41
  );
39
42
  const [isRetrieving, setIsRetrieving] = useState(false);
40
43
  const [retrieveError, setRetrieveError] = useState<string | null>(null);
41
44
 
42
- // --- REVISED STATE FOR SEARCH LOGIC ---
43
45
  const searchTimerRef = useRef<NodeJS.Timeout>();
44
46
  const lastExecutionTimeRef = useRef<number>(0);
45
47
  const pendingQueryRef = useRef<string | null>(null);
46
48
  const inflightQueryRef = useRef<string | null>(null);
47
- const api = useMemo(() => new TractStackAPI(), []);
49
+ const api = useMemo(() => new TractStackAPI(tenantId), []);
48
50
 
49
51
  const performDiscovery = useCallback(
50
52
  async (query: string) => {
@@ -228,33 +228,11 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
228
228
  );
229
229
  const sessionMeta = document.querySelector('meta[name="session-id"]');
230
230
 
231
- // Dynamic Backend URL Fix:
232
- // If we are on a subdomain (sandbox.domain.com), force the API call to match
233
- // (sandbox.domain.com:10000) so cookies are sent correctly.
234
- let dynamicBackendUrl = goBackend;
235
- try {
236
- const currentHost = window.location.hostname;
237
- const backendUrlObj = new URL(goBackend);
238
-
239
- // If the hostnames differ (e.g. sandbox.site.com vs site.com), align them
240
- if (currentHost !== backendUrlObj.hostname) {
241
- backendUrlObj.hostname = currentHost;
242
- dynamicBackendUrl = backendUrlObj.toString().replace(/\/$/, '');
243
- }
244
- } catch (e) {
245
- console.warn(
246
- 'TractStack: Failed to construct dynamic backend URL',
247
- e
248
- );
249
- }
250
-
251
231
  window.TRACTSTACK_CONFIG = {
252
232
  configured: true,
253
- backendUrl: dynamicBackendUrl,
233
+ backendUrl: goBackend,
254
234
  tenantId: tenantId,
255
235
  fontBasePath: fontBasePath,
256
- // Use the meta tag if it exists (for subsequent client-side loads),
257
- // otherwise fall back to the initial server-rendered value.
258
236
  storyfragmentId: storyfragmentMeta
259
237
  ? storyfragmentMeta.content
260
238
  : initialStoryfragmentId,
@@ -213,7 +213,6 @@ for (const [key, value] of Astro.url.searchParams) {
213
213
  }
214
214
  <div class="pointer-events-auto max-h-full">
215
215
  <SettingsPanel
216
- config={brandConfig}
217
216
  availableCodeHooks={Object.keys(codeHookComponents)}
218
217
  client:only="react"
219
218
  />
@@ -1,12 +1,10 @@
1
1
  import type { APIRoute } from '@/types/astro';
2
2
 
3
- export const GET: APIRoute = async ({ request }) => {
3
+ export const GET: APIRoute = async ({ request, locals }) => {
4
4
  const goBackend =
5
5
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
6
  const tenantId =
7
- request.headers.get('X-Tenant-ID') ||
8
- import.meta.env.PUBLIC_TENANTID ||
9
- 'default';
7
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
10
8
 
11
9
  try {
12
10
  // Get Authorization header from frontend
@@ -1,6 +1,9 @@
1
1
  import type { APIRoute } from '@/types/astro';
2
2
 
3
- export const POST: APIRoute = async ({ request }) => {
3
+ export const POST: APIRoute = async ({ request, locals }) => {
4
+ const tenantId =
5
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
6
+
4
7
  try {
5
8
  // Parse request body
6
9
  const body = await request.json();
@@ -17,10 +20,6 @@ export const POST: APIRoute = async ({ request }) => {
17
20
  }
18
21
 
19
22
  // Get tenant info from environment
20
- const tenantId =
21
- request.headers.get('X-Tenant-ID') ||
22
- import.meta.env.PUBLIC_TENANTID ||
23
- 'default';
24
23
  const backendUrl =
25
24
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
26
25
 
@@ -2,14 +2,29 @@ import type { APIRoute } from '@/types/astro';
2
2
 
3
3
  export const POST: APIRoute = async ({ cookies }) => {
4
4
  try {
5
- // Clear admin and editor auth cookies
6
- cookies.delete('admin_auth', {
7
- path: '/',
8
- });
5
+ const goBackend =
6
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
7
+ let rootDomain: string | undefined;
9
8
 
10
- cookies.delete('editor_auth', {
11
- path: '/',
12
- });
9
+ try {
10
+ const url = new URL(goBackend);
11
+ // Only set domain for non-localhost to preserve local dev behavior
12
+ if (url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
13
+ rootDomain = url.hostname;
14
+ }
15
+ } catch (e) {
16
+ console.warn('Logout: Failed to parse backend URL for cookie domain', e);
17
+ }
18
+
19
+ // Determine the options ONCE to prevent overwriting
20
+ const cookieOptions: any = { path: '/' };
21
+ if (rootDomain) {
22
+ cookieOptions.domain = rootDomain;
23
+ }
24
+
25
+ // Execute deletion with the single, correct configuration
26
+ cookies.delete('admin_auth', cookieOptions);
27
+ cookies.delete('editor_auth', cookieOptions);
13
28
 
14
29
  return new Response(
15
30
  JSON.stringify({
@@ -1,6 +1,6 @@
1
1
  import type { APIRoute } from '@/types/astro';
2
2
 
3
- export const POST: APIRoute = async ({ request }) => {
3
+ export const POST: APIRoute = async ({ request, locals }) => {
4
4
  const GO_BACKEND =
5
5
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
6
 
@@ -11,13 +11,15 @@ export const POST: APIRoute = async ({ request }) => {
11
11
  // Create abort controller for request timeout
12
12
  const controller = new AbortController();
13
13
  const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
14
+ const tenantId =
15
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
14
16
 
15
17
  try {
16
18
  const response = await fetch(`${GO_BACKEND}/api/v1/auth/profile`, {
17
19
  method: 'POST',
18
20
  headers: {
19
21
  'Content-Type': 'application/json',
20
- 'X-Tenant-ID': import.meta.env.PUBLIC_TENANTID || 'default',
22
+ 'X-Tenant-ID': tenantId,
21
23
  ...(request.headers.get('Authorization') && {
22
24
  Authorization: request.headers.get('Authorization')!,
23
25
  }),
@@ -1,13 +1,11 @@
1
1
  import type { APIRoute } from '@/types/astro';
2
2
 
3
- export const POST: APIRoute = async ({ request }) => {
3
+ export const POST: APIRoute = async ({ request, locals }) => {
4
+ const tenantId =
5
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
4
6
  const goBackend =
5
7
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
6
8
  const sharedSecret = import.meta.env.PRIVATE_SANDBOX_SECRET;
7
- const tenantId =
8
- request.headers.get('X-Tenant-ID') ||
9
- import.meta.env.PUBLIC_TENANTID ||
10
- 'default';
11
9
 
12
10
  if (!sharedSecret || sharedSecret === 'false' || sharedSecret === 'true') {
13
11
  return new Response(
@@ -3,18 +3,15 @@ import { createTailwindcss } from '@mhsdesign/jit-browser-tailwindcss';
3
3
  import fs from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
 
6
- export const POST: APIRoute = async ({ request }) => {
6
+ export const POST: APIRoute = async ({ request, locals }) => {
7
+ const tenantId =
8
+ locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
9
+ const goBackend =
10
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
11
+
7
12
  try {
8
13
  const { dirtyPaneIds, dirtyClasses } = await request.json();
9
14
 
10
- const tenantId =
11
- request.headers.get('X-Tenant-ID') ||
12
- import.meta.env.PUBLIC_TENANTID ||
13
- 'default';
14
-
15
- const goBackend =
16
- import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
17
-
18
15
  // Forward authentication cookies to the backend
19
16
  const cookieHeader = request.headers.get('cookie') || '';
20
17
 
@@ -6,6 +6,7 @@ import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
6
6
  import { getFullContentMap } from '@/stores/analytics';
7
7
  import { getBrandConfig } from '@/utils/api/brandConfig';
8
8
  import { preHealthCheck } from '@/utils/backend';
9
+ import type { LoadData } from '@/types/compositorTypes';
9
10
 
10
11
  const tenantId =
11
12
  Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
@@ -23,10 +24,25 @@ if (authCheck) {
23
24
  const userIsAuthenticated = isAuthenticated(Astro);
24
25
  const userIsAdmin = isAdmin(Astro);
25
26
  const role = userIsAdmin ? `admin` : userIsAuthenticated ? `editor` : null;
26
-
27
27
  const brandConfig = await getBrandConfig(tenantId);
28
28
  const initializing = !brandConfig.SITE_INIT;
29
29
 
30
+ let initialSuitcase: LoadData | null = null;
31
+ if (initializing) {
32
+ const goBackend =
33
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
34
+ try {
35
+ const response = await fetch(`${goBackend}/api/v1/setup/suitcase`, {
36
+ headers: { 'X-Tenant-Id': tenantId },
37
+ });
38
+ if (response.ok) {
39
+ initialSuitcase = await response.json();
40
+ }
41
+ } catch (e) {
42
+ console.warn('[Branding] Failed to probe suitcase:', e);
43
+ }
44
+ }
45
+
30
46
  const title = 'Branding | StoryKeep';
31
47
 
32
48
  let fullContentMap;
@@ -52,6 +68,7 @@ try {
52
68
  role={role}
53
69
  initializing={initializing}
54
70
  initialBrandConfig={brandConfig}
71
+ initialSuitcase={initialSuitcase}
55
72
  />
56
73
  </div>
57
74
  </main>