@digitalygo/create-diggocms-app 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +96 -23
  2. package/bin/cli.js +52 -94
  3. package/package.json +1 -1
  4. package/templates/full/.env.local.example +3 -2
  5. package/templates/full/README.md +64 -22
  6. package/templates/full/components/PageLayout.tsx +40 -0
  7. package/templates/full/lib/data-fetching.ts +55 -10
  8. package/templates/full/lib/diggo-config.ts +4 -2
  9. package/templates/full/package.json +3 -1
  10. package/templates/full/pages/[...slug].tsx +93 -0
  11. package/templates/full/pages/_app.tsx +11 -0
  12. package/templates/full/pages/index.tsx +14 -0
  13. package/templates/full/postcss.config.js +6 -0
  14. package/templates/full/styles/globals.css +113 -0
  15. package/templates/full/tailwind.config.ts +14 -0
  16. package/templates/full/tsconfig.json +2 -1
  17. package/templates/minimal/.env.local.example +3 -2
  18. package/templates/minimal/README.md +44 -10
  19. package/templates/minimal/lib/data-fetching.ts +46 -12
  20. package/templates/minimal/lib/diggo-config.ts +2 -5
  21. package/templates/minimal/package.json +3 -1
  22. package/templates/minimal/pages/[...slug].tsx +73 -0
  23. package/templates/minimal/pages/_app.tsx +11 -0
  24. package/templates/minimal/pages/index.tsx +14 -0
  25. package/templates/minimal/postcss.config.js +6 -0
  26. package/templates/minimal/{app → styles}/globals.css +4 -0
  27. package/templates/minimal/tailwind.config.ts +14 -0
  28. package/templates/minimal/tsconfig.json +2 -1
  29. package/templates/with-mock/.env.local.example +3 -2
  30. package/templates/with-mock/README.md +61 -33
  31. package/templates/with-mock/components/PageLayout.tsx +40 -0
  32. package/templates/with-mock/lib/data-fetching.ts +56 -11
  33. package/templates/with-mock/lib/diggo-config.ts +4 -2
  34. package/templates/with-mock/package.json +7 -3
  35. package/templates/with-mock/pages/[...slug].tsx +117 -0
  36. package/templates/with-mock/pages/_app.tsx +11 -0
  37. package/templates/with-mock/pages/index.tsx +14 -0
  38. package/templates/with-mock/postcss.config.js +6 -0
  39. package/templates/with-mock/scripts/mock-server.ts +0 -6
  40. package/templates/with-mock/styles/globals.css +118 -0
  41. package/templates/with-mock/tailwind.config.ts +14 -0
  42. package/templates/with-mock/tsconfig.json +2 -1
  43. package/templates/full/app/[...slug]/page.tsx +0 -115
  44. package/templates/full/app/globals.css +0 -238
  45. package/templates/full/app/layout.tsx +0 -25
  46. package/templates/full/app/page.tsx +0 -6
  47. package/templates/full/tsconfig.tsbuildinfo +0 -1
  48. package/templates/minimal/app/[...slug]/page.tsx +0 -56
  49. package/templates/minimal/app/layout.tsx +0 -22
  50. package/templates/minimal/app/page.tsx +0 -6
  51. package/templates/with-mock/app/[...slug]/page.tsx +0 -115
  52. package/templates/with-mock/app/globals.css +0 -238
  53. package/templates/with-mock/app/layout.tsx +0 -25
  54. package/templates/with-mock/app/page.tsx +0 -6
  55. package/templates/with-mock/tsconfig.tsbuildinfo +0 -1
@@ -1,31 +1,59 @@
1
1
  import type { PagePayload } from '@digitalygo/diggocms-sdk-core';
2
2
  import { sdkConfig, isMockMode, hasBaseUrl } from './diggo-config';
3
3
 
4
+ const FETCH_TIMEOUT_MS = 5000;
5
+
4
6
  export interface FetchPageResult {
5
7
  page: PagePayload | null;
6
8
  error: string | null;
7
9
  }
8
10
 
