create-aziz-app 2.0.13 โ 2.0.15
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/README.md +227 -0
- package/package.json +1 -1
- package/template/README.md +862 -138
- package/template/_framework/boot.js +36 -0
- package/template/_framework/hooks.js +207 -0
- package/template/_framework/router.js +279 -0
- package/template/_framework/runtime.js +86 -0
- package/template/api/createUser.js +69 -0
- package/template/api/getUser.js +60 -0
- package/template/api/getUserWithDB.js +65 -0
- package/template/api/hello.js +8 -17
- package/template/api/loginUser.js +61 -0
- package/template/api/users.js +38 -134
- package/template/app/about/page.js +45 -0
- package/template/app/layout.js +23 -0
- package/template/app/page.js +54 -0
- package/template/app/test/page.js +55 -0
- package/template/components/Footer.js +10 -0
- package/template/components/Navbar.js +30 -0
- package/template/docs/NEXTJS-APP-ROUTER.md +536 -0
- package/template/index.html +24 -0
- package/template/lib/db.js +67 -0
- package/template/models/User.js +102 -0
- package/template/modules/next-response.js +58 -0
- package/template/package.json +4 -10
- package/template/scripts/dev-server.js +357 -268
- package/template/scripts/live-reload-client.js +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// API Route: /api/getUser
|
|
2
|
+
// Next.js App Router style with NextResponse
|
|
3
|
+
|
|
4
|
+
const { NextResponse } = require('../modules/next-response');
|
|
5
|
+
|
|
6
|
+
// Mock database
|
|
7
|
+
const users = [
|
|
8
|
+
{ _id: '1', name: 'Aziz Ahmed', email: 'aziz@example.com', role: 'admin', phone: '123-456-7890' },
|
|
9
|
+
{ _id: '2', name: 'John Doe', email: 'john@example.com', role: 'user', phone: '098-765-4321' },
|
|
10
|
+
{ _id: '3', name: 'Jane Smith', email: 'jane@example.com', role: 'user', phone: '555-123-4567' }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// GET handler - Next.js App Router style
|
|
14
|
+
async function GET(request) {
|
|
15
|
+
try {
|
|
16
|
+
// Parse URL and get search params
|
|
17
|
+
const { searchParams } = new URL(request.url);
|
|
18
|
+
const email = searchParams.get('email');
|
|
19
|
+
const id = searchParams.get('id');
|
|
20
|
+
|
|
21
|
+
// Find by email
|
|
22
|
+
if (email) {
|
|
23
|
+
const user = users.find(u => u.email === email);
|
|
24
|
+
if (!user) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'User not found' },
|
|
27
|
+
{ status: 404 }
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return NextResponse.json(user);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Find by ID
|
|
34
|
+
if (id) {
|
|
35
|
+
const user = users.find(u => u._id === id);
|
|
36
|
+
if (!user) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: 'User not found' },
|
|
39
|
+
{ status: 404 }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return NextResponse.json(user);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// No params provided
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: 'Email or ID required' },
|
|
48
|
+
{ status: 400 }
|
|
49
|
+
);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error fetching user:', error);
|
|
52
|
+
return NextResponse.json(
|
|
53
|
+
{ error: 'Failed to fetch user' },
|
|
54
|
+
{ status: 500 }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Export the handler
|
|
60
|
+
module.exports = { GET };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// API Route: /api/getUserWithDB
|
|
2
|
+
// Complete Next.js App Router style with DB and Model
|
|
3
|
+
|
|
4
|
+
const { NextResponse } = require('../modules/next-response');
|
|
5
|
+
const connectDB = require('../lib/db');
|
|
6
|
+
const User = require('../models/User');
|
|
7
|
+
|
|
8
|
+
// GET handler - Exactly like Next.js!
|
|
9
|
+
async function GET(request) {
|
|
10
|
+
try {
|
|
11
|
+
// Connect to database
|
|
12
|
+
await connectDB();
|
|
13
|
+
|
|
14
|
+
// Parse URL and get search params
|
|
15
|
+
const { searchParams } = new URL(request.url);
|
|
16
|
+
const email = searchParams.get('email');
|
|
17
|
+
const id = searchParams.get('id');
|
|
18
|
+
|
|
19
|
+
// Find by email
|
|
20
|
+
if (email) {
|
|
21
|
+
const user = await User.findOne({ email });
|
|
22
|
+
|
|
23
|
+
if (!user) {
|
|
24
|
+
return NextResponse.json(
|
|
25
|
+
{ error: 'User not found' },
|
|
26
|
+
{ status: 404 }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Return user without sensitive fields
|
|
31
|
+
const { password, ...userData } = user;
|
|
32
|
+
return NextResponse.json(userData);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Find by ID
|
|
36
|
+
if (id) {
|
|
37
|
+
const user = await User.findById(id);
|
|
38
|
+
|
|
39
|
+
if (!user) {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: 'User not found' },
|
|
42
|
+
{ status: 404 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Return user without sensitive fields
|
|
47
|
+
const { password, ...userData } = user;
|
|
48
|
+
return NextResponse.json(userData);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// No params - return error
|
|
52
|
+
return NextResponse.json(
|
|
53
|
+
{ error: 'Email or ID required' },
|
|
54
|
+
{ status: 400 }
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error fetching user:', error);
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ error: 'Failed to fetch user' },
|
|
60
|
+
{ status: 500 }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { GET };
|
package/template/api/hello.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
// API Route: /api/hello
|
|
2
|
-
//
|
|
2
|
+
// Next.js App Router style
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const { method, query } = req;
|
|
4
|
+
const { NextResponse } = require('../modules/next-response');
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
error: 'Method not allowed',
|
|
11
|
-
message: `Method ${method} is not allowed. Use GET instead.`
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const name = query.name || 'World';
|
|
6
|
+
async function GET(request) {
|
|
7
|
+
const { searchParams } = new URL(request.url);
|
|
8
|
+
const name = searchParams.get('name') || 'World';
|
|
16
9
|
|
|
17
|
-
return
|
|
10
|
+
return NextResponse.json({
|
|
18
11
|
message: `Hello, ${name}!`,
|
|
19
12
|
timestamp: new Date().toISOString(),
|
|
20
|
-
method: method
|
|
21
|
-
query: query
|
|
13
|
+
method: request.method
|
|
22
14
|
});
|
|
23
15
|
}
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
module.exports = handler;
|
|
17
|
+
module.exports = { GET };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// API Route: /api/loginUser
|
|
2
|
+
// Next.js App Router style
|
|
3
|
+
|
|
4
|
+
const { NextResponse } = require('../modules/next-response');
|
|
5
|
+
|
|
6
|
+
// Mock database
|
|
7
|
+
const users = [
|
|
8
|
+
{ _id: '1', name: 'Aziz Ahmed', email: 'aziz@example.com', password: 'admin123', role: 'admin' },
|
|
9
|
+
{ _id: '2', name: 'John Doe', email: 'john@example.com', password: 'user123', role: 'user' }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
// POST handler
|
|
13
|
+
async function POST(request) {
|
|
14
|
+
try {
|
|
15
|
+
// Parse request body
|
|
16
|
+
const body = await request.json();
|
|
17
|
+
const { email, password } = body;
|
|
18
|
+
|
|
19
|
+
// Validation
|
|
20
|
+
if (!email || !password) {
|
|
21
|
+
return NextResponse.json(
|
|
22
|
+
{ error: 'Email and password are required' },
|
|
23
|
+
{ status: 400 }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Find user
|
|
28
|
+
const user = users.find(u => u.email === email);
|
|
29
|
+
|
|
30
|
+
if (!user) {
|
|
31
|
+
return NextResponse.json(
|
|
32
|
+
{ error: 'Invalid credentials' },
|
|
33
|
+
{ status: 401 }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check password (in production, use bcrypt!)
|
|
38
|
+
if (user.password !== password) {
|
|
39
|
+
return NextResponse.json(
|
|
40
|
+
{ error: 'Invalid credentials' },
|
|
41
|
+
{ status: 401 }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Return user data (without password)
|
|
46
|
+
const { password: _, ...userData } = user;
|
|
47
|
+
|
|
48
|
+
return NextResponse.json({
|
|
49
|
+
...userData,
|
|
50
|
+
message: 'Login successful'
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Login error:', error);
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ error: 'Failed to login' },
|
|
56
|
+
{ status: 500 }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { POST };
|
package/template/api/users.js
CHANGED
|
@@ -1,155 +1,59 @@
|
|
|
1
1
|
// API Route: /api/users
|
|
2
|
-
// Next.js
|
|
2
|
+
// Next.js App Router style
|
|
3
|
+
|
|
4
|
+
const { NextResponse } = require('../modules/next-response');
|
|
3
5
|
|
|
4
|
-
// Mock database (in production, use a real database)
|
|
5
6
|
let users = [
|
|
6
7
|
{ id: 1, name: 'Aziz Ahmed', email: 'aziz@example.com', createdAt: new Date().toISOString() },
|
|
7
8
|
{ id: 2, name: 'John Doe', email: 'john@example.com', createdAt: new Date().toISOString() },
|
|
8
9
|
{ id: 3, name: 'Jane Smith', email: 'jane@example.com', createdAt: new Date().toISOString() }
|
|
9
10
|
];
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
switch (method) {
|
|
16
|
-
case 'GET':
|
|
17
|
-
// GET /api/users - Get all users
|
|
18
|
-
// GET /api/users?id=1 - Get specific user
|
|
19
|
-
if (query.id) {
|
|
20
|
-
const user = users.find(u => u.id === parseInt(query.id));
|
|
21
|
-
if (!user) {
|
|
22
|
-
return res.status(404).json({
|
|
23
|
-
error: 'User not found',
|
|
24
|
-
message: `No user found with id ${query.id}`
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
return res.status(200).json(user);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Return all users
|
|
31
|
-
return res.status(200).json({
|
|
32
|
-
users,
|
|
33
|
-
total: users.length,
|
|
34
|
-
timestamp: new Date().toISOString()
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
case 'POST':
|
|
38
|
-
// POST /api/users - Create new user
|
|
39
|
-
const { name, email } = body;
|
|
40
|
-
|
|
41
|
-
// Validation
|
|
42
|
-
if (!name || !email) {
|
|
43
|
-
return res.status(400).json({
|
|
44
|
-
error: 'Validation error',
|
|
45
|
-
message: 'Name and email are required',
|
|
46
|
-
fields: {
|
|
47
|
-
name: !name ? 'Name is required' : null,
|
|
48
|
-
email: !email ? 'Email is required' : null
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
12
|
+
async function GET(request) {
|
|
13
|
+
const { searchParams } = new URL(request.url);
|
|
14
|
+
const id = searchParams.get('id');
|
|
52
15
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check if email already exists
|
|
61
|
-
if (users.find(u => u.email === email)) {
|
|
62
|
-
return res.status(409).json({
|
|
63
|
-
error: 'Conflict',
|
|
64
|
-
message: 'User with this email already exists'
|
|
65
|
-
});
|
|
66
|
-
}
|
|
16
|
+
if (id) {
|
|
17
|
+
const user = users.find(u => u.id === parseInt(id));
|
|
18
|
+
if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
19
|
+
return NextResponse.json(user);
|
|
20
|
+
}
|
|
67
21
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
|
|
71
|
-
name,
|
|
72
|
-
email,
|
|
73
|
-
createdAt: new Date().toISOString()
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
users.push(newUser);
|
|
77
|
-
|
|
78
|
-
return res.status(201).json({
|
|
79
|
-
message: 'User created successfully',
|
|
80
|
-
user: newUser
|
|
81
|
-
});
|
|
22
|
+
return NextResponse.json({ users, total: users.length });
|
|
23
|
+
}
|
|
82
24
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const userId = query.id || body.id;
|
|
86
|
-
|
|
87
|
-
if (!userId) {
|
|
88
|
-
return res.status(400).json({
|
|
89
|
-
error: 'Bad request',
|
|
90
|
-
message: 'User ID is required'
|
|
91
|
-
});
|
|
92
|
-
}
|
|
25
|
+
async function POST(request) {
|
|
26
|
+
const { name, email } = await request.json();
|
|
93
27
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return res.status(404).json({
|
|
98
|
-
error: 'User not found',
|
|
99
|
-
message: `No user found with id ${userId}`
|
|
100
|
-
});
|
|
101
|
-
}
|
|
28
|
+
if (!name || !email) {
|
|
29
|
+
return NextResponse.json({ error: 'Name and email are required' }, { status: 400 });
|
|
30
|
+
}
|
|
102
31
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...body,
|
|
107
|
-
id: users[userIndex].id, // Prevent ID change
|
|
108
|
-
updatedAt: new Date().toISOString()
|
|
109
|
-
};
|
|
32
|
+
if (users.find(u => u.email === email)) {
|
|
33
|
+
return NextResponse.json({ error: 'Email already exists' }, { status: 409 });
|
|
34
|
+
}
|
|
110
35
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
36
|
+
const newUser = {
|
|
37
|
+
id: Math.max(...users.map(u => u.id)) + 1,
|
|
38
|
+
name, email,
|
|
39
|
+
createdAt: new Date().toISOString()
|
|
40
|
+
};
|
|
41
|
+
users.push(newUser);
|
|
115
42
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const deleteId = query.id;
|
|
119
|
-
|
|
120
|
-
if (!deleteId) {
|
|
121
|
-
return res.status(400).json({
|
|
122
|
-
error: 'Bad request',
|
|
123
|
-
message: 'User ID is required'
|
|
124
|
-
});
|
|
125
|
-
}
|
|
43
|
+
return NextResponse.json({ message: 'User created', user: newUser }, { status: 201 });
|
|
44
|
+
}
|
|
126
45
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return res.status(404).json({
|
|
131
|
-
error: 'User not found',
|
|
132
|
-
message: `No user found with id ${deleteId}`
|
|
133
|
-
});
|
|
134
|
-
}
|
|
46
|
+
async function DELETE(request) {
|
|
47
|
+
const { searchParams } = new URL(request.url);
|
|
48
|
+
const id = searchParams.get('id');
|
|
135
49
|
|
|
136
|
-
|
|
50
|
+
if (!id) return NextResponse.json({ error: 'ID required' }, { status: 400 });
|
|
137
51
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
user: deletedUser
|
|
141
|
-
});
|
|
52
|
+
const idx = users.findIndex(u => u.id === parseInt(id));
|
|
53
|
+
if (idx === -1) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
142
54
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
|
146
|
-
return res.status(405).json({
|
|
147
|
-
error: 'Method not allowed',
|
|
148
|
-
message: `Method ${method} is not allowed for this endpoint`,
|
|
149
|
-
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
|
|
150
|
-
});
|
|
151
|
-
}
|
|
55
|
+
const deleted = users.splice(idx, 1)[0];
|
|
56
|
+
return NextResponse.json({ message: 'User deleted', user: deleted });
|
|
152
57
|
}
|
|
153
58
|
|
|
154
|
-
|
|
155
|
-
module.exports = handler;
|
|
59
|
+
module.exports = { GET, POST, DELETE };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import { navigate } from '/_framework/router.js';
|
|
4
|
+
|
|
5
|
+
// About page โ route: /about
|
|
6
|
+
export default function Page() {
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ padding: '40px 32px', maxWidth: '900px', margin: '0 auto' }}>
|
|
9
|
+
<h1>About ๐</h1>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>What is AzizApp?</h2>
|
|
13
|
+
<p>
|
|
14
|
+
A lightweight Next.js clone โ file-based routing, React hooks,
|
|
15
|
+
server-side API routes, and ES module imports. Zero dependencies.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="card">
|
|
20
|
+
<h2>File Structure</h2>
|
|
21
|
+
<pre style={{ background: '#282c34', color: '#61dafb', padding: '16px', borderRadius: '8px', fontSize: '13px' }}>
|
|
22
|
+
{`app/
|
|
23
|
+
layout.js โ root layout
|
|
24
|
+
page.js โ /
|
|
25
|
+
about/
|
|
26
|
+
page.js โ /about
|
|
27
|
+
blog/
|
|
28
|
+
[slug]/
|
|
29
|
+
page.js โ /blog/:slug
|
|
30
|
+
|
|
31
|
+
api/
|
|
32
|
+
users.js โ /api/users
|
|
33
|
+
|
|
34
|
+
components/
|
|
35
|
+
Navbar.js
|
|
36
|
+
Footer.js`}
|
|
37
|
+
</pre>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<button class="btn btn-primary" onClick={() => navigate('/')}>
|
|
41
|
+
โ Back Home
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import Navbar from '/components/Navbar.js';
|
|
4
|
+
import Footer from '/components/Footer.js';
|
|
5
|
+
|
|
6
|
+
// Root layout โ wraps every page (like Next.js app/layout.js)
|
|
7
|
+
export default function Layout({ children }) {
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
|
10
|
+
<Navbar />
|
|
11
|
+
<main style={{ flex: 1 }}>
|
|
12
|
+
{children}
|
|
13
|
+
</main>
|
|
14
|
+
<Footer />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Optional: export metadata
|
|
20
|
+
export const metadata = {
|
|
21
|
+
title: 'My App',
|
|
22
|
+
description: 'Built with AzizApp'
|
|
23
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import { navigate } from '/_framework/router.js';
|
|
4
|
+
|
|
5
|
+
// Home page โ route: /
|
|
6
|
+
export default function Page() {
|
|
7
|
+
const [count, setCount] = useState(0);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ padding: '40px 32px', maxWidth: '900px', margin: '0 auto' }}>
|
|
11
|
+
<h1>Welcome Home ๐</h1>
|
|
12
|
+
|
|
13
|
+
<div class="card">
|
|
14
|
+
<h2>Counter</h2>
|
|
15
|
+
<p>useState works exactly like React!</p>
|
|
16
|
+
<div class="count-display">{count}</div>
|
|
17
|
+
<div class="counter-actions">
|
|
18
|
+
<button class="btn btn-danger" onClick={() => setCount(c => c - 1)}>โ</button>
|
|
19
|
+
<button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
|
|
20
|
+
<button class="btn btn-grey" onClick={() => setCount(0)}>Reset</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h2>File-based Routing</h2>
|
|
26
|
+
<p>
|
|
27
|
+
Each <code>page.js</code> inside <code>app/</code> is a route โ just like Next.js.
|
|
28
|
+
Create <code>app/about/page.js</code> and it becomes <code>/about</code>.
|
|
29
|
+
</p>
|
|
30
|
+
<div style={{ display: 'flex', gap: '12px' }}>
|
|
31
|
+
<button class="btn btn-primary" onClick={() => navigate('/about')}>
|
|
32
|
+
Go to About โ
|
|
33
|
+
</button>
|
|
34
|
+
<button class="btn btn-primary" onClick={() => navigate('/test')}>
|
|
35
|
+
Go to Test โ
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="card">
|
|
41
|
+
<h2>Import Anything</h2>
|
|
42
|
+
<p>
|
|
43
|
+
Use <code>import</code> statements just like Next.js.
|
|
44
|
+
The dev server resolves bare imports from <code>node_modules</code>.
|
|
45
|
+
</p>
|
|
46
|
+
<pre style={{ background: '#282c34', color: '#61dafb', padding: '16px', borderRadius: '8px', overflow: 'auto', fontSize: '13px' }}>
|
|
47
|
+
{`import mongoose from 'mongoose';
|
|
48
|
+
import connectDB from '@/lib/db';
|
|
49
|
+
import User from '@/models/User';`}
|
|
50
|
+
</pre>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
|
|
4
|
+
// Test page โ route: /test
|
|
5
|
+
export default function Page() {
|
|
6
|
+
const [count, setCount] = useState(0);
|
|
7
|
+
const [name, setName] = useState('');
|
|
8
|
+
const [email, setEmail] = useState('');
|
|
9
|
+
|
|
10
|
+
const handleSubmit = (e) => {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
alert(`Name: ${name}\nEmail: ${email}\nCount: ${count}`);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div style={{ padding: '40px 32px', maxWidth: '900px', margin: '0 auto' }}>
|
|
17
|
+
<h1>Test Page ๐งช</h1>
|
|
18
|
+
|
|
19
|
+
<div class="card">
|
|
20
|
+
<h2>useState</h2>
|
|
21
|
+
<div class="count-display">{count}</div>
|
|
22
|
+
<div class="counter-actions">
|
|
23
|
+
<button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
|
|
24
|
+
<button class="btn btn-danger" onClick={() => setCount(0)}>Reset</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="card">
|
|
29
|
+
<h2>Form</h2>
|
|
30
|
+
<form onSubmit={handleSubmit}>
|
|
31
|
+
<input
|
|
32
|
+
type="text"
|
|
33
|
+
value={name}
|
|
34
|
+
onInput={(e) => setName(e.target.value)}
|
|
35
|
+
placeholder="Your name"
|
|
36
|
+
/>
|
|
37
|
+
<input
|
|
38
|
+
type="email"
|
|
39
|
+
value={email}
|
|
40
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
41
|
+
placeholder="Your email"
|
|
42
|
+
/>
|
|
43
|
+
<button type="submit" class="btn btn-primary">Submit</button>
|
|
44
|
+
</form>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="card">
|
|
48
|
+
<h2>State</h2>
|
|
49
|
+
<p>Name: <strong>{name || 'โ'}</strong></p>
|
|
50
|
+
<p>Email: <strong>{email || 'โ'}</strong></p>
|
|
51
|
+
<p>Count: <strong>{count}</strong></p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import { navigate, useRouter } from '/_framework/router.js';
|
|
4
|
+
|
|
5
|
+
export default function Navbar() {
|
|
6
|
+
const { pathname } = useRouter();
|
|
7
|
+
|
|
8
|
+
const links = [
|
|
9
|
+
{ href: '/', label: 'Home' },
|
|
10
|
+
{ href: '/about', label: 'About' },
|
|
11
|
+
{ href: '/test', label: 'Test' },
|
|
12
|
+
{ href: '/api-demo', label: 'API Demo' },
|
|
13
|
+
{ href: '/contact', label: 'Contact' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<nav>
|
|
18
|
+
<span class="brand" onClick={() => navigate('/')}>โก AzizApp</span>
|
|
19
|
+
{links.map(({ href, label }) =>
|
|
20
|
+
<a
|
|
21
|
+
href={href}
|
|
22
|
+
class={pathname === href ? 'active' : ''}
|
|
23
|
+
onClick={(e) => { e.preventDefault(); navigate(href); }}
|
|
24
|
+
>
|
|
25
|
+
{label}
|
|
26
|
+
</a>
|
|
27
|
+
)}
|
|
28
|
+
</nav>
|
|
29
|
+
);
|
|
30
|
+
}
|