frontend-hamroun 1.2.75 → 1.2.77

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 (113) hide show
  1. package/dist/batch/package.json +16 -0
  2. package/dist/client-router/package.json +16 -0
  3. package/dist/component/package.json +16 -0
  4. package/dist/context/package.json +16 -0
  5. package/dist/event-bus/package.json +16 -0
  6. package/dist/forms/package.json +16 -0
  7. package/dist/hooks/package.json +16 -0
  8. package/dist/jsx-runtime/package.json +16 -0
  9. package/dist/lifecycle-events/package.json +16 -0
  10. package/dist/package.json +71 -0
  11. package/dist/render-component/package.json +16 -0
  12. package/dist/renderer/package.json +16 -0
  13. package/dist/router/package.json +16 -0
  14. package/dist/server/package.json +17 -0
  15. package/dist/server/src/client-router.d.ts +60 -0
  16. package/dist/server/src/client-router.js +210 -0
  17. package/dist/server/src/client-router.js.map +1 -0
  18. package/dist/server/src/component.js +1 -1
  19. package/dist/server/src/event-bus.d.ts +23 -0
  20. package/dist/server/src/event-bus.js +75 -0
  21. package/dist/server/src/event-bus.js.map +1 -0
  22. package/dist/server/src/forms.d.ts +40 -0
  23. package/dist/server/src/forms.js +148 -0
  24. package/dist/server/src/forms.js.map +1 -0
  25. package/dist/server/src/hooks.js +2 -2
  26. package/dist/server/src/index.js +19 -11
  27. package/dist/server/src/lifecycle-events.d.ts +108 -0
  28. package/dist/server/src/lifecycle-events.js +177 -0
  29. package/dist/server/src/lifecycle-events.js.map +1 -0
  30. package/dist/server/src/renderComponent.js +1 -1
  31. package/dist/server/src/renderer.js +3 -3
  32. package/dist/server/src/router.d.ts +55 -0
  33. package/dist/server/src/router.js +166 -0
  34. package/dist/server/src/router.js.map +1 -0
  35. package/dist/server/src/server/index.d.ts +75 -2
  36. package/dist/server/src/server/index.js +224 -8
  37. package/dist/server/src/server/index.js.map +1 -1
  38. package/dist/server/src/server/server.js +1 -1
  39. package/dist/server/src/server/templates.d.ts +28 -0
  40. package/dist/server/src/server/templates.js +204 -0
  41. package/dist/server/src/server/templates.js.map +1 -0
  42. package/dist/server/src/server/utils.d.ts +70 -0
  43. package/dist/server/src/server/utils.js +156 -0
  44. package/dist/server/src/server/utils.js.map +1 -0
  45. package/dist/server/src/server-renderer.js +1 -1
  46. package/dist/server/src/store.d.ts +41 -0
  47. package/dist/server/src/store.js +99 -0
  48. package/dist/server/src/store.js.map +1 -0
  49. package/dist/server/src/utils.d.ts +46 -0
  50. package/dist/server/src/utils.js +144 -0
  51. package/dist/server/src/utils.js.map +1 -0
  52. package/dist/server/tsconfig.server.tsbuildinfo +1 -1
  53. package/dist/server-renderer/package.json +16 -0
  54. package/dist/store/package.json +16 -0
  55. package/dist/types/package.json +16 -0
  56. package/dist/utils/package.json +16 -0
  57. package/dist/vdom/package.json +16 -0
  58. package/dist/wasm/package.json +16 -0
  59. package/package.json +14 -13
  60. package/templates/complete-app/build.js +284 -0
  61. package/templates/complete-app/package.json +40 -0
  62. package/templates/complete-app/public/styles.css +345 -0
  63. package/templates/complete-app/src/api/index.js +31 -0
  64. package/templates/complete-app/src/client.js +93 -0
  65. package/templates/complete-app/src/components/App.js +66 -0
  66. package/templates/complete-app/src/components/Footer.js +19 -0
  67. package/templates/complete-app/src/components/Header.js +38 -0
  68. package/templates/complete-app/src/pages/About.js +59 -0
  69. package/templates/complete-app/src/pages/Home.js +54 -0
  70. package/templates/complete-app/src/pages/WasmDemo.js +136 -0
  71. package/templates/complete-app/src/server.js +186 -0
  72. package/templates/complete-app/src/wasm/build.bat +16 -0
  73. package/templates/complete-app/src/wasm/build.sh +16 -0
  74. package/templates/complete-app/src/wasm/example.go +101 -0
  75. package/templates/fullstack-app/build/main.css +225 -15
  76. package/templates/fullstack-app/build/main.css.map +2 -2
  77. package/templates/fullstack-app/build/main.js +657 -372
  78. package/templates/fullstack-app/build/main.js.map +4 -4
  79. package/templates/fullstack-app/build.ts +3 -4
  80. package/templates/fullstack-app/public/styles.css +222 -15
  81. package/templates/fullstack-app/server.ts +46 -12
  82. package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
  83. package/templates/fullstack-app/src/components/ErrorBoundary.tsx +36 -0
  84. package/templates/fullstack-app/src/components/Layout.tsx +23 -26
  85. package/templates/fullstack-app/src/components/StateDemo.tsx +207 -0
  86. package/templates/fullstack-app/src/components/UserList.tsx +30 -13
  87. package/templates/fullstack-app/src/data/api.ts +173 -38
  88. package/templates/fullstack-app/src/main.tsx +88 -154
  89. package/templates/fullstack-app/src/middleware.ts +28 -0
  90. package/templates/fullstack-app/src/pages/404.tsx +28 -0
  91. package/templates/fullstack-app/src/pages/[id].tsx +0 -0
  92. package/templates/fullstack-app/src/pages/_app.tsx +11 -0
  93. package/templates/fullstack-app/src/pages/_document.tsx +25 -0
  94. package/templates/fullstack-app/src/pages/_error.tsx +45 -0
  95. package/templates/fullstack-app/src/pages/about.tsx +71 -0
  96. package/templates/fullstack-app/src/pages/api/users/[id].ts +73 -0
  97. package/templates/fullstack-app/src/pages/api/users/index.ts +43 -0
  98. package/templates/fullstack-app/src/pages/index.tsx +97 -20
  99. package/templates/fullstack-app/src/pages/users/[id].tsx +153 -0
  100. package/templates/fullstack-app/src/pages/wasm-demo.tsx +1 -0
  101. package/templates/go/example.go +99 -86
  102. package/templates/go-wasm-app/babel.config.js +8 -2
  103. package/templates/go-wasm-app/build.config.js +62 -0
  104. package/templates/go-wasm-app/build.js +218 -0
  105. package/templates/go-wasm-app/package.json +21 -12
  106. package/templates/go-wasm-app/server.js +59 -510
  107. package/templates/go-wasm-app/src/app.js +173 -0
  108. package/templates/go-wasm-app/vite.config.js +16 -5
  109. package/templates/ssr-template/client.js +54 -26
  110. package/templates/ssr-template/server.js +5 -28
  111. package/templates/ssr-template/vite.config.js +21 -5
  112. package/dist/server/wasm.d.ts +0 -7
  113. package/dist/wasm.d.ts +0 -37
