jac-client 0.2.10__py3-none-any.whl → 0.2.12__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 (85) hide show
  1. jac_client/examples/all-in-one/button.jac +4 -3
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +36 -24
  3. jac_client/examples/all-in-one/components/Header.jac +12 -8
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +49 -35
  5. jac_client/examples/all-in-one/components/Summary.jac +59 -36
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +142 -112
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +37 -30
  8. jac_client/examples/all-in-one/components/TransactionList.jac +33 -26
  9. jac_client/examples/all-in-one/components/button.jac +4 -3
  10. jac_client/examples/all-in-one/components/navigation.jac +111 -117
  11. jac_client/examples/all-in-one/constants/categories.jac +23 -24
  12. jac_client/examples/all-in-one/constants/clients.jac +7 -8
  13. jac_client/examples/all-in-one/context/BudgetContext.jac +9 -6
  14. jac_client/examples/all-in-one/hooks/useBudget.jac +18 -12
  15. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +14 -13
  16. jac_client/examples/all-in-one/main.jac +340 -371
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +19 -12
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +31 -15
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +113 -90
  20. jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +34 -39
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +464 -352
  22. jac_client/examples/all-in-one/pages/loginPage.jac +114 -119
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +43 -50
  24. jac_client/examples/all-in-one/pages/notFound.jac +14 -15
  25. jac_client/examples/all-in-one/pages/signupPage.jac +113 -119
  26. jac_client/examples/all-in-one/utils/formatters.jac +5 -8
  27. jac_client/examples/asset-serving/css-with-image/main.jac +77 -73
  28. jac_client/examples/asset-serving/image-asset/main.jac +47 -46
  29. jac_client/examples/asset-serving/import-alias/main.jac +93 -95
  30. jac_client/examples/basic/main.jac +17 -15
  31. jac_client/examples/basic-auth/main.jac +246 -254
  32. jac_client/examples/basic-auth-with-router/main.jac +272 -285
  33. jac_client/examples/basic-full-stack/main.jac +245 -242
  34. jac_client/examples/css-styling/js-styling/main.jac +41 -62
  35. jac_client/examples/css-styling/material-ui/main.jac +90 -90
  36. jac_client/examples/css-styling/pure-css/main.jac +35 -44
  37. jac_client/examples/css-styling/sass-example/main.jac +35 -44
  38. jac_client/examples/css-styling/styled-components/main.jac +38 -47
  39. jac_client/examples/css-styling/tailwind-example/main.jac +54 -43
  40. jac_client/examples/full-stack-with-auth/main.jac +407 -433
  41. jac_client/examples/little-x/main.jac +306 -344
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +18 -27
  44. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
  45. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
  46. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
  47. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
  48. jac_client/examples/nested-folders/nested-basic/main.jac +7 -5
  49. jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
  50. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
  51. jac_client/examples/ts-support/main.jac +26 -26
  52. jac_client/examples/with-router/main.jac +186 -223
  53. jac_client/plugin/client_runtime.cl.jac +5 -3
  54. jac_client/plugin/impl/client_runtime.impl.jac +1 -1
  55. jac_client/plugin/plugin_config.jac +53 -99
  56. jac_client/plugin/src/__init__.jac +0 -2
  57. jac_client/plugin/src/compiler.jac +0 -1
  58. jac_client/plugin/src/impl/compiler.impl.jac +49 -17
  59. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  60. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  61. jac_client/plugin/src/impl/vite_bundler.impl.jac +146 -84
  62. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +54 -41
  63. jac_client/plugin/utils/__init__.jac +3 -0
  64. jac_client/plugin/utils/bun_installer.jac +16 -0
  65. jac_client/plugin/utils/client_deps.jac +14 -0
  66. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  67. jac_client/plugin/utils/impl/client_deps.impl.jac +73 -0
  68. jac_client/templates/client.jacpack +0 -4
  69. jac_client/templates/fullstack.jacpack +1 -5
  70. jac_client/tests/conftest.py +56 -41
  71. jac_client/tests/fixtures/spawn_test/app.jac +49 -52
  72. jac_client/tests/fixtures/with-ts/app.jac +27 -27
  73. jac_client/tests/test_cli.py +71 -6
  74. jac_client/tests/test_helpers.py +11 -18
  75. jac_client/tests/test_it.py +1 -1
  76. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/METADATA +5 -5
  77. jac_client-0.2.12.dist-info/RECORD +115 -0
  78. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/WHEEL +1 -1
  79. jac_client/plugin/src/babel_processor.jac +0 -18
  80. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -89
  81. jac_client/plugin/utils/impl/node_installer.impl.jac +0 -249
  82. jac_client/plugin/utils/node_installer.jac +0 -41
  83. jac_client-0.2.10.dist-info/RECORD +0 -115
  84. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/entry_points.txt +0 -0
  85. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ cl import from react {
3
3
  useState,
4
4
  useEffect
5
5
  }
