astro-tractstack 2.2.9 → 2.3.0

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 (50) hide show
  1. package/bin/create-tractstack.js +2 -2
  2. package/dist/index.js +89 -8
  3. package/package.json +3 -1
  4. package/templates/custom/minimal/CodeHook.astro +14 -5
  5. package/templates/custom/shopify/CalDotComBooking.tsx +44 -0
  6. package/templates/custom/shopify/Cart.tsx +345 -0
  7. package/templates/custom/shopify/CartIcon.tsx +47 -0
  8. package/templates/custom/shopify/CartModal.tsx +63 -0
  9. package/templates/custom/shopify/CheckoutModal.tsx +187 -0
  10. package/templates/custom/shopify/ShopifyCartManager.tsx +145 -0
  11. package/templates/custom/shopify/ShopifyCheckout.tsx +167 -0
  12. package/templates/custom/shopify/ShopifyProductGrid.tsx +281 -0
  13. package/templates/custom/shopify/ShopifyServiceList.tsx +118 -0
  14. package/templates/custom/shopify/cart.astro +23 -0
  15. package/templates/custom/with-examples/CodeHook.astro +9 -1
  16. package/templates/custom/with-examples/ProductGrid.astro +1 -1
  17. package/templates/src/client/app.js +4 -2
  18. package/templates/src/components/Header.astro +37 -11
  19. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +13 -2
  20. package/templates/src/components/form/advanced/APIConfigSection.tsx +165 -38
  21. package/templates/src/components/storykeep/Dashboard.tsx +17 -3
  22. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -0
  23. package/templates/src/components/storykeep/Dashboard_Content.tsx +5 -96
  24. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +525 -0
  25. package/templates/src/components/storykeep/StoryKeepBackdrop.astro +43 -23
  26. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +0 -14
  27. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +36 -13
  28. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +4 -11
  29. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +254 -0
  30. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +108 -8
  31. package/templates/src/lib/resources.ts +11 -21
  32. package/templates/src/pages/api/shopify/createCart.ts +73 -0
  33. package/templates/src/pages/api/shopify/getProducts.ts +64 -0
  34. package/templates/src/pages/storykeep/login.astro +5 -10
  35. package/templates/src/pages/storykeep/logout.astro +1 -10
  36. package/templates/src/pages/storykeep/manage.astro +69 -0
  37. package/templates/src/pages/storykeep/{content.astro → pages.astro} +4 -8
  38. package/templates/src/pages/storykeep/shopify.astro +101 -0
  39. package/templates/src/stores/navigation.ts +3 -42
  40. package/templates/src/stores/nodes.ts +3 -1
  41. package/templates/src/stores/resources.ts +7 -10
  42. package/templates/src/stores/shopify.ts +210 -0
  43. package/templates/src/types/tractstack.ts +21 -0
  44. package/templates/src/utils/api/advancedConfig.ts +5 -1
  45. package/templates/src/utils/api/advancedHelpers.ts +48 -5
  46. package/templates/src/utils/api/brandHelpers.ts +4 -0
  47. package/templates/src/utils/api/resourceConfig.ts +13 -5
  48. package/templates/src/utils/customHelpers.ts +70 -0
  49. package/templates/src/utils/helpers.ts +59 -0
  50. package/utils/inject-files.ts +83 -2
