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