jac-client 0.2.4__py3-none-any.whl → 0.2.5__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 (70) hide show
  1. jac_client/examples/all-in-one/src/app.jac +841 -0
  2. jac_client/examples/all-in-one/src/button.jac +7 -0
  3. jac_client/examples/all-in-one/src/components/button.jac +7 -0
  4. jac_client/examples/asset-serving/css-with-image/src/app.jac +88 -0
  5. jac_client/examples/asset-serving/image-asset/src/app.jac +55 -0
  6. jac_client/examples/asset-serving/import-alias/src/app.jac +111 -0
  7. jac_client/examples/basic/src/app.jac +21 -0
  8. jac_client/examples/basic-auth/src/app.jac +377 -0
  9. jac_client/examples/basic-auth-with-router/src/app.jac +464 -0
  10. jac_client/examples/basic-full-stack/src/app.jac +365 -0
  11. jac_client/examples/css-styling/js-styling/src/app.jac +84 -0
  12. jac_client/examples/css-styling/material-ui/src/app.jac +122 -0
  13. jac_client/examples/css-styling/pure-css/src/app.jac +64 -0
  14. jac_client/examples/css-styling/sass-example/src/app.jac +64 -0
  15. jac_client/examples/css-styling/styled-components/src/app.jac +71 -0
  16. jac_client/examples/css-styling/tailwind-example/src/app.jac +63 -0
  17. jac_client/examples/full-stack-with-auth/src/app.jac +722 -0
  18. jac_client/examples/little-x/src/app.jac +719 -0
  19. jac_client/examples/little-x/src/submit-button.jac +16 -0
  20. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +11 -0
  21. jac_client/examples/nested-folders/nested-advance/src/app.jac +35 -0
  22. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +19 -0
  23. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +43 -0
  24. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +25 -0
  25. jac_client/examples/nested-folders/nested-basic/src/app.jac +13 -0
  26. jac_client/examples/nested-folders/nested-basic/src/button.jac +7 -0
  27. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +7 -0
  28. jac_client/examples/ts-support/src/app.jac +35 -0
  29. jac_client/examples/with-router/src/app.jac +323 -0
  30. jac_client/plugin/cli.jac +547 -0
  31. jac_client/plugin/client.jac +52 -0
  32. jac_client/plugin/client_runtime.cl.jac +38 -0
  33. jac_client/plugin/impl/client.impl.jac +134 -0
  34. jac_client/plugin/impl/client_runtime.impl.jac +177 -0
  35. jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
  36. jac_client/plugin/plugin_config.jac +195 -0
  37. jac_client/plugin/src/__init__.jac +20 -0
  38. jac_client/plugin/src/asset_processor.jac +33 -0
  39. jac_client/plugin/src/babel_processor.jac +18 -0
  40. jac_client/plugin/src/compiler.jac +66 -0
  41. jac_client/plugin/src/config_loader.jac +32 -0
  42. jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
  43. jac_client/plugin/src/impl/babel_processor.impl.jac +84 -0
  44. jac_client/plugin/src/impl/compiler.impl.jac +251 -0
  45. jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
  46. jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
  47. jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
  48. jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
  49. jac_client/plugin/src/impl/vite_bundler.impl.jac +513 -0
  50. jac_client/plugin/src/import_processor.jac +19 -0
  51. jac_client/plugin/src/jac_to_js.jac +35 -0
  52. jac_client/plugin/src/package_installer.jac +26 -0
  53. jac_client/plugin/src/vite_bundler.jac +36 -0
  54. jac_client/plugin/vite_client_bundle.jac +31 -0
  55. jac_client/tests/fixtures/basic-app/app.jac +23 -0
  56. jac_client/tests/fixtures/cl_file/app.cl.jac +48 -0
  57. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  58. jac_client/tests/fixtures/client_app_with_antd/app.jac +34 -0
  59. jac_client/tests/fixtures/js_import/app.jac +34 -0
  60. jac_client/tests/fixtures/relative_import/app.jac +11 -0
  61. jac_client/tests/fixtures/relative_import/button.jac +7 -0
  62. jac_client/tests/fixtures/spawn_test/app.jac +129 -0
  63. jac_client/tests/fixtures/test_fragments_spread/app.jac +67 -0
  64. jac_client/tests/fixtures/with-ts/app.jac +35 -0
  65. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/METADATA +2 -2
  66. jac_client-0.2.5.dist-info/RECORD +74 -0
  67. jac_client-0.2.4.dist-info/RECORD +0 -10
  68. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/WHEEL +0 -0
  69. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/entry_points.txt +0 -0
  70. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,841 @@
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 }
30
+
31
+ #
32
+ # Basic backend walkers
33
+ #
34
+ node Todo {
35
+ has text: str,
36
+ done: bool = False;
37
+ }
38
+
39
+ walker create_todo {
40
+ has text: str;
41
+
42
+ can create with `root entry {
43
+ new_todo = here ++> Todo(text=self.text);
44
+ report new_todo ;
45
+ }
46
+ }
47
+
48
+ walker ping_server {
49
+ can ping with `root entry {
50
+ report "pong from backend!" ;
51
+ }
52
+ }
53
+
54
+ walker get_server_message {
55
+ can info with `root entry {
56
+ report "hello from a basic walker!" ;
57
+ }
58
+ }
59
+
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
+
83
+ def handleUsernameChange(e: any) -> None {
84
+ setUsername(e.target.value);
85
+ }
86
+
87
+ def handlePasswordChange(e: any) -> None {
88
+ setPassword(e.target.value);
89
+ }
90
+
91
+ errorDisplay = None;
92
+ if error {
93
+ errorDisplay = <div
94
+ style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "12px"}}
95
+ >
96
+ {error}
97
+ </div>;
98
+ }
99
+
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>;
186
+ }
187
+
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
+ }
209
+
210
+ def handleUsernameChange(e: any) -> None {
211
+ setUsername(e.target.value);
212
+ }
213
+
214
+ def handlePasswordChange(e: any) -> None {
215
+ setPassword(e.target.value);
216
+ }
217
+
218
+ errorDisplay = None;
219
+ if error {
220
+ errorDisplay = <div
221
+ style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "12px"}}
222
+ >
223
+ {error}
224
+ </div>;
225
+ }
226
+
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>;
313
+ }
314
+
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" />;
320
+ }
321
+ [count, setCount] = useState(0);
322
+ [pingResult, setPingResult] = useState("");
323
+ [serverMessage, setServerMessage] = useState("");
324
+ [lastTodoMessage, setLastTodoMessage] = useState("");
325
+
326
+ useEffect(
327
+ lambda -> None{ console.log("Home count changed: ", count);} , [count]
328
+ );
329
+
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
+ }
336
+ }
337
+
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
+ }
343
+ }
344
+
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
+ }
353
+ }
354
+
355
+ useEffect(lambda -> None{ loadServerMessage();} , []);
356
+
357
+ # Initialize a Web Worker and handle message-based communication
358
+ workerRef = useRef(null);
359
+ [message, setMessage] = useState("");
360
+
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("");
373
+ };
374
+
375
+ return <div
376
+ style={{
377
+ "padding": "2rem",
378
+ "fontFamily": "system-ui, -apple-system, sans-serif"
379
+ }}
380
+ >
381
+ <h1>
382
+ 🍔 Router + Styling + Assets Demo
383
+ </h1>
384
+ <p>
385
+ This home page combines
386
+ {" "}
387
+ <strong>
388
+ React Router,
389
+ </strong>
390
+ {" "}
391
+ <strong>
392
+ pure CSS styling,
393
+ </strong>
394
+ {" "}
395
+ <strong>
396
+ static assets
397
+ </strong>
398
+ {" "}
399
+ and
400
+ {" "}
401
+ <strong>
402
+ nested folder imports
403
+ </strong>
404
+ </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"
434
+ >
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"}}
470
+ >
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
+ }}
495
+ >
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
513
+ {" "}
514
+ <code>
515
+ get_server_message
516
+ </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>
591
+ <p
592
+ style={{"fontSize": "0.9rem", "color": "#666", "marginTop": "0.5rem"}}
593
+ >
594
+ File:
595
+ {" "}
596
+ <code>
597
+ worker.py
598
+ </code>
599
+ {" "}
600
+ — Runs in a separate thread to avoid blocking the UI.
601
+ </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
+ }}
613
+ >
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
+ }}
637
+ >
638
+ <h1>
639
+ 📁 Nested Folder Imports
640
+ </h1>
641
+ <p>
642
+ This page mirrors the
643
+ {" "}
644
+ <code>
645
+ nested-folders/nested-basic
646
+ </code>
647
+ {" "}
648
+ example by importing components from both
649
+ {" "}
650
+ <code>
651
+ components.button
652
+ </code>
653
+ {" "}
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.
688
+ </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}
724
+ style={{
725
+ "padding": "0.5rem 1rem",
726
+ "background": "#ef4444",
727
+ "color": "#ffffff",
728
+ "border": "none",
729
+ "borderRadius": "4px",
730
+ "cursor": "pointer",
731
+ "fontWeight": "600"
732
+ }}
733
+ >
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
+ }
752
+
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
+ }}
760
+ >
761
+ <div
762
+ style={{
763
+ "maxWidth": "1200px",
764
+ "margin": "0 auto",
765
+ "display": "flex",
766
+ "gap": "1rem",
767
+ "alignItems": "center",
768
+ "justifyContent": "space-between"
769
+ }}
770
+ >
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>
838
+ </div>
839
+ </Router>;
840
+ }
841
+ }