frontend-hamroun 1.2.82 → 1.2.84
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/bin/cli.js +58 -870
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.client.cjs +1 -1
- package/dist/index.client.cjs.map +1 -1
- package/dist/index.client.js +2 -2
- package/dist/index.client.js.map +1 -1
- package/dist/index.js +116 -133
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.cjs.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/renderer-DaVfBeVi.cjs +2 -0
- package/dist/renderer-DaVfBeVi.cjs.map +1 -0
- package/dist/renderer-nfT7XSpo.js +61 -0
- package/dist/renderer-nfT7XSpo.js.map +1 -0
- package/dist/server-renderer-B5b0Q0ck.cjs +2 -0
- package/dist/server-renderer-B5b0Q0ck.cjs.map +1 -0
- package/dist/{server-renderer-QHt45Ip2.js → server-renderer-C4MB-jAp.js} +89 -96
- package/dist/server-renderer-C4MB-jAp.js.map +1 -0
- package/dist/server-renderer.cjs +1 -1
- package/dist/server-renderer.js +1 -1
- package/package.json +1 -1
- package/templates/basic-app/src/App.tsx +397 -19
- package/templates/ssr-template/dist/client/assets/main-D-VH3xOb.js +1 -0
- package/templates/ssr-template/dist/client/index.html +23 -0
- package/templates/ssr-template/dist/client.js +951 -0
- package/templates/ssr-template/dist/server.js +739 -0
- package/templates/ssr-template/package.json +22 -22
- package/templates/ssr-template/src/App.tsx +874 -11
- package/templates/ssr-template/tsconfig.json +14 -10
- package/templates/ssr-template/vite.config.ts +19 -17
- package/templates/wasm/dist/assets/index-BNqTDBdE.js +295 -0
- package/templates/wasm/dist/example.wasm +0 -0
- package/templates/wasm/dist/index.html +53 -0
- package/templates/wasm/dist/wasm_exec.js +572 -0
- package/templates/wasm/package-lock.json +4577 -5307
- package/templates/wasm/package.json +25 -42
- package/templates/wasm/public/wasm_exec.js +12 -1
- package/templates/wasm/src/App.tsx +41 -55
- package/templates/wasm/vite.config.ts +24 -42
- package/dist/renderer-Bo9zkUZ_.js +0 -52
- package/dist/renderer-Bo9zkUZ_.js.map +0 -1
- package/dist/renderer-Din1y3YM.cjs +0 -2
- package/dist/renderer-Din1y3YM.cjs.map +0 -1
- package/dist/server-renderer-CqIpQ-od.cjs +0 -2
- package/dist/server-renderer-CqIpQ-od.cjs.map +0 -1
- package/dist/server-renderer-QHt45Ip2.js.map +0 -1
@@ -1,26 +1,404 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
jsx,
|
3
|
+
useState,
|
4
|
+
useEffect,
|
5
|
+
useMemo,
|
6
|
+
useErrorBoundary,
|
7
|
+
useForm
|
8
|
+
} from 'frontend-hamroun';
|
2
9
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
10
|
+
// Todo item interface
|
11
|
+
interface Todo {
|
12
|
+
id: number;
|
13
|
+
text: string;
|
14
|
+
completed: boolean;
|
15
|
+
priority: 'high' | 'medium' | 'low';
|
16
|
+
}
|
17
|
+
|
18
|
+
// Component prop interfaces
|
19
|
+
interface TodoItemProps {
|
20
|
+
todo: Todo;
|
21
|
+
onToggle: (id: number) => void;
|
22
|
+
onDelete: (id: number) => void;
|
23
|
+
}
|
24
|
+
|
25
|
+
interface ThemeToggleButtonProps {
|
26
|
+
theme: string;
|
27
|
+
onToggle: () => void;
|
28
|
+
}
|
29
|
+
|
30
|
+
interface AppFooterProps {
|
31
|
+
theme: string;
|
32
|
+
}
|
33
|
+
|
34
|
+
// Todo Item Component
|
35
|
+
function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
|
7
36
|
return (
|
8
|
-
<div>
|
9
|
-
<
|
10
|
-
|
37
|
+
<div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
|
38
|
+
<input
|
39
|
+
type="checkbox"
|
40
|
+
checked={todo.completed}
|
41
|
+
onChange={() => onToggle(todo.id)}
|
42
|
+
className="todo-checkbox"
|
43
|
+
/>
|
44
|
+
<span className="todo-text">{todo.text}</span>
|
45
|
+
<span className={`todo-priority priority-${todo.priority}`}>
|
46
|
+
{todo.priority === 'high' && '🔴'}
|
47
|
+
{todo.priority === 'medium' && '🟡'}
|
48
|
+
{todo.priority === 'low' && '🟢'}
|
49
|
+
{todo.priority}
|
50
|
+
</span>
|
51
|
+
<div className="todo-actions">
|
11
52
|
<button
|
12
|
-
onClick={() =>
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
53
|
+
onClick={() => onDelete(todo.id)}
|
54
|
+
className="btn btn-sm btn-danger"
|
55
|
+
title="Delete todo"
|
56
|
+
>
|
57
|
+
🗑️
|
58
|
+
</button>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
);
|
62
|
+
}
|
63
|
+
|
64
|
+
// Theme Toggle Button Component
|
65
|
+
function ThemeToggleButton({ theme, onToggle }: ThemeToggleButtonProps) {
|
66
|
+
return (
|
67
|
+
<button
|
68
|
+
onClick={onToggle}
|
69
|
+
className="btn btn-secondary theme-toggle"
|
70
|
+
title="Toggle theme"
|
71
|
+
>
|
72
|
+
{theme === 'light' ? '🌙' : '☀️'}
|
73
|
+
</button>
|
74
|
+
);
|
75
|
+
}
|
76
|
+
|
77
|
+
// App Footer Component
|
78
|
+
function AppFooter({ theme }: AppFooterProps) {
|
79
|
+
return (
|
80
|
+
<footer className="app-footer">
|
81
|
+
<div className="container">
|
82
|
+
<p>Built with Frontend Hamroun • Hooks: useState, useEffect, useMemo, useErrorBoundary, useForm</p>
|
83
|
+
<p>Theme: <strong>{theme}</strong> • Environment: <strong>Client</strong></p>
|
84
|
+
</div>
|
85
|
+
</footer>
|
86
|
+
);
|
87
|
+
}
|
88
|
+
|
89
|
+
export function App() {
|
90
|
+
// State hooks with proper typing
|
91
|
+
const [todos, setTodos] = useState<Todo[]>([
|
92
|
+
{ id: 1, text: 'Learn Frontend Hamroun hooks', completed: false, priority: 'high' },
|
93
|
+
{ id: 2, text: 'Build a todo app', completed: false, priority: 'medium' },
|
94
|
+
{ id: 3, text: 'Master client-side concepts', completed: true, priority: 'low' }
|
95
|
+
]);
|
96
|
+
const [theme, setTheme] = useState<string>('light');
|
97
|
+
const [taskFilter, setTaskFilter] = useState<string>('all');
|
98
|
+
|
99
|
+
// Error boundary hook
|
100
|
+
const [error, resetError] = useErrorBoundary();
|
101
|
+
|
102
|
+
// Form hook for adding new todos
|
103
|
+
const addTodoForm = useForm({
|
104
|
+
initialValues: {
|
105
|
+
text: '',
|
106
|
+
priority: 'medium' as 'high' | 'medium' | 'low'
|
107
|
+
},
|
108
|
+
validate: (values) => {
|
109
|
+
const errors: Record<string, string> = {};
|
110
|
+
if (!values.text.trim()) {
|
111
|
+
errors.text = 'Todo text is required';
|
112
|
+
}
|
113
|
+
return errors;
|
114
|
+
},
|
115
|
+
onSubmit: (values) => {
|
116
|
+
const newTodo: Todo = {
|
117
|
+
id: Date.now(),
|
118
|
+
text: values.text.trim(),
|
119
|
+
completed: false,
|
120
|
+
priority: values.priority
|
121
|
+
};
|
122
|
+
setTodos(prev => [...prev, newTodo]);
|
123
|
+
addTodoForm.resetForm();
|
124
|
+
}
|
125
|
+
});
|
126
|
+
|
127
|
+
// Mount effect
|
128
|
+
useEffect(() => {
|
129
|
+
console.log('Todo App mounted');
|
130
|
+
|
131
|
+
// Load saved todos from localStorage
|
132
|
+
const savedTodos = localStorage.getItem('todos');
|
133
|
+
const savedTheme = localStorage.getItem('theme');
|
134
|
+
|
135
|
+
if (savedTodos) {
|
136
|
+
try {
|
137
|
+
setTodos(JSON.parse(savedTodos));
|
138
|
+
} catch (e) {
|
139
|
+
console.error('Failed to load saved todos');
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
if (savedTheme) {
|
144
|
+
setTheme(savedTheme);
|
145
|
+
}
|
146
|
+
|
147
|
+
return () => {
|
148
|
+
console.log('Todo App unmounting');
|
149
|
+
};
|
150
|
+
}, []);
|
151
|
+
|
152
|
+
// Theme effect
|
153
|
+
useEffect(() => {
|
154
|
+
document.body.className = `theme-${theme}`;
|
155
|
+
document.documentElement.setAttribute('data-theme', theme);
|
156
|
+
localStorage.setItem('theme', theme);
|
157
|
+
}, [theme]);
|
158
|
+
|
159
|
+
// Save todos effect
|
160
|
+
useEffect(() => {
|
161
|
+
if (todos.length === 0) return;
|
162
|
+
localStorage.setItem('todos', JSON.stringify(todos));
|
163
|
+
}, [todos]);
|
164
|
+
|
165
|
+
// Memoized filtered todos
|
166
|
+
const filteredTodos = useMemo(() => {
|
167
|
+
console.log('Filtering todos - memoized computation');
|
168
|
+
return todos.filter(todo => {
|
169
|
+
if (taskFilter === 'completed') return todo.completed;
|
170
|
+
if (taskFilter === 'active') return !todo.completed;
|
171
|
+
if (taskFilter === 'high') return todo.priority === 'high';
|
172
|
+
if (taskFilter === 'medium') return todo.priority === 'medium';
|
173
|
+
if (taskFilter === 'low') return todo.priority === 'low';
|
174
|
+
return true;
|
175
|
+
});
|
176
|
+
}, [todos, taskFilter]);
|
177
|
+
|
178
|
+
// Memoized todo stats
|
179
|
+
const todoStats = useMemo(() => {
|
180
|
+
const total = todos.length;
|
181
|
+
const completed = todos.filter(t => t.completed).length;
|
182
|
+
const active = total - completed;
|
183
|
+
const highPriority = todos.filter(t => t.priority === 'high' && !t.completed).length;
|
184
|
+
|
185
|
+
return { total, completed, active, highPriority };
|
186
|
+
}, [todos]);
|
187
|
+
|
188
|
+
// Todo action handlers
|
189
|
+
const handleToggleTodo = (id: number) => {
|
190
|
+
console.log('Toggling todo:', id);
|
191
|
+
setTodos(prev => prev.map(todo =>
|
192
|
+
todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
193
|
+
));
|
194
|
+
};
|
195
|
+
|
196
|
+
const handleDeleteTodo = (id: number) => {
|
197
|
+
console.log('Deleting todo:', id);
|
198
|
+
setTodos(prev => prev.filter(todo => todo.id !== id));
|
199
|
+
};
|
200
|
+
|
201
|
+
// Event handlers
|
202
|
+
const clearCompleted = () => {
|
203
|
+
setTodos(prev => prev.filter(todo => !todo.completed));
|
204
|
+
};
|
205
|
+
|
206
|
+
const markAllComplete = () => {
|
207
|
+
setTodos(prev => prev.map(todo => ({ ...todo, completed: true })));
|
208
|
+
};
|
209
|
+
|
210
|
+
const toggleTheme = () => {
|
211
|
+
setTheme(prev => prev === 'light' ? 'dark' : 'light');
|
212
|
+
};
|
213
|
+
|
214
|
+
const simulateError = () => {
|
215
|
+
throw new Error('Simulated error for testing error boundary');
|
216
|
+
};
|
217
|
+
|
218
|
+
if (error) {
|
219
|
+
return (
|
220
|
+
<div className="error-container">
|
221
|
+
<h2>Something went wrong!</h2>
|
222
|
+
<p>{(error as Error).message}</p>
|
223
|
+
<button onClick={resetError} className="btn btn-primary">
|
224
|
+
Try Again
|
225
|
+
</button>
|
20
226
|
</div>
|
21
|
-
|
22
|
-
|
23
|
-
|
227
|
+
);
|
228
|
+
}
|
229
|
+
|
230
|
+
return (
|
231
|
+
<div className={`app theme-${theme}`}>
|
232
|
+
|
233
|
+
{/* Header */}
|
234
|
+
<header className="app-header">
|
235
|
+
<div className="container">
|
236
|
+
<h1 className="app-title">
|
237
|
+
📝 Todo App
|
238
|
+
<span className="subtitle">Built with Frontend Hamroun</span>
|
239
|
+
</h1>
|
240
|
+
|
241
|
+
<div className="header-controls">
|
242
|
+
<ThemeToggleButton theme={theme} onToggle={toggleTheme} />
|
243
|
+
<div className="stats">
|
244
|
+
<span className="stat">
|
245
|
+
Total: <strong>{todoStats.total}</strong>
|
246
|
+
</span>
|
247
|
+
<span className="stat">
|
248
|
+
Active: <strong>{todoStats.active}</strong>
|
249
|
+
</span>
|
250
|
+
<span className="stat">
|
251
|
+
Done: <strong>{todoStats.completed}</strong>
|
252
|
+
</span>
|
253
|
+
</div>
|
254
|
+
</div>
|
255
|
+
</div>
|
256
|
+
</header>
|
257
|
+
|
258
|
+
<main className="main-content">
|
259
|
+
<div className="container">
|
260
|
+
|
261
|
+
{/* Add Todo Section */}
|
262
|
+
<section className="card add-todo-section">
|
263
|
+
<h2>➕ Add New Todo</h2>
|
264
|
+
|
265
|
+
<form onSubmit={addTodoForm.handleSubmit} className="add-todo-form">
|
266
|
+
<input
|
267
|
+
type="text"
|
268
|
+
name="text"
|
269
|
+
value={addTodoForm.values.text}
|
270
|
+
onChange={addTodoForm.handleChange}
|
271
|
+
onBlur={addTodoForm.handleBlur}
|
272
|
+
placeholder="What needs to be done?"
|
273
|
+
className={`input todo-input ${addTodoForm.errors.text && addTodoForm.touched.text ? 'error' : ''}`}
|
274
|
+
/>
|
275
|
+
|
276
|
+
<select
|
277
|
+
name="priority"
|
278
|
+
value={addTodoForm.values.priority}
|
279
|
+
onChange={addTodoForm.handleChange}
|
280
|
+
className="select priority-select"
|
281
|
+
>
|
282
|
+
<option value="low">🟢 Low Priority</option>
|
283
|
+
<option value="medium">🟡 Medium Priority</option>
|
284
|
+
<option value="high">🔴 High Priority</option>
|
285
|
+
</select>
|
286
|
+
|
287
|
+
<button
|
288
|
+
type="submit"
|
289
|
+
className={`btn btn-primary add-btn ${addTodoForm.isSubmitting ? 'loading' : ''}`}
|
290
|
+
disabled={addTodoForm.isSubmitting || !addTodoForm.values.text.trim()}
|
291
|
+
>
|
292
|
+
{addTodoForm.isSubmitting ? '⏳ Adding...' : '➕ Add Todo'}
|
293
|
+
</button>
|
294
|
+
</form>
|
295
|
+
|
296
|
+
{addTodoForm.errors.text && addTodoForm.touched.text && (
|
297
|
+
<div className="error-message">{addTodoForm.errors.text}</div>
|
298
|
+
)}
|
299
|
+
</section>
|
300
|
+
|
301
|
+
{/* Filters Section */}
|
302
|
+
<section className="card filters-section">
|
303
|
+
<h2>🔍 Filter Todos</h2>
|
304
|
+
|
305
|
+
<div className="filters">
|
306
|
+
{(['all', 'active', 'completed', 'high', 'medium', 'low'] as const).map(filterType => (
|
307
|
+
<button
|
308
|
+
key={filterType}
|
309
|
+
onClick={() => setTaskFilter(filterType)}
|
310
|
+
className={`btn btn-sm filter-btn ${taskFilter === filterType ? 'btn-primary' : 'btn-outline'}`}
|
311
|
+
>
|
312
|
+
{filterType === 'all' && '📋 All'}
|
313
|
+
{filterType === 'active' && '⏳ Active'}
|
314
|
+
{filterType === 'completed' && '✅ Completed'}
|
315
|
+
{filterType === 'high' && '🔴 High Priority'}
|
316
|
+
{filterType === 'medium' && '🟡 Medium Priority'}
|
317
|
+
{filterType === 'low' && '🟢 Low Priority'}
|
318
|
+
</button>
|
319
|
+
))}
|
320
|
+
</div>
|
321
|
+
|
322
|
+
<div className="bulk-actions">
|
323
|
+
<button
|
324
|
+
onClick={markAllComplete}
|
325
|
+
className="btn btn-success btn-sm"
|
326
|
+
disabled={todoStats.active === 0}
|
327
|
+
>
|
328
|
+
✅ Mark All Complete
|
329
|
+
</button>
|
330
|
+
<button
|
331
|
+
onClick={clearCompleted}
|
332
|
+
className="btn btn-warning btn-sm"
|
333
|
+
disabled={todoStats.completed === 0}
|
334
|
+
>
|
335
|
+
🗑️ Clear Completed
|
336
|
+
</button>
|
337
|
+
</div>
|
338
|
+
</section>
|
339
|
+
|
340
|
+
{/* Todos List Section */}
|
341
|
+
<section className="card todos-section">
|
342
|
+
<div className="section-header">
|
343
|
+
<h2>📋 Todo List</h2>
|
344
|
+
<div className="filter-info">
|
345
|
+
Showing <strong>{filteredTodos.length}</strong> of <strong>{todoStats.total}</strong> todos
|
346
|
+
{taskFilter !== 'all' && <span className="filter-badge">{taskFilter}</span>}
|
347
|
+
</div>
|
348
|
+
</div>
|
349
|
+
|
350
|
+
<div className="todos-list">
|
351
|
+
{filteredTodos.length > 0 ? (
|
352
|
+
filteredTodos.map(todo => (
|
353
|
+
<TodoItem
|
354
|
+
key={todo.id}
|
355
|
+
todo={todo}
|
356
|
+
onToggle={handleToggleTodo}
|
357
|
+
onDelete={handleDeleteTodo}
|
358
|
+
/>
|
359
|
+
))
|
360
|
+
) : (
|
361
|
+
<div className="empty-state">
|
362
|
+
<p>
|
363
|
+
{taskFilter === 'all' ? '🎉 No todos yet. Add one above!' :
|
364
|
+
taskFilter === 'completed' ? '📝 No completed todos yet.' :
|
365
|
+
taskFilter === 'active' ? '🎯 No active todos. Great job!' :
|
366
|
+
`🔍 No ${taskFilter} priority todos found.`}
|
367
|
+
</p>
|
368
|
+
</div>
|
369
|
+
)}
|
370
|
+
</div>
|
371
|
+
</section>
|
372
|
+
|
373
|
+
{/* Actions Section */}
|
374
|
+
<section className="card actions-section">
|
375
|
+
<h2>⚙️ Actions</h2>
|
376
|
+
<div className="action-buttons">
|
377
|
+
<button onClick={simulateError} className="btn btn-danger">
|
378
|
+
💥 Test Error Boundary
|
379
|
+
</button>
|
380
|
+
</div>
|
381
|
+
</section>
|
382
|
+
|
383
|
+
</div>
|
384
|
+
</main>
|
385
|
+
|
386
|
+
{/* Footer */}
|
387
|
+
<AppFooter theme={theme} />
|
388
|
+
|
389
|
+
{/* Styles */}
|
390
|
+
<style>{`
|
391
|
+
/* ...existing styles... */
|
392
|
+
.error-message {
|
393
|
+
color: #e74c3c;
|
394
|
+
font-size: 0.875rem;
|
395
|
+
margin-top: 0.5rem;
|
396
|
+
}
|
397
|
+
|
398
|
+
.input.error {
|
399
|
+
border-color: #e74c3c;
|
400
|
+
}
|
401
|
+
`}</style>
|
24
402
|
</div>
|
25
403
|
);
|
26
404
|
}
|
@@ -0,0 +1 @@
|
|
1
|
+
import{useState as l,useEffect as g,jsx as e,hydrate as f,render as y}from"frontend-hamroun";(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))o(t);new MutationObserver(t=>{for(const r of t)if(r.type==="childList")for(const i of r.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function s(t){const r={};return t.integrity&&(r.integrity=t.integrity),t.referrerPolicy&&(r.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?r.credentials="include":t.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(t){if(t.ep)return;t.ep=!0;const r=s(t);fetch(t.href,r)}})();function c({path:a="/",dev:n=!1,timestamp:s}){const[o,t]=l(0),[r,i]=l(new Date().toLocaleTimeString());return g(()=>{const u=setInterval(()=>{i(new Date().toLocaleTimeString())},1e3);return()=>clearInterval(u)},[]),e("div",{className:"container"},[e("header",{style:{background:"linear-gradient(135deg, #667eea 0%, #764ba2 100%)",color:"white",padding:"2rem",borderRadius:"12px",marginBottom:"2rem",textAlign:"center"}},[e("h1",{},"🚀 Frontend Hamroun SSR"),e("p",{},"Server-Side Rendering with Client Hydration"),n&&e("div",{style:{background:"rgba(255, 255, 255, 0.2)",padding:"0.5rem 1rem",borderRadius:"8px",fontSize:"0.9rem",marginTop:"1rem"}},"🔥 Development Mode - Hot Reload Active")]),e("main",{},[e("section",{style:{background:"#f8f9fa",padding:"2rem",borderRadius:"8px",marginBottom:"2rem"}},[e("h2",{},"📍 Route Information"),e("p",{},[e("strong",{},"Current path: "),a]),e("p",{},[e("strong",{},"Render time: "),s||"Client-side"]),e("p",{},[e("strong",{},"Current time: "),r])]),e("section",{style:{background:"#e3f2fd",padding:"2rem",borderRadius:"8px",marginBottom:"2rem"}},[e("h2",{},"🎛️ Interactive Counter"),e("p",{},"This demonstrates client-side hydration:"),e("div",{style:{display:"flex",alignItems:"center",gap:"1rem",marginTop:"1rem"}},[e("button",{onClick:()=>t(o-1),style:{background:"#f44336",color:"white",border:"none",padding:"0.5rem 1rem",borderRadius:"4px",cursor:"pointer"}},"-"),e("span",{style:{fontSize:"1.5rem",fontWeight:"bold",minWidth:"60px",textAlign:"center"}},o.toString()),e("button",{onClick:()=>t(o+1),style:{background:"#4caf50",color:"white",border:"none",padding:"0.5rem 1rem",borderRadius:"4px",cursor:"pointer"}},"+")])]),e("section",{style:{background:"#fff3e0",padding:"2rem",borderRadius:"8px"}},[e("h2",{},"✨ Framework Features"),e("ul",{style:{lineHeight:"1.8"}},[e("li",{},"🔄 Server-Side Rendering (SSR)"),e("li",{},"💧 Client-Side Hydration"),e("li",{},"🔥 Hot Reload in Development"),e("li",{},"🎣 React-like Hooks (useState, useEffect)"),e("li",{},"⚡ Fast Development Server"),e("li",{},"📦 Optimized Production Builds"),e("li",{},"🎨 Component-Based Architecture"),e("li",{},"🔧 TypeScript Support")])])]),e("footer",{style:{textAlign:"center",padding:"2rem",marginTop:"2rem",borderTop:"1px solid #eee"}},[e("p",{},"Built with ❤️ using Frontend Hamroun"),e("p",{style:{fontSize:"0.9rem",color:"#666"}},["Edit ",e("code",{},"src/App.tsx")," and save to see changes"])])])}const p=window.__INITIAL_DATA__||{path:window.location.pathname,dev:!1,timestamp:new Date().toISOString()},d=document.getElementById("app");if(!d)throw new Error("Root element not found");function m(){try{d.hasChildNodes()?(console.log("🔄 Hydrating SSR content"),f(e(c,p),d)):(console.log("🚀 Rendering app from scratch"),y(e(c,p),d)),console.log("✅ App started successfully")}catch(a){console.error("❌ Error starting app:",a),d.innerHTML='<div style="color: red; padding: 20px;">Error loading application</div>'}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m();
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Frontend Hamroun SSR App</title>
|
7
|
+
<style>
|
8
|
+
body {
|
9
|
+
margin: 0;
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
11
|
+
}
|
12
|
+
.container {
|
13
|
+
max-width: 1200px;
|
14
|
+
margin: 0 auto;
|
15
|
+
padding: 20px;
|
16
|
+
}
|
17
|
+
</style>
|
18
|
+
<script type="module" crossorigin src="/assets/main-D-VH3xOb.js"></script>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
<div id="app"></div>
|
22
|
+
</body>
|
23
|
+
</html>
|