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 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<KcContext, I18n>) {
|
||||
@ -26,7 +26,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
i18n,
|
||||
doUseDefaultCss,
|
||||
children
|
||||
} = props;
|
||||
} = props;
|
||||
|
||||
const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;
|
||||
|
||||
@ -35,6 +35,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
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<KcContext, I18n>) {
|
||||
}, [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<KcContext, I18n>) {
|
||||
>
|
||||
<Layout style={{ minHeight: "var(--unit-100vh)", maxHeight: "var(--unit-100vh)" }}>
|
||||
<ParticlesBackground />
|
||||
<Sider
|
||||
width={isMobile ? "100%" : "500px"}
|
||||
|
||||
{loading == true ? (<div className="loadingOverlay" style={{backgroundColor: darkMode ? '#000000' : '#ffffff'}}>
|
||||
<Spin/>
|
||||
</div>) : null}
|
||||
|
||||
<Content
|
||||
style={{
|
||||
backgroundColor: darkMode ? "#141414" : "#fff",
|
||||
boxShadow: "2px 0 8px rgba(0,0,0,0.1)",
|
||||
padding: "40px 40px",
|
||||
paddingRight: "20px",
|
||||
background: "#f5f5f5",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
zIndex: 1
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Flex style={{ height: "100%" }} vertical>
|
||||
<div style={{ marginRight: "20px" }}>
|
||||
<img src="https://cdn.tombutcher.work/logos/logo-auth.png" alt="Logo" style={{ width: "220px" }} />
|
||||
<Divider style={{ margin: "24px 0" }} />
|
||||
><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"
|
||||
}} variant="borderless" styles={{body: {
|
||||
padding: 0
|
||||
}}}><div style={{ height: "100%" }}>
|
||||
<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" }}>
|
||||
{(() => {
|
||||
@ -173,7 +198,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
/>
|
||||
)}
|
||||
</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}
|
||||
{auth !== undefined && auth.showTryAnotherWayLink && (
|
||||
<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>
|
||||
|
||||
{enabledLanguages.length > 1 && (
|
||||
<div style={{ paddingRight: "20px" }}>
|
||||
<div style={{ marginRight: "20px"}}>
|
||||
<Divider style={{ margin: "24px 0" }} />
|
||||
<Dropdown menu={languageItems} trigger={["click"]}>
|
||||
<Button style={{ width: "100%", textAlign: "left" }}>
|
||||
@ -207,17 +232,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
</Dropdown>
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
</Sider>
|
||||
|
||||
<Content
|
||||
style={{
|
||||
background: "#f5f5f5",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
></Content>
|
||||
</div></Card></Content>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
||||
showIcon
|
||||
></Alert>
|
||||
)}
|
||||
|
||||
{oauth.clientScopesRequested.length > 0 && (
|
||||
<List
|
||||
dataSource={oauth.clientScopesRequested}
|
||||
bordered={true}
|
||||
@ -101,7 +101,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
|
||||
)}
|
||||
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
||||
<>
|
||||
<Space direction="vertical" style={{ marginTop: "12px" }}>
|
||||
|
||||
@ -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<Extract<KcContext,
|
||||
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
|
||||
const getAuthenticatorIcon = (iconClass: string): React.ReactNode => {
|
||||
if (iconClass.includes("mobile")) return <MobileOutlined />;
|
||||
@ -50,9 +61,10 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
||||
doUseDefaultCss={false}
|
||||
displayInfo={realm.registrationAllowed && !registrationDisabled}
|
||||
infoNode={
|
||||
<Paragraph>
|
||||
{msg("noAccount")} <a href={url.registrationUrl}>{msg("doRegister")}</a>
|
||||
</Paragraph>
|
||||
<Space>
|
||||
<Text>{msg("noAccount")}</Text>
|
||||
<Link href={url.registrationUrl}>{msg("doRegister")}</Link>
|
||||
</Space>
|
||||
}
|
||||
headerNode={
|
||||
<>
|
||||
@ -66,7 +78,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
||||
<Title level={3}>{msg("webauthn-login-title")}</Title>
|
||||
</Space>
|
||||
) : (
|
||||
<Title level={3}>{msg("webauthn-login-title")}</Title>
|
||||
null
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user