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