baraqex 2.0.3 → 2.0.5
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/browser.js +11 -9
- package/dist/browser.js.map +2 -2
- package/dist/core/jsx-runtime.d.ts +1 -1
- package/dist/core/jsx-runtime.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/templates/fullstack-app/README.md +62 -37
- package/templates/fullstack-app/build-wasm.js +80 -0
- package/templates/fullstack-app/build.mjs +133 -0
- package/templates/fullstack-app/go/main.go +53 -0
- package/templates/fullstack-app/index.html +17 -36
- package/templates/fullstack-app/package.json +41 -35
- package/templates/fullstack-app/postcss.config.js +6 -6
- package/templates/fullstack-app/public/wasm/.gitkeep +1 -0
- package/templates/fullstack-app/server.ts +239 -474
- package/templates/fullstack-app/src/App.tsx +77 -0
- package/templates/fullstack-app/src/main.tsx +31 -155
- package/templates/fullstack-app/src/pages/About.tsx +40 -0
- package/templates/fullstack-app/src/pages/Home.tsx +67 -0
- package/templates/fullstack-app/src/pages/WasmDemo.tsx +81 -0
- package/templates/fullstack-app/src/styles.css +12 -5
- package/templates/fullstack-app/tailwind.config.js +8 -11
- package/templates/fullstack-app/tsconfig.json +20 -18
- package/templates/fullstack-app/api/hello.js +0 -11
- package/templates/fullstack-app/api/hello.ts +0 -18
- package/templates/fullstack-app/api/users/[id].ts +0 -73
- package/templates/fullstack-app/api/users/index.ts +0 -32
- package/templates/fullstack-app/build/main.css +0 -874
- package/templates/fullstack-app/build/main.css.map +0 -7
- package/templates/fullstack-app/build/main.js +0 -967
- package/templates/fullstack-app/build/main.js.map +0 -7
- package/templates/fullstack-app/build.ts +0 -210
- package/templates/fullstack-app/process-tailwind.js +0 -45
- package/templates/fullstack-app/public/_redirects +0 -1
- package/templates/fullstack-app/public/route-handler.js +0 -47
- package/templates/fullstack-app/public/spa-fix.html +0 -17
- package/templates/fullstack-app/public/styles.css +0 -975
- package/templates/fullstack-app/public/tailwind.css +0 -15
- package/templates/fullstack-app/server.js +0 -267
- package/templates/fullstack-app/src/README.md +0 -55
- package/templates/fullstack-app/src/client.js +0 -87
- package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
- package/templates/fullstack-app/src/components/ErrorBoundary.tsx +0 -36
- package/templates/fullstack-app/src/components/Layout.tsx +0 -42
- package/templates/fullstack-app/src/components/StateDemo.tsx +0 -207
- package/templates/fullstack-app/src/components/UserList.tsx +0 -44
- package/templates/fullstack-app/src/config.ts +0 -42
- package/templates/fullstack-app/src/data/api.ts +0 -206
- package/templates/fullstack-app/src/middleware.ts +0 -28
- package/templates/fullstack-app/src/pages/404.tsx +0 -28
- package/templates/fullstack-app/src/pages/[id].tsx +0 -0
- package/templates/fullstack-app/src/pages/_app.tsx +0 -11
- package/templates/fullstack-app/src/pages/_document.tsx +0 -25
- package/templates/fullstack-app/src/pages/_error.tsx +0 -45
- package/templates/fullstack-app/src/pages/about/index.tsx +0 -67
- package/templates/fullstack-app/src/pages/about.tsx +0 -71
- package/templates/fullstack-app/src/pages/api/users/[id].ts +0 -73
- package/templates/fullstack-app/src/pages/api/users/index.ts +0 -43
- package/templates/fullstack-app/src/pages/index.tsx +0 -110
- package/templates/fullstack-app/src/pages/users/[id].tsx +0 -153
- package/templates/fullstack-app/src/pages/users.tsx +0 -60
- package/templates/fullstack-app/src/pages/wasm-demo.tsx +0 -1
- package/templates/fullstack-app/src/router.ts +0 -255
- package/templates/fullstack-app/vite.config.js +0 -87
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'frontend-hamroun';
|
|
2
|
-
import Layout from '../../components/Layout';
|
|
3
|
-
|
|
4
|
-
const AboutPage = ({ initialState }) => {
|
|
5
|
-
return (
|
|
6
|
-
<Layout title="About This App">
|
|
7
|
-
<div className="max-w-4xl mx-auto bg-white shadow-lg rounded-lg overflow-hidden">
|
|
8
|
-
<div className="p-8">
|
|
9
|
-
<p className="text-lg text-gray-700 mb-6">
|
|
10
|
-
This is a frontend application built with Frontend Hamroun framework and styled with Tailwind CSS.
|
|
11
|
-
</p>
|
|
12
|
-
<p className="text-gray-600 mb-8">
|
|
13
|
-
It features server-side rendering, client-side navigation, and websocket-based live reloading during development.
|
|
14
|
-
</p>
|
|
15
|
-
|
|
16
|
-
<div className="bg-gray-50 p-6 rounded-lg border border-gray-200 mb-8">
|
|
17
|
-
<h2 className="text-xl font-semibold text-gray-800 mb-4">Key Features</h2>
|
|
18
|
-
<ul className="space-y-2 text-gray-700">
|
|
19
|
-
<li className="flex items-center">
|
|
20
|
-
<svg className="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
21
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|
22
|
-
</svg>
|
|
23
|
-
Server-side rendering
|
|
24
|
-
</li>
|
|
25
|
-
<li className="flex items-center">
|
|
26
|
-
<svg className="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
27
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|
28
|
-
</svg>
|
|
29
|
-
Client-side navigation
|
|
30
|
-
</li>
|
|
31
|
-
<li className="flex items-center">
|
|
32
|
-
<svg className="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
33
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|
34
|
-
</svg>
|
|
35
|
-
Component-based architecture
|
|
36
|
-
</li>
|
|
37
|
-
<li className="flex items-center">
|
|
38
|
-
<svg className="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
39
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|
40
|
-
</svg>
|
|
41
|
-
Integrated API backend
|
|
42
|
-
</li>
|
|
43
|
-
<li className="flex items-center">
|
|
44
|
-
<svg className="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
45
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|
46
|
-
</svg>
|
|
47
|
-
Live reload during development
|
|
48
|
-
</li>
|
|
49
|
-
<li className="flex items-center">
|
|
50
|
-
<svg className="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
51
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
|
|
52
|
-
</svg>
|
|
53
|
-
Tailwind CSS for styling
|
|
54
|
-
</li>
|
|
55
|
-
</ul>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<a href="/" className="inline-block px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors">
|
|
59
|
-
Back to Home
|
|
60
|
-
</a>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</Layout>
|
|
64
|
-
);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export default AboutPage;
|
|
@@ -1,71 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
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;
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { jsx, useState, useEffect, batchUpdates } from 'frontend-hamroun';
|
|
2
|
-
import UserList from '../components/UserList';
|
|
3
|
-
import StateDemo from '../components/StateDemo';
|
|
4
|
-
import { ErrorBoundary } from '../components/ErrorBoundary';
|
|
5
|
-
import { UserApi } from '../data/api';
|
|
6
|
-
|
|
7
|
-
// Home Page Component with Server Side Props
|
|
8
|
-
export default function HomePage({ users, posts, initialState }) {
|
|
9
|
-
const [state, setState] = useState(initialState || {});
|
|
10
|
-
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
11
|
-
|
|
12
|
-
// Fetch data when refresh is triggered
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (refreshTrigger === 0) return; // Skip initial render
|
|
15
|
-
|
|
16
|
-
async function fetchData() {
|
|
17
|
-
try {
|
|
18
|
-
const [users, posts] = await Promise.all([
|
|
19
|
-
UserApi.getAll(),
|
|
20
|
-
UserApi.getPosts()
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
// Use batch updates for efficiency
|
|
24
|
-
batchUpdates(() => {
|
|
25
|
-
setState(prev => ({
|
|
26
|
-
...prev,
|
|
27
|
-
data: {
|
|
28
|
-
...prev.data,
|
|
29
|
-
users,
|
|
30
|
-
posts
|
|
31
|
-
},
|
|
32
|
-
lastUpdate: new Date().toISOString()
|
|
33
|
-
}));
|
|
34
|
-
});
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.error('Error fetching data:', error);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
fetchData();
|
|
41
|
-
}, [refreshTrigger]);
|
|
42
|
-
|
|
43
|
-
const handleRefresh = () => {
|
|
44
|
-
setRefreshTrigger(t => t + 1);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div className="max-w-4xl mx-auto py-8">
|
|
49
|
-
<h1 className="text-3xl font-bold text-blue-600 mb-6">
|
|
50
|
-
Welcome to your Next-style Frontend Hamroun application!
|
|
51
|
-
</h1>
|
|
52
|
-
|
|
53
|
-
<div className="mb-8">
|
|
54
|
-
<button
|
|
55
|
-
className="mb-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
|
56
|
-
onClick={handleRefresh}
|
|
57
|
-
>
|
|
58
|
-
Refresh Data
|
|
59
|
-
</button>
|
|
60
|
-
|
|
61
|
-
<div className="bg-blue-50 p-4 rounded-lg border border-blue-100 mb-6">
|
|
62
|
-
<p className="text-blue-700">
|
|
63
|
-
Last updated: {state.lastUpdate || 'Never'}
|
|
64
|
-
</p>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<ErrorBoundary>
|
|
69
|
-
<div className="bg-white shadow-lg rounded-lg p-6 mb-8">
|
|
70
|
-
<h2 className="text-xl font-semibold text-gray-800 mb-4">User List</h2>
|
|
71
|
-
<UserList users={users || state.data?.users || []} />
|
|
72
|
-
</div>
|
|
73
|
-
</ErrorBoundary>
|
|
74
|
-
|
|
75
|
-
<ErrorBoundary>
|
|
76
|
-
<StateDemo />
|
|
77
|
-
</ErrorBoundary>
|
|
78
|
-
|
|
79
|
-
<div className="bg-gray-50 rounded-lg p-6 border border-gray-200 mt-8">
|
|
80
|
-
<h3 className="text-lg font-medium text-gray-700 mb-3">Application State</h3>
|
|
81
|
-
<pre className="overflow-auto p-4 bg-gray-100 rounded-md text-sm text-gray-800">
|
|
82
|
-
{JSON.stringify({ users, posts, ...state }, null, 2)}
|
|
83
|
-
</pre>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Server-side data fetching (Next.js style)
|
|
90
|
-
export async function getServerSideProps() {
|
|
91
|
-
try {
|
|
92
|
-
const users = await UserApi.getAll();
|
|
93
|
-
const posts = await UserApi.getPosts();
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
props: {
|
|
97
|
-
users,
|
|
98
|
-
posts
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error('Error fetching initial data:', error);
|
|
103
|
-
return {
|
|
104
|
-
props: {
|
|
105
|
-
users: [],
|
|
106
|
-
posts: []
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { jsx, useState, useEffect } from 'frontend-hamroun';
|
|
2
|
-
import { UserApi } from '../../data/api';
|
|
3
|
-
|
|
4
|
-
export default function UserDetail({ user, posts, initialState }) {
|
|
5
|
-
// State for client-side data fetching when needed
|
|
6
|
-
const [userData, setUserData] = useState(user);
|
|
7
|
-
const [userPosts, setUserPosts] = useState(posts || []);
|
|
8
|
-
const [loading, setLoading] = useState(false);
|
|
9
|
-
const [error, setError] = useState(null);
|
|
10
|
-
|
|
11
|
-
// Get user ID from route params
|
|
12
|
-
const { id } = initialState?.params || {};
|
|
13
|
-
|
|
14
|
-
// Fetch user data if not provided from server
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (!userData && id) {
|
|
17
|
-
setLoading(true);
|
|
18
|
-
|
|
19
|
-
Promise.all([
|
|
20
|
-
UserApi.getById(id),
|
|
21
|
-
UserApi.getPosts(id)
|
|
22
|
-
])
|
|
23
|
-
.then(([userData, postsData]) => {
|
|
24
|
-
setUserData(userData);
|
|
25
|
-
setUserPosts(postsData);
|
|
26
|
-
setLoading(false);
|
|
27
|
-
})
|
|
28
|
-
.catch(err => {
|
|
29
|
-
console.error('Error fetching user data:', err);
|
|
30
|
-
setError(err.message || 'Failed to load user data');
|
|
31
|
-
setLoading(false);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}, [userData, id]);
|
|
35
|
-
|
|
36
|
-
if (loading) {
|
|
37
|
-
return (
|
|
38
|
-
<div className="max-w-4xl mx-auto p-4">
|
|
39
|
-
<div className="animate-pulse rounded-md bg-gray-100 p-8">
|
|
40
|
-
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4"></div>
|
|
41
|
-
<div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
|
|
42
|
-
<div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
|
|
43
|
-
<div className="h-40 bg-gray-200 rounded mb-4"></div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (error) {
|
|
50
|
-
return (
|
|
51
|
-
<div className="max-w-4xl mx-auto p-4">
|
|
52
|
-
<div className="rounded-md bg-red-50 p-4 border border-red-200">
|
|
53
|
-
<h2 className="text-lg font-bold text-red-700">Error Loading User</h2>
|
|
54
|
-
<p className="text-red-600">{error}</p>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!userData) {
|
|
61
|
-
return (
|
|
62
|
-
<div className="max-w-4xl mx-auto p-4">
|
|
63
|
-
<div className="rounded-md bg-yellow-50 p-4 border border-yellow-200">
|
|
64
|
-
<h2 className="text-lg font-bold text-yellow-700">User Not Found</h2>
|
|
65
|
-
<p className="text-yellow-600">Could not find user with ID: {id}</p>
|
|
66
|
-
<a href="/users" className="text-blue-600 hover:underline mt-2 block">
|
|
67
|
-
Back to Users List
|
|
68
|
-
</a>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className="max-w-4xl mx-auto p-4">
|
|
76
|
-
<div className="bg-white shadow-lg rounded-lg p-6 mb-6">
|
|
77
|
-
<h1 className="text-2xl font-bold text-gray-800 mb-4">{userData.name}</h1>
|
|
78
|
-
<div className="user-info mb-6">
|
|
79
|
-
<p className="text-gray-600">
|
|
80
|
-
<span className="font-bold">ID:</span> {userData.id}
|
|
81
|
-
</p>
|
|
82
|
-
<p className="text-gray-600">
|
|
83
|
-
<span className="font-bold">Email:</span> {userData.email}
|
|
84
|
-
</p>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
<a href="/users" className="text-blue-600 hover:underline">
|
|
88
|
-
Back to Users List
|
|
89
|
-
</a>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
{userPosts && userPosts.length > 0 ? (
|
|
93
|
-
<div className="bg-white shadow-lg rounded-lg p-6">
|
|
94
|
-
<h2 className="text-xl font-bold text-gray-800 mb-4">Posts by {userData.name}</h2>
|
|
95
|
-
<div className="space-y-4">
|
|
96
|
-
{userPosts.map(post => (
|
|
97
|
-
<div key={post.id} className="border-b pb-4">
|
|
98
|
-
<h3 className="text-lg font-semibold">{post.title}</h3>
|
|
99
|
-
<p className="text-gray-600">{post.content}</p>
|
|
100
|
-
</div>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
) : (
|
|
105
|
-
<div className="bg-white shadow-lg rounded-lg p-6">
|
|
106
|
-
<h2 className="text-xl font-bold text-gray-800 mb-4">Posts by {userData.name}</h2>
|
|
107
|
-
<p className="text-gray-500 italic">No posts found for this user.</p>
|
|
108
|
-
</div>
|
|
109
|
-
)}
|
|
110
|
-
</div>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Next.js style server-side data fetching
|
|
115
|
-
export async function getServerSideProps({ params }) {
|
|
116
|
-
try {
|
|
117
|
-
const userId = params.id;
|
|
118
|
-
|
|
119
|
-
// Parallel data fetching
|
|
120
|
-
const [user, posts] = await Promise.all([
|
|
121
|
-
UserApi.getById(parseInt(userId)),
|
|
122
|
-
UserApi.getPosts(parseInt(userId))
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
// Handle user not found
|
|
126
|
-
if (!user) {
|
|
127
|
-
return {
|
|
128
|
-
notFound: true
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
props: {
|
|
134
|
-
user,
|
|
135
|
-
posts
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error(`Error fetching user ${params.id}:`, error);
|
|
140
|
-
|
|
141
|
-
// Pass the error for client-side handling
|
|
142
|
-
return {
|
|
143
|
-
props: {
|
|
144
|
-
error: {
|
|
145
|
-
message: error.message,
|
|
146
|
-
status: error.status || 500
|
|
147
|
-
},
|
|
148
|
-
user: null,
|
|
149
|
-
posts: []
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'frontend-hamroun';
|
|
2
|
-
import Layout from '../components/Layout';
|
|
3
|
-
import { UserApi } from '../data/api';
|
|
4
|
-
|
|
5
|
-
const UsersPage = ({ initialState }) => {
|
|
6
|
-
const users = initialState.data?.users || [];
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<Layout title="User Management">
|
|
10
|
-
<div className="max-w-4xl mx-auto">
|
|
11
|
-
<div className="bg-blue-50 p-6 rounded-lg mb-8 border border-blue-100">
|
|
12
|
-
<h2 className="text-xl font-semibold text-blue-800 mb-2">Data Fetching Demo</h2>
|
|
13
|
-
<p className="text-blue-700">This page demonstrates dynamic data fetching with the Users API.</p>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<div className="bg-white shadow-md rounded-lg overflow-hidden">
|
|
17
|
-
<div className="px-6 py-4 border-b border-gray-200">
|
|
18
|
-
<h2 className="text-xl font-semibold text-gray-800">User List</h2>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
{users.length === 0 ? (
|
|
22
|
-
<div className="p-6 text-center text-gray-500">
|
|
23
|
-
<p>No users found.</p>
|
|
24
|
-
</div>
|
|
25
|
-
) : (
|
|
26
|
-
<div className="overflow-x-auto">
|
|
27
|
-
<table className="w-full">
|
|
28
|
-
<thead>
|
|
29
|
-
<tr className="bg-gray-50">
|
|
30
|
-
<th className="text-left py-3 px-6 font-medium text-gray-600 text-sm uppercase tracking-wider border-b">ID</th>
|
|
31
|
-
<th className="text-left py-3 px-6 font-medium text-gray-600 text-sm uppercase tracking-wider border-b">Name</th>
|
|
32
|
-
<th className="text-left py-3 px-6 font-medium text-gray-600 text-sm uppercase tracking-wider border-b">Email</th>
|
|
33
|
-
</tr>
|
|
34
|
-
</thead>
|
|
35
|
-
<tbody className="divide-y divide-gray-200">
|
|
36
|
-
{users.map(user => (
|
|
37
|
-
<tr key={user.id} className="hover:bg-gray-50">
|
|
38
|
-
<td className="py-4 px-6 text-sm text-gray-900">{user.id}</td>
|
|
39
|
-
<td className="py-4 px-6 text-sm font-medium text-gray-900">{user.name}</td>
|
|
40
|
-
<td className="py-4 px-6 text-sm text-gray-500">{user.email}</td>
|
|
41
|
-
</tr>
|
|
42
|
-
))}
|
|
43
|
-
</tbody>
|
|
44
|
-
</table>
|
|
45
|
-
</div>
|
|
46
|
-
)}
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</Layout>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Static method to fetch initial data for this page
|
|
54
|
-
UsersPage.getInitialData = async () => {
|
|
55
|
-
return {
|
|
56
|
-
users: await UserApi.getAll()
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export default UsersPage;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// This file will be removed as it contains WASM functionality
|