jac-client 0.2.6__py3-none-any.whl → 0.2.7__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 (61) hide show
  1. jac_client/examples/all-in-one/src/app.jac +473 -741
  2. jac_client/examples/all-in-one/src/components/CategoryFilter.jac +35 -0
  3. jac_client/examples/all-in-one/src/components/Header.jac +13 -0
  4. jac_client/examples/all-in-one/src/components/ProfitOverview.jac +50 -0
  5. jac_client/examples/all-in-one/src/components/Summary.jac +53 -0
  6. jac_client/examples/all-in-one/src/components/TransactionForm.jac +158 -0
  7. jac_client/examples/all-in-one/src/components/TransactionItem.jac +55 -0
  8. jac_client/examples/all-in-one/src/components/TransactionList.jac +37 -0
  9. jac_client/examples/all-in-one/src/components/navigation.jac +132 -0
  10. jac_client/examples/all-in-one/src/constants/categories.jac +37 -0
  11. jac_client/examples/all-in-one/src/constants/clients.jac +13 -0
  12. jac_client/examples/all-in-one/src/context/BudgetContext.jac +28 -0
  13. jac_client/examples/all-in-one/src/hooks/useBudget.jac +116 -0
  14. jac_client/examples/all-in-one/src/hooks/useLocalStorage.jac +36 -0
  15. jac_client/examples/all-in-one/src/pages/BudgetPlanner.cl.jac +70 -0
  16. jac_client/examples/all-in-one/src/pages/BudgetPlanner.jac +126 -0
  17. jac_client/examples/all-in-one/src/pages/FeaturesTest.cl.jac +552 -0
  18. jac_client/examples/all-in-one/src/pages/FeaturesTest.jac +126 -0
  19. jac_client/examples/all-in-one/src/pages/LandingPage.jac +101 -0
  20. jac_client/examples/all-in-one/src/pages/loginPage.jac +132 -0
  21. jac_client/examples/all-in-one/src/pages/nestedDemo.jac +61 -0
  22. jac_client/examples/all-in-one/src/pages/notFound.jac +24 -0
  23. jac_client/examples/all-in-one/src/pages/signupPage.jac +133 -0
  24. jac_client/examples/all-in-one/src/utils/formatters.jac +52 -0
  25. jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
  26. jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
  27. jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
  28. jac_client/examples/basic/src/app.jac +3 -3
  29. jac_client/examples/basic-auth/src/app.jac +31 -37
  30. jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
  31. jac_client/examples/basic-full-stack/src/app.jac +24 -30
  32. jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
  33. jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
  34. jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
  35. jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
  36. jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
  37. jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
  38. jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
  39. jac_client/examples/ts-support/src/app.jac +4 -4
  40. jac_client/examples/with-router/src/app.jac +4 -4
  41. jac_client/plugin/cli.jac +155 -203
  42. jac_client/plugin/client_runtime.cl.jac +5 -1
  43. jac_client/plugin/impl/client.impl.jac +74 -12
  44. jac_client/plugin/plugin_config.jac +11 -11
  45. jac_client/plugin/src/compiler.jac +2 -1
  46. jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
  47. jac_client/plugin/src/impl/compiler.impl.jac +57 -18
  48. jac_client/plugin/src/impl/vite_bundler.impl.jac +66 -102
  49. jac_client/plugin/src/package_installer.jac +1 -1
  50. jac_client/plugin/src/vite_bundler.jac +1 -0
  51. jac_client/tests/conftest.py +10 -8
  52. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  53. jac_client/tests/fixtures/with-ts/app.jac +4 -4
  54. jac_client/tests/test_cli.py +99 -45
  55. jac_client/tests/test_it.py +290 -79
  56. {jac_client-0.2.6.dist-info → jac_client-0.2.7.dist-info}/METADATA +16 -7
  57. jac_client-0.2.7.dist-info/RECORD +97 -0
  58. jac_client-0.2.6.dist-info/RECORD +0 -74
  59. {jac_client-0.2.6.dist-info → jac_client-0.2.7.dist-info}/WHEEL +0 -0
  60. {jac_client-0.2.6.dist-info → jac_client-0.2.7.dist-info}/entry_points.txt +0 -0
  61. {jac_client-0.2.6.dist-info → jac_client-0.2.7.dist-info}/top_level.txt +0 -0
