fix(web): show change-password validation messages and failure reason

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-05 22:27:30 -04:00
1 parent a6d642616b
commit 436c463cc8
5 files changed
+24 -21

No files matched your search

@@ -47,8 +47,9 @@ import { Spinner } from "@/components/ui/spinner";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useResetUserState } from "@/hooks/use-reset-user-state";
import { authClient, signOut } from "@/lib/auth/client";
import { getErrorMessage } from "@/lib/error-messages";
import { getAuthErrorMessage, getErrorMessage } from "@/lib/error-messages";
import { useAppForm } from "@/lib/form";
import { formatFieldErrors } from "@/lib/form/fields";
import { orpc } from "@/lib/orpc/client";
import type { NormalizedImport } from "@sofa/api/schemas";
import { formatDate } from "@sofa/i18n/format";
@@ -611,7 +612,7 @@ function ChangePasswordDialog() {
revokeOtherSessions: value.revokeOtherSessions,
});
if (result.error) {
setError(t`Failed to change password`);
setError(getAuthErrorMessage(result.error, t`Failed to change password`));
return;
}
toast.success(t`Password updated`);
@@ -677,9 +678,7 @@ function ChangePasswordDialog() {
/>
{field.state.meta.errors.length > 0 && (
<p className="text-destructive text-xs">
{field.state.meta.errors
.map((e) => (typeof e === "string" ? e : ""))
.join(", ")}
{formatFieldErrors(field.state.meta.errors)}
</p>
)}
</div>
@@ -703,9 +702,7 @@ function ChangePasswordDialog() {
/>
{field.state.meta.errors.length > 0 && (
<p className="text-destructive text-xs">
{field.state.meta.errors
.map((e) => (typeof e === "string" ? e : ""))
.join(", ")}
{formatFieldErrors(field.state.meta.errors)}
</p>
)}
</div>
@@ -729,9 +726,7 @@ function ChangePasswordDialog() {
/>
{field.state.meta.errors.length > 0 && (
<p className="text-destructive text-xs">
{field.state.meta.errors
.map((e) => (typeof e === "string" ? e : ""))
.join(", ")}
{formatFieldErrors(field.state.meta.errors)}
</p>
)}
</div>
@@ -1,7 +1,7 @@
import { Trans, useLingui } from "@lingui/react/macro";
import { IconDoorEnter } from "@tabler/icons-react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useOptimistic, useState, useTransition } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useOptimistic, useTransition } from "react";
import { toast } from "sonner";
import { CardContent, CardDescription, CardTitle } from "@/components/ui/card";
@@ -12,9 +12,9 @@ import { orpc } from "@/lib/orpc/client";
export function RegistrationSection() {
const { t } = useLingui();
const { data, isPending: isLoading } = useQuery(orpc.admin.settings.get.queryOptions());
const [registrationOpen, setRegistrationOpen] = useState<boolean | null>(null);
const currentOpen = registrationOpen ?? data?.registration.open ?? false;
const currentOpen = data?.registration.open ?? false;
const [optimisticOpen, setOptimisticOpen] = useOptimistic(currentOpen);
const queryClient = useQueryClient();
const [isPending, startTransition] = useTransition();
const toggleMutation = useMutation(orpc.admin.settings.update.mutationOptions());
@@ -31,7 +31,7 @@ export function RegistrationSection() {
setOptimisticOpen(checked);
try {
await toggleMutation.mutateAsync({ registration: { open: checked } });
setRegistrationOpen(checked);
await queryClient.invalidateQueries({ queryKey: orpc.admin.settings.get.key() });
toast.success(checked ? t`Registration opened` : t`Registration closed`);
} catch {
toast.error(t`Failed to update registration setting`);
@@ -1,7 +1,7 @@
import { Trans, useLingui } from "@lingui/react/macro";
import { IconWorldUpload } from "@tabler/icons-react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useOptimistic, useState, useTransition } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useOptimistic, useTransition } from "react";
import { toast } from "sonner";
import { CardContent, CardDescription, CardTitle } from "@/components/ui/card";
@@ -12,9 +12,9 @@ import { orpc } from "@/lib/orpc/client";
export function UpdateCheckSection() {
const { t } = useLingui();
const { data, isPending: isLoading } = useQuery(orpc.admin.settings.get.queryOptions());
const [localEnabled, setLocalEnabled] = useState<boolean | null>(null);
const currentEnabled = localEnabled ?? data?.updateCheck.enabled ?? true;
const currentEnabled = data?.updateCheck.enabled ?? true;
const [optimisticEnabled, setOptimisticEnabled] = useOptimistic(currentEnabled);
const queryClient = useQueryClient();
const [isPending, startTransition] = useTransition();
const toggleMutation = useMutation(orpc.admin.settings.update.mutationOptions());
@@ -31,7 +31,7 @@ export function UpdateCheckSection() {
setOptimisticEnabled(checked);
try {
await toggleMutation.mutateAsync({ updateCheck: { enabled: checked } });
setLocalEnabled(checked);
await queryClient.invalidateQueries({ queryKey: orpc.admin.settings.get.key() });
toast.success(checked ? t`Update checks enabled` : t`Update checks disabled`);
} catch {
toast.error(t`Failed to update setting`);
+1
View File
@@ -4,4 +4,5 @@ export const updateToastDismissedVersionAtom = atomWithStorage<string | null>(
"sofa:update-toast-dismissed-version",
null,
createJSONStorage(() => sessionStorage),
{ getOnInit: true },
);
+7
View File
@@ -15,6 +15,13 @@ function normalizeErrors(errors: unknown[]): Array<{ message?: string }> {
return errors.map((e) => (typeof e === "string" ? { message: e } : (e as { message?: string })));
}
export function formatFieldErrors(errors: unknown[]): string {
return normalizeErrors(errors)
.map((e) => e.message ?? "")
.filter(Boolean)
.join(", ");
}
function TextField({
label,
description,