baraqex 1.0.74 → 2.0.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.
- package/CHANGELOG.md +73 -0
- package/README.md +272 -472
- package/bin/baraqex.js +150 -12
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2319 -205
- package/dist/browser.js.map +4 -4
- package/dist/core/batch.d.ts +10 -0
- package/dist/core/batch.d.ts.map +1 -0
- package/dist/core/component.d.ts +69 -0
- package/dist/core/component.d.ts.map +1 -0
- package/dist/core/context.d.ts +22 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/errors.d.ts +125 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/event-bus.d.ts +29 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/forms.d.ts +128 -0
- package/dist/core/forms.d.ts.map +1 -0
- package/dist/core/hooks.d.ts +84 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/jsx-runtime.d.ts +45 -0
- package/dist/core/jsx-runtime.d.ts.map +1 -0
- package/dist/core/lifecycle-events.d.ts +71 -0
- package/dist/core/lifecycle-events.d.ts.map +1 -0
- package/dist/core/renderer.d.ts +39 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/router.d.ts +108 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/server-renderer.d.ts +26 -0
- package/dist/core/server-renderer.d.ts.map +1 -0
- package/dist/core/store.d.ts +102 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/types.d.ts +66 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/utils.d.ts +153 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/vdom.d.ts +29 -0
- package/dist/core/vdom.d.ts.map +1 -0
- package/dist/index.d.ts +39 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2592 -508
- package/dist/index.js.map +4 -4
- package/dist/server/auth.d.ts +8 -3
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +173 -112
- package/dist/server/index.js.map +4 -4
- package/dist/server/middleware.d.ts +10 -1
- package/dist/server/middleware.d.ts.map +1 -1
- package/package.json +224 -124
- package/templates/basic-app/src/main.jsx +336 -0
- package/templates/complete-app/api/hello.js +0 -0
- package/templates/complete-app/client.js +58 -0
- package/templates/complete-app/lib/frontend-hamroun.js +182 -0
- package/templates/complete-app/package.json +18 -0
- package/templates/complete-app/pages/about.js +119 -0
- package/templates/complete-app/pages/about.jsx +0 -0
- package/templates/complete-app/pages/index.js +157 -0
- package/templates/complete-app/pages/index.jsx +0 -0
- package/templates/complete-app/pages/wasm-demo.js +290 -0
- package/templates/complete-app/pages/wasm-demo.jsx +0 -0
- package/templates/complete-app/public/client.js +89 -0
- package/templates/complete-app/public/index.html +118 -0
- package/templates/complete-app/public/styles.css +76 -0
- package/templates/complete-app/readme.md +188 -0
- package/templates/complete-app/server.js +226 -0
- package/templates/complete-app/server.ts +275 -0
- package/templates/complete-app/src/App.tsx +59 -0
- package/templates/complete-app/src/client.ts +61 -0
- package/templates/complete-app/src/client.tsx +18 -0
- package/templates/complete-app/src/pages/index.tsx +51 -0
- package/templates/complete-app/src/server.ts +218 -0
- package/templates/complete-app/tsconfig.json +22 -0
- package/templates/complete-app/tsconfig.server.json +19 -0
- package/templates/complete-app/vite.config.js +57 -0
- package/templates/complete-app/vite.config.ts +30 -0
- package/templates/complete-app/wasm/build.bat +0 -0
- package/templates/complete-app/wasm/build.sh +0 -0
- package/templates/complete-app/wasm/example.go +0 -0
- package/templates/fullstack-app/README.md +37 -0
- package/templates/fullstack-app/api/hello.js +11 -0
- package/templates/fullstack-app/api/hello.ts +18 -0
- package/templates/fullstack-app/api/users/[id].ts +73 -0
- package/templates/fullstack-app/api/users/index.ts +32 -0
- package/templates/fullstack-app/build/main.css +874 -0
- package/templates/fullstack-app/build/main.css.map +7 -0
- package/templates/fullstack-app/build/main.js +967 -0
- package/templates/fullstack-app/build/main.js.map +7 -0
- package/templates/fullstack-app/build.ts +210 -0
- package/templates/fullstack-app/index.html +36 -0
- package/templates/fullstack-app/package.json +35 -0
- package/templates/fullstack-app/postcss.config.js +6 -0
- package/templates/fullstack-app/process-tailwind.js +45 -0
- package/templates/fullstack-app/public/_redirects +1 -0
- package/templates/fullstack-app/public/route-handler.js +47 -0
- package/templates/fullstack-app/public/spa-fix.html +17 -0
- package/templates/fullstack-app/public/styles.css +975 -0
- package/templates/fullstack-app/public/tailwind.css +15 -0
- package/templates/fullstack-app/server.js +267 -0
- package/templates/fullstack-app/server.ts +474 -0
- package/templates/fullstack-app/src/README.md +55 -0
- package/templates/fullstack-app/src/client.js +87 -0
- 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 +42 -0
- package/templates/fullstack-app/src/components/StateDemo.tsx +207 -0
- package/templates/fullstack-app/src/components/UserList.tsx +44 -0
- package/templates/fullstack-app/src/config.ts +42 -0
- package/templates/fullstack-app/src/data/api.ts +206 -0
- package/templates/fullstack-app/src/main.tsx +155 -0
- 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/index.tsx +67 -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 +110 -0
- package/templates/fullstack-app/src/pages/users/[id].tsx +153 -0
- package/templates/fullstack-app/src/pages/users.tsx +60 -0
- package/templates/fullstack-app/src/pages/wasm-demo.tsx +1 -0
- package/templates/fullstack-app/src/router.ts +255 -0
- package/templates/fullstack-app/src/styles.css +5 -0
- package/templates/fullstack-app/tailwind.config.js +11 -0
- package/templates/fullstack-app/tsconfig.json +18 -0
- package/templates/fullstack-app/vite.config.js +87 -0
- package/templates/go/build.bat +36 -0
- package/templates/go/build.sh +43 -0
- package/templates/go/example.go +205 -0
- package/templates/go-wasm-app/README.md +38 -0
- package/templates/go-wasm-app/babel.config.js +21 -0
- package/templates/go-wasm-app/build-client.js +49 -0
- package/templates/go-wasm-app/build-wasm.js +237 -0
- 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 +32 -0
- package/templates/go-wasm-app/public/index.html +128 -0
- package/templates/go-wasm-app/public/styles.css +197 -0
- package/templates/go-wasm-app/public/wasm/example.wasm +0 -0
- package/templates/go-wasm-app/public/wasm/wasm_exec.js +561 -0
- package/templates/go-wasm-app/public/wasm/wasm_exec_node.js +39 -0
- package/templates/go-wasm-app/server.js +70 -0
- package/templates/go-wasm-app/src/App.jsx +38 -0
- package/templates/go-wasm-app/src/app.js +173 -0
- package/templates/go-wasm-app/src/client.js +57 -0
- package/templates/go-wasm-app/src/components/Footer.jsx +13 -0
- package/templates/go-wasm-app/src/components/Header.jsx +19 -0
- package/templates/go-wasm-app/src/components/WasmDemo.jsx +120 -0
- package/templates/go-wasm-app/src/main.jsx +12 -0
- package/templates/go-wasm-app/src/wasm/example.go +75 -0
- package/templates/go-wasm-app/tsconfig.server.json +18 -0
- package/templates/go-wasm-app/vite.config.js +45 -0
- package/templates/ssr-template/package.json +16 -10
- package/templates/wasm/package.json +43 -43
- package/templates/wasm/src/App.tsx +2 -2
- package/dist/forms/index.d.ts +0 -2
- package/dist/forms/index.d.ts.map +0 -1
- package/dist/forms/types.d.ts +0 -2
- package/dist/forms/types.d.ts.map +0 -1
- package/dist/frontend/components/WasmLoader.d.ts +0 -2
- package/dist/frontend/components/WasmLoader.d.ts.map +0 -1
- package/dist/frontend/hooks/useWasm.d.ts +0 -2
- package/dist/frontend/hooks/useWasm.d.ts.map +0 -1
- package/dist/frontend/index.d.ts +0 -2
- package/dist/frontend/index.d.ts.map +0 -1
- package/dist/router/index.d.ts +0 -2
- package/dist/router/index.d.ts.map +0 -1
- package/dist/router/types.d.ts +0 -2
- package/dist/router/types.d.ts.map +0 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/types.d.ts +0 -2
- package/dist/store/types.d.ts.map +0 -1
- package/templates/basic-app/package-lock.json +0 -7173
- package/templates/fullstack-template/package-lock.json +0 -3558
- package/templates/ssr-template/package-lock.json +0 -5380
- package/templates/wasm/package-lock.json +0 -5347
- package/templates/wasm/public/example.wasm +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { jsx, useState, useEffect, useMemo } from 'frontend-hamroun';
|
|
2
|
+
|
|
3
|
+
// Define our state types for better type safety
|
|
4
|
+
interface CounterState {
|
|
5
|
+
count: number;
|
|
6
|
+
lastUpdated: string | null;
|
|
7
|
+
history: number[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function StateDemo() {
|
|
11
|
+
// More comprehensive state with history tracking
|
|
12
|
+
const [state, setState] = useState<CounterState>({
|
|
13
|
+
count: 0,
|
|
14
|
+
lastUpdated: null,
|
|
15
|
+
history: []
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Track UI state separately
|
|
19
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
20
|
+
const [showHistory, setShowHistory] = useState(false);
|
|
21
|
+
|
|
22
|
+
// Check for server-side rendering
|
|
23
|
+
const isSSR = typeof window === 'undefined';
|
|
24
|
+
|
|
25
|
+
// Calculate derived state with useMemo for performance
|
|
26
|
+
const stats = useMemo(() => {
|
|
27
|
+
// Ensure we have a valid history array
|
|
28
|
+
if (isSSR || !state || !state.history || state.history.length === 0) {
|
|
29
|
+
return { avg: 0, max: 0, min: 0 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const sum = state.history.reduce((a, b) => a + b, 0);
|
|
34
|
+
return {
|
|
35
|
+
avg: parseFloat((sum / state.history.length).toFixed(1)),
|
|
36
|
+
max: Math.max(...state.history),
|
|
37
|
+
min: Math.min(...state.history)
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error calculating stats:', error);
|
|
41
|
+
return { avg: 0, max: 0, min: 0 };
|
|
42
|
+
}
|
|
43
|
+
}, [state?.history]);
|
|
44
|
+
|
|
45
|
+
// Handle increment with history tracking
|
|
46
|
+
const increment = () => {
|
|
47
|
+
if (isSSR) return;
|
|
48
|
+
|
|
49
|
+
setIsLoading(true);
|
|
50
|
+
|
|
51
|
+
// Simulate async operation
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
setState(prevState => ({
|
|
54
|
+
count: prevState.count + 1,
|
|
55
|
+
lastUpdated: new Date().toLocaleTimeString(),
|
|
56
|
+
history: [...(prevState.history || []), prevState.count + 1]
|
|
57
|
+
}));
|
|
58
|
+
setIsLoading(false);
|
|
59
|
+
}, 300);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Handle decrement with bounds checking
|
|
63
|
+
const decrement = () => {
|
|
64
|
+
if (isSSR) return;
|
|
65
|
+
|
|
66
|
+
setIsLoading(true);
|
|
67
|
+
|
|
68
|
+
// Simulate async operation
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
setState(prevState => ({
|
|
71
|
+
count: Math.max(0, prevState.count - 1),
|
|
72
|
+
lastUpdated: new Date().toLocaleTimeString(),
|
|
73
|
+
history: prevState.count > 0 ? [...(prevState.history || []), prevState.count - 1] : (prevState.history || [])
|
|
74
|
+
}));
|
|
75
|
+
setIsLoading(false);
|
|
76
|
+
}, 300);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Reset counter
|
|
80
|
+
const reset = () => {
|
|
81
|
+
if (isSSR) return;
|
|
82
|
+
|
|
83
|
+
setState({
|
|
84
|
+
count: 0,
|
|
85
|
+
lastUpdated: new Date().toLocaleTimeString(),
|
|
86
|
+
history: []
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Toggle history visibility
|
|
91
|
+
const toggleHistory = () => {
|
|
92
|
+
if (isSSR) return;
|
|
93
|
+
|
|
94
|
+
setShowHistory(prev => !prev);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// For SSR, return a simpler version of the component
|
|
98
|
+
if (isSSR) {
|
|
99
|
+
return (
|
|
100
|
+
<div className="bg-white shadow-lg rounded-lg p-6 mb-8">
|
|
101
|
+
<h2 className="text-xl font-semibold text-gray-800 mb-4">
|
|
102
|
+
State Management Demo
|
|
103
|
+
</h2>
|
|
104
|
+
<div className="flex items-center justify-between mb-4">
|
|
105
|
+
<button className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg">-</button>
|
|
106
|
+
<span className="text-2xl font-bold mx-4">0</span>
|
|
107
|
+
<button className="px-4 py-2 bg-blue-600 text-white rounded-lg">+</button>
|
|
108
|
+
</div>
|
|
109
|
+
<p className="text-sm text-gray-500">Interactive counter (client-side only)</p>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Regular client-side render
|
|
115
|
+
return (
|
|
116
|
+
<div className="bg-white shadow-lg rounded-lg p-6 mb-8">
|
|
117
|
+
<h2 className="text-xl font-semibold text-gray-800 mb-4">
|
|
118
|
+
State Management Demo
|
|
119
|
+
</h2>
|
|
120
|
+
|
|
121
|
+
<div className="mb-6">
|
|
122
|
+
<div className="flex items-center justify-between mb-4">
|
|
123
|
+
<button
|
|
124
|
+
onClick={decrement}
|
|
125
|
+
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 disabled:opacity-50"
|
|
126
|
+
disabled={state.count === 0 || isLoading}
|
|
127
|
+
>
|
|
128
|
+
{isLoading ? '...' : '-'}
|
|
129
|
+
</button>
|
|
130
|
+
|
|
131
|
+
<span className="text-2xl font-bold mx-4">{state.count}</span>
|
|
132
|
+
|
|
133
|
+
<button
|
|
134
|
+
onClick={increment}
|
|
135
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
136
|
+
disabled={isLoading}
|
|
137
|
+
>
|
|
138
|
+
{isLoading ? '...' : '+'}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<button
|
|
143
|
+
onClick={reset}
|
|
144
|
+
className="text-sm text-gray-600 hover:text-red-600"
|
|
145
|
+
disabled={state.count === 0 && (!state.history || state.history.length === 0)}
|
|
146
|
+
>
|
|
147
|
+
Reset Counter
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{state.lastUpdated && (
|
|
152
|
+
<div className="mb-4">
|
|
153
|
+
<p className="text-sm text-gray-500">
|
|
154
|
+
Last updated: {state.lastUpdated}
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{state.history && state.history.length > 0 && (
|
|
160
|
+
<div className="border-t pt-4 mt-4">
|
|
161
|
+
<div className="flex items-center justify-between mb-2">
|
|
162
|
+
<h3 className="text-md font-medium text-gray-700">Statistics</h3>
|
|
163
|
+
<button
|
|
164
|
+
onClick={toggleHistory}
|
|
165
|
+
className="text-sm text-blue-600 hover:underline"
|
|
166
|
+
>
|
|
167
|
+
{showHistory ? 'Hide History' : 'Show History'}
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div className="grid grid-cols-3 gap-2 mb-3">
|
|
172
|
+
<div className="bg-blue-50 p-2 rounded text-center">
|
|
173
|
+
<div className="text-xs text-gray-500">Average</div>
|
|
174
|
+
<div className="font-bold">{stats.avg}</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div className="bg-green-50 p-2 rounded text-center">
|
|
177
|
+
<div className="text-xs text-gray-500">Maximum</div>
|
|
178
|
+
<div className="font-bold">{stats.max}</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="bg-red-50 p-2 rounded text-center">
|
|
181
|
+
<div className="text-xs text-gray-500">Minimum</div>
|
|
182
|
+
<div className="font-bold">{stats.min}</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{showHistory && (
|
|
187
|
+
<div className="mt-3">
|
|
188
|
+
<h4 className="text-sm font-medium text-gray-600 mb-1">
|
|
189
|
+
History ({state.history.length} events)
|
|
190
|
+
</h4>
|
|
191
|
+
<div className="bg-gray-50 p-2 rounded max-h-24 overflow-y-auto text-xs">
|
|
192
|
+
{state.history.map((value, index) => (
|
|
193
|
+
<span
|
|
194
|
+
key={index}
|
|
195
|
+
className="inline-block bg-gray-200 rounded px-2 py-1 m-1"
|
|
196
|
+
>
|
|
197
|
+
{value}
|
|
198
|
+
</span>
|
|
199
|
+
))}
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx } from 'frontend-hamroun';
|
|
2
|
+
|
|
3
|
+
interface User {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
email: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UserListProps {
|
|
10
|
+
users: User[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function UserList({ users }: UserListProps) {
|
|
14
|
+
if (!users || users.length === 0) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="empty-state p-4 text-center bg-gray-50 rounded">
|
|
17
|
+
<p className="text-gray-500">No users available</p>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="user-list">
|
|
24
|
+
<ul className="divide-y divide-gray-100">
|
|
25
|
+
{users.map(user => (
|
|
26
|
+
<li key={user.id} className="py-3">
|
|
27
|
+
<div className="flex justify-between">
|
|
28
|
+
<div>
|
|
29
|
+
<p className="font-medium text-gray-900">{user.name}</p>
|
|
30
|
+
<p className="text-sm text-gray-500">{user.email}</p>
|
|
31
|
+
</div>
|
|
32
|
+
<a
|
|
33
|
+
href={`/users/${user.id}`}
|
|
34
|
+
className="text-blue-600 hover:underline text-sm self-center"
|
|
35
|
+
>
|
|
36
|
+
View Profile
|
|
37
|
+
</a>
|
|
38
|
+
</div>
|
|
39
|
+
</li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Application configuration
|
|
2
|
+
// Modify this file to customize your application without changing core files
|
|
3
|
+
|
|
4
|
+
export const AppConfig = {
|
|
5
|
+
// App information
|
|
6
|
+
title: 'Frontend Hamroun App',
|
|
7
|
+
description: 'A full-stack application built with Frontend Hamroun',
|
|
8
|
+
|
|
9
|
+
// Navigation
|
|
10
|
+
navigation: [
|
|
11
|
+
{ path: '/', label: 'Home' },
|
|
12
|
+
{ path: '/about', label: 'About' },
|
|
13
|
+
{ path: '/users', label: 'Users' }
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
// API endpoints
|
|
17
|
+
api: {
|
|
18
|
+
baseUrl: '/api',
|
|
19
|
+
endpoints: {
|
|
20
|
+
users: '/users',
|
|
21
|
+
posts: '/posts'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Default meta tags
|
|
26
|
+
meta: {
|
|
27
|
+
viewport: 'width=device-width, initial-scale=1.0',
|
|
28
|
+
charset: 'UTF-8',
|
|
29
|
+
author: 'Your Name',
|
|
30
|
+
keywords: 'frontend-hamroun, fullstack, template'
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// Style customization
|
|
34
|
+
theme: {
|
|
35
|
+
primaryColor: '#0066cc',
|
|
36
|
+
backgroundColor: '#ffffff',
|
|
37
|
+
textColor: '#333333',
|
|
38
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default AppConfig;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API utilities for making requests to the backend
|
|
3
|
+
*/
|
|
4
|
+
import { batchUpdates } from 'frontend-hamroun';
|
|
5
|
+
|
|
6
|
+
// Cache for API responses
|
|
7
|
+
const apiCache = new Map<string, { data: any, timestamp: number }>();
|
|
8
|
+
const CACHE_DURATION = 60000; // 1 minute cache
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fetch data from the API with caching
|
|
12
|
+
*/
|
|
13
|
+
export async function fetchApi(
|
|
14
|
+
endpoint: string,
|
|
15
|
+
options: RequestInit & {
|
|
16
|
+
useCache?: boolean,
|
|
17
|
+
forceFresh?: boolean
|
|
18
|
+
} = {}
|
|
19
|
+
): Promise<any> {
|
|
20
|
+
const url = endpoint.startsWith('/') ? `/api${endpoint}` : `/api/${endpoint}`;
|
|
21
|
+
const { useCache = true, forceFresh = false, ...fetchOptions } = options;
|
|
22
|
+
|
|
23
|
+
// Check cache first if enabled
|
|
24
|
+
if (useCache && !forceFresh) {
|
|
25
|
+
const cached = apiCache.get(url);
|
|
26
|
+
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
27
|
+
console.log(`[API] Using cached data for: ${url}`);
|
|
28
|
+
return cached.data;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
console.log(`[API] Fetching data from: ${url}`);
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'Accept': 'application/json',
|
|
38
|
+
},
|
|
39
|
+
...fetchOptions
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
console.log(`[API] Successfully fetched data from: ${url}`);
|
|
48
|
+
|
|
49
|
+
// Cache the response if caching is enabled
|
|
50
|
+
if (useCache) {
|
|
51
|
+
apiCache.set(url, { data, timestamp: Date.now() });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return data;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`[API] Error fetching from ${url}:`, error);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clear the API cache
|
|
63
|
+
*/
|
|
64
|
+
export function clearApiCache(endpoint?: string): void {
|
|
65
|
+
if (endpoint) {
|
|
66
|
+
const url = endpoint.startsWith('/') ? `/api${endpoint}` : `/api/${endpoint}`;
|
|
67
|
+
apiCache.delete(url);
|
|
68
|
+
} else {
|
|
69
|
+
apiCache.clear();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Sample data for development
|
|
74
|
+
const sampleUsers = [
|
|
75
|
+
{ id: 1, name: 'User 1', email: 'user1@example.com' },
|
|
76
|
+
{ id: 2, name: 'User 2', email: 'user2@example.com' },
|
|
77
|
+
{ id: 3, name: 'User 3', email: 'user3@example.com' }
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const samplePosts = [
|
|
81
|
+
{ id: 1, title: 'Post 1', content: 'Content for post 1', authorId: 1 },
|
|
82
|
+
{ id: 2, title: 'Post 2', content: 'Content for post 2', authorId: 2 },
|
|
83
|
+
{ id: 3, title: 'Post 3', content: 'Content for post 3', authorId: 1 }
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// Helper to simulate network delay
|
|
87
|
+
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
88
|
+
|
|
89
|
+
// User API client
|
|
90
|
+
export const UserApi = {
|
|
91
|
+
// Get all users
|
|
92
|
+
async getAll() {
|
|
93
|
+
try {
|
|
94
|
+
// In a real app, we'd fetch from API
|
|
95
|
+
// const response = await fetch('/api/users');
|
|
96
|
+
// if (!response.ok) throw new Error('Failed to fetch users');
|
|
97
|
+
// return await response.json();
|
|
98
|
+
|
|
99
|
+
// Simulate API delay
|
|
100
|
+
await delay(300);
|
|
101
|
+
return [...sampleUsers]; // Return a copy to avoid mutations
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('Error fetching users:', error);
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Get user by ID
|
|
109
|
+
async getById(id: number | string) {
|
|
110
|
+
try {
|
|
111
|
+
// In a real app, we'd fetch from API
|
|
112
|
+
// const response = await fetch(`/api/users/${id}`);
|
|
113
|
+
// if (!response.ok) throw new Error('User not found');
|
|
114
|
+
// return await response.json();
|
|
115
|
+
|
|
116
|
+
// Convert ID to number if it's a string
|
|
117
|
+
const userId = typeof id === 'string' ? parseInt(id, 10) : id;
|
|
118
|
+
|
|
119
|
+
// Simulate API delay
|
|
120
|
+
await delay(200);
|
|
121
|
+
|
|
122
|
+
// Find user
|
|
123
|
+
const user = sampleUsers.find(u => u.id === userId);
|
|
124
|
+
if (!user) throw new Error('User not found');
|
|
125
|
+
|
|
126
|
+
return { ...user }; // Return a copy to avoid mutations
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(`Error fetching user ${id}:`, error);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Get posts (all or by author)
|
|
134
|
+
async getPosts(authorId?: number | string) {
|
|
135
|
+
try {
|
|
136
|
+
// In a real app, we'd fetch from API
|
|
137
|
+
// const url = authorId ? `/api/posts?authorId=${authorId}` : '/api/posts';
|
|
138
|
+
// const response = await fetch(url);
|
|
139
|
+
// if (!response.ok) throw new Error('Failed to fetch posts');
|
|
140
|
+
// return await response.json();
|
|
141
|
+
|
|
142
|
+
// Convert authorId to number if it's a string
|
|
143
|
+
const userId = authorId ? (typeof authorId === 'string' ? parseInt(authorId, 10) : authorId) : undefined;
|
|
144
|
+
|
|
145
|
+
// Simulate API delay
|
|
146
|
+
await delay(400);
|
|
147
|
+
|
|
148
|
+
// Filter posts if authorId is provided
|
|
149
|
+
const posts = userId
|
|
150
|
+
? samplePosts.filter(p => p.authorId === userId)
|
|
151
|
+
: samplePosts;
|
|
152
|
+
|
|
153
|
+
return [...posts]; // Return a copy to avoid mutations
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Error fetching posts:', error);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// WASM API service
|
|
162
|
+
export const WasmApi = {
|
|
163
|
+
calculate: (operation: 'add' | 'multiply', a: number, b: number) =>
|
|
164
|
+
fetchApi(`/wasm/calculate?op=${operation}&a=${a}&b=${b}`),
|
|
165
|
+
|
|
166
|
+
processJson: (data: any) => fetchApi('/wasm/process-json', {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
body: JSON.stringify(data),
|
|
169
|
+
useCache: false
|
|
170
|
+
})
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Batch multiple API calls for efficiency
|
|
174
|
+
export const batchApiCalls = async <T>(
|
|
175
|
+
apiFunctions: Array<() => Promise<T>>
|
|
176
|
+
): Promise<T[]> => {
|
|
177
|
+
const results: T[] = [];
|
|
178
|
+
let errors: Error[] = [];
|
|
179
|
+
|
|
180
|
+
// Execute all API calls in parallel
|
|
181
|
+
const promises = apiFunctions.map(fn => fn());
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const settledResults = await Promise.allSettled(promises);
|
|
185
|
+
|
|
186
|
+
// Process results
|
|
187
|
+
batchUpdates(() => {
|
|
188
|
+
settledResults.forEach(result => {
|
|
189
|
+
if (result.status === 'fulfilled') {
|
|
190
|
+
results.push(result.value);
|
|
191
|
+
} else {
|
|
192
|
+
errors.push(result.reason);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (errors.length) {
|
|
198
|
+
console.error('[API] Some batch API calls failed:', errors);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return results;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('[API] Batch API calls failed:', error);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { render, hydrate, jsx } from 'frontend-hamroun';
|
|
2
|
+
// Import Tailwind CSS
|
|
3
|
+
import './styles.css';
|
|
4
|
+
|
|
5
|
+
// Type declaration for window.__INITIAL_STATE__
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
__INITIAL_STATE__?: any;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Get initial state from server
|
|
13
|
+
const initialState = window.__INITIAL_STATE__ || {
|
|
14
|
+
route: window.location.pathname,
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
serverRendered: false,
|
|
17
|
+
data: {
|
|
18
|
+
users: null,
|
|
19
|
+
posts: null
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
console.log('[Client] Initial state:', initialState);
|
|
24
|
+
|
|
25
|
+
// Create a mutable variable for hydration state
|
|
26
|
+
let isHydrating = document.getElementById('root')?.innerHTML.trim() !== '';
|
|
27
|
+
|
|
28
|
+
// Function to handle navigation
|
|
29
|
+
async function handleRouteChange(path: string, isPushState = true) {
|
|
30
|
+
try {
|
|
31
|
+
console.log(`[Router] Navigating to: ${path}`);
|
|
32
|
+
|
|
33
|
+
// Update URL if needed
|
|
34
|
+
if (isPushState) {
|
|
35
|
+
window.history.pushState(null, '', path);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Dynamically load the page component based on the path
|
|
39
|
+
const normalizedPath = path === '/' ? 'index' : path.replace(/^\//, '');
|
|
40
|
+
|
|
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`);
|
|
48
|
+
try {
|
|
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
|
|
56
|
+
try {
|
|
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
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
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
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
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;
|
|
103
|
+
|
|
104
|
+
if (isHydrating && path === initialState.route) {
|
|
105
|
+
console.log('[Client] Hydrating server-rendered content');
|
|
106
|
+
hydrate(<Page {...pageProps} initialState={updatedState} />, rootElement);
|
|
107
|
+
isHydrating = false;
|
|
108
|
+
} else {
|
|
109
|
+
console.log('[Client] Rendering client-side');
|
|
110
|
+
render(<Page {...pageProps} initialState={updatedState} />, rootElement);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('[Router] Navigation error:', error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle initial route
|
|
118
|
+
handleRouteChange(window.location.pathname, false);
|
|
119
|
+
|
|
120
|
+
// Handle client-side navigation
|
|
121
|
+
document.addEventListener('click', (e) => {
|
|
122
|
+
let target = e.target as HTMLElement | null;
|
|
123
|
+
|
|
124
|
+
// Find closest anchor element
|
|
125
|
+
while (target && target.tagName !== 'A') {
|
|
126
|
+
target = target.parentElement;
|
|
127
|
+
if (!target) break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (target &&
|
|
131
|
+
target.tagName === 'A' &&
|
|
132
|
+
target.getAttribute('href') &&
|
|
133
|
+
target.getAttribute('href')?.startsWith('/') &&
|
|
134
|
+
!target.getAttribute('href')?.startsWith('//') &&
|
|
135
|
+
!target.getAttribute('target')) {
|
|
136
|
+
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
const href = target.getAttribute('href') || '/';
|
|
139
|
+
handleRouteChange(href);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Handle back/forward navigation
|
|
144
|
+
window.addEventListener('popstate', () => {
|
|
145
|
+
handleRouteChange(window.location.pathname, false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Set up socket.io for live reload in development
|
|
149
|
+
if (typeof io !== 'undefined') {
|
|
150
|
+
const socket = io();
|
|
151
|
+
socket.on('reload', () => {
|
|
152
|
+
console.log('[Dev] Reloading page due to file changes');
|
|
153
|
+
window.location.reload();
|
|
154
|
+
});
|
|
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
|
+
}
|