jac-client 0.2.4__tar.gz → 0.2.9__tar.gz

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 (110) hide show
  1. {jac_client-0.2.4 → jac_client-0.2.9}/PKG-INFO +18 -9
  2. {jac_client-0.2.4 → jac_client-0.2.9}/README.md +16 -7
  3. jac_client-0.2.9/jac_client/examples/all-in-one/button.jac +7 -0
  4. jac_client-0.2.9/jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
  5. jac_client-0.2.9/jac_client/examples/all-in-one/components/Header.jac +13 -0
  6. jac_client-0.2.9/jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
  7. jac_client-0.2.9/jac_client/examples/all-in-one/components/Summary.jac +53 -0
  8. jac_client-0.2.9/jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
  9. jac_client-0.2.9/jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
  10. jac_client-0.2.9/jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
  11. jac_client-0.2.9/jac_client/examples/all-in-one/components/button.jac +7 -0
  12. jac_client-0.2.9/jac_client/examples/all-in-one/components/navigation.jac +132 -0
  13. jac_client-0.2.9/jac_client/examples/all-in-one/constants/categories.jac +37 -0
  14. jac_client-0.2.9/jac_client/examples/all-in-one/constants/clients.jac +13 -0
  15. jac_client-0.2.9/jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
  16. jac_client-0.2.9/jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
  17. jac_client-0.2.9/jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
  18. jac_client-0.2.9/jac_client/examples/all-in-one/main.jac +573 -0
  19. jac_client-0.2.9/jac_client/examples/all-in-one/pages/BudgetPlanner.jac +133 -0
  20. jac_client-0.2.9/jac_client/examples/all-in-one/pages/FeaturesTest.jac +141 -0
  21. jac_client-0.2.9/jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
  22. jac_client-0.2.9/jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +70 -0
  23. jac_client-0.2.9/jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +563 -0
  24. jac_client-0.2.9/jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
  25. jac_client-0.2.9/jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
  26. jac_client-0.2.9/jac_client/examples/all-in-one/pages/notFound.jac +19 -0
  27. jac_client-0.2.9/jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
  28. jac_client-0.2.9/jac_client/examples/all-in-one/utils/formatters.jac +52 -0
  29. jac_client-0.2.9/jac_client/examples/asset-serving/css-with-image/main.jac +88 -0
  30. jac_client-0.2.9/jac_client/examples/asset-serving/image-asset/main.jac +55 -0
  31. jac_client-0.2.9/jac_client/examples/asset-serving/import-alias/main.jac +111 -0
  32. jac_client-0.2.9/jac_client/examples/basic/main.jac +21 -0
  33. jac_client-0.2.9/jac_client/examples/basic-auth/main.jac +371 -0
  34. jac_client-0.2.9/jac_client/examples/basic-auth-with-router/main.jac +464 -0
  35. jac_client-0.2.9/jac_client/examples/basic-full-stack/main.jac +359 -0
  36. jac_client-0.2.9/jac_client/examples/css-styling/js-styling/main.jac +84 -0
  37. jac_client-0.2.9/jac_client/examples/css-styling/material-ui/main.jac +122 -0
  38. jac_client-0.2.9/jac_client/examples/css-styling/pure-css/main.jac +64 -0
  39. jac_client-0.2.9/jac_client/examples/css-styling/sass-example/main.jac +64 -0
  40. jac_client-0.2.9/jac_client/examples/css-styling/styled-components/main.jac +71 -0
  41. jac_client-0.2.9/jac_client/examples/css-styling/tailwind-example/main.jac +63 -0
  42. jac_client-0.2.9/jac_client/examples/full-stack-with-auth/main.jac +722 -0
  43. jac_client-0.2.9/jac_client/examples/little-x/main.jac +719 -0
  44. jac_client-0.2.9/jac_client/examples/little-x/src/submit-button.jac +16 -0
  45. jac_client-0.2.9/jac_client/examples/nested-folders/nested-advance/main.jac +35 -0
  46. jac_client-0.2.9/jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +11 -0
  47. jac_client-0.2.9/jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +19 -0
  48. jac_client-0.2.9/jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +43 -0
  49. jac_client-0.2.9/jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +25 -0
  50. jac_client-0.2.9/jac_client/examples/nested-folders/nested-basic/main.jac +13 -0
  51. jac_client-0.2.9/jac_client/examples/nested-folders/nested-basic/src/button.jac +7 -0
  52. jac_client-0.2.9/jac_client/examples/nested-folders/nested-basic/src/components/button.jac +7 -0
  53. jac_client-0.2.9/jac_client/examples/ts-support/main.jac +35 -0
  54. jac_client-0.2.9/jac_client/examples/with-router/main.jac +323 -0
  55. jac_client-0.2.9/jac_client/plugin/cli.jac +231 -0
  56. jac_client-0.2.9/jac_client/plugin/client.jac +70 -0
  57. jac_client-0.2.9/jac_client/plugin/client_runtime.cl.jac +46 -0
  58. jac_client-0.2.9/jac_client/plugin/impl/client.impl.jac +234 -0
  59. jac_client-0.2.9/jac_client/plugin/impl/client_runtime.impl.jac +349 -0
  60. jac_client-0.2.9/jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
  61. jac_client-0.2.9/jac_client/plugin/plugin_config.jac +423 -0
  62. jac_client-0.2.9/jac_client/plugin/src/__init__.jac +20 -0
  63. jac_client-0.2.9/jac_client/plugin/src/asset_processor.jac +33 -0
  64. jac_client-0.2.9/jac_client/plugin/src/babel_processor.jac +18 -0
  65. jac_client-0.2.9/jac_client/plugin/src/compiler.jac +67 -0
  66. jac_client-0.2.9/jac_client/plugin/src/config_loader.jac +33 -0
  67. jac_client-0.2.9/jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
  68. jac_client-0.2.9/jac_client/plugin/src/impl/babel_processor.impl.jac +89 -0
  69. jac_client-0.2.9/jac_client/plugin/src/impl/compiler.impl.jac +288 -0
  70. jac_client-0.2.9/jac_client/plugin/src/impl/config_loader.impl.jac +127 -0
  71. jac_client-0.2.9/jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
  72. jac_client-0.2.9/jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
  73. jac_client-0.2.9/jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
  74. jac_client-0.2.9/jac_client/plugin/src/impl/vite_bundler.impl.jac +707 -0
  75. jac_client-0.2.9/jac_client/plugin/src/import_processor.jac +19 -0
  76. jac_client-0.2.9/jac_client/plugin/src/jac_to_js.jac +35 -0
  77. jac_client-0.2.9/jac_client/plugin/src/package_installer.jac +26 -0
  78. jac_client-0.2.9/jac_client/plugin/src/vite_bundler.jac +50 -0
  79. jac_client-0.2.9/jac_client/plugin/utils/__init__.jac +1 -0
  80. jac_client-0.2.9/jac_client/plugin/utils/impl/node_installer.impl.jac +249 -0
  81. jac_client-0.2.9/jac_client/plugin/utils/node_installer.jac +41 -0
  82. jac_client-0.2.9/jac_client/plugin/vite_client_bundle.jac +31 -0
  83. jac_client-0.2.9/jac_client/templates/client.jacpack +72 -0
  84. jac_client-0.2.9/jac_client/templates/fullstack.jacpack +61 -0
  85. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client/tests/conftest.py +58 -15
  86. jac_client-0.2.9/jac_client/tests/fixtures/basic-app/app.jac +23 -0
  87. jac_client-0.2.9/jac_client/tests/fixtures/cl_file/app.cl.jac +48 -0
  88. jac_client-0.2.9/jac_client/tests/fixtures/cl_file/app.jac +15 -0
  89. jac_client-0.2.9/jac_client/tests/fixtures/client_app_with_antd/app.jac +34 -0
  90. jac_client-0.2.9/jac_client/tests/fixtures/js_import/app.jac +34 -0
  91. jac_client-0.2.9/jac_client/tests/fixtures/relative_import/app.jac +11 -0
  92. jac_client-0.2.9/jac_client/tests/fixtures/relative_import/button.jac +7 -0
  93. jac_client-0.2.9/jac_client/tests/fixtures/spawn_test/app.jac +126 -0
  94. jac_client-0.2.9/jac_client/tests/fixtures/test_fragments_spread/app.jac +67 -0
  95. jac_client-0.2.9/jac_client/tests/fixtures/with-ts/app.jac +35 -0
  96. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client/tests/test_cli.py +282 -112
  97. jac_client-0.2.9/jac_client/tests/test_e2e.py +232 -0
  98. jac_client-0.2.9/jac_client/tests/test_helpers.py +65 -0
  99. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client/tests/test_it.py +325 -154
  100. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client.egg-info/PKG-INFO +18 -9
  101. jac_client-0.2.9/jac_client.egg-info/SOURCES.txt +107 -0
  102. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client.egg-info/requires.txt +1 -1
  103. {jac_client-0.2.4 → jac_client-0.2.9}/pyproject.toml +6 -2
  104. jac_client-0.2.4/jac_client.egg-info/SOURCES.txt +0 -13
  105. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client/examples/all-in-one/assets/workers/worker.py +0 -0
  106. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client/tests/__init__.py +0 -0
  107. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client.egg-info/dependency_links.txt +0 -0
  108. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client.egg-info/entry_points.txt +0 -0
  109. {jac_client-0.2.4 → jac_client-0.2.9}/jac_client.egg-info/top_level.txt +0 -0
  110. {jac_client-0.2.4 → jac_client-0.2.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jac-client
3
- Version: 0.2.4
3
+ Version: 0.2.9
4
4
  Summary: Build full-stack web applications with Jac - one language for frontend and backend.
5
5
  Author-email: Jason Mars <jason@mars.ninja>
6
6
  Maintainer-email: Jason Mars <jason@mars.ninja>
@@ -11,7 +11,7 @@ Project-URL: Documentation, https://jac-lang.org
11
11
  Keywords: jac,jaclang,jaseci,frontend,full-stack,web-development
12
12
  Requires-Python: >=3.12
13
13
  Description-Content-Type: text/markdown
14
- Requires-Dist: jaclang==0.9.4
14
+ Requires-Dist: jaclang>=0.9.9
15
15
  Provides-Extra: dev
16
16
  Requires-Dist: python-dotenv==1.0.1; extra == "dev"
17
17
  Requires-Dist: pytest==8.3.5; extra == "dev"
@@ -28,7 +28,7 @@ Jac Client enables you to write React-like components, manage state, and build i
28
28
 
29
29
  - **Single Language**: Write frontend and backend in Jac
30
30
  - **No HTTP Client**: Use `jacSpawn()` instead of fetch/axios
31
- - **React Hooks**: Use standard React `useState` and `useEffect` hooks
31
+ - **React Hooks**: Use standard React `useState` and `useEffect` hooks (useState is auto-injected when using `has` variables)
32
32
  - **Component-Based**: Build reusable UI components with JSX
33
33
  - **Graph Database**: Built-in graph data model eliminates need for SQL/NoSQL
34
34
  - **Type Safety**: Type checking across frontend and backend
@@ -47,14 +47,16 @@ pip install jac-client
47
47
  ### Create a New App
48
48
 
49
49
  ```bash
50
- jac create --cl my-app
50
+ jac create --use client my-app
51
51
  cd my-app
52
- jac serve src/app.jac
52
+ jac start src/app.jac
53
53
  ```
54
54
 
55
- Visit `http://localhost:8000/page/app` to see your app!
55
+ Visit `http://localhost:8000` to see your app! (The `app` component is served at the root by default.)
56
56
 
57
- > **Note**: The `--cl` flag creates a client-side project with an organized folder structure. Without `--cl`, `jac create` creates a standard Jac project.
57
+ You can also access the app at `http://localhost:8000/cl/app`.
58
+
59
+ > **Note**: The `--npm` flag creates a client-side project with an organized folder structure. Without `--npm`, `jac create` creates a standard Jac project.
58
60
 
59
61
  ---
60
62
 
@@ -75,10 +77,13 @@ For detailed guides and tutorials, see the **[docs folder](jac_client/docs/)**:
75
77
  ### Simple Counter with React Hooks
76
78
 
77
79
  ```jac
78
- cl import from react { useState, useEffect }
80
+ # Note: useState is auto-injected when using has variables in cl blocks
81
+ # Only useEffect needs explicit import
82
+ cl import from react { useEffect }
79
83
 
80
84
  cl {
81
85
  def Counter() -> any {
86
+ # useState is automatically available - no import needed!
82
87
  [count, setCount] = useState(0);
83
88
 
84
89
  useEffect(lambda -> None {
@@ -101,10 +106,13 @@ cl {
101
106
  }
102
107
  ```
103
108
 
109
+ > **Note:** When using `has` variables in `cl {}` blocks or `.cl.jac` files, the `useState` import is automatically injected. You only need to explicitly import other hooks like `useEffect`.
110
+
104
111
  ### Full-Stack Todo App
105
112
 
106
113
  ```jac
107
- cl import from react { useState, useEffect }
114
+ # useState is auto-injected, only import useEffect
115
+ cl import from react { useEffect }
108
116
  cl import from '@jac-client/utils' { jacSpawn }
109
117
 
110
118
  # Backend: Jac nodes and walkers
@@ -130,6 +138,7 @@ walker read_todos {
130
138
  # Frontend: React component
131
139
  cl {
132
140
  def app() -> any {
141
+ # useState is automatically available - no import needed!
133
142
  [todos, setTodos] = useState([]);
134
143
 
135
144
  useEffect(lambda -> None {
@@ -10,7 +10,7 @@ Jac Client enables you to write React-like components, manage state, and build i
10
10
 
11
11
  - **Single Language**: Write frontend and backend in Jac
12
12
  - **No HTTP Client**: Use `jacSpawn()` instead of fetch/axios
13
- - **React Hooks**: Use standard React `useState` and `useEffect` hooks
13
+ - **React Hooks**: Use standard React `useState` and `useEffect` hooks (useState is auto-injected when using `has` variables)
14
14
  - **Component-Based**: Build reusable UI components with JSX
15
15
  - **Graph Database**: Built-in graph data model eliminates need for SQL/NoSQL
16
16
  - **Type Safety**: Type checking across frontend and backend
@@ -29,14 +29,16 @@ pip install jac-client
29
29
  ### Create a New App
30
30
 
31
31
  ```bash
32
- jac create --cl my-app
32
+ jac create --use client my-app
33
33
  cd my-app
34
- jac serve src/app.jac
34
+ jac start src/app.jac
35
35
  ```
36
36
 
37
- Visit `http://localhost:8000/page/app` to see your app!
37
+ Visit `http://localhost:8000` to see your app! (The `app` component is served at the root by default.)
38
38
 
39
- > **Note**: The `--cl` flag creates a client-side project with an organized folder structure. Without `--cl`, `jac create` creates a standard Jac project.
39
+ You can also access the app at `http://localhost:8000/cl/app`.
40
+
41
+ > **Note**: The `--npm` flag creates a client-side project with an organized folder structure. Without `--npm`, `jac create` creates a standard Jac project.
40
42
 
41
43
  ---
42
44
 
@@ -57,10 +59,13 @@ For detailed guides and tutorials, see the **[docs folder](jac_client/docs/)**:
57
59
  ### Simple Counter with React Hooks
58
60
 
59
61
  ```jac
60
- cl import from react { useState, useEffect }
62
+ # Note: useState is auto-injected when using has variables in cl blocks
63
+ # Only useEffect needs explicit import
64
+ cl import from react { useEffect }
61
65
 
62
66
  cl {
63
67
  def Counter() -> any {
68
+ # useState is automatically available - no import needed!
64
69
  [count, setCount] = useState(0);
65
70
 
66
71
  useEffect(lambda -> None {
@@ -83,10 +88,13 @@ cl {
83
88
  }
84
89
  ```
85
90
 
91
+ > **Note:** When using `has` variables in `cl {}` blocks or `.cl.jac` files, the `useState` import is automatically injected. You only need to explicitly import other hooks like `useEffect`.
92
+
86
93
  ### Full-Stack Todo App
87
94
 
88
95
  ```jac
89
- cl import from react { useState, useEffect }
96
+ # useState is auto-injected, only import useEffect
97
+ cl import from react { useEffect }
90
98
  cl import from '@jac-client/utils' { jacSpawn }
91
99
 
92
100
  # Backend: Jac nodes and walkers
@@ -112,6 +120,7 @@ walker read_todos {
112
120
  # Frontend: React component
113
121
  cl {
114
122
  def app() -> any {
123
+ # useState is automatically available - no import needed!
115
124
  [todos, setTodos] = useState([]);
116
125
 
117
126
  useEffect(lambda -> None {
@@ -0,0 +1,7 @@
1
+ cl import from antd { Button }
2
+
3
+ cl def:pub CustomButtonRoot -> any {
4
+ return <Button>
5
+ Root Button
6
+ </Button>;
7
+ }
@@ -0,0 +1,35 @@
1
+ # Category filter component
2
+ # Demonstrates: iteration with map, conditional classes, events
3
+
4
+ cl import from ..constants.categories { CATEGORIES, CATEGORY_LABELS, CATEGORY_COLORS }
5
+
6
+ cl {
7
+ # Filter buttons for categories
8
+ def:pub CategoryFilter(selectedCategory: str, onSelect: any) -> any {
9
+ # Add "ALL" to the beginning of categories
10
+ allCategories = ["ALL"].concat(CATEGORIES);
11
+
12
+ return <div className="category-filter">
13
+ <h3 className="filter-title">Filter by Category</h3>
14
+ <div className="filter-buttons">
15
+ {allCategories.map(lambda cat: str -> any {
16
+ isActive = selectedCategory == cat;
17
+ color = CATEGORY_COLORS[cat] if cat != "ALL" else "#374151";
18
+
19
+ return <button
20
+ key={cat}
21
+ className={("filter-btn active") if isActive else ("filter-btn")}
22
+ style={{
23
+ "borderColor": color,
24
+ "backgroundColor": (color) if isActive else ("transparent"),
25
+ "color": ("#fff") if isActive else (color)
26
+ }}
27
+ onClick={lambda: onSelect(cat)}
28
+ >
29
+ {(cat) if cat == "ALL" else (CATEGORY_LABELS[cat])}
30
+ </button>;
31
+ })}
32
+ </div>
33
+ </div>;
34
+ }
35
+ }
@@ -0,0 +1,13 @@
1
+ # Header component
2
+ # Demonstrates: simple component, CSS classes
3
+
4
+ cl {
5
+ def:pub Header() -> any {
6
+ return <header className="header">
7
+ <div className="header-content">
8
+ <h1 className="header-title">Budget Planner</h1>
9
+ <p className="header-subtitle">Track your income and expenses</p>
10
+ </div>
11
+ </header>;
12
+ }
13
+ }
@@ -0,0 +1,50 @@
1
+ # Profit Overview component - monthly profit snapshot
2
+ # Demonstrates: context usage, calculated displays, formatting
3
+
4
+ cl import from ..context.BudgetContext { useBudgetContext }
5
+ cl import from ..utils.formatters { formatCurrency }
6
+
7
+ cl {
8
+ def:pub ProfitOverview() -> any {
9
+ budget = useBudgetContext();
10
+
11
+ businessIncome = budget["businessIncome"];
12
+ businessExpenses = budget["businessExpenses"];
13
+ taxReserve = budget["taxReserve"];
14
+ netProfit = budget["netProfit"];
15
+
16
+ return <div className="profit-overview">
17
+ <h3 className="profit-title">Monthly Profit Snapshot</h3>
18
+
19
+ <div className="profit-breakdown">
20
+ <div className="profit-row income">
21
+ <span className="profit-label">Business Income</span>
22
+ <span className="profit-value positive">
23
+ +{formatCurrency(businessIncome)}
24
+ </span>
25
+ </div>
26
+
27
+ <div className="profit-row expense">
28
+ <span className="profit-label">Business Expenses</span>
29
+ <span className="profit-value negative">
30
+ -{formatCurrency(businessExpenses)}
31
+ </span>
32
+ </div>
33
+
34
+ <div className="profit-row tax">
35
+ <span className="profit-label">Tax Reserve (20%)</span>
36
+ <span className="profit-value negative">
37
+ -{formatCurrency(taxReserve)}
38
+ </span>
39
+ </div>
40
+
41
+ <div className="profit-row total">
42
+ <span className="profit-label">Net Profit</span>
43
+ <span className={("profit-value bold positive") if netProfit >= 0 else ("profit-value bold negative")}>
44
+ {("+") if netProfit > 0 else ("")}{formatCurrency(netProfit)}
45
+ </span>
46
+ </div>
47
+ </div>
48
+ </div>;
49
+ }
50
+ }
@@ -0,0 +1,53 @@
1
+ # Summary component - displays budget totals with business/personal breakdown
2
+ # Demonstrates: context usage, conditional rendering, ternary operator, 6-card layout
3
+
4
+ cl import from ..context.BudgetContext { useBudgetContext }
5
+ cl import from ..utils.formatters { formatCurrency }
6
+
7
+ cl {
8
+ def:pub Summary() -> any {
9
+ budget = useBudgetContext();
10
+
11
+ # Business/Personal breakdown
12
+ businessIncome = budget["businessIncome"];
13
+ businessExpenses = budget["businessExpenses"];
14
+ personalIncome = budget["personalIncome"];
15
+ personalExpenses = budget["personalExpenses"];
16
+ taxReserve = budget["taxReserve"];
17
+ netProfit = budget["netProfit"];
18
+
19
+ return <div className="summary">
20
+ <div className="summary-card business-income">
21
+ <span className="summary-label">Business Income</span>
22
+ <span className="summary-value">{formatCurrency(businessIncome)}</span>
23
+ </div>
24
+
25
+ <div className="summary-card business-expenses">
26
+ <span className="summary-label">Business Expenses</span>
27
+ <span className="summary-value">{formatCurrency(businessExpenses)}</span>
28
+ </div>
29
+
30
+ <div className="summary-card personal-income">
31
+ <span className="summary-label">Personal Income</span>
32
+ <span className="summary-value">{formatCurrency(personalIncome)}</span>
33
+ </div>
34
+
35
+ <div className="summary-card personal-expenses">
36
+ <span className="summary-label">Personal Expenses</span>
37
+ <span className="summary-value">{formatCurrency(personalExpenses)}</span>
38
+ </div>
39
+
40
+ <div className="summary-card tax-reserve">
41
+ <span className="summary-label">Tax Reserve (20%)</span>
42
+ <span className="summary-value">{formatCurrency(taxReserve)}</span>
43
+ </div>
44
+
45
+ <div className={("summary-card net-profit positive") if netProfit >= 0 else ("summary-card net-profit negative")}>
46
+ <span className="summary-label">Net Profit</span>
47
+ <span className="summary-value">
48
+ {("+") if netProfit > 0 else ("")}{formatCurrency(netProfit)}
49
+ </span>
50
+ </div>
51
+ </div>;
52
+ }
53
+ }
@@ -0,0 +1,158 @@
1
+ # Transaction form component
2
+ # Demonstrates: form handling, useState, events, select options
3
+
4
+ cl import from ..context.BudgetContext { useBudgetContext }
5
+ cl import from ..constants.categories { CATEGORIES, CATEGORY_LABELS }
6
+ cl import from ..constants.clients { CLIENTS }
7
+
8
+ cl {
9
+ def:pub TransactionForm() -> any {
10
+ [description, setDescription] = useState("");
11
+ [amount, setAmount] = useState("");
12
+ [category, setCategory] = useState("OTHER");
13
+ [txType, setTxType] = useState("expense");
14
+ [isBusiness, setIsBusiness] = useState(false);
15
+ [clientName, setClientName] = useState("");
16
+ budget = useBudgetContext();
17
+
18
+ def handleSubmit(e: any) -> None {
19
+ e.preventDefault();
20
+
21
+ # Validate inputs
22
+ trimmedDesc = description.trim();
23
+ if trimmedDesc == "" or amount == "" {
24
+ return;
25
+ }
26
+
27
+ parsedAmount = parseFloat(amount);
28
+ if isNaN(parsedAmount) or parsedAmount <= 0 {
29
+ return;
30
+ }
31
+
32
+ # Add the transaction
33
+ budget["addTransaction"](trimmedDesc, parsedAmount, category, txType, isBusiness, clientName);
34
+
35
+ # Reset form
36
+ setDescription("");
37
+ setAmount("");
38
+ setCategory("OTHER");
39
+ setIsBusiness(false);
40
+ setClientName("");
41
+ }
42
+
43
+ # Filter categories based on type (income only has INCOME category)
44
+ availableCategories = CATEGORIES.filter(lambda cat: str -> bool {
45
+ if txType == "income" {
46
+ return cat == "INCOME";
47
+ }
48
+ return cat != "INCOME";
49
+ });
50
+
51
+ # Show client dropdown only for business income
52
+ showClientDropdown = (txType == "income" and isBusiness);
53
+
54
+ return <form className="transaction-form" onSubmit={handleSubmit}>
55
+ <div className="form-row">
56
+ <div className="form-group type-toggle">
57
+ <button
58
+ type="button"
59
+ className={("toggle-btn active") if txType == "expense" else ("toggle-btn")}
60
+ onClick={lambda: setTxType("expense")}
61
+ >
62
+ Expense
63
+ </button>
64
+ <button
65
+ type="button"
66
+ className={("toggle-btn active income") if txType == "income" else ("toggle-btn")}
67
+ onClick={lambda: setTxType("income")}
68
+ >
69
+ Income
70
+ </button>
71
+ </div>
72
+
73
+ <div className="form-group business-toggle">
74
+ <button
75
+ type="button"
76
+ className={("toggle-btn active") if isBusiness else ("toggle-btn")}
77
+ onClick={lambda: setIsBusiness(true)}
78
+ >
79
+ Business
80
+ </button>
81
+ <button
82
+ type="button"
83
+ className={("toggle-btn active") if not isBusiness else ("toggle-btn")}
84
+ onClick={lambda: setIsBusiness(false)}
85
+ >
86
+ Personal
87
+ </button>
88
+ </div>
89
+ </div>
90
+
91
+ <div className="form-row">
92
+ <div className="form-group">
93
+ <label htmlFor="description">Description</label>
94
+ <input
95
+ id="description"
96
+ type="text"
97
+ value={description}
98
+ onChange={lambda e: any -> None { setDescription(e.target.value); }}
99
+ placeholder="Enter description..."
100
+ className="form-input"
101
+ />
102
+ </div>
103
+
104
+ <div className="form-group">
105
+ <label htmlFor="amount">Amount</label>
106
+ <input
107
+ id="amount"
108
+ type="number"
109
+ value={amount}
110
+ onChange={lambda e: any -> None { setAmount(e.target.value); }}
111
+ placeholder="0.00"
112
+ min="0"
113
+ step="0.01"
114
+ className="form-input"
115
+ />
116
+ </div>
117
+
118
+ <div className="form-group">
119
+ <label htmlFor="category">Category</label>
120
+ <select
121
+ id="category"
122
+ value={category}
123
+ onChange={lambda e: any -> None { setCategory(e.target.value); }}
124
+ className="form-select"
125
+ >
126
+ {availableCategories.map(lambda cat: str -> any {
127
+ return <option key={cat} value={cat}>
128
+ {CATEGORY_LABELS[cat]}
129
+ </option>;
130
+ })}
131
+ </select>
132
+ </div>
133
+
134
+ {showClientDropdown and <div className="form-group">
135
+ <label htmlFor="client">Client</label>
136
+ <select
137
+ id="client"
138
+ value={clientName}
139
+ onChange={lambda e: any -> None { setClientName(e.target.value); }}
140
+ className="form-select"
141
+ >
142
+ <option value="">Select Client (Optional)</option>
143
+ {CLIENTS.map(lambda client: str -> any {
144
+ return <option key={client} value={client}>{client}</option>;
145
+ })}
146
+ </select>
147
+ </div>}
148
+
149
+ <div className="form-group">
150
+ <label>Action</label>
151
+ <button type="submit" className="submit-btn">
152
+ Add {(txType[0].toUpperCase() + txType.slice(1))}
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </form>;
157
+ }
158
+ }
@@ -0,0 +1,55 @@
1
+ # Single transaction item component
2
+ # Demonstrates: NEW PROPS PATTERN (direct parameters, NOT props dict)
3
+
4
+ cl import from ..utils.formatters { formatCurrency, formatDate }
5
+ cl import from ..constants.categories { CATEGORY_COLORS, CATEGORY_LABELS }
6
+
7
+ cl {
8
+ # NEW WAY: Direct parameters instead of props: dict
9
+ def:pub TransactionItem(
10
+ id: str,
11
+ description: str,
12
+ amount: float,
13
+ category: str,
14
+ txType: str,
15
+ date: str,
16
+ isBusiness: bool,
17
+ clientName: any,
18
+ onDelete: any
19
+ ) -> any {
20
+ isIncome = txType == "income";
21
+ color = CATEGORY_COLORS[category];
22
+ label = CATEGORY_LABELS[category];
23
+
24
+ return <div className="transaction-item">
25
+ <div className="tx-left">
26
+ <span
27
+ className="tx-category"
28
+ style={{"backgroundColor": color}}
29
+ >
30
+ {label}
31
+ </span>
32
+ <div className="tx-details">
33
+ <span className="tx-description">
34
+ {description}
35
+ {(clientName != None and isIncome) and <span className="tx-client"> • {clientName}</span>}
36
+ </span>
37
+ <span className="tx-date">{formatDate(date)}</span>
38
+ </div>
39
+ </div>
40
+
41
+ <div className="tx-right">
42
+ <span className={("tx-amount income") if isIncome else ("tx-amount expense")}>
43
+ {("+") if isIncome else ("-")}{formatCurrency(amount)}
44
+ </span>
45
+ <button
46
+ className="delete-btn"
47
+ onClick={lambda:onDelete(id)}
48
+ title="Delete transaction"
49
+ >
50
+ X
51
+ </button>
52
+ </div>
53
+ </div>;
54
+ }
55
+ }
@@ -0,0 +1,37 @@
1
+ # Transaction list component
2
+ # Demonstrates: rendering lists with map, passing props to child components
3
+
4
+ cl import from .TransactionItem { TransactionItem }
5
+
6
+ cl {
7
+ # Props: transactions list and delete handler
8
+ def:pub TransactionList(transactions: list, onDelete: any) -> any {
9
+ if transactions.length == 0 {
10
+ return <div className="empty-state">
11
+ <p>No transactions yet.</p>
12
+ <p>Add your first income or expense above!</p>
13
+ </div>;
14
+ }
15
+
16
+ return <div className="transaction-list">
17
+ <h3 className="list-title">
18
+ Transactions ({transactions.length})
19
+ </h3>
20
+
21
+ {transactions.map(lambda tx: dict -> any {
22
+ return <TransactionItem
23
+ key={tx["id"]}
24
+ id={tx["id"]}
25
+ description={tx["description"]}
26
+ amount={tx["amount"]}
27
+ category={tx["category"]}
28
+ txType={tx["type"]}
29
+ date={tx["date"]}
30
+ isBusiness={tx["isBusinessTransaction"] || false}
31
+ clientName={tx["clientName"] || None}
32
+ onDelete={onDelete}
33
+ />;
34
+ })}
35
+ </div>;
36
+ }
37
+ }
@@ -0,0 +1,7 @@
1
+ cl import from antd { Button }
2
+
3
+ cl def:pub CustomButton -> any {
4
+ return <Button>
5
+ Nested Button
6
+ </Button>;
7
+ }