jac-client 0.1.0__py3-none-any.whl → 0.2.1__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/docs/README.md +232 -172
- jac_client/docs/advanced-state.md +1012 -452
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/intro.md +90 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +538 -46
- jac_client/docs/lifecycle-hooks.md +517 -297
- jac_client/docs/routing.md +487 -357
- jac_client/docs/styling/intro.md +250 -0
- jac_client/docs/styling/js-styling.md +373 -0
- jac_client/docs/styling/material-ui.md +346 -0
- jac_client/docs/styling/pure-css.md +305 -0
- jac_client/docs/styling/sass.md +409 -0
- jac_client/docs/styling/styled-components.md +401 -0
- jac_client/docs/styling/tailwind.md +303 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +43 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +27 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +57 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +63 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +82 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +63 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +112 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +63 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +158 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +66 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +91 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +95 -179
- jac_client/plugin/client.py +111 -2
- jac_client/plugin/client_runtime.jac +183 -890
- jac_client/plugin/vite_client_bundle.py +185 -205
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
- jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
- jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
- jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
- jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +345 -151
- jac_client/tests/test_create_jac_app.py +41 -45
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client/examples/little-x/package-lock.json +0 -2840
- jac_client/examples/todo-app/README.md +0 -82
- jac_client/examples/todo-app/app.jac +0 -683
- jac_client/examples/todo-app/package-lock.json +0 -999
- jac_client/examples/todo-app/package.json +0 -22
- jac_client-0.1.0.dist-info/RECORD +0 -33
- /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# Step 10: Adding Routing
|
|
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
|
+
In this step, you'll add multiple pages to your app so users can navigate between login, signup, and todos!
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🏗️ Part 1: Building the App
|
|
10
|
+
|
|
11
|
+
### Step 10.1: Import Routing Components
|
|
12
|
+
|
|
13
|
+
Update your imports:
|
|
14
|
+
|
|
15
|
+
```jac
|
|
16
|
+
cl import from react {useState, useEffect}
|
|
17
|
+
cl import from "@jac-client/utils" {
|
|
18
|
+
Router,
|
|
19
|
+
Routes,
|
|
20
|
+
Route,
|
|
21
|
+
Link,
|
|
22
|
+
Navigate,
|
|
23
|
+
useNavigate,
|
|
24
|
+
jacSignup,
|
|
25
|
+
jacLogin,
|
|
26
|
+
jacLogout,
|
|
27
|
+
jacIsLoggedIn
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cl {
|
|
31
|
+
# ... your components
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 10.2: Add Navigation Links to Login Page
|
|
36
|
+
|
|
37
|
+
Update your `LoginPage` to include a link to signup:
|
|
38
|
+
|
|
39
|
+
```jac
|
|
40
|
+
# In LoginPage, replace the last <p> tag with:
|
|
41
|
+
<p style={{
|
|
42
|
+
"textAlign": "center",
|
|
43
|
+
"marginTop": "12px",
|
|
44
|
+
"fontSize": "14px"
|
|
45
|
+
}}>
|
|
46
|
+
Need an account? <Link to="/signup">Sign up</Link>
|
|
47
|
+
</p>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Step 10.3: Add Navigation Links to Signup Page
|
|
51
|
+
|
|
52
|
+
Update your `SignupPage`:
|
|
53
|
+
|
|
54
|
+
```jac
|
|
55
|
+
# In SignupPage, replace the last <p> tag with:
|
|
56
|
+
<p style={{
|
|
57
|
+
"textAlign": "center",
|
|
58
|
+
"marginTop": "12px",
|
|
59
|
+
"fontSize": "14px"
|
|
60
|
+
}}>
|
|
61
|
+
Have an account? <Link to="/login">Login</Link>
|
|
62
|
+
</p>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 10.4: Add Navigation After Login/Signup
|
|
66
|
+
|
|
67
|
+
Update the login handler to navigate to todos after successful login:
|
|
68
|
+
|
|
69
|
+
```jac
|
|
70
|
+
# In LoginPage
|
|
71
|
+
async def handleLogin(e: any) -> None {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
setError("");
|
|
74
|
+
|
|
75
|
+
if not username or not password {
|
|
76
|
+
setError("Please fill in all fields");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
success = await jacLogin(username, password);
|
|
81
|
+
if success {
|
|
82
|
+
window.location.href = "/page/app#/todos"; # Navigate to todos
|
|
83
|
+
} else {
|
|
84
|
+
setError("Invalid credentials");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Update the signup handler:
|
|
90
|
+
|
|
91
|
+
```jac
|
|
92
|
+
# In SignupPage
|
|
93
|
+
async def handleSignup(e: any) -> None {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
setError("");
|
|
96
|
+
|
|
97
|
+
if not username or not password {
|
|
98
|
+
setError("Please fill in all fields");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
result = await jacSignup(username, password);
|
|
103
|
+
if result["success"] {
|
|
104
|
+
window.location.href = "/page/app#/todos"; # Navigate to todos
|
|
105
|
+
} else {
|
|
106
|
+
setError(result["error"] if result["error"] else "Signup failed");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Step 10.5: Create a Navigation Component
|
|
112
|
+
|
|
113
|
+
Add this component to show navigation at the top:
|
|
114
|
+
|
|
115
|
+
```jac
|
|
116
|
+
def Navigation() -> any {
|
|
117
|
+
let isLoggedIn = jacIsLoggedIn();
|
|
118
|
+
|
|
119
|
+
if isLoggedIn {
|
|
120
|
+
return <nav style={{
|
|
121
|
+
"padding": "12px 24px",
|
|
122
|
+
"background": "#3b82f6",
|
|
123
|
+
"color": "#ffffff",
|
|
124
|
+
"display": "flex",
|
|
125
|
+
"justifyContent": "space-between"
|
|
126
|
+
}}>
|
|
127
|
+
<div style={{"fontWeight": "600"}}>Todo App</div>
|
|
128
|
+
<div style={{"display": "flex", "gap": "16px"}}>
|
|
129
|
+
<Link to="/todos" style={{
|
|
130
|
+
"color": "#ffffff",
|
|
131
|
+
"textDecoration": "none"
|
|
132
|
+
}}>
|
|
133
|
+
Todos
|
|
134
|
+
</Link>
|
|
135
|
+
<button
|
|
136
|
+
onClick={lambda e: any -> None {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
jacLogout();
|
|
139
|
+
window.location.href = "/page/app#/login";
|
|
140
|
+
}}
|
|
141
|
+
style={{
|
|
142
|
+
"background": "none",
|
|
143
|
+
"color": "#ffffff",
|
|
144
|
+
"border": "1px solid #ffffff",
|
|
145
|
+
"padding": "2px 10px",
|
|
146
|
+
"borderRadius": "4px",
|
|
147
|
+
"cursor": "pointer"
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
Logout
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</nav>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return <nav style={{
|
|
157
|
+
"padding": "12px 24px",
|
|
158
|
+
"background": "#3b82f6",
|
|
159
|
+
"color": "#ffffff",
|
|
160
|
+
"display": "flex",
|
|
161
|
+
"justifyContent": "space-between"
|
|
162
|
+
}}>
|
|
163
|
+
<div style={{"fontWeight": "600"}}>Todo App</div>
|
|
164
|
+
<div style={{"display": "flex", "gap": "16px"}}>
|
|
165
|
+
<Link to="/login" style={{
|
|
166
|
+
"color": "#ffffff",
|
|
167
|
+
"textDecoration": "none"
|
|
168
|
+
}}>
|
|
169
|
+
Login
|
|
170
|
+
</Link>
|
|
171
|
+
<Link to="/signup" style={{
|
|
172
|
+
"color": "#ffffff",
|
|
173
|
+
"textDecoration": "none"
|
|
174
|
+
}}>
|
|
175
|
+
Sign Up
|
|
176
|
+
</Link>
|
|
177
|
+
</div>
|
|
178
|
+
</nav>;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Step 10.6: Update TodosPage to Redirect if Not Logged In
|
|
183
|
+
|
|
184
|
+
Update your `TodosPage`:
|
|
185
|
+
|
|
186
|
+
```jac
|
|
187
|
+
def TodosPage() -> any {
|
|
188
|
+
# Redirect to login if not logged in
|
|
189
|
+
if not jacIsLoggedIn() {
|
|
190
|
+
return <Navigate to="/login" />;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# ... all your existing todo code
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Step 10.7: Create a Home Page
|
|
198
|
+
|
|
199
|
+
Add this simple home page:
|
|
200
|
+
|
|
201
|
+
```jac
|
|
202
|
+
def HomePage() -> any {
|
|
203
|
+
if jacIsLoggedIn() {
|
|
204
|
+
return <Navigate to="/todos" />;
|
|
205
|
+
}
|
|
206
|
+
return <Navigate to="/login" />;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Step 10.8: Set Up Router in app()
|
|
211
|
+
|
|
212
|
+
Now, update your `app` function to use the router:
|
|
213
|
+
|
|
214
|
+
```jac
|
|
215
|
+
def app() -> any {
|
|
216
|
+
return <Router>
|
|
217
|
+
<div style={{"fontFamily": "system-ui, sans-serif"}}>
|
|
218
|
+
<Navigation />
|
|
219
|
+
<Routes>
|
|
220
|
+
<Route path="/" element={<HomePage />} />
|
|
221
|
+
<Route path="/login" element={<LoginPage />} />
|
|
222
|
+
<Route path="/signup" element={<SignupPage />} />
|
|
223
|
+
<Route path="/todos" element={<TodosPage />} />
|
|
224
|
+
</Routes>
|
|
225
|
+
</div>
|
|
226
|
+
</Router>;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Try it!**
|
|
231
|
+
|
|
232
|
+
1. Go to `http://localhost:8000/page/app` - you'll be redirected to login
|
|
233
|
+
2. Click "Sign up" - goes to signup page
|
|
234
|
+
3. Create an account - redirects to todos
|
|
235
|
+
4. Click "Logout" - redirects to login
|
|
236
|
+
5. Try manually going to `/page/app#/todos` while logged out - redirects to login!
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
**⏭️ Want to skip the theory?** Jump to [Step 11: Final Integration](./step-11-final.md)
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 💡 Part 2: Understanding the Concepts
|
|
245
|
+
|
|
246
|
+
### What is Routing?
|
|
247
|
+
|
|
248
|
+
Routing = Different URLs show different pages
|
|
249
|
+
|
|
250
|
+
**Traditional websites:**
|
|
251
|
+
- `/login` → Server sends login.html
|
|
252
|
+
- `/todos` → Server sends todos.html
|
|
253
|
+
- Every click = full page reload
|
|
254
|
+
|
|
255
|
+
**Single-Page Apps (SPAs):**
|
|
256
|
+
- `/login` → JavaScript shows login component
|
|
257
|
+
- `/todos` → JavaScript shows todos component
|
|
258
|
+
- No page reload = instant and smooth!
|
|
259
|
+
|
|
260
|
+
### Router Components
|
|
261
|
+
|
|
262
|
+
```jac
|
|
263
|
+
<Router> # Container for all routing
|
|
264
|
+
<Routes> # Groups route definitions
|
|
265
|
+
<Route /> # Defines one route
|
|
266
|
+
</Routes>
|
|
267
|
+
</Router>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Think of it like:**
|
|
271
|
+
```python
|
|
272
|
+
# Python routing (Flask)
|
|
273
|
+
@app.route("/login")
|
|
274
|
+
def login():
|
|
275
|
+
return render_template("login.html")
|
|
276
|
+
|
|
277
|
+
# Jac routing
|
|
278
|
+
<Route path="/login" element={<LoginPage />} />
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### The Router Setup
|
|
282
|
+
|
|
283
|
+
```jac
|
|
284
|
+
<Router>
|
|
285
|
+
<div>
|
|
286
|
+
<Navigation /> # Shows on all pages
|
|
287
|
+
<Routes>
|
|
288
|
+
<Route path="/" element={<HomePage />} />
|
|
289
|
+
<Route path="/login" element={<LoginPage />} />
|
|
290
|
+
</Routes>
|
|
291
|
+
</div>
|
|
292
|
+
</Router>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**What happens:**
|
|
296
|
+
- `Router` manages the current URL
|
|
297
|
+
- `Navigation` is always visible
|
|
298
|
+
- `Routes` shows ONE matching route
|
|
299
|
+
- Components render based on URL
|
|
300
|
+
|
|
301
|
+
### Route Definitions
|
|
302
|
+
|
|
303
|
+
```jac
|
|
304
|
+
<Route path="/login" element={<LoginPage />} />
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Breakdown:**
|
|
308
|
+
- `path="/login"` - URL pattern to match
|
|
309
|
+
- `element={<LoginPage />}` - Component to show (must be JSX!)
|
|
310
|
+
|
|
311
|
+
**Important:** Use `element={<Component />}` not `element={Component}`
|
|
312
|
+
|
|
313
|
+
### Link Component
|
|
314
|
+
|
|
315
|
+
```jac
|
|
316
|
+
<Link to="/login">Go to Login</Link>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**vs regular anchor tag:**
|
|
320
|
+
|
|
321
|
+
```jac
|
|
322
|
+
# ❌ Wrong - causes page reload
|
|
323
|
+
<a href="/login">Go to Login</a>
|
|
324
|
+
|
|
325
|
+
# ✅ Correct - no page reload
|
|
326
|
+
<Link to="/login">Go to Login</Link>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`Link` updates the URL without reloading the page!
|
|
330
|
+
|
|
331
|
+
### Navigate Component
|
|
332
|
+
|
|
333
|
+
```jac
|
|
334
|
+
def TodosPage() -> any {
|
|
335
|
+
if not jacIsLoggedIn() {
|
|
336
|
+
return <Navigate to="/login" />;
|
|
337
|
+
}
|
|
338
|
+
// Show todos
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Purpose:** Redirect programmatically
|
|
343
|
+
|
|
344
|
+
**When to use:**
|
|
345
|
+
- Protecting routes (if not logged in, redirect)
|
|
346
|
+
- After form submission
|
|
347
|
+
- Conditional redirects
|
|
348
|
+
|
|
349
|
+
### useNavigate Hook
|
|
350
|
+
|
|
351
|
+
```jac
|
|
352
|
+
let navigate = useNavigate();
|
|
353
|
+
|
|
354
|
+
async def handleLogin() -> None {
|
|
355
|
+
let success = await jacLogin(username, password);
|
|
356
|
+
if success {
|
|
357
|
+
navigate("/todos"); # Navigate programmatically
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Use when:** You need to navigate from JavaScript code (not from JSX)
|
|
363
|
+
|
|
364
|
+
### URL Structure in Jac
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
http://localhost:8000/page/app#/todos
|
|
368
|
+
^^^^^^^ ^^^^^^
|
|
369
|
+
Jac app Route
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
- `/page/app` - Your Jac app
|
|
373
|
+
- `#/todos` - Client-side route (hash routing)
|
|
374
|
+
|
|
375
|
+
### Protected Routes Pattern
|
|
376
|
+
|
|
377
|
+
```jac
|
|
378
|
+
def ProtectedPage() -> any {
|
|
379
|
+
if not jacIsLoggedIn() {
|
|
380
|
+
return <Navigate to="/login" />;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
# User is authenticated, show page
|
|
384
|
+
return <div>Protected content</div>;
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
This pattern:
|
|
389
|
+
1. Checks authentication
|
|
390
|
+
2. Redirects if not logged in
|
|
391
|
+
3. Shows content if logged in
|
|
392
|
+
|
|
393
|
+
### Conditional Navigation
|
|
394
|
+
|
|
395
|
+
```jac
|
|
396
|
+
def Navigation() -> any {
|
|
397
|
+
let isLoggedIn = jacIsLoggedIn();
|
|
398
|
+
|
|
399
|
+
if isLoggedIn {
|
|
400
|
+
return <nav>
|
|
401
|
+
<Link to="/todos">Todos</Link>
|
|
402
|
+
<button onClick={logout}>Logout</button>
|
|
403
|
+
</nav>;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return <nav>
|
|
407
|
+
<Link to="/login">Login</Link>
|
|
408
|
+
<Link to="/signup">Sign Up</Link>
|
|
409
|
+
</nav>;
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Shows different links based on login status!
|
|
414
|
+
|
|
415
|
+
### Route Matching
|
|
416
|
+
|
|
417
|
+
Routes are matched **in order**:
|
|
418
|
+
|
|
419
|
+
```jac
|
|
420
|
+
<Routes>
|
|
421
|
+
<Route path="/" element={<HomePage />} />
|
|
422
|
+
<Route path="/login" element={<LoginPage />} />
|
|
423
|
+
<Route path="/todos" element={<TodosPage />} />
|
|
424
|
+
</Routes>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
- URL `/` → Shows `HomePage`
|
|
428
|
+
- URL `/login` → Shows `LoginPage`
|
|
429
|
+
- URL `/todos` → Shows `TodosPage`
|
|
430
|
+
- URL `/other` → Shows nothing (we could add a 404 page)
|
|
431
|
+
|
|
432
|
+
### Common Routing Patterns
|
|
433
|
+
|
|
434
|
+
**Pattern 1: Auto-redirect based on auth**
|
|
435
|
+
|
|
436
|
+
```jac
|
|
437
|
+
def HomePage() -> any {
|
|
438
|
+
if jacIsLoggedIn() {
|
|
439
|
+
return <Navigate to="/todos" />;
|
|
440
|
+
}
|
|
441
|
+
return <Navigate to="/login" />;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Pattern 2: Logout and redirect**
|
|
446
|
+
|
|
447
|
+
```jac
|
|
448
|
+
def handleLogout() -> None {
|
|
449
|
+
jacLogout();
|
|
450
|
+
navigate("/login");
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Pattern 3: Conditional links**
|
|
455
|
+
|
|
456
|
+
```jac
|
|
457
|
+
{(
|
|
458
|
+
<Link to="/dashboard">Dashboard</Link>
|
|
459
|
+
) if jacIsLoggedIn() else (
|
|
460
|
+
<Link to="/login">Login</Link>
|
|
461
|
+
)}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## ✅ What You've Learned
|
|
467
|
+
|
|
468
|
+
- ✅ What client-side routing is
|
|
469
|
+
- ✅ Setting up Router, Routes, and Route
|
|
470
|
+
- ✅ Creating navigation with Link
|
|
471
|
+
- ✅ Programmatic navigation with Navigate
|
|
472
|
+
- ✅ Protected routes with authentication checks
|
|
473
|
+
- ✅ Conditional navigation based on auth status
|
|
474
|
+
- ✅ Common routing patterns
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## 🐛 Common Issues
|
|
479
|
+
|
|
480
|
+
### Issue: Links don't work
|
|
481
|
+
|
|
482
|
+
**Check:**
|
|
483
|
+
- Is everything wrapped in `<Router>`?
|
|
484
|
+
- Are you using `<Link>` not `<a>`?
|
|
485
|
+
- Is the `to` prop correct? `to="/login"` not `href="/login"`
|
|
486
|
+
|
|
487
|
+
### Issue: Page reloads when clicking links
|
|
488
|
+
|
|
489
|
+
**Cause:** Using `<a href="">` instead of `<Link to="">`
|
|
490
|
+
|
|
491
|
+
```jac
|
|
492
|
+
# ❌ Wrong
|
|
493
|
+
<a href="/login">Login</a>
|
|
494
|
+
|
|
495
|
+
# ✅ Correct
|
|
496
|
+
<Link to="/login">Login</Link>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Issue: Navigate not working
|
|
500
|
+
|
|
501
|
+
**Check:**
|
|
502
|
+
- Is `<Navigate>` inside a component rendered by `<Route>`?
|
|
503
|
+
- Is it wrapped in `<Router>`?
|
|
504
|
+
|
|
505
|
+
### Issue: Can't access protected page when logged in
|
|
506
|
+
|
|
507
|
+
**Check:**
|
|
508
|
+
- Is `jacIsLoggedIn()` returning true?
|
|
509
|
+
- Did you successfully login?
|
|
510
|
+
- Check browser console for errors
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 🎯 Quick Exercise
|
|
515
|
+
|
|
516
|
+
Try adding a 404 page for unknown routes:
|
|
517
|
+
|
|
518
|
+
```jac
|
|
519
|
+
def NotFoundPage() -> any {
|
|
520
|
+
return <div style={{
|
|
521
|
+
"textAlign": "center",
|
|
522
|
+
"padding": "50px"
|
|
523
|
+
}}>
|
|
524
|
+
<h1>404 - Page Not Found</h1>
|
|
525
|
+
<Link to="/">Go Home</Link>
|
|
526
|
+
</div>;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
# Add as last route (catches everything else)
|
|
530
|
+
<Route path="*" element={<NotFoundPage />} />
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## ➡️ Next Step
|
|
536
|
+
|
|
537
|
+
Congratulations! You've learned all the key concepts. Now let's put everything together into the **complete, final app**!
|
|
538
|
+
|
|
539
|
+
👉 **[Continue to Step 11: Final Integration](./step-11-final.md)**
|
|
540
|
+
|