@@ -1,20 +1,16 @@
1
1
  import { render, hydrate, jsx } from 'frontend-hamroun';
2
- import { router } from './router';
3
2
  // Import Tailwind CSS
4
3
  import './styles.css';
5
4
 
6
- // Import pages directly to ensure they're available
7
- import HomePage from './pages/index';
8
- import AboutPage from './pages/about';
9
- import UsersPage from './pages/users';
10
-
11
- // Create a mutable variable for hydration state
12
- let isHydrating = document.getElementById('root')?.innerHTML.trim() !== '';
13
-
14
- console.log('[Client] Running client-side code. Hydration state:', isHydrating);
5
+ // Type declaration for window.__INITIAL_STATE__
6
+ declare global {
7
+ interface Window {
8
+ __INITIAL_STATE__?: any;
9
+ }
10
+ }
15
11
 
16
12
  // Get initial state from server
17
- const initialState = window.__INITIAL_STATE__ || {
13
+ const initialState = window.__INITIAL_STATE__ || {
18
14
  route: window.location.pathname,
19
15
  timestamp: new Date().toISOString(),
20
16
  serverRendered: false,
@@ -26,196 +22,134 @@ const initialState = window.__INITIAL_STATE__ || {
26
22
 
27
23
  console.log('[Client] Initial state:', initialState);
28
24
 
29
- // Function to fetch data from API
30
- async function fetchData(path) {
31
- try {
32
- console.log(`[Client] Fetching data from API: ${path}`);
33
- const response = await fetch(`/api${path}`);
34
- if (!response.ok) {
35
- throw new Error(`API request failed: ${response.status}`);
36
- }
37
- const data = await response.json();
38
- console.log(`[Client] Received data:`, data);
39
- return data;
40
- } catch (error) {
41
- console.error(`[Client] Error fetching data from ${path}:`, error);
42
- return null;
43
- }
44
- }
45
-
46
- // Initialize known routes
47
- function initializeRouter() {
48
- console.log('[Client] Initializing router with known pages');
49
- router.register('index', HomePage);
50
- router.register('about', AboutPage);
51
- router.register('users', UsersPage);
52
- }
53
-
54
- // Call initialization before rendering
55
- initializeRouter();
25
+ // Create a mutable variable for hydration state
26
+ let isHydrating = document.getElementById('root')?.innerHTML.trim() !== '';
56
27
 
57
- // Function to determine which page to render based on the route
58
- async function renderApp() {
28
+ // Function to handle navigation
29
+ async function handleRouteChange(path: string, isPushState = true) {
59
30
  try {
60
- // Get current path
61
- const currentPath = window.location.pathname;
62
-
63
- console.log(`[Client] Rendering for path: ${currentPath}`);
31
+ console.log(`[Router] Navigating to: ${path}`);
64
32
 
65
- // Find the root element
66
- const rootElement = document.getElementById('root');
67
- if (!rootElement) {
68
- console.error('[Client] Root element not found');
69
- return;
33
+ // Update URL if needed
34
+ if (isPushState) {
35
+ window.history.pushState(null, '', path);
70
36
  }
71
37
 
72
- // Fetch data based on the current route
73
- console.log(`[Client] Fetching users data for ${currentPath}`);
74
- const users = await fetchData('/users');
75
-
76
- if (users) {
77
- initialState.data = {
78
- ...initialState.data,
79
- users
80
- };
81
- console.log(`[Client] Updated state with ${users.length} users`);
82
- }
38
+ // Dynamically load the page component based on the path
39
+ const normalizedPath = path === '/' ? 'index' : path.replace(/^\//, '');
83
40
 
84
- // Get the component for the current path
85
- let PageComponent = null;
86
-
87
- // Special cases for known routes
88
- if (currentPath === '/about') {
89
- try {
90
- const AboutPage = await import('./pages/about');
91
- PageComponent = AboutPage.default;
92
- } catch (err) {
93
- console.error('[Client] Failed to load about page:', err);
94
- }
95
- } else if (currentPath === '/users') {
41
+ let Page;
42
+ try {
43
+ // Dynamic import for the page component
44
+ const module = await import(`./pages/${normalizedPath}.tsx`);
45
+ Page = module.default;
46
+ } catch (error) {
47
+ console.warn(`[Router] Could not load page for ${path}, trying index file`);
96
48
  try {
97
- const UsersPage = await import('./pages/users');
98
- PageComponent = UsersPage.default;
99
- } catch (err) {
100
- console.error('[Client] Failed to load users page:', err);
101
- }
102
- } else {
103
- // Try the router for other paths
104
- PageComponent = await router.resolve(currentPath);
105
- }
106
-
107
- // If component wasn't found, handle special cases
108
- if (!PageComponent) {
109
- console.warn(`[Client] Page component not found for ${currentPath}, checking special cases`);
110
-
111
- // Special case for /users
112
- if (currentPath === '/users') {
49
+ // Try loading index file in directory
50
+ const module = await import(`./pages/${normalizedPath}/index.tsx`);
51
+ Page = module.default;
52
+ } catch (innerError) {
53
+ console.error(`[Router] Failed to load page component for ${path}`);
54
+
55
+ // Try to load 404 page
113
56
  try {
114
- console.log('[Client] Attempting direct import of Users page');
115
- const UsersModule = await import('./pages/users');
116
- if (UsersModule.default) {
117
- PageComponent = UsersModule.default;
57
+ const notFoundModule = await import(`./pages/404.tsx`);
58
+ Page = notFoundModule.default;
59
+ } catch (notFoundError) {
60
+ // If all fails, render a simple not found message
61
+ const rootElement = document.getElementById('root');
62
+ if (rootElement) {
63
+ render(
64
+ <div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
65
+ <h1>Page Not Found</h1>
66
+ <p>The page you requested could not be found.</p>
67
+ <a href="/" style={{ color: '#0066cc' }}>Go to Home</a>
68
+ </div>,
69
+ rootElement
70
+ );
118
71
  }
119
- } catch (err) {
120
- console.error('[Client] Failed to import Users page:', err);
72
+ return;
121
73
  }
122
74
  }
123
75
  }
124
76
 
125
- // Final fallback if still no component
126
- if (!PageComponent) {
127
- // Create a simple fallback component
128
- PageComponent = ({ initialState }) => jsx('div', {
129
- style: 'padding: 20px; max-width: 800px; margin: 0 auto;'
130
- }, [
131
- jsx('h1', {}, ['Page Not Found']),
132
- jsx('p', {}, [`No component found for path: ${initialState.route}`]),
133
- jsx('a', { href: '/', style: 'color: #0066cc;' }, ['Go to Home'])
134
- ]);
77
+ // Get the current page props
78
+ let pageProps = initialState.pageProps || {};
79
+
80
+ // If the page defines getServerSideProps, fetch data
81
+ if (Page.getServerSideProps) {
82
+ try {
83
+ const response = await fetch(`/api/__props${path}`);
84
+ if (response.ok) {
85
+ const data = await response.json();
86
+ pageProps = data.props || {};
87
+ }
88
+ } catch (error) {
89
+ console.error('[Router] Error fetching page props:', error);
90
+ }
135
91
  }
136
92
 
137
- // Update state with current route and timestamp
138
- initialState.route = currentPath;
139
- initialState.timestamp = new Date().toISOString();
140
- initialState.serverRendered = false; // This is now a client render
93
+ // Update the state with current route
94
+ const updatedState = {
95
+ ...initialState,
96
+ route: path,
97
+ pageProps
98
+ };
99
+
100
+ // Render the page
101
+ const rootElement = document.getElementById('root');
102
+ if (!rootElement) return;
141
103
 
142
- // Render or hydrate
143
- if (isHydrating) {
104
+ if (isHydrating && path === initialState.route) {
144
105
  console.log('[Client] Hydrating server-rendered content');
145
- hydrate(<PageComponent initialState={initialState} />, rootElement);
146
- // After hydration is complete, set to false for future renders
106
+ hydrate(<Page {...pageProps} initialState={updatedState} />, rootElement);
147
107
  isHydrating = false;
148
108
  } else {
149
109
  console.log('[Client] Rendering client-side');
150
- render(<PageComponent initialState={initialState} />, rootElement);
110
+ render(<Page {...pageProps} initialState={updatedState} />, rootElement);
151
111
  }
152
112
  } catch (error) {
153
- console.error('[Client] Error rendering app:', error);
154
-
155
- // Render error message
156
- const rootElement = document.getElementById('root');
157
- if (rootElement) {
158
- rootElement.innerHTML = `
159
- <div style="padding: 20px; color: red;">
160
- <h1>Error</h1>
161
- <p>${error.message}</p>
162
- <pre>${error.stack}</pre>
163
- </div>
164
- `;
165
- }
113
+ console.error('[Router] Navigation error:', error);
166
114
  }
167
115
  }
168
116
 
169
- // Initial render/hydrate
170
- renderApp();
117
+ // Handle initial route
118
+ handleRouteChange(window.location.pathname, false);
171
119
 
172
- // Set up client-side navigation
120
+ // Handle client-side navigation
173
121
  document.addEventListener('click', (e) => {
174
- // Find if the clicked element is an anchor or inside an anchor
175
- let target = e.target;
122
+ let target = e.target as HTMLElement | null;
123
+
124
+ // Find closest anchor element
176
125
  while (target && target.tagName !== 'A') {
177
126
  target = target.parentElement;
178
127
  if (!target) break;
179
128
  }
180
129
 
181
- // If we found an anchor that's for internal navigation
182
130
  if (target &&
183
131
  target.tagName === 'A' &&
184
- target.getAttribute('href') &&
185
- target.getAttribute('href').startsWith('/') &&
186
- !target.getAttribute('href').startsWith('//') &&
132
+ target.getAttribute('href') &&
133
+ target.getAttribute('href')?.startsWith('/') &&
134
+ !target.getAttribute('href')?.startsWith('//') &&
187
135
  !target.getAttribute('target')) {
188
- e.preventDefault();
189
- const href = target.getAttribute('href');
190
-
191
- // Update history
192
- window.history.pushState(null, '', href);
193
136
 
194
- // Set to false since we're doing client-side navigation now
195
- isHydrating = false;
196
-
197
- // Re-render the app
198
- renderApp();
137
+ e.preventDefault();
138
+ const href = target.getAttribute('href') || '/';
139
+ handleRouteChange(href);
199
140
  }
200
141
  });
201
142
 
202
143
  // Handle back/forward navigation
203
144
  window.addEventListener('popstate', () => {
204
- // Not hydrating during history navigation
205
- isHydrating = false;
206
- renderApp();
145
+ handleRouteChange(window.location.pathname, false);
207
146
  });
208
147
 
209
- // Setup live reload for development
148
+ // Set up socket.io for live reload in development
210
149
  if (typeof io !== 'undefined') {
211
150
  const socket = io();
212
-
213
151
  socket.on('reload', () => {
214
152
  console.log('[Dev] Reloading page due to file changes');
215
153
  window.location.reload();
216
154
  });
217
-
218
- socket.on('welcome', (data) => {
219
- console.log('[Dev] Connected to development server:', data);
220
- });
221
155
  }
@@ -0,0 +1,28 @@
1
+ // Next.js-like global middleware
2
+ import { Request, Response, NextFunction } from 'express';
3
+
4
+ // Extend the Express Request interface
5
+ declare global {
6
+ namespace Express {
7
+ interface Request {
8
+ timestamp: string;
9
+ }
10
+ }
11
+ }
12
+
13
+ // This middleware will be applied to all routes (API and pages)
14
+ export default async function middleware(req: Request, res: Response, next?: NextFunction) {
15
+ // Add request timestamp
16
+ req.timestamp = await new Date().toISOString();
17
+
18
+ // Log all requests in development mode
19
+ if (process.env.NODE_ENV !== 'production') {
20
+ console.log(`[${req.timestamp}] ${req.method} ${req.url}`);
21
+ }
22
+
23
+ // Example of response header modification
24
+ res.setHeader('X-Powered-By', 'Frontend Hamroun');
25
+
26
+ // Continue to next middleware
27
+ if (next) next();
28
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+
3
+ export default function NotFound({ initialState }) {
4
+ return (
5
+ <div className="not-found-container max-w-4xl mx-auto p-6">
6
+ <div className="bg-gray-50 border border-gray-200 rounded-lg p-8 shadow-sm">
7
+ <h1 className="text-3xl font-bold text-gray-700 mb-4">Page Not Found</h1>
8
+
9
+ <p className="text-lg text-gray-600 mb-4">
10
+ The page you are looking for does not exist or has been moved.
11
+ </p>
12
+
13
+ <p className="text-gray-600 mb-6">
14
+ Path: <code className="bg-gray-100 px-2 py-1 rounded">{initialState?.route || 'unknown'}</code>
15
+ </p>
16
+
17
+ <div className="mt-6">
18
+ <a
19
+ href="/"
20
+ className="inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
21
+ >
22
+ Back to Home
23
+ </a>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ );
28
+ }
File without changes
@@ -0,0 +1,11 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+ import Layout from '../components/Layout';
3
+
4
+ // This is the main App wrapper component similar to Next.js _app.js
5
+ export default function App({ Component, pageProps, initialState }) {
6
+ return (
7
+ <Layout>
8
+ <Component {...pageProps} initialState={initialState} />
9
+ </Layout>
10
+ );
11
+ }
@@ -0,0 +1,25 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+
3
+ export default function Document({
4
+ title = 'Frontend Hamroun App',
5
+ headContent,
6
+ bodyContent,
7
+ scripts
8
+ }) {
9
+ return (
10
+ <html lang="en">
11
+ <head>
12
+ <meta charSet="UTF-8" />
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
+ <title>{title}</title>
15
+ <link rel="stylesheet" href="/styles.css" />
16
+ {headContent}
17
+ </head>
18
+ <body>
19
+ <div id="root">{bodyContent}</div>
20
+ <script src="/build/main.js" type="module"></script>
21
+ {scripts}
22
+ </body>
23
+ </html>
24
+ );
25
+ }
@@ -0,0 +1,45 @@
1
+ import { jsx, useState, useEffect } from 'frontend-hamroun';
2
+
3
+ export default function ErrorPage({ initialState }) {
4
+ const { error } = initialState || {};
5
+ const [showDetails, setShowDetails] = useState(false);
6
+ const isDev = process.env.NODE_ENV !== 'production';
7
+
8
+ return (
9
+ <div className="error-page-container max-w-4xl mx-auto p-6">
10
+ <div className="bg-red-50 border border-red-200 rounded-lg p-8 shadow-sm">
11
+ <h1 className="text-3xl font-bold text-red-700 mb-4">Something went wrong</h1>
12
+
13
+ <p className="text-lg text-red-600 mb-4">
14
+ {error?.message || "An unexpected error occurred"}
15
+ </p>
16
+
17
+ {isDev && error?.stack && (
18
+ <div className="mt-6">
19
+ <button
20
+ className="text-blue-600 underline mb-2"
21
+ onClick={() => setShowDetails(!showDetails)}
22
+ >
23
+ {showDetails ? "Hide" : "Show"} technical details
24
+ </button>
25
+
26
+ {showDetails && (
27
+ <pre className="bg-gray-100 p-4 rounded-md text-sm overflow-auto max-h-96 text-gray-800">
28
+ {error.stack}
29
+ </pre>
30
+ )}
31
+ </div>
32
+ )}
33
+
34
+ <div className="mt-6">
35
+ <a
36
+ href="/"
37
+ className="inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
38
+ >
39
+ Back to Home
40
+ </a>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,71 @@
1
+ import { jsx } from 'frontend-hamroun';
2
+ import Layout from '../components/Layout';
3
+
4
+ export default function AboutPage({ initialState }) {
5
+ return (
6
+ <Layout title="About - Frontend Hamroun">
7
+ <div className="max-w-4xl mx-auto">
8
+ <h1 className="text-3xl font-bold text-blue-600 mb-6">
9
+ About Frontend Hamroun
10
+ </h1>
11
+
12
+ <div className="bg-white shadow-lg rounded-lg p-8 mb-8">
13
+ <h2 className="text-xl font-semibold text-gray-800 mb-4">What is Frontend Hamroun?</h2>
14
+ <p className="text-gray-600 mb-4">
15
+ Frontend Hamroun is a lightweight JavaScript framework for building modern web applications.
16
+ It provides a familiar component-based architecture with hooks, JSX support, and
17
+ server-side rendering capabilities.
18
+ </p>
19
+ <p className="text-gray-600 mb-4">
20
+ This framework is designed to be simple yet powerful, offering the essential features
21
+ needed for web application development without the complexity of larger frameworks.
22
+ </p>
23
+
24
+ <h3 className="text-lg font-medium text-gray-700 mt-6 mb-2">Key Features:</h3>
25
+ <ul className="list-disc pl-6 text-gray-600 space-y-2">
26
+ <li>Component-based architecture</li>
27
+ <li>JSX support</li>
28
+ <li>Hooks for state and effects</li>
29
+ <li>Server-side rendering</li>
30
+ <li>Minimal API surface</li>
31
+ <li>File-based routing</li>
32
+ <li>Built-in API routes</li>
33
+ </ul>
34
+ </div>
35
+
36
+ <div className="bg-white shadow-lg rounded-lg p-8">
37
+ <h2 className="text-xl font-semibold text-gray-800 mb-4">Getting Started</h2>
38
+ <p className="text-gray-600 mb-4">
39
+ This application was created using the Frontend Hamroun fullstack template,
40
+ which provides a complete setup for building applications with server-side rendering,
41
+ API routes, and client-side navigation.
42
+ </p>
43
+
44
+ <div className="bg-gray-50 p-4 rounded-md mt-4">
45
+ <h3 className="text-md font-medium text-gray-700 mb-2">Quick Start:</h3>
46
+ <pre className="bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto">
47
+ <code>{`# Create a new application
48
+ npx frontend-hamroun create my-app
49
+
50
+ # Change directory
51
+ cd my-app
52
+
53
+ # Start the development server
54
+ npm run dev`}</code>
55
+ </pre>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </Layout>
60
+ );
61
+ }
62
+
63
+ // Server-side data fetching
64
+ export async function getServerSideProps() {
65
+ return {
66
+ props: {
67
+ pageTitle: 'About Frontend Hamroun',
68
+ description: 'Learn more about the Frontend Hamroun framework'
69
+ }
70
+ };
71
+ }
@@ -0,0 +1,73 @@
1
+ // Dynamic API Route for individual user operations
2
+ import { Request, Response } from 'express';
3
+
4
+ // Sample data store
5
+ const users = [
6
+ { id: 1, name: 'User 1', email: 'user1@example.com' },
7
+ { id: 2, name: 'User 2', email: 'user2@example.com' },
8
+ { id: 3, name: 'User 3', email: 'user3@example.com' }
9
+ ];
10
+
11
+ // Utility to find user by ID
12
+ const findUser = (id: number) => users.find(user => user.id === id);
13
+
14
+ // GET handler for retrieving a specific user
15
+ export async function get(req: Request, res: Response) {
16
+ const userId = parseInt(req.params.id);
17
+
18
+ if (isNaN(userId)) {
19
+ return res.status(400).json({ error: 'Invalid user ID' });
20
+ }
21
+
22
+ const user = findUser(userId);
23
+
24
+ if (!user) {
25
+ return res.status(404).json({ error: 'User not found' });
26
+ }
27
+
28
+ res.json(user);
29
+ }
30
+
31
+ // PUT handler for updating a user
32
+ export async function put(req: Request, res: Response) {
33
+ const userId = parseInt(req.params.id);
34
+
35
+ if (isNaN(userId)) {
36
+ return res.status(400).json({ error: 'Invalid user ID' });
37
+ }
38
+
39
+ const userIndex = users.findIndex(user => user.id === userId);
40
+
41
+ if (userIndex === -1) {
42
+ return res.status(404).json({ error: 'User not found' });
43
+ }
44
+
45
+ // Update user, but preserve the ID
46
+ const updatedUser = { ...req.body, id: userId };
47
+ users[userIndex] = updatedUser;
48
+
49
+ res.json(updatedUser);
50
+ }
51
+
52
+ // DELETE handler for removing a user
53
+ export async function del(req: Request, res: Response) {
54
+ const userId = parseInt(req.params.id);
55
+
56
+ if (isNaN(userId)) {
57
+ return res.status(400).json({ error: 'Invalid user ID' });
58
+ }
59
+
60
+ const userIndex = users.findIndex(user => user.id === userId);
61
+
62
+ if (userIndex === -1) {
63
+ return res.status(404).json({ error: 'User not found' });
64
+ }
65
+
66
+ // Remove the user
67
+ users.splice(userIndex, 1);
68
+
69
+ res.status(204).end();
70
+ }
71
+
72
+ // Use delete handler for DELETE method since 'delete' is a reserved word
73
+ export const DELETE = del;
@@ -0,0 +1,43 @@
1
+ // Next.js-like API Route for Users
2
+ import { Request, Response } from 'express';
3
+
4
+ // Sample data store
5
+ const users = [
6
+ { id: 1, name: 'User 1', email: 'user1@example.com' },
7
+ { id: 2, name: 'User 2', email: 'user2@example.com' },
8
+ { id: 3, name: 'User 3', email: 'user3@example.com' }
9
+ ];
10
+
11
+ // GET handler for retrieving all users
12
+ export async function get(req: Request, res: Response) {
13
+ // Option to simulate delay for testing loading states
14
+ const delay = req.query.delay ? parseInt(req.query.delay as string) : 0;
15
+
16
+ if (delay) {
17
+ await new Promise(resolve => setTimeout(resolve, delay));
18
+ }
19
+
20
+ res.json(users);
21
+ }
22
+
23
+ // POST handler for creating a new user
24
+ export async function post(req: Request, res: Response) {
25
+ try {
26
+ const newUser = req.body;
27
+
28
+ // Validation
29
+ if (!newUser.name || !newUser.email) {
30
+ return res.status(400).json({ error: 'Name and email are required' });
31
+ }
32
+
33
+ // Generate new ID
34
+ const newId = Math.max(0, ...users.map(u => u.id)) + 1;
35
+ const createdUser = { ...newUser, id: newId };
36
+
37
+ users.push(createdUser);
38
+
39
+ res.status(201).json(createdUser);
40
+ } catch (error) {
41
+ res.status(500).json({ error: 'Failed to create user' });
42
+ }
43
+ }