@@ -0,0 +1,64 @@
1
+ import type { APIRoute } from '@/types/astro';
2
+ import { shopifyData } from '@/stores/shopify';
3
+ import { resolveTenantId } from '@/utils/tenantResolver';
4
+
5
+ export const prerender = false;
6
+
7
+ const getBackendUrl = () => {
8
+ return import.meta.env.PUBLIC_API_URL || 'http://localhost:8080';
9
+ };
10
+
11
+ export const GET: APIRoute = async ({ request }) => {
12
+ // 1. Resolve Tenant Identity
13
+ const resolution = await resolveTenantId(request);
14
+ const tenantId = resolution.id;
15
+
16
+ // 2. Fetch from Backend Proxy
17
+ const backendEndpoint = `${getBackendUrl()}/api/v1/shopify/products`;
18
+ const cookieHeader = request.headers.get('cookie') || '';
19
+
20
+ try {
21
+ const backendResponse = await fetch(backendEndpoint, {
22
+ method: 'GET',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ 'X-Tenant-ID': tenantId,
26
+ Cookie: cookieHeader,
27
+ },
28
+ });
29
+
30
+ if (!backendResponse.ok) {
31
+ const errorText = await backendResponse.text();
32
+ throw new Error(
33
+ `Backend Proxy Error: ${backendResponse.status} - ${errorText}`
34
+ );
35
+ }
36
+
37
+ const result = await backendResponse.json();
38
+
39
+ // 3. Update Client Store
40
+ const newState = {
41
+ products: result.products || [],
42
+ lastFetched: Date.now(),
43
+ };
44
+
45
+ shopifyData.set(newState);
46
+
47
+ return new Response(JSON.stringify(newState), {
48
+ status: 200,
49
+ headers: { 'Content-Type': 'application/json' },
50
+ });
51
+ } catch (error) {
52
+ console.error('Shopify proxy fetch failed:', error);
53
+ return new Response(
54
+ JSON.stringify({
55
+ error:
56
+ error instanceof Error ? error.message : 'Failed to fetch products',
57
+ }),
58
+ {
59
+ status: 500,
60
+ headers: { 'Content-Type': 'application/json' },
61
+ }
62
+ );
63
+ }
64
+ };
@@ -31,8 +31,11 @@ const getAssetPath = (configPath: string, fallback: string) => {
31
31
  }
32
32
  return fallback;
33
33
  };
34
- const logo = getAssetPath(brandConfig?.LOGO, '/brand/logo.svg');
35
- const wordmark = getAssetPath(brandConfig?.WORDMARK, '/brand/wordmark.svg');
34
+ const logo = getAssetPath(brandConfig?.LOGO || ``, '/brand/logo.svg');
35
+ const wordmark = getAssetPath(
36
+ brandConfig?.WORDMARK || ``,
37
+ '/brand/wordmark.svg'
38
+ );
36
39
  const mainStylesUrl = isDev
37
40
  ? `${cssBasePath}/storykeep.css`
38
41
  : `${cssBasePath}/frontend.css`;
@@ -193,10 +196,8 @@ const mainStylesUrl = isDev
193
196
  const buttonText = submitButton.textContent.trim();
194
197
 
