Upgraded UI to use floating design
This commit is contained in:
parent
a16c83967d
commit
6950f5f258
@ -6,11 +6,11 @@ import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
|||||||
import { useInitialize } from "keycloakify/login/Template.useInitialize";
|
import { useInitialize } from "keycloakify/login/Template.useInitialize";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
import type { KcContext } from "./KcContext";
|
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 { GlobalOutlined } from "@ant-design/icons";
|
||||||
import ParticlesBackground from "./ParticlesBackground";
|
import ParticlesBackground from "./ParticlesBackground";
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Content } = Layout;
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
@ -26,7 +26,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
i18n,
|
i18n,
|
||||||
doUseDefaultCss,
|
doUseDefaultCss,
|
||||||
children
|
children
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;
|
const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;
|
||||||
|
|
||||||
@ -35,6 +35,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
const isMobile = useMediaQuery({ maxWidth: 600 });
|
const isMobile = useMediaQuery({ maxWidth: 600 });
|
||||||
|
|
||||||
const [darkMode, setDarkMode] = useState(false);
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const windowQuery = window.matchMedia("(prefers-color-scheme:dark)");
|
const windowQuery = window.matchMedia("(prefers-color-scheme:dark)");
|
||||||
|
|
||||||
const darkModeChange = useCallback((event: MediaQueryListEvent) => {
|
const darkModeChange = useCallback((event: MediaQueryListEvent) => {
|
||||||
@ -50,8 +51,28 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
}, [windowQuery, darkModeChange]);
|
}, [windowQuery, darkModeChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(windowQuery.matches ? true : false);
|
|
||||||
setDarkMode(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(() => {
|
useEffect(() => {
|
||||||
@ -90,23 +111,27 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
>
|
>
|
||||||
<Layout style={{ minHeight: "var(--unit-100vh)", maxHeight: "var(--unit-100vh)" }}>
|
<Layout style={{ minHeight: "var(--unit-100vh)", maxHeight: "var(--unit-100vh)" }}>
|
||||||
<ParticlesBackground />
|
<ParticlesBackground />
|
||||||
<Sider
|
|
||||||
width={isMobile ? "100%" : "500px"}
|
{loading == true ? (<div className="loadingOverlay" style={{backgroundColor: darkMode ? '#000000' : '#ffffff'}}>
|
||||||
|
<Spin/>
|
||||||
|
</div>) : null}
|
||||||
|
|
||||||
|
<Content
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: darkMode ? "#141414" : "#fff",
|
background: "#f5f5f5",
|
||||||
boxShadow: "2px 0 8px rgba(0,0,0,0.1)",
|
|
||||||
padding: "40px 40px",
|
|
||||||
paddingRight: "20px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "center"
|
||||||
zIndex: 1
|
|
||||||
}}
|
}}
|
||||||
>
|
><Card style={{ borderRadius: isMobile ? "0px" : "20px", width: isMobile ? "100vw" : "500px", zIndex: 1, height: isMobile ? "100vh" : "unset", padding: "40px", boxShadow: "0px 5px 15px 5px rgb(0 0 0 / 25%)", paddingRight: "20px"
|
||||||
<Flex style={{ height: "100%" }} vertical>
|
}} variant="borderless" styles={{body: {
|
||||||
<div style={{ marginRight: "20px" }}>
|
padding: 0
|
||||||
<img src="https://cdn.tombutcher.work/logos/logo-auth.png" alt="Logo" style={{ width: "220px" }} />
|
}}}><div style={{ height: "100%" }}>
|
||||||
<Divider style={{ margin: "24px 0" }} />
|
<div style={{height: "100%", marginRight: "20px"}}>
|
||||||
|
|
||||||
|
<img src="https://cdn.tombutcher.work/logos/logo-auth-horizontal.png" alt="Logo" style={{ width: "100%", margin: "0" }} /> <Divider style={{ margin: "24px 0" }} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div style={{ marginBottom: "24px" }}>
|
<div style={{ marginBottom: "24px" }}>
|
||||||
{(() => {
|
{(() => {
|
||||||
@ -173,7 +198,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ overflowY: "auto", paddingRight: "20px", height: "100%" }}>
|
<div style={{ overflowY: "auto", height: isMobile ? "100%" : "unset", maxHeight: isMobile ? "100%" : "calc(100vh / 2.25)", paddingRight: "20px" }}>
|
||||||
{children}
|
{children}
|
||||||
{auth !== undefined && auth.showTryAnotherWayLink && (
|
{auth !== undefined && auth.showTryAnotherWayLink && (
|
||||||
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
|
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
|
||||||
@ -195,7 +220,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{enabledLanguages.length > 1 && (
|
{enabledLanguages.length > 1 && (
|
||||||
<div style={{ paddingRight: "20px" }}>
|
<div style={{ marginRight: "20px"}}>
|
||||||
<Divider style={{ margin: "24px 0" }} />
|
<Divider style={{ margin: "24px 0" }} />
|
||||||
<Dropdown menu={languageItems} trigger={["click"]}>
|
<Dropdown menu={languageItems} trigger={["click"]}>
|
||||||
<Button style={{ width: "100%", textAlign: "left" }}>
|
<Button style={{ width: "100%", textAlign: "left" }}>
|
||||||
@ -207,17 +232,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</div></Card></Content>
|
||||||
</Sider>
|
|
||||||
|
|
||||||
<Content
|
|
||||||
style={{
|
|
||||||
background: "#f5f5f5",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center"
|
|
||||||
}}
|
|
||||||
></Content>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -54,14 +54,14 @@ a.ant-typography,
|
|||||||
.ant-btn-color-link.ant-btn-variant-link:hover,
|
.ant-btn-color-link.ant-btn-variant-link:hover,
|
||||||
.ant-typography code {
|
.ant-typography code {
|
||||||
color: black;
|
color: black;
|
||||||
background: rgb(110, 0, 255);
|
background: rgb(110, 0, 255) !important;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
rgba(110, 0, 255, 1) 0%,
|
rgba(110, 0, 255, 1) 0%,
|
||||||
rgba(212, 0, 255, 1) 100%
|
rgba(212, 0, 255, 1) 100%
|
||||||
);
|
) !important ;
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text !important;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-checkbox-checked .ant-checkbox-inner {
|
.ant-checkbox-checked .ant-checkbox-inner {
|
||||||
@ -202,3 +202,14 @@ input:-webkit-autofill:active {
|
|||||||
transition: background-color 5000s ease-in-out 0s;
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
box-shadow: inset 0 0 20px 20px #23232329;
|
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;
|
||||||
|
}
|
||||||
@ -282,7 +282,7 @@ h2 span * {
|
|||||||
font-family: "Grold-Rounded-Slim" !important;
|
font-family: "Grold-Rounded-Slim" !important;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 40px;
|
font-size: 38px;
|
||||||
line-height: 0.9px;
|
line-height: 0.9px;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
|||||||
showIcon
|
showIcon
|
||||||
></Alert>
|
></Alert>
|
||||||
)}
|
)}
|
||||||
|
{oauth.clientScopesRequested.length > 0 && (
|
||||||
<List
|
<List
|
||||||
dataSource={oauth.clientScopesRequested}
|
dataSource={oauth.clientScopesRequested}
|
||||||
bordered={true}
|
bordered={true}
|
||||||
@ -101,7 +101,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
|||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
||||||
<>
|
<>
|
||||||
<Space direction="vertical" style={{ marginTop: "12px" }}>
|
<Space direction="vertical" style={{ marginTop: "12px" }}>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type { KcContext } from "../KcContext";
|
|||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
import { useScript } from "keycloakify/login/pages/WebauthnAuthenticate.useScript";
|
import { useScript } from "keycloakify/login/pages/WebauthnAuthenticate.useScript";
|
||||||
|
|
||||||
const { Title, Text, Paragraph } = Typography;
|
const { Title, Text, Link } = Typography;
|
||||||
|
|
||||||
// Define interfaces for better TypeScript support
|
// Define interfaces for better TypeScript support
|
||||||
interface Authenticator {
|
interface Authenticator {
|
||||||
@ -36,6 +36,17 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
i18n
|
i18n
|
||||||
});
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
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
|
// Function to determine the appropriate icon based on transport
|
||||||
const getAuthenticatorIcon = (iconClass: string): React.ReactNode => {
|
const getAuthenticatorIcon = (iconClass: string): React.ReactNode => {
|
||||||
if (iconClass.includes("mobile")) return <MobileOutlined />;
|
if (iconClass.includes("mobile")) return <MobileOutlined />;
|
||||||
@ -50,9 +61,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
doUseDefaultCss={false}
|
doUseDefaultCss={false}
|
||||||
displayInfo={realm.registrationAllowed && !registrationDisabled}
|
displayInfo={realm.registrationAllowed && !registrationDisabled}
|
||||||
infoNode={
|
infoNode={
|
||||||
<Paragraph>
|
<Space>
|
||||||
{msg("noAccount")} <a href={url.registrationUrl}>{msg("doRegister")}</a>
|
<Text>{msg("noAccount")}</Text>
|
||||||
</Paragraph>
|
<Link href={url.registrationUrl}>{msg("doRegister")}</Link>
|
||||||
|
</Space>
|
||||||
}
|
}
|
||||||
headerNode={
|
headerNode={
|
||||||
<>
|
<>
|
||||||
@ -66,7 +78,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
<Title level={3}>{msg("webauthn-login-title")}</Title>
|
<Title level={3}>{msg("webauthn-login-title")}</Title>
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
<Title level={3}>{msg("webauthn-login-title")}</Title>
|
null
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user