jac-client 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl

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.
Files changed (61) hide show
  1. jac_client/examples/all-in-one/src/app.jac +473 -741
  2. jac_client/examples/all-in-one/src/components/CategoryFilter.jac +35 -0
  3. jac_client/examples/all-in-one/src/components/Header.jac +13 -0
  4. jac_client/examples/all-in-one/src/components/ProfitOverview.jac +50 -0
  5. jac_client/examples/all-in-one/src/components/Summary.jac +53 -0
  6. jac_client/examples/all-in-one/src/components/TransactionForm.jac +158 -0
  7. jac_client/examples/all-in-one/src/components/TransactionItem.jac +55 -0
  8. jac_client/examples/all-in-one/src/components/TransactionList.jac +37 -0
  9. jac_client/examples/all-in-one/src/components/navigation.jac +132 -0
  10. jac_client/examples/all-in-one/src/constants/categories.jac +37 -0
  11. jac_client/examples/all-in-one/src/constants/clients.jac +13 -0
  12. jac_client/examples/all-in-one/src/context/BudgetContext.jac +28 -0
  13. jac_client/examples/all-in-one/src/hooks/useBudget.jac +116 -0
  14. jac_client/examples/all-in-one/src/hooks/useLocalStorage.jac +36 -0
  15. jac_client/examples/all-in-one/src/pages/BudgetPlanner.cl.jac +70 -0
  16. jac_client/examples/all-in-one/src/pages/BudgetPlanner.jac +126 -0
  17. jac_client/examples/all-in-one/src/pages/FeaturesTest.cl.jac +552 -0
  18. jac_client/examples/all-in-one/src/pages/FeaturesTest.jac +126 -0
  19. jac_client/examples/all-in-one/src/pages/LandingPage.jac +101 -0
  20. jac_client/examples/all-in-one/src/pages/loginPage.jac +132 -0
  21. jac_client/examples/all-in-one/src/pages/nestedDemo.jac +61 -0
  22. jac_client/examples/all-in-one/src/pages/notFound.jac +24 -0
  23. jac_client/examples/all-in-one/src/pages/signupPage.jac +133 -0
  24. jac_client/examples/all-in-one/src/utils/formatters.jac +52 -0
  25. jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
  26. jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
  27. jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
  28. jac_client/examples/basic/src/app.jac +3 -3
  29. jac_client/examples/basic-auth/src/app.jac +31 -37
  30. jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
  31. jac_client/examples/basic-full-stack/src/app.jac +24 -30
  32. jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
  33. jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
  34. jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
  35. jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
  36. jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
  37. jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
  38. jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
  39. jac_client/examples/ts-support/src/app.jac +4 -4
  40. jac_client/examples/with-router/src/app.jac +4 -4
  41. jac_client/plugin/cli.jac +155 -203
  42. jac_client/plugin/client_runtime.cl.jac +5 -1
  43. jac_client/plugin/impl/client.impl.jac +74 -12
  44. jac_client/plugin/plugin_config.jac +11 -11
  45. jac_client/plugin/src/compiler.jac +2 -1
  46. jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
  47. jac_client/plugin/src/impl/compiler.impl.jac +57 -18
  48. jac_client/plugin/src/impl/vite_bundler.impl.jac +66 -102
  49. jac_client/plugin/src/package_installer.jac +1 -1
  50. jac_client/plugin/src/vite_bundler.jac +1 -0
  51. jac_client/tests/conftest.py +10 -8
  52. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  53. jac_client/tests/fixtures/with-ts/app.jac +4 -4
  54. jac_client/tests/test_cli.py +99 -45
  55. jac_client/tests/test_it.py +290 -79
  56. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/METADATA +16 -7
  57. jac_client-0.2.7.dist-info/RECORD +97 -0
  58. jac_client-0.2.5.dist-info/RECORD +0 -74
  59. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/WHEEL +0 -0
  60. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/entry_points.txt +0 -0
  61. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/top_level.txt +0 -0
@@ -1,32 +1,4 @@
1
- #
2
- # Combined example: auth + routing + CSS styling + asset serving + nested folder imports
3
- #
4
- cl import from react { useState, useEffect, useRef }
5
- cl import from "@jac-client/utils" {
6
- Router,
7
- Routes,
8
- Route,
9
- Link,
10
- Navigate,
11
- useNavigate,
12
- useLocation,
13
- jacSignup,
14
- jacLogin,
15
- jacLogout,
16
- jacIsLoggedIn
17
- }
18
-
19
- # Pure CSS + asset-in-CSS example
20
- cl import ".styles.css";
21
-
22
- # Nested folder imports (same pattern as nested-basic/)
23
- cl import from .components.button {
24
- CustomButton
25
- }
26
- cl import from .button { CustomButtonRoot }
27
-
28
- # TypeScript component import
29
- cl import from ".components/Card.tsx" { Card }
1
+ import from datetime { datetime, timedelta }
30
2
 
31
3
  #
32
4
  # Basic backend walkers
@@ -57,785 +29,545 @@ walker get_server_message {
57
29
  }
58
30
  }
59
31
 