11
+ async function fetchWithTimeout(
12
+ url: string,
13
+ timeoutMs: number
14
+ ): Promise<Response> {
15
+ const controller = new AbortController();
16
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
17
+
18
+ try {
19
+ const response = await fetch(url, { signal: controller.signal });
20
+ clearTimeout(timeoutId);
21
+ return response;
22
+ } catch (error) {
23
+ clearTimeout(timeoutId);
24
+ if (error instanceof Error && error.name === 'AbortError') {
25
+ throw new Error(`Request timeout after ${timeoutMs}ms`);
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+
9
31
  export async function fetchPageData(slug: string): Promise<FetchPageResult> {
10
32
  if (isMockMode()) {
11
- // Mock mode: fetch from local mock API
12
33
  try {
13
34
  const mockApiUrl = process.env.MOCK_API_URL ?? 'http://localhost:3001';
14
- const response = await fetch(`${mockApiUrl}/pages/${slug}`);
15
-
35
+ const response = await fetchWithTimeout(
36
+ `${mockApiUrl}/pages/${slug}`,
37
+ FETCH_TIMEOUT_MS
38
+ );
39
+
16
40
  if (!response.ok) {
17
41
  return {
18
42
  page: null,
19
43
  error: `Mock API returned ${response.status}`,
20
44
  };
21
45
  }
22
-
46
+
23
47
  const page = (await response.json()) as PagePayload;
24
48
  return { page, error: null };
25
- } catch {
49
+ } catch (error) {
50
+ const errorMessage =
51
+ error instanceof Error ? error.message : 'Unknown error';
26
52
  return {
27
53
  page: null,
28
- error: 'Failed to connect to mock API. Is it running?',
54
+ error: errorMessage.includes('timeout')
55
+ ? 'Mock API request timed out. Is the server running?'
56
+ : 'Failed to connect to mock API. Is it running?',
29
57
  };
30
58
  }
31
59
  }
@@ -37,23 +65,29 @@ export async function fetchPageData(slug: string): Promise<FetchPageResult> {
37
65
  };
38
66
  }
39
67
 
40
- // Real API mode: fetch from CMS
41
68
  try {
42
- const response = await fetch(`${sdkConfig.baseUrl}/pages/${slug}`);
43
-
69
+ const response = await fetchWithTimeout(
70
+ `${sdkConfig.baseUrl}/pages/${slug}`,
71
+ FETCH_TIMEOUT_MS
72
+ );
73
+
44
74
  if (!response.ok) {
45
75
  return {
46
76
  page: null,
47
77
  error: `CMS returned ${response.status}`,
48
78
  };
49
79
  }
50
-
80
+
51
81
  const page = (await response.json()) as PagePayload;
52
82
  return { page, error: null };
53
- } catch {
83
+ } catch (error) {
84
+ const errorMessage =
85
+ error instanceof Error ? error.message : 'Unknown error';
54
86
  return {
55
87
  page: null,
56
- error: 'Error connecting to CMS',
88
+ error: errorMessage.includes('timeout')
89
+ ? 'Request to CMS timed out. Please check your BASE_URL and network connection.'
90
+ : 'Error connecting to CMS',
57
91
  };
58
92
  }
59
93
  }
@@ -1,12 +1,9 @@
1
1
  import type { ComponentRegistry, NavigationRegistry, DiggoConfig } from '@digitalygo/diggocms-sdk-core';
2
2
 
3
- // Add your custom components here
4
3
  export const componentsRegistry: ComponentRegistry = {
5
- // Example: title: ExtendedTitle,
6
4
  };
7
5
 
8
6
  export const navigationRegistry: NavigationRegistry = {
9
- // Example: MenuItem: ExtendedMenuItem,
10
7
  };
11
8
 
12
9
  const isMock = process.env.MOCK === '1';
@@ -17,7 +14,7 @@ function getBaseUrl(): string {
17
14
  if (isMock) {
18
15
  return mockApiUrl ?? 'http://localhost:3001';
19
16
  }
20
- return baseUrl ?? 'http://placeholder.local';
17
+ return baseUrl ?? '';
21
18
  }
22
19
 
23
20
  export const sdkConfig: DiggoConfig = {
@@ -31,5 +28,5 @@ export function isMockMode(): boolean {
31
28
  }
32
29
 
33
30
  export function hasBaseUrl(): boolean {
34
- return !!baseUrl;
31
+ return !!baseUrl && baseUrl.length > 0;
35
32
  }
@@ -2,7 +2,6 @@
2
2
  "name": "my-diggocms-app",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
- "type": "module",
6
5
  "scripts": {
7
6
  "dev": "next dev",
8
7
  "build": "next build",
@@ -20,6 +19,9 @@
20
19
  "@types/node": "^20.0.0",
21
20
  "@types/react": "^18.3.0",
22
21
  "@types/react-dom": "^18.3.0",
22
+ "autoprefixer": "^10.4.19",
23
+ "postcss": "^8.4.38",
24
+ "tailwindcss": "^3.4.4",
23
25
  "typescript": "^5.5.0"
24
26
  }
25
27
  }
@@ -0,0 +1,73 @@
1
+ import type { GetStaticPaths, GetStaticProps } from 'next';
2
+ import type { PagePayload } from '@digitalygo/diggocms-sdk-core';
3
+ import { useRouter } from 'next/router';
4
+ import Head from 'next/head';
5
+ import { renderPage } from '@digitalygo/diggocms-sdk-core/server';
6
+ import { componentsRegistry } from '@/lib/diggo-config';
7
+ import { fetchPageData, FetchPageResult } from '@/lib/data-fetching';
8
+
9
+ interface CatchAllPageProps {
10
+ page: PagePayload | null;
11
+ error: string | null;
12
+ }
13
+
14
+ export default function CatchAllPage({ page, error }: CatchAllPageProps) {
15
+ const router = useRouter();
16
+
17
+ if (router.isFallback) {
18
+ return <div className="p-8 text-center">Loading...</div>;
19
+ }
20
+
21
+ if (!page) {
22
+ return (
23
+ <main className="max-w-5xl mx-auto p-8">
24
+ <h1 className="text-2xl font-bold mb-4">Page Not Found</h1>
25
+ {error && <p className="text-red-600">{error}</p>}
26
+ </main>
27
+ );
28
+ }
29
+
30
+ return (
31
+ <>
32
+ <Head>
33
+ <title>{page.title || 'My DiggoCMS App'}</title>
34
+ </Head>
35
+ <main className="max-w-5xl mx-auto p-8">
36
+ {error && (
37
+ <div className="bg-yellow-50 border border-yellow-200 text-yellow-800 p-4 mb-6 rounded">
38
+ <strong>Warning:</strong> {error}
39
+ </div>
40
+ )}
41
+ {renderPage(page, componentsRegistry)}
42
+ </main>
43
+ </>
44
+ );
45
+ }
46
+
47
+ export const getStaticPaths: GetStaticPaths = async () => {
48
+ return {
49
+ paths: [],
50
+ fallback: 'blocking',
51
+ };
52
+ };
53
+
54
+ export const getStaticProps: GetStaticProps<CatchAllPageProps> = async ({ params }) => {
55
+ const slugArray = params?.slug as string[] | undefined;
56
+ const slug = Array.isArray(slugArray) ? slugArray.join('/') : 'home';
57
+
58
+ const { page, error }: FetchPageResult = await fetchPageData(slug);
59
+
60
+ if (!page) {
61
+ return {
62
+ notFound: true,
63
+ };
64
+ }
65
+
66
+ return {
67
+ props: {
68
+ page,
69
+ error,
70
+ },
71
+ revalidate: 60,
72
+ };
73
+ };
@@ -0,0 +1,11 @@
1
+ import type { AppProps } from 'next/app';
2
+ import { DiggoProvider } from '@/components/DiggoProvider';
3
+ import '@/styles/globals.css';
4
+
5
+ export default function App({ Component, pageProps }: AppProps) {
6
+ return (
7
+ <DiggoProvider>
8
+ <Component {...pageProps} />
9
+ </DiggoProvider>
10
+ );
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { GetStaticProps } from 'next';
2
+
3
+ export default function HomePage() {
4
+ return null;
5
+ }
6
+
7
+ export const getStaticProps: GetStaticProps = async () => {
8
+ return {
9
+ redirect: {
10
+ destination: '/home',
11
+ permanent: false,
12
+ },
13
+ };
14
+ };
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -1,3 +1,7 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
1
5
  * {
2
6
  margin: 0;
3
7
  padding: 0;
@@ -0,0 +1,14 @@
1
+ import type { Config } from 'tailwindcss';
2
+
3
+ const config: Config = {
4
+ content: [
5
+ './pages/**/*.{js,ts,jsx,tsx,mdx}',
6
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
7
+ ],
8
+ theme: {
9
+ extend: {},
10
+ },
11
+ plugins: [],
12
+ };
13
+
14
+ export default config;
@@ -14,10 +14,11 @@
14
14
  "jsx": "preserve",
15
15
  "incremental": true,
16
16
  "plugins": [{ "name": "next" }],
17
+ "baseUrl": ".",
17
18
  "paths": {
18
19
  "@/*": ["./*"]
19
20
  }
20
21
  },
21
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
22
23
  "exclude": ["node_modules"]
23
24
  }
@@ -2,8 +2,9 @@
2
2
  # Copy this file to .env.local and update the values
3
3
 
4
4
  # CMS API Configuration
5
- # Set your DiggoCMS API base URL (not needed in mock mode)
6
- BASE_URL=https://your-cms-api.com
5
+ # Set your DiggoCMS API base URL (only needed when not using mock mode)
6
+ # Example: BASE_URL=https://cms.example.com
7
+ BASE_URL=
7
8
 
8
9
  # Mock Mode
9
10
  # Set to 1 to use the local mock API server
@@ -1,32 +1,32 @@
1
1
  # My DiggoCMS App
2
2
 
3
- A complete DiggoCMS application with mock API server for development.
3
+ A complete DiggoCMS application with mock API server for development, built with Next.js Pages Router.
4
4
 
5
5
  ## Features
6
6
 
7
- - Dynamic routing with `[...slug]` pattern
8
- - Extended components for the current supported standalone types: title, image, text, video, gallery, and card
9
- - Navigation with dropdown support
10
- - Built-in mock API server
11
- - Sample fixtures included
12
- - Mock mode for development
13
- - Error handling and loading states
7
+ - Next.js 16 with Pages Router
8
+ - Static Site Generation (SSG) with `getStaticProps`/`getStaticPaths`
9
+ - Tailwind CSS for styling
10
+ - Hot reload via `next dev`
11
+ - Extended components for all supported types: title, image, text, video, gallery, and card
12
+ - Navigation components with dropdown support
13
+ - Header renders the main menu from mock API data (or CMS when configured)
14
+ - Built-in mock API server (runs with tsx, works with any package manager)
15
+ - Sample fixtures aligned with SDK payload model
16
+ - Mock mode indicator during development
14
17
 
15
18
  ## Quick Start
16
19
 
17
20
  1. Copy `.env.local.example` to `.env.local`:
21
+
18
22
  ```bash
19
23
  cp .env.local.example .env.local
20
24
  ```
21
25
 
22
- 2. Install dependencies:
23
- ```bash
24
- bun install
25
- # or
26
- npm install
27
- ```
26
+ 2. Install dependencies using your chosen package manager.
28
27
 
29
28
  3. Start the mock API and dev server:
29
+
30
30
  ```bash
31
31
  bun run dev:mock
32
32
  # or run separately:
@@ -34,13 +34,15 @@ A complete DiggoCMS application with mock API server for development.
34
34
  bun run dev # Terminal 2
35
35
  ```
36
36
 
37
+ With npm, yarn, or pnpm, use: `npm run dev:mock`, `yarn dev:mock`, or `pnpm run dev:mock`.
38
+
37
39
  4. Open [http://localhost:3000](http://localhost:3000) in your browser.
38
40
 
39
41
  ## Available Pages
40
42
 
41
43
  The mock API includes these sample pages:
42
44
 
43
- - `/` - Home page with title, text, image, video, gallery, and card payloads
45
+ - `/` or `/home` - Home page with title, text, image, video, gallery, and card payloads
44
46
  - `/chi-siamo` - About page with title, text, and image payloads
45
47
  - `/contatti` - Contact page with title, text, and card payloads
46
48
 
@@ -48,11 +50,13 @@ The mock API includes these sample pages:
48
50
 
49
51
  | Script | Description |
50
52
  |--------|-------------|
51
- | `bun run dev` | Start Next.js dev server only |
52
- | `bun run build` | Build for production |
53
- | `bun run start` | Start production server |
54
- | `bun run mock:api` | Start mock API server only |
55
- | `bun run dev:mock` | Start mock API and dev server together |
53
+ | `dev` | Start Next.js dev server only |
54
+ | `build` | Build for production |
55
+ | `start` | Start production server |
56
+ | `lint` | Run ESLint |
57
+ | `typecheck` | Run TypeScript check |
58
+ | `mock:api` | Start mock API server only |
59
+ | `dev:mock` | Start mock API and dev server together |
56
60
 
57
61
  ## Mock API Endpoints
58
62
 
@@ -65,13 +69,7 @@ The mock server provides these endpoints:
65
69
 
66
70
  ## Project Structure
67
71
 
68
- ```
69
- app/ # Next.js app router
70
- ├── [...slug]/ # Dynamic catch-all route
71
- ├── layout.tsx # Root layout with DiggoProvider
72
- ├── page.tsx # Home redirect
73
- └── globals.css # Global styles
74
-
72
+ ```text
75
73
  components/ # React components
76
74
  ├── DiggoProvider.tsx
77
75
  ├── server-components.ts
@@ -85,10 +83,15 @@ components/ # React components
85
83
  └── ExtendedMenuContainer.tsx
86
84
 
87
85
  lib/ # Utility functions
88
- ├── diggo-config.ts
89
- └── data-fetching.ts
86
+ ├── diggo-config.ts # SDK configuration
87
+ └── data-fetching.ts # Data fetching helpers
88
+
89
+ pages/ # Next.js pages (Pages Router)
90
+ ├── _app.tsx # App wrapper with DiggoProvider
91
+ ├── index.tsx # Home redirect
92
+ └── [...slug].tsx # Dynamic catch-all route
90
93
 
91
- fixtures/ # Mock data
94
+ fixtures/ # Mock data (optional, CLI-configurable)
92
95
  ├── pages/
93
96
  │ ├── index.json # Pages list
94
97
  │ ├── home.json # Home page
@@ -99,6 +102,10 @@ fixtures/ # Mock data
99
102
 
100
103
  scripts/
101
104
  └── mock-server.ts # Mock API server
105
+
106
+ public/ # Static assets
107
+ styles/ # Global styles
108
+ └── globals.css # Tailwind + component styles
102
109
  ```
103
110
 
104
111
  ## Switching to Real API
@@ -106,26 +113,47 @@ scripts/
106
113
  To use a real CMS API instead of mock data:
107
114
 
108
115
  1. Update `.env.local`:
116
+
109
117
  ```env
110
118
  BASE_URL=https://your-cms-api.com
111
119
  MOCK=0
112
120
  ```
113
121
 
122
+ Note: `BASE_URL` defaults to empty. You must set a valid URL. Requests timeout after 5 seconds if the server is unreachable.
123
+
114
124
  2. Restart the dev server:
125
+
115
126
  ```bash
116
127
  bun run dev
117
128
  ```
118
129
 
130
+ Or use npm, yarn, or pnpm: `npm run dev`, `yarn dev`, `pnpm run dev`.
131
+
132
+ ## CLI Options
133
+
134
+ When creating a project with `create-diggocms-app`, you can choose:
135
+
136
+ - **Template**: `minimal`, `full`, or `with-mock`
137
+ - **Package manager**: `bun` (default), `npm`, `yarn`, or `pnpm`
138
+ - **Fixtures** (with-mock only): Include or exclude sample fixtures
139
+
140
+ ## SSG Configuration
141
+
142
+ Pages are automatically pre-generated at build time from the fixtures when using mock mode. For production with a real API, update `getStaticPaths` in `pages/[...slug].tsx` to fetch from your CMS.
143
+
119
144
  ## Customization
120
145
 
121
146
  1. **Edit fixtures** - Modify files in `fixtures/` to change content
122
147
  2. **Add components** - Create new components in `components/` and register in `lib/diggo-config.ts`
123
- 3. **Update styles** - Edit `app/globals.css` or component styles
148
+ 3. **Update styles** - Edit `styles/globals.css` or component styles
124
149
  4. **Extend mock API** - Add new routes in `scripts/mock-server.ts`
125
150
 
126
- Standalone page fixtures intentionally avoid deprecated `subtitle` and `richtext` component entries. Card fixtures may still use card-specific fields such as `richtext` because that remains part of the card contract.
151
+ ## Fixture Notes
152
+
153
+ Page fixtures intentionally use only the current supported component types: `title`, `text`, `image`, `video`, `gallery`, and `card`. Deprecated types like `subtitle` and `richtext` (as standalone components) are excluded from page fixtures. Card components may still use card-specific fields like `richtext` as part of the card data structure.
127
154
 
128
155
  ## Documentation
129
156
 
130
157
  - [DiggoCMS SDK Documentation](https://github.com/digitalygo/diggocms-sdk)
131
- - [Next.js Documentation](https://nextjs.org/docs)
158
+ - [Next.js Pages Router Documentation](https://nextjs.org/docs/pages)
159
+ - [Tailwind CSS Documentation](https://tailwindcss.com/docs)
@@ -0,0 +1,40 @@
1
+ import type { MenuPayload } from '@digitalygo/diggocms-sdk-core';
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ import { renderMenu } from '@digitalygo/diggocms-sdk-core/server';
4
+ import { navigationRegistry } from '@/lib/diggo-config';
5
+
6
+ interface PageLayoutProps {
7
+ children: ReactNode;
8
+ menu: MenuPayload | null;
9
+ menuError: string | null;
10
+ }
11
+
12
+ export function PageLayout({ children, menu, menuError }: PageLayoutProps): ReactElement {
13
+ return (
14
+ <div className="page-shell">
15
+ <header className="site-header">
16
+ <div className="site-header__inner">
17
+ <a href="/home" className="site-brand">
18
+ DiggoCMS Demo
19
+ </a>
20
+ <div className="site-nav">
21
+ {menu ? (
22
+ renderMenu(menu, navigationRegistry)
23
+ ) : (
24
+ <div className="menu-fallback" role="status">
25
+ Menu unavailable
26
+ </div>
27
+ )}
28
+ </div>
29
+ </div>
30
+ {menuError && (
31
+ <div className="menu-alert" role="status">
32
+ {menuError}
33
+ </div>
34
+ )}
35
+ </header>
36
+
37
+ <main className="page-content">{children}</main>
38
+ </div>
39
+ );
40
+ }
@@ -12,15 +12,39 @@ interface PageIndexItem {
12
12
  }
13
13
 
14
14
  const DEFAULT_MOCK_API_URL = 'http://localhost:3001';
15
+ const FETCH_TIMEOUT_MS = 5000;
15
16
 
16
17
  function getMockApiBaseUrl(): string {
17
18
  return process.env.MOCK_API_URL ?? DEFAULT_MOCK_API_URL;
18
19
  }
19
20
 
21
+ async function fetchWithTimeout(
22
+ url: string,
23
+ timeoutMs: number
24
+ ): Promise<Response> {
25
+ const controller = new AbortController();
26
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
27
+
28
+ try {
29
+ const response = await fetch(url, { signal: controller.signal });
30
+ clearTimeout(timeoutId);
31
+ return response;
32
+ } catch (error) {
33
+ clearTimeout(timeoutId);
34
+ if (error instanceof Error && error.name === 'AbortError') {
35
+ throw new Error(`Request timeout after ${timeoutMs}ms`);
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+
20
41
  async function fetchFromMockApi<T>(endpoint: string): Promise<T | null> {
21
42
  try {
22
43
  const baseUrl = getMockApiBaseUrl();
23
- const response = await fetch(`${baseUrl}${endpoint}`);
44
+ const response = await fetchWithTimeout(
45
+ `${baseUrl}${endpoint}`,
46
+ FETCH_TIMEOUT_MS
47
+ );
24
48
 
25
49
  if (!response.ok) {
26
50
  return null;
@@ -32,7 +56,7 @@ async function fetchFromMockApi<T>(endpoint: string): Promise<T | null> {
32
56
  }
33
57
  }
34
58
 
35
- async function loadPagesIndex(): Promise<PageIndexItem[]> {
59
+ export async function loadPagesIndex(): Promise<PageIndexItem[]> {
36
60
  const index = await fetchFromMockApi<PageIndexItem[]>('/pages');
37
61
  return index ?? [];
38
62
  }
@@ -99,7 +123,10 @@ export async function fetchPageData(slug: string): Promise<FetchPageResult> {
99
123
  }
100
124
 
101
125
  try {
102
- const response = await fetch(`${sdkConfig.baseUrl}/pages/${slug}`);
126
+ const response = await fetchWithTimeout(
127
+ `${sdkConfig.baseUrl}/pages/${slug}`,
128
+ FETCH_TIMEOUT_MS
129
+ );
103
130
 
104
131
  if (!response.ok) {
105
132
  return {
@@ -111,10 +138,14 @@ export async function fetchPageData(slug: string): Promise<FetchPageResult> {
111
138
 
112
139
  const page = (await response.json()) as PagePayload;
113
140
  return { page, error: null, usingMock: false };
114
- } catch {
141
+ } catch (error) {
142
+ const errorMessage =
143
+ error instanceof Error ? error.message : 'Unknown error';
115
144
  return {
116
145
  page: null,
117
- error: 'Error connecting to CMS',
146
+ error: errorMessage.includes('timeout')
147
+ ? 'Request to CMS timed out. Please check your BASE_URL and network connection.'
148
+ : 'Error connecting to CMS',
118
149
  usingMock: false,
119
150
  };
120
151
  }
@@ -143,7 +174,10 @@ export async function fetchCollectionData(
143
174
  }
144
175
 
145
176
  try {
146
- const response = await fetch(`${sdkConfig.baseUrl}/collections/${type}`);
177
+ const response = await fetchWithTimeout(
178
+ `${sdkConfig.baseUrl}/collections/${type}`,
179
+ FETCH_TIMEOUT_MS
180
+ );
147
181
 
148
182
  if (!response.ok) {
149
183
  return {
@@ -155,10 +189,14 @@ export async function fetchCollectionData(
155
189
 
156
190
  const collection = (await response.json()) as CollectionPayload;
157
191
  return { collection, error: null, usingMock: false };
158
- } catch {
192
+ } catch (error) {
193
+ const errorMessage =
194
+ error instanceof Error ? error.message : 'Unknown error';
159
195
  return {
160
196
  collection: null,
161
- error: 'Error connecting to CMS',
197
+ error: errorMessage.includes('timeout')
198
+ ? 'Request to CMS timed out. Please check your BASE_URL and network connection.'
199
+ : 'Error connecting to CMS',
162
200
  usingMock: false,
163
201
  };
164
202
  }
@@ -183,7 +221,10 @@ export async function fetchMenuData(key: string): Promise<FetchMenuResult> {
183
221
  }
184
222
 
185
223
  try {
186
- const response = await fetch(`${sdkConfig.baseUrl}/menus/${key}`);
224
+ const response = await fetchWithTimeout(
225
+ `${sdkConfig.baseUrl}/menus/${key}`,
226
+ FETCH_TIMEOUT_MS
227
+ );
187
228
 
188
229
  if (!response.ok) {
189
230
  return {
@@ -195,10 +236,14 @@ export async function fetchMenuData(key: string): Promise<FetchMenuResult> {
195
236
 
196
237
  const menu = (await response.json()) as MenuPayload;
197
238
  return { menu, error: null, usingMock: false };
198
- } catch {
239
+ } catch (error) {
240
+ const errorMessage =
241
+ error instanceof Error ? error.message : 'Unknown error';
199
242
  return {
200
243
  menu: null,
201
- error: 'Error connecting to CMS',
244
+ error: errorMessage.includes('timeout')
245
+ ? 'Request to CMS timed out. Please check your BASE_URL and network connection.'
246
+ : 'Error connecting to CMS',
202
247
  usingMock: false,
203
248
  };
204
249
  }