jac-client 0.2.8__py3-none-any.whl → 0.2.11__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 (119) 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 +542 -0
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +26 -12
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +43 -12
  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 +65 -0
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +675 -0
  22. jac_client/examples/all-in-one/pages/loginPage.jac +114 -119
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +44 -51
  24. jac_client/examples/all-in-one/pages/notFound.jac +15 -21
  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 +92 -0
  28. jac_client/examples/asset-serving/image-asset/main.jac +56 -0
  29. jac_client/examples/asset-serving/import-alias/main.jac +109 -0
  30. jac_client/examples/basic/main.jac +23 -0
  31. jac_client/examples/basic-auth/main.jac +363 -0
  32. jac_client/examples/basic-auth-with-router/main.jac +451 -0
  33. jac_client/examples/basic-full-stack/main.jac +362 -0
  34. jac_client/examples/css-styling/js-styling/main.jac +63 -0
  35. jac_client/examples/css-styling/material-ui/main.jac +122 -0
  36. jac_client/examples/css-styling/pure-css/main.jac +55 -0
  37. jac_client/examples/css-styling/sass-example/main.jac +55 -0
  38. jac_client/examples/css-styling/styled-components/main.jac +62 -0
  39. jac_client/examples/css-styling/tailwind-example/main.jac +74 -0
  40. jac_client/examples/full-stack-with-auth/main.jac +696 -0
  41. jac_client/examples/little-x/main.jac +681 -0
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +26 -0
  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/{src/app.jac → 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 +35 -0
  52. jac_client/examples/with-router/main.jac +286 -0
  53. jac_client/plugin/cli.jac +491 -411
  54. jac_client/plugin/client.jac +25 -0
  55. jac_client/plugin/client_runtime.cl.jac +10 -4
  56. jac_client/plugin/impl/client.impl.jac +96 -55
  57. jac_client/plugin/impl/client_runtime.impl.jac +155 -1
  58. jac_client/plugin/plugin_config.jac +211 -29
  59. jac_client/plugin/src/__init__.jac +0 -2
  60. jac_client/plugin/src/compiler.jac +0 -1
  61. jac_client/plugin/src/config_loader.jac +1 -0
  62. jac_client/plugin/src/desktop_config.jac +31 -0
  63. jac_client/plugin/src/impl/compiler.impl.jac +49 -17
  64. jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
  65. jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
  66. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  67. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  68. jac_client/plugin/src/impl/vite_bundler.impl.jac +191 -64
  69. jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
  70. jac_client/plugin/src/targets/desktop_target.jac +37 -0
  71. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
  72. jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
  73. jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
  74. jac_client/plugin/src/targets/register.jac +21 -0
  75. jac_client/plugin/src/targets/registry.jac +87 -0
  76. jac_client/plugin/src/targets/web_target.jac +35 -0
  77. jac_client/plugin/src/vite_bundler.jac +6 -0
  78. jac_client/plugin/utils/__init__.jac +3 -0
  79. jac_client/plugin/utils/bun_installer.jac +16 -0
  80. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  81. jac_client/templates/client.jacpack +72 -0
  82. jac_client/templates/fullstack.jacpack +61 -0
  83. jac_client/tests/conftest.py +103 -47
  84. jac_client/tests/fixtures/spawn_test/app.jac +49 -52
  85. jac_client/tests/fixtures/with-ts/app.jac +27 -27
  86. jac_client/tests/test_cli.py +182 -71
  87. jac_client/tests/test_e2e.py +232 -0
  88. jac_client/tests/test_helpers.py +58 -0
  89. jac_client/tests/test_it.py +91 -135
  90. jac_client/tests/test_it_desktop.py +891 -0
  91. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/METADATA +6 -6
  92. jac_client-0.2.11.dist-info/RECORD +113 -0
  93. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
  94. jac_client/examples/all-in-one/app.jac +0 -573
  95. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +0 -70
  96. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +0 -552
  97. jac_client/examples/asset-serving/css-with-image/src/app.jac +0 -88
  98. jac_client/examples/asset-serving/image-asset/src/app.jac +0 -55
  99. jac_client/examples/asset-serving/import-alias/src/app.jac +0 -111
  100. jac_client/examples/basic/src/app.jac +0 -21
  101. jac_client/examples/basic-auth/src/app.jac +0 -371
  102. jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
  103. jac_client/examples/basic-full-stack/src/app.jac +0 -359
  104. jac_client/examples/css-styling/js-styling/src/app.jac +0 -84
  105. jac_client/examples/css-styling/material-ui/src/app.jac +0 -122
  106. jac_client/examples/css-styling/pure-css/src/app.jac +0 -64
  107. jac_client/examples/css-styling/sass-example/src/app.jac +0 -64
  108. jac_client/examples/css-styling/styled-components/src/app.jac +0 -71
  109. jac_client/examples/css-styling/tailwind-example/src/app.jac +0 -63
  110. jac_client/examples/full-stack-with-auth/src/app.jac +0 -722
  111. jac_client/examples/little-x/src/app.jac +0 -719
  112. jac_client/examples/nested-folders/nested-advance/src/app.jac +0 -35
  113. jac_client/examples/ts-support/src/app.jac +0 -35
  114. jac_client/examples/with-router/src/app.jac +0 -323
  115. jac_client/plugin/src/babel_processor.jac +0 -18
  116. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -89
  117. jac_client-0.2.8.dist-info/RECORD +0 -97
  118. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
  119. {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,681 @@
1
+ import datetime;
2
+ cl import from lodash { * as _ }
3
+ cl import from antd { Button, Input, Card, Typography, Space }
4
+ cl import from pluralize { default as pluralize }
5
+ cl import from 'react-animated-components' { Rotate }
6
+
7
+ node Profile {
8
+ has username: str = "";
9
+
10
+ can update with update_profile entry {
11
+ self.username = visitor.new_username;
12
+ report self;
13
+ }
14
+
15
+ can get with get_profile entry {
16
+ follwers = [
17
+ {"id": jid(i), "username": i.username} for i in [self-->(`?Profile)]
18
+ ];
19
+ report {"user": self, "followers": follwers};
20
+ }
21
+
22
+ can follow with follow_request entry {
23
+ current_profile = [root-->(`?Profile)];
24
+ current_profile[0] +>: Follow() :+> self;
25
+ report self;
26
+ }
27
+
28
+ can un_follow with un_follow_request entry {
29
+ current_profile = [root-->(`?Profile)];
30
+ follow_edge = [edge current_profile[0]->:Follow:->self];
31
+ del follow_edge[0];
32
+ report self;
33
+ }
34
+ }
35
+
36
+ obj TweetInfo {
37
+ has username: str,
38
+ id: str,
39
+ content: str,
40
+ embedding: list,
41
+ likes: list,
42
+ comments: list;
43
+ }
44
+
45
+ node Tweet {
46
+ has content: str,
47
+ embedding: list,
48
+ created_at: str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S");
49
+
50
+ can update with update_tweet exit {
51
+ self.content = visitor.updated_content;
52
+ report self;
53
+ }
54
+
55
+ can delete with remove_tweet exit {
56
+ del self;
57
+ disengage;
58
+ }
59
+
60
+ can like_tweet with like_tweet entry {
61
+ current_profile = [root-->(`?Profile)];
62
+ self +>: Like() :+> current_profile[0];
63
+ report self;
64
+ }
65
+
66
+ can remove_like with remove_like entry {
67
+ current_profile = [root-->(`?Profile)];
68
+ like_edge = [edge self->:Like:->current_profile[0]];
69
+ del like_edge[0];
70
+ report self;
71
+ }
72
+
73
+ can comment with comment_tweet entry {
74
+ current_profile = [root-->(`?Profile)];
75
+ comment_node = current_profile[0] +>: Post() :+> Comment(
76
+ content=visitor.content
77
+ );
78
+ grant(comment_node[0], level=ConnectPerm);
79
+ self ++> comment_node[0];
80
+ report comment_node[0];
81
+ }
82
+
83
+ def get_info -> TweetInfo {
84
+ return TweetInfo(
85
+ username=[self<-:Post:<-][0].username,
86
+ id=jid(self),
87
+ content=self.content,
88
+ embedding=self.embedding,
89
+ likes=[i.username for i in [self->:Like:->]],
90
+ comments=[
91
+ {
92
+ "username": [i<--(`?Profile)][0].username,
93
+ "id": jid(i),
94
+ "content": i.content
95
+ } for i in [self-->(`?Comment)]
96
+ ]
97
+ );
98
+ }
99
+
100
+ can get with load_feed entry {
101
+ tweet_info = self.get_info();
102
+ visitor.results.append({"Tweet_Info": tweet_info});
103
+ }
104
+ }
105
+
106
+ node Comment {
107
+ has content: str;
108
+
109
+ can update with update_comment entry {
110
+ self.content = visitor.updated_content;
111
+ report self;
112
+ }
113
+
114
+ can delete with remove_comment entry {
115
+ del self;
116
+ disengage;
117
+ }
118
+ }
119
+
120
+ edge Follow {}
121
+
122
+ edge Like {}
123
+
124
+ edge Post {}
125
+
126
+ walker visit_profile {
127
+ can visit_profile with `root entry {
128
+ visit [-->(`?Profile)] else {
129
+ new_profile = here ++> Profile();
130
+ grant(new_profile[0], level=ConnectPerm);
131
+ visit new_profile;
132
+ }
133
+ }
134
+ }
135
+
136
+ walker update_profile(visit_profile) {
137
+ has new_username: str;
138
+ }
139
+
140
+ walker get_profile(visit_profile) {}
141
+
142
+ walker load_user_profiles {
143
+ can load_profiles with `root entry {
144
+ self.profiles: list = [];
145
+ for each_root in allroots() {
146
+ profile = [each_root-->(`?Profile)][0];
147
+ self.profiles.append({"name": profile.username, "id": jid(profile)});
148
+ }
149
+ }
150
+
151
+ can report_profiles with exit {
152
+ report self.profiles;
153
+ }
154
+ }
155
+
156
+ walker follow_request {}
157
+
158
+ walker un_follow_request {}
159
+
160
+ walker create_tweet(visit_profile) {
161
+ has content: str;
162
+
163
+ can tweet with Profile entry {
164
+ tweet_node = here +>: Post() :+> Tweet(content=self.content, embedding=[]);
165
+ grant(tweet_node[0], level=ConnectPerm);
166
+ report tweet_node;
167
+ }
168
+ }
169
+
170
+ walker update_tweet {
171
+ has updated_content: str;
172
+ }
173
+
174
+ walker remove_tweet {}
175
+
176
+ walker like_tweet {}
177
+
178
+ walker remove_like {}
179
+
180
+ walker comment_tweet {
181
+ has content: str;
182
+ }
183
+
184
+ walker update_comment {
185
+ has updated_content: str;
186
+ }
187
+
188
+ walker remove_comment {}
189
+
190
+ walker load_feed(visit_profile) {
191
+ has search_query: str = "",
192
+ results: list = [];
193
+
194
+ can load with Profile entry {
195
+ visit [-->(`?Tweet)];
196
+ for user_node in [->:Follow:->(`?Profile)] {
197
+ visit [user_node-->(`?Tweet)];
198
+ }
199
+ }
200
+
201
+ can report_feed with exit {
202
+ report self.results;
203
+ }
204
+ }
205
+
206
+ # Client-side UI Components (marked with 'cl' for browser execution)
207
+ # ===================================================================
208
+
209
+ # Manual routing functions removed - now using reactive createRouter
210
+
211
+ # Shared data model for client/server
212
+ cl obj ClientTweet {
213
+ has username: str = "",
214
+ id: str = "",
215
+ content: str = "",
216
+ likes: list = [],
217
+ comments: list = [];
218
+ }
219
+
220
+ cl obj ClientProfile {
221
+ has username: str = "",
222
+ id: str = "";
223
+ }
224
+
225
+ # UI Components - Render a single tweet card
226
+ cl def TweetCard(tweet: ClientTweet) -> any {
227
+ return
228
+ <div
229
+ class="tweet-card"
230
+ style={{
231
+ "border": "1px solid #e1e8ed",
232
+ "padding": "15px",
233
+ "margin": "10px 0",
234
+ "borderRadius": "8px"
235
+ }}
236
+ >
237
+ <div
238
+ class="tweet-header"
239
+ style={{"fontWeight": "bold", "marginBottom": "80px"}}
240
+ >
241
+ @{tweet.username}
242
+ </div>
243
+ <div class="tweet-content" style={{"marginBottom": "12px"}}>
244
+ {tweet.content}
245
+ </div>
246
+ <div class="tweet-actions" style={{"display": "flex", "gap": "15px"}}>
247
+ <button
248
+ onclick={like_tweet_action(tweet.id)}
249
+ style={{"padding": "5px 10px", "cursor": "pointer"}}
250
+ >
251
+ Like ({tweet.likes.length})
252
+ </button>
253
+ <button style={{"padding": "5px 10px"}}>
254
+ Comment ({tweet.comments.length})
255
+ </button>
256
+ </div>
257
+ </div>;
258
+ }
259
+
260
+ # Handle liking a tweet - calls server walker directly
261
+ # The compiler automatically transforms this to __jacCallFunction at compile time!
262
+ cl async def like_tweet_action(
263
+ tweet_id: str
264
+ ) -> any {
265
+ try {
266
+ result = await like_tweet(tweet_id);
267
+ print("Tweet liked:", result);
268
+ # Re-render feed after like
269
+ window.location.reload();
270
+ } except Exception as e {
271
+ print("Error liking tweet:", e);
272
+ }
273
+ }
274
+
275
+ # Render the main feed view
276
+ cl def FeedView(tweets: list) -> any {
277
+ return
278
+ <div
279
+ class="feed-container"
280
+ style={{
281
+ "maxWidth": "600px",
282
+ "margin": "0 auto",
283
+ "fontFamily": "sans-serif"
284
+ }}
285
+ >
286
+ <div
287
+ class="feed-header"
288
+ style={{"padding": "20px", "borderBottom": "1px solid #e1e8ed"}}
289
+ >
290
+ <h1 style={{"margin": "0"}}>
291
+ LittleX Feed
292
+ </h1>
293
+ </div>
294
+ <div class="feed-content">
295
+ {[TweetCard(tweet) for tweet in tweets]}
296
+ </div>
297
+ </div>;
298
+ }
299
+
300
+ # Render login form
301
+ cl def LoginForm -> any {
302
+ suggestions = ['good luck', 'have fun', 'enjoy the ride'];
303
+ randomSuggestion = _.sample(suggestions);
304
+ result = "Good luck with your journey!";
305
+ return
306
+ <Card
307
+ title="Login to LittleX"
308
+ style={{"maxWidth": "400px", "margin": "50px auto"}}
309
+ >
310
+ <Card.Meta title={randomSuggestion} description={result} />
311
+ <form onSubmit={handle_login}>
312
+ <div style={{"marginBottom": "15px"}}>
313
+ <label style={{"display": "block", "marginBottom": "5px"}}>
314
+ Username:
315
+ </label>
316
+ <input
317
+ type="text"
318
+ id="username"
319
+ style={{
320
+ "width": "100%",
321
+ "padding": "8px",
322
+ "boxSizing": "border-box"
323
+ }}
324
+ />
325
+ </div>
326
+ <div style={{"marginBottom": "15px"}}>
327
+ <label style={{"display": "block", "marginBottom": "5px"}}>
328
+ Password:
329
+ </label>
330
+ <input
331
+ type="password"
332
+ id="password"
333
+ style={{
334
+ "width": "100%",
335
+ "padding": "8px",
336
+ "boxSizing": "border-box"
337
+ }}
338
+ />
339
+ </div>
340
+ <Button
341
+ htmlType="submit"
342
+ style={{
343
+ "width": "100%",
344
+ "padding": "10px",
345
+ "backgroundColor": "#1da1f2",
346
+ "color": "white",
347
+ "border": "none",
348
+ "borderRadius": "4px",
349
+ "cursor": "pointer"
350
+ }}
351
+ >
352
+ Login
353
+ </Button>
354
+ <Button color="default" variant="dashed">
355
+ Dashed
356
+ </Button>
357
+ <Button color="default" variant="filled">
358
+ Filled
359
+ </Button>
360
+ <Button color="default" variant="text">
361
+ Text
362
+ </Button>
363
+ <Button color="default" variant="link">
364
+ Link
365
+ </Button>
366
+ </form>
367
+ <div style={{"marginTop": "15px", "textAlign": "center"}}>
368
+ <Link href="/signup">
369
+ Don't have an account? Sign up
370
+ </Link>
371
+ </div>
372
+ </Card>;
373
+ }
374
+
375
+ # Handle login form submission
376
+ cl async def handle_login(event: any) -> None {
377
+ event.preventDefault();
378
+ username = document.getElementById("username").value;
379
+ password = document.getElementById("password").value;
380
+ success = await jacLogin(username, password);
381
+ if success {
382
+ navigate("/home");
383
+ } else {
384
+ alert("Login failed. Please try again.");
385
+ }
386
+ }
387
+
388
+ # Render signup form
389
+ cl def SignupForm -> any {
390
+ return
391
+ <div
392
+ class="signup-container"
393
+ style={{
394
+ "maxWidth": "400px",
395
+ "margin": "50px auto",
396
+ "padding": "20px",
397
+ "border": "1px solid #e1e8ed",
398
+ "borderRadius": "8px",
399
+ "fontFamily": "sans-serif"
400
+ }}
401
+ >
402
+ <Typography.Title level={2} style={{"marginTop": "0"}}>
403
+ Sign Up for LittleX
404
+ </Typography.Title>
405
+ <form onSubmit={handle_signup}>
406
+ <div style={{"marginBottom": "15px"}}>
407
+ <label style={{"display": "block", "marginBottom": "5px"}}>
408
+ Username:
409
+ </label>
410
+ <input
411
+ type="text"
412
+ id="signup-username"
413
+ required
414
+ style={{
415
+ "width": "100%",
416
+ "padding": "8px",
417
+ "boxSizing": "border-box"
418
+ }}
419
+ />
420
+ </div>
421
+ <div style={{"marginBottom": "15px"}}>
422
+ <label style={{"display": "block", "marginBottom": "5px"}}>
423
+ Password:
424
+ </label>
425
+ <input
426
+ type="password"
427
+ id="signup-password"
428
+ required
429
+ style={{
430
+ "width": "100%",
431
+ "padding": "8px",
432
+ "boxSizing": "border-box"
433
+ }}
434
+ />
435
+ </div>
436
+ <div style={{"marginBottom": "15px"}}>
437
+ <label style={{"display": "block", "marginBottom": "5px"}}>
438
+ Confirm Password:
439
+ </label>
440
+ <input
441
+ type="password"
442
+ id="signup-password-confirm"
443
+ required
444
+ style={{
445
+ "width": "100%",
446
+ "padding": "8px",
447
+ "boxSizing": "border-box"
448
+ }}
449
+ />
450
+ </div>
451
+ <button
452
+ type="submit"
453
+ style={{
454
+ "width": "100%",
455
+ "padding": "10px",
456
+ "backgroundColor": "#1da1f2",
457
+ "color": "white",
458
+ "border": "none",
459
+ "borderRadius": "4px",
460
+ "cursor": "pointer"
461
+ }}
462
+ >
463
+ Sign Up
464
+ </button>
465
+ </form>
466
+ <div style={{"marginTop": "15px", "textAlign": "center"}}>
467
+ <Link href="/login">
468
+ Already have an account? Login
469
+ </Link>
470
+ </div>
471
+ </div>;
472
+ }
473
+
474
+ # Navigation helper functions removed - using Link component and navigate() directly
475
+
476
+ # Handle signup form submission
477
+ cl async def handle_signup(event: any) -> None {
478
+ event.preventDefault();
479
+ username = document.getElementById("signup-username").value;
480
+ password = document.getElementById("signup-password").value;
481
+ password_confirm = document.getElementById("signup-password-confirm").value;
482
+ # Client-side validation
483
+ if password != password_confirm {
484
+ alert("Passwords do not match!");
485
+ return;
486
+ }
487
+ if username.length < 3 {
488
+ alert("Username must be at least 3 characters long.");
489
+ return;
490
+ }
491
+ if password.length < 6 {
492
+ alert("Password must be at least 6 characters long.");
493
+ return;
494
+ }
495
+ # Use runtime auth function - no need to know about /user/register endpoint!
496
+ result = await jacSignup(username, password);
497
+ if result["success"] if "success" in result else False {
498
+ alert("Account created successfully! Welcome to LittleX!");
499
+ navigate("/home");
500
+ } else {
501
+ alert(result["error"] if "error" in result else "Signup failed");
502
+ }
503
+ }
504
+
505
+ # Handle logout
506
+ cl def logout_action -> None {
507
+ jacLogout();
508
+ navigate("/login");
509
+ }
510
+
511
+ # Main App component with declarative router
512
+ cl def:pub App -> any {
513
+ # Create routes array manually (workaround for JS compiler bug with named args)
514
+ login_route = {
515
+ "path": "/login",
516
+ "component": lambda -> any { return LoginForm(); },
517
+ "guard": None
518
+ };
519
+ signup_route = {
520
+ "path": "/signup",
521
+ "component": lambda -> any { return SignupForm(); },
522
+ "guard": None
523
+ };
524
+ home_route = {
525
+ "path": "/home",
526
+ "component": lambda -> any { return HomeView(); },
527
+ "guard": jacIsLoggedIn
528
+ };
529
+ profile_route = {
530
+ "path": "/profile",
531
+ "component": lambda -> any { return ProfileView(); },
532
+ "guard": jacIsLoggedIn
533
+ };
534
+
535
+ routes = [login_route, signup_route, home_route, profile_route];
536
+ router = initRouter(routes, "/login");
537
+
538
+ # Get current path for navbar
539
+ currentPath = router.path();
540
+
541
+ return
542
+ <div class="app-container">
543
+ {build_nav_bar(currentPath)}
544
+ <Rotate>
545
+ <span>
546
+ 😂
547
+ </span>
548
+ </Rotate>
549
+ {router.render()}
550
+ </div>;
551
+ }
552
+
553
+ # Helper to build navigation bar
554
+ cl def build_nav_bar(route: str) -> any {
555
+ if not jacIsLoggedIn() or route == "/login" or route == "/signup" {
556
+ return None;
557
+ }
558
+ return
559
+ <nav
560
+ style={{
561
+ "backgroundColor": "#1da1f2",
562
+ "padding": "15px",
563
+ "marginBottom": "20px"
564
+ }}
565
+ >
566
+ <div
567
+ style={{
568
+ "maxWidth": "600px",
569
+ "margin": "0 auto",
570
+ "display": "flex",
571
+ "gap": "20px",
572
+ "alignItems": "center"
573
+ }}
574
+ >
575
+ <Link href="/home">
576
+ <span
577
+ style={{
578
+ "color": "white",
579
+ "textDecoration": "none",
580
+ "fontWeight": "bold"
581
+ }}
582
+ >
583
+ Home
584
+ </span>
585
+ </Link>
586
+ <Link href="/profile">
587
+ <span
588
+ style={{
589
+ "color": "white",
590
+ "textDecoration": "none",
591
+ "fontWeight": "bold"
592
+ }}
593
+ >
594
+ Profile
595
+ </span>
596
+ </Link>
597
+ <button
598
+ onClick={logout_action}
599
+ style={{
600
+ "marginLeft": "auto",
601
+ "padding": "5px 15px",
602
+ "backgroundColor": "white",
603
+ "color": "#1da1f2",
604
+ "border": "none",
605
+ "borderRadius": "4px",
606
+ "cursor": "pointer",
607
+ "fontWeight": "bold"
608
+ }}
609
+ >
610
+ Logout
611
+ </button>
612
+ </div>
613
+ </nav>;
614
+ }
615
+
616
+ # Home view - simplified for testing reactive routing
617
+ cl def HomeView -> any {
618
+ if not jacIsLoggedIn() {
619
+ navigate("/login");
620
+ return
621
+ <div></div>;
622
+ }
623
+
624
+ return
625
+ <div
626
+ style={{
627
+ "textAlign": "center",
628
+ "padding": "50px",
629
+ "fontFamily": "sans-serif"
630
+ }}
631
+ >
632
+ <h1>
633
+ Home Feed
634
+ </h1>
635
+ <p>
636
+ Welcome to LittleX! This is the home page.
637
+ </p>
638
+ <p>
639
+ The reactive router is working!
640
+ </p>
641
+ </div>;
642
+ }
643
+
644
+ # Profile view
645
+ cl def ProfileView -> any {
646
+ if not jacIsLoggedIn() {
647
+ navigate("/login");
648
+ return
649
+ <div></div>;
650
+ }
651
+ return
652
+ <div
653
+ class="profile-container"
654
+ style={{
655
+ "maxWidth": "600px",
656
+ "margin": "20px auto",
657
+ "padding": "20px",
658
+ "fontFamily": "sans-serif"
659
+ }}
660
+ >
661
+ <h1>
662
+ Profile
663
+ </h1>
664
+ <div
665
+ style={{
666
+ "padding": "15px",
667
+ "border": "1px solid #e1e8ed",
668
+ "borderRadius": "8px"
669
+ }}
670
+ >
671
+ <p>
672
+ Profile information will be displayed here.
673
+ </p>
674
+ </div>
675
+ </div>;
676
+ }
677
+
678
+ # Main SPA entry point - simplified with reactive routing
679
+ cl def:pub jac_app -> any {
680
+ return App();
681
+ }
@@ -1,16 +1,17 @@
1
1
  cl def:pub SubmitButton -> any {
2
- return <button
3
- type="submit"
4
- style={{
5
- "width": "100%",
6
- "padding": "10px",
7
- "backgroundColor": "#1da1f2",
8
- "color": "white",
9
- "border": "none",
10
- "borderRadius": "4px",
11
- "cursor": "pointer"
12
- }}
13
- >
14
- Sign Up
15
- </button>;
2
+ return
3
+ <button
4
+ type="submit"
5
+ style={{
6
+ "width": "100%",
7
+ "padding": "10px",
8
+ "backgroundColor": "#1da1f2",
9
+ "color": "white",
10
+ "border": "none",
11
+ "borderRadius": "4px",
12
+ "cursor": "pointer"
13
+ }}
14
+ >
15
+ Sign Up
16
+ </button>;
16
17
  }