fix: fix loading states in forms

This commit is contained in:
Stavros
2025-05-30 18:14:33 +03:00
parent 75f07a9d7f
commit 34c8d16c7d
7 changed files with 54 additions and 24 deletions

View File

@@ -136,7 +136,7 @@ h4 {
} }
p { p {
@apply leading-7 [&:not(:first-child)]:mt-6; @apply leading-6 [&:not(:first-child)]:mt-6;
} }
blockquote { blockquote {

View File

@@ -12,6 +12,7 @@ import { isValidUrl } from "@/lib/utils";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { Navigate, useLocation, useNavigate } from "react-router"; import { Navigate, useLocation, useNavigate } from "react-router";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { useState } from "react";
export const ContinuePage = () => { export const ContinuePage = () => {
const { isLoggedIn } = useUserContext(); const { isLoggedIn } = useUserContext();
@@ -22,6 +23,7 @@ export const ContinuePage = () => {
const { domain, disableContinue } = useAppContext(); const { domain, disableContinue } = useAppContext();
const { search } = useLocation(); const { search } = useLocation();
const [loading, setLoading] = useState(false);
const searchParams = new URLSearchParams(search); const searchParams = new URLSearchParams(search);
const redirectURI = searchParams.get("redirect_uri"); const redirectURI = searchParams.get("redirect_uri");
@@ -34,10 +36,15 @@ export const ContinuePage = () => {
return <Navigate to="/logout" />; return <Navigate to="/logout" />;
} }
if (disableContinue) { const handleRedirect = () => {
setLoading(true);
window.location.href = DOMPurify.sanitize(redirectURI); window.location.href = DOMPurify.sanitize(redirectURI);
} }
if (disableContinue) {
handleRedirect();
}
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -63,14 +70,13 @@ export const ContinuePage = () => {
</CardHeader> </CardHeader>
<CardFooter className="flex flex-col items-stretch gap-2"> <CardFooter className="flex flex-col items-stretch gap-2">
<Button <Button
onClick={() => onClick={handleRedirect}
(window.location.href = DOMPurify.sanitize(redirectURI)) loading={loading}
}
variant="destructive" variant="destructive"
> >
{t("continueTitle")} {t("continueTitle")}
</Button> </Button>
<Button onClick={() => navigate("/logout")} variant="outline"> <Button onClick={() => navigate("/logout")} variant="outline" disabled={loading}>
{t("cancelTitle")} {t("cancelTitle")}
</Button> </Button>
</CardFooter> </CardFooter>
@@ -97,14 +103,13 @@ export const ContinuePage = () => {
</CardHeader> </CardHeader>
<CardFooter className="flex flex-col items-stretch gap-2"> <CardFooter className="flex flex-col items-stretch gap-2">
<Button <Button
onClick={() => onClick={handleRedirect}
(window.location.href = DOMPurify.sanitize(redirectURI)) loading={loading}
}
variant="warning" variant="warning"
> >
{t("continueTitle")} {t("continueTitle")}
</Button> </Button>
<Button onClick={() => navigate("/logout")} variant="outline"> <Button onClick={() => navigate("/logout")} variant="outline" disabled={loading}>
{t("cancelTitle")} {t("cancelTitle")}
</Button> </Button>
</CardFooter> </CardFooter>
@@ -120,9 +125,8 @@ export const ContinuePage = () => {
</CardHeader> </CardHeader>
<CardFooter className="flex flex-col items-stretch"> <CardFooter className="flex flex-col items-stretch">
<Button <Button
onClick={() => onClick={handleRedirect}
(window.location.href = DOMPurify.sanitize(redirectURI)) loading={loading}
}
> >
{t("continueTitle")} {t("continueTitle")}
</Button> </Button>

View File

@@ -126,6 +126,8 @@ export const LoginPage = () => {
icon={<GoogleIcon />} icon={<GoogleIcon />}
className="w-full" className="w-full"
onClick={() => oauthMutation.mutate("google")} onClick={() => oauthMutation.mutate("google")}
loading={oauthMutation.isPending && oauthMutation.variables === "google"}
disabled={oauthMutation.isPending || loginMutation.isPending}
/> />
)} )}
{configuredProviders.includes("github") && ( {configuredProviders.includes("github") && (
@@ -134,6 +136,8 @@ export const LoginPage = () => {
icon={<GithubIcon />} icon={<GithubIcon />}
className="w-full" className="w-full"
onClick={() => oauthMutation.mutate("github")} onClick={() => oauthMutation.mutate("github")}
loading={oauthMutation.isPending && oauthMutation.variables === "github"}
disabled={oauthMutation.isPending || loginMutation.isPending}
/> />
)} )}
{configuredProviders.includes("generic") && ( {configuredProviders.includes("generic") && (
@@ -142,6 +146,8 @@ export const LoginPage = () => {
icon={<GenericIcon />} icon={<GenericIcon />}
className="w-full" className="w-full"
onClick={() => oauthMutation.mutate("generic")} onClick={() => oauthMutation.mutate("generic")}
loading={oauthMutation.isPending && oauthMutation.variables === "generic"}
disabled={oauthMutation.isPending || loginMutation.isPending}
/> />
)} )}
</div> </div>
@@ -152,7 +158,7 @@ export const LoginPage = () => {
{userAuthConfigured && ( {userAuthConfigured && (
<LoginForm <LoginForm
onSubmit={(values) => loginMutation.mutate(values)} onSubmit={(values) => loginMutation.mutate(values)}
loading={loginMutation.isPending} loading={loginMutation.isPending || oauthMutation.isPending}
/> />
)} )}
{configuredProviders.length == 0 && ( {configuredProviders.length == 0 && (

View File

@@ -6,12 +6,19 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
export const NotFoundPage = () => { export const NotFoundPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const handleRedirect = () => {
setLoading(true);
navigate("/");
};
return ( return (
<Card className="min-w-xs sm:min-w-sm"> <Card className="min-w-xs sm:min-w-sm">
@@ -20,7 +27,7 @@ export const NotFoundPage = () => {
<CardDescription>{t("notFoundSubtitle")}</CardDescription> <CardDescription>{t("notFoundSubtitle")}</CardDescription>
</CardHeader> </CardHeader>
<CardFooter className="flex flex-col items-stretch"> <CardFooter className="flex flex-col items-stretch">
<Button onClick={() => navigate("/")}>{t("notFoundButton")}</Button> <Button onClick={handleRedirect} loading={loading}>{t("notFoundButton")}</Button>
</CardFooter> </CardFooter>
</Card> </Card>
); );

View File

@@ -8,18 +8,24 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { useUserContext } from "@/context/user-context";
import { TotpSchema } from "@/schemas/totp-schema"; import { TotpSchema } from "@/schemas/totp-schema";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { useId } from "react"; import { useId } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router"; import { Navigate, useLocation } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
export const TotpPage = () => { export const TotpPage = () => {
const { totpPending } = useUserContext();
if (!totpPending) {
return <Navigate to="/" />;
}
const { t } = useTranslation(); const { t } = useTranslation();
const { search } = useLocation(); const { search } = useLocation();
const navigate = useNavigate();
const formId = useId(); const formId = useId();
const searchParams = new URLSearchParams(search); const searchParams = new URLSearchParams(search);
@@ -34,7 +40,7 @@ export const TotpPage = () => {
}); });
setTimeout(() => { setTimeout(() => {
navigate( window.location.replace(
`/continue?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`, `/continue?redirect_uri=${encodeURIComponent(redirectUri ?? "")}`,
); );
}, 500); }, 500);

View File

@@ -6,6 +6,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { useState } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { Navigate, useLocation, useNavigate } from "react-router"; import { Navigate, useLocation, useNavigate } from "react-router";
@@ -23,6 +24,12 @@ export const UnauthorizedPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const handleRedirect = () => {
setLoading(true);
navigate("/login");
};
let i18nKey = "unauthorizedLoginSubtitle"; let i18nKey = "unauthorizedLoginSubtitle";
@@ -53,7 +60,7 @@ export const UnauthorizedPage = () => {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter className="flex flex-col items-stretch"> <CardFooter className="flex flex-col items-stretch">
<Button onClick={() => navigate("/login")}> <Button onClick={handleRedirect} loading={loading}>
{t("unauthorizedButton")} {t("unauthorizedButton")}
</Button> </Button>
</CardFooter> </CardFooter>

View File

@@ -255,16 +255,16 @@ func ParseUser(user string) (types.User, error) {
// Check if the user has a totp secret // Check if the user has a totp secret
if len(userSplit) == 2 { if len(userSplit) == 2 {
return types.User{ return types.User{
Username: userSplit[0], Username: strings.TrimSpace(userSplit[0]),
Password: userSplit[1], Password: strings.TrimSpace(userSplit[1]),
}, nil }, nil
} }
// Return the user struct // Return the user struct
return types.User{ return types.User{
Username: userSplit[0], Username: strings.TrimSpace(userSplit[0]),
Password: userSplit[1], Password: strings.TrimSpace(userSplit[1]),
TotpSecret: userSplit[2], TotpSecret: strings.TrimSpace(userSplit[2]),
}, nil }, nil
} }