60
- cl {
61
- # Login Page
62
- def LoginPage -> any {
63
- [username, setUsername] = useState("");
64
- [password, setPassword] = useState("");
65
- [error, setError] = useState("");
66
- navigate = useNavigate();
67
-
68
- async def handleLogin(e: any) -> None {
69
- e.preventDefault();
70
- setError("");
71
- if not username or not password {
72
- setError("Please fill in all fields");
73
- return;
74
- }
75
- success = await jacLogin(username, password);
76
- if success {
77
- navigate("/");
78
- } else {
79
- setError("Invalid credentials");
80
- }
81
- }
82
32
 
83
- def handleUsernameChange(e: any) -> None {
84
- setUsername(e.target.value);
85
- }
86
33
 
87
- def handlePasswordChange(e: any) -> None {
88
- setPassword(e.target.value);
89
- }
34
+ # Features Test Page - Backend Logic
35
+ # This file is intentionally kept empty to demonstrate the separation of concerns
36
+ # where UI logic resides in .cl.jac files and backend walker logic in app.jac
90
37
 
91
- errorDisplay = None;
92
- if error {
93
- errorDisplay = <div
94
- style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "12px"}}
95
- >
96
- {error}
97
- </div>;
98
- }
38
+ # All walker implementations for this feature are in src/app.jac
39
+ # This demonstrates that you can have separate .jac files for different purposes
40
+ # Features Test - Backend Walkers
41
+ # This file demonstrates walker functionality for testing various JAC features
99
42
 
