Add user password change functionality
All checks were successful
PostgreSQL Compatibility Matrix / PG14 smoke (push) Successful in 9s
PostgreSQL Compatibility Matrix / PG15 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG16 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG17 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG18 smoke (push) Successful in 7s

Introduced a backend API endpoint for changing user passwords with validation. Added a new "User Settings" page in the frontend to allow users to update their passwords, including a matching UI update for navigation and styles.
This commit is contained in:
2026-02-13 09:32:54 +01:00
parent bd53bce231
commit 07a7236282
6 changed files with 176 additions and 4 deletions

View File

@@ -9,6 +9,7 @@ import { QueryInsightsPage } from "./pages/QueryInsightsPage";
import { AlertsPage } from "./pages/AlertsPage";
import { AdminUsersPage } from "./pages/AdminUsersPage";
import { ServiceInfoPage } from "./pages/ServiceInfoPage";
import { UserSettingsPage } from "./pages/UserSettingsPage";
function Protected({ children }) {
const { tokens } = useAuth();
@@ -102,6 +103,9 @@ function Layout({ children }) {
</div>
<div>{me?.email}</div>
<div className="role">{me?.role}</div>
<NavLink to="/user-settings" className={({ isActive }) => `profile-btn${isActive ? " active" : ""}`}>
User Settings
</NavLink>
<button className="logout-btn" onClick={logout}>Logout</button>
</div>
</aside>
@@ -163,6 +167,7 @@ export function App() {
<Route path="/query-insights" element={<QueryInsightsPage />} />
<Route path="/alerts" element={<AlertsPage />} />
<Route path="/service-info" element={<ServiceInfoPage />} />
<Route path="/user-settings" element={<UserSettingsPage />} />
<Route path="/admin/users" element={<AdminUsersPage />} />
</Routes>
</Layout>

View File

@@ -0,0 +1,100 @@
import React, { useState } from "react";
import { apiFetch } from "../api";
import { useAuth } from "../state";
export function UserSettingsPage() {
const { tokens, refresh } = useAuth();
const [form, setForm] = useState({
current_password: "",
new_password: "",
confirm_password: "",
});
const [message, setMessage] = useState("");
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const submit = async (e) => {
e.preventDefault();
setMessage("");
setError("");
if (form.new_password.length < 8) {
setError("New password must be at least 8 characters.");
return;
}
if (form.new_password !== form.confirm_password) {
setError("Password confirmation does not match.");
return;
}
try {
setBusy(true);
await apiFetch(
"/me/password",
{
method: "POST",
body: JSON.stringify({
current_password: form.current_password,
new_password: form.new_password,
}),
},
tokens,
refresh
);
setForm({ current_password: "", new_password: "", confirm_password: "" });
setMessage("Password changed successfully.");
} catch (e) {
setError(String(e.message || e));
} finally {
setBusy(false);
}
};
return (
<div className="user-settings-page">
<h2>User Settings</h2>
<p className="muted">Manage your personal account security settings.</p>
{error && <div className="card error">{error}</div>}
{message && <div className="test-connection-result ok">{message}</div>}
<div className="card user-settings-card">
<h3>Change Password</h3>
<form className="grid two" onSubmit={submit}>
<div className="admin-field field-full">
<label>Current password</label>
<input
type="password"
value={form.current_password}
onChange={(e) => setForm({ ...form, current_password: e.target.value })}
required
/>
</div>
<div className="admin-field">
<label>New password</label>
<input
type="password"
value={form.new_password}
onChange={(e) => setForm({ ...form, new_password: e.target.value })}
minLength={8}
required
/>
</div>
<div className="admin-field">
<label>Confirm new password</label>
<input
type="password"
value={form.confirm_password}
onChange={(e) => setForm({ ...form, confirm_password: e.target.value })}
minLength={8}
required
/>
</div>
<div className="form-actions field-full">
<button className="primary-btn" type="submit" disabled={busy}>
{busy ? "Saving..." : "Update Password"}
</button>
</div>
</form>
</div>
</div>
);
}

View File

@@ -1115,6 +1115,39 @@ button {
border-color: #38bdf8;
}
.profile-btn {
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 8px;
border: 1px solid #3a63a1;
border-radius: 10px;
background: linear-gradient(180deg, #15315d, #11274c);
color: #e7f2ff;
min-height: 40px;
font-weight: 650;
}
.profile-btn:hover {
border-color: #58b0e8;
background: linear-gradient(180deg, #1a427a, #15335f);
}
.profile-btn.active {
border-color: #66c7f4;
box-shadow: inset 0 0 0 1px #66c7f455;
}
.user-settings-page h2 {
margin-top: 4px;
margin-bottom: 4px;
}
.user-settings-card {
max-width: 760px;
}
table {
width: 100%;
border-collapse: collapse;