jac-client 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jac_client/docs/README.md +659 -0
- jac_client/docs/advanced-state.md +1266 -0
- jac_client/docs/assets/pipe_line.png +0 -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 +1142 -0
- jac_client/docs/lifecycle-hooks.md +774 -0
- jac_client/docs/routing.md +660 -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/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/little-x/app.jac +615 -0
- jac_client/examples/little-x/package.json +23 -0
- jac_client/examples/little-x/submit-button.jac +8 -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 +239 -0
- jac_client/plugin/client.py +89 -0
- jac_client/plugin/client_runtime.jac +234 -0
- jac_client/plugin/vite_client_bundle.py +355 -0
- jac_client/tests/__init__.py +2 -0
- jac_client/tests/fixtures/basic-app/app.jac +18 -0
- jac_client/tests/fixtures/client_app_with_antd/app.jac +28 -0
- jac_client/tests/fixtures/js_import/app.jac +30 -0
- jac_client/tests/fixtures/js_import/utils.js +22 -0
- jac_client/tests/fixtures/package-lock.json +329 -0
- jac_client/tests/fixtures/package.json +11 -0
- jac_client/tests/fixtures/relative_import/app.jac +13 -0
- jac_client/tests/fixtures/relative_import/button.jac +6 -0
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/test_fragments_spread/app.jac +53 -0
- jac_client/tests/test_cl.py +476 -0
- jac_client/tests/test_create_jac_app.py +139 -0
- jac_client-0.2.0.dist-info/METADATA +182 -0
- jac_client-0.2.0.dist-info/RECORD +72 -0
- jac_client-0.2.0.dist-info/WHEEL +4 -0
- jac_client-0.2.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
# Step 9: Adding Authentication
|
|
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 user authentication so each person has their own private todos!
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🏗️ Part 1: Building the App
|
|
10
|
+
|
|
11
|
+
### Step 9.1: Import Authentication Functions
|
|
12
|
+
|
|
13
|
+
Add these imports at the top of your `cl` block:
|
|
14
|
+
|
|
15
|
+
```jac
|
|
16
|
+
cl import from react {useState, useEffect}
|
|
17
|
+
cl import from "@jac-client/utils" {
|
|
18
|
+
jacLogin,
|
|
19
|
+
jacSignup,
|
|
20
|
+
jacLogout,
|
|
21
|
+
jacIsLoggedIn
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
cl {
|
|
25
|
+
# ... your components
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Step 9.2: Create the Login Page
|
|
30
|
+
|
|
31
|
+
Add this component:
|
|
32
|
+
|
|
33
|
+
```jac
|
|
34
|
+
def LoginPage() -> any {
|
|
35
|
+
let [username, setUsername] = useState("");
|
|
36
|
+
let [password, setPassword] = useState("");
|
|
37
|
+
let [error, setError] = useState("");
|
|
38
|
+
|
|
39
|
+
async def handleLogin(e: any) -> None {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
setError("");
|
|
42
|
+
|
|
43
|
+
if not username or not password {
|
|
44
|
+
setError("Please fill in all fields");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
success = await jacLogin(username, password);
|
|
49
|
+
if success {
|
|
50
|
+
console.log("Login successful!");
|
|
51
|
+
} else {
|
|
52
|
+
setError("Invalid credentials");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def handleUsernameChange(e: any) -> None {
|
|
57
|
+
setUsername(e.target.value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def handlePasswordChange(e: any) -> None {
|
|
61
|
+
setPassword(e.target.value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let errorDisplay = None;
|
|
65
|
+
if error {
|
|
66
|
+
errorDisplay = <div style={{
|
|
67
|
+
"color": "#dc2626",
|
|
68
|
+
"fontSize": "14px",
|
|
69
|
+
"marginBottom": "10px"
|
|
70
|
+
}}>
|
|
71
|
+
{error}
|
|
72
|
+
</div>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return <div style={{
|
|
76
|
+
"minHeight": "100vh",
|
|
77
|
+
"display": "flex",
|
|
78
|
+
"alignItems": "center",
|
|
79
|
+
"justifyContent": "center",
|
|
80
|
+
"background": "#f5f5f5"
|
|
81
|
+
}}>
|
|
82
|
+
<div style={{
|
|
83
|
+
"background": "#ffffff",
|
|
84
|
+
"padding": "30px",
|
|
85
|
+
"borderRadius": "8px",
|
|
86
|
+
"width": "280px",
|
|
87
|
+
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
|
|
88
|
+
}}>
|
|
89
|
+
<h2 style={{"marginBottom": "20px"}}>Login</h2>
|
|
90
|
+
<form onSubmit={handleLogin}>
|
|
91
|
+
<input
|
|
92
|
+
type="text"
|
|
93
|
+
value={username}
|
|
94
|
+
onChange={handleUsernameChange}
|
|
95
|
+
placeholder="Username"
|
|
96
|
+
style={{
|
|
97
|
+
"width": "100%",
|
|
98
|
+
"padding": "8px",
|
|
99
|
+
"marginBottom": "10px",
|
|
100
|
+
"border": "1px solid #ddd",
|
|
101
|
+
"borderRadius": "4px",
|
|
102
|
+
"boxSizing": "border-box"
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
<input
|
|
106
|
+
type="password"
|
|
107
|
+
value={password}
|
|
108
|
+
onChange={handlePasswordChange}
|
|
109
|
+
placeholder="Password"
|
|
110
|
+
style={{
|
|
111
|
+
"width": "100%",
|
|
112
|
+
"padding": "8px",
|
|
113
|
+
"marginBottom": "10px",
|
|
114
|
+
"border": "1px solid #ddd",
|
|
115
|
+
"borderRadius": "4px",
|
|
116
|
+
"boxSizing": "border-box"
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
{errorDisplay}
|
|
120
|
+
<button
|
|
121
|
+
type="submit"
|
|
122
|
+
style={{
|
|
123
|
+
"width": "100%",
|
|
124
|
+
"padding": "8px",
|
|
125
|
+
"background": "#3b82f6",
|
|
126
|
+
"color": "#ffffff",
|
|
127
|
+
"border": "none",
|
|
128
|
+
"borderRadius": "4px",
|
|
129
|
+
"cursor": "pointer",
|
|
130
|
+
"fontWeight": "600"
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
Login
|
|
134
|
+
</button>
|
|
135
|
+
</form>
|
|
136
|
+
<p style={{
|
|
137
|
+
"textAlign": "center",
|
|
138
|
+
"marginTop": "12px",
|
|
139
|
+
"fontSize": "14px"
|
|
140
|
+
}}>
|
|
141
|
+
Need an account? Sign up link here
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Step 9.3: Create the Signup Page
|
|
149
|
+
|
|
150
|
+
Add this component:
|
|
151
|
+
|
|
152
|
+
```jac
|
|
153
|
+
def SignupPage() -> any {
|
|
154
|
+
let [username, setUsername] = useState("");
|
|
155
|
+
let [password, setPassword] = useState("");
|
|
156
|
+
let [error, setError] = useState("");
|
|
157
|
+
|
|
158
|
+
async def handleSignup(e: any) -> None {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
setError("");
|
|
161
|
+
|
|
162
|
+
if not username or not password {
|
|
163
|
+
setError("Please fill in all fields");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
result = await jacSignup(username, password);
|
|
168
|
+
if result["success"] {
|
|
169
|
+
console.log("Signup successful!");
|
|
170
|
+
} else {
|
|
171
|
+
setError(result["error"] if result["error"] else "Signup failed");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
def handleUsernameChange(e: any) -> None {
|
|
176
|
+
setUsername(e.target.value);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
def handlePasswordChange(e: any) -> None {
|
|
180
|
+
setPassword(e.target.value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let errorDisplay = None;
|
|
184
|
+
if error {
|
|
185
|
+
errorDisplay = <div style={{
|
|
186
|
+
"color": "#dc2626",
|
|
187
|
+
"fontSize": "14px",
|
|
188
|
+
"marginBottom": "10px"
|
|
189
|
+
}}>
|
|
190
|
+
{error}
|
|
191
|
+
</div>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return <div style={{
|
|
195
|
+
"minHeight": "100vh",
|
|
196
|
+
"display": "flex",
|
|
197
|
+
"alignItems": "center",
|
|
198
|
+
"justifyContent": "center",
|
|
199
|
+
"background": "#f5f5f5"
|
|
200
|
+
}}>
|
|
201
|
+
<div style={{
|
|
202
|
+
"background": "#ffffff",
|
|
203
|
+
"padding": "30px",
|
|
204
|
+
"borderRadius": "8px",
|
|
205
|
+
"width": "280px",
|
|
206
|
+
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
|
|
207
|
+
}}>
|
|
208
|
+
<h2 style={{"marginBottom": "20px"}}>Sign Up</h2>
|
|
209
|
+
<form onSubmit={handleSignup}>
|
|
210
|
+
<input
|
|
211
|
+
type="text"
|
|
212
|
+
value={username}
|
|
213
|
+
onChange={handleUsernameChange}
|
|
214
|
+
placeholder="Username"
|
|
215
|
+
style={{
|
|
216
|
+
"width": "100%",
|
|
217
|
+
"padding": "8px",
|
|
218
|
+
"marginBottom": "10px",
|
|
219
|
+
"border": "1px solid #ddd",
|
|
220
|
+
"borderRadius": "4px",
|
|
221
|
+
"boxSizing": "border-box"
|
|
222
|
+
}}
|
|
223
|
+
/>
|
|
224
|
+
<input
|
|
225
|
+
type="password"
|
|
226
|
+
value={password}
|
|
227
|
+
onChange={handlePasswordChange}
|
|
228
|
+
placeholder="Password"
|
|
229
|
+
style={{
|
|
230
|
+
"width": "100%",
|
|
231
|
+
"padding": "8px",
|
|
232
|
+
"marginBottom": "10px",
|
|
233
|
+
"border": "1px solid #ddd",
|
|
234
|
+
"borderRadius": "4px",
|
|
235
|
+
"boxSizing": "border-box"
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
238
|
+
{errorDisplay}
|
|
239
|
+
<button
|
|
240
|
+
type="submit"
|
|
241
|
+
style={{
|
|
242
|
+
"width": "100%",
|
|
243
|
+
"padding": "8px",
|
|
244
|
+
"background": "#3b82f6",
|
|
245
|
+
"color": "#ffffff",
|
|
246
|
+
"border": "none",
|
|
247
|
+
"borderRadius": "4px",
|
|
248
|
+
"cursor": "pointer",
|
|
249
|
+
"fontWeight": "600"
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
Sign Up
|
|
253
|
+
</button>
|
|
254
|
+
</form>
|
|
255
|
+
<p style={{
|
|
256
|
+
"textAlign": "center",
|
|
257
|
+
"marginTop": "12px",
|
|
258
|
+
"fontSize": "14px"
|
|
259
|
+
}}>
|
|
260
|
+
Have an account? Login link here
|
|
261
|
+
</p>
|
|
262
|
+
</div>
|
|
263
|
+
</div>;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Step 9.4: Test the Pages
|
|
268
|
+
|
|
269
|
+
For now, update your `app()` function to show the login page:
|
|
270
|
+
|
|
271
|
+
```jac
|
|
272
|
+
def app() -> any {
|
|
273
|
+
return <LoginPage />;
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Try it!** You should see a login form. Try logging in (it won't work yet because we haven't created an account).
|
|
278
|
+
|
|
279
|
+
Change it to show signup:
|
|
280
|
+
|
|
281
|
+
```jac
|
|
282
|
+
def app() -> any {
|
|
283
|
+
return <SignupPage />;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Create an account!** Enter a username and password, then click "Sign Up". Check the browser console - you should see "Signup successful!"
|
|
288
|
+
|
|
289
|
+
### Step 9.5: Protect Your Todo Page
|
|
290
|
+
|
|
291
|
+
Now let's make the todo page require login. Rename your current `app` function to `TodosPage`:
|
|
292
|
+
|
|
293
|
+
```jac
|
|
294
|
+
# Rename app to TodosPage
|
|
295
|
+
def TodosPage() -> any {
|
|
296
|
+
# Check if user is logged in
|
|
297
|
+
if not jacIsLoggedIn() {
|
|
298
|
+
return <div style={{"padding": "20px"}}>
|
|
299
|
+
<h1>Please login to view todos</h1>
|
|
300
|
+
</div>;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# ... all your existing todo code (useState, useEffect, functions, return)
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**What we did:**
|
|
308
|
+
- Renamed `app` to `TodosPage`
|
|
309
|
+
- Added a check: if not logged in, show a message
|
|
310
|
+
- If logged in, show the todos
|
|
311
|
+
|
|
312
|
+
**Try it!** You should see the "Please login" message (we'll add routing next to make this work properly).
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
**⏭️ Want to skip the theory?** Jump to [Step 10: Routing](./step-10-routing.md)
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 💡 Part 2: Understanding the Concepts
|
|
321
|
+
|
|
322
|
+
### What is Authentication?
|
|
323
|
+
|
|
324
|
+
Authentication = Proving who you are
|
|
325
|
+
|
|
326
|
+
**Real-world analogy:**
|
|
327
|
+
- **ID card** - You show it to prove your identity
|
|
328
|
+
- **Username/Password** - Same thing, but digital!
|
|
329
|
+
|
|
330
|
+
### Jac's Built-in Auth Functions
|
|
331
|
+
|
|
332
|
+
```jac
|
|
333
|
+
# 1. Sign up a new user
|
|
334
|
+
let result = await jacSignup(username, password);
|
|
335
|
+
|
|
336
|
+
# 2. Log in an existing user
|
|
337
|
+
let success = await jacLogin(username, password);
|
|
338
|
+
|
|
339
|
+
# 3. Log out
|
|
340
|
+
jacLogout();
|
|
341
|
+
|
|
342
|
+
# 4. Check if logged in
|
|
343
|
+
if jacIsLoggedIn() {
|
|
344
|
+
// User is logged in
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### How jacSignup Works
|
|
349
|
+
|
|
350
|
+
```jac
|
|
351
|
+
let result = await jacSignup("alice", "password123");
|
|
352
|
+
|
|
353
|
+
// Returns:
|
|
354
|
+
{
|
|
355
|
+
"success": true, // or false if failed
|
|
356
|
+
"error": null // or error message
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**What happens:**
|
|
361
|
+
1. Jac creates a new user account
|
|
362
|
+
2. Hashes the password (secure!)
|
|
363
|
+
3. Creates a session token
|
|
364
|
+
4. Stores token in browser
|
|
365
|
+
5. Returns success/failure
|
|
366
|
+
|
|
367
|
+
### How jacLogin Works
|
|
368
|
+
|
|
369
|
+
```jac
|
|
370
|
+
let success = await jacLogin("alice", "password123");
|
|
371
|
+
|
|
372
|
+
// Returns:
|
|
373
|
+
true // Login successful
|
|
374
|
+
false // Login failed
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**What happens:**
|
|
378
|
+
1. Jac checks if user exists
|
|
379
|
+
2. Verifies password (securely)
|
|
380
|
+
3. Creates a session token
|
|
381
|
+
4. Stores token in browser
|
|
382
|
+
5. Returns true/false
|
|
383
|
+
|
|
384
|
+
### How jacLogout Works
|
|
385
|
+
|
|
386
|
+
```jac
|
|
387
|
+
jacLogout();
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**What happens:**
|
|
391
|
+
1. Removes session token from browser
|
|
392
|
+
2. You're now logged out
|
|
393
|
+
3. Next API call will fail (not authenticated)
|
|
394
|
+
|
|
395
|
+
### How jacIsLoggedIn Works
|
|
396
|
+
|
|
397
|
+
```jac
|
|
398
|
+
if jacIsLoggedIn() {
|
|
399
|
+
// User is logged in
|
|
400
|
+
} else {
|
|
401
|
+
// User is NOT logged in
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**What it checks:**
|
|
406
|
+
1. Is there a valid session token?
|
|
407
|
+
2. Has it expired?
|
|
408
|
+
3. Returns true/false
|
|
409
|
+
|
|
410
|
+
### Form Handling with onSubmit
|
|
411
|
+
|
|
412
|
+
```jac
|
|
413
|
+
<form onSubmit={handleLogin}>
|
|
414
|
+
<input type="text" />
|
|
415
|
+
<button type="submit">Login</button>
|
|
416
|
+
</form>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Key points:**
|
|
420
|
+
- `onSubmit` fires when form is submitted
|
|
421
|
+
- Submitting = clicking button OR pressing Enter
|
|
422
|
+
- Always call `e.preventDefault()` to stop page reload
|
|
423
|
+
|
|
424
|
+
```jac
|
|
425
|
+
async def handleLogin(e: any) -> None {
|
|
426
|
+
e.preventDefault(); # Stop page reload!
|
|
427
|
+
// Your login logic
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Password Input Type
|
|
432
|
+
|
|
433
|
+
```jac
|
|
434
|
+
<input type="password" /> # Hides characters (•••)
|
|
435
|
+
<input type="text" /> # Shows characters (abc)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Always use `type="password"` for passwords!
|
|
439
|
+
|
|
440
|
+
### Error Handling
|
|
441
|
+
|
|
442
|
+
```jac
|
|
443
|
+
let [error, setError] = useState("");
|
|
444
|
+
|
|
445
|
+
# Show error if exists
|
|
446
|
+
{(<div style={{"color": "red"}}>{error}</div>) if error else None}
|
|
447
|
+
|
|
448
|
+
# Set error
|
|
449
|
+
setError("Invalid credentials");
|
|
450
|
+
|
|
451
|
+
# Clear error
|
|
452
|
+
setError("");
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Conditional Rendering for Auth
|
|
456
|
+
|
|
457
|
+
```jac
|
|
458
|
+
def TodosPage() -> any {
|
|
459
|
+
if not jacIsLoggedIn() {
|
|
460
|
+
return <div>Please login</div>;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
# User is logged in, show todos
|
|
464
|
+
return <div>Your todos here</div>;
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
This pattern protects pages from unauthorized access!
|
|
469
|
+
|
|
470
|
+
### User Isolation
|
|
471
|
+
|
|
472
|
+
**Magic happens automatically!**
|
|
473
|
+
|
|
474
|
+
When you add authentication to walkers:
|
|
475
|
+
|
|
476
|
+
```jac
|
|
477
|
+
walker read_todos {
|
|
478
|
+
# No special code needed - Jac handles it!
|
|
479
|
+
can read with `root entry {
|
|
480
|
+
visit [-->(`?Todo)];
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Jac automatically:
|
|
486
|
+
- Uses the logged-in user's root node
|
|
487
|
+
- Each user sees only their own todos
|
|
488
|
+
- No way to access other users' data
|
|
489
|
+
|
|
490
|
+
### Session Persistence
|
|
491
|
+
|
|
492
|
+
Sessions persist across page refreshes!
|
|
493
|
+
|
|
494
|
+
```jac
|
|
495
|
+
# User logs in
|
|
496
|
+
await jacLogin("alice", "password123");
|
|
497
|
+
|
|
498
|
+
# Refresh page
|
|
499
|
+
# jacIsLoggedIn() still returns true!
|
|
500
|
+
|
|
501
|
+
# Sessions last until:
|
|
502
|
+
# 1. User logs out (jacLogout)
|
|
503
|
+
# 2. Session expires (configurable)
|
|
504
|
+
# 3. User clears browser data
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## ✅ What You've Learned
|
|
510
|
+
|
|
511
|
+
- ✅ What authentication is and why it's important
|
|
512
|
+
- ✅ Using `jacSignup` to create accounts
|
|
513
|
+
- ✅ Using `jacLogin` to log users in
|
|
514
|
+
- ✅ Using `jacLogout` to log users out
|
|
515
|
+
- ✅ Using `jacIsLoggedIn` to check auth status
|
|
516
|
+
- ✅ Creating login and signup forms
|
|
517
|
+
- ✅ Handling form submission
|
|
518
|
+
- ✅ Protecting pages with auth checks
|
|
519
|
+
- ✅ User isolation (each user sees only their data)
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 🐛 Common Issues
|
|
524
|
+
|
|
525
|
+
### Issue: "Signup failed"
|
|
526
|
+
|
|
527
|
+
**Check:**
|
|
528
|
+
- Is the username already taken? Try a different one
|
|
529
|
+
- Are username/password not empty?
|
|
530
|
+
- Check browser console for errors
|
|
531
|
+
|
|
532
|
+
### Issue: Login says "Invalid credentials"
|
|
533
|
+
|
|
534
|
+
**Check:**
|
|
535
|
+
- Did you create an account first?
|
|
536
|
+
- Is the username/password correct?
|
|
537
|
+
- Usernames are case-sensitive!
|
|
538
|
+
|
|
539
|
+
### Issue: jacIsLoggedIn() always returns false
|
|
540
|
+
|
|
541
|
+
**Check:**
|
|
542
|
+
- Did you successfully login/signup?
|
|
543
|
+
- Check browser console for errors
|
|
544
|
+
- Try logging in again
|
|
545
|
+
|
|
546
|
+
### Issue: Can't create multiple accounts
|
|
547
|
+
|
|
548
|
+
**Solution:** Each username can only be used once. Try different usernames:
|
|
549
|
+
- alice, bob, carol
|
|
550
|
+
- user1, user2, user3
|
|
551
|
+
- test_alice, test_bob
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## 🎯 Quick Exercise
|
|
556
|
+
|
|
557
|
+
Try adding a "Remember me" message:
|
|
558
|
+
|
|
559
|
+
```jac
|
|
560
|
+
def LoginPage() -> any {
|
|
561
|
+
let [username, setUsername] = useState("");
|
|
562
|
+
let [password, setPassword] = useState("");
|
|
563
|
+
|
|
564
|
+
# Check if already logged in
|
|
565
|
+
if jacIsLoggedIn() {
|
|
566
|
+
return <div style={{"padding": "20px"}}>
|
|
567
|
+
<h2>You're already logged in!</h2>
|
|
568
|
+
<button onClick={lambda -> None { jacLogout(); }}>
|
|
569
|
+
Logout
|
|
570
|
+
</button>
|
|
571
|
+
</div>;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
# ... rest of login form
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## ➡️ Next Step
|
|
581
|
+
|
|
582
|
+
Great! You now have authentication, but you're still showing only one page at a time.
|
|
583
|
+
|
|
584
|
+
In the next step, we'll add **routing** so users can navigate between login, signup, and todos pages!
|
|
585
|
+
|
|
586
|
+
👉 **[Continue to Step 10: Routing](./step-10-routing.md)**
|