feat(15-03): rewrite login page and auth hooks for OIDC

- Login page redirects to Logto instead of showing credential form
- AuthState uses string id (Logto sub claim) instead of number
- Remove useLogin, useSetup, useChangePassword hooks
- useLogout redirects to /logout (server-side OIDC logout)
- Remove ChangePasswordSection from settings page
- Update UserMenu to use new useLogout API
- Settings page shows API keys section when authenticated
This commit is contained in:
2026-04-04 20:52:58 +02:00
parent f7c9f3dc94
commit 79b27b6bcc
4 changed files with 37 additions and 186 deletions

View File

@@ -3,7 +3,6 @@ import { useRef, useState } from "react";
import {
useApiKeys,
useAuth,
useChangePassword,
useCreateApiKey,
useDeleteApiKey,
} from "../hooks/useAuth";
@@ -16,9 +15,9 @@ import type { Currency, WeightUnit } from "../lib/formatters";
const UNITS: WeightUnit[] = ["g", "oz", "lb", "kg"];
const CURRENCIES: { value: Currency; label: string }[] = [
{ value: "USD", label: "$" },
{ value: "EUR", label: "" },
{ value: "GBP", label: "£" },
{ value: "JPY", label: "¥" },
{ value: "EUR", label: "\u20AC" },
{ value: "GBP", label: "\u00A3" },
{ value: "JPY", label: "\u00A5" },
{ value: "CAD", label: "CA$" },
{ value: "AUD", label: "A$" },
];
@@ -27,66 +26,6 @@ export const Route = createFileRoute("/settings")({
component: SettingsPage,
});
function ChangePasswordSection() {
const changePassword = useChangePassword();
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [message, setMessage] = useState<{
type: "success" | "error";
text: string;
} | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setMessage(null);
try {
await changePassword.mutateAsync({ currentPassword, newPassword });
setMessage({ type: "success", text: "Password changed" });
setCurrentPassword("");
setNewPassword("");
} catch (err) {
setMessage({ type: "error", text: (err as Error).message });
}
}
return (
<form onSubmit={handleSubmit} className="space-y-3">
<h3 className="text-sm font-medium text-gray-900">Change Password</h3>
<input
type="password"
placeholder="Current password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-200"
/>
<input
type="password"
placeholder="New password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
minLength={6}
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-200"
/>
{message && (
<p
className={`text-sm ${message.type === "success" ? "text-green-600" : "text-red-600"}`}
>
{message.text}
</p>
)}
<button
type="submit"
disabled={changePassword.isPending}
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
>
{changePassword.isPending ? "..." : "Change Password"}
</button>
</form>
);
}
function ApiKeySection() {
const { data: keys } = useApiKeys();
const createKey = useCreateApiKey();
@@ -349,10 +288,8 @@ function SettingsPage() {
<ImportExportSection />
</div>
{auth?.user && (
{auth?.authenticated && (
<div className="bg-white rounded-xl border border-gray-100 p-5 space-y-6 mt-4">
<ChangePasswordSection />
<div className="border-t border-gray-100" />
<ApiKeySection />
</div>
)}