mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-03-07 07:11:58 +00:00
refactor: card title and layout tweaks (#675)
* refactor: card title and layout tweaks * chore: review comments * refactor: update domain warning screen
This commit is contained in:
@@ -18,6 +18,11 @@ import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TFunction } from "i18next";
|
||||
import { Mail, Shield, User, Users } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
type Scope = {
|
||||
id: string;
|
||||
@@ -27,7 +32,7 @@ type Scope = {
|
||||
};
|
||||
|
||||
const scopeMapIconProps = {
|
||||
className: "stroke-card stroke-2.5",
|
||||
className: "stroke-muted-foreground stroke-[1.75] h-4",
|
||||
};
|
||||
|
||||
const createScopeMap = (t: TFunction<"translation", undefined>): Scope[] => {
|
||||
@@ -124,13 +129,15 @@ export const AuthorizePage = () => {
|
||||
|
||||
if (getClientInfo.isLoading) {
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<Card className="gap-0">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">
|
||||
<CardTitle className="text-xl">
|
||||
{t("authorizeLoadingTitle")}
|
||||
</CardTitle>
|
||||
<CardDescription>{t("authorizeLoadingSubtitle")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{t("authorizeLoadingSubtitle")}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -145,41 +152,46 @@ export const AuthorizePage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm mx-4">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">
|
||||
{t("authorizeCardTitle", {
|
||||
app: getClientInfo.data?.name || "Unknown",
|
||||
})}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{scopes.includes("openid")
|
||||
? t("authorizeSubtitle")
|
||||
: t("authorizeSubtitleOAuth")}
|
||||
</CardDescription>
|
||||
<Card>
|
||||
<CardHeader className="mb-2">
|
||||
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
||||
<div className="bg-accent-foreground text-muted text-xl font-bold font-sans rounded-lg px-4 py-3">
|
||||
{getClientInfo.data?.name.slice(0, 1)}
|
||||
</div>
|
||||
<CardTitle className="text-xl">
|
||||
{t("authorizeCardTitle", {
|
||||
app: getClientInfo.data?.name || "Unknown",
|
||||
})}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-sm max-w-sm">
|
||||
{scopes.includes("openid")
|
||||
? t("authorizeSubtitle")
|
||||
: t("authorizeSubtitleOAuth")}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{scopes.includes("openid") && (
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{scopes.map((id) => {
|
||||
const scope = scopeMap.find((s) => s.id === id);
|
||||
if (!scope) return null;
|
||||
return (
|
||||
<div key={scope.id} className="flex flex-row items-center gap-3">
|
||||
<div className="p-2 flex flex-col items-center justify-center bg-card-foreground rounded-md">
|
||||
{scope.icon}
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="text-md">{scope.name}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{scope.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</CardContent>
|
||||
)}
|
||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||
<CardContent className="mb-2">
|
||||
{scopes.includes("openid") && (
|
||||
<div className="flex flex-wrap gap-2 items-center justify-center">
|
||||
{scopes.map((id) => {
|
||||
const scope = scopeMap.find((s) => s.id === id);
|
||||
if (!scope) return null;
|
||||
return (
|
||||
<Tooltip key={scope.id}>
|
||||
<TooltipTrigger className="flex flex-row justify-center items-center gap-1 rounded-full bg-secondary font-light pl-2 pr-4 py-1 border-border border">
|
||||
<div>{scope.icon}</div>
|
||||
<div className="text-sm text-accent-foreground">
|
||||
{scope.name}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{scope.description}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||
<Button
|
||||
onClick={() => authorizeMutation.mutate()}
|
||||
loading={authorizeMutation.isPending}
|
||||
|
||||
@@ -93,9 +93,9 @@ export const ContinuePage = () => {
|
||||
|
||||
if (showUntrustedWarning) {
|
||||
return (
|
||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">
|
||||
<Card role="alert" aria-live="assertive">
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">
|
||||
{t("continueUntrustedRedirectTitle")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -110,7 +110,7 @@ export const ContinuePage = () => {
|
||||
/>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||
<Button
|
||||
onClick={handleRedirect}
|
||||
loading={isLoading}
|
||||
@@ -132,9 +132,9 @@ export const ContinuePage = () => {
|
||||
|
||||
if (showInsecureWarning) {
|
||||
return (
|
||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">
|
||||
<Card role="alert" aria-live="assertive">
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">
|
||||
{t("continueInsecureRedirectTitle")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -147,7 +147,7 @@ export const ContinuePage = () => {
|
||||
/>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||
<Button
|
||||
onClick={handleRedirect}
|
||||
loading={isLoading}
|
||||
@@ -168,16 +168,16 @@ export const ContinuePage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">
|
||||
{t("continueRedirectingTitle")}
|
||||
</CardTitle>
|
||||
<CardDescription>{t("continueRedirectingSubtitle")}</CardDescription>
|
||||
</CardHeader>
|
||||
{showRedirectButton && (
|
||||
<CardFooter className="flex flex-col items-stretch">
|
||||
<Button onClick={handleRedirect}>
|
||||
<CardFooter>
|
||||
<Button className="w-full" onClick={handleRedirect}>
|
||||
{t("continueRedirectManually")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
|
||||
@@ -14,10 +14,10 @@ export const ErrorPage = () => {
|
||||
const error = searchParams.get("error") ?? "";
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">{t("errorTitle")}</CardTitle>
|
||||
<CardDescription className="flex flex-col gap-1.5">
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">{t("errorTitle")}</CardTitle>
|
||||
<CardDescription className="flex flex-col gap-3">
|
||||
{error ? (
|
||||
<>
|
||||
<p>{t("errorSubtitleInfo")}</p>
|
||||
|
||||
@@ -1,25 +1,47 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAppContext } from "@/context/app-context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export const ForgotPasswordPage = () => {
|
||||
const { forgotPasswordMessage } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">{t("forgotPasswordTitle")}</CardTitle>
|
||||
<CardDescription>
|
||||
<Markdown>{forgotPasswordMessage !== "" ? forgotPasswordMessage : t('forgotPasswordMessage')}</Markdown>
|
||||
</CardDescription>
|
||||
<CardTitle className="text-xl">{t("forgotPasswordTitle")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>
|
||||
<Markdown>
|
||||
{forgotPasswordMessage !== ""
|
||||
? forgotPasswordMessage
|
||||
: t("forgotPasswordMessage")}
|
||||
</Markdown>
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
navigate("/login");
|
||||
}}
|
||||
>
|
||||
{t("notFoundButton")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||
import { LoginSchema } from "@/schemas/login-schema";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Navigate, useLocation } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
@@ -48,6 +48,8 @@ export const LoginPage = () => {
|
||||
const redirectTimer = useRef<number | null>(null);
|
||||
const redirectButtonTimer = useRef<number | null>(null);
|
||||
|
||||
const formId = useId();
|
||||
|
||||
const searchParams = new URLSearchParams(search);
|
||||
const {
|
||||
values: props,
|
||||
@@ -187,9 +189,9 @@ export const LoginPage = () => {
|
||||
|
||||
if (isOauthAutoRedirect) {
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">
|
||||
<CardTitle className="text-xl">
|
||||
{t("loginOauthAutoRedirectTitle")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -218,9 +220,9 @@ export const LoginPage = () => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center text-3xl">{title}</CardTitle>
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-center text-xl">{title}</CardTitle>
|
||||
{providers.length > 0 && (
|
||||
<CardDescription className="text-center">
|
||||
{oauthProviders.length !== 0
|
||||
@@ -231,7 +233,7 @@ export const LoginPage = () => {
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{oauthProviders.length !== 0 && (
|
||||
<div className="flex flex-col gap-2 items-center justify-center">
|
||||
<div className="flex flex-col gap-2.5 items-center justify-center">
|
||||
{oauthProviders.map((provider) => (
|
||||
<OAuthButton
|
||||
key={provider.id}
|
||||
@@ -252,6 +254,7 @@ export const LoginPage = () => {
|
||||
<LoginForm
|
||||
onSubmit={(values) => loginMutate(values)}
|
||||
loading={loginIsPending || oauthIsPending}
|
||||
formId={formId}
|
||||
/>
|
||||
)}
|
||||
{providers.length == 0 && (
|
||||
@@ -260,6 +263,18 @@ export const LoginPage = () => {
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
{userAuthConfigured && (
|
||||
<Button
|
||||
className="w-full"
|
||||
type="submit"
|
||||
form={formId}
|
||||
loading={loginIsPending || oauthIsPending}
|
||||
>
|
||||
{t("loginSubmit")}
|
||||
</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,9 +52,9 @@ export const LogoutPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">{t("logoutTitle")}</CardTitle>
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">{t("logoutTitle")}</CardTitle>
|
||||
<CardDescription>
|
||||
{provider !== "local" && provider !== "ldap" ? (
|
||||
<Trans
|
||||
@@ -84,8 +84,10 @@ export const LogoutPage = () => {
|
||||
)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex flex-col items-stretch">
|
||||
<CardFooter>
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
loading={logoutMutation.isPending}
|
||||
onClick={() => logoutMutation.mutate()}
|
||||
>
|
||||
|
||||
@@ -21,13 +21,20 @@ export const NotFoundPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">{t("notFoundTitle")}</CardTitle>
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">{t("notFoundTitle")}</CardTitle>
|
||||
<CardDescription>{t("notFoundSubtitle")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex flex-col items-stretch">
|
||||
<Button onClick={handleRedirect} loading={loading}>{t("notFoundButton")}</Button>
|
||||
<CardFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleRedirect}
|
||||
loading={loading}
|
||||
>
|
||||
{t("notFoundButton")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -72,9 +72,9 @@ export const TotpPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">{t("totpTitle")}</CardTitle>
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">{t("totpTitle")}</CardTitle>
|
||||
<CardDescription>{t("totpSubtitle")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col items-center">
|
||||
@@ -83,8 +83,13 @@ export const TotpPage = () => {
|
||||
onSubmit={(values) => totpMutation.mutate(values)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col items-stretch">
|
||||
<Button form={formId} type="submit" loading={totpMutation.isPending}>
|
||||
<CardFooter>
|
||||
<Button
|
||||
className="w-full"
|
||||
form={formId}
|
||||
type="submit"
|
||||
loading={totpMutation.isPending}
|
||||
>
|
||||
{t("continueTitle")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
|
||||
@@ -47,9 +47,9 @@ export const UnauthorizedPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="min-w-xs sm:min-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl">{t("unauthorizedTitle")}</CardTitle>
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">{t("unauthorizedTitle")}</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans
|
||||
i18nKey={i18nKey}
|
||||
@@ -65,8 +65,13 @@ export const UnauthorizedPage = () => {
|
||||
/>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="flex flex-col items-stretch">
|
||||
<Button onClick={handleRedirect} loading={loading}>
|
||||
<CardFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleRedirect}
|
||||
loading={loading}
|
||||
>
|
||||
{t("unauthorizedButton")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
|
||||
Reference in New Issue
Block a user