@@ -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,132 @@
1
+ cl import from "@jac-client/utils" {
2
+
3
+ Link,
4
+ useNavigate,
5
+ useLocation,
6
+ jacLogout,
7
+ jacIsLoggedIn
8
+ }
9
+ cl {
10
+ def:pub Navigation -> any {
11
+ location = useLocation();
12
+ isLoggedIn = jacIsLoggedIn();
13
+ navigate = useNavigate();
14
+
15
+ def linkStyle(path: str) -> dict {
16
+ isActive = location.pathname == path;
17
+ return {
18
+ "padding": "0.5rem 1rem",
19
+ "textDecoration": "none",
20
+ "color": "#0066cc" if isActive else "#333",
21
+ "fontWeight": "bold" if isActive else "normal",
22
+ "backgroundColor": "#e3f2fd" if isActive else "transparent",
23
+ "borderRadius": "4px",
24
+ "display": "inline-block"
25
+ };
26
+ }
27
+
28
+ def handleLogout(e: any) -> None {
29
+ e.preventDefault();
30
+ jacLogout();
31
+ navigate("/login");
32
+ }
33
+
34
+ authButtons = None;
35
+ if isLoggedIn {
36
+ authButtons = <button
37
+ onClick={handleLogout}
38
+ style={{
39
+ "padding": "0.5rem 1rem",
40
+ "background": "#ef4444",
41
+ "color": "#ffffff",
42
+ "border": "none",
43
+ "borderRadius": "4px",
44
+ "cursor": "pointer",
45
+ "fontWeight": "600"
46
+ }}
47
+ >
48
+ Logout
49
+ </button>;
50
+ } else {
51
+ authButtons = <>
52
+ <Link
53
+ to="/login"
54
+ style={linkStyle("/login")}
55
+ >
56
+ Login
57
+ </Link>
58
+ <Link
59
+ to="/signup"
60
+ style={linkStyle("/signup")}
61
+ >
62
+ Sign Up
63
+ </Link>
64
+ </>;
65
+ }
66
+
67
+ return <nav
68
+ style={{
69
+ "padding": "1rem",
70
+ "backgroundColor": "#f5f5f5",
71
+ "marginBottom": "2rem",
72
+ "boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
73
+ }}
74
+ >
75
+ <div
76
+ style={{
77
+ "maxWidth": "1200px",
78
+ "margin": "0 auto",
79
+ "display": "flex",
80
+ "gap": "1rem",
81
+ "alignItems": "center",
82
+ "justifyContent": "space-between"
83
+ }}
84
+ >
85
+ <div
86
+ style={{"display": "flex", "gap": "1rem", "alignItems": "center"}}
87
+ >
88
+ {isLoggedIn
89
+ and (
90
+ <>
91
+ <Link
92
+ to="/"
93
+ style={linkStyle("/")}
94
+ >
95
+ Home
96
+ </Link>
97
+ <Link
98
+ to="/nested"
99
+ style={linkStyle("/nested")}
100
+ >
101
+ Nested Imports
102
+ </Link>
103
+ <Link
104
+ to="/features-test"
105
+ style={linkStyle("/features-test")}
106
+ >
107
+ Features Test
108
+ </Link>
109
+ <Link
110
+ to="/landing"
111
+ style={linkStyle("/landing")}
112
+ >
113
+ Landing Page
114
+ </Link>
115
+ <Link
116
+ to="/budget-planner"
117
+ style={linkStyle("/budget-planner")}
118
+ >
119
+ Budget Planner
120
+ </Link>
121
+ </>
122
+ )}
123
+ </div>
124
+ <div
125
+ style={{"display": "flex", "gap": "1rem", "alignItems": "center"}}
126
+ >
127
+ {authButtons}
128
+ </div>
129
+ </div>
130
+ </nav>;
131
+ }
132
+ }
@@ -0,0 +1,37 @@
1
+ # Category constants and enums for Budget Planner
2
+ # Demonstrates: enum:pub, glob:pub exports
3
+
4
+ cl {
5
+ # Category enum - demonstrates enum:pub export
6
+ enum:pub Category {
7
+ INCOME,
8
+ FOOD,
9
+ TRANSPORT,
10
+ UTILITIES,
11
+ ENTERTAINMENT,
12
+ OTHER
13
+ }
14
+
15
+ # Category colors - demonstrates glob:pub export
16
+ glob:pub CATEGORY_COLORS: dict = {
17
+ "INCOME": "#10b981",
18
+ "FOOD": "#f59e0b",
19
+ "TRANSPORT": "#3b82f6",
20
+ "UTILITIES": "#8b5cf6",
21
+ "ENTERTAINMENT": "#ec4899",
22
+ "OTHER": "#6b7280"
23
+ };
24
+
25
+ # Category labels for display
26
+ glob:pub CATEGORY_LABELS: dict = {
27
+ "INCOME": "Income",
28
+ "FOOD": "Food & Dining",
29
+ "TRANSPORT": "Transport",
30
+ "UTILITIES": "Utilities",
31
+ "ENTERTAINMENT": "Entertainment",
32
+ "OTHER": "Other"
33
+ };
34
+
35
+ # All category keys as a list
36
+ glob:pub CATEGORIES: list = ["INCOME", "FOOD", "TRANSPORT", "UTILITIES", "ENTERTAINMENT", "OTHER"];
37
+ }
@@ -0,0 +1,13 @@
1
+ # Client list for freelancer income tracking
2
+ # Demonstrates: glob exports, array constants
3
+
4
+ cl {
5
+ glob:pub CLIENTS = [
6
+ "Acme Corp",
7
+ "TechStart Inc",
8
+ "Design Studio Co",
9
+ "Marketing Agency",
10
+ "Freelance Marketplace",
11
+ "Other"
12
+ ];
13
+ }
@@ -0,0 +1,28 @@
1
+ # Budget Context for global state management
2
+ # Demonstrates: createContext, useContext, Context.Provider
3
+
4
+ cl import from react { createContext, useContext }
5
+ cl import from ..hooks.useBudget { useBudget }
6
+
7
+ cl {
8
+ # Create the context - demonstrates glob for context
9
+ glob:pub BudgetContext = createContext(None);
10
+
11
+ # Provider component - wraps app and provides budget state
12
+ def:pub BudgetProvider(children: any) -> any {
13
+ budget = useBudget();
14
+
15
+ return <BudgetContext.Provider value={budget}>
16
+ {children}
17
+ </BudgetContext.Provider>;
18
+ }
19
+
20
+ # Custom hook to access budget context
21
+ def:pub useBudgetContext() -> dict {
22
+ context = useContext(BudgetContext);
23
+ if context == None {
24
+ console.error("useBudgetContext must be used within BudgetProvider");
25
+ }
26
+ return context;
27
+ }
28
+ }