create-aziz-app 3.0.9 → 3.0.11
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 +32 -52
- package/package.json +1 -1
- package/template/README.md +46 -330
- package/template/_framework/router.js +27 -1
- package/template/app/about/page.js +0 -3
- package/template/app/layout.js +1 -3
- package/template/app/page.js +1 -4
- package/template/app/test/page.js +5 -8
- package/template/components/Footer.js +0 -3
- package/template/components/Navbar.js +5 -8
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Aziz.js gives you the Next.js developer experience without the weight:
|
|
|
22
22
|
| API routes | ✅ | ✅ |
|
|
23
23
|
| `NextResponse` | ✅ | ✅ |
|
|
24
24
|
| React hooks | ✅ | ✅ |
|
|
25
|
-
|
|
|
25
|
+
| No imports needed in pages | ✅ | ❌ |
|
|
26
26
|
| `@/` alias | ✅ | ✅ |
|
|
27
27
|
| Live reload | ✅ | ✅ |
|
|
28
28
|
| SSR / SSG | ❌ | ✅ |
|
|
@@ -50,7 +50,7 @@ The CLI will ask you:
|
|
|
50
50
|
|
|
51
51
|
```
|
|
52
52
|
my-app/
|
|
53
|
-
├── index.html
|
|
53
|
+
├── index.html
|
|
54
54
|
├── app/
|
|
55
55
|
│ ├── layout.js ← root layout
|
|
56
56
|
│ ├── page.js ← route: /
|
|
@@ -76,11 +76,10 @@ my-app/
|
|
|
76
76
|
|
|
77
77
|
## Pages
|
|
78
78
|
|
|
79
|
+
No imports needed — JSX and all hooks just work.
|
|
80
|
+
|
|
79
81
|
```js
|
|
80
82
|
// app/about/page.js → /about
|
|
81
|
-
/** @jsx h */
|
|
82
|
-
import { h } from '/_framework/runtime.js';
|
|
83
|
-
|
|
84
83
|
export default function Page() {
|
|
85
84
|
const [count, setCount] = useState(0);
|
|
86
85
|
|
|
@@ -100,8 +99,6 @@ export default function Page() {
|
|
|
100
99
|
|
|
101
100
|
```js
|
|
102
101
|
// app/layout.js → wraps every page
|
|
103
|
-
/** @jsx h */
|
|
104
|
-
import { h } from '/_framework/runtime.js';
|
|
105
102
|
import Navbar from '/components/Navbar.js';
|
|
106
103
|
|
|
107
104
|
export default function Layout({ children }) {
|
|
@@ -120,18 +117,14 @@ export default function Layout({ children }) {
|
|
|
120
117
|
|
|
121
118
|
```js
|
|
122
119
|
// api/users.js → /api/users
|
|
123
|
-
const { NextResponse } = require('../
|
|
120
|
+
const { NextResponse } = require('../_framework/next-response');
|
|
124
121
|
|
|
125
122
|
async function GET(request) {
|
|
126
|
-
const { searchParams } = new URL(request.url);
|
|
127
|
-
const id = searchParams.get('id');
|
|
128
|
-
// fetch from DB...
|
|
129
123
|
return NextResponse.json({ users: [] });
|
|
130
124
|
}
|
|
131
125
|
|
|
132
126
|
async function POST(request) {
|
|
133
127
|
const { name, email } = await request.json();
|
|
134
|
-
// save to DB...
|
|
135
128
|
return NextResponse.json({ created: true }, { status: 201 });
|
|
136
129
|
}
|
|
137
130
|
|
|
@@ -140,39 +133,15 @@ module.exports = { GET, POST };
|
|
|
140
133
|
|
|
141
134
|
---
|
|
142
135
|
|
|
143
|
-
##
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
// Framework
|
|
147
|
-
import { h } from '/_framework/runtime.js';
|
|
148
|
-
import { navigate } from '/_framework/router.js';
|
|
149
|
-
|
|
150
|
-
// Absolute paths
|
|
151
|
-
import Navbar from '/components/Navbar.js';
|
|
152
|
-
|
|
153
|
-
// @/ alias
|
|
154
|
-
import connectDB from '@/lib/db';
|
|
155
|
-
import User from '@/models/User';
|
|
156
|
-
|
|
157
|
-
// npm packages (install first, then import)
|
|
158
|
-
import mongoose from 'mongoose';
|
|
159
|
-
import bcrypt from 'bcryptjs';
|
|
160
|
-
import jwt from 'jsonwebtoken';
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
136
|
+
## Hooks
|
|
164
137
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
All hooks work exactly like React — no imports needed in pages:
|
|
138
|
+
All React hooks are available globally — no imports needed:
|
|
168
139
|
|
|
169
140
|
```js
|
|
170
|
-
const [count, setCount]
|
|
171
|
-
const [user, setUser]
|
|
141
|
+
const [count, setCount] = useState(0);
|
|
142
|
+
const [user, setUser] = useState(null);
|
|
172
143
|
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
fetchUser().then(setUser);
|
|
175
|
-
}, []);
|
|
144
|
+
useEffect(() => { fetchUser().then(setUser); }, []);
|
|
176
145
|
|
|
177
146
|
const doubled = useMemo(() => count * 2, [count]);
|
|
178
147
|
const ref = useRef(null);
|
|
@@ -191,30 +160,41 @@ const { data, loading, error } = useFetch('/api/users');
|
|
|
191
160
|
## Navigation
|
|
192
161
|
|
|
193
162
|
```js
|
|
194
|
-
import { navigate, Link, useRouter, useParams, useSearchParams } from '/_framework/router.js';
|
|
195
|
-
|
|
196
163
|
// Programmatic
|
|
197
164
|
navigate('/about');
|
|
198
165
|
|
|
199
|
-
//
|
|
166
|
+
// Link component
|
|
200
167
|
<Link href="/about">About</Link>
|
|
201
168
|
|
|
202
|
-
//
|
|
169
|
+
// Hooks
|
|
203
170
|
const { pathname, push, back } = useRouter();
|
|
204
|
-
|
|
205
|
-
// Dynamic route params (/blog/[slug])
|
|
206
171
|
const { slug } = useParams();
|
|
172
|
+
const q = useSearchParams().get('q');
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Imports
|
|
207
178
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
179
|
+
```js
|
|
180
|
+
// Absolute paths
|
|
181
|
+
import Navbar from '/components/Navbar.js';
|
|
182
|
+
import connectDB from '/lib/db.js';
|
|
183
|
+
|
|
184
|
+
// @/ alias
|
|
185
|
+
import User from '@/models/User';
|
|
186
|
+
|
|
187
|
+
// npm packages (install first, then import normally)
|
|
188
|
+
import mongoose from 'mongoose';
|
|
189
|
+
import bcrypt from 'bcryptjs';
|
|
190
|
+
import jwt from 'jsonwebtoken';
|
|
211
191
|
```
|
|
212
192
|
|
|
213
193
|
---
|
|
214
194
|
|
|
215
195
|
## Full docs
|
|
216
196
|
|
|
217
|
-
See [README
|
|
197
|
+
See the [full README](template/README.md) for complete guides on hooks, API routes, database, authentication, and more.
|
|
218
198
|
|
|
219
199
|
---
|
|
220
200
|
|
|
@@ -224,4 +204,4 @@ See [README.md](template/README.md) inside your generated project for the comple
|
|
|
224
204
|
|
|
225
205
|
## License
|
|
226
206
|
|
|
227
|
-
|
|
207
|
+
MIT
|
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -58,13 +58,10 @@ my-app/
|
|
|
58
58
|
|
|
59
59
|
## Pages
|
|
60
60
|
|
|
61
|
-
Every `page.js` inside `app/` becomes a route. Export a default function
|
|
61
|
+
Every `page.js` inside `app/` becomes a route. Export a default function — no imports needed.
|
|
62
62
|
|
|
63
63
|
```js
|
|
64
64
|
// app/about/page.js → route: /about
|
|
65
|
-
/** @jsx h */
|
|
66
|
-
import { h } from '/_framework/runtime.js';
|
|
67
|
-
|
|
68
65
|
export default function Page() {
|
|
69
66
|
return (
|
|
70
67
|
<div>
|
|
@@ -87,19 +84,15 @@ export default function Page() {
|
|
|
87
84
|
|
|
88
85
|
### Creating a new page
|
|
89
86
|
|
|
90
|
-
Just create the file — no registration
|
|
87
|
+
Just create the file — no registration needed:
|
|
91
88
|
|
|
92
89
|
```bash
|
|
93
|
-
# Creates route /dashboard
|
|
94
90
|
mkdir app/dashboard
|
|
95
91
|
touch app/dashboard/page.js
|
|
96
92
|
```
|
|
97
93
|
|
|
98
94
|
```js
|
|
99
|
-
// app/dashboard/page.js
|
|
100
|
-
/** @jsx h */
|
|
101
|
-
import { h } from '/_framework/runtime.js';
|
|
102
|
-
|
|
95
|
+
// app/dashboard/page.js → route: /dashboard
|
|
103
96
|
export default function Page() {
|
|
104
97
|
return <h1>Dashboard</h1>;
|
|
105
98
|
}
|
|
@@ -109,12 +102,10 @@ export default function Page() {
|
|
|
109
102
|
|
|
110
103
|
## Layouts
|
|
111
104
|
|
|
112
|
-
`layout.js` wraps all pages at the same level and below. The root `app/layout.js` wraps every page
|
|
105
|
+
`layout.js` wraps all pages at the same level and below. The root `app/layout.js` wraps every page.
|
|
113
106
|
|
|
114
107
|
```js
|
|
115
108
|
// app/layout.js
|
|
116
|
-
/** @jsx h */
|
|
117
|
-
import { h } from '/_framework/runtime.js';
|
|
118
109
|
import Navbar from '/components/Navbar.js';
|
|
119
110
|
import Footer from '/components/Footer.js';
|
|
120
111
|
|
|
@@ -133,8 +124,6 @@ export default function Layout({ children }) {
|
|
|
133
124
|
|
|
134
125
|
```js
|
|
135
126
|
// app/dashboard/layout.js — wraps all /dashboard/* pages
|
|
136
|
-
/** @jsx h */
|
|
137
|
-
import { h } from '/_framework/runtime.js';
|
|
138
127
|
import Sidebar from '/components/Sidebar.js';
|
|
139
128
|
|
|
140
129
|
export default function Layout({ children }) {
|
|
@@ -154,8 +143,6 @@ export default function Layout({ children }) {
|
|
|
154
143
|
### navigate()
|
|
155
144
|
|
|
156
145
|
```js
|
|
157
|
-
import { navigate } from '/_framework/router.js';
|
|
158
|
-
|
|
159
146
|
navigate('/about');
|
|
160
147
|
navigate('/blog/my-post');
|
|
161
148
|
navigate('/');
|
|
@@ -164,8 +151,6 @@ navigate('/');
|
|
|
164
151
|
### Link component
|
|
165
152
|
|
|
166
153
|
```js
|
|
167
|
-
import { Link } from '/_framework/router.js';
|
|
168
|
-
|
|
169
154
|
<Link href="/about">About</Link>
|
|
170
155
|
<Link href="/blog/hello" className="nav-link">Blog Post</Link>
|
|
171
156
|
```
|
|
@@ -173,8 +158,6 @@ import { Link } from '/_framework/router.js';
|
|
|
173
158
|
### useRouter hook
|
|
174
159
|
|
|
175
160
|
```js
|
|
176
|
-
import { useRouter } from '/_framework/router.js';
|
|
177
|
-
|
|
178
161
|
export default function Page() {
|
|
179
162
|
const router = useRouter();
|
|
180
163
|
|
|
@@ -192,8 +175,6 @@ export default function Page() {
|
|
|
192
175
|
|
|
193
176
|
```js
|
|
194
177
|
// app/blog/[slug]/page.js
|
|
195
|
-
import { useParams } from '/_framework/router.js';
|
|
196
|
-
|
|
197
178
|
export default function Page() {
|
|
198
179
|
const { slug } = useParams();
|
|
199
180
|
return <h1>Post: {slug}</h1>;
|
|
@@ -204,8 +185,6 @@ export default function Page() {
|
|
|
204
185
|
|
|
205
186
|
```js
|
|
206
187
|
// URL: /search?q=hello&page=2
|
|
207
|
-
import { useSearchParams } from '/_framework/router.js';
|
|
208
|
-
|
|
209
188
|
export default function Page() {
|
|
210
189
|
const searchParams = useSearchParams();
|
|
211
190
|
const query = searchParams.get('q'); // "hello"
|
|
@@ -219,7 +198,7 @@ export default function Page() {
|
|
|
219
198
|
|
|
220
199
|
## React Hooks
|
|
221
200
|
|
|
222
|
-
All hooks work exactly like React — no imports needed
|
|
201
|
+
All hooks work exactly like React — **no imports needed**.
|
|
223
202
|
|
|
224
203
|
### useState
|
|
225
204
|
|
|
@@ -228,24 +207,20 @@ const [count, setCount] = useState(0);
|
|
|
228
207
|
const [user, setUser] = useState(null);
|
|
229
208
|
const [items, setItems] = useState([]);
|
|
230
209
|
|
|
231
|
-
// Functional update
|
|
232
210
|
setCount(prev => prev + 1);
|
|
233
211
|
```
|
|
234
212
|
|
|
235
213
|
### useEffect
|
|
236
214
|
|
|
237
215
|
```js
|
|
238
|
-
// Run on mount
|
|
239
216
|
useEffect(() => {
|
|
240
217
|
console.log('mounted');
|
|
241
218
|
}, []);
|
|
242
219
|
|
|
243
|
-
// Run when value changes
|
|
244
220
|
useEffect(() => {
|
|
245
221
|
document.title = `Count: ${count}`;
|
|
246
222
|
}, [count]);
|
|
247
223
|
|
|
248
|
-
// Cleanup
|
|
249
224
|
useEffect(() => {
|
|
250
225
|
const timer = setInterval(() => tick(), 1000);
|
|
251
226
|
return () => clearInterval(timer);
|
|
@@ -255,7 +230,7 @@ useEffect(() => {
|
|
|
255
230
|
### useMemo
|
|
256
231
|
|
|
257
232
|
```js
|
|
258
|
-
const
|
|
233
|
+
const total = useMemo(() => {
|
|
259
234
|
return items.filter(i => i.active).length;
|
|
260
235
|
}, [items]);
|
|
261
236
|
```
|
|
@@ -273,10 +248,8 @@ const handleClick = useCallback(() => {
|
|
|
273
248
|
```js
|
|
274
249
|
const inputRef = useRef(null);
|
|
275
250
|
|
|
276
|
-
// In JSX
|
|
277
251
|
<input ref={inputRef} type="text" />
|
|
278
252
|
|
|
279
|
-
// Access DOM element
|
|
280
253
|
inputRef.current.focus();
|
|
281
254
|
```
|
|
282
255
|
|
|
@@ -295,13 +268,11 @@ const reducer = (state, action) => {
|
|
|
295
268
|
const [state, dispatch] = useReducer(reducer, { count: 0 });
|
|
296
269
|
|
|
297
270
|
dispatch({ type: 'increment' });
|
|
298
|
-
dispatch({ type: 'reset' });
|
|
299
271
|
```
|
|
300
272
|
|
|
301
273
|
### useContext
|
|
302
274
|
|
|
303
275
|
```js
|
|
304
|
-
// Create context
|
|
305
276
|
const ThemeContext = createContext('light');
|
|
306
277
|
|
|
307
278
|
// Provide value (in layout or parent)
|
|
@@ -309,14 +280,13 @@ const ThemeContext = createContext('light');
|
|
|
309
280
|
{children}
|
|
310
281
|
</ThemeContext.Provider>
|
|
311
282
|
|
|
312
|
-
// Consume
|
|
283
|
+
// Consume anywhere
|
|
313
284
|
const theme = useContext(ThemeContext);
|
|
314
285
|
```
|
|
315
286
|
|
|
316
287
|
### useLocalStorage
|
|
317
288
|
|
|
318
289
|
```js
|
|
319
|
-
// Persists across page refreshes
|
|
320
290
|
const [name, setName] = useLocalStorage('userName', '');
|
|
321
291
|
```
|
|
322
292
|
|
|
@@ -341,12 +311,11 @@ API routes run on the **server** (Node.js). Every `.js` file in `api/` becomes a
|
|
|
341
311
|
|
|
342
312
|
```js
|
|
343
313
|
// api/hello.js → GET /api/hello
|
|
344
|
-
const { NextResponse } = require('../
|
|
314
|
+
const { NextResponse } = require('../_framework/next-response');
|
|
345
315
|
|
|
346
316
|
async function GET(request) {
|
|
347
317
|
const { searchParams } = new URL(request.url);
|
|
348
318
|
const name = searchParams.get('name') || 'World';
|
|
349
|
-
|
|
350
319
|
return NextResponse.json({ message: `Hello, ${name}!` });
|
|
351
320
|
}
|
|
352
321
|
|
|
@@ -357,139 +326,70 @@ module.exports = { GET };
|
|
|
357
326
|
|
|
358
327
|
```js
|
|
359
328
|
// api/users.js → /api/users
|
|
360
|
-
const { NextResponse } = require('../
|
|
329
|
+
const { NextResponse } = require('../_framework/next-response');
|
|
361
330
|
|
|
362
331
|
async function GET(request) {
|
|
363
|
-
const { searchParams } = new URL(request.url);
|
|
364
|
-
const id = searchParams.get('id');
|
|
365
|
-
|
|
366
|
-
if (id) {
|
|
367
|
-
const user = await User.findById(id);
|
|
368
|
-
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
369
|
-
return NextResponse.json(user);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
332
|
const users = await User.find();
|
|
373
333
|
return NextResponse.json(users);
|
|
374
334
|
}
|
|
375
335
|
|
|
376
336
|
async function POST(request) {
|
|
377
|
-
const
|
|
378
|
-
const { name, email } = body;
|
|
379
|
-
|
|
337
|
+
const { name, email } = await request.json();
|
|
380
338
|
if (!name || !email) {
|
|
381
339
|
return NextResponse.json({ error: 'Name and email required' }, { status: 400 });
|
|
382
340
|
}
|
|
383
|
-
|
|
384
341
|
const user = await User.create({ name, email });
|
|
385
342
|
return NextResponse.json(user, { status: 201 });
|
|
386
343
|
}
|
|
387
344
|
|
|
388
345
|
async function DELETE(request) {
|
|
389
346
|
const { searchParams } = new URL(request.url);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
await User.findByIdAndDelete(id);
|
|
347
|
+
await User.findByIdAndDelete(searchParams.get('id'));
|
|
393
348
|
return NextResponse.json({ message: 'Deleted' });
|
|
394
349
|
}
|
|
395
350
|
|
|
396
351
|
module.exports = { GET, POST, DELETE };
|
|
397
352
|
```
|
|
398
353
|
|
|
399
|
-
### Request object
|
|
400
|
-
|
|
401
|
-
```js
|
|
402
|
-
async function POST(request) {
|
|
403
|
-
// Parse body
|
|
404
|
-
const body = await request.json();
|
|
405
|
-
|
|
406
|
-
// Query params
|
|
407
|
-
const { searchParams } = new URL(request.url);
|
|
408
|
-
const page = searchParams.get('page');
|
|
409
|
-
|
|
410
|
-
// Headers
|
|
411
|
-
const token = request.headers.authorization;
|
|
412
|
-
|
|
413
|
-
// Method
|
|
414
|
-
console.log(request.method); // "POST"
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
354
|
### NextResponse
|
|
419
355
|
|
|
420
356
|
```js
|
|
421
|
-
// JSON response
|
|
422
357
|
return NextResponse.json({ data: 'value' });
|
|
423
|
-
|
|
424
|
-
// With status code
|
|
425
358
|
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
426
|
-
|
|
427
|
-
// With custom headers
|
|
428
|
-
return NextResponse.json(data, {
|
|
429
|
-
status: 200,
|
|
430
|
-
headers: { 'X-Custom': 'value' }
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// Redirect
|
|
434
359
|
return NextResponse.redirect('/login');
|
|
435
|
-
|
|
436
|
-
// Plain text
|
|
437
360
|
return NextResponse.text('Hello');
|
|
438
361
|
```
|
|
439
362
|
|
|
440
|
-
### HTTP status codes
|
|
441
|
-
|
|
442
|
-
| Code | Meaning |
|
|
443
|
-
|------|---------|
|
|
444
|
-
| `200` | OK |
|
|
445
|
-
| `201` | Created |
|
|
446
|
-
| `400` | Bad Request |
|
|
447
|
-
| `401` | Unauthorized |
|
|
448
|
-
| `403` | Forbidden |
|
|
449
|
-
| `404` | Not Found |
|
|
450
|
-
| `409` | Conflict |
|
|
451
|
-
| `500` | Server Error |
|
|
452
|
-
|
|
453
363
|
---
|
|
454
364
|
|
|
455
365
|
## Imports
|
|
456
366
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
### Framework imports
|
|
367
|
+
### Components and pages — no imports needed for JSX or hooks
|
|
460
368
|
|
|
461
369
|
```js
|
|
462
|
-
|
|
463
|
-
|
|
370
|
+
// app/dashboard/page.js
|
|
371
|
+
export default function Page() {
|
|
372
|
+
const [count, setCount] = useState(0); // hooks are global
|
|
373
|
+
return <h1>Count: {count}</h1>; // JSX works automatically
|
|
374
|
+
}
|
|
464
375
|
```
|
|
465
376
|
|
|
466
|
-
###
|
|
377
|
+
### Importing other files
|
|
467
378
|
|
|
468
379
|
```js
|
|
469
|
-
|
|
380
|
+
// Absolute path
|
|
381
|
+
import Navbar from '/components/Navbar.js';
|
|
470
382
|
import connectDB from '/lib/db.js';
|
|
471
|
-
import User
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### @/ alias (same as absolute)
|
|
383
|
+
import User from '/models/User.js';
|
|
475
384
|
|
|
476
|
-
|
|
385
|
+
// @/ alias (same as absolute)
|
|
477
386
|
import connectDB from '@/lib/db';
|
|
478
387
|
import User from '@/models/User';
|
|
479
|
-
import Button from '@/components/Button';
|
|
480
|
-
```
|
|
481
388
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
```js
|
|
485
|
-
// Install first: npm install mongoose
|
|
389
|
+
// npm packages (install first: npm install mongoose)
|
|
486
390
|
import mongoose from 'mongoose';
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
import bcrypt from 'bcryptjs';
|
|
490
|
-
|
|
491
|
-
// Install first: npm install jsonwebtoken
|
|
492
|
-
import jwt from 'jsonwebtoken';
|
|
391
|
+
import bcrypt from 'bcryptjs';
|
|
392
|
+
import jwt from 'jsonwebtoken';
|
|
493
393
|
```
|
|
494
394
|
|
|
495
395
|
The dev server resolves bare imports from `node_modules` automatically — no bundler config needed.
|
|
@@ -498,19 +398,13 @@ The dev server resolves bare imports from `node_modules` automatically — no bu
|
|
|
498
398
|
|
|
499
399
|
## Components
|
|
500
400
|
|
|
501
|
-
Components are just functions that return JSX
|
|
401
|
+
Components are just functions that return JSX — no pragma or imports needed.
|
|
502
402
|
|
|
503
403
|
```js
|
|
504
404
|
// components/Button.js
|
|
505
|
-
/** @jsx h */
|
|
506
|
-
import { h } from '/_framework/runtime.js';
|
|
507
|
-
|
|
508
405
|
export default function Button({ onClick, children, variant = 'primary' }) {
|
|
509
406
|
return (
|
|
510
|
-
<button
|
|
511
|
-
class={`btn btn-${variant}`}
|
|
512
|
-
onClick={onClick}
|
|
513
|
-
>
|
|
407
|
+
<button class={`btn btn-${variant}`} onClick={onClick}>
|
|
514
408
|
{children}
|
|
515
409
|
</button>
|
|
516
410
|
);
|
|
@@ -521,35 +415,7 @@ export default function Button({ onClick, children, variant = 'primary' }) {
|
|
|
521
415
|
// Using the component
|
|
522
416
|
import Button from '/components/Button.js';
|
|
523
417
|
|
|
524
|
-
<Button onClick={() => alert('hi')} variant="danger">
|
|
525
|
-
Delete
|
|
526
|
-
</Button>
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### Props
|
|
530
|
-
|
|
531
|
-
```js
|
|
532
|
-
// Strings
|
|
533
|
-
<Component title="Hello" />
|
|
534
|
-
|
|
535
|
-
// Numbers
|
|
536
|
-
<Component count={42} />
|
|
537
|
-
|
|
538
|
-
// Booleans
|
|
539
|
-
<Component disabled={true} />
|
|
540
|
-
<Component disabled />
|
|
541
|
-
|
|
542
|
-
// Objects / arrays
|
|
543
|
-
<Component style={{ color: 'red' }} />
|
|
544
|
-
<Component items={[1, 2, 3]} />
|
|
545
|
-
|
|
546
|
-
// Functions
|
|
547
|
-
<Component onClick={() => doSomething()} />
|
|
548
|
-
|
|
549
|
-
// Children
|
|
550
|
-
<Component>
|
|
551
|
-
<p>Child content</p>
|
|
552
|
-
</Component>
|
|
418
|
+
<Button onClick={() => alert('hi')} variant="danger">Delete</Button>
|
|
553
419
|
```
|
|
554
420
|
|
|
555
421
|
### JSX rules
|
|
@@ -563,7 +429,6 @@ import Button from '/components/Button.js';
|
|
|
563
429
|
|
|
564
430
|
// Self-closing tags need /
|
|
565
431
|
<input type="text" />
|
|
566
|
-
<br />
|
|
567
432
|
|
|
568
433
|
// Expressions in {}
|
|
569
434
|
<p>{count > 0 ? 'positive' : 'zero'}</p>
|
|
@@ -573,7 +438,6 @@ import Button from '/components/Button.js';
|
|
|
573
438
|
|
|
574
439
|
// Conditional rendering
|
|
575
440
|
{isLoggedIn && <Dashboard />}
|
|
576
|
-
{error ? <Error msg={error} /> : <Content />}
|
|
577
441
|
```
|
|
578
442
|
|
|
579
443
|
---
|
|
@@ -596,7 +460,6 @@ export default async function connectDB() {
|
|
|
596
460
|
if (isConnected) return;
|
|
597
461
|
await mongoose.connect(process.env.MONGODB_URI);
|
|
598
462
|
isConnected = true;
|
|
599
|
-
console.log('✅ MongoDB connected');
|
|
600
463
|
}
|
|
601
464
|
```
|
|
602
465
|
|
|
@@ -605,97 +468,41 @@ export default async function connectDB() {
|
|
|
605
468
|
import mongoose from 'mongoose';
|
|
606
469
|
|
|
607
470
|
const userSchema = new mongoose.Schema({
|
|
608
|
-
name:
|
|
609
|
-
email:
|
|
471
|
+
name: { type: String, required: true },
|
|
472
|
+
email: { type: String, required: true, unique: true },
|
|
610
473
|
password: { type: String, required: true },
|
|
611
|
-
role: { type: String, enum: ['user', 'admin'], default: 'user' }
|
|
612
474
|
}, { timestamps: true });
|
|
613
475
|
|
|
614
476
|
export default mongoose.models.User || mongoose.model('User', userSchema);
|
|
615
477
|
```
|
|
616
478
|
|
|
617
|
-
```js
|
|
618
|
-
// api/users.js
|
|
619
|
-
const { NextResponse } = require('../modules/next-response');
|
|
620
|
-
const connectDB = require('../lib/db');
|
|
621
|
-
const User = require('../models/User');
|
|
622
|
-
|
|
623
|
-
async function GET(request) {
|
|
624
|
-
await connectDB();
|
|
625
|
-
|
|
626
|
-
const { searchParams } = new URL(request.url);
|
|
627
|
-
const email = searchParams.get('email');
|
|
628
|
-
|
|
629
|
-
if (email) {
|
|
630
|
-
const user = await User.findOne({ email }).select('-password');
|
|
631
|
-
if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
632
|
-
return NextResponse.json(user);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const users = await User.find().select('-password');
|
|
636
|
-
return NextResponse.json(users);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
module.exports = { GET };
|
|
640
|
-
```
|
|
641
|
-
|
|
642
479
|
### Environment variables
|
|
643
480
|
|
|
644
|
-
Create
|
|
481
|
+
Create `.env` in your project root:
|
|
645
482
|
|
|
646
483
|
```bash
|
|
647
484
|
MONGODB_URI=mongodb://localhost:27017/myapp
|
|
648
485
|
JWT_SECRET=your_secret_key
|
|
649
|
-
API_KEY=your_api_key
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
Access in API routes:
|
|
653
|
-
|
|
654
|
-
```js
|
|
655
|
-
const uri = process.env.MONGODB_URI;
|
|
656
|
-
const secret = process.env.JWT_SECRET;
|
|
657
486
|
```
|
|
658
487
|
|
|
659
|
-
>
|
|
488
|
+
> Install `dotenv` and add `require('dotenv').config()` at the top of `scripts/dev-server.js` to load `.env` automatically.
|
|
660
489
|
|
|
661
490
|
---
|
|
662
491
|
|
|
663
492
|
## Calling APIs from Pages
|
|
664
493
|
|
|
665
494
|
```js
|
|
666
|
-
//
|
|
667
|
-
const
|
|
668
|
-
const users = await
|
|
495
|
+
// GET
|
|
496
|
+
const res = await fetch('/api/users');
|
|
497
|
+
const users = await res.json();
|
|
669
498
|
|
|
670
|
-
// POST
|
|
671
|
-
const
|
|
499
|
+
// POST
|
|
500
|
+
const res = await fetch('/api/users', {
|
|
672
501
|
method: 'POST',
|
|
673
502
|
headers: { 'Content-Type': 'application/json' },
|
|
674
503
|
body: JSON.stringify({ name: 'Aziz', email: 'aziz@example.com' })
|
|
675
504
|
});
|
|
676
|
-
const result = await
|
|
677
|
-
|
|
678
|
-
// With error handling
|
|
679
|
-
async function createUser(data) {
|
|
680
|
-
try {
|
|
681
|
-
const res = await fetch('/api/createUser', {
|
|
682
|
-
method: 'POST',
|
|
683
|
-
headers: { 'Content-Type': 'application/json' },
|
|
684
|
-
body: JSON.stringify(data)
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
const result = await res.json();
|
|
688
|
-
|
|
689
|
-
if (!res.ok) {
|
|
690
|
-
throw new Error(result.error || 'Request failed');
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
return result;
|
|
694
|
-
} catch (err) {
|
|
695
|
-
console.error(err);
|
|
696
|
-
throw err;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
505
|
+
const result = await res.json();
|
|
699
506
|
```
|
|
700
507
|
|
|
701
508
|
### Full login example
|
|
@@ -742,16 +549,16 @@ export default function Page() {
|
|
|
742
549
|
<form onSubmit={handleLogin}>
|
|
743
550
|
<input
|
|
744
551
|
type="email"
|
|
552
|
+
placeholder="Email"
|
|
745
553
|
value={email}
|
|
746
554
|
onInput={(e) => setEmail(e.target.value)}
|
|
747
|
-
placeholder="Email"
|
|
748
555
|
required
|
|
749
556
|
/>
|
|
750
557
|
<input
|
|
751
558
|
type="password"
|
|
559
|
+
placeholder="Password"
|
|
752
560
|
value={password}
|
|
753
561
|
onInput={(e) => setPassword(e.target.value)}
|
|
754
|
-
placeholder="Password"
|
|
755
562
|
required
|
|
756
563
|
/>
|
|
757
564
|
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
@@ -770,21 +577,14 @@ export default function Page() {
|
|
|
770
577
|
|
|
771
578
|
### globals.css
|
|
772
579
|
|
|
773
|
-
Edit `styles/globals.css` for
|
|
580
|
+
Edit `styles/globals.css` for global styles. Built-in utility classes:
|
|
774
581
|
|
|
775
582
|
```css
|
|
776
|
-
|
|
777
|
-
.card { background: white; border-radius: 10px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 20px; }
|
|
778
|
-
|
|
779
|
-
/* Buttons */
|
|
583
|
+
.card { background: white; border-radius: 10px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 20px; }
|
|
780
584
|
.btn { display: inline-block; padding: 10px 22px; border: none; border-radius: 8px; cursor: pointer; }
|
|
781
585
|
.btn-primary { background: #61dafb; color: #282c34; font-weight: bold; }
|
|
782
586
|
.btn-danger { background: #ff6b6b; color: white; }
|
|
783
587
|
.btn-grey { background: #ccc; color: #333; }
|
|
784
|
-
|
|
785
|
-
/* Counter */
|
|
786
|
-
.count-display { font-size: 64px; font-weight: bold; text-align: center; }
|
|
787
|
-
.counter-actions { display: flex; gap: 12px; justify-content: center; }
|
|
788
588
|
```
|
|
789
589
|
|
|
790
590
|
### Inline styles
|
|
@@ -797,27 +597,12 @@ Edit `styles/globals.css` for your global styles. Built-in utility classes:
|
|
|
797
597
|
|
|
798
598
|
### Tailwind CSS
|
|
799
599
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
```bash
|
|
803
|
-
npx create-aziz-app my-app
|
|
804
|
-
# Choose: Tailwind CSS
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
Or add it manually to `index.html`:
|
|
600
|
+
Choose Tailwind during project creation, or add it manually to `index.html`:
|
|
808
601
|
|
|
809
602
|
```html
|
|
810
603
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
811
604
|
```
|
|
812
605
|
|
|
813
|
-
Then use Tailwind classes directly:
|
|
814
|
-
|
|
815
|
-
```js
|
|
816
|
-
<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow">
|
|
817
|
-
<h1 class="text-2xl font-bold text-gray-800">Hello</h1>
|
|
818
|
-
</div>
|
|
819
|
-
```
|
|
820
|
-
|
|
821
606
|
---
|
|
822
607
|
|
|
823
608
|
## Dev Server
|
|
@@ -826,37 +611,16 @@ Then use Tailwind classes directly:
|
|
|
826
611
|
npm run dev
|
|
827
612
|
```
|
|
828
613
|
|
|
829
|
-
The dev server:
|
|
830
614
|
- Serves your app at `http://localhost:3000`
|
|
831
|
-
- Watches
|
|
832
|
-
- Reloads the browser automatically on save (WebSocket live reload)
|
|
615
|
+
- Watches all source folders and live-reloads on save
|
|
833
616
|
- Resolves bare imports from `node_modules`
|
|
834
|
-
- Loads API routes from `api/`
|
|
835
617
|
- Hot-reloads API routes when changed
|
|
836
618
|
|
|
837
|
-
### Custom port
|
|
838
|
-
|
|
839
619
|
```bash
|
|
620
|
+
# Custom port
|
|
840
621
|
PORT=4000 npm run dev
|
|
841
622
|
```
|
|
842
623
|
|
|
843
|
-
Or set it during project creation:
|
|
844
|
-
|
|
845
|
-
```bash
|
|
846
|
-
npx create-aziz-app my-app
|
|
847
|
-
# Enter port: 4000
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
### Live reload indicator
|
|
851
|
-
|
|
852
|
-
A small indicator appears in the bottom-right corner:
|
|
853
|
-
|
|
854
|
-
- 🟢 **Live Reload** — connected, watching for changes
|
|
855
|
-
- 🔵 **Reloading...** — change detected, refreshing
|
|
856
|
-
- 🟠 **Disconnected** — reconnecting...
|
|
857
|
-
|
|
858
|
-
Click it to manually reload.
|
|
859
|
-
|
|
860
624
|
---
|
|
861
625
|
|
|
862
626
|
## Comparison with Next.js
|
|
@@ -870,13 +634,12 @@ Click it to manually reload.
|
|
|
870
634
|
| Catch-all `[...slug]` | ✅ | ✅ |
|
|
871
635
|
| API routes | ✅ | ✅ |
|
|
872
636
|
| `NextResponse` | ✅ | ✅ |
|
|
873
|
-
| Named HTTP exports | ✅ | ✅ |
|
|
874
637
|
| React hooks | ✅ | ✅ |
|
|
638
|
+
| No imports needed in pages | ✅ | ❌ |
|
|
875
639
|
| ES module imports | ✅ | ✅ |
|
|
876
640
|
| `@/` path alias | ✅ | ✅ |
|
|
877
641
|
| Live reload | ✅ | ✅ |
|
|
878
642
|
| SSR / SSG | ❌ | ✅ |
|
|
879
|
-
| Image optimization | ❌ | ✅ |
|
|
880
643
|
| TypeScript | ❌ | ✅ |
|
|
881
644
|
| Bundle size | **~50KB** | ~400KB+ |
|
|
882
645
|
| Dependencies | **0** | 20+ |
|
|
@@ -884,53 +647,6 @@ Click it to manually reload.
|
|
|
884
647
|
|
|
885
648
|
---
|
|
886
649
|
|
|
887
|
-
## FAQ
|
|
888
|
-
|
|
889
|
-
**Do I need to register pages manually?**
|
|
890
|
-
No. Just create `app/your-route/page.js` and it becomes a route automatically.
|
|
891
|
-
|
|
892
|
-
**Can I use npm packages?**
|
|
893
|
-
Yes. Run `npm install package-name` and import it normally:
|
|
894
|
-
```js
|
|
895
|
-
import mongoose from 'mongoose';
|
|
896
|
-
```
|
|
897
|
-
|
|
898
|
-
**Where do I put shared utilities?**
|
|
899
|
-
In `lib/`:
|
|
900
|
-
```js
|
|
901
|
-
// lib/utils.js
|
|
902
|
-
export function formatDate(date) {
|
|
903
|
-
return new Date(date).toLocaleDateString();
|
|
904
|
-
}
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
**How do I share state between pages?**
|
|
908
|
-
Use `useLocalStorage` for persistence, or create a context:
|
|
909
|
-
```js
|
|
910
|
-
const AuthContext = createContext(null);
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
**Can I use this in production?**
|
|
914
|
-
Aziz.js is designed for development and learning. For production apps with SEO requirements, consider Next.js. For simple SPAs and internal tools, Aziz.js works great.
|
|
915
|
-
|
|
916
|
-
**How do I add authentication?**
|
|
917
|
-
```js
|
|
918
|
-
// api/login.js
|
|
919
|
-
const { NextResponse } = require('../modules/next-response');
|
|
920
|
-
const jwt = require('jsonwebtoken'); // npm install jsonwebtoken
|
|
921
|
-
|
|
922
|
-
async function POST(request) {
|
|
923
|
-
const { email, password } = await request.json();
|
|
924
|
-
// verify credentials...
|
|
925
|
-
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET);
|
|
926
|
-
return NextResponse.json({ token });
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
module.exports = { POST };
|
|
930
|
-
```
|
|
931
|
-
|
|
932
|
-
---
|
|
933
|
-
|
|
934
650
|
## License
|
|
935
651
|
|
|
936
652
|
MIT
|
|
@@ -271,7 +271,15 @@ export const router = {
|
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
// Render
|
|
274
|
+
// Render — preserve focus so inputs don't lose cursor on state update
|
|
275
|
+
const activeEl = document.activeElement;
|
|
276
|
+
const activeId = activeEl?.id || null;
|
|
277
|
+
const activeName = activeEl?.getAttribute?.('name') || null;
|
|
278
|
+
const activePlaceholder = activeEl?.getAttribute?.('placeholder') || null;
|
|
279
|
+
const activeTag = activeEl?.tagName || null;
|
|
280
|
+
const activeCursorStart = activeEl?.selectionStart ?? null;
|
|
281
|
+
const activeCursorEnd = activeEl?.selectionEnd ?? null;
|
|
282
|
+
|
|
275
283
|
root.innerHTML = '';
|
|
276
284
|
const tree = content();
|
|
277
285
|
if (Array.isArray(tree)) {
|
|
@@ -281,6 +289,24 @@ export const router = {
|
|
|
281
289
|
} else if (tree instanceof Node) {
|
|
282
290
|
root.appendChild(tree);
|
|
283
291
|
}
|
|
292
|
+
|
|
293
|
+
// Restore focus to the previously active input
|
|
294
|
+
let restored = null;
|
|
295
|
+
if (activeId) {
|
|
296
|
+
restored = root.querySelector(`#${CSS.escape(activeId)}`);
|
|
297
|
+
}
|
|
298
|
+
if (!restored && activeName) {
|
|
299
|
+
restored = root.querySelector(`[name="${activeName}"]`);
|
|
300
|
+
}
|
|
301
|
+
if (!restored && activePlaceholder && activeTag) {
|
|
302
|
+
restored = root.querySelector(`${activeTag.toLowerCase()}[placeholder="${activePlaceholder}"]`);
|
|
303
|
+
}
|
|
304
|
+
if (restored && typeof restored.focus === 'function') {
|
|
305
|
+
restored.focus();
|
|
306
|
+
if (activeCursorStart !== null && restored.setSelectionRange) {
|
|
307
|
+
try { restored.setSelectionRange(activeCursorStart, activeCursorEnd); } catch {}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
284
310
|
}
|
|
285
311
|
};
|
|
286
312
|
|
package/template/app/layout.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
import { h } from '/_framework/runtime.js';
|
|
3
1
|
import Navbar from '/components/Navbar.js';
|
|
4
2
|
import Footer from '/components/Footer.js';
|
|
3
|
+
|
|
5
4
|
// Root layout — wraps every page (like Next.js app/layout.js)
|
|
6
5
|
export default function Layout({ children }) {
|
|
7
6
|
return (
|
|
@@ -15,7 +14,6 @@ export default function Layout({ children }) {
|
|
|
15
14
|
);
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
// Optional: export metadata
|
|
19
17
|
export const metadata = {
|
|
20
18
|
title: 'My App',
|
|
21
19
|
description: 'Built with Aziz.js'
|
package/template/app/page.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
import { h } from '/_framework/runtime.js';
|
|
3
|
-
|
|
4
1
|
// Home page — route: /
|
|
5
2
|
export default function Page() {
|
|
6
3
|
const [count, setCount] = useState(0);
|
|
@@ -40,7 +37,7 @@ export default function Page() {
|
|
|
40
37
|
<h2>Import Anything</h2>
|
|
41
38
|
<p>
|
|
42
39
|
Use <code>import</code> statements just like Next.js.
|
|
43
|
-
The dev server resolves bare imports from <code>node_modules</code
|
|
40
|
+
The dev server resolves bare imports from <code>node_modules</code> automatically.
|
|
44
41
|
</p>
|
|
45
42
|
<pre style={{ background: '#282c34', color: '#61dafb', padding: '16px', borderRadius: '8px', overflow: 'auto', fontSize: '13px' }}>
|
|
46
43
|
{`import mongoose from 'mongoose';
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
import { h } from '/_framework/runtime.js';
|
|
3
|
-
|
|
4
1
|
// Test page — route: /test
|
|
5
2
|
export default function Page() {
|
|
6
|
-
const [count, setCount]
|
|
7
|
-
const [name, setName]
|
|
8
|
-
const [email, setEmail]
|
|
3
|
+
const [count, setCount] = useState(0);
|
|
4
|
+
const [name, setName] = useState('');
|
|
5
|
+
const [email, setEmail] = useState('');
|
|
9
6
|
|
|
10
7
|
const handleSubmit = (e) => {
|
|
11
8
|
e.preventDefault();
|
|
@@ -30,15 +27,15 @@ export default function Page() {
|
|
|
30
27
|
<form onSubmit={handleSubmit}>
|
|
31
28
|
<input
|
|
32
29
|
type="text"
|
|
30
|
+
placeholder="Your name"
|
|
33
31
|
value={name}
|
|
34
32
|
onInput={(e) => setName(e.target.value)}
|
|
35
|
-
placeholder="Your name"
|
|
36
33
|
/>
|
|
37
34
|
<input
|
|
38
35
|
type="email"
|
|
36
|
+
placeholder="Your email"
|
|
39
37
|
value={email}
|
|
40
38
|
onInput={(e) => setEmail(e.target.value)}
|
|
41
|
-
placeholder="Your email"
|
|
42
39
|
/>
|
|
43
40
|
<button type="submit" class="btn btn-primary">Submit</button>
|
|
44
41
|
</form>
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import { h } from '/_framework/runtime.js';
|
|
3
|
-
|
|
4
|
-
// navigate and useRouter are globals set by boot.js
|
|
1
|
+
// navigate and useRouter are globals — no imports needed
|
|
5
2
|
export default function Navbar() {
|
|
6
3
|
const { pathname } = useRouter();
|
|
7
4
|
|
|
8
5
|
const links = [
|
|
9
|
-
{ href: '/',
|
|
10
|
-
{ href: '/about',
|
|
11
|
-
{ href: '/test',
|
|
12
|
-
{ href: '/contact',
|
|
6
|
+
{ href: '/', label: 'Home' },
|
|
7
|
+
{ href: '/about', label: 'About' },
|
|
8
|
+
{ href: '/test', label: 'Test' },
|
|
9
|
+
{ href: '/contact', label: 'Contact' },
|
|
13
10
|
];
|
|
14
11
|
|
|
15
12
|
return (
|