diff --git a/src/login/Template.tsx b/src/login/Template.tsx index 2570375..710fec0 100644 --- a/src/login/Template.tsx +++ b/src/login/Template.tsx @@ -6,11 +6,11 @@ import { useSetClassName } from "keycloakify/tools/useSetClassName"; import { useInitialize } from "keycloakify/login/Template.useInitialize"; import type { I18n } from "./i18n"; import type { KcContext } from "./KcContext"; -import { Layout, Typography, Dropdown, Button, Alert, Space, Divider, ConfigProvider, Flex, theme } from "antd"; +import { Layout, Typography, Dropdown, Button, Alert, Space, Divider, Card, ConfigProvider, Flex, theme, Spin } from "antd"; import { GlobalOutlined } from "@ant-design/icons"; import ParticlesBackground from "./ParticlesBackground"; -const { Sider, Content } = Layout; +const { Content } = Layout; const { Title, Text } = Typography; export default function Template(props: TemplateProps) { @@ -26,7 +26,7 @@ export default function Template(props: TemplateProps) { i18n, doUseDefaultCss, children - } = props; +} = props; const { msg, msgStr, currentLanguage, enabledLanguages } = i18n; @@ -35,6 +35,7 @@ export default function Template(props: TemplateProps) { const isMobile = useMediaQuery({ maxWidth: 600 }); const [darkMode, setDarkMode] = useState(false); + const [loading, setLoading] = useState(true); const windowQuery = window.matchMedia("(prefers-color-scheme:dark)"); const darkModeChange = useCallback((event: MediaQueryListEvent) => { @@ -50,8 +51,28 @@ export default function Template(props: TemplateProps) { }, [windowQuery, darkModeChange]); useEffect(() => { - console.log(windowQuery.matches ? true : false); setDarkMode(windowQuery.matches ? true : false); + document.fonts.ready.then(() => { + // Wait for all images + const images = Array.from(document.images); + if (images.length === 0) { + setLoading(false); + return; + } + let loaded = 0; + function checkDone() { + loaded++; + if (loaded === images.length) setLoading(false); + } + images.forEach(img => { + if (img.complete) { + checkDone(); + } else { + img.addEventListener("load", checkDone); + img.addEventListener("error", checkDone); + } + }); + }); }, []); useEffect(() => { @@ -90,23 +111,27 @@ export default function Template(props: TemplateProps) { > - + + ) : null} + + - -
- Logo - + >
+
+ + Logo + +
{(() => { @@ -173,7 +198,7 @@ export default function Template(props: TemplateProps) { /> )}
-
+
{children} {auth !== undefined && auth.showTryAnotherWayLink && (
@@ -195,7 +220,7 @@ export default function Template(props: TemplateProps) {
{enabledLanguages.length > 1 && ( -
+
)} - - - - +
); diff --git a/src/login/custom.css b/src/login/custom.css index 81223cc..d8f993f 100644 --- a/src/login/custom.css +++ b/src/login/custom.css @@ -54,14 +54,14 @@ a.ant-typography, .ant-btn-color-link.ant-btn-variant-link:hover, .ant-typography code { color: black; - background: rgb(110, 0, 255); + background: rgb(110, 0, 255) !important; background: linear-gradient( 45deg, rgba(110, 0, 255, 1) 0%, rgba(212, 0, 255, 1) 100% - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; + ) !important ; + -webkit-background-clip: text !important; + -webkit-text-fill-color: transparent !important; } .ant-checkbox-checked .ant-checkbox-inner { @@ -202,3 +202,14 @@ input:-webkit-autofill:active { transition: background-color 5000s ease-in-out 0s; box-shadow: inset 0 0 20px 20px #23232329; } + +.ant-form-item:last-child { + margin-bottom: 0 !important; +} + +.loadingOverlay { + width: 100vw; + height: var(--unit-100vh); + z-index: 2; + position: fixed; +} \ No newline at end of file diff --git a/src/login/fonts.css b/src/login/fonts.css index 6744804..6c998b1 100644 --- a/src/login/fonts.css +++ b/src/login/fonts.css @@ -282,7 +282,7 @@ h2 span * { font-family: "Grold-Rounded-Slim" !important; text-transform: uppercase; font-weight: 700; - font-size: 40px; + font-size: 38px; line-height: 0.9px; letter-spacing: 0.02em; } diff --git a/src/login/pages/LoginOauthGrant.tsx b/src/login/pages/LoginOauthGrant.tsx index d48b4e5..374e25f 100644 --- a/src/login/pages/LoginOauthGrant.tsx +++ b/src/login/pages/LoginOauthGrant.tsx @@ -77,7 +77,7 @@ export default function LoginOauthGrant(props: PageProps )} - +{oauth.clientScopesRequested.length > 0 && ( )} /> - +)} {(client.attributes.policyUri || client.attributes.tosUri) && ( <> diff --git a/src/login/pages/WebauthnAuthenticate.tsx b/src/login/pages/WebauthnAuthenticate.tsx index e3f971d..8edf44a 100644 --- a/src/login/pages/WebauthnAuthenticate.tsx +++ b/src/login/pages/WebauthnAuthenticate.tsx @@ -6,7 +6,7 @@ import type { KcContext } from "../KcContext"; import type { I18n } from "../i18n"; import { useScript } from "keycloakify/login/pages/WebauthnAuthenticate.useScript"; -const { Title, Text, Paragraph } = Typography; +const { Title, Text, Link } = Typography; // Define interfaces for better TypeScript support interface Authenticator { @@ -36,6 +36,17 @@ export default function WebauthnAuthenticate(props: PageProps { + if (isLoading) return; + const timer = setTimeout(() => { + const btn = document.getElementById(authButtonId) as HTMLButtonElement | null; + if (btn && !btn.disabled) { + btn.click(); + } + }, 500); + return () => clearTimeout(timer); + }, [isLoading]); + // Function to determine the appropriate icon based on transport const getAuthenticatorIcon = (iconClass: string): React.ReactNode => { if (iconClass.includes("mobile")) return ; @@ -50,9 +61,10 @@ export default function WebauthnAuthenticate(props: PageProps - {msg("noAccount")} {msg("doRegister")} - + + {msg("noAccount")} + {msg("doRegister")} + } headerNode={ <> @@ -66,7 +78,7 @@ export default function WebauthnAuthenticate(props: PageProps{msg("webauthn-login-title")} ) : ( - {msg("webauthn-login-title")} + null )} }