create-aziz-app 2.0.14 โ 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -0
- package/bin/create-aziz-app.js +9 -9
- package/package.json +10 -11
- package/template/README.md +862 -138
- package/template/_framework/boot.js +36 -0
- package/template/_framework/hooks.js +207 -0
- package/template/{modules โ _framework}/next-response.js +5 -13
- package/template/_framework/router.js +279 -0
- package/template/_framework/runtime.js +86 -0
- package/template/api/createUser.js +1 -1
- package/template/api/hello.js +8 -17
- package/template/api/loginUser.js +1 -1
- 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/index.html +24 -0
- package/template/package.json +5 -14
- package/template/scripts/dev-server.js +330 -302
- package/template/scripts/live-reload-client.js +0 -1
- package/template/.vscode/settings.json +0 -15
- package/template/api/getUser.js +0 -60
- package/template/api/getUserWithDB.js +0 -65
- package/template/app.config.js +0 -41
- package/template/app.html +0 -55
- package/template/components/Counter.jsx +0 -36
- package/template/components/Footer.jsx +0 -9
- package/template/components/Navbar.jsx +0 -17
- package/template/docs/API-GUIDE.md +0 -196
- package/template/docs/API-ROUTES.md +0 -538
- package/template/docs/AUTO-IMPORT.md +0 -99
- package/template/docs/AUTO-REGISTRATION.md +0 -248
- package/template/docs/CSS-FRAMEWORKS.md +0 -221
- package/template/docs/DEV-SERVER.md +0 -184
- package/template/docs/GETTING-STARTED.md +0 -205
- package/template/docs/HOW-IT-WORKS.md +0 -362
- package/template/docs/NEXTJS-APP-ROUTER.md +0 -536
- package/template/docs/NEXTJS-COMPARISON.md +0 -473
- package/template/docs/QUICK-START.md +0 -111
- package/template/docs/STYLING.md +0 -269
- package/template/docs/TROUBLESHOOTING.md +0 -282
- package/template/modules/README.md +0 -129
- package/template/modules/api.js +0 -44
- package/template/modules/auto-import.js +0 -130
- package/template/modules/router.js +0 -218
- package/template/modules/state.js +0 -248
- package/template/package-lock.json +0 -13
- package/template/pages/About.jsx +0 -48
- package/template/pages/ApiDemo.jsx +0 -209
- package/template/pages/Contact.jsx +0 -25
- package/template/pages/Example.jsx +0 -59
- package/template/pages/Home.jsx +0 -39
- package/template/pages/HooksDemo.jsx +0 -194
- package/template/pages/test.jsx +0 -72
- package/template/scripts/build.js +0 -81
- package/template/scripts/generate-imports.js +0 -88
- package/template/tailwind.config.js +0 -17
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// Auto-registered as "api-demo" route (or manually register for custom name)
|
|
4
|
-
registerPage("api-demo", ApiDemoPage);
|
|
5
|
-
|
|
6
|
-
function ApiDemoPage() {
|
|
7
|
-
const [users, setUsers] = useState([]);
|
|
8
|
-
const [loading, setLoading] = useState(false);
|
|
9
|
-
const [message, setMessage] = useState('');
|
|
10
|
-
const [newUserName, setNewUserName] = useState('');
|
|
11
|
-
const [newUserEmail, setNewUserEmail] = useState('');
|
|
12
|
-
|
|
13
|
-
// Fetch users from API
|
|
14
|
-
const fetchUsers = async () => {
|
|
15
|
-
setLoading(true);
|
|
16
|
-
try {
|
|
17
|
-
const response = await fetchApi('/api/users');
|
|
18
|
-
const data = await response.json();
|
|
19
|
-
setUsers(data);
|
|
20
|
-
setMessage('Users loaded successfully!');
|
|
21
|
-
} catch (error) {
|
|
22
|
-
setMessage('Error loading users: ' + error.message);
|
|
23
|
-
}
|
|
24
|
-
setLoading(false);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Test hello API
|
|
28
|
-
const testHelloApi = async () => {
|
|
29
|
-
try {
|
|
30
|
-
const response = await fetchApi('/api/hello?name=Aziz');
|
|
31
|
-
const data = await response.json();
|
|
32
|
-
setMessage(data.message);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
setMessage('Error: ' + error.message);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Add new user
|
|
39
|
-
const addUser = async (e) => {
|
|
40
|
-
e.preventDefault();
|
|
41
|
-
if (!newUserName || !newUserEmail) {
|
|
42
|
-
setMessage('Please fill in all fields');
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
setLoading(true);
|
|
47
|
-
try {
|
|
48
|
-
const response = await fetchApi('/api/users', {
|
|
49
|
-
method: 'POST',
|
|
50
|
-
body: JSON.stringify({
|
|
51
|
-
name: newUserName,
|
|
52
|
-
email: newUserEmail
|
|
53
|
-
})
|
|
54
|
-
});
|
|
55
|
-
const data = await response.json();
|
|
56
|
-
setMessage(`User added: ${data.name}`);
|
|
57
|
-
setNewUserName('');
|
|
58
|
-
setNewUserEmail('');
|
|
59
|
-
// Refresh users list
|
|
60
|
-
fetchUsers();
|
|
61
|
-
} catch (error) {
|
|
62
|
-
setMessage('Error adding user: ' + error.message);
|
|
63
|
-
}
|
|
64
|
-
setLoading(false);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Delete user
|
|
68
|
-
const deleteUser = async (userId) => {
|
|
69
|
-
setLoading(true);
|
|
70
|
-
try {
|
|
71
|
-
const response = await fetchApi(`/api/users?id=${userId}`, {
|
|
72
|
-
method: 'DELETE'
|
|
73
|
-
});
|
|
74
|
-
const data = await response.json();
|
|
75
|
-
setMessage(data.message);
|
|
76
|
-
// Refresh users list
|
|
77
|
-
fetchUsers();
|
|
78
|
-
} catch (error) {
|
|
79
|
-
setMessage('Error deleting user: ' + error.message);
|
|
80
|
-
}
|
|
81
|
-
setLoading(false);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<div>
|
|
86
|
-
<h1>API Routes Demo ๐</h1>
|
|
87
|
-
|
|
88
|
-
<div class="card">
|
|
89
|
-
<h2>Test API Endpoints</h2>
|
|
90
|
-
<p>This demonstrates the Next.js-like API route system.</p>
|
|
91
|
-
|
|
92
|
-
<div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
|
|
93
|
-
<button class="btn btn-primary" onClick={testHelloApi}>
|
|
94
|
-
Test /api/hello
|
|
95
|
-
</button>
|
|
96
|
-
<button class="btn btn-primary" onClick={fetchUsers}>
|
|
97
|
-
Fetch Users
|
|
98
|
-
</button>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{message && (
|
|
102
|
-
<div style={{
|
|
103
|
-
padding: '12px',
|
|
104
|
-
background: '#e3f2fd',
|
|
105
|
-
borderRadius: '8px',
|
|
106
|
-
marginTop: '12px',
|
|
107
|
-
color: '#1976d2'
|
|
108
|
-
}}>
|
|
109
|
-
{message}
|
|
110
|
-
</div>
|
|
111
|
-
)}
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
<div class="card">
|
|
115
|
-
<h2>Add New User</h2>
|
|
116
|
-
<form onSubmit={addUser}>
|
|
117
|
-
<div>
|
|
118
|
-
<label>Name:</label>
|
|
119
|
-
<input
|
|
120
|
-
type="text"
|
|
121
|
-
value={newUserName}
|
|
122
|
-
onInput={(e) => setNewUserName(e.target.value)}
|
|
123
|
-
placeholder="Enter name"
|
|
124
|
-
/>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
<div>
|
|
128
|
-
<label>Email:</label>
|
|
129
|
-
<input
|
|
130
|
-
type="email"
|
|
131
|
-
value={newUserEmail}
|
|
132
|
-
onInput={(e) => setNewUserEmail(e.target.value)}
|
|
133
|
-
placeholder="Enter email"
|
|
134
|
-
/>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
<button type="submit" class="btn btn-primary" disabled={loading}>
|
|
138
|
-
{loading ? 'Adding...' : 'Add User'}
|
|
139
|
-
</button>
|
|
140
|
-
</form>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<div class="card">
|
|
144
|
-
<h2>Users List</h2>
|
|
145
|
-
{loading && <p>Loading...</p>}
|
|
146
|
-
|
|
147
|
-
{users.length === 0 && !loading && (
|
|
148
|
-
<p>No users loaded. Click "Fetch Users" to load data.</p>
|
|
149
|
-
)}
|
|
150
|
-
|
|
151
|
-
{users.length > 0 && (
|
|
152
|
-
<div>
|
|
153
|
-
{users.map(user => (
|
|
154
|
-
<div style={{
|
|
155
|
-
padding: '12px',
|
|
156
|
-
background: '#f5f5f5',
|
|
157
|
-
borderRadius: '8px',
|
|
158
|
-
marginBottom: '8px',
|
|
159
|
-
display: 'flex',
|
|
160
|
-
justifyContent: 'space-between',
|
|
161
|
-
alignItems: 'center'
|
|
162
|
-
}}>
|
|
163
|
-
<div>
|
|
164
|
-
<strong>{user.name}</strong>
|
|
165
|
-
<br />
|
|
166
|
-
<span style={{ fontSize: '14px', color: '#666' }}>
|
|
167
|
-
{user.email}
|
|
168
|
-
</span>
|
|
169
|
-
</div>
|
|
170
|
-
<button
|
|
171
|
-
class="btn btn-danger"
|
|
172
|
-
onClick={() => deleteUser(user.id)}
|
|
173
|
-
disabled={loading}
|
|
174
|
-
>
|
|
175
|
-
Delete
|
|
176
|
-
</button>
|
|
177
|
-
</div>
|
|
178
|
-
))}
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<div class="card">
|
|
184
|
-
<h2>How to Use API Routes</h2>
|
|
185
|
-
<p>Create API routes in the <code>api/</code> folder:</p>
|
|
186
|
-
<pre style={{
|
|
187
|
-
background: '#282c34',
|
|
188
|
-
color: '#61dafb',
|
|
189
|
-
padding: '16px',
|
|
190
|
-
borderRadius: '8px',
|
|
191
|
-
overflow: 'auto'
|
|
192
|
-
}}>
|
|
193
|
-
{`// api/myroute.js
|
|
194
|
-
registerApiRoute('myroute', async (req, res) => {
|
|
195
|
-
if (req.method === 'GET') {
|
|
196
|
-
return res.status(200).json({
|
|
197
|
-
message: 'Hello!'
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Use in components
|
|
203
|
-
const response = await fetchApi('/api/myroute');
|
|
204
|
-
const data = await response.json();`}
|
|
205
|
-
</pre>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// Auto-registered as "contact" route
|
|
4
|
-
function ContactPage() {
|
|
5
|
-
return (
|
|
6
|
-
<div>
|
|
7
|
-
<h1>Contact ๐ฌ</h1>
|
|
8
|
-
|
|
9
|
-
<div class="card">
|
|
10
|
-
<h2>Get in touch</h2>
|
|
11
|
-
<p>Fill out the form below and we'll get back to you.</p>
|
|
12
|
-
|
|
13
|
-
<form onSubmit={(e) => {
|
|
14
|
-
e.preventDefault();
|
|
15
|
-
alert("Message sent! (wire this up to a backend)");
|
|
16
|
-
}}>
|
|
17
|
-
<input type="text" placeholder="Your name" required />
|
|
18
|
-
<input type="email" placeholder="Your email" required />
|
|
19
|
-
<textarea placeholder="Your message" required></textarea>
|
|
20
|
-
<button type="submit" class="btn btn-primary">Send Message</button>
|
|
21
|
-
</form>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// That's it! No registerPage() needed.
|
|
4
|
-
// This automatically becomes route "#/example"
|
|
5
|
-
function ExamplePage() {
|
|
6
|
-
const [message, setMessage] = useState('Hello from Example Page! ๐');
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div>
|
|
10
|
-
<h1>Example Page</h1>
|
|
11
|
-
|
|
12
|
-
<div class="card">
|
|
13
|
-
<h2>โจ Auto-Registration Demo</h2>
|
|
14
|
-
<p>
|
|
15
|
-
This page was created without calling <code>registerPage()</code>.
|
|
16
|
-
Just define a function ending with "Page" and it works!
|
|
17
|
-
</p>
|
|
18
|
-
|
|
19
|
-
<div style={{
|
|
20
|
-
background: '#f0f0f0',
|
|
21
|
-
padding: '16px',
|
|
22
|
-
borderRadius: '8px',
|
|
23
|
-
fontFamily: 'monospace',
|
|
24
|
-
fontSize: '14px'
|
|
25
|
-
}}>
|
|
26
|
-
<strong>Function name:</strong> ExamplePage()<br/>
|
|
27
|
-
<strong>Auto route:</strong> #/example<br/>
|
|
28
|
-
<strong>URL:</strong> {window.location.href}
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div class="card">
|
|
33
|
-
<h2>State Management Works Too</h2>
|
|
34
|
-
<p>{message}</p>
|
|
35
|
-
<button
|
|
36
|
-
class="btn btn-primary"
|
|
37
|
-
onClick={() => setMessage('State updated! โ
')}
|
|
38
|
-
>
|
|
39
|
-
Update Message
|
|
40
|
-
</button>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<div class="card">
|
|
44
|
-
<h2>Navigation</h2>
|
|
45
|
-
<div style={{ display: 'flex', gap: '12px' }}>
|
|
46
|
-
<button class="btn btn-primary" onClick={() => navigate("home")}>
|
|
47
|
-
Home
|
|
48
|
-
</button>
|
|
49
|
-
<button class="btn btn-primary" onClick={() => navigate("about")}>
|
|
50
|
-
About
|
|
51
|
-
</button>
|
|
52
|
-
<button class="btn btn-primary" onClick={() => navigate("test")}>
|
|
53
|
-
Test
|
|
54
|
-
</button>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
}
|
package/template/pages/Home.jsx
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// Auto-registered as "home" route
|
|
4
|
-
function HomePage() {
|
|
5
|
-
return (
|
|
6
|
-
<div>
|
|
7
|
-
<h1>Welcome Home ๐</h1>
|
|
8
|
-
|
|
9
|
-
<div class="card">
|
|
10
|
-
<h2>Global State Counter</h2>
|
|
11
|
-
<p>This uses the global state. Click the buttons to update it.</p>
|
|
12
|
-
|
|
13
|
-
<div class="count-display">{state.count}</div>
|
|
14
|
-
|
|
15
|
-
<div class="counter-actions">
|
|
16
|
-
<button class="btn btn-danger" onClick={() => setState({ count: state.count - 1 })}>โ</button>
|
|
17
|
-
<button class="btn btn-primary" onClick={() => setState({ count: state.count + 1 })}>+</button>
|
|
18
|
-
<button class="btn btn-grey" onClick={() => setState({ count: 0 })}>Reset</button>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<div class="card">
|
|
23
|
-
<h2>File-based Routing</h2>
|
|
24
|
-
<p>
|
|
25
|
-
Each file in <strong>pages/</strong> is a route โ just like Next.js.
|
|
26
|
-
Add a new file, run the build script, and it's a new page.
|
|
27
|
-
</p>
|
|
28
|
-
<div style={{ display: 'flex', gap: '12px' }}>
|
|
29
|
-
<button class="btn btn-primary" onClick={() => navigate("test")}>
|
|
30
|
-
Go to Test Page โ
|
|
31
|
-
</button>
|
|
32
|
-
<button class="btn btn-primary" onClick={() => navigate("about")}>
|
|
33
|
-
Go to About โ
|
|
34
|
-
</button>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// Demo page showing all React-like hooks
|
|
4
|
-
function HooksDemoPage() {
|
|
5
|
-
// useState - Basic state
|
|
6
|
-
const [count, setCount] = useState(0);
|
|
7
|
-
const [name, setName] = useState('');
|
|
8
|
-
|
|
9
|
-
// useEffect - Side effects
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
console.log('Component mounted!');
|
|
12
|
-
document.title = `Count: ${count}`;
|
|
13
|
-
|
|
14
|
-
return () => {
|
|
15
|
-
console.log('Cleanup!');
|
|
16
|
-
};
|
|
17
|
-
}, [count]);
|
|
18
|
-
|
|
19
|
-
// useMemo - Memoized value
|
|
20
|
-
const expensiveValue = useMemo(() => {
|
|
21
|
-
console.log('Computing expensive value...');
|
|
22
|
-
return count * 2;
|
|
23
|
-
}, [count]);
|
|
24
|
-
|
|
25
|
-
// useCallback - Memoized function
|
|
26
|
-
const handleIncrement = useCallback(() => {
|
|
27
|
-
setCount(c => c + 1);
|
|
28
|
-
}, []);
|
|
29
|
-
|
|
30
|
-
// useRef - Mutable ref
|
|
31
|
-
const inputRef = useRef(null);
|
|
32
|
-
|
|
33
|
-
const focusInput = () => {
|
|
34
|
-
if (inputRef.current) {
|
|
35
|
-
inputRef.current.focus();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// useReducer - Complex state
|
|
40
|
-
const [state, dispatch] = useReducer((state, action) => {
|
|
41
|
-
switch (action.type) {
|
|
42
|
-
case 'increment':
|
|
43
|
-
return { ...state, value: state.value + 1 };
|
|
44
|
-
case 'decrement':
|
|
45
|
-
return { ...state, value: state.value - 1 };
|
|
46
|
-
case 'reset':
|
|
47
|
-
return { ...state, value: 0 };
|
|
48
|
-
default:
|
|
49
|
-
return state;
|
|
50
|
-
}
|
|
51
|
-
}, { value: 0 });
|
|
52
|
-
|
|
53
|
-
// useLocalStorage - Custom hook
|
|
54
|
-
const [savedName, setSavedName] = useLocalStorage('userName', '');
|
|
55
|
-
|
|
56
|
-
// useFetch - Custom hook
|
|
57
|
-
const { data, loading, error } = useFetch('/api/hello?name=Hooks');
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div>
|
|
61
|
-
<h1>React Hooks Demo ๐ช</h1>
|
|
62
|
-
|
|
63
|
-
<div class="card">
|
|
64
|
-
<h2>useState</h2>
|
|
65
|
-
<p>Basic state management</p>
|
|
66
|
-
<div class="count-display">{count}</div>
|
|
67
|
-
<div class="counter-actions">
|
|
68
|
-
<button class="btn btn-primary" onClick={() => setCount(count + 1)}>
|
|
69
|
-
Increment
|
|
70
|
-
</button>
|
|
71
|
-
<button class="btn btn-danger" onClick={() => setCount(count - 1)}>
|
|
72
|
-
Decrement
|
|
73
|
-
</button>
|
|
74
|
-
<button class="btn btn-grey" onClick={() => setCount(0)}>
|
|
75
|
-
Reset
|
|
76
|
-
</button>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<div class="card">
|
|
81
|
-
<h2>useEffect</h2>
|
|
82
|
-
<p>Check the browser console and page title!</p>
|
|
83
|
-
<p>Current count: <strong>{count}</strong></p>
|
|
84
|
-
<p style={{ color: '#666', fontSize: '14px' }}>
|
|
85
|
-
useEffect runs after render and updates the document title
|
|
86
|
-
</p>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
<div class="card">
|
|
90
|
-
<h2>useMemo</h2>
|
|
91
|
-
<p>Memoized expensive computation</p>
|
|
92
|
-
<p>Count ร 2 = <strong>{expensiveValue}</strong></p>
|
|
93
|
-
<p style={{ color: '#666', fontSize: '14px' }}>
|
|
94
|
-
Check console - computation only runs when count changes
|
|
95
|
-
</p>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<div class="card">
|
|
99
|
-
<h2>useCallback</h2>
|
|
100
|
-
<p>Memoized callback function</p>
|
|
101
|
-
<button class="btn btn-primary" onClick={handleIncrement}>
|
|
102
|
-
Increment (Memoized)
|
|
103
|
-
</button>
|
|
104
|
-
<p style={{ color: '#666', fontSize: '14px', marginTop: '8px' }}>
|
|
105
|
-
Function reference stays the same between renders
|
|
106
|
-
</p>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<div class="card">
|
|
110
|
-
<h2>useRef</h2>
|
|
111
|
-
<p>Mutable reference to DOM element</p>
|
|
112
|
-
<input
|
|
113
|
-
ref={inputRef}
|
|
114
|
-
type="text"
|
|
115
|
-
placeholder="Click button to focus me"
|
|
116
|
-
value={name}
|
|
117
|
-
onInput={(e) => setName(e.target.value)}
|
|
118
|
-
/>
|
|
119
|
-
<button class="btn btn-primary" onClick={focusInput} style={{ marginTop: '8px' }}>
|
|
120
|
-
Focus Input
|
|
121
|
-
</button>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<div class="card">
|
|
125
|
-
<h2>useReducer</h2>
|
|
126
|
-
<p>Complex state management with actions</p>
|
|
127
|
-
<div class="count-display">{state.value}</div>
|
|
128
|
-
<div class="counter-actions">
|
|
129
|
-
<button class="btn btn-primary" onClick={() => dispatch({ type: 'increment' })}>
|
|
130
|
-
+
|
|
131
|
-
</button>
|
|
132
|
-
<button class="btn btn-danger" onClick={() => dispatch({ type: 'decrement' })}>
|
|
133
|
-
โ
|
|
134
|
-
</button>
|
|
135
|
-
<button class="btn btn-grey" onClick={() => dispatch({ type: 'reset' })}>
|
|
136
|
-
Reset
|
|
137
|
-
</button>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
<div class="card">
|
|
142
|
-
<h2>useLocalStorage</h2>
|
|
143
|
-
<p>Persistent state in localStorage</p>
|
|
144
|
-
<input
|
|
145
|
-
type="text"
|
|
146
|
-
placeholder="Enter your name"
|
|
147
|
-
value={savedName}
|
|
148
|
-
onInput={(e) => setSavedName(e.target.value)}
|
|
149
|
-
/>
|
|
150
|
-
<p style={{ marginTop: '8px' }}>
|
|
151
|
-
Saved name: <strong>{savedName || 'Not set'}</strong>
|
|
152
|
-
</p>
|
|
153
|
-
<p style={{ color: '#666', fontSize: '14px' }}>
|
|
154
|
-
Refresh the page - your name persists!
|
|
155
|
-
</p>
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
<div class="card">
|
|
159
|
-
<h2>useFetch</h2>
|
|
160
|
-
<p>Data fetching with loading and error states</p>
|
|
161
|
-
{loading && <p>Loading...</p>}
|
|
162
|
-
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
|
|
163
|
-
{data && (
|
|
164
|
-
<div style={{
|
|
165
|
-
background: '#f0f0f0',
|
|
166
|
-
padding: '12px',
|
|
167
|
-
borderRadius: '8px',
|
|
168
|
-
fontFamily: 'monospace',
|
|
169
|
-
fontSize: '14px'
|
|
170
|
-
}}>
|
|
171
|
-
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
172
|
-
</div>
|
|
173
|
-
)}
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<div class="card">
|
|
177
|
-
<h2>All Hooks Available</h2>
|
|
178
|
-
<ul style={{ lineHeight: '1.8', marginLeft: '20px' }}>
|
|
179
|
-
<li><code>useState</code> - State management</li>
|
|
180
|
-
<li><code>useEffect</code> - Side effects</li>
|
|
181
|
-
<li><code>useMemo</code> - Memoized values</li>
|
|
182
|
-
<li><code>useCallback</code> - Memoized functions</li>
|
|
183
|
-
<li><code>useRef</code> - Mutable refs</li>
|
|
184
|
-
<li><code>useReducer</code> - Complex state</li>
|
|
185
|
-
<li><code>useLocalStorage</code> - Persistent state</li>
|
|
186
|
-
<li><code>useFetch</code> - Data fetching</li>
|
|
187
|
-
</ul>
|
|
188
|
-
<p style={{ marginTop: '12px', color: '#666' }}>
|
|
189
|
-
Just like React/Next.js! ๐
|
|
190
|
-
</p>
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
);
|
|
194
|
-
}
|
package/template/pages/test.jsx
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// Auto-registered as "test" route
|
|
4
|
-
function TestPage() {
|
|
5
|
-
const [count, setCount] = useState(0);
|
|
6
|
-
const [name, setName] = useState('');
|
|
7
|
-
const [email, setEmail] = useState('');
|
|
8
|
-
|
|
9
|
-
const handleSubmit = (e) => {
|
|
10
|
-
e.preventDefault();
|
|
11
|
-
alert(`Form submitted!\nName: ${name}\nEmail: ${email}\nCount: ${count}`);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div>
|
|
16
|
-
<h1>Test Page ๐งช</h1>
|
|
17
|
-
|
|
18
|
-
<div class="card">
|
|
19
|
-
<h2>Counter Example</h2>
|
|
20
|
-
<p>This demonstrates the state management system.</p>
|
|
21
|
-
|
|
22
|
-
<div class="count-display">{count}</div>
|
|
23
|
-
<div class="counter-actions">
|
|
24
|
-
<button class="btn btn-primary" onClick={() => setCount(count + 1)}>
|
|
25
|
-
Increment
|
|
26
|
-
</button>
|
|
27
|
-
<button class="btn btn-danger" onClick={() => setCount(0)}>
|
|
28
|
-
Reset
|
|
29
|
-
</button>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<div class="card">
|
|
34
|
-
<h2>Form Example</h2>
|
|
35
|
-
<p>Try the form with state management.</p>
|
|
36
|
-
|
|
37
|
-
<form onSubmit={handleSubmit}>
|
|
38
|
-
<div>
|
|
39
|
-
<label>Name:</label>
|
|
40
|
-
<input
|
|
41
|
-
type="text"
|
|
42
|
-
value={name}
|
|
43
|
-
onInput={(e) => setName(e.target.value)}
|
|
44
|
-
placeholder="Enter your name"
|
|
45
|
-
/>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div>
|
|
49
|
-
<label>Email:</label>
|
|
50
|
-
<input
|
|
51
|
-
type="email"
|
|
52
|
-
value={email}
|
|
53
|
-
onInput={(e) => setEmail(e.target.value)}
|
|
54
|
-
placeholder="Enter your email"
|
|
55
|
-
/>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<button type="submit" class="btn btn-primary">
|
|
59
|
-
Submit Form
|
|
60
|
-
</button>
|
|
61
|
-
</form>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<div class="card">
|
|
65
|
-
<h2>State Information</h2>
|
|
66
|
-
<p>Current count: <strong>{count}</strong></p>
|
|
67
|
-
<p>Current name: <strong>{name || 'Not set'}</strong></p>
|
|
68
|
-
<p>Current email: <strong>{email || 'Not set'}</strong></p>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
console.log('๐ Scanning for pages, components, and API routes...\n');
|
|
7
|
-
|
|
8
|
-
// Scan a directory for files
|
|
9
|
-
function scanDirectory(dir, extensions = []) {
|
|
10
|
-
const files = [];
|
|
11
|
-
|
|
12
|
-
if (!fs.existsSync(dir)) {
|
|
13
|
-
return files;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const items = fs.readdirSync(dir);
|
|
17
|
-
|
|
18
|
-
for (const item of items) {
|
|
19
|
-
const fullPath = path.join(dir, item);
|
|
20
|
-
const stat = fs.statSync(fullPath);
|
|
21
|
-
|
|
22
|
-
if (stat.isFile()) {
|
|
23
|
-
const ext = path.extname(item);
|
|
24
|
-
if (extensions.length === 0 || extensions.includes(ext)) {
|
|
25
|
-
files.push(path.relative('.', fullPath).replace(/\\/g, '/'));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return files;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Scan for files
|
|
34
|
-
const apiRoutes = scanDirectory('api', ['.js']);
|
|
35
|
-
const components = scanDirectory('components', ['.jsx', '.js']);
|
|
36
|
-
const pages = scanDirectory('pages', ['.jsx', '.js']);
|
|
37
|
-
|
|
38
|
-
console.log(`๐ก Found ${apiRoutes.length} API routes`);
|
|
39
|
-
console.log(`๐งฉ Found ${components.length} components`);
|
|
40
|
-
console.log(`๐ Found ${pages.length} pages\n`);
|
|
41
|
-
|
|
42
|
-
// Generate app.config.js
|
|
43
|
-
const configContent = `// ============================================================
|
|
44
|
-
// APP CONFIGURATION (AUTO-GENERATED)
|
|
45
|
-
// This file is automatically generated by build.js
|
|
46
|
-
// Run: node build.js to regenerate
|
|
47
|
-
// ============================================================
|
|
48
|
-
|
|
49
|
-
window.APP_CONFIG = {
|
|
50
|
-
apiRoutes: ${JSON.stringify(apiRoutes, null, 8).replace(/\n/g, '\n ')},
|
|
51
|
-
|
|
52
|
-
components: ${JSON.stringify(components, null, 8).replace(/\n/g, '\n ')},
|
|
53
|
-
|
|
54
|
-
pages: ${JSON.stringify(pages, null, 8).replace(/\n/g, '\n ')}
|
|
55
|
-
};
|
|
56
|
-
`;
|
|
57
|
-
|
|
58
|
-
fs.writeFileSync('app.config.js', configContent);
|
|
59
|
-
console.log('โ
Generated app.config.js\n');
|
|
60
|
-
|
|
61
|
-
// Generate script tags for app.html
|
|
62
|
-
const scriptTags = [];
|
|
63
|
-
|
|
64
|
-
// Add API routes
|
|
65
|
-
apiRoutes.forEach(route => {
|
|
66
|
-
scriptTags.push(` <script type="text/babel" src="${route}"></script>`);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Add components
|
|
70
|
-
components.forEach(component => {
|
|
71
|
-
scriptTags.push(` <script type="text/babel" src="${component}"></script>`);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Add pages
|
|
75
|
-
pages.forEach(page => {
|
|
76
|
-
scriptTags.push(` <script type="text/babel" src="${page}"></script>`);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
console.log('๐ Copy these script tags to app.html:\n');
|
|
80
|
-
console.log(scriptTags.join('\n'));
|
|
81
|
-
console.log('\nโจ Done! Refresh your browser to see changes.');
|