diff --git a/src/client/routes/settings.tsx b/src/client/routes/settings.tsx index 0aab7cf..d3e9014 100644 --- a/src/client/routes/settings.tsx +++ b/src/client/routes/settings.tsx @@ -1,4 +1,12 @@ import { createFileRoute, Link } from "@tanstack/react-router"; +import { useState } from "react"; +import { + useApiKeys, + useAuth, + useChangePassword, + useCreateApiKey, + useDeleteApiKey, +} from "../hooks/useAuth"; import { useCurrency } from "../hooks/useCurrency"; import { useUpdateSetting } from "../hooks/useSettings"; import { useWeightUnit } from "../hooks/useWeightUnit"; @@ -18,10 +26,157 @@ 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 ( +
+

Change Password

+ 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" + /> + 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 && ( +

+ {message.text} +

+ )} + +
+ ); +} + +function ApiKeySection() { + const { data: keys } = useApiKeys(); + const createKey = useCreateApiKey(); + const deleteKey = useDeleteApiKey(); + const [name, setName] = useState(""); + const [newKey, setNewKey] = useState(null); + + async function handleCreate(e: React.FormEvent) { + e.preventDefault(); + const result = await createKey.mutateAsync({ name }); + setNewKey(result.key); + setName(""); + } + + return ( +
+

API Keys

+

+ API keys allow programmatic access to GearBox (e.g., from Claude Desktop + or scripts). +

+ + {newKey && ( +
+

+ Copy this key now — it won't be shown again: +

+ + {newKey} + + +
+ )} + +
+ setName(e.target.value)} + required + className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-200" + /> + +
+ + {keys && keys.length > 0 && ( +
+ {keys.map((key) => ( +
+
+ {key.name} + + {key.keyPrefix}... + +
+ +
+ ))} +
+ )} +
+ ); +} + function SettingsPage() { const unit = useWeightUnit(); const currency = useCurrency(); const updateSetting = useUpdateSetting(); + const { data: auth } = useAuth(); return (
@@ -99,6 +254,14 @@ function SettingsPage() {
+ + {auth?.user && ( +
+ +
+ +
+ )}
); }