100
- return <div
101
- style={{
102
- "minHeight": "calc(100vh - 56px)",
103
- "display": "flex",
104
- "alignItems": "center",
105
- "justifyContent": "center",
106
- "background": "#f5f5f5"
107
- }}
108
- >
109
- <div
110
- style={{
111
- "background": "#ffffff",
112
- "padding": "32px",
113
- "borderRadius": "8px",
114
- "width": "300px",
115
- "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"
116
- }}
117
- >
118
- <h2
119
- style={{"marginBottom": "24px", "textAlign": "center"}}
120
- >
121
- Login
122
- </h2>
123
- <form
124
- onSubmit={handleLogin}
125
- >
126
- <input
127
- type="text"
128
- value={username}
129
- onChange={handleUsernameChange}
130
- placeholder="Username"
131
- style={{
132
- "width": "100%",
133
- "padding": "10px",
134
- "marginBottom": "12px",
135
- "border": "1px solid #ddd",
136
- "borderRadius": "4px",
137
- "boxSizing": "border-box"
138
- }}
139
- />
140
- <input
141
- type="password"
142
- value={password}
143
- onChange={handlePasswordChange}
144
- placeholder="Password"
145
- style={{
146
- "width": "100%",
147
- "padding": "10px",
148
- "marginBottom": "12px",
149
- "border": "1px solid #ddd",
150
- "borderRadius": "4px",
151
- "boxSizing": "border-box"
152
- }}
153
- />
154
- {errorDisplay}
155
- <button
156
- type="submit"
157
- style={{
158
- "width": "100%",
159
- "padding": "10px",
160
- "background": "#3b82f6",
161
- "color": "#ffffff",
162
- "border": "none",
163
- "borderRadius": "4px",
164
- "cursor": "pointer",
165
- "fontWeight": "600"
166
- }}
167
- >
168
- Login
169
- </button>
170
- </form>
171
- <p
172
- style={{
173
- "textAlign": "center",
174
- "marginTop": "16px",
175
- "fontSize": "14px"
176
- }}
177
- >
178
- Need an account?
179
- {" "}
180
- <Link to="/signup">
181
- Sign up
182
- </Link>
183
- </p>
184
- </div>
185
- </div>;
43
+
44
+ # Node definition for storing test data
45
+ node TestData {
46
+ has message: str;
47
+ has count: int = 0;
48
+ has created_at: str = "";
49
+ }
50
+
51
+ # Walker: Create test data
52
+ walker create_test_data {
53
+ has message: str;
54
+
55
+ can create with `root entry {
56
+ new_data = here ++> TestData(
57
+ message=self.message,
58
+ count=1,
59
+ created_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
60
+ );
61
+ report new_data;
186
62
  }
63
+ }
187
64
 
188
- # Signup Page
189
- def SignupPage -> any {
190
- [username, setUsername] = useState("");
191
- [password, setPassword] = useState("");
192
- [error, setError] = useState("");
193
- navigate = useNavigate();
194
-
195
- async def handleSignup(e: any) -> None {
196
- e.preventDefault();
197
- setError("");
198
- if not username or not password {
199
- setError("Please fill in all fields");
200
- return;
201
- }
202
- result = await jacSignup(username, password);
203
- if result["success"] {
204
- navigate("/");
205
- } else {
206
- setError(result["error"] if result["error"] else "Signup failed");
207
- }
208
- }
65
+ # Walker: Read all test data
66
+ walker read_test_data {
67
+ can read with `root entry {
68
+ visit [-->(`?TestData)];
69
+ }
209
70
 
210
- def handleUsernameChange(e: any) -> None {
211
- setUsername(e.target.value);
212
- }
71
+ can report_data with exit {
72
+ report here;
73
+ }
74
+ }
213
75
 
214
- def handlePasswordChange(e: any) -> None {
215
- setPassword(e.target.value);
216
- }
76
+ # Walker: Update test data
77
+ walker update_test_data {
78
+ has new_message: str;
217
79
 
218
- errorDisplay = None;
219
- if error {
220
- errorDisplay = <div
221
- style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "12px"}}
222
- >
223
- {error}
224
- </div>;
225
- }
80
+ can update with TestData entry {
81
+ here.message = self.new_message;
82
+ here.count = here.count + 1;
83
+ report here;
84
+ }
85
+ }
226
86
 
227
- return <div
228
- style={{
229
- "minHeight": "calc(100vh - 56px)",
230
- "display": "flex",
231
- "alignItems": "center",
232
- "justifyContent": "center",
233
- "background": "#f5f5f5"
234
- }}
235
- >
236
- <div
237
- style={{
238
- "background": "#ffffff",
239
- "padding": "32px",
240
- "borderRadius": "8px",
241
- "width": "300px",
242
- "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"
243
- }}
244
- >
245
- <h2
246
- style={{"marginBottom": "24px", "textAlign": "center"}}
247
- >
248
- Sign Up
249
- </h2>
250
- <form
251
- onSubmit={handleSignup}
252
- >
253
- <input
254
- type="text"
255
- value={username}
256
- onChange={handleUsernameChange}
257
- placeholder="Username"
258
- style={{
259
- "width": "100%",
260
- "padding": "10px",
261
- "marginBottom": "12px",
262
- "border": "1px solid #ddd",
263
- "borderRadius": "4px",
264
- "boxSizing": "border-box"
265
- }}
266
- />
267
- <input
268
- type="password"
269
- value={password}
270
- onChange={handlePasswordChange}
271
- placeholder="Password"
272
- style={{
273
- "width": "100%",
274
- "padding": "10px",
275
- "marginBottom": "12px",
276
- "border": "1px solid #ddd",
277
- "borderRadius": "4px",
278
- "boxSizing": "border-box"
279
- }}
280
- />
281
- {errorDisplay}
282
- <button
283
- type="submit"
284
- style={{
285
- "width": "100%",
286
- "padding": "10px",
287
- "background": "#3b82f6",
288
- "color": "#ffffff",
289
- "border": "none",
290
- "borderRadius": "4px",
291
- "cursor": "pointer",
292
- "fontWeight": "600"
293
- }}
294
- >
295
- Sign Up
296
- </button>
297
- </form>
298
- <p
299
- style={{
300
- "textAlign": "center",
301
- "marginTop": "16px",
302
- "fontSize": "14px"
303
- }}
304
- >
305
- Have an account?
306
- {" "}
307
- <Link to="/login">
308
- Login
309
- </Link>
310
- </p>
311
- </div>
312
- </div>;
87
+ # Walker: Delete test data
88
+ walker delete_test_data {
89
+ can delete with TestData entry {
90
+ del here;
91
+ report {"status": "deleted"};
313
92
  }
93
+ }
94
+
95
+ # Walker: String manipulation test
96
+ walker test_string_methods {
97
+ has input_text: str;
98
+
99
+ can process with `root entry {
100
+ result = {
101
+ "original": self.input_text,
102
+ "uppercase": self.input_text.upper(),
103
+ "lowercase": self.input_text.lower(),
104
+ "capitalized": self.input_text.capitalize(),
105
+ "reversed": self.input_text[::-1],
106
+ "length": len(self.input_text),
107
+ "words": self.input_text.split(),
108
+ "trimmed": self.input_text.strip(),
109
+ "replaced": self.input_text.replace("test", "demo")
110
+ };
111
+ report result;
112
+ }
113
+ }
114
+
115
+ # Walker: Array/List operations test
116
+ walker test_list_operations {
117
+ has numbers: list;
118
+
119
+ can process with `root entry {
120
+ result = {
121
+ "original": self.numbers,
122
+ "sorted": sorted(self.numbers),
123
+ "reversed": list(reversed(self.numbers)),
124
+ "sum": sum(self.numbers),
125
+ "max": max(self.numbers) if len(self.numbers) > 0 else 0,
126
+ "min": min(self.numbers) if len(self.numbers) > 0 else 0,
127
+ "length": len(self.numbers),
128
+ "doubled": [x * 2 for x in self.numbers]
129
+ };
130
+ report result;
131
+ }
132
+ }
314
133
 
315
- # Home page demonstrating CSS styling + direct asset usage + basic walkers
316
- def Home -> any {
317
- # Check if user is logged in, redirect if not
318
- if not jacIsLoggedIn() {
319
- return <Navigate to="/login" />;
134
+ # Walker: Complex data processing
135
+ walker process_complex_data {
136
+ has items: list;
137
+
138
+ can process with `root entry {
139
+ processed = [];
140
+ for item in self.items {
141
+ processed.append({
142
+ "id": item.get("id", 0),
143
+ "name": item.get("name", "").upper(),
144
+ "value": item.get("value", 0) * 2,
145
+ "processed_at": datetime.now().strftime("%H:%M:%S")
146
+ });
320
147
  }
321
- [count, setCount] = useState(0);
322
- [pingResult, setPingResult] = useState("");
323
- [serverMessage, setServerMessage] = useState("");
324
- [lastTodoMessage, setLastTodoMessage] = useState("");
325
148
 
326
- useEffect(
327
- lambda -> None{ console.log("Home count changed: ", count);} , [count]
328
- );
149
+ result = {
150
+ "original_count": len(self.items),
151
+ "processed_count": len(processed),
152
+ "items": processed,
153
+ "total_value": sum([p["value"] for p in processed])
154
+ };
155
+
156
+ report result;
157
+ }
158
+ }
159
+
160
+
161
+ #
162
+ # Combined example: auth + routing + CSS styling + asset serving + nested folder imports
163
+ #
164
+ cl import from react { useEffect, useRef }
165
+ cl import from "@jac-client/utils" {
166
+ Router,
167
+ Routes,
168
+ Route,
169
+ Link,
170
+ useNavigate,
171
+ Navigate,
172
+ jacIsLoggedIn
173
+
174
+ }
175
+
176
+ # Pure CSS + asset-in-CSS example
177
+ cl import ".styles.css";
178
+
179
+ # Login Page
180
+ cl import from .pages.loginPage { LoginPage }
181
+
182
+ # Signup Page
183
+ cl import from .pages.signupPage { SignupPage }
329
184
 
330
- # Call simple backend walkers
331
- async def handlePing -> None {
332
- result = root spawn ping_server();
333
- if result.reports and result.reports.length > 0 {
334
- setPingResult(result.reports[0][0]);
335
- }
185
+ # Simple 404 page
186
+ cl import from .pages.notFound { NotFound }
187
+
188
+ # Navigation component with active link styling and auth
189
+ cl import from .components.navigation { Navigation }
190
+
191
+ # Page showing nested imports from different folders
192
+ cl import from .pages.nestedDemo { NestedImportsDemo }
193
+
194
+ cl import from .pages.FeaturesTest { FeaturesTest }
195
+
196
+ cl import from .pages.LandingPage { LandingPage }
197
+
198
+ cl import from .pages.BudgetPlanner { BudgetPlanner }
199
+
200
+ # Context provider
201
+ cl import from .context.BudgetContext { BudgetProvider }
202
+
203
+
204
+
205
+ # TypeScript component import
206
+ cl import from ".components/Card.tsx" { Card }
207
+
208
+
209
+ # Main app wrapped in Router (same API as with-router/ example)
210
+ cl {
211
+ def:pub HomePage -> any {
212
+ # Check if user is logged in, redirect if not
213
+ if not jacIsLoggedIn() {
214
+ return <Navigate to="/login" />;
215
+ }
216
+ has count: int = 0;
217
+ has pingResult: str = "";
218
+ has serverMessage: str = "";
219
+ has lastTodoMessage: str = "";
220
+
221
+ useEffect(
222
+ lambda -> None{ console.log("Home count changed: ", count);} , [count]
223
+ );
224
+
225
+ # Call simple backend walkers
226
+ async def handlePing -> None {
227
+ result = root spawn ping_server();
228
+ if result.reports and result.reports.length > 0 {
229
+ pingResult = result.reports[0][0];
336
230
  }
231
+ }
337
232
 
338
- async def loadServerMessage -> None {
339
- result = root spawn get_server_message();
340
- if result.reports and result.reports.length > 0 {
341
- setServerMessage(result.reports[0][0]);
342
- }
233
+ async def loadServerMessage -> None {
234
+ result = root spawn get_server_message();
235
+ if result.reports and result.reports.length > 0 {
236
+ serverMessage = result.reports[0][0];
343
237
  }
238
+ }
344
239
 
345
- # Create a sample Todo node in the graph with a hardcoded payload
346
- async def handleCreateSampleTodo -> None {
347
- result = root spawn create_todo(text="Sample todo from all-in-one app");
348
- if result.reports and result.reports.length > 0 {
349
- todo = result.reports[0][0];
350
- setLastTodoMessage("Created Todo: " + todo.text);
351
- console.log("Created Todo node:", todo);
352
- }
240
+ # Create a sample Todo node in the graph with a hardcoded payload
241
+ async def handleCreateSampleTodo -> None {
242
+ result = root spawn create_todo(text="Sample todo from all-in-one app");
243
+ if result.reports and result.reports.length > 0 {
244
+ todo = result.reports[0][0];
245
+ lastTodoMessage = "Created Todo: " + todo.text;
246
+ console.log("Created Todo node:", todo);
353
247
  }
248
+ }
354
249
 
355
- useEffect(lambda -> None{ loadServerMessage();} , []);
250
+ useEffect(lambda -> None{ loadServerMessage();} , []);
356
251
 
357
- # Initialize a Web Worker and handle message-based communication
358
- workerRef = useRef(null);
359
- [message, setMessage] = useState("");
252
+ # Initialize a Web Worker and handle message-based communication
253
+ workerRef = useRef(None);
254
+ has message: str = "";
360
255
 
361
- useEffect(lambda -> None {
362
- workerRef.current = Reflect.construct(Worker, ["/workers/worker.js"]);
363
- workerRef.current.onmessage = lambda event: any -> None {
364
- console.log("Message received from worker:", event.data);
365
- setMessage(event.data);
366
- };
367
- return (lambda -> None {
368
- workerRef.current.terminate();
369
- });
370
- }, []);
371
- handleClick = lambda -> None {
372
- workerRef.current.postMessage("");
256
+ useEffect(lambda -> None {
257
+ workerRef.current = Reflect.construct(Worker, ["/workers/worker.js"]);
258
+ workerRef.current.onmessage = lambda event: any -> None {
259
+ console.log("Message received from worker:", event.data);
260
+ message = event.data;
373
261
  };
374
-
375
- return <div
376
- style={{
377
- "padding": "2rem",
378
- "fontFamily": "system-ui, -apple-system, sans-serif"
379
- }}
262
+ return (lambda -> None {
263
+ workerRef.current.terminate();
264
+ });
265
+ }, []);
266
+ handleClick = lambda -> None {
267
+ workerRef.current.postMessage("");
268
+ };
269
+
270
+ return <div
271
+ style={{
272
+ "padding": "2rem",
273
+ "fontFamily": "system-ui, -apple-system, sans-serif"
274
+ }}
275
+ >
276
+ <h1>
277
+ 🍔 Router + Styling + Assets Demo
278
+ </h1>
279
+ <p>
280
+ This home page combines
281
+ {" "}
282
+ <strong>
283
+ React Router,
284
+ </strong>
285
+ {" "}
286
+ <strong>
287
+ pure CSS styling,
288
+ </strong>
289
+ {" "}
290
+ <strong>
291
+ static assets
292
+ </strong>
293
+ {" "}
294
+ and
295
+ {" "}
296
+ <strong>
297
+ nested folder imports
298
+ </strong>
299
+ </p>
300
+ <div className="container">
301
+ <h2
302
+ style={{
303
+ "color": "white",
304
+ "textShadow": "2px 2px 4px rgba(0,0,0,0.6)"
305
+ }}
306
+ >
307
+ CSS Background Image
308
+ </h2>
309
+ <p
310
+ style={{
311
+ "color": "white",
312
+ "maxWidth": "480px",
313
+ "textShadow": "1px 1px 3px rgba(0,0,0,0.7)"
314
+ }}
315
+ >
316
+ This section uses the burger image as a background via CSS, just like the
317
+ {" "}
318
+ <code>
319
+ asset-serving/css-with-image
320
+ </code>
321
+ {" "}
322
+ example.
323
+ </p>
324
+ </div>
325
+ <Card
326
+ title="TypeScript Card Component"
327
+ description="This card is built with TypeScript and demonstrates type-safe component usage in Jac"
328
+ variant="highlighted"
380
329
  >
381
- <h1>
382
- 🍔 Router + Styling + Assets Demo
383
- </h1>
330
+ <p
331
+ style={{"margin": "0.5rem 0", "color": "#374151"}}
332
+ >
333
+ This is a TypeScript component imported and used in Jac code!
334
+ </p>
335
+ </Card>
336
+ <div className="card">
337
+ <h3>
338
+ Direct &lt;img&gt; asset
339
+ </h3>
340
+ <img
341
+ src="/static/assets/burger.png"
342
+ alt="Burger asset served by Jac"
343
+ className="burgerImage"
344
+ />
345
+ <p
346
+ style={{"marginTop": "0.75rem", "color": "#555"}}
347
+ >
348
+ This image is served from the project
349
+ {" "}
350
+ <code>
351
+ assets/
352
+ </code>
353
+ {" "}
354
+ folder using the
355
+ {" "}
356
+ <code>
357
+ /static/assets/
358
+ </code>
359
+ {" "}
360
+ path.
361
+ </p>
362
+ </div>
363
+ <div
364
+ style={{"marginTop": "2rem"}}
365
+ >
366
+ <h3>
367
+ Counter with pure CSS classes
368
+ </h3>
384
369
  <p>
385
- This home page combines
370
+ You've clicked the burger
386
371
  {" "}
387
372
  <strong>
388
- React Router,
373
+ {count}
389
374
  </strong>
390
375
  {" "}
391
- <strong>
392
- pure CSS styling,
393
- </strong>
376
+ times.
377
+ </p>
378
+ <button
379
+ onClick={lambda e: any -> None{ count = count + 1;} }
380
+ style={{
381
+ "padding": "0.6rem 1.4rem",
382
+ "fontSize": "1rem",
383
+ "backgroundColor": "#ff6b35",
384
+ "color": "white",
385
+ "border": "none",
386
+ "borderRadius": "6px",
387
+ "cursor": "pointer",
388
+ "boxShadow": "0 2px 4px rgba(0,0,0,0.2)"
389
+ }}
390
+ >
391
+ Click the Burger! 🍔
392
+ </button>
393
+ </div>
394
+ <div
395
+ style={{"marginTop": "2rem"}}
396
+ >
397
+ <h3>
398
+ Backend Walkers
399
+ </h3>
400
+ <p>
401
+ Basic example walkers:
394
402
  {" "}
395
- <strong>
396
- static assets
397
- </strong>
403
+ <code>
404
+ ping_server
405
+ </code>
398
406
  {" "}
399
407
  and
400
408
  {" "}
401
- <strong>
402
- nested folder imports
403
- </strong>
409
+ <code>
410
+ get_server_message
411
+ </code>
404
412
  </p>
405
- <div className="container">
406
- <h2
407
- style={{
408
- "color": "white",
409
- "textShadow": "2px 2px 4px rgba(0,0,0,0.6)"
410
- }}
411
- >
412
- CSS Background Image
413
- </h2>
414
- <p
415
- style={{
416
- "color": "white",
417
- "maxWidth": "480px",
418
- "textShadow": "1px 1px 3px rgba(0,0,0,0.7)"
419
- }}
420
- >
421
- This section uses the burger image as a background via CSS, just like the
422
- {" "}
423
- <code>
424
- asset-serving/css-with-image
425
- </code>
426
- {" "}
427
- example.
428
- </p>
429
- </div>
430
- <Card
431
- title="TypeScript Card Component"
432
- description="This card is built with TypeScript and demonstrates type-safe component usage in Jac"
433
- variant="highlighted"
413
+ <button
414
+ onClick={lambda e: any -> None{ handlePing();} }
415
+ style={{
416
+ "padding": "0.5rem 1.2rem",
417
+ "marginRight": "0.75rem",
418
+ "backgroundColor": "#3b82f6",
419
+ "color": "white",
420
+ "border": "none",
421
+ "borderRadius": "6px",
422
+ "cursor": "pointer"
423
+ }}
434
424
  >
435
- <p
436
- style={{"margin": "0.5rem 0", "color": "#374151"}}
437
- >
438
- This is a TypeScript component imported and used in Jac code!
439
- </p>
440
- </Card>
441
- <div className="card">
442
- <h3>
443
- Direct &lt;img&gt; asset
444
- </h3>
445
- <img
446
- src="/static/assets/burger.png"
447
- alt="Burger asset served by Jac"
448
- className="burgerImage"
449
- />
450
- <p
451
- style={{"marginTop": "0.75rem", "color": "#555"}}
452
- >
453
- This image is served from the project
454
- {" "}
455
- <code>
456
- assets/
457
- </code>
458
- {" "}
459
- folder using the
460
- {" "}
461
- <code>
462
- /static/assets/
463
- </code>
464
- {" "}
465
- path.
466
- </p>
467
- </div>
468
- <div
469
- style={{"marginTop": "2rem"}}
425
+ Ping Backend
426
+ </button>
427
+ <button
428
+ onClick={lambda e: any -> None{ handleCreateSampleTodo();} }
429
+ style={{
430
+ "padding": "0.5rem 1.2rem",
431
+ "backgroundColor": "#10b981",
432
+ "color": "white",
433
+ "border": "none",
434
+ "borderRadius": "6px",
435
+ "cursor": "pointer"
436
+ }}
470
437
  >
471
- <h3>
472
- Counter with pure CSS classes
473
- </h3>
474
- <p>
475
- You've clicked the burger
476
- {" "}
477
- <strong>
478
- {count}
479
- </strong>
480
- {" "}
481
- times.
482
- </p>
483
- <button
484
- onClick={lambda e: any -> None{ setCount(count + 1);} }
485
- style={{
486
- "padding": "0.6rem 1.4rem",
487
- "fontSize": "1rem",
488
- "backgroundColor": "#ff6b35",
489
- "color": "white",
490
- "border": "none",
491
- "borderRadius": "6px",
492
- "cursor": "pointer",
493
- "boxShadow": "0 2px 4px rgba(0,0,0,0.2)"
494
- }}
438
+ Create Sample Todo
439
+ </button>
440
+ {pingResult
441
+ and (
442
+ <span
443
+ style={{"marginLeft": "0.5rem", "color": "#374151"}}
495
444
  >
496
- Click the Burger! 🍔
497
- </button>
498
- </div>
499
- <div
500
- style={{"marginTop": "2rem"}}
501
- >
502
- <h3>
503
- Backend Walkers
504
- </h3>
505
- <p>
506
- Basic example walkers:
507
- {" "}
508
- <code>
509
- ping_server
510
- </code>
511
- {" "}
512
- and
445
+ Result:
513
446
  {" "}
514
447
  <code>
515
- get_server_message
448
+ {pingResult}
516
449
  </code>
517
- </p>
518
- <button
519
- onClick={lambda e: any -> None{ handlePing();} }
520
- style={{
521
- "padding": "0.5rem 1.2rem",
522
- "marginRight": "0.75rem",
523
- "backgroundColor": "#3b82f6",
524
- "color": "white",
525
- "border": "none",
526
- "borderRadius": "6px",
527
- "cursor": "pointer"
528
- }}
529
- >
530
- Ping Backend
531
- </button>
532
- <button
533
- onClick={lambda e: any -> None{ handleCreateSampleTodo();} }
534
- style={{
535
- "padding": "0.5rem 1.2rem",
536
- "backgroundColor": "#10b981",
537
- "color": "white",
538
- "border": "none",
539
- "borderRadius": "6px",
540
- "cursor": "pointer"
541
- }}
542
- >
543
- Create Sample Todo
544
- </button>
545
- {pingResult
546
- and (
547
- <span
548
- style={{"marginLeft": "0.5rem", "color": "#374151"}}
549
- >
550
- Result:
551
- {" "}
552
- <code>
553
- {pingResult}
554
- </code>
555
- </span>
556
- )}
557
- {serverMessage
558
- and (
559
- <p
560
- style={{"marginTop": "0.75rem", "color": "#374151"}}
561
- >
562
- Message:
563
- {" "}
564
- <code>
565
- {serverMessage}
566
- </code>
567
- </p>
568
- )}
569
- {lastTodoMessage
570
- and (
571
- <p
572
- style={{"marginTop": "0.5rem", "color": "#111827"}}
573
- >
574
- {lastTodoMessage}
575
- </p>
576
- )}
577
- </div>
578
- <div
579
- style={{"marginTop": "2rem"}}
580
- >
581
- <h3>
582
- Web Worker
583
- </h3>
584
- <p>
585
- This demonstrates how to communicate with a
586
- {" "}
587
- <strong>Python backend worker</strong>
588
- {" "}
589
- using Web Workers for asynchronous processing.
590
- </p>
450
+ </span>
451
+ )}
452
+ {serverMessage
453
+ and (
591
454
  <p
592
- style={{"fontSize": "0.9rem", "color": "#666", "marginTop": "0.5rem"}}
455
+ style={{"marginTop": "0.75rem", "color": "#374151"}}
593
456
  >
594
- File:
457
+ Message:
595
458
  {" "}
596
459
  <code>
597
- worker.py
460
+ {serverMessage}
598
461
  </code>
599
- {" "}
600
- — Runs in a separate thread to avoid blocking the UI.
601
462
  </p>
602
- <button
603
- onClick={lambda -> None { handleClick(); }}
604
- style={{
605
- "padding": "0.5rem 1.2rem",
606
- "marginRight": "0.75rem",
607
- "backgroundColor": "#d73bf6ff",
608
- "color": "white",
609
- "border": "none",
610
- "borderRadius": "6px",
611
- "cursor": "pointer"
612
- }}
463
+ )}
464
+ {lastTodoMessage
465
+ and (
466
+ <p
467
+ style={{"marginTop": "0.5rem", "color": "#111827"}}
613
468
  >
614
- Call Python Worker
615
- </button>
616
- {message && (
617
- <p style={{ marginTop: "1rem", fontWeight: "bold" }}>
618
- {message}
619
- </p>
620
- )}
621
- </div>
622
- </div>;
623
- }
624
-
625
- # Page showing nested imports from different folders
626
- def NestedImportsDemo -> any {
627
- # Check if user is logged in, redirect if not
628
- if not jacIsLoggedIn() {
629
- return <Navigate to="/login" />;
630
- }
631
-
632
- return <div
633
- style={{
634
- "padding": "2rem",
635
- "fontFamily": "system-ui, -apple-system, sans-serif"
636
- }}
469
+ {lastTodoMessage}
470
+ </p>
471
+ )}
472
+ </div>
473
+ <div
474
+ style={{"marginTop": "2rem"}}
637
475
  >
638
- <h1>
639
- 📁 Nested Folder Imports
640
- </h1>
476
+ <h3>
477
+ Web Worker
478
+ </h3>
641
479
  <p>
642
- This page mirrors the
480
+ This demonstrates how to communicate with a
643
481
  {" "}
644
- <code>
645
- nested-folders/nested-basic
646
- </code>
482
+ <strong>Python backend worker</strong>
647
483
  {" "}
648
- example by importing components from both
484
+ using Web Workers for asynchronous processing.
485
+ </p>
486
+ <p
487
+ style={{"fontSize": "0.9rem", "color": "#666", "marginTop": "0.5rem"}}
488
+ >
489
+ File:
649
490
  {" "}
650
491
  <code>
651
- components.button
492
+ worker.py
652
493
  </code>
653
494
  {" "}
654
- and
655
- {" "}
656
- <code>
657
- button
658
- </code>
659
- </p>
660
- <p
661
- style={{"marginTop": "0.75rem"}}
662
- >
663
- Both buttons below are rendered via relative imports:
664
- </p>
665
- <div
666
- style={{"display": "flex", "gap": "1rem", "marginTop": "1.5rem"}}
667
- >
668
- <CustomButton />
669
- <CustomButtonRoot />
670
- </div>
671
- </div>;
672
- }
673
-
674
- # Simple 404 page
675
- def NotFound -> any {
676
- return <div
677
- style={{
678
- "padding": "2rem",
679
- "textAlign": "center",
680
- "fontFamily": "system-ui, -apple-system, sans-serif"
681
- }}
682
- >
683
- <h1>
684
- 🔍 404 - Page Not Found
685
- </h1>
686
- <p>
687
- The page you are looking for does not exist.
495
+ — Runs in a separate thread to avoid blocking the UI.
688
496
  </p>
689
- <Link to="/">
690
- Back to Home
691
- </Link>
692
- </div>;
693
- }
694
-
695
- # Navigation component with active link styling and auth
696
- def Navigation -> any {
697
- location = useLocation();
698
- isLoggedIn = jacIsLoggedIn();
699
- navigate = useNavigate();
700
-
701
- def linkStyle(path: str) -> dict {
702
- isActive = location.pathname == path;
703
- return {
704
- "padding": "0.5rem 1rem",
705
- "textDecoration": "none",
706
- "color": "#0066cc" if isActive else "#333",
707
- "fontWeight": "bold" if isActive else "normal",
708
- "backgroundColor": "#e3f2fd" if isActive else "transparent",
709
- "borderRadius": "4px",
710
- "display": "inline-block"
711
- };
712
- }
713
-
714
- def handleLogout(e: any) -> None {
715
- e.preventDefault();
716
- jacLogout();
717
- navigate("/login");
718
- }
719
-
720
- authButtons = None;
721
- if isLoggedIn {
722
- authButtons = <button
723
- onClick={handleLogout}
497
+ <button
498
+ onClick={lambda -> None { handleClick(); }}
724
499
  style={{
725
- "padding": "0.5rem 1rem",
726
- "background": "#ef4444",
727
- "color": "#ffffff",
500
+ "padding": "0.5rem 1.2rem",
501
+ "marginRight": "0.75rem",
502
+ "backgroundColor": "#d73bf6ff",
503
+ "color": "white",
728
504
  "border": "none",
729
- "borderRadius": "4px",
730
- "cursor": "pointer",
731
- "fontWeight": "600"
505
+ "borderRadius": "6px",
506
+ "cursor": "pointer"
732
507
  }}
733
508
  >
734
- Logout
735
- </button>;
736
- } else {
737
- authButtons = <>
738
- <Link
739
- to="/login"
740
- style={linkStyle("/login")}
741
- >
742
- Login
743
- </Link>
744
- <Link
745
- to="/signup"
746
- style={linkStyle("/signup")}
747
- >
748
- Sign Up
749
- </Link>
750
- </>;
751
- }
509
+ Call Python Worker
510
+ </button>
511
+ {message && (
512
+ <p style={{ marginTop: "1rem", fontWeight: "bold" }}>
513
+ {message}
514
+ </p>
515
+ )}
516
+ </div>
517
+ </div>;
518
+ }
752
519
 
753
- return <nav
754
- style={{
755
- "padding": "1rem",
756
- "backgroundColor": "#f5f5f5",
757
- "marginBottom": "2rem",
758
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
759
- }}
520
+ def:pub app -> any {
521
+ return <Router>
522
+ <div
523
+ style={{"fontFamily": "system-ui, -apple-system, sans-serif"}}
760
524
  >
525
+ <Navigation />
761
526
  <div
762
527
  style={{
763
- "maxWidth": "1200px",
528
+ "maxWidth": "960px",
764
529
  "margin": "0 auto",
765
- "display": "flex",
766
- "gap": "1rem",
767
- "alignItems": "center",
768
- "justifyContent": "space-between"
530
+ "padding": "0 1rem 3rem 1rem"
769
531
  }}
770
532
  >
771
- <div
772
- style={{"display": "flex", "gap": "1rem", "alignItems": "center"}}
773
- >
774
- {isLoggedIn
775
- and (
776
- <>
777
- <Link
778
- to="/"
779
- style={linkStyle("/")}
780
- >
781
- Home
782
- </Link>
783
- <Link
784
- to="/nested"
785
- style={linkStyle("/nested")}
786
- >
787
- Nested Imports
788
- </Link>
789
- </>
790
- )}
791
- </div>
792
- <div
793
- style={{"display": "flex", "gap": "1rem", "alignItems": "center"}}
794
- >
795
- {authButtons}
796
- </div>
797
- </div>
798
- </nav>;
799
- }
800
-
801
- # Main app wrapped in Router (same API as with-router/ example)
802
- def:pub app -> any {
803
- return <Router>
804
- <div
805
- style={{"fontFamily": "system-ui, -apple-system, sans-serif"}}
806
- >
807
- <Navigation />
808
- <div
809
- style={{
810
- "maxWidth": "960px",
811
- "margin": "0 auto",
812
- "padding": "0 1rem 3rem 1rem"
813
- }}
814
- >
815
- <Routes>
816
- <Route
817
- path="/"
818
- element={<Home />}
819
- />
820
- <Route
821
- path="/login"
822
- element={<LoginPage />}
823
- />
824
- <Route
825
- path="/signup"
826
- element={<SignupPage />}
827
- />
828
- <Route
829
- path="/nested"
830
- element={<NestedImportsDemo />}
831
- />
832
- <Route
833
- path="*"
834
- element={<NotFound />}
835
- />
836
- </Routes>
837
- </div>
533
+ <Routes>
534
+ <Route
535
+ path="/"
536
+ element={<HomePage />}
537
+ />
538
+ <Route
539
+ path="/login"
540
+ element={<LoginPage />}
541
+ />
542
+ <Route
543
+ path="/signup"
544
+ element={<SignupPage />}
545
+ />
546
+ <Route
547
+ path="/nested"
548
+ element={<NestedImportsDemo />}
549
+ />
550
+ <Route
551
+ path="/features-test"
552
+ element={<FeaturesTest />}
553
+ />
554
+ <Route
555
+ path="/landing"
556
+ element={<LandingPage />}
557
+ />
558
+ <Route
559
+ path="/budget-planner"
560
+ element={<BudgetProvider>
561
+ <BudgetPlanner />
562
+ </BudgetProvider>}
563
+ />
564
+ <Route
565
+ path="*"
566
+ element={<NotFound />}
567
+ />
568
+ </Routes>
838
569
  </div>
839
- </Router>;
840
- }
570
+ </div>
571
+ </Router>;
572
+ }
841
573
  }