6
- cl import from "@jac-client/utils" {
6
+ cl import from "@jac/runtime" {
7
7
  Router,
8
8
  Routes,
9
9
  Route,
@@ -29,7 +29,7 @@ walker create_todo {
29
29
 
30
30
  can create with `root entry {
31
31
  new_todo = here ++> Todo(text=self.text);
32
- report new_todo ;
32
+ report new_todo;
33
33
  }
34
34
  }
35
35
 
@@ -39,14 +39,14 @@ walker read_todos {
39
39
  }
40
40
 
41
41
  can report_todos with Todo entry {
42
- report here ;
42
+ report here;
43
43
  }
44
44
  }
45
45
 
46
46
  walker toggle_todo {
47
47
  can toggle with Todo entry {
48
48
  here.done = not here.done;
49
- report here ;
49
+ report here;
50
50
  }
51
51
  }
52
52
 
@@ -64,7 +64,45 @@ cl {
64
64
  }
65
65
 
66
66
  if isLoggedIn {
67
- return <nav
67
+ return
68
+ <nav
69
+ style={{
70
+ "padding": "12px 24px",
71
+ "background": "#3b82f6",
72
+ "color": "#ffffff",
73
+ "display": "flex",
74
+ "justifyContent": "space-between"
75
+ }}
76
+ >
77
+ <div style={{"fontWeight": "600"}}>
78
+ Todo App
79
+ </div>
80
+ <div style={{"display": "flex", "gap": "16px"}}>
81
+ <Link
82
+ to="/todos"
83
+ style={{"color": "#ffffff", "textDecoration": "none"}}
84
+ >
85
+ Todos
86
+ </Link>
87
+ <button
88
+ onClick={handleLogout}
89
+ style={{
90
+ "background": "none",
91
+ "color": "#ffffff",
92
+ "border": "1px solid #ffffff",
93
+ "padding": "2px 10px",
94
+ "borderRadius": "4px",
95
+ "cursor": "pointer"
96
+ }}
97
+ >
98
+ Logout
99
+ </button>
100
+ </div>
101
+ </nav>;
102
+ }
103
+
104
+ return
105
+ <nav
68
106
  style={{
69
107
  "padding": "12px 24px",
70
108
  "background": "#3b82f6",
@@ -73,75 +111,32 @@ cl {
73
111
  "justifyContent": "space-between"
74
112
  }}
75
113
  >
76
- <div
77
- style={{"fontWeight": "600"}}
78
- >
114
+ <div style={{"fontWeight": "600"}}>
79
115
  Todo App
80
116
  </div>
81
- <div
82
- style={{"display": "flex", "gap": "16px"}}
83
- >
117
+ <div style={{"display": "flex", "gap": "16px"}}>
84
118
  <Link
85
- to="/todos"
119
+ to="/login"
86
120
  style={{"color": "#ffffff", "textDecoration": "none"}}
87
121
  >
88
- Todos
122
+ Login
89
123
  </Link>
90
- <button
91
- onClick={handleLogout}
92
- style={{
93
- "background": "none",
94
- "color": "#ffffff",
95
- "border": "1px solid #ffffff",
96
- "padding": "2px 10px",
97
- "borderRadius": "4px",
98
- "cursor": "pointer"
99
- }}
124
+ <Link
125
+ to="/signup"
126
+ style={{"color": "#ffffff", "textDecoration": "none"}}
100
127
  >
101
- Logout
102
- </button>
128
+ Sign Up
129
+ </Link>
103
130
  </div>
104
131
  </nav>;
105
- }
106
-
107
- return <nav
108
- style={{
109
- "padding": "12px 24px",
110
- "background": "#3b82f6",
111
- "color": "#ffffff",
112
- "display": "flex",
113
- "justifyContent": "space-between"
114
- }}
115
- >
116
- <div
117
- style={{"fontWeight": "600"}}
118
- >
119
- Todo App
120
- </div>
121
- <div
122
- style={{"display": "flex", "gap": "16px"}}
123
- >
124
- <Link
125
- to="/login"
126
- style={{"color": "#ffffff", "textDecoration": "none"}}
127
- >
128
- Login
129
- </Link>
130
- <Link
131
- to="/signup"
132
- style={{"color": "#ffffff", "textDecoration": "none"}}
133
- >
134
- Sign Up
135
- </Link>
136
- </div>
137
- </nav>;
138
132
  }
139
133
 
140
134
  # Login Page
141
135
  def LoginPage -> any {
142
- has username: str = "";
143
- has password: str = "";
144
- has error: str = "";
136
+ has username: str = "",
137
+ password: str = "",
138
+ error: str = "";
139
+
145
140
  navigate = useNavigate();
146
141
 
147
142
  async def handleLogin(e: any) -> None {
@@ -176,98 +171,96 @@ cl {
176
171
  </div>;
177
172
  }
178
173
 
179
- return <div
180
- style={{
181
- "minHeight": "calc(100vh - 48px)",
182
- "display": "flex",
183
- "alignItems": "center",
184
- "justifyContent": "center",
185
- "background": "#f5f5f5"
186
- }}
187
- >
174
+ return
188
175
  <div
189
176
  style={{
190
- "background": "#ffffff",
191
- "padding": "30px",
192
- "borderRadius": "8px",
193
- "width": "280px",
194
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
177
+ "minHeight": "calc(100vh - 48px)",
178
+ "display": "flex",
179
+ "alignItems": "center",
180
+ "justifyContent": "center",
181
+ "background": "#f5f5f5"
195
182
  }}
196
183
  >
197
- <h2
198
- style={{"marginBottom": "20px"}}
199
- >
200
- Login
201
- </h2>
202
- <form
203
- onSubmit={handleLogin}
184
+ <div
185
+ style={{
186
+ "background": "#ffffff",
187
+ "padding": "30px",
188
+ "borderRadius": "8px",
189
+ "width": "280px",
190
+ "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
191
+ }}
204
192
  >
205
- <input
206
- type="text"
207
- value={username}
208
- onChange={handleUsernameChange}
209
- placeholder="Username"
210
- style={{
211
- "width": "100%",
212
- "padding": "8px",
213
- "marginBottom": "10px",
214
- "border": "1px solid #ddd",
215
- "borderRadius": "4px",
216
- "boxSizing": "border-box"
217
- }}
218
- />
219
- <input
220
- type="password"
221
- value={password}
222
- onChange={handlePasswordChange}
223
- placeholder="Password"
224
- style={{
225
- "width": "100%",
226
- "padding": "8px",
227
- "marginBottom": "10px",
228
- "border": "1px solid #ddd",
229
- "borderRadius": "4px",
230
- "boxSizing": "border-box"
231
- }}
232
- />
233
- {errorDisplay}
234
- <button
235
- type="submit"
193
+ <h2 style={{"marginBottom": "20px"}}>
194
+ Login
195
+ </h2>
196
+ <form onSubmit={handleLogin}>
197
+ <input
198
+ type="text"
199
+ value={username}
200
+ onChange={handleUsernameChange}
201
+ placeholder="Username"
202
+ style={{
203
+ "width": "100%",
204
+ "padding": "8px",
205
+ "marginBottom": "10px",
206
+ "border": "1px solid #ddd",
207
+ "borderRadius": "4px",
208
+ "boxSizing": "border-box"
209
+ }}
210
+ />
211
+ <input
212
+ type="password"
213
+ value={password}
214
+ onChange={handlePasswordChange}
215
+ placeholder="Password"
216
+ style={{
217
+ "width": "100%",
218
+ "padding": "8px",
219
+ "marginBottom": "10px",
220
+ "border": "1px solid #ddd",
221
+ "borderRadius": "4px",
222
+ "boxSizing": "border-box"
223
+ }}
224
+ />
225
+ {errorDisplay}
226
+ <button
227
+ type="submit"
228
+ style={{
229
+ "width": "100%",
230
+ "padding": "8px",
231
+ "background": "#3b82f6",
232
+ "color": "#ffffff",
233
+ "border": "none",
234
+ "borderRadius": "4px",
235
+ "cursor": "pointer",
236
+ "fontWeight": "600"
237
+ }}
238
+ >
239
+ Login
240
+ </button>
241
+ </form>
242
+ <p
236
243
  style={{
237
- "width": "100%",
238
- "padding": "8px",
239
- "background": "#3b82f6",
240
- "color": "#ffffff",
241
- "border": "none",
242
- "borderRadius": "4px",
243
- "cursor": "pointer",
244
- "fontWeight": "600"
244
+ "textAlign": "center",
245
+ "marginTop": "12px",
246
+ "fontSize": "14px"
245
247
  }}
246
248
  >
247
- Login
248
- </button>
249
- </form>
250
- <p
251
- style={{
252
- "textAlign": "center",
253
- "marginTop": "12px",
254
- "fontSize": "14px"
255
- }}
256
- >
257
- Need an account?
258
- <Link to="/signup">
259
- Sign up
260
- </Link>
261
- </p>
262
- </div>
263
- </div>;
249
+ Need an account?
250
+ <Link to="/signup">
251
+ Sign up
252
+ </Link>
253
+ </p>
254
+ </div>
255
+ </div>;
264
256
  }
265
257
 
266
258
  # Signup Page
267
259
  def SignupPage -> any {
268
- has username: str = "";
269
- has password: str = "";
270
- has error: str = "";
260
+ has username: str = "",
261
+ password: str = "",
262
+ error: str = "";
263
+
271
264
  navigate = useNavigate();
272
265
 
273
266
  async def handleSignup(e: any) -> None {
@@ -302,91 +295,88 @@ cl {
302
295
  </div>;
303
296
  }
304
297
 
305
- return <div
306
- style={{
307
- "minHeight": "calc(100vh - 48px)",
308
- "display": "flex",
309
- "alignItems": "center",
310
- "justifyContent": "center",
311
- "background": "#f5f5f5"
312
- }}
313
- >
298
+ return
314
299
  <div
315
300
  style={{
316
- "background": "#ffffff",
317
- "padding": "30px",
318
- "borderRadius": "8px",
319
- "width": "280px",
320
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
301
+ "minHeight": "calc(100vh - 48px)",
302
+ "display": "flex",
303
+ "alignItems": "center",
304
+ "justifyContent": "center",
305
+ "background": "#f5f5f5"
321
306
  }}
322
307
  >
323
- <h2
324
- style={{"marginBottom": "20px"}}
325
- >
326
- Sign Up
327
- </h2>
328
- <form
329
- onSubmit={handleSignup}
308
+ <div
309
+ style={{
310
+ "background": "#ffffff",
311
+ "padding": "30px",
312
+ "borderRadius": "8px",
313
+ "width": "280px",
314
+ "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
315
+ }}
330
316
  >
331
- <input
332
- type="text"
333
- value={username}
334
- onChange={handleUsernameChange}
335
- placeholder="Username"
336
- style={{
337
- "width": "100%",
338
- "padding": "8px",
339
- "marginBottom": "10px",
340
- "border": "1px solid #ddd",
341
- "borderRadius": "4px",
342
- "boxSizing": "border-box"
343
- }}
344
- />
345
- <input
346
- type="password"
347
- value={password}
348
- onChange={handlePasswordChange}
349
- placeholder="Password"
350
- style={{
351
- "width": "100%",
352
- "padding": "8px",
353
- "marginBottom": "10px",
354
- "border": "1px solid #ddd",
355
- "borderRadius": "4px",
356
- "boxSizing": "border-box"
357
- }}
358
- />
359
- {errorDisplay}
360
- <button
361
- type="submit"
317
+ <h2 style={{"marginBottom": "20px"}}>
318
+ Sign Up
319
+ </h2>
320
+ <form onSubmit={handleSignup}>
321
+ <input
322
+ type="text"
323
+ value={username}
324
+ onChange={handleUsernameChange}
325
+ placeholder="Username"
326
+ style={{
327
+ "width": "100%",
328
+ "padding": "8px",
329
+ "marginBottom": "10px",
330
+ "border": "1px solid #ddd",
331
+ "borderRadius": "4px",
332
+ "boxSizing": "border-box"
333
+ }}
334
+ />
335
+ <input
336
+ type="password"
337
+ value={password}
338
+ onChange={handlePasswordChange}
339
+ placeholder="Password"
340
+ style={{
341
+ "width": "100%",
342
+ "padding": "8px",
343
+ "marginBottom": "10px",
344
+ "border": "1px solid #ddd",
345
+ "borderRadius": "4px",
346
+ "boxSizing": "border-box"
347
+ }}
348
+ />
349
+ {errorDisplay}
350
+ <button
351
+ type="submit"
352
+ style={{
353
+ "width": "100%",
354
+ "padding": "8px",
355
+ "background": "#3b82f6",
356
+ "color": "#ffffff",
357
+ "border": "none",
358
+ "borderRadius": "4px",
359
+ "cursor": "pointer",
360
+ "fontWeight": "600"
361
+ }}
362
+ >
363
+ Sign Up
364
+ </button>
365
+ </form>
366
+ <p
362
367
  style={{
363
- "width": "100%",
364
- "padding": "8px",
365
- "background": "#3b82f6",
366
- "color": "#ffffff",
367
- "border": "none",
368
- "borderRadius": "4px",
369
- "cursor": "pointer",
370
- "fontWeight": "600"
368
+ "textAlign": "center",
369
+ "marginTop": "12px",
370
+ "fontSize": "14px"
371
371
  }}
372
372
  >
373
- Sign Up
374
- </button>
375
- </form>
376
- <p
377
- style={{
378
- "textAlign": "center",
379
- "marginTop": "12px",
380
- "fontSize": "14px"
381
- }}
382
- >
383
- Have an account?
384
- <Link to="/login">
385
- Login
386
- </Link>
387
- </p>
388
- </div>
389
- </div>;
373
+ Have an account?
374
+ <Link to="/login">
375
+ Login
376
+ </Link>
377
+ </p>
378
+ </div>
379
+ </div>;
390
380
  }
391
381
 
392
382
  # TodoInput Component
@@ -395,39 +385,38 @@ cl {
395
385
  setInput = props.setInput;
396
386
  addTodo = props.addTodo;
397
387
 
398
- return <div
399
- style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}
400
- >
401
- <input
402
- type="text"
403
- value={input}
404
- onChange={lambda e: any -> None{ setInput(e.target.value);} }
405
- onKeyPress={lambda e: any -> None{ if e.key == "Enter" {
406
- addTodo();
407
- }} }
408
- placeholder="What needs to be done?"
409
- style={{
410
- "flex": "1",
411
- "padding": "8px",
412
- "border": "1px solid #ddd",
413
- "borderRadius": "4px"
414
- }}
415
- />
416
- <button
417
- onClick={addTodo}
418
- style={{
419
- "padding": "8px 16px",
420
- "background": "#3b82f6",
421
- "color": "#ffffff",
422
- "border": "none",
423
- "borderRadius": "4px",
424
- "cursor": "pointer",
425
- "fontWeight": "600"
426
- }}
427
- >
428
- Add
429
- </button>
430
- </div>;
388
+ return
389
+ <div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
390
+ <input
391
+ type="text"
392
+ value={input}
393
+ onChange={lambda e: any -> None { setInput(e.target.value);}}
394
+ onKeyPress={lambda e: any -> None { if e.key == "Enter" {
395
+ addTodo();
396
+ }}}
397
+ placeholder="What needs to be done?"
398
+ style={{
399
+ "flex": "1",
400
+ "padding": "8px",
401
+ "border": "1px solid #ddd",
402
+ "borderRadius": "4px"
403
+ }}
404
+ />
405
+ <button
406
+ onClick={addTodo}
407
+ style={{
408
+ "padding": "8px 16px",
409
+ "background": "#3b82f6",
410
+ "color": "#ffffff",
411
+ "border": "none",
412
+ "borderRadius": "4px",
413
+ "cursor": "pointer",
414
+ "fontWeight": "600"
415
+ }}
416
+ >
417
+ Add
418
+ </button>
419
+ </div>;
431
420
  }
432
421
 
433
422
  # TodoFilters Component
@@ -435,52 +424,53 @@ cl {
435
424
  filter = props.filter;
436
425
  setFilter = props.setFilter;
437
426
 
438
- return <div
439
- style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}
440
- >
441
- <button
442
- onClick={lambda -> None{ setFilter("all");} }
443
- style={{
444
- "padding": "6px 12px",
445
- "background": ("#3b82f6" if filter == "all" else "#e5e7eb"),
446
- "color": ("#ffffff" if filter == "all" else "#000000"),
447
- "border": "none",
448
- "borderRadius": "4px",
449
- "cursor": "pointer",
450
- "fontSize": "14px"
451
- }}
452
- >
453
- All
454
- </button>
455
- <button
456
- onClick={lambda -> None{ setFilter("active");} }
457
- style={{
458
- "padding": "6px 12px",
459
- "background": ("#3b82f6" if filter == "active" else "#e5e7eb"),
460
- "color": ("#ffffff" if filter == "active" else "#000000"),
461
- "border": "none",
462
- "borderRadius": "4px",
463
- "cursor": "pointer",
464
- "fontSize": "14px"
465
- }}
466
- >
467
- Active
468
- </button>
469
- <button
470
- onClick={lambda -> None{ setFilter("completed");} }
471
- style={{
472
- "padding": "6px 12px",
473
- "background": ("#3b82f6" if filter == "completed" else "#e5e7eb"),
474
- "color": ("#ffffff" if filter == "completed" else "#000000"),
475
- "border": "none",
476
- "borderRadius": "4px",
477
- "cursor": "pointer",
478
- "fontSize": "14px"
479
- }}
480
- >
481
- Completed
482
- </button>
483
- </div>;
427
+ return
428
+ <div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
429
+ <button
430
+ onClick={lambda -> None { setFilter("all");}}
431
+ style={{
432
+ "padding": "6px 12px",
433
+ "background": ("#3b82f6" if filter == "all" else "#e5e7eb"),
434
+ "color": ("#ffffff" if filter == "all" else "#000000"),
435
+ "border": "none",
436
+ "borderRadius": "4px",
437
+ "cursor": "pointer",
438
+ "fontSize": "14px"
439
+ }}
440
+ >
441
+ All
442
+ </button>
443
+ <button
444
+ onClick={lambda -> None { setFilter("active");}}
445
+ style={{
446
+ "padding": "6px 12px",
447
+ "background": ("#3b82f6" if filter == "active" else "#e5e7eb"),
448
+ "color": ("#ffffff" if filter == "active" else "#000000"),
449
+ "border": "none",
450
+ "borderRadius": "4px",
451
+ "cursor": "pointer",
452
+ "fontSize": "14px"
453
+ }}
454
+ >
455
+ Active
456
+ </button>
457
+ <button
458
+ onClick={lambda -> None { setFilter("completed");}}
459
+ style={{
460
+ "padding": "6px 12px",
461
+ "background": (
462
+ "#3b82f6" if filter == "completed" else "#e5e7eb"
463
+ ),
464
+ "color": ("#ffffff" if filter == "completed" else "#000000"),
465
+ "border": "none",
466
+ "borderRadius": "4px",
467
+ "cursor": "pointer",
468
+ "fontSize": "14px"
469
+ }}
470
+ >
471
+ Completed
472
+ </button>
473
+ </div>;
484
474
  }
485
475
 
486
476
  # TodoItem Component
@@ -489,46 +479,47 @@ cl {
489
479
  toggleTodo = props.toggleTodo;
490
480
  deleteTodo = props.deleteTodo;
491
481
 
492
- return <div
493
- key={todo._jac_id}
494
- style={{
495
- "display": "flex",
496
- "alignItems": "center",
497
- "gap": "10px",
498
- "padding": "10px",
499
- "borderBottom": "1px solid #e5e7eb"
500
- }}
501
- >
502
- <input
503
- type="checkbox"
504
- checked={todo.done}
505
- onChange={lambda -> None{ toggleTodo(todo._jac_id);} }
506
- style={{"cursor": "pointer"}}
507
- />
508
- <span
509
- style={{
510
- "flex": "1",
511
- "textDecoration": ("line-through" if todo.done else "none"),
512
- "color": ("#999" if todo.done else "#000")
513
- }}
514
- >
515
- {todo.text}
516
- </span>
517
- <button
518
- onClick={lambda -> None{ deleteTodo(todo._jac_id);} }
482
+ return
483
+ <div
484
+ key={todo._jac_id}
519
485
  style={{
520
- "padding": "4px 8px",
521
- "background": "#ef4444",
522
- "color": "#ffffff",
523
- "border": "none",
524
- "borderRadius": "4px",
525
- "cursor": "pointer",
526
- "fontSize": "12px"
486
+ "display": "flex",
487
+ "alignItems": "center",
488
+ "gap": "10px",
489
+ "padding": "10px",
490
+ "borderBottom": "1px solid #e5e7eb"
527
491
  }}
528
492
  >
529
- Delete
530
- </button>
531
- </div>;
493
+ <input
494
+ type="checkbox"
495
+ checked={todo.done}
496
+ onChange={lambda -> None { toggleTodo(todo._jac_id);}}
497
+ style={{"cursor": "pointer"}}
498
+ />
499
+ <span
500
+ style={{
501
+ "flex": "1",
502
+ "textDecoration": ("line-through" if todo.done else "none"),
503
+ "color": ("#999" if todo.done else "#000")
504
+ }}
505
+ >
506
+ {todo.text}
507
+ </span>
508
+ <button
509
+ onClick={lambda -> None { deleteTodo(todo._jac_id);}}
510
+ style={{
511
+ "padding": "4px 8px",
512
+ "background": "#ef4444",
513
+ "color": "#ffffff",
514
+ "border": "none",
515
+ "borderRadius": "4px",
516
+ "cursor": "pointer",
517
+ "fontSize": "12px"
518
+ }}
519
+ >
520
+ Delete
521
+ </button>
522
+ </div>;
532
523
  }
533
524
 
534
525
  # TodoList Component
@@ -538,30 +529,34 @@ cl {
538
529
  deleteTodo = props.deleteTodo;
539
530
 
540
531
  if filteredTodos.length == 0 {
541
- return <div
542
- style={{"padding": "20px", "textAlign": "center", "color": "#999"}}
543
- >
544
- No todos yet. Add one above!
545
- </div>;
532
+ return
533
+ <div
534
+ style={{"padding": "20px", "textAlign": "center", "color": "#999"}}
535
+ >
536
+ No todos yet. Add one above!
537
+ </div>;
546
538
  }
547
539
 
548
- return <div>
549
- {filteredTodos.map(
550
- lambda todo: any -> any{ return <TodoItem
551
- key={todo._jac_id}
552
- todo={todo}
553
- toggleTodo={toggleTodo}
554
- deleteTodo={deleteTodo}
555
- />; }
556
- )}
557
- </div>;
540
+ return
541
+ <div>
542
+ {filteredTodos.map(
543
+ lambda todo: any -> any { return
544
+ <TodoItem
545
+ key={todo._jac_id}
546
+ todo={todo}
547
+ toggleTodo={toggleTodo}
548
+ deleteTodo={deleteTodo}
549
+ />; }
550
+ )}
551
+ </div>;
558
552
  }
559
553
 
560
554
  # Todos Page (Protected)
561
555
  def TodosPage -> any {
562
556
  # Check if user is logged in, redirect if not
563
557
  if not jacIsLoggedIn() {
564
- return <Navigate to="/login" />;
558
+ return
559
+ <Navigate to="/login" />;
565
560
  }
566
561
 
567
562
  [todos, setTodos] = useState([]);
@@ -570,10 +565,10 @@ cl {
570
565
 
571
566
  # Load todos on mount
572
567
  useEffect(
573
- lambda -> None{ async def loadTodos -> None {
568
+ lambda -> None { async def loadTodos -> None {
574
569
  result = root spawn read_todos();
575
- setTodos(result.reports if result.reports else []);
576
- } loadTodos();} ,
570
+ setTodos(result.reports or []);
571
+ } loadTodos();},
577
572
  []
578
573
  );
579
574
 
@@ -592,7 +587,7 @@ cl {
592
587
  id spawn toggle_todo();
593
588
  setTodos(
594
589
  todos.map(
595
- lambda todo: any -> any{ if todo._jac_id == id {
590
+ lambda todo: any -> any { if todo._jac_id == id {
596
591
  return {
597
592
  "_jac_id": todo._jac_id,
598
593
  "text": todo.text,
@@ -607,7 +602,7 @@ cl {
607
602
  async def deleteTodo(id: any) -> None {
608
603
  #id spawn delete_todo();
609
604
  setTodos(
610
- todos.filter(lambda todo: any -> bool{ return todo._jac_id != id; } )
605
+ todos.filter(lambda todo: any -> bool { return todo._jac_id != id; })
611
606
  );
612
607
  }
613
608
 
@@ -615,102 +610,81 @@ cl {
615
610
  def getFilteredTodos -> list {
616
611
  if filter == "active" {
617
612
  return todos.filter(
618
- lambda todo: any -> bool{ return not todo.done; }
613
+ lambda todo: any -> bool { return not todo.done; }
619
614
  );
620
615
  } elif filter == "completed" {
621
- return todos.filter(lambda todo: any -> bool{ return todo.done; } );
616
+ return todos.filter(lambda todo: any -> bool { return todo.done; });
622
617
  }
623
618
  return todos;
624
619
  }
625
620
 
626
621
  filteredTodos = getFilteredTodos();
627
- activeCount = todos.filter(
628
- lambda todo: any -> bool{ return not todo.done; }
629
- ).length;
630
-
631
- return <div
632
- style={{
633
- "maxWidth": "600px",
634
- "margin": "20px auto",
635
- "padding": "20px",
636
- "background": "#ffffff",
637
- "borderRadius": "8px",
638
- "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
639
- }}
640
- >
641
- <h1
642
- style={{"marginBottom": "20px"}}
622
+ activeCount = todos.filter(lambda todo: any -> bool { return not todo.done; }).length;
623
+
624
+ return
625
+ <div
626
+ style={{
627
+ "maxWidth": "600px",
628
+ "margin": "20px auto",
629
+ "padding": "20px",
630
+ "background": "#ffffff",
631
+ "borderRadius": "8px",
632
+ "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
633
+ }}
643
634
  >
644
- My Todos
645
- </h1>
646
- <TodoInput
647
- input={input}
648
- setInput={setInput}
649
- addTodo={addTodo}
650
- />
651
- <TodoFilters
652
- filter={filter}
653
- setFilter={setFilter}
654
- />
655
- <TodoList
656
- filteredTodos={filteredTodos}
657
- toggleTodo={toggleTodo}
658
- deleteTodo={deleteTodo}
659
- />
660
- {(
661
- <div
662
- style={{
663
- "marginTop": "16px",
664
- "padding": "10px",
665
- "background": "#f9fafb",
666
- "borderRadius": "4px",
667
- "fontSize": "14px",
668
- "color": "#666"
669
- }}
670
- >
671
- {activeCount} {"item" if activeCount == 1 else "items"} left
672
- </div>
673
- )
674
- if todos.length > 0
675
- else None}
676
- </div>;
635
+ <h1 style={{"marginBottom": "20px"}}>
636
+ My Todos
637
+ </h1>
638
+ <TodoInput input={input} setInput={setInput} addTodo={addTodo} />
639
+ <TodoFilters filter={filter} setFilter={setFilter} />
640
+ <TodoList
641
+ filteredTodos={filteredTodos}
642
+ toggleTodo={toggleTodo}
643
+ deleteTodo={deleteTodo}
644
+ />
645
+ {(
646
+ <div
647
+ style={{
648
+ "marginTop": "16px",
649
+ "padding": "10px",
650
+ "background": "#f9fafb",
651
+ "borderRadius": "4px",
652
+ "fontSize": "14px",
653
+ "color": "#666"
654
+ }}
655
+ >
656
+ {activeCount}{"item" if activeCount == 1 else "items"}left
657
+ </div>
658
+ )
659
+ if todos.length > 0
660
+ else None}
661
+ </div>;
677
662
  }
678
663
 
679
664
  # Home/Landing Page - auto-redirect
680
665
  def HomePage -> any {
681
666
  if jacIsLoggedIn() {
682
- return <Navigate to="/todos" />;
667
+ return
668
+ <Navigate to="/todos" />;
683
669
  }
684
- return <Navigate to="/login" />;
670
+ return
671
+ <Navigate to="/login" />;
685
672
  }
686
673
 
687
674
  # Main App with React Router
688
675
  def:pub app -> any {
689
- return <Router>
690
- <div
691
- style={{"fontFamily": "system-ui, sans-serif"}}
692
- >
693
- <Navigation />
694
- <Routes>
695
- <Route
696
- path="/"
697
- element={<HomePage />}
698
- />
699
- <Route
700
- path="/login"
701
- element={<LoginPage />}
702
- />
703
- <Route
704
- path="/signup"
705
- element={<SignupPage />}
706
- />
707
- <Route
708
- path="/todos"
709
- element={<TodosPage />}
710
- />
711
- </Routes>
712
- </div>
713
- </Router>;
676
+ return
677
+ <Router>
678
+ <div style={{"fontFamily": "system-ui, sans-serif"}}>
679
+ <Navigation />
680
+ <Routes>
681
+ <Route path="/" element={<HomePage />} />
682
+ <Route path="/login" element={<LoginPage />} />
683
+ <Route path="/signup" element={<SignupPage />} />
684
+ <Route path="/todos" element={<TodosPage />} />
685
+ </Routes>
686
+ </div>
687
+ </Router>;
714
688
  }
715
689
  }
716
690
  # Add todo input