195
198
  submitButton.addEventListener('click', async function () {
196
- // Hide error message
197
199
  if (loginErrorDiv) loginErrorDiv.style.display = 'none';
198
200
 
199
- // Show loading state
200
201
  submitButton.disabled = true;
201
202
  submitButton.textContent = 'Signing in...';
202
203
 
@@ -226,17 +227,11 @@ const mainStylesUrl = isDev
226
227
  const result = await response.json();
227
228
 
228
229
  if (result.success) {
229
- // Show success message with role
230
230
  if (loginFormDiv) loginFormDiv.style.display = 'none';
231
231
  if (loginSuccessDiv) loginSuccessDiv.style.display = 'block';
232
232
  if (userRoleSpan)
233
233
  userRoleSpan.textContent = result.role || 'authenticated';
234
234
 
235
- // Console log for debugging
236
- console.log('Login successful:', result);
237
- console.log('User role:', result.role);
238
-
239
- // Redirect after short delay
240
235
  setTimeout(() => {
241
236
  window.location.href = result.redirect || redirectPath;
242
237
  }, 1500);
@@ -35,10 +35,8 @@
35
35
  </div>
36
36
 
37
37
  <script>
38
- // Clear authentication data
39
38
  async function logout() {
40
39
  try {
41
- // Call server-side logout API to clear HTTP-only cookies
42
40
  const response = await fetch('/api/auth/logout', {
43
41
  method: 'POST',
44
42
  headers: {
@@ -48,13 +46,10 @@
48
46
 
49
47
  const result = await response.json();
50
48
 
51
- if (result.success) {
52
- console.log('StoryKeep: Admin logout successful');
53
- } else {
49
+ if (!result.success) {
54
50
  console.error('StoryKeep: Admin logout failed:', result.error);
55
51
  }
56
52
 
57
- // Also clear any remaining TractStack user profile data
58
53
  const tractStackKeys = [];
59
54
  for (let i = 0; i < localStorage.length; i++) {
60
55
  const key = localStorage.key(i);
@@ -64,15 +59,11 @@
64
59
  }
65
60
  tractStackKeys.forEach((key) => localStorage.removeItem(key));
66
61
 
67
- console.log('TractStack: Complete logout finished');
68
-
69
- // Redirect to home page
70
62
  setTimeout(() => {
71
63
  window.location.href = '/';
72
64
  }, 1000);
73
65
  } catch (error) {
74
66
  console.error('Logout error:', error);
75
- // Still redirect even if logout API fails
76
67
  window.location.href = '/';
77
68
  }
78
69
  }
@@ -0,0 +1,69 @@
1
+ ---
2
+ import Layout from '@/layouts/Layout.astro';
3
+ import StoryKeepBackdrop from '@/components/storykeep/StoryKeepBackdrop.astro';
4
+ import StoryKeepDashboard from '@/components/storykeep/Dashboard';
5
+ import ManageContent from '@/components/storykeep/controls/content/ManageContent';
6
+ import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
7
+ import { getFullContentMap } from '@/stores/analytics';
8
+ import { getBrandConfig } from '@/utils/api/brandConfig';
9
+ import { preHealthCheck } from '@/utils/backend';
10
+
11
+ const tenantId =
12
+ Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
13
+
14
+ const healthCheckRedirect = await preHealthCheck(tenantId);
15
+ if (healthCheckRedirect !== undefined) {
16
+ return healthCheckRedirect;
17
+ }
18
+
19
+ const authCheck = requireAdminOrEditor(Astro);
20
+ if (authCheck) {
21
+ return authCheck;
22
+ }
23
+
24
+ const userIsAuthenticated = isAuthenticated(Astro);
25
+ const userIsAdmin = isAdmin(Astro);
26
+ const role = userIsAdmin ? `admin` : userIsAuthenticated ? `editor` : null;
27
+ const brandConfig = await getBrandConfig(tenantId);
28
+ const initializing = !brandConfig.SITE_INIT;
29
+
30
+ if (initializing) {
31
+ return Astro.redirect('/storykeep/branding');
32
+ }
33
+
34
+ const title = 'Manage Content | StoryKeep';
35
+ const createMenu = Astro.url.searchParams.has('create-menu');
36
+ const homeSlug = brandConfig.HOME_SLUG || 'hello';
37
+ let fullContentMap;
38
+
39
+ try {
40
+ fullContentMap = await getFullContentMap(tenantId);
41
+ } catch (error) {
42
+ return Astro.redirect(
43
+ `/maint?from=${encodeURIComponent(Astro.url.pathname)}`
44
+ );
45
+ }
46
+ ---
47
+
48
+ <Layout title={title} slug="storykeep" isStoryKeep={true}>
49
+ <main id="main-content" class="relative min-h-screen w-full">
50
+ <StoryKeepBackdrop brandConfig={brandConfig} />
51
+ <div class="max-w-5xl p-3.5 md:p-8">
52
+ <StoryKeepDashboard
53
+ client:only="react"
54
+ fullContentMap={fullContentMap}
55
+ homeSlug={homeSlug}
56
+ activeTab="manage"
57
+ role={role}
58
+ initializing={initializing}
59
+ />
60
+
61
+ <ManageContent
62
+ client:only="react"
63
+ fullContentMap={fullContentMap}
64
+ homeSlug={homeSlug}
65
+ createMenu={createMenu}
66
+ />
67
+ </div>
68
+ </main>
69
+ </Layout>
@@ -7,6 +7,7 @@ import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
7
7
  import { getFullContentMap } from '@/stores/analytics';
8
8
  import { getBrandConfig } from '@/utils/api/brandConfig';
9
9
  import { preHealthCheck } from '@/utils/backend';
10
+ import type { FullContentMapItem } from '@/types/compositorTypes';
10
11
 
11
12
  const tenantId =
12
13
  Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
@@ -24,20 +25,16 @@ if (authCheck) {
24
25
  const userIsAuthenticated = isAuthenticated(Astro);
25
26
  const userIsAdmin = isAdmin(Astro);
26
27
  const role = userIsAdmin ? `admin` : userIsAuthenticated ? `editor` : null;
27
-
28
28
  const brandConfig = await getBrandConfig(tenantId);
29
29
  const initializing = !brandConfig.SITE_INIT;
30
30
 
31
- // Redirect to branding page during initial setup
32
31
  if (initializing) {
33
32
  return Astro.redirect('/storykeep/branding');
34
33
  }
35
34
 
36
- const title = 'Content | StoryKeep';
37
- const createMenu = Astro.url.searchParams.has('create-menu');
38
-
39
- let fullContentMap;
35
+ const title = 'Web Pages | StoryKeep';
40
36
  const homeSlug = brandConfig.HOME_SLUG || 'hello';
37
+ let fullContentMap: FullContentMapItem[];
41
38
 
42
39
  try {
43
40
  fullContentMap = await getFullContentMap(tenantId);
@@ -56,7 +53,7 @@ try {
56
53
  client:only="react"
57
54
  fullContentMap={fullContentMap}
58
55
  homeSlug={homeSlug}
59
- activeTab="content"
56
+ activeTab="pages"
60
57
  role={role}
61
58
  initializing={initializing}
62
59
  />
@@ -65,7 +62,6 @@ try {
65
62
  client:only="react"
66
63
  fullContentMap={fullContentMap}
67
64
  homeSlug={homeSlug}
68
- createMenu={createMenu}
69
65
  />
70
66
  </div>
71
67
  </main>
@@ -0,0 +1,101 @@
1
+ ---
2
+ import Layout from '@/layouts/Layout.astro';
3
+ import StoryKeepBackdrop from '@/components/storykeep/StoryKeepBackdrop.astro';
4
+ import StoryKeepDashboard from '@/components/storykeep/Dashboard';
5
+ import StoryKeepDashboard_Shopify from '@/components/storykeep/Dashboard_Shopify';
6
+ import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
7
+ import { getFullContentMap } from '@/stores/analytics';
8
+ import { getBrandConfig } from '@/utils/api/brandConfig';
9
+ import { getResourcesByIds } from '@/utils/api/resourceConfig';
10
+ import { preHealthCheck } from '@/utils/backend';
11
+ import type { ResourceNode } from '@/types/compositorTypes';
12
+
13
+ const tenantId =
14
+ Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
15
+
16
+ const healthCheckRedirect = await preHealthCheck(tenantId);
17
+ if (healthCheckRedirect !== undefined) {
18
+ return healthCheckRedirect;
19
+ }
20
+
21
+ const authCheck = requireAdminOrEditor(Astro);
22
+ if (authCheck) {
23
+ return authCheck;
24
+ }
25
+
26
+ // Fetch configuration early to check enablement
27
+ const brandConfig = await getBrandConfig(tenantId);
28
+
29
+ if (!brandConfig.HAS_SHOPIFY) {
30
+ return Astro.redirect('/storykeep');
31
+ }
32
+
33
+ const userIsAuthenticated = isAuthenticated(Astro);
34
+ const userIsAdmin = isAdmin(Astro);
35
+ const role = userIsAdmin ? 'admin' : userIsAuthenticated ? 'editor' : null;
36
+
37
+ const initializing = !brandConfig.SITE_INIT;
38
+
39
+ if (initializing) {
40
+ return Astro.redirect('/storykeep/branding');
41
+ }
42
+
43
+ const title = 'Shopify | StoryKeep';
44
+ let fullContentMap = [];
45
+ let relevantResources: ResourceNode[] = [];
46
+ const homeSlug = brandConfig.HOME_SLUG || 'hello';
47
+
48
+ try {
49
+ fullContentMap = await getFullContentMap(tenantId);
50
+ const knownResources = brandConfig.KNOWN_RESOURCES || {};
51
+ const activeCategories = Object.keys(knownResources);
52
+
53
+ if (Array.isArray(fullContentMap)) {
54
+ const slimResources = fullContentMap.filter(
55
+ (node: any) =>
56
+ node.type === 'Resource' &&
57
+ node.categorySlug &&
58
+ activeCategories.includes(node.categorySlug)
59
+ ) as ResourceNode[];
60
+
61
+ if (slimResources.length > 0) {
62
+ const ids = slimResources.map((r) => r.id);
63
+ const configs = await getResourcesByIds(tenantId, ids);
64
+ relevantResources = configs.map((config) => ({
65
+ ...config,
66
+ nodeType: 'Resource',
67
+ parentId: null,
68
+ })) as ResourceNode[];
69
+ } else {
70
+ relevantResources = [];
71
+ }
72
+ }
73
+ } catch (error) {
74
+ console.error('Failed to load shopify resources:', error);
75
+ return Astro.redirect(
76
+ `/maint?from=${encodeURIComponent(Astro.url.pathname)}`
77
+ );
78
+ }
79
+ ---
80
+
81
+ <Layout title={title} slug="storykeep" isStoryKeep={true}>
82
+ <main id="main-content" class="relative min-h-screen w-full">
83
+ <StoryKeepBackdrop brandConfig={brandConfig} />
84
+ <div class="max-w-5xl p-3.5 md:p-8">
85
+ <StoryKeepDashboard
86
+ client:only="react"
87
+ fullContentMap={fullContentMap}
88
+ homeSlug={homeSlug}
89
+ activeTab="shopify"
90
+ role={role}
91
+ initializing={initializing}
92
+ brandConfig={brandConfig}
93
+ />
94
+ <StoryKeepDashboard_Shopify
95
+ client:only="react"
96
+ brandConfig={brandConfig}
97
+ existingResources={relevantResources}
98
+ />
99
+ </div>
100
+ </main>
101
+ </Layout>
@@ -1,11 +1,8 @@
1
1
  import { atom } from 'nanostores';
2
2
 
3
- // Used by StoryKeepWizard
4
3
  export const skipWizard = atom<boolean>(false);
5
4
 
6
- // Navigation state structure for Content sub-navigation only
7
5
  export interface ContentNavigationState {
8
- subtab: 'webpages' | 'manage';
9
6
  manageSubtab:
10
7
  | 'summary'
11
8
  | 'storyfragments'
@@ -17,24 +14,22 @@ export interface ContentNavigationState {
17
14
  | 'files';
18
15
  }
19
16
 
20
- // Default content navigation state
21
17
  const defaultContentNavigationState: ContentNavigationState = {
22
- subtab: 'webpages',
23
18
  manageSubtab: 'summary',
24
19
  };
25
20
 
26
- // Storage key for localStorage persistence
27
21
  const CONTENT_NAVIGATION_STORAGE_KEY = 'tractstack_content_navigation_state';
28
22
 
29
- // Helper functions for localStorage
30
23
  function loadContentNavigationFromStorage(): ContentNavigationState {
31
24
  try {
32
25
  const stored = localStorage.getItem(CONTENT_NAVIGATION_STORAGE_KEY);
33
26
  if (stored) {
34
27
  const parsed = JSON.parse(stored);
28
+ // Ensure we only merge valid keys, ignoring old 'subtab' if present
35
29
  return {
36
30
  ...defaultContentNavigationState,
37
- ...parsed,
31
+ manageSubtab:
32
+ parsed.manageSubtab || defaultContentNavigationState.manageSubtab,
38
33
  };
39
34
  }
40
35
  } catch (error) {
@@ -57,29 +52,15 @@ function saveContentNavigationToStorage(state: ContentNavigationState): void {
57
52
  }
58
53
  }
59
54
 
60
- // Create the persistent nanostore for content navigation
61
55
  export const contentNavigationStore = atom<ContentNavigationState>(
62
56
  loadContentNavigationFromStorage()
63
57
  );
64
58
 
65
- // Subscribe to changes and persist to localStorage
66
59
  contentNavigationStore.subscribe((state) => {
67
60
  saveContentNavigationToStorage(state);
68
61
  });
69
62
 
70
- // Action creators for content navigation
71
63
  export const contentNavigationActions = {
72
- /**
73
- * Set content subtab (webpages vs manage)
74
- */
75
- setContentSubtab: (subtab: ContentNavigationState['subtab']) => {
76
- const currentState = contentNavigationStore.get();
77
- contentNavigationStore.set({
78
- ...currentState,
79
- subtab,
80
- });
81
- },
82
-
83
64
  /**
84
65
  * Set manage content sub-subtab (summary, storyfragments, etc.)
85
66
  */
@@ -106,22 +87,6 @@ export const contentNavigationActions = {
106
87
  },
107
88
  };
108
89
 
109
- // Navigation helper functions (moved from navigationHelpers.ts)
110
-
111
- /**
112
- * Handle content subtab change with navigation tracking
113
- */
114
- export function handleContentSubtabChange(
115
- newSubtab: ContentNavigationState['subtab'],
116
- setActiveContentTab: (tab: string) => void
117
- ) {
118
- // Update the active subtab in the component
119
- setActiveContentTab(newSubtab);
120
-
121
- // Update navigation store
122
- contentNavigationActions.setContentSubtab(newSubtab);
123
- }
124
-
125
90
  /**
126
91
  * Handle manage content sub-subtab change with navigation tracking
127
92
  */
@@ -129,10 +94,7 @@ export function handleManageSubtabChange(
129
94
  newSubtab: ContentNavigationState['manageSubtab'],
130
95
  setActiveTab: (tab: string) => void
131
96
  ) {
132
- // Update the active sub-subtab in the component
133
97
  setActiveTab(newSubtab);
134
-
135
- // Update navigation store
136
98
  contentNavigationActions.setManageSubtab(newSubtab);
137
99
  }
138
100
 
@@ -143,7 +105,6 @@ export function handleManageSubtabChange(
143
105
  export function restoreTabNavigation() {
144
106
  const state = contentNavigationStore.get();
145
107
  return {
146
- subtab: state.subtab,
147
108
  manageSubtab: state.manageSubtab,
148
109
  };
149
110
  }
@@ -1893,6 +1893,7 @@ export class NodesContext {
1893
1893
  delete duplicatedPane.gridLayout;
1894
1894
  }
1895
1895
 
1896
+ duplicatedPane.isChanged = true;
1896
1897
  this.addNode(duplicatedPane as PaneNode);
1897
1898
  this.addNodes(allNodes);
1898
1899
  this.notifyNode(ownerId);
@@ -1953,7 +1954,6 @@ export class NodesContext {
1953
1954
  const storyFragmentNode = ownerNode as StoryFragmentNode;
1954
1955
  let specificIdx = -1;
1955
1956
  let elIdx = -1;
1956
- let storyFragmentWasChanged: boolean = false;
1957
1957
 
1958
1958
  if (
1959
1959
  insertPaneId &&
@@ -1976,8 +1976,10 @@ export class NodesContext {
1976
1976
  );
1977
1977
  }
1978
1978
  }
1979
+ storyFragmentNode.isChanged = true;
1979
1980
  }
1980
1981
 
1982
+ duplicatedPane.isChanged = true;
1981
1983
  this.addNode(duplicatedPane as PaneNode);
1982
1984
  this.linkChildToParent(
1983
1985
  duplicatedPane.id,
@@ -1,17 +1,14 @@
1
- import { atom } from 'nanostores';
1
+ import { map } from 'nanostores';
2
2
  import type { ResourceNode } from '@/types/compositorTypes';
3
3
 
4
- export interface ResourcesCache {
4
+ export const HEADER_RESOURCES_TTL = 5 * 60 * 1000;
5
+
6
+ export const headerResourcesStore = map<{
5
7
  data: ResourceNode[];
6
8
  lastFetched: number;
7
- }
8
-
9
- // Initialize with an empty state. This atom will live on the server and persist
10
- // across page requests for the lifetime of the server instance.
11
- export const headerResourcesStore = atom<ResourcesCache>({
9
+ key?: string;
10
+ }>({
12
11
  data: [],
13
12
  lastFetched: 0,
13
+ key: '',
14
14
  });
15
-
16
- // Default Time-To-Live for the cache: 5 minutes in milliseconds.
17
- export const HEADER_RESOURCES_TTL = 5 * 60 * 1000;