160 lines
6.1 KiB
TypeScript
160 lines
6.1 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { KeyRound, UserPen } from "lucide-react";
|
|
|
|
export default function Settings() {
|
|
const [username, setUsername] = useState("");
|
|
const [newUsername, setNewUsername] = useState("");
|
|
const [usernameError, setUsernameError] = useState("");
|
|
const [usernameSuccess, setUsernameSuccess] = useState(false);
|
|
const [usernameLoading, setUsernameLoading] = useState(false);
|
|
|
|
const [pwForm, setPwForm] = useState({
|
|
current_password: "",
|
|
new_password: "",
|
|
confirm_password: "",
|
|
});
|
|
const [pwError, setPwError] = useState("");
|
|
const [pwSuccess, setPwSuccess] = useState(false);
|
|
const [pwLoading, setPwLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetch("/api/auth/me")
|
|
.then((r) => r.json())
|
|
.then((d) => {
|
|
setUsername(d.username);
|
|
setNewUsername(d.username);
|
|
});
|
|
}, []);
|
|
|
|
const saveUsername = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setUsernameError("");
|
|
setUsernameSuccess(false);
|
|
if (newUsername.trim() === username) return;
|
|
setUsernameLoading(true);
|
|
const res = await fetch("/api/auth/change-username", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ new_username: newUsername.trim() }),
|
|
});
|
|
setUsernameLoading(false);
|
|
if (res.ok) {
|
|
setUsername(newUsername.trim());
|
|
setUsernameSuccess(true);
|
|
} else {
|
|
const data = await res.json();
|
|
setUsernameError(data.error || "Failed to update username");
|
|
}
|
|
};
|
|
|
|
const savePassword = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setPwError("");
|
|
setPwSuccess(false);
|
|
if (pwForm.new_password !== pwForm.confirm_password) {
|
|
setPwError("New passwords do not match");
|
|
return;
|
|
}
|
|
setPwLoading(true);
|
|
const res = await fetch("/api/auth/change-password", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
current_password: pwForm.current_password,
|
|
new_password: pwForm.new_password,
|
|
}),
|
|
});
|
|
setPwLoading(false);
|
|
if (res.ok) {
|
|
setPwSuccess(true);
|
|
setPwForm({ current_password: "", new_password: "", confirm_password: "" });
|
|
} else {
|
|
const data = await res.json();
|
|
setPwError(data.error || "Failed to change password");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<h1 className="text-xl font-semibold">Settings</h1>
|
|
|
|
{/* Username */}
|
|
<section className="bg-gray-900 border border-gray-800 rounded-lg p-5 space-y-4 max-w-md">
|
|
<div className="flex items-center gap-2">
|
|
<UserPen className="w-4 h-4 text-gray-400" />
|
|
<h2 className="text-sm font-medium text-gray-400 uppercase tracking-wider">
|
|
Change Username
|
|
</h2>
|
|
</div>
|
|
<form onSubmit={saveUsername} className="space-y-3">
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400">Username</label>
|
|
<input
|
|
value={newUsername}
|
|
onChange={(e) => setNewUsername(e.target.value)}
|
|
className="w-full bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
{usernameError && <p className="text-xs text-red-400">{usernameError}</p>}
|
|
{usernameSuccess && <p className="text-xs text-green-400">Username updated successfully.</p>}
|
|
<button
|
|
type="submit"
|
|
disabled={usernameLoading || newUsername.trim() === username}
|
|
className="bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white text-sm px-4 py-2 rounded-md transition-colors"
|
|
>
|
|
{usernameLoading ? "Saving..." : "Save"}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
{/* Password */}
|
|
<section className="bg-gray-900 border border-gray-800 rounded-lg p-5 space-y-4 max-w-md">
|
|
<div className="flex items-center gap-2">
|
|
<KeyRound className="w-4 h-4 text-gray-400" />
|
|
<h2 className="text-sm font-medium text-gray-400 uppercase tracking-wider">
|
|
Change Password
|
|
</h2>
|
|
</div>
|
|
<form onSubmit={savePassword} className="space-y-3">
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400">Current Password</label>
|
|
<input
|
|
type="password"
|
|
value={pwForm.current_password}
|
|
onChange={(e) => setPwForm((f) => ({ ...f, current_password: e.target.value }))}
|
|
className="w-full bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400">New Password</label>
|
|
<input
|
|
type="password"
|
|
value={pwForm.new_password}
|
|
onChange={(e) => setPwForm((f) => ({ ...f, new_password: e.target.value }))}
|
|
className="w-full bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400">Confirm New Password</label>
|
|
<input
|
|
type="password"
|
|
value={pwForm.confirm_password}
|
|
onChange={(e) => setPwForm((f) => ({ ...f, confirm_password: e.target.value }))}
|
|
className="w-full bg-gray-800 border border-gray-700 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
{pwError && <p className="text-xs text-red-400">{pwError}</p>}
|
|
{pwSuccess && <p className="text-xs text-green-400">Password changed successfully.</p>}
|
|
<button
|
|
type="submit"
|
|
disabled={pwLoading}
|
|
className="bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white text-sm px-4 py-2 rounded-md transition-colors"
|
|
>
|
|
{pwLoading ? "Saving..." : "Save"}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|