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.
- package/dist/batch/package.json +16 -0
- package/dist/client-router/package.json +16 -0
- package/dist/component/package.json +16 -0
- package/dist/context/package.json +16 -0
- package/dist/event-bus/package.json +16 -0
- package/dist/forms/package.json +16 -0
- package/dist/hooks/package.json +16 -0
- package/dist/jsx-runtime/package.json +16 -0
- package/dist/lifecycle-events/package.json +16 -0
- package/dist/package.json +71 -0
- package/dist/render-component/package.json +16 -0
- package/dist/renderer/package.json +16 -0
- package/dist/router/package.json +16 -0
- package/dist/server/package.json +17 -0
- package/dist/server/src/client-router.d.ts +60 -0
- package/dist/server/src/client-router.js +210 -0
- package/dist/server/src/client-router.js.map +1 -0
- package/dist/server/src/component.js +1 -1
- package/dist/server/src/event-bus.d.ts +23 -0
- package/dist/server/src/event-bus.js +75 -0
- package/dist/server/src/event-bus.js.map +1 -0
- package/dist/server/src/forms.d.ts +40 -0
- package/dist/server/src/forms.js +148 -0
- package/dist/server/src/forms.js.map +1 -0
- package/dist/server/src/hooks.js +2 -2
- package/dist/server/src/index.js +19 -11
- package/dist/server/src/lifecycle-events.d.ts +108 -0
- package/dist/server/src/lifecycle-events.js +177 -0
- package/dist/server/src/lifecycle-events.js.map +1 -0
- package/dist/server/src/renderComponent.js +1 -1
- package/dist/server/src/renderer.js +3 -3
- package/dist/server/src/router.d.ts +55 -0
- package/dist/server/src/router.js +166 -0
- package/dist/server/src/router.js.map +1 -0
- package/dist/server/src/server/index.d.ts +75 -2
- package/dist/server/src/server/index.js +224 -8
- package/dist/server/src/server/index.js.map +1 -1
- package/dist/server/src/server/server.js +1 -1
- package/dist/server/src/server/templates.d.ts +28 -0
- package/dist/server/src/server/templates.js +204 -0
- package/dist/server/src/server/templates.js.map +1 -0
- package/dist/server/src/server/utils.d.ts +70 -0
- package/dist/server/src/server/utils.js +156 -0
- package/dist/server/src/server/utils.js.map +1 -0
- package/dist/server/src/server-renderer.js +1 -1
- package/dist/server/src/store.d.ts +41 -0
- package/dist/server/src/store.js +99 -0
- package/dist/server/src/store.js.map +1 -0
- package/dist/server/src/utils.d.ts +46 -0
- package/dist/server/src/utils.js +144 -0
- package/dist/server/src/utils.js.map +1 -0
- package/dist/server/tsconfig.server.tsbuildinfo +1 -1
- package/dist/server-renderer/package.json +16 -0
- package/dist/store/package.json +16 -0
- package/dist/types/package.json +16 -0
- package/dist/utils/package.json +16 -0
- package/dist/vdom/package.json +16 -0
- package/dist/wasm/package.json +16 -0
- package/package.json +14 -13
- package/templates/complete-app/build.js +284 -0
- package/templates/complete-app/package.json +40 -0
- package/templates/complete-app/public/styles.css +345 -0
- package/templates/complete-app/src/api/index.js +31 -0
- package/templates/complete-app/src/client.js +93 -0
- package/templates/complete-app/src/components/App.js +66 -0
- package/templates/complete-app/src/components/Footer.js +19 -0
- package/templates/complete-app/src/components/Header.js +38 -0
- package/templates/complete-app/src/pages/About.js +59 -0
- package/templates/complete-app/src/pages/Home.js +54 -0
- package/templates/complete-app/src/pages/WasmDemo.js +136 -0
- package/templates/complete-app/src/server.js +186 -0
- package/templates/complete-app/src/wasm/build.bat +16 -0
- package/templates/complete-app/src/wasm/build.sh +16 -0
- package/templates/complete-app/src/wasm/example.go +101 -0
- package/templates/fullstack-app/build/main.css +225 -15
- package/templates/fullstack-app/build/main.css.map +2 -2
- package/templates/fullstack-app/build/main.js +657 -372
- package/templates/fullstack-app/build/main.js.map +4 -4
- package/templates/fullstack-app/build.ts +3 -4
- package/templates/fullstack-app/public/styles.css +222 -15
- package/templates/fullstack-app/server.ts +46 -12
- package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
- package/templates/fullstack-app/src/components/ErrorBoundary.tsx +36 -0
- package/templates/fullstack-app/src/components/Layout.tsx +23 -26
- package/templates/fullstack-app/src/components/StateDemo.tsx +207 -0
- package/templates/fullstack-app/src/components/UserList.tsx +30 -13
- package/templates/fullstack-app/src/data/api.ts +173 -38
- package/templates/fullstack-app/src/main.tsx +88 -154
- package/templates/fullstack-app/src/middleware.ts +28 -0
- package/templates/fullstack-app/src/pages/404.tsx +28 -0
- package/templates/fullstack-app/src/pages/[id].tsx +0 -0
- package/templates/fullstack-app/src/pages/_app.tsx +11 -0
- package/templates/fullstack-app/src/pages/_document.tsx +25 -0
- package/templates/fullstack-app/src/pages/_error.tsx +45 -0
- package/templates/fullstack-app/src/pages/about.tsx +71 -0
- package/templates/fullstack-app/src/pages/api/users/[id].ts +73 -0
- package/templates/fullstack-app/src/pages/api/users/index.ts +43 -0
- package/templates/fullstack-app/src/pages/index.tsx +97 -20
- package/templates/fullstack-app/src/pages/users/[id].tsx +153 -0
- package/templates/fullstack-app/src/pages/wasm-demo.tsx +1 -0
- package/templates/go/example.go +99 -86
- package/templates/go-wasm-app/babel.config.js +8 -2
- package/templates/go-wasm-app/build.config.js +62 -0
- package/templates/go-wasm-app/build.js +218 -0
- package/templates/go-wasm-app/package.json +21 -12
- package/templates/go-wasm-app/server.js +59 -510
- package/templates/go-wasm-app/src/app.js +173 -0
- package/templates/go-wasm-app/vite.config.js +16 -5
- package/templates/ssr-template/client.js +54 -26
- package/templates/ssr-template/server.js +5 -28
- package/templates/ssr-template/vite.config.js +21 -5
- package/dist/server/wasm.d.ts +0 -7
- 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
|
-
//
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
//
|
30
|
-
|
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
|
58
|
-
async function
|
28
|
+
// Function to handle navigation
|
29
|
+
async function handleRouteChange(path: string, isPushState = true) {
|
59
30
|
try {
|
60
|
-
|
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
|
-
//
|
66
|
-
|
67
|
-
|
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
|
-
//
|
73
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
120
|
-
console.error('[Client] Failed to import Users page:', err);
|
72
|
+
return;
|
121
73
|
}
|
122
74
|
}
|
123
75
|
}
|
124
76
|
|
125
|
-
//
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
143
|
-
if (isHydrating) {
|
104
|
+
if (isHydrating && path === initialState.route) {
|
144
105
|
console.log('[Client] Hydrating server-rendered content');
|
145
|
-
hydrate(<
|
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(<
|
110
|
+
render(<Page {...pageProps} initialState={updatedState} />, rootElement);
|
151
111
|
}
|
152
112
|
} catch (error) {
|
153
|
-
console.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
|
-
//
|
170
|
-
|
117
|
+
// Handle initial route
|
118
|
+
handleRouteChange(window.location.pathname, false);
|
171
119
|
|
172
|
-
//
|
120
|
+
// Handle client-side navigation
|
173
121
|
document.addEventListener('click', (e) => {
|
174
|
-
|
175
|
-
|
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')
|
186
|
-
!target.getAttribute('href')
|
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
|
-
|
195
|
-
|
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
|
-
|
205
|
-
isHydrating = false;
|
206
|
-
renderApp();
|
145
|
+
handleRouteChange(window.location.pathname, false);
|
207
146
|
});
|
208
147
|
|
209
|
-
//
|
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
|
+
}
|