jac-client 0.2.3__py3-none-any.whl → 0.2.5__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.
- jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
- jac_client/examples/all-in-one/src/app.jac +841 -0
- jac_client/examples/all-in-one/{button.jac → src/button.jac} +1 -1
- jac_client/examples/all-in-one/{components → src/components}/button.jac +1 -1
- jac_client/examples/asset-serving/css-with-image/{app.jac → src/app.jac} +2 -2
- jac_client/examples/asset-serving/image-asset/{app.jac → src/app.jac} +2 -2
- jac_client/examples/asset-serving/import-alias/{app.jac → src/app.jac} +3 -3
- jac_client/examples/basic/{app.jac → src/app.jac} +2 -2
- jac_client/examples/basic-auth/src/app.jac +377 -0
- jac_client/examples/basic-auth-with-router/{app.jac → src/app.jac} +18 -18
- jac_client/examples/basic-full-stack/{app.jac → src/app.jac} +175 -130
- jac_client/examples/css-styling/js-styling/{app.jac → src/app.jac} +6 -6
- jac_client/examples/css-styling/material-ui/{app.jac → src/app.jac} +5 -5
- jac_client/examples/css-styling/pure-css/{app.jac → src/app.jac} +6 -6
- jac_client/examples/css-styling/sass-example/{app.jac → src/app.jac} +6 -6
- jac_client/examples/css-styling/styled-components/{app.jac → src/app.jac} +5 -5
- jac_client/examples/css-styling/tailwind-example/{app.jac → src/app.jac} +6 -6
- jac_client/examples/full-stack-with-auth/{app.jac → src/app.jac} +37 -37
- jac_client/examples/little-x/{app.jac → src/app.jac} +27 -32
- jac_client/examples/little-x/src/submit-button.jac +16 -0
- jac_client/examples/nested-folders/nested-advance/{ButtonRoot.jac → src/ButtonRoot.jac} +1 -1
- jac_client/examples/nested-folders/nested-advance/{app.jac → src/app.jac} +1 -1
- jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/ButtonSecondL.jac +1 -1
- jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/Card.jac +1 -1
- jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/level2/ButtonThirdL.jac +1 -1
- jac_client/examples/nested-folders/nested-basic/{app.jac → src/app.jac} +2 -2
- jac_client/examples/nested-folders/nested-basic/{button.jac → src/button.jac} +1 -1
- jac_client/examples/nested-folders/nested-basic/{components → src/components}/button.jac +1 -1
- jac_client/examples/ts-support/src/app.jac +35 -0
- jac_client/examples/with-router/{app.jac → src/app.jac} +11 -11
- jac_client/plugin/cli.jac +547 -0
- jac_client/plugin/client.jac +52 -0
- jac_client/plugin/client_runtime.cl.jac +38 -0
- jac_client/plugin/impl/client.impl.jac +134 -0
- jac_client/plugin/impl/client_runtime.impl.jac +177 -0
- jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
- jac_client/plugin/plugin_config.jac +195 -0
- jac_client/plugin/src/__init__.jac +20 -0
- jac_client/plugin/src/asset_processor.jac +33 -0
- jac_client/plugin/src/babel_processor.jac +18 -0
- jac_client/plugin/src/compiler.jac +66 -0
- jac_client/plugin/src/config_loader.jac +32 -0
- jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
- jac_client/plugin/src/impl/babel_processor.impl.jac +84 -0
- jac_client/plugin/src/impl/compiler.impl.jac +251 -0
- jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
- jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
- jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
- jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
- jac_client/plugin/src/impl/vite_bundler.impl.jac +513 -0
- jac_client/plugin/src/import_processor.jac +19 -0
- jac_client/plugin/src/jac_to_js.jac +35 -0
- jac_client/plugin/src/package_installer.jac +26 -0
- jac_client/plugin/src/vite_bundler.jac +36 -0
- jac_client/plugin/vite_client_bundle.jac +31 -0
- jac_client/tests/conftest.py +281 -0
- jac_client/tests/fixtures/basic-app/app.jac +2 -2
- jac_client/tests/fixtures/cl_file/app.cl.jac +2 -2
- jac_client/tests/fixtures/client_app_with_antd/app.jac +1 -1
- jac_client/tests/fixtures/js_import/app.jac +5 -5
- jac_client/tests/fixtures/spawn_test/app.jac +7 -7
- jac_client/tests/fixtures/with-ts/app.jac +35 -0
- jac_client/tests/test_cli.py +755 -0
- jac_client/tests/test_it.py +347 -67
- {jac_client-0.2.3.dist-info → jac_client-0.2.5.dist-info}/METADATA +28 -30
- jac_client-0.2.5.dist-info/RECORD +74 -0
- {jac_client-0.2.3.dist-info → jac_client-0.2.5.dist-info}/WHEEL +2 -1
- jac_client-0.2.5.dist-info/entry_points.txt +4 -0
- jac_client-0.2.5.dist-info/top_level.txt +1 -0
- jac_client/docs/README.md +0 -689
- jac_client/docs/advanced-state.md +0 -1265
- jac_client/docs/asset-serving/intro.md +0 -209
- jac_client/docs/assets/pipe_line-v2.svg +0 -32
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/app.jac.md +0 -121
- jac_client/docs/file-system/backend-frontend.md +0 -217
- jac_client/docs/file-system/intro.md +0 -72
- jac_client/docs/file-system/nested-imports.md +0 -348
- jac_client/docs/guide-example/intro.md +0 -115
- jac_client/docs/guide-example/step-01-setup.md +0 -270
- jac_client/docs/guide-example/step-02-components.md +0 -416
- jac_client/docs/guide-example/step-03-styling.md +0 -478
- jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
- jac_client/docs/guide-example/step-05-local-state.md +0 -530
- jac_client/docs/guide-example/step-06-events.md +0 -749
- jac_client/docs/guide-example/step-07-effects.md +0 -468
- jac_client/docs/guide-example/step-08-walkers.md +0 -534
- jac_client/docs/guide-example/step-09-authentication.md +0 -586
- jac_client/docs/guide-example/step-10-routing.md +0 -539
- jac_client/docs/guide-example/step-11-final.md +0 -963
- jac_client/docs/imports.md +0 -1141
- jac_client/docs/lifecycle-hooks.md +0 -773
- jac_client/docs/routing.md +0 -659
- jac_client/docs/styling/intro.md +0 -249
- jac_client/docs/styling/js-styling.md +0 -367
- jac_client/docs/styling/material-ui.md +0 -341
- jac_client/docs/styling/pure-css.md +0 -299
- jac_client/docs/styling/sass.md +0 -403
- jac_client/docs/styling/styled-components.md +0 -395
- jac_client/docs/styling/tailwind.md +0 -298
- jac_client/examples/all-in-one/.babelrc +0 -9
- jac_client/examples/all-in-one/README.md +0 -16
- jac_client/examples/all-in-one/app.jac +0 -426
- jac_client/examples/all-in-one/assets/burger.png +0 -0
- jac_client/examples/all-in-one/package.json +0 -29
- jac_client/examples/all-in-one/styles.css +0 -26
- jac_client/examples/all-in-one/vite.config.js +0 -28
- jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
- jac_client/examples/asset-serving/css-with-image/README.md +0 -91
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +0 -28
- jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
- jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
- jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
- jac_client/examples/asset-serving/image-asset/README.md +0 -119
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +0 -28
- jac_client/examples/asset-serving/image-asset/styles.css +0 -26
- jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
- jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
- jac_client/examples/asset-serving/import-alias/README.md +0 -83
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +0 -28
- jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
- jac_client/examples/basic/.babelrc +0 -9
- jac_client/examples/basic/README.md +0 -16
- jac_client/examples/basic/package.json +0 -27
- jac_client/examples/basic/vite.config.js +0 -27
- jac_client/examples/basic-auth/.babelrc +0 -9
- jac_client/examples/basic-auth/README.md +0 -16
- jac_client/examples/basic-auth/app.jac +0 -308
- jac_client/examples/basic-auth/package.json +0 -27
- jac_client/examples/basic-auth/vite.config.js +0 -27
- jac_client/examples/basic-auth-with-router/.babelrc +0 -9
- jac_client/examples/basic-auth-with-router/README.md +0 -60
- jac_client/examples/basic-auth-with-router/package.json +0 -28
- jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
- jac_client/examples/basic-full-stack/.babelrc +0 -9
- jac_client/examples/basic-full-stack/README.md +0 -18
- jac_client/examples/basic-full-stack/package.json +0 -28
- jac_client/examples/basic-full-stack/vite.config.js +0 -27
- jac_client/examples/css-styling/js-styling/.babelrc +0 -9
- jac_client/examples/css-styling/js-styling/README.md +0 -183
- jac_client/examples/css-styling/js-styling/package.json +0 -28
- jac_client/examples/css-styling/js-styling/styles.js +0 -100
- jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
- jac_client/examples/css-styling/material-ui/.babelrc +0 -9
- jac_client/examples/css-styling/material-ui/README.md +0 -16
- jac_client/examples/css-styling/material-ui/package.json +0 -32
- jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
- jac_client/examples/css-styling/pure-css/.babelrc +0 -9
- jac_client/examples/css-styling/pure-css/README.md +0 -16
- jac_client/examples/css-styling/pure-css/package.json +0 -28
- jac_client/examples/css-styling/pure-css/styles.css +0 -111
- jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
- jac_client/examples/css-styling/sass-example/.babelrc +0 -9
- jac_client/examples/css-styling/sass-example/README.md +0 -16
- jac_client/examples/css-styling/sass-example/package.json +0 -29
- jac_client/examples/css-styling/sass-example/styles.scss +0 -153
- jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
- jac_client/examples/css-styling/styled-components/.babelrc +0 -9
- jac_client/examples/css-styling/styled-components/README.md +0 -16
- jac_client/examples/css-styling/styled-components/package.json +0 -29
- jac_client/examples/css-styling/styled-components/styled.js +0 -90
- jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
- jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
- jac_client/examples/css-styling/tailwind-example/README.md +0 -16
- jac_client/examples/css-styling/tailwind-example/global.css +0 -1
- jac_client/examples/css-styling/tailwind-example/package.json +0 -30
- jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
- jac_client/examples/full-stack-with-auth/.babelrc +0 -9
- jac_client/examples/full-stack-with-auth/README.md +0 -16
- jac_client/examples/full-stack-with-auth/package.json +0 -28
- jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
- jac_client/examples/little-x/package.json +0 -23
- jac_client/examples/little-x/submit-button.jac +0 -8
- jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
- jac_client/examples/nested-folders/nested-advance/README.md +0 -77
- jac_client/examples/nested-folders/nested-advance/package.json +0 -29
- jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
- jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
- jac_client/examples/nested-folders/nested-basic/README.md +0 -183
- jac_client/examples/nested-folders/nested-basic/app.js +0 -7
- jac_client/examples/nested-folders/nested-basic/package.json +0 -28
- jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
- jac_client/examples/with-router/.babelrc +0 -9
- jac_client/examples/with-router/README.md +0 -17
- jac_client/examples/with-router/package.json +0 -28
- jac_client/examples/with-router/vite.config.js +0 -27
- jac_client/plugin/cli.py +0 -244
- jac_client/plugin/client.py +0 -152
- jac_client/plugin/client_runtime.jac +0 -234
- jac_client/plugin/vite_client_bundle.py +0 -503
- jac_client/tests/fixtures/js_import/utils.js +0 -21
- jac_client/tests/fixtures/package-lock.json +0 -329
- jac_client/tests/fixtures/package.json +0 -11
- jac_client/tests/test_asset_examples.py +0 -322
- jac_client/tests/test_cl.py +0 -530
- jac_client/tests/test_create_jac_app.py +0 -131
- jac_client/tests/test_nested_file.py +0 -374
- jac_client-0.2.3.dist-info/RECORD +0 -171
- jac_client-0.2.3.dist-info/entry_points.txt +0 -4
|
@@ -1,963 +0,0 @@
|
|
|
1
|
-
# Step 11: Final Integration - Complete App
|
|
2
|
-
|
|
3
|
-
> ** Quick Tip:** Each step has two parts. **Part 1** shows you what to build. **Part 2** explains why it works. Want to just build? Skip all Part 2 sections!
|
|
4
|
-
|
|
5
|
-
Congratulations! In this final step, you'll see the complete, production-ready todo application with all features integrated!
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Part 1: The Complete App
|
|
10
|
-
|
|
11
|
-
### Complete `app.jac` File
|
|
12
|
-
|
|
13
|
-
Here's your entire application in one file. This is the exact app from the `full-stack-with-auth` example:
|
|
14
|
-
|
|
15
|
-
```jac
|
|
16
|
-
# Full Stack Todo App with Auth and React Router
|
|
17
|
-
cl import from react {
|
|
18
|
-
useState,
|
|
19
|
-
useEffect
|
|
20
|
-
}
|
|
21
|
-
cl import from "@jac-client/utils" {
|
|
22
|
-
Router,
|
|
23
|
-
Routes,
|
|
24
|
-
Route,
|
|
25
|
-
Link,
|
|
26
|
-
Navigate,
|
|
27
|
-
useNavigate,
|
|
28
|
-
jacSignup,
|
|
29
|
-
jacLogin,
|
|
30
|
-
jacLogout,
|
|
31
|
-
jacIsLoggedIn
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Backend - Todo Node
|
|
35
|
-
node Todo {
|
|
36
|
-
has text: str;
|
|
37
|
-
has done: bool = False;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
# Backend - Walkers
|
|
41
|
-
walker create_todo {
|
|
42
|
-
has text: str;
|
|
43
|
-
|
|
44
|
-
can create with `root entry {
|
|
45
|
-
new_todo = here ++> Todo(text=self.text);
|
|
46
|
-
report new_todo ;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
walker read_todos {
|
|
51
|
-
can read with `root entry {
|
|
52
|
-
visit [-->(`?Todo)];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
can report_todos with Todo entry {
|
|
56
|
-
report here ;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
walker toggle_todo {
|
|
61
|
-
can toggle with Todo entry {
|
|
62
|
-
here.done = not here.done;
|
|
63
|
-
report here ;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
# Frontend Components
|
|
68
|
-
cl {
|
|
69
|
-
# Navigation
|
|
70
|
-
def Navigation() -> any {
|
|
71
|
-
let isLoggedIn = jacIsLoggedIn();
|
|
72
|
-
let navigate = useNavigate();
|
|
73
|
-
|
|
74
|
-
def handleLogout(e: any) -> None {
|
|
75
|
-
e.preventDefault();
|
|
76
|
-
jacLogout();
|
|
77
|
-
navigate("/login");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if isLoggedIn {
|
|
81
|
-
return <nav
|
|
82
|
-
style={{
|
|
83
|
-
"padding": "12px 24px",
|
|
84
|
-
"background": "#3b82f6",
|
|
85
|
-
"color": "#ffffff",
|
|
86
|
-
"display": "flex",
|
|
87
|
-
"justifyContent": "space-between"
|
|
88
|
-
}}
|
|
89
|
-
>
|
|
90
|
-
<div
|
|
91
|
-
style={{"fontWeight": "600"}}
|
|
92
|
-
>
|
|
93
|
-
Todo App
|
|
94
|
-
</div>
|
|
95
|
-
<div
|
|
96
|
-
style={{"display": "flex", "gap": "16px"}}
|
|
97
|
-
>
|
|
98
|
-
<Link
|
|
99
|
-
to="/todos"
|
|
100
|
-
style={{"color": "#ffffff", "textDecoration": "none"}}
|
|
101
|
-
>
|
|
102
|
-
Todos
|
|
103
|
-
</Link>
|
|
104
|
-
<button
|
|
105
|
-
onClick={handleLogout}
|
|
106
|
-
style={{
|
|
107
|
-
"background": "none",
|
|
108
|
-
"color": "#ffffff",
|
|
109
|
-
"border": "1px solid #ffffff",
|
|
110
|
-
"padding": "2px 10px",
|
|
111
|
-
"borderRadius": "4px",
|
|
112
|
-
"cursor": "pointer"
|
|
113
|
-
}}
|
|
114
|
-
>
|
|
115
|
-
Logout
|
|
116
|
-
</button>
|
|
117
|
-
</div>
|
|
118
|
-
</nav>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return <nav
|
|
122
|
-
style={{
|
|
123
|
-
"padding": "12px 24px",
|
|
124
|
-
"background": "#3b82f6",
|
|
125
|
-
"color": "#ffffff",
|
|
126
|
-
"display": "flex",
|
|
127
|
-
"justifyContent": "space-between"
|
|
128
|
-
}}
|
|
129
|
-
>
|
|
130
|
-
<div
|
|
131
|
-
style={{"fontWeight": "600"}}
|
|
132
|
-
>
|
|
133
|
-
Todo App
|
|
134
|
-
</div>
|
|
135
|
-
<div
|
|
136
|
-
style={{"display": "flex", "gap": "16px"}}
|
|
137
|
-
>
|
|
138
|
-
<Link
|
|
139
|
-
to="/login"
|
|
140
|
-
style={{"color": "#ffffff", "textDecoration": "none"}}
|
|
141
|
-
>
|
|
142
|
-
Login
|
|
143
|
-
</Link>
|
|
144
|
-
<Link
|
|
145
|
-
to="/signup"
|
|
146
|
-
style={{"color": "#ffffff", "textDecoration": "none"}}
|
|
147
|
-
>
|
|
148
|
-
Sign Up
|
|
149
|
-
</Link>
|
|
150
|
-
</div>
|
|
151
|
-
</nav>;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
# Login Page
|
|
155
|
-
def LoginPage() -> any {
|
|
156
|
-
let [username, setUsername] = useState("");
|
|
157
|
-
let [password, setPassword] = useState("");
|
|
158
|
-
let [error, setError] = useState("");
|
|
159
|
-
let navigate = useNavigate();
|
|
160
|
-
|
|
161
|
-
async def handleLogin(e: any) -> None {
|
|
162
|
-
e.preventDefault();
|
|
163
|
-
setError("");
|
|
164
|
-
if not username or not password {
|
|
165
|
-
setError("Please fill in all fields");
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
success = await jacLogin(username, password);
|
|
169
|
-
if success {
|
|
170
|
-
navigate("/todos");
|
|
171
|
-
} else {
|
|
172
|
-
setError("Invalid credentials");
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
def handleUsernameChange(e: any) -> None {
|
|
177
|
-
setUsername(e.target.value);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
def handlePasswordChange(e: any) -> None {
|
|
181
|
-
setPassword(e.target.value);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
let errorDisplay = None;
|
|
185
|
-
if error {
|
|
186
|
-
errorDisplay = <div
|
|
187
|
-
style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "10px"}}
|
|
188
|
-
>
|
|
189
|
-
{error}
|
|
190
|
-
</div>;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return <div
|
|
194
|
-
style={{
|
|
195
|
-
"minHeight": "calc(100vh - 48px)",
|
|
196
|
-
"display": "flex",
|
|
197
|
-
"alignItems": "center",
|
|
198
|
-
"justifyContent": "center",
|
|
199
|
-
"background": "#f5f5f5"
|
|
200
|
-
}}
|
|
201
|
-
>
|
|
202
|
-
<div
|
|
203
|
-
style={{
|
|
204
|
-
"background": "#ffffff",
|
|
205
|
-
"padding": "30px",
|
|
206
|
-
"borderRadius": "8px",
|
|
207
|
-
"width": "280px",
|
|
208
|
-
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
<h2
|
|
212
|
-
style={{"marginBottom": "20px"}}
|
|
213
|
-
>
|
|
214
|
-
Login
|
|
215
|
-
</h2>
|
|
216
|
-
<form
|
|
217
|
-
onSubmit={handleLogin}
|
|
218
|
-
>
|
|
219
|
-
<input
|
|
220
|
-
type="text"
|
|
221
|
-
value={username}
|
|
222
|
-
onChange={handleUsernameChange}
|
|
223
|
-
placeholder="Username"
|
|
224
|
-
style={{
|
|
225
|
-
"width": "100%",
|
|
226
|
-
"padding": "8px",
|
|
227
|
-
"marginBottom": "10px",
|
|
228
|
-
"border": "1px solid #ddd",
|
|
229
|
-
"borderRadius": "4px",
|
|
230
|
-
"boxSizing": "border-box"
|
|
231
|
-
}}
|
|
232
|
-
/>
|
|
233
|
-
<input
|
|
234
|
-
type="password"
|
|
235
|
-
value={password}
|
|
236
|
-
onChange={handlePasswordChange}
|
|
237
|
-
placeholder="Password"
|
|
238
|
-
style={{
|
|
239
|
-
"width": "100%",
|
|
240
|
-
"padding": "8px",
|
|
241
|
-
"marginBottom": "10px",
|
|
242
|
-
"border": "1px solid #ddd",
|
|
243
|
-
"borderRadius": "4px",
|
|
244
|
-
"boxSizing": "border-box"
|
|
245
|
-
}}
|
|
246
|
-
/>
|
|
247
|
-
{errorDisplay}
|
|
248
|
-
<button
|
|
249
|
-
type="submit"
|
|
250
|
-
style={{
|
|
251
|
-
"width": "100%",
|
|
252
|
-
"padding": "8px",
|
|
253
|
-
"background": "#3b82f6",
|
|
254
|
-
"color": "#ffffff",
|
|
255
|
-
"border": "none",
|
|
256
|
-
"borderRadius": "4px",
|
|
257
|
-
"cursor": "pointer",
|
|
258
|
-
"fontWeight": "600"
|
|
259
|
-
}}
|
|
260
|
-
>
|
|
261
|
-
Login
|
|
262
|
-
</button>
|
|
263
|
-
</form>
|
|
264
|
-
<p
|
|
265
|
-
style={{
|
|
266
|
-
"textAlign": "center",
|
|
267
|
-
"marginTop": "12px",
|
|
268
|
-
"fontSize": "14px"
|
|
269
|
-
}}
|
|
270
|
-
>
|
|
271
|
-
Need an account?
|
|
272
|
-
<Link to="/signup">
|
|
273
|
-
Sign up
|
|
274
|
-
</Link>
|
|
275
|
-
</p>
|
|
276
|
-
</div>
|
|
277
|
-
</div>;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
# Signup Page
|
|
281
|
-
def SignupPage() -> any {
|
|
282
|
-
let [username, setUsername] = useState("");
|
|
283
|
-
let [password, setPassword] = useState("");
|
|
284
|
-
let [error, setError] = useState("");
|
|
285
|
-
let navigate = useNavigate();
|
|
286
|
-
|
|
287
|
-
async def handleSignup(e: any) -> None {
|
|
288
|
-
e.preventDefault();
|
|
289
|
-
setError("");
|
|
290
|
-
if not username or not password {
|
|
291
|
-
setError("Please fill in all fields");
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
result = await jacSignup(username, password);
|
|
295
|
-
if result["success"] {
|
|
296
|
-
navigate("/todos");
|
|
297
|
-
} else {
|
|
298
|
-
setError(result["error"] if result["error"] else "Signup failed");
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
def handleUsernameChange(e: any) -> None {
|
|
303
|
-
setUsername(e.target.value);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
def handlePasswordChange(e: any) -> None {
|
|
307
|
-
setPassword(e.target.value);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
let errorDisplay = None;
|
|
311
|
-
if error {
|
|
312
|
-
errorDisplay = <div
|
|
313
|
-
style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "10px"}}
|
|
314
|
-
>
|
|
315
|
-
{error}
|
|
316
|
-
</div>;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return <div
|
|
320
|
-
style={{
|
|
321
|
-
"minHeight": "calc(100vh - 48px)",
|
|
322
|
-
"display": "flex",
|
|
323
|
-
"alignItems": "center",
|
|
324
|
-
"justifyContent": "center",
|
|
325
|
-
"background": "#f5f5f5"
|
|
326
|
-
}}
|
|
327
|
-
>
|
|
328
|
-
<div
|
|
329
|
-
style={{
|
|
330
|
-
"background": "#ffffff",
|
|
331
|
-
"padding": "30px",
|
|
332
|
-
"borderRadius": "8px",
|
|
333
|
-
"width": "280px",
|
|
334
|
-
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
|
|
335
|
-
}}
|
|
336
|
-
>
|
|
337
|
-
<h2
|
|
338
|
-
style={{"marginBottom": "20px"}}
|
|
339
|
-
>
|
|
340
|
-
Sign Up
|
|
341
|
-
</h2>
|
|
342
|
-
<form
|
|
343
|
-
onSubmit={handleSignup}
|
|
344
|
-
>
|
|
345
|
-
<input
|
|
346
|
-
type="text"
|
|
347
|
-
value={username}
|
|
348
|
-
onChange={handleUsernameChange}
|
|
349
|
-
placeholder="Username"
|
|
350
|
-
style={{
|
|
351
|
-
"width": "100%",
|
|
352
|
-
"padding": "8px",
|
|
353
|
-
"marginBottom": "10px",
|
|
354
|
-
"border": "1px solid #ddd",
|
|
355
|
-
"borderRadius": "4px",
|
|
356
|
-
"boxSizing": "border-box"
|
|
357
|
-
}}
|
|
358
|
-
/>
|
|
359
|
-
<input
|
|
360
|
-
type="password"
|
|
361
|
-
value={password}
|
|
362
|
-
onChange={handlePasswordChange}
|
|
363
|
-
placeholder="Password"
|
|
364
|
-
style={{
|
|
365
|
-
"width": "100%",
|
|
366
|
-
"padding": "8px",
|
|
367
|
-
"marginBottom": "10px",
|
|
368
|
-
"border": "1px solid #ddd",
|
|
369
|
-
"borderRadius": "4px",
|
|
370
|
-
"boxSizing": "border-box"
|
|
371
|
-
}}
|
|
372
|
-
/>
|
|
373
|
-
{errorDisplay}
|
|
374
|
-
<button
|
|
375
|
-
type="submit"
|
|
376
|
-
style={{
|
|
377
|
-
"width": "100%",
|
|
378
|
-
"padding": "8px",
|
|
379
|
-
"background": "#3b82f6",
|
|
380
|
-
"color": "#ffffff",
|
|
381
|
-
"border": "none",
|
|
382
|
-
"borderRadius": "4px",
|
|
383
|
-
"cursor": "pointer",
|
|
384
|
-
"fontWeight": "600"
|
|
385
|
-
}}
|
|
386
|
-
>
|
|
387
|
-
Sign Up
|
|
388
|
-
</button>
|
|
389
|
-
</form>
|
|
390
|
-
<p
|
|
391
|
-
style={{
|
|
392
|
-
"textAlign": "center",
|
|
393
|
-
"marginTop": "12px",
|
|
394
|
-
"fontSize": "14px"
|
|
395
|
-
}}
|
|
396
|
-
>
|
|
397
|
-
Have an account?
|
|
398
|
-
<Link to="/login">
|
|
399
|
-
Login
|
|
400
|
-
</Link>
|
|
401
|
-
</p>
|
|
402
|
-
</div>
|
|
403
|
-
</div>;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
# Todos Page (Protected)
|
|
407
|
-
def TodosPage() -> any {
|
|
408
|
-
# Check if user is logged in, redirect if not
|
|
409
|
-
if not jacIsLoggedIn() {
|
|
410
|
-
return <Navigate to="/login" />;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
let [todos, setTodos] = useState([]);
|
|
414
|
-
let [input, setInput] = useState("");
|
|
415
|
-
let [filter, setFilter] = useState("all");
|
|
416
|
-
|
|
417
|
-
# Load todos on mount
|
|
418
|
-
useEffect(
|
|
419
|
-
lambda -> None{ async def loadTodos() -> None {
|
|
420
|
-
result = root spawn read_todos();
|
|
421
|
-
setTodos(result.reports if result.reports else []);
|
|
422
|
-
} loadTodos();} ,
|
|
423
|
-
[]
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
# Add todo
|
|
427
|
-
async def addTodo() -> None {
|
|
428
|
-
if not input.trim() {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
result = root spawn create_todo(text=input.trim());
|
|
432
|
-
setTodos(todos.concat([result.reports[0][0]]));
|
|
433
|
-
setInput("");
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
# Toggle todo
|
|
437
|
-
async def toggleTodo(id: any) -> None {
|
|
438
|
-
id spawn toggle_todo();
|
|
439
|
-
setTodos(
|
|
440
|
-
todos.map(
|
|
441
|
-
lambda todo: any -> any{
|
|
442
|
-
if todo._jac_id == id {
|
|
443
|
-
return {
|
|
444
|
-
"_jac_id": todo._jac_id,
|
|
445
|
-
"text": todo.text,
|
|
446
|
-
"done": not todo.done
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
return todo;
|
|
450
|
-
}
|
|
451
|
-
)
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
# Delete todo
|
|
456
|
-
async def deleteTodo(id: any) -> None {
|
|
457
|
-
#id spawn delete_todo();
|
|
458
|
-
setTodos(
|
|
459
|
-
todos.filter(lambda todo: any -> bool{ return todo._jac_id != id; } )
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
# Filter todos
|
|
464
|
-
def getFilteredTodos() -> list {
|
|
465
|
-
if filter == "active" {
|
|
466
|
-
return todos.filter(
|
|
467
|
-
lambda todo: any -> bool{ return not todo.done; }
|
|
468
|
-
);
|
|
469
|
-
} elif filter == "completed" {
|
|
470
|
-
return todos.filter(lambda todo: any -> bool{ return todo.done; } );
|
|
471
|
-
}
|
|
472
|
-
return todos;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
filteredTodos = getFilteredTodos();
|
|
476
|
-
activeCount = todos.filter(
|
|
477
|
-
lambda todo: any -> bool{ return not todo.done; }
|
|
478
|
-
).length;
|
|
479
|
-
|
|
480
|
-
return <div
|
|
481
|
-
style={{
|
|
482
|
-
"maxWidth": "600px",
|
|
483
|
-
"margin": "20px auto",
|
|
484
|
-
"padding": "20px",
|
|
485
|
-
"background": "#ffffff",
|
|
486
|
-
"borderRadius": "8px",
|
|
487
|
-
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
|
|
488
|
-
}}
|
|
489
|
-
>
|
|
490
|
-
<h1
|
|
491
|
-
style={{"marginBottom": "20px"}}
|
|
492
|
-
>
|
|
493
|
-
My Todos
|
|
494
|
-
</h1>
|
|
495
|
-
|
|
496
|
-
# Add todo input
|
|
497
|
-
<div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
|
|
498
|
-
<input
|
|
499
|
-
type="text"
|
|
500
|
-
value={input}
|
|
501
|
-
onChange={lambda e: any -> None { setInput(e.target.value); }}
|
|
502
|
-
onKeyPress={lambda e: any -> None {
|
|
503
|
-
if e.key == "Enter" {
|
|
504
|
-
addTodo();
|
|
505
|
-
}
|
|
506
|
-
}}
|
|
507
|
-
placeholder="What needs to be done?"
|
|
508
|
-
style={{
|
|
509
|
-
"flex": "1",
|
|
510
|
-
"padding": "8px",
|
|
511
|
-
"border": "1px solid #ddd",
|
|
512
|
-
"borderRadius": "4px"
|
|
513
|
-
}}
|
|
514
|
-
/>
|
|
515
|
-
<button
|
|
516
|
-
onClick={addTodo}
|
|
517
|
-
style={{
|
|
518
|
-
"padding": "8px 16px",
|
|
519
|
-
"background": "#3b82f6",
|
|
520
|
-
"color": "#ffffff",
|
|
521
|
-
"border": "none",
|
|
522
|
-
"borderRadius": "4px",
|
|
523
|
-
"cursor": "pointer",
|
|
524
|
-
"fontWeight": "600"
|
|
525
|
-
}}
|
|
526
|
-
>
|
|
527
|
-
Add
|
|
528
|
-
</button>
|
|
529
|
-
</div>
|
|
530
|
-
|
|
531
|
-
# Filter buttons
|
|
532
|
-
<div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
|
|
533
|
-
<button
|
|
534
|
-
onClick={lambda -> None{ setFilter("all");} }
|
|
535
|
-
style={{
|
|
536
|
-
"padding": "6px 12px",
|
|
537
|
-
"background": ("#3b82f6" if filter == "all" else "#e5e7eb"),
|
|
538
|
-
"color": ("#ffffff" if filter == "all" else "#000000"),
|
|
539
|
-
"border": "none",
|
|
540
|
-
"borderRadius": "4px",
|
|
541
|
-
"cursor": "pointer",
|
|
542
|
-
"fontSize": "14px"
|
|
543
|
-
}}
|
|
544
|
-
>
|
|
545
|
-
All
|
|
546
|
-
</button>
|
|
547
|
-
<button
|
|
548
|
-
onClick={lambda -> None{ setFilter("active");} }
|
|
549
|
-
style={{
|
|
550
|
-
"padding": "6px 12px",
|
|
551
|
-
"background": ("#3b82f6" if filter == "active" else "#e5e7eb"),
|
|
552
|
-
"color": ("#ffffff" if filter == "active" else "#000000"),
|
|
553
|
-
"border": "none",
|
|
554
|
-
"borderRadius": "4px",
|
|
555
|
-
"cursor": "pointer",
|
|
556
|
-
"fontSize": "14px"
|
|
557
|
-
}}
|
|
558
|
-
>
|
|
559
|
-
Active
|
|
560
|
-
</button>
|
|
561
|
-
<button
|
|
562
|
-
onClick={lambda -> None{ setFilter("completed");} }
|
|
563
|
-
style={{
|
|
564
|
-
"padding": "6px 12px",
|
|
565
|
-
"background": (
|
|
566
|
-
"#3b82f6" if filter == "completed" else "#e5e7eb"
|
|
567
|
-
),
|
|
568
|
-
"color": ("#ffffff" if filter == "completed" else "#000000"),
|
|
569
|
-
"border": "none",
|
|
570
|
-
"borderRadius": "4px",
|
|
571
|
-
"cursor": "pointer",
|
|
572
|
-
"fontSize": "14px"
|
|
573
|
-
}}
|
|
574
|
-
>
|
|
575
|
-
Completed
|
|
576
|
-
</button>
|
|
577
|
-
</div>
|
|
578
|
-
|
|
579
|
-
# Todo list
|
|
580
|
-
<div>
|
|
581
|
-
{(<div style={{"padding": "20px", "textAlign": "center", "color": "#999"}}>
|
|
582
|
-
No todos yet. Add one above!
|
|
583
|
-
</div>) if filteredTodos.length == 0 else (
|
|
584
|
-
filteredTodos.map(lambda todo: any -> any {
|
|
585
|
-
return <div
|
|
586
|
-
key={todo._jac_id}
|
|
587
|
-
style={{
|
|
588
|
-
"display": "flex",
|
|
589
|
-
"alignItems": "center",
|
|
590
|
-
"gap": "10px",
|
|
591
|
-
"padding": "10px",
|
|
592
|
-
"borderBottom": "1px solid #e5e7eb"
|
|
593
|
-
}}
|
|
594
|
-
>
|
|
595
|
-
<input
|
|
596
|
-
type="checkbox"
|
|
597
|
-
checked={todo.done}
|
|
598
|
-
onChange={lambda -> None{ toggleTodo(todo._jac_id);} }
|
|
599
|
-
style={{"cursor": "pointer"}}
|
|
600
|
-
/>
|
|
601
|
-
<span
|
|
602
|
-
style={{
|
|
603
|
-
"flex": "1",
|
|
604
|
-
"textDecoration": (
|
|
605
|
-
"line-through" if todo.done else "none"
|
|
606
|
-
),
|
|
607
|
-
"color": ("#999" if todo.done else "#000")
|
|
608
|
-
}}
|
|
609
|
-
>
|
|
610
|
-
{todo.text}
|
|
611
|
-
</span>
|
|
612
|
-
<button
|
|
613
|
-
onClick={lambda -> None{ deleteTodo(todo._jac_id);} }
|
|
614
|
-
style={{
|
|
615
|
-
"padding": "4px 8px",
|
|
616
|
-
"background": "#ef4444",
|
|
617
|
-
"color": "#ffffff",
|
|
618
|
-
"border": "none",
|
|
619
|
-
"borderRadius": "4px",
|
|
620
|
-
"cursor": "pointer",
|
|
621
|
-
"fontSize": "12px"
|
|
622
|
-
}}
|
|
623
|
-
>
|
|
624
|
-
Delete
|
|
625
|
-
</button>
|
|
626
|
-
</div>;
|
|
627
|
-
})
|
|
628
|
-
)}
|
|
629
|
-
</div>
|
|
630
|
-
|
|
631
|
-
# Stats
|
|
632
|
-
{(
|
|
633
|
-
<div
|
|
634
|
-
style={{
|
|
635
|
-
"marginTop": "16px",
|
|
636
|
-
"padding": "10px",
|
|
637
|
-
"background": "#f9fafb",
|
|
638
|
-
"borderRadius": "4px",
|
|
639
|
-
"fontSize": "14px",
|
|
640
|
-
"color": "#666"
|
|
641
|
-
}}
|
|
642
|
-
>
|
|
643
|
-
{activeCount} {"item" if activeCount == 1 else "items"} left
|
|
644
|
-
</div>
|
|
645
|
-
)
|
|
646
|
-
if todos.length > 0
|
|
647
|
-
else None}
|
|
648
|
-
</div>;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
# Home/Landing Page - auto-redirect
|
|
652
|
-
def HomePage() -> any {
|
|
653
|
-
if jacIsLoggedIn() {
|
|
654
|
-
return <Navigate to="/todos" />;
|
|
655
|
-
}
|
|
656
|
-
return <Navigate to="/login" />;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
# Main App with React Router
|
|
660
|
-
def app() -> any {
|
|
661
|
-
return <Router>
|
|
662
|
-
<div
|
|
663
|
-
style={{"fontFamily": "system-ui, sans-serif"}}
|
|
664
|
-
>
|
|
665
|
-
<Navigation />
|
|
666
|
-
<Routes>
|
|
667
|
-
<Route
|
|
668
|
-
path="/"
|
|
669
|
-
element={<HomePage />}
|
|
670
|
-
/>
|
|
671
|
-
<Route
|
|
672
|
-
path="/login"
|
|
673
|
-
element={<LoginPage />}
|
|
674
|
-
/>
|
|
675
|
-
<Route
|
|
676
|
-
path="/signup"
|
|
677
|
-
element={<SignupPage />}
|
|
678
|
-
/>
|
|
679
|
-
<Route
|
|
680
|
-
path="/todos"
|
|
681
|
-
element={<TodosPage />}
|
|
682
|
-
/>
|
|
683
|
-
</Routes>
|
|
684
|
-
</div>
|
|
685
|
-
</Router>;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### Running the App
|
|
691
|
-
|
|
692
|
-
1. **Save the code** to `app.jac`
|
|
693
|
-
|
|
694
|
-
2. **Start the server:**
|
|
695
|
-
```bash
|
|
696
|
-
jac serve app.jac
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
3. **Open in browser:**
|
|
700
|
-
```
|
|
701
|
-
http://localhost:8000/page/app
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
4. **Test it out:**
|
|
705
|
-
- Create an account (signup)
|
|
706
|
-
- Login
|
|
707
|
-
- Add some todos
|
|
708
|
-
- Toggle them complete/incomplete
|
|
709
|
-
- Filter (All/Active/Completed)
|
|
710
|
-
- Delete todos
|
|
711
|
-
- Logout and login again - your todos persist!
|
|
712
|
-
|
|
713
|
-
---
|
|
714
|
-
|
|
715
|
-
** You did it!** You've built a complete full-stack app. The rest of this page explains what you built and what to do next.
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## Part 2: What You Built
|
|
720
|
-
|
|
721
|
-
### Features Checklist
|
|
722
|
-
|
|
723
|
-
**Authentication:**
|
|
724
|
-
- User signup
|
|
725
|
-
- User login
|
|
726
|
-
- Logout
|
|
727
|
-
- Session persistence
|
|
728
|
-
- Protected routes
|
|
729
|
-
|
|
730
|
-
**Todo Management:**
|
|
731
|
-
- Create todos
|
|
732
|
-
- Mark as complete/incomplete
|
|
733
|
-
- Delete todos
|
|
734
|
-
- Filter by status (all/active/completed)
|
|
735
|
-
- Item counter
|
|
736
|
-
- Empty state handling
|
|
737
|
-
|
|
738
|
-
**UI/UX:**
|
|
739
|
-
- Responsive design
|
|
740
|
-
- Modern styling
|
|
741
|
-
- Form validation
|
|
742
|
-
- Error handling
|
|
743
|
-
- Loading states
|
|
744
|
-
- Smooth navigation
|
|
745
|
-
|
|
746
|
-
**Backend:**
|
|
747
|
-
- Data persistence with walkers
|
|
748
|
-
- User isolation (each user sees only their data)
|
|
749
|
-
- Graph-based data structure
|
|
750
|
-
- Automatic API endpoints
|
|
751
|
-
|
|
752
|
-
### Technology Stack
|
|
753
|
-
|
|
754
|
-
**Frontend:**
|
|
755
|
-
- React (via Jac's `cl` blocks)
|
|
756
|
-
- React Router (for navigation)
|
|
757
|
-
- Inline CSS styling
|
|
758
|
-
- JSX syntax
|
|
759
|
-
|
|
760
|
-
**Backend:**
|
|
761
|
-
- Jac walkers (backend functions)
|
|
762
|
-
- Jac nodes (data structures)
|
|
763
|
-
- Graph database (automatic)
|
|
764
|
-
- Built-in authentication
|
|
765
|
-
|
|
766
|
-
**Architecture:**
|
|
767
|
-
- Single-page application (SPA)
|
|
768
|
-
- Client-side routing
|
|
769
|
-
- RESTful-like walker calls
|
|
770
|
-
- Full-stack in one language
|
|
771
|
-
|
|
772
|
-
### File Structure
|
|
773
|
-
|
|
774
|
-
```
|
|
775
|
-
Your entire app:
|
|
776
|
-
├── app.jac (735 lines)
|
|
777
|
-
├── Backend (nodes + walkers)
|
|
778
|
-
├── Frontend (React components)
|
|
779
|
-
└── Routes (navigation)
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
That's it! Just one file!
|
|
783
|
-
|
|
784
|
-
### Code Organization
|
|
785
|
-
|
|
786
|
-
```
|
|
787
|
-
app.jac
|
|
788
|
-
├── Backend Section
|
|
789
|
-
│ ├── node Todo (data model)
|
|
790
|
-
│ └── Walkers (create, read, toggle, delete)
|
|
791
|
-
│
|
|
792
|
-
└── Frontend Section (cl block)
|
|
793
|
-
├── Navigation component
|
|
794
|
-
├── LoginPage component
|
|
795
|
-
├── SignupPage component
|
|
796
|
-
├── TodosPage component
|
|
797
|
-
├── HomePage component (redirects)
|
|
798
|
-
└── app function (router setup)
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
---
|
|
802
|
-
|
|
803
|
-
## What's Next?
|
|
804
|
-
|
|
805
|
-
You've completed the tutorial! Here are some ideas to continue learning:
|
|
806
|
-
|
|
807
|
-
### 1. Enhance Your App
|
|
808
|
-
|
|
809
|
-
**Easy additions:**
|
|
810
|
-
- Edit todo text
|
|
811
|
-
- Add due dates
|
|
812
|
-
- Priority levels (high/medium/low)
|
|
813
|
-
- Todo categories/tags
|
|
814
|
-
- Search functionality
|
|
815
|
-
|
|
816
|
-
**Medium difficulty:**
|
|
817
|
-
- Drag-and-drop reordering
|
|
818
|
-
- Dark mode toggle
|
|
819
|
-
- Keyboard shortcuts
|
|
820
|
-
- Undo/redo
|
|
821
|
-
- Export/import todos
|
|
822
|
-
|
|
823
|
-
**Advanced features:**
|
|
824
|
-
- Real-time collaboration
|
|
825
|
-
- Recurring todos
|
|
826
|
-
- Notifications
|
|
827
|
-
- Attach files to todos
|
|
828
|
-
- Share lists with others
|
|
829
|
-
|
|
830
|
-
### 2. Improve the UI
|
|
831
|
-
|
|
832
|
-
**Styling:**
|
|
833
|
-
- Add CSS animations
|
|
834
|
-
- Use a CSS framework (Tailwind CSS)
|
|
835
|
-
- Better mobile responsiveness
|
|
836
|
-
- Custom color themes
|
|
837
|
-
- Icons library (React Icons)
|
|
838
|
-
|
|
839
|
-
**UX improvements:**
|
|
840
|
-
- Smooth transitions
|
|
841
|
-
- Better loading states
|
|
842
|
-
- Toast notifications
|
|
843
|
-
- Confirmation dialogs
|
|
844
|
-
- Keyboard navigation
|
|
845
|
-
|
|
846
|
-
### 3. Deploy Your App
|
|
847
|
-
|
|
848
|
-
**Deployment options:**
|
|
849
|
-
- Jac Cloud (easiest)
|
|
850
|
-
- Vercel
|
|
851
|
-
- Netlify
|
|
852
|
-
- Digital Ocean
|
|
853
|
-
- AWS
|
|
854
|
-
|
|
855
|
-
**Steps:**
|
|
856
|
-
```bash
|
|
857
|
-
# Install Jac Cloud
|
|
858
|
-
pip install jac-cloud
|
|
859
|
-
|
|
860
|
-
# Deploy
|
|
861
|
-
jac cloud deploy app.jac
|
|
862
|
-
|
|
863
|
-
# Your app is now live!
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
### 4. Learn Advanced Jac Features
|
|
867
|
-
|
|
868
|
-
**Explore:**
|
|
869
|
-
- AI features with byLLM
|
|
870
|
-
- Complex graph structures
|
|
871
|
-
- Advanced walker patterns
|
|
872
|
-
- Multi-file organization
|
|
873
|
-
- Testing strategies
|
|
874
|
-
- Performance optimization
|
|
875
|
-
|
|
876
|
-
### 5. Build Something New
|
|
877
|
-
|
|
878
|
-
Apply what you learned:
|
|
879
|
-
- Blog platform
|
|
880
|
-
- E-commerce store
|
|
881
|
-
- Social media app
|
|
882
|
-
- Project management tool
|
|
883
|
-
- Chat application
|
|
884
|
-
- Portfolio website
|
|
885
|
-
|
|
886
|
-
---
|
|
887
|
-
|
|
888
|
-
## Resources
|
|
889
|
-
|
|
890
|
-
**Official Documentation:**
|
|
891
|
-
- [Jac Documentation](https://www.jac-lang.org)
|
|
892
|
-
- [Jac Examples](https://github.com/Jaseci-Labs/jaclang)
|
|
893
|
-
- [React Docs](https://react.dev) (underlying framework)
|
|
894
|
-
|
|
895
|
-
**Community:**
|
|
896
|
-
- Jac Discord/Forum
|
|
897
|
-
- GitHub Issues
|
|
898
|
-
- Stack Overflow (tag: jac-lang)
|
|
899
|
-
|
|
900
|
-
**Tutorials:**
|
|
901
|
-
- Jac AI Features
|
|
902
|
-
- Advanced Graph Patterns
|
|
903
|
-
- Deployment Guides
|
|
904
|
-
- Best Practices
|
|
905
|
-
|
|
906
|
-
---
|
|
907
|
-
|
|
908
|
-
## What You Learned
|
|
909
|
-
|
|
910
|
-
Looking back at all 11 steps:
|
|
911
|
-
|
|
912
|
-
1. Project setup and structure
|
|
913
|
-
2. Components and props
|
|
914
|
-
3. Styling with inline CSS
|
|
915
|
-
4. Building complex UIs
|
|
916
|
-
5. State management with useState
|
|
917
|
-
6. Event handlers
|
|
918
|
-
7. Side effects with useEffect
|
|
919
|
-
8. Backend with walkers and nodes
|
|
920
|
-
9. User authentication
|
|
921
|
-
10. Client-side routing
|
|
922
|
-
11. Complete full-stack integration
|
|
923
|
-
|
|
924
|
-
**Key concepts mastered:**
|
|
925
|
-
- Full-stack development in one language
|
|
926
|
-
- React component patterns
|
|
927
|
-
- State management
|
|
928
|
-
- Graph-based data storage
|
|
929
|
-
- Authentication and authorization
|
|
930
|
-
- Client-side routing
|
|
931
|
-
- Async/await patterns
|
|
932
|
-
- Form handling
|
|
933
|
-
- Error handling
|
|
934
|
-
|
|
935
|
-
---
|
|
936
|
-
|
|
937
|
-
## Congratulations!
|
|
938
|
-
|
|
939
|
-
You built a **complete, production-ready full-stack application** from scratch!
|
|
940
|
-
|
|
941
|
-
**What makes this special:**
|
|
942
|
-
- **735 lines** of code (compared to 2000+ in traditional stacks)
|
|
943
|
-
- **One language** (compared to 3-4: JavaScript, Python, SQL, HTML/CSS)
|
|
944
|
-
- **One file** (compared to dozens of files)
|
|
945
|
-
- **Zero configuration** (no webpack, babel, etc.)
|
|
946
|
-
- **Built-in auth** (no OAuth setup needed)
|
|
947
|
-
- **Automatic backend** (no Express/Flask setup)
|
|
948
|
-
|
|
949
|
-
You're now ready to build amazing full-stack applications with Jac!
|
|
950
|
-
|
|
951
|
-
---
|
|
952
|
-
|
|
953
|
-
## Share Your Success!
|
|
954
|
-
|
|
955
|
-
Built something cool? Share it:
|
|
956
|
-
- Tag #JacLang on social media
|
|
957
|
-
- Contribute to Jac examples
|
|
958
|
-
- Write a blog post
|
|
959
|
-
- Help others learn
|
|
960
|
-
|
|
961
|
-
**Thank you for completing this tutorial!**
|
|
962
|
-
|
|
963
|
-
Happy coding with Jac!
|