create-bdpamke-react-scaffold 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/bin/create-bdpamke-react-scaffold.js +101 -0
- package/package.json +39 -0
- package/template/.env.example +6 -0
- package/template/FUNCTIONS_EXAMPLES.md +480 -0
- package/template/HOWTOadd a page.md +166 -0
- package/template/REACT_PROPS_USEEFFECT.md +210 -0
- package/template/REGISTRATION_FLOW.md +268 -0
- package/template/USESTATE_EXAMPLES.md +451 -0
- package/template/components.json +20 -0
- package/template/index.html +13 -0
- package/template/jsconfig.json +19 -0
- package/template/package-lock.json +5988 -0
- package/template/package.json +73 -0
- package/template/postcss.config.cjs +6 -0
- package/template/public/images/BDPA_edited.png +0 -0
- package/template/server/server.js +86 -0
- package/template/server/utils/apiClient.js +59 -0
- package/template/server/utils/password.js +60 -0
- package/template/src/App.jsx +10 -0
- package/template/src/components/layout/Container.jsx +7 -0
- package/template/src/components/layout/Section.jsx +7 -0
- package/template/src/components/ui/accordion.jsx +41 -0
- package/template/src/components/ui/alert-dialog.jsx +99 -0
- package/template/src/components/ui/alert.jsx +47 -0
- package/template/src/components/ui/aspect-ratio.jsx +5 -0
- package/template/src/components/ui/avatar.jsx +35 -0
- package/template/src/components/ui/badge.jsx +34 -0
- package/template/src/components/ui/button.jsx +47 -0
- package/template/src/components/ui/calendar.jsx +173 -0
- package/template/src/components/ui/card.jsx +50 -0
- package/template/src/components/ui/carousel.jsx +194 -0
- package/template/src/components/ui/checkbox.jsx +22 -0
- package/template/src/components/ui/collapsible.jsx +11 -0
- package/template/src/components/ui/command.jsx +116 -0
- package/template/src/components/ui/dialog.jsx +94 -0
- package/template/src/components/ui/drawer.jsx +92 -0
- package/template/src/components/ui/dropdown-menu.jsx +155 -0
- package/template/src/components/ui/form.jsx +138 -0
- package/template/src/components/ui/hover-card.jsx +25 -0
- package/template/src/components/ui/icons.jsx +81 -0
- package/template/src/components/ui/input.jsx +19 -0
- package/template/src/components/ui/label.jsx +16 -0
- package/template/src/components/ui/menubar.jsx +200 -0
- package/template/src/components/ui/navigation-menu.jsx +104 -0
- package/template/src/components/ui/popover.jsx +25 -0
- package/template/src/components/ui/progress.jsx +20 -0
- package/template/src/components/ui/radio-group.jsx +29 -0
- package/template/src/components/ui/scroll-area.jsx +40 -0
- package/template/src/components/ui/select.jsx +120 -0
- package/template/src/components/ui/separator.jsx +25 -0
- package/template/src/components/ui/sheet.jsx +108 -0
- package/template/src/components/ui/skeleton.jsx +10 -0
- package/template/src/components/ui/slider.jsx +23 -0
- package/template/src/components/ui/sonner.jsx +42 -0
- package/template/src/components/ui/switch.jsx +24 -0
- package/template/src/components/ui/table.jsx +83 -0
- package/template/src/components/ui/tabs.jsx +41 -0
- package/template/src/components/ui/textarea.jsx +18 -0
- package/template/src/components/ui/toast.jsx +82 -0
- package/template/src/components/ui/toaster.jsx +33 -0
- package/template/src/components/ui/toggle.jsx +40 -0
- package/template/src/components/ui/tooltip.jsx +24 -0
- package/template/src/hooks/use-toast.js +155 -0
- package/template/src/index.css +61 -0
- package/template/src/index.js +6 -0
- package/template/src/lib/utils.js +11 -0
- package/template/src/main.jsx +15 -0
- package/template/src/pages/Home.jsx +26 -0
- package/template/tailwind.config.cjs +76 -0
- package/template/vite.config.mts +22 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# How-To Guides
|
|
2
|
+
|
|
3
|
+
## How to Add a New Page
|
|
4
|
+
|
|
5
|
+
1. **Create the page component** in `src/pages/`:
|
|
6
|
+
|
|
7
|
+
```jsx
|
|
8
|
+
// src/pages/Dashboard.jsx
|
|
9
|
+
import React from "react";
|
|
10
|
+
import Container from "../components/layout/Container";
|
|
11
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
12
|
+
|
|
13
|
+
export default function Dashboard() {
|
|
14
|
+
return (
|
|
15
|
+
<Container>
|
|
16
|
+
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
|
|
17
|
+
<Card>
|
|
18
|
+
<p>Welcome to your dashboard!</p>
|
|
19
|
+
</Card>
|
|
20
|
+
</Container>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. **Add the route** in `src/App.jsx`:
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
29
|
+
import Dashboard from "./pages/Dashboard";
|
|
30
|
+
// ... other imports
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
return (
|
|
34
|
+
<BrowserRouter>
|
|
35
|
+
<Routes>
|
|
36
|
+
<Route path="/" element={<Home />} />
|
|
37
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
38
|
+
{/* Add your new route here */}
|
|
39
|
+
</Routes>
|
|
40
|
+
</BrowserRouter>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. **Add navigation link** (optional) in `src/components/ui/Navbar.jsx` or `Sidebar.jsx`:
|
|
46
|
+
|
|
47
|
+
```jsx
|
|
48
|
+
import { Link } from "react-router-dom";
|
|
49
|
+
|
|
50
|
+
<Link to="/dashboard" className="text-gray-700 hover:text-blue-600">
|
|
51
|
+
Dashboard
|
|
52
|
+
</Link>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## How to Make a REST API Call
|
|
56
|
+
|
|
57
|
+
The scaffold includes an Axios-based API client in `src/utils/api.js` with built-in CRUD methods.
|
|
58
|
+
|
|
59
|
+
### Basic API Configuration
|
|
60
|
+
|
|
61
|
+
Set your API base URL in a `.env` file:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
VITE_API_BASE_URL=https://api.example.com
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Making API Calls
|
|
68
|
+
|
|
69
|
+
```jsx
|
|
70
|
+
import { useState, useEffect } from "react";
|
|
71
|
+
import api from "../utils/api";
|
|
72
|
+
|
|
73
|
+
function MyComponent() {
|
|
74
|
+
const [data, setData] = useState([]);
|
|
75
|
+
const [loading, setLoading] = useState(false);
|
|
76
|
+
const [error, setError] = useState(null);
|
|
77
|
+
|
|
78
|
+
// GET request - Fetch all items
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const fetchData = async () => {
|
|
81
|
+
setLoading(true);
|
|
82
|
+
try {
|
|
83
|
+
const response = await api.getAll("/students");
|
|
84
|
+
setData(response.data);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
setError(err.message);
|
|
87
|
+
} finally {
|
|
88
|
+
setLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
fetchData();
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
// GET request - Fetch single item
|
|
95
|
+
const fetchStudent = async (id) => {
|
|
96
|
+
try {
|
|
97
|
+
const response = await api.getById("/students", id);
|
|
98
|
+
console.log(response.data);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(err);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// POST request - Create new item
|
|
105
|
+
const createStudent = async () => {
|
|
106
|
+
try {
|
|
107
|
+
const newStudent = { name: "Ada Lovelace", grade: "A" };
|
|
108
|
+
const response = await api.create("/students", newStudent);
|
|
109
|
+
setData([...data, response.data]);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
setError(err.message);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// PUT request - Update item
|
|
116
|
+
const updateStudent = async (id) => {
|
|
117
|
+
try {
|
|
118
|
+
const updates = { name: "Grace Hopper" };
|
|
119
|
+
const response = await api.update(`/students/${id}`, updates);
|
|
120
|
+
setData(data.map(item => item.id === id ? response.data : item));
|
|
121
|
+
} catch (err) {
|
|
122
|
+
setError(err.message);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// DELETE request - Remove item
|
|
127
|
+
const deleteStudent = async (id) => {
|
|
128
|
+
try {
|
|
129
|
+
await api.delete(`/students/${id}`);
|
|
130
|
+
setData(data.filter(item => item.id !== id));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
setError(err.message);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div>
|
|
138
|
+
{loading && <p>Loading...</p>}
|
|
139
|
+
{error && <p className="text-red-500">Error: {error}</p>}
|
|
140
|
+
{/* Render your data here */}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Available API Methods
|
|
147
|
+
|
|
148
|
+
- `api.getAll(endpoint)` - GET all items
|
|
149
|
+
- `api.getById(endpoint, id)` - GET single item
|
|
150
|
+
- `api.create(endpoint, data)` - POST new item
|
|
151
|
+
- `api.update(endpoint, data)` - PUT update item
|
|
152
|
+
- `api.delete(endpoint)` - DELETE item
|
|
153
|
+
- `api.setToken(token)` - Set authorization token
|
|
154
|
+
|
|
155
|
+
### Custom API Client
|
|
156
|
+
|
|
157
|
+
Create a custom client for different base URLs:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
import { ApiClient } from "./utils/api";
|
|
161
|
+
|
|
162
|
+
const adminApi = new ApiClient("https://admin.example.com");
|
|
163
|
+
adminApi.setToken("your-jwt-token");
|
|
164
|
+
|
|
165
|
+
const response = await adminApi.getAll("/users");
|
|
166
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# React Props and useEffect
|
|
2
|
+
|
|
3
|
+
This guide explains two core React concepts:
|
|
4
|
+
|
|
5
|
+
- `props`
|
|
6
|
+
- `useEffect`
|
|
7
|
+
|
|
8
|
+
## See also
|
|
9
|
+
|
|
10
|
+
For plain JavaScript function examples, see `FUNCTIONS_EXAMPLES.md`.
|
|
11
|
+
|
|
12
|
+
## 1. What props are
|
|
13
|
+
|
|
14
|
+
Props are values passed from a parent component to a child component.
|
|
15
|
+
|
|
16
|
+
They let components share data and behavior.
|
|
17
|
+
|
|
18
|
+
## 2. Basic props example
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
function WelcomeMessage(props) {
|
|
22
|
+
return <h1>Hello, {props.name}</h1>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function App() {
|
|
26
|
+
return <WelcomeMessage name="Ada" />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Here:
|
|
31
|
+
|
|
32
|
+
- `name` is a prop
|
|
33
|
+
- the parent passes it in
|
|
34
|
+
- the child reads it
|
|
35
|
+
|
|
36
|
+
## 3. Destructured props example
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
function WelcomeMessage({ name, role }) {
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<h1>Hello, {name}</h1>
|
|
43
|
+
<p>Role: {role}</p>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default function App() {
|
|
49
|
+
return <WelcomeMessage name="Grace" role="Engineer" />;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This is the most common style in React.
|
|
54
|
+
|
|
55
|
+
## 4. Passing a function as a prop
|
|
56
|
+
|
|
57
|
+
Parents can pass functions to children.
|
|
58
|
+
|
|
59
|
+
```jsx
|
|
60
|
+
function ChildButton({ onPress }) {
|
|
61
|
+
return <button onClick={onPress}>Click Me</button>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default function Parent() {
|
|
65
|
+
function handlePress() {
|
|
66
|
+
console.log("Button clicked in child.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return <ChildButton onPress={handlePress} />;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This is useful because child components can trigger logic owned by the parent.
|
|
74
|
+
|
|
75
|
+
## 5. What useEffect is
|
|
76
|
+
|
|
77
|
+
`useEffect` is a React hook used for side effects.
|
|
78
|
+
|
|
79
|
+
Examples of side effects:
|
|
80
|
+
|
|
81
|
+
- fetching API data
|
|
82
|
+
- setting up timers
|
|
83
|
+
- listening for browser events
|
|
84
|
+
- updating browser state outside the render
|
|
85
|
+
|
|
86
|
+
Basic syntax:
|
|
87
|
+
|
|
88
|
+
```jsx
|
|
89
|
+
import { useEffect } from "react";
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
console.log("Effect ran");
|
|
93
|
+
}, []);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The second argument is the dependency array.
|
|
97
|
+
|
|
98
|
+
## 6. Run once when the component loads
|
|
99
|
+
|
|
100
|
+
```jsx
|
|
101
|
+
import { useEffect } from "react";
|
|
102
|
+
|
|
103
|
+
export default function Page() {
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
console.log("Component mounted");
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
return <p>Page loaded</p>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This runs once after the first render.
|
|
113
|
+
|
|
114
|
+
## 7. Fetch data with useEffect
|
|
115
|
+
|
|
116
|
+
```jsx
|
|
117
|
+
import { useEffect, useState } from "react";
|
|
118
|
+
|
|
119
|
+
export default function HealthCheck() {
|
|
120
|
+
const [data, setData] = useState(null);
|
|
121
|
+
const [error, setError] = useState("");
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const loadHealth = async () => {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch("/api/health");
|
|
127
|
+
const result = await response.json();
|
|
128
|
+
setData(result);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
setError("Failed to load health status.");
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
loadHealth();
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
if (error) return <p>{error}</p>;
|
|
138
|
+
if (!data) return <p>Loading...</p>;
|
|
139
|
+
|
|
140
|
+
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This is a common pattern for loading server data when a component appears.
|
|
145
|
+
|
|
146
|
+
## 8. Run when a value changes
|
|
147
|
+
|
|
148
|
+
```jsx
|
|
149
|
+
import { useEffect, useState } from "react";
|
|
150
|
+
|
|
151
|
+
export default function SearchExample() {
|
|
152
|
+
const [query, setQuery] = useState("");
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!query) return;
|
|
156
|
+
console.log("Search query changed:", query);
|
|
157
|
+
}, [query]);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<input
|
|
161
|
+
value={query}
|
|
162
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
163
|
+
placeholder="Search"
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This effect runs every time `query` changes.
|
|
170
|
+
|
|
171
|
+
## 9. Cleanup example
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
import { useEffect } from "react";
|
|
175
|
+
|
|
176
|
+
export default function TimerExample() {
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const id = setInterval(() => {
|
|
179
|
+
console.log("Tick");
|
|
180
|
+
}, 1000);
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
clearInterval(id);
|
|
184
|
+
};
|
|
185
|
+
}, []);
|
|
186
|
+
|
|
187
|
+
return <p>Timer running</p>;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The function returned by `useEffect` is cleanup logic.
|
|
192
|
+
|
|
193
|
+
It runs when:
|
|
194
|
+
|
|
195
|
+
- the component is removed
|
|
196
|
+
- or before the effect runs again
|
|
197
|
+
|
|
198
|
+
## 10. Summary
|
|
199
|
+
|
|
200
|
+
Use `props` when:
|
|
201
|
+
|
|
202
|
+
- a parent needs to pass data to a child
|
|
203
|
+
- a parent needs to pass a function to a child
|
|
204
|
+
|
|
205
|
+
Use `useEffect` when:
|
|
206
|
+
|
|
207
|
+
- code should run after render
|
|
208
|
+
- you need to fetch data
|
|
209
|
+
- you need timers or subscriptions
|
|
210
|
+
- you need cleanup logic
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Registration Flow: Send Email and Password to the Server
|
|
2
|
+
|
|
3
|
+
This guide shows how to:
|
|
4
|
+
|
|
5
|
+
1. Create a registration page in React
|
|
6
|
+
2. Send `email` and `password` to the server
|
|
7
|
+
3. Call the server-side encryption function
|
|
8
|
+
4. Hash the password on the server
|
|
9
|
+
5. Prove it works by printing the encrypted password in the server console
|
|
10
|
+
|
|
11
|
+
Note: logging password hashes is only for development proof. Do not keep that log in production.
|
|
12
|
+
|
|
13
|
+
## 1. Use the existing server-side password utility
|
|
14
|
+
|
|
15
|
+
The server already has a password utility in:
|
|
16
|
+
|
|
17
|
+
- `server/utils/password.js`
|
|
18
|
+
|
|
19
|
+
It exports:
|
|
20
|
+
|
|
21
|
+
- `hashPassword(password)`
|
|
22
|
+
- `verifyPassword(password, hash)`
|
|
23
|
+
- `getPasswordStrength(password)`
|
|
24
|
+
- `getPasswordStrengthLabel(password)`
|
|
25
|
+
|
|
26
|
+
That means the registration flow should hash the password on the server, not in React.
|
|
27
|
+
|
|
28
|
+
## 2. Update the server registration route
|
|
29
|
+
|
|
30
|
+
In `server/server.js`, import `hashPassword` from the server utility.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
"use strict";
|
|
36
|
+
const express = require("express");
|
|
37
|
+
const cors = require("cors");
|
|
38
|
+
const { ApiClient } = require("./utils/apiClient");
|
|
39
|
+
const { hashPassword } = require("./utils/password");
|
|
40
|
+
|
|
41
|
+
const app = express();
|
|
42
|
+
const PORT = 5000;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then add a registration route like this:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
app.post("/api/register", async (req, res) => {
|
|
49
|
+
const { email, password } = req.body;
|
|
50
|
+
|
|
51
|
+
if (!email || typeof email !== "string") {
|
|
52
|
+
return res.status(400).json({ error: "Email is required." });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!password || typeof password !== "string") {
|
|
56
|
+
return res.status(400).json({ error: "Password is required." });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const hashedPassword = await hashPassword(password);
|
|
61
|
+
|
|
62
|
+
// Development proof only.
|
|
63
|
+
console.log("Registered email:", email);
|
|
64
|
+
console.log("Encrypted password:", hashedPassword);
|
|
65
|
+
|
|
66
|
+
// In a real app, save email + hashedPassword to the database here.
|
|
67
|
+
return res.status(201).json({
|
|
68
|
+
message: "Registration received.",
|
|
69
|
+
email,
|
|
70
|
+
hashedPassword,
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return res.status(500).json({ error: error.message || "Registration failed." });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 3. Create a React registration page
|
|
79
|
+
|
|
80
|
+
Create a page such as `src/pages/Register.jsx`.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
|
|
84
|
+
```jsx
|
|
85
|
+
import { useState } from "react";
|
|
86
|
+
|
|
87
|
+
export default function Register() {
|
|
88
|
+
const [email, setEmail] = useState("");
|
|
89
|
+
const [password, setPassword] = useState("");
|
|
90
|
+
const [result, setResult] = useState(null);
|
|
91
|
+
const [error, setError] = useState("");
|
|
92
|
+
|
|
93
|
+
const handleSubmit = async (event) => {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
setError("");
|
|
96
|
+
setResult(null);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch("/api/register", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ email, password }),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const data = await response.json();
|
|
108
|
+
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(data.error || "Request failed");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setResult(data);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
setError(err.message);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<main className="min-h-screen px-6 py-12">
|
|
121
|
+
<div className="mx-auto max-w-md">
|
|
122
|
+
<h1 className="mb-6 text-2xl font-bold">Register</h1>
|
|
123
|
+
|
|
124
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
125
|
+
<div>
|
|
126
|
+
<label htmlFor="email" className="block text-sm font-medium">
|
|
127
|
+
Email
|
|
128
|
+
</label>
|
|
129
|
+
<input
|
|
130
|
+
id="email"
|
|
131
|
+
type="email"
|
|
132
|
+
value={email}
|
|
133
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
134
|
+
className="mt-1 w-full rounded border px-3 py-2"
|
|
135
|
+
required
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div>
|
|
140
|
+
<label htmlFor="password" className="block text-sm font-medium">
|
|
141
|
+
Password
|
|
142
|
+
</label>
|
|
143
|
+
<input
|
|
144
|
+
id="password"
|
|
145
|
+
type="password"
|
|
146
|
+
value={password}
|
|
147
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
148
|
+
className="mt-1 w-full rounded border px-3 py-2"
|
|
149
|
+
required
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<button
|
|
154
|
+
type="submit"
|
|
155
|
+
className="rounded bg-slate-900 px-4 py-2 text-white"
|
|
156
|
+
>
|
|
157
|
+
Submit Registration
|
|
158
|
+
</button>
|
|
159
|
+
</form>
|
|
160
|
+
|
|
161
|
+
{error ? <p className="mt-4 text-red-600">{error}</p> : null}
|
|
162
|
+
|
|
163
|
+
{result ? (
|
|
164
|
+
<pre className="mt-4 overflow-auto rounded bg-slate-100 p-4 text-sm">
|
|
165
|
+
{JSON.stringify(result, null, 2)}
|
|
166
|
+
</pre>
|
|
167
|
+
) : null}
|
|
168
|
+
</div>
|
|
169
|
+
</main>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 4. Add the route in React
|
|
175
|
+
|
|
176
|
+
In `src/App.jsx`, add the page route.
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
|
|
180
|
+
```jsx
|
|
181
|
+
import { Route, Routes } from "react-router-dom";
|
|
182
|
+
import Home from "./pages/Home.jsx";
|
|
183
|
+
import Register from "./pages/Register.jsx";
|
|
184
|
+
|
|
185
|
+
export default function App() {
|
|
186
|
+
return (
|
|
187
|
+
<Routes>
|
|
188
|
+
<Route path="/" element={<Home />} />
|
|
189
|
+
<Route path="/register" element={<Register />} />
|
|
190
|
+
</Routes>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 5. Why the frontend can call `/api/register`
|
|
196
|
+
|
|
197
|
+
This project already proxies `/api` requests from Vite to the Express server.
|
|
198
|
+
|
|
199
|
+
That setup is in:
|
|
200
|
+
|
|
201
|
+
- `vite.config.mts`
|
|
202
|
+
|
|
203
|
+
Because of that, this frontend request works during development:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
fetch("/api/register", {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: {
|
|
209
|
+
"Content-Type": "application/json",
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify({ email, password }),
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
It will be forwarded to the Express server running on port `5000`.
|
|
216
|
+
|
|
217
|
+
## 6. What proves the encryption worked
|
|
218
|
+
|
|
219
|
+
When the form is submitted:
|
|
220
|
+
|
|
221
|
+
1. React sends the plain-text `email` and `password` to `POST /api/register`
|
|
222
|
+
2. The server receives them
|
|
223
|
+
3. The server calls `hashPassword(password)`
|
|
224
|
+
4. The server logs the hashed value with `console.log`
|
|
225
|
+
|
|
226
|
+
Expected server console output will look similar to this:
|
|
227
|
+
|
|
228
|
+
```txt
|
|
229
|
+
Registered email: user@example.com
|
|
230
|
+
Encrypted password: $2a$10$w3Pj7x8nK8D2r9M0T6Qv4eM9K5h7sX2mN1aB3cD4eF5gH6iJ7kL8m
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The value beginning with `$2a$10$` or `$2b$10$` is a bcrypt hash, which proves the password was encrypted on the server.
|
|
234
|
+
|
|
235
|
+
## 7. Quick test steps
|
|
236
|
+
|
|
237
|
+
1. Start the stack:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
npm run dev:all
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
2. Open the app in the browser.
|
|
244
|
+
|
|
245
|
+
3. Go to `/register`.
|
|
246
|
+
|
|
247
|
+
4. Enter an email and password.
|
|
248
|
+
|
|
249
|
+
5. Submit the form.
|
|
250
|
+
|
|
251
|
+
6. Check the server terminal.
|
|
252
|
+
|
|
253
|
+
You should see the email and encrypted password in the server console.
|
|
254
|
+
|
|
255
|
+
## 8. Important production note
|
|
256
|
+
|
|
257
|
+
For production, remove this line after testing:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
console.log("Encrypted password:", hashedPassword);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
In production, you should:
|
|
264
|
+
|
|
265
|
+
- Hash the password on the server
|
|
266
|
+
- Save only the hash to the database
|
|
267
|
+
- Never log plain-text passwords
|
|
268
|
+
- Usually avoid logging password hashes too
|