import { useEffect, useState, useCallback } from "react"; import { useMediaQuery } from "react-responsive"; import { kcSanitize } from "keycloakify/lib/kcSanitize"; import type { TemplateProps } from "keycloakify/login/TemplateProps"; 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, Alert, Space, Divider, Card, ConfigProvider, Flex, theme, Spin } from "antd"; import { GlobalOutlined } from "@ant-design/icons"; import ParticlesBackground from "./ParticlesBackground"; import type { ReactNode } from "react"; const { Content } = Layout; const { Title, Text, Link } = Typography; export default function Template(props: TemplateProps) { const { displayInfo = false, displayMessage = true, headerNode, infoNode = null, documentTitle, bodyClassName, kcContext, i18n, doUseDefaultCss, children } = props; const { msg, msgStr, currentLanguage, enabledLanguages } = i18n; const { realm, auth, url, message, isAppInitiatedAction, client } = kcContext; 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) => { console.log(event.matches ? true : false); setDarkMode(event.matches ? true : false); }, []); useEffect(() => { windowQuery.addEventListener("change", darkModeChange); return () => { windowQuery.removeEventListener("change", darkModeChange); }; }, [windowQuery, darkModeChange]); useEffect(() => { 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(() => { document.title = documentTitle ?? msgStr("loginTitle", realm.displayName); }, []); useSetClassName({ qualifiedName: "body", className: bodyClassName ?? "" }); const { isReadyToRender } = useInitialize({ kcContext, doUseDefaultCss }); if (!isReadyToRender) { return null; } // Language menu items for dropdown const languageItems = { items: enabledLanguages.map((language, index) => ({ key: `language-${index}`, label: {language.label} })) }; const clientInfo = ( {client.attributes.logoUri ? ( {client.name ) : ( {client.name )}
{client.name}
); const showTryAnotherWayLink = (
{ document.forms["kc-select-try-another-way-form" as never].submit(); return false; }} > {msg("doTryAnotherWay")}
); const showMessage = displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction); const showImutableUsername = auth !== undefined && auth.showUsername && !auth.showResetCredentials; return ( {loading == true ? (
) : null} {!isMobile && ( <> {darkMode ? ( Logo ) : ( Logo )} )} {isMobile && ( Logo{" "} )} {headerNode && ( <> {headerNode} {isMobile && } )} {showImutableUsername && ( <> {auth.attemptedUsername} {msg("restartLoginTooltip")} {isMobile && } )} {/* App-initiated actions should not see warning messages about the need to complete the action during login. */} {showMessage && ( <> } type={ message.type === "error" ? "error" : message.type === "success" ? "success" : message.type === "warning" ? "warning" : "info" } showIcon style={{ margin: 0, width: "100%" }} /> {isMobile && } )} {children} {isMobile && ( {client.name && {clientInfo}} {auth !== undefined && auth.showTryAnotherWayLink && ( {showTryAnotherWayLink} )} {displayInfo && {infoNode}} )} {!isMobile && ( {client.name && {clientInfo}} {auth !== undefined && auth.showTryAnotherWayLink && ( {showTryAnotherWayLink} )} {displayInfo && {infoNode}} )} {!isMobile && ( © 2025 {enabledLanguages.length > 1 && ( <> | {currentLanguage.label} )} )}
); } function FooterCard({ children, darkMode = false }: { children?: ReactNode; darkMode?: boolean }) { const isMobile = useMediaQuery({ maxWidth: 600 }); return ( {children} ); }