Improved design
Some checks failed
ci / test (push) Has been cancelled
ci / Check if version upgrade (push) Has been cancelled
ci / create_github_release (push) Has been cancelled

This commit is contained in:
Tom Butcher 2025-08-04 01:33:44 +01:00
parent f7789858fa
commit e23ac4cc27
35 changed files with 928 additions and 956 deletions

View File

@ -30,7 +30,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;
const { realm, auth, url, message, isAppInitiatedAction } = kcContext;
const { realm, auth, url, message, isAppInitiatedAction, client } = kcContext;
const isMobile = useMediaQuery({ maxWidth: 600 });
@ -105,7 +105,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
colorPrimary: "rgba(212, 0, 255, 1)",
colorLink: "#6E00FF",
colorLinkHover: "#b175ff",
borderRadius: 15
borderRadius: 20
},
algorithm: darkMode ? theme.darkAlgorithm : theme.defaultAlgorithm
}}
@ -113,35 +113,70 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<Layout style={{ minHeight: "var(--unit-100vh)", maxHeight: "var(--unit-100vh)" }}>
<ParticlesBackground />
{loading == true ? (<div className="loadingOverlay" style={{backgroundColor: darkMode ? '#000000' : '#ffffff'}}>
{loading == true ? (
<div className="loadingOverlay" style={{ backgroundColor: darkMode ? "#000000" : "#ffffff" }}>
<Spin />
</div>) : null}
</div>
) : null}
<Content
style={{
background: "#f5f5f5",
background: "#f5f5f5"
}}
>
<Flex vertical align="center" justify="center" style={{width: '100vw', height: "var(--unit-100vh)"}} gap={'50px'}>
{!isMobile && <img src="https://cdn.tombutcher.work/logos/logo-horizontal.svg" alt="Logo" style={{ height: "65px", padding: "0 30px", zIndex: 1 }} /> }
<Card style={{ borderRadius: isMobile ? "0px" : "20px", width: isMobile ? "100vw" : "450px", zIndex: 1, height: isMobile ? "100vh" : "unset", padding: "30px 15px", boxShadow: "0px 5px 15px 5px rgb(0 0 0 / 10%)",
}} variant="borderless" styles={{body: {
padding: 0
}}}><div style={{ height: "100%" }}>
<Flex vertical align="center" justify="center" style={{ width: "100vw", height: "var(--unit-100vh)" }} gap={"40px"}>
{!isMobile && (
<img
src="https://cdn.tombutcher.work/logos/logo-horizontal.svg"
alt="Logo"
style={{ height: "60px", padding: "0 30px", zIndex: 1, marginBottom: 5 }}
/>
)}
<Card
style={{
borderRadius: isMobile ? "0px" : "20px",
width: isMobile ? "100vw" : "450px",
zIndex: 1,
height: isMobile ? "100vh" : "unset",
padding: "23px 15px 30px 15px",
boxShadow: "0px 5px 15px 5px rgb(0 0 0 / 10%)"
}}
variant="borderless"
styles={{
body: {
padding: 0,
height: "100%"
}
}}
>
<Flex style={{ height: "100%" }} vertical>
<div style={{ height: "100%", margin: "0 15px" }}>
{isMobile && <><img src="https://cdn.tombutcher.work/logos/logo-auth.png" alt="Logo" style={{ width: "70%", margin: "0" }} /> <Divider style={{ margin: "24px 0" }} /></>}
{isMobile && (
<>
<img
src="https://cdn.tombutcher.work/logos/logo-auth.png"
alt="Logo"
style={{ width: "70%", margin: "0" }}
/>{" "}
<Divider style={{ margin: "24px 0" }} />
</>
)}
<div style={{ marginBottom: "24px" }}>
<div style={{ marginBottom: "10px" }}>
{(() => {
const node = !(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
<Title level={2} style={{ marginBottom: "16px" }}>
<Flex gap={"large"} align="center" style={{ paddingBottom: "8px" }}>
{client.attributes.logoUri && (
<img
src={client.attributes.logoUri}
alt={client.name || client.clientId}
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "5px" }}
/>
)}
<Title level={2} style={{ marginBottom: "0" }}>
{headerNode}
</Title>
</Flex>
) : (
<>
<div style={{ display: "flex", alignItems: "center", marginBottom: "24px" }}>
@ -169,7 +204,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
if (displayRequiredFields) {
return (
<>
{node}
<div style={{ marginBottom: "12px" }}>
<Text type="secondary">
@ -198,11 +232,11 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
: "info"
}
showIcon
style={{ marginBottom: "24px" }}
style={{ marginBottom: "26px" }}
/>
)}
</div>
<div style={{ overflowY: "auto", height: isMobile ? "100%" : "unset", maxHeight: isMobile ? "100%" : "calc(100vh / 2.25)", padding: "0 15px" }}>
{children}
{auth !== undefined && auth.showTryAnotherWayLink && (
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
@ -220,23 +254,30 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
</div>
</form>
)}
{displayInfo && <div style={{ marginTop: "24px", textAlign: "center" }}>{infoNode}</div>}
</div>
{displayInfo && <div style={{ marginTop: "24px", textAlign: "center" }}>{infoNode}</div>}
</Flex>
</Card>
{!isMobile && (
<Flex style={{ zIndex: 1 }} gap={"large"}>
<Text style={{ color: "#ffffff", fontWeight: 700 }}>© 2025</Text>
{enabledLanguages.length > 1 && (
<div style={{ margin: "0 15px"}}>
<Divider style={{ margin: "24px 0" }} />
<Dropdown menu={languageItems} trigger={["click"]}>
<Button style={{ width: "100%", textAlign: "left" }}>
<>
<Text style={{ color: "#ffffff" }}>|</Text>
<Dropdown menu={languageItems} trigger={["hover"]}>
<Text style={{ color: "#ffffff", fontWeight: 700 }}>
<Space>
{currentLanguage.label}
<GlobalOutlined />
</Space>
</Button>
</Text>
</Dropdown>
</div>
</>
)}
</div></Card></Flex></Content>
</Flex>
)}
</Flex>
</Content>
</Layout>
</ConfigProvider>
);

View File

@ -195,11 +195,16 @@ a.ant-typography,
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
input:-webkit-autofill:active,
input:-internal-autofill-selected {
-webkit-background-clip: text;
-webkit-text-fill-color: #ffffff;
transition: background-color 5000s ease-in-out 0s;
box-shadow: inset 0 0 20px 20px #23232329;
-webkit-text-fill-color: #7e8500;
box-shadow: inset 0 0 20px 20px #ffffff !important;
background-color: green !important
}
input[type="password"] {
letter-spacing: 5px;
}
.ant-form-item:last-child {
@ -216,3 +221,7 @@ input:-webkit-autofill:active {
.ant-alert {
margin-bottom: 5px;
}
.ant-form-item {
margin-bottom: 15px;
}

View File

@ -282,9 +282,9 @@ h2 span * {
font-family: "Grold-Rounded-Slim" !important;
text-transform: uppercase;
font-weight: 700;
font-size: 40px;
line-height: 0.9px;
letter-spacing: 0.02em;
font-size: 36px;
line-height: 0.9px !important;
letter-spacing: 0.01em;
}
h1 span {

View File

@ -19,6 +19,7 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
classes={classes}
headerNode={code.success ? msg("codeSuccessTitle") : msg("codeErrorTitle", code.error)}
>
<div style={{ margin: "0 15px" }}>
{code.success ? (
<>
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
@ -47,6 +48,7 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
/>
)
)}
</div>
</Template>
);
}

View File

@ -1,4 +1,4 @@
import { Form, Button, Typography, Alert, Divider, List, Flex, FormProps } from "antd";
import { Form, Button, Typography, Alert, List, Flex, FormProps } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
@ -53,7 +53,7 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
return (
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("deleteAccountConfirm")}>
<Form form={form} layout="vertical" onFinish={handleSubmit} style={{ width: "100%" }}>
<Form form={form} layout="vertical" onFinish={handleSubmit} style={{ width: "100%", padding: "0 15px" }}>
<Alert message={msg("irreversibleAction")} type="warning" showIcon style={{ marginBottom: 16 }} />
<Text>{msg("deletingImplies")}</Text>
@ -72,9 +72,8 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
<Text strong style={{ marginTop: 8 }}>
{msg("finalDeletionConfirmation")}
</Text>
<Divider />
<Flex gap={"middle"}>
<Flex gap={"middle"} style={{ marginTop: "20px" }}>
<Button
type="primary"
style={{ flexGrow: 2 }}

View File

@ -1,4 +1,4 @@
import { Button, Flex, Alert, Divider } from "antd";
import { Button, Flex, Alert } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
@ -17,15 +17,15 @@ export default function DeleteCredential(props: PageProps<Extract<KcContext, { p
displayMessage={false}
headerNode={msg("deleteCredentialTitle", credentialLabel)}
>
<div style={{ margin: "0 15px" }}>
<Alert
message={msg("deleteCredentialMessage", credentialLabel)}
type="warning"
showIcon
style={{
marginBottom: 24
marginBottom: 26
}}
/>
<Divider />
<form action={url.loginAction} method="POST">
<Flex gap="middle">
<Button
@ -46,6 +46,7 @@ export default function DeleteCredential(props: PageProps<Extract<KcContext, { p
</Button>
</Flex>
</form>
</div>
</Template>
);
}

View File

@ -2,9 +2,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { Alert, Button, Typography, Space, Divider } from "antd";
const { Title } = Typography;
import { Alert, Button, Space} from "antd";
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -18,19 +16,18 @@ export default function Error(props: PageProps<Extract<KcContext, { pageId: "err
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={false}
headerNode={<Title level={2}>{msg("errorTitle")}</Title>}
headerNode={msg("errorTitle")}
>
<Space direction="vertical" size="middle">
<div style={{ margin: "0 15px" }}>
<Space direction="vertical" size="middle" style={{ marginBottom: 24 }}>
<Alert message={<div dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />} type="error" showIcon />
</Space>
{!skipLink && client !== undefined && client.baseUrl !== undefined && (
<>
<Divider />
<Button type="primary" id="backToApplication" size={"large"} block href={client.baseUrl}>
{msg("backToApplication")}
</Button>
</>
)}
</div>
</Template>
);
}

View File

@ -24,9 +24,9 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
doUseDefaultCss={doUseDefaultCss}
classes={classes}
documentTitle={msgStr("frontchannel-logout.title")}
headerNode={<Title level={3}>{msg("frontchannel-logout.title")}</Title>}
headerNode={msg("frontchannel-logout.title")}
>
<Space direction="vertical" style={{ width: "100%" }}>
<Space direction="vertical" style={{ width: "100%", padding: "0 15px" }}>
<Paragraph>{msg("frontchannel-logout.message")}</Paragraph>
<List

View File

@ -1,5 +1,5 @@
import { useState } from "react";
import { Form, Button, Space, Divider } from "antd";
import { Form, Button, Space } from "antd";
import type { JSX } from "keycloakify/tools/JSX";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { PageProps } from "keycloakify/login/pages/PageProps";
@ -7,6 +7,7 @@ import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFo
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { CheckOutlined } from "@ant-design/icons";
import { useMediaQuery } from "react-responsive";
type IdpReviewUserProfileProps = PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
@ -17,6 +18,7 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
const { kcContext, i18n, doUseDefaultCss, Template, classes, UserProfileFormFields, doMakeUserConfirmPassword } = props;
const { msg, msgStr } = i18n;
const { url, messagesPerField } = kcContext;
const isMobile = useMediaQuery({ maxWidth: 600 });
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
return (
@ -26,11 +28,13 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={messagesPerField.exists("global")}
displayRequiredFields
headerNode={msg("loginIdpReviewProfileTitle")}
>
<Form id="kc-idp-review-profile-form" layout="vertical" method="post" action={url.loginAction} size="large">
<div className="kctbform" style={{ paddingBottom: 0 }}>
<div
className="kctbform"
style={{ paddingBottom: 0, overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px", marginBottom: 24 }}
>
<UserProfileFormFields
kcContext={kcContext}
i18n={i18n}
@ -39,8 +43,7 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
</div>
<Divider />
<Space direction="vertical" style={{ width: "100%" }}>
<Space direction="vertical" style={{ width: "100%", padding: "0 15px" }}>
<Form.Item>
<Button type="primary" htmlType="submit" icon={<CheckOutlined />} block size="large" disabled={!isFormSubmittable}>
{msgStr("doSubmit")}

View File

@ -1,9 +1,9 @@
import { Typography, Button, Space, Divider } from "antd";
import { Typography, Button, Space } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Title, Text } = Typography;
const { Text } = Typography;
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {
const { kcContext, i18n, Template } = props;
@ -29,20 +29,16 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
if (pageRedirectUri) {
return (
<>
<Divider />
<Button type="primary" block size={"large"} href={pageRedirectUri}>
<Button type="primary" block size={"large"} href={pageRedirectUri} style={{ marginTop: 24 }}>
{msg("backToApplication")}
</Button>
</>
);
}
if (actionUri) {
return (
<>
<Divider />
<Button type="primary" block size={"large"} href={actionUri}>
<Button type="primary" block size={"large"} href={actionUri} style={{ marginTop: 24 }}>
{msg("proceedWithAction")}
</Button>
</>
@ -52,8 +48,7 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
if (client.baseUrl) {
return (
<>
<Divider />
<Button type="primary" block size={"large"} href={client.baseUrl}>
<Button type="primary" block size={"large"} href={client.baseUrl} style={{ marginTop: 24 }}>
{msg("backToApplication")}
</Button>
</>
@ -64,19 +59,16 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
};
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={false}
displayMessage={false}
headerNode={<Title level={3}>{messageHeader}</Title>}
>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} displayMessage={false} headerNode={messageHeader}>
<div style={{ padding: "0 15px" }}>
<Space direction="vertical" size="middle">
<Text className="instruction">
<span dangerouslySetInnerHTML={{ __html: getMessageContent() }}></span>
</Text>
</Space>
{renderActionLink()}
</div>
</Template>
);
}

View File

@ -68,22 +68,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={false}
headerNode={
<>
{client.attributes.logoUri ? (
<Space align="start" direction="vertical">
<img
src={client.attributes.logoUri}
alt={client.name || client.clientId}
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "20px" }}
/>
<Title level={3}>{msg("loginAccountTitle")}</Title>
</Space>
) : (
<Title level={3}>{msg("loginAccountTitle")}</Title>
)}
</>
}
headerNode={msg("loginAccountTitle")}
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
infoNode={
<Space>
@ -123,6 +108,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
form={form}
initialValues={{ username: login.username || "", rememberMe: !!login.rememberMe }}
layout="vertical"
style={{ padding: "0 15px" }}
requiredMark={false}
onFinish={onFinish}
>
@ -154,7 +140,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
/>
</Form.Item>
<Row justify="space-between" align="middle">
<Row justify="space-between" align="middle" style={{ paddingTop: "4px" }}>
{realm.rememberMe && !usernameHidden && (
<Col>
<Form.Item name="rememberMe" valuePropName="checked" noStyle>
@ -171,7 +157,6 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
</Col>
)}
</Row>
<Divider />
<Form.Item>
<Button
type="primary"
@ -183,6 +168,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
disabled={isLoading}
loading={isLoading}
iconPosition={"end"}
style={{ marginTop: 24 }}
icon={
<img
src={"https://cdn.tombutcher.work/icons/auth/w-right.svg"}

View File

@ -5,6 +5,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { Typography, List, Button, Form, Input, Checkbox, Space, Steps, Divider, Flex } from "antd";
import { useMediaQuery } from "react-responsive";
const { Title, Text, Paragraph, Link } = Typography;
@ -67,6 +68,8 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
onFinish({ "cancel-aia": "true" });
};
const isMobile = useMediaQuery({ maxWidth: 600 });
return (
<Template
kcContext={kcContext}
@ -76,6 +79,8 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
headerNode={<Title level={3}>{msg("loginTotpTitle")}</Title>}
displayMessage={!messagesPerField.existsError("totp", "userLabel")}
>
<Form layout="vertical" onFinish={handleSubmit} style={{ margin: "0 auto" }}>
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px", marginBottom: 24 }}>
<Steps
direction="vertical"
current={3}
@ -180,7 +185,6 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
]}
/>
<Divider />
<Form layout="vertical" onFinish={handleSubmit} style={{ margin: "0 auto" }}>
<Form.Item
label={
<>
@ -236,11 +240,11 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
aria-invalid={messagesPerField.existsError("userLabel")}
/>
</Form.Item>
</div>
<div style={{ padding: "0 15px" }}>
<Form.Item>
<LogoutOtherSessions kcClsx={kcClsx} i18n={i18n} />
</Form.Item>
<Divider />
<Form.Item>
<Flex gap={"middle"}>
<Button
@ -278,6 +282,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
) : null}
</Flex>
</Form.Item>
</div>
</Form>
</Template>
);

View File

@ -1,4 +1,4 @@
import { Button, Divider, Form, Space } from "antd";
import { Button, Form, Space } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
@ -10,8 +10,7 @@ export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext,
return (
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={{}} headerNode={msg("confirmLinkIdpTitle")}>
<Divider />
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<Space direction="vertical" size="large" style={{ width: "100%", padding: "0 15px" }}>
<Form id="kc-register-form" action={url.loginAction} method="post">
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Button type="default" htmlType="submit" id="updateProfile" name="submitAction" value="updateProfile" block size="large">

View File

@ -23,7 +23,7 @@ export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, {
</Space>
}
>
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<Space direction="vertical" size="large" style={{ width: "100%", padding: "0 15px" }}>
<Text id="instruction1" className="instruction">
<span style={{ fontWeight: "800" }}>{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}</span>
</Text>

View File

@ -1,9 +1,11 @@
import { useState } from "react";
import { Button, Divider, Form, Input } from "antd";
import { Button, Typography, Form, Input } from "antd";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Text } = Typography;
export default function LoginOauth2DeviceVerifyUserCode(
props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>
) {
@ -42,18 +44,20 @@ export default function LoginOauth2DeviceVerifyUserCode(
return (
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={{}} headerNode={msg("oauth2DeviceVerificationTitle")}>
<Form id="kc-user-verify-device-user-code-form" layout="vertical" onFinish={handleSubmit}>
<Form.Item label={msg("verifyOAuth2DeviceUserCode")} name="device_user_code" rules={[{ required: true, message: "Required" }]}>
<Form id="kc-user-verify-device-user-code-form" layout="vertical" onFinish={handleSubmit} style={{ padding: "0 15px" }}>
<Text>{msg("verifyOAuth2DeviceUserCode")}</Text>
<Form.Item label={"Code"} name="device_user_code" rules={[{ required: true, message: "Required" }]}>
<Input
id="device-user-code"
name="device_user_code"
style={{ marginBottom: "10px" }}
autoComplete="off"
autoFocus
size="large"
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
/>
</Form.Item>
<Divider />
<Form.Item>
<Button
type="primary"

View File

@ -4,7 +4,7 @@ import { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { Typography, Space, List, Button, Flex, Divider, Alert } from "antd";
const { Title, Text, Link } = Typography;
const { Text, Link } = Typography;
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
@ -47,31 +47,15 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
doUseDefaultCss={doUseDefaultCss}
classes={classes}
bodyClassName="oauth"
headerNode={
<>
{client.attributes.logoUri ? (
<Space align="start" direction="vertical">
<img
src={client.attributes.logoUri}
alt={client.name || client.clientId}
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "20px" }}
/>
<Title level={3}>
{client.name ? msg("oauthGrantTitle", advancedMsgStr(client.name)) : msg("oauthGrantTitle", client.clientId)}
</Title>
</Space>
) : (
<Title level={3}>
{client.name ? msg("oauthGrantTitle", advancedMsgStr(client.name)) : msg("oauthGrantTitle", client.clientId)}
</Title>
)}
</>
}
headerNode={client.name ? msg("oauthGrantTitle", advancedMsgStr(client.name)) : msg("oauthGrantTitle", client.clientId)}
>
<div style={{ margin: "0 15px" }}>
<Alert message={msg("oauthGrantRequest")} type="warning" style={{ marginBottom: "20px" }} showIcon></Alert>
{(client.attributes.policyUri || client.attributes.tosUri) && (
<Alert
message={client.name ? msg("oauthGrantInformation", advancedMsgStr(client.name)) : msg("oauthGrantInformation", client.clientId)}
message={
client.name ? msg("oauthGrantInformation", advancedMsgStr(client.name)) : msg("oauthGrantInformation", client.clientId)
}
type="info"
style={{ marginBottom: "20px" }}
showIcon
@ -103,7 +87,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
/>
)}
{(client.attributes.policyUri || client.attributes.tosUri) && (
<>
<div style={{ marginTop: 10 }}>
<Space direction="vertical" style={{ marginTop: "12px" }}>
{client.attributes.tosUri && (
<Text>
@ -122,12 +106,9 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
</Text>
)}
</Space>
</>
</div>
)}
<Divider />
<Flex gap="middle">
<Flex gap="middle" style={{ marginTop: 24 }}>
<Button
type="primary"
style={{ flexGrow: 1 }}
@ -165,6 +146,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
{msgStr("doNo")}
</Button>
</Flex>
</div>
</Template>
);
}

View File

@ -1,5 +1,5 @@
import { useState } from "react";
import { Form, Input, Button, Radio, List, Typography, Space, Divider } from "antd";
import { Form, Input, Button, Radio, List, Typography, Space } from "antd";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
@ -51,7 +51,7 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
displayMessage={!messagesPerField.existsError("totp")}
headerNode={msg("doLogIn")}
>
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<Space direction="vertical" size="large" style={{ width: "100%", padding: "0 15px" }}>
<Form id="kc-otp-login-form" layout="vertical" onFinish={handleSubmit}>
{otpLogin.userOtpCredentials.length > 1 && (
<Form.Item name="selectedCredentialId">
@ -96,7 +96,6 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
/>
</Form.Item>
<Divider />
<Form.Item>
<Button
type="primary"
@ -107,6 +106,7 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
size="large"
disabled={isSubmitLoading}
loading={isSubmitLoading}
style={{ marginTop: 10 }}
iconPosition={"end"}
icon={
<img

View File

@ -3,7 +3,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Title, Text, Link } = Typography;
const { Text, Link } = Typography;
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -11,14 +11,8 @@ export default function LoginPageExpired(props: PageProps<Extract<KcContext, { p
const { msg } = i18n;
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={<Title level={3}>{msg("pageExpiredTitle")}</Title>}
>
<Space direction="vertical" size="small" style={{ width: "100%" }}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("pageExpiredTitle")}>
<Space direction="vertical" size="small" style={{ width: "100%", margin: "0 15px" }}>
<Text id="instruction1">
{msg("pageExpiredMsg1")}{" "}
<Link id="loginRestartLink" href={url.loginRestartFlowUrl}>

View File

@ -1,19 +1,19 @@
import { useState } from "react";
import { Form, Input, Button, Divider, Typography, Space, FormProps } from "antd";
import { Form, Input, Button, Typography, FormProps } from "antd";
import { EyeTwoTone, EyeInvisibleOutlined } from "@ant-design/icons";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Title, Link } = Typography;
const { Link } = Typography;
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>, I18n>) {
const { kcContext, i18n, Template } = props;
const [isLoading, setIsLoading] = useState(false);
const [form] = Form.useForm();
const { realm, url, messagesPerField, client } = kcContext;
const { realm, url, messagesPerField } = kcContext;
const { msg, msgStr } = i18n;
type FieldType = {
@ -47,25 +47,10 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={false}
headerNode={
<>
{client.attributes.logoUri ? (
<Space align="start" direction="vertical">
<img
src={client.attributes.logoUri}
alt={client.name || client.clientId}
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "20px" }}
/>
<Title level={3}>{msg("loginAccountTitle")}</Title>
</Space>
) : (
<Title level={3}>{msg("loginAccountTitle")}</Title>
)}
</>
}
headerNode={msg("loginAccountTitle")}
displayMessage={!messagesPerField.existsError("password")}
>
<Form form={form} layout="vertical" requiredMark={false} onFinish={onFinish}>
<Form form={form} layout="vertical" requiredMark={false} onFinish={onFinish} style={{ padding: "0 15px" }}>
<Form.Item
name="password"
label={msg("password")}
@ -92,13 +77,11 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
</Form.Item>
{realm.resetPasswordAllowed && (
<div style={{ textAlign: "right", marginBottom: 16 }}>
<div style={{ textAlign: "right", paddingTop: "2px" }}>
<Link href={url.loginResetCredentialsUrl}>{msg("doForgotPassword")}</Link>
</div>
)}
<Divider />
<Form.Item>
<Button
type="primary"
@ -110,6 +93,7 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
disabled={isLoading}
loading={isLoading}
iconPosition="end"
style={{ marginTop: 24 }}
icon={
<img
src={"https://cdn.tombutcher.work/icons/auth/w-right.svg"}

View File

@ -1,5 +1,5 @@
import { useState } from "react";
import { Alert, Flex, Button, Checkbox, Form, Input, Space, Typography, Divider } from "antd";
import { Alert, Flex, Button, Checkbox, Form, Input, Space, Typography } from "antd";
import { PrinterOutlined, SaveOutlined, CopyOutlined } from "@ant-design/icons";
import { useScript } from "keycloakify/login/pages/LoginRecoveryAuthnCodeConfig.useScript";
import type { PageProps } from "keycloakify/login/pages/PageProps";
@ -32,6 +32,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
classes={classes}
headerNode={msg("recovery-code-config-header")}
>
<div style={{ margin: "0 15px" }}>
<Space direction="vertical" size="middle">
<Space direction="vertical" size="large">
<Alert message={msg("recovery-code-config-warning-title")} type="warning" showIcon />
@ -68,14 +69,17 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
</Space>
<Form id="kc-recovery-codes-settings-form" method="post" action={kcContext.url.loginAction}>
<Input type="hidden" name="generatedRecoveryAuthnCodes" value={recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString} />
<Input
type="hidden"
name="generatedRecoveryAuthnCodes"
value={recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}
/>
<Input type="hidden" name="generatedAt" value={recoveryAuthnCodesConfigBean.generatedAt} />
<Input type="hidden" id="userLabel" name="userLabel" value={msgStr("recovery-codes-label-default")} />
<LogoutOtherSessions i18n={i18n} />
<Divider />
<div style={{ marginTop: 24 }}>
{isAppInitiatedAction ? (
<Space>
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed}>
@ -90,7 +94,9 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
{msg("recovery-codes-action-complete")}
</Button>
)}
</div>
</Form>
</div>
</Template>
);
}

View File

@ -1,11 +1,9 @@
import { Form, Input, Button, Typography, Divider } from "antd";
import { Form, Input, Button } from "antd";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Title } = Typography;
export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-input.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
const { url, messagesPerField, recoveryAuthnCodesInputBean } = kcContext;
@ -20,10 +18,17 @@ export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcC
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
headerNode={<Title level={3}>{msg("auth-recovery-code-header")}</Title>}
headerNode={msg("auth-recovery-code-header")}
displayMessage={!hasError}
>
<Form id="kc-recovery-code-login-form" layout="vertical" method="post" action={url.loginAction} size="large">
<Form
id="kc-recovery-code-login-form"
layout="vertical"
method="post"
action={url.loginAction}
size="large"
style={{ padding: "0 15px" }}
>
<Form.Item
label={msg("auth-recovery-code-prompt", `${recoveryAuthnCodesInputBean.codeNumber}`)}
validateStatus={hasError ? "error" : ""}
@ -41,9 +46,9 @@ export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcC
>
<Input id="recoveryCodeInput" name="recoveryCodeInput" autoComplete="off" autoFocus aria-invalid={hasError} tabIndex={1} />
</Form.Item>
<Divider />
<Form.Item>
<Button type="primary" htmlType="submit" block size="large" id="kc-login" name="login">
<Button type="primary" htmlType="submit" block size="large" id="kc-login" name="login" style={{ marginTop: 12 }}>
{msgStr("doLogIn")}
</Button>
</Form.Item>

View File

@ -3,7 +3,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Text, Title } = Typography;
const { Text } = Typography;
export default function LoginResetOtp(props: PageProps<Extract<KcContext, { pageId: "login-reset-otp.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -17,9 +17,9 @@ export default function LoginResetOtp(props: PageProps<Extract<KcContext, { page
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={!messagesPerField.existsError("totp")}
headerNode={<Title level={3}>{msg("doLogIn")}</Title>}
headerNode={msg("doLogIn")}
>
<Form id="kc-otp-reset-form" layout="vertical" method="post" action={url.loginAction}>
<Form id="kc-otp-reset-form" layout="vertical" method="post" action={url.loginAction} style={{ padding: "0 15px" }}>
<Space direction="vertical" style={{ width: "100%" }}>
<Text id="kc-otp-reset-form-description">{msg("otp-reset-description")}</Text>
@ -32,12 +32,10 @@ export default function LoginResetOtp(props: PageProps<Extract<KcContext, { page
id={`kc-otp-credential-${index}`}
style={{
display: "block",
height: "40px",
lineHeight: "40px",
margin: "8px 0"
}}
>
<Space style={{ marginBottom: "3px" }}>
<Space style={{ paddingBottom: "10px" }}>
<img
src={"https://cdn.tombutcher.work/icons/auth/c-key.svg"}
width={14}

View File

@ -1,10 +1,12 @@
import React from "react";
import { Form, Input, Button, Alert, Space, Divider } from "antd";
import { Form, Input, Button, Alert, Space, Typography } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
const { Link } = Typography;
// Define form values interface
interface ResetPasswordFormValues {
username: string;
@ -22,9 +24,9 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
// Determine which label to use based on realm settings
const inputLabel = !realm.loginWithEmailAllowed ? msg("username") : !realm.registrationEmailAsUsername ? msg("usernameOrEmail") : msg("email");
const inputPrefix = !realm.loginWithEmailAllowed ? (
<img src={"/c-person.svg"} width={14} style={{ marginRight: "3px" }} />
<img src={"https://cdn.tombutcher.work/icons/auth/c-person.svg"} width={14} style={{ marginRight: "3px" }} />
) : (
<img src={"/c-at.svg"} width={14} />
<img src={"https://cdn.tombutcher.work/icons/auth/c-at.svg"} width={14} />
);
const handleSubmit = (): void => {
@ -41,11 +43,10 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={false}
infoNode={false}
displayMessage={!messagesPerField.existsError("username")}
headerNode={msg("emailForgotTitle")}
>
{displayInfo && displayMessage && <Alert message={infoMessage} type="info" showIcon style={{ marginBottom: 24 }} />}
<Form<ResetPasswordFormValues>
id="kc-reset-password-form"
layout="vertical"
@ -54,7 +55,10 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
onFinish={handleSubmit}
action={url.loginAction}
method="post"
style={{ padding: "0 15px" }}
>
{displayInfo && displayMessage && <Alert message={infoMessage} type="info" showIcon style={{ marginBottom: 24 }} />}
<Form.Item
label={inputLabel}
name="username"
@ -79,17 +83,15 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
aria-invalid={messagesPerField.existsError("username") ? "true" : "false"}
/>
</Form.Item>
<Divider />
<Form.Item style={{ marginBottom: 12 }}>
<Form.Item style={{ marginBottom: 22, marginTop: 26 }}>
<Button type="primary" htmlType="submit" size="large" block>
{msgStr("doSubmit")}
</Button>
</Form.Item>
<Space style={{ width: "100%", justifyContent: "center" }}>
<Button type="link" href={url.loginUrl}>
{msg("backToLogin")}
</Button>
<Link href={url.loginUrl}>{msg("backToLogin")}</Link>
</Space>
</Form>
</Template>

View File

@ -1,12 +1,10 @@
import { useState } from "react";
import { Button, Typography, Form, Input, Checkbox, Divider, Flex } from "antd";
import { Button, Form, Input, Checkbox, Flex } from "antd";
import { EyeOutlined, EyeInvisibleOutlined } from "@ant-design/icons";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Title } = Typography;
type FieldType = {
"password-new": string;
"password-confirm": string;
@ -56,9 +54,9 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
doUseDefaultCss={false}
classes={props.classes}
displayMessage={!messagesPerField.existsError("password", "password-confirm")}
headerNode={<Title level={2}>{msg("updatePasswordTitle")}</Title>}
headerNode={msg("updatePasswordTitle")}
>
<Form id="kc-passwd-update-form" layout="vertical" onFinish={handleSubmit}>
<Form id="kc-passwd-update-form" layout="vertical" onFinish={handleSubmit} style={{ padding: "0 15px" }}>
<Form.Item
label={msg("passwordNew")}
name="password-new"
@ -96,8 +94,6 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
<Checkbox>{msg("logoutOtherSessions")}</Checkbox>
</Form.Item>
<Divider />
<Form.Item>
<Flex gap="middle">
<Button
@ -105,6 +101,7 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
size="large"
htmlType="submit"
iconPosition="end"
style={{ marginTop: 6, flexGrow: 2 }}
icon={
<img
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
@ -114,7 +111,6 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
}
loading={isSubmitLoading}
disabled={isCancelLoading || isSubmitLoading}
style={{ flexGrow: 2 }}
>
{msgStr("doSubmit")}
</Button>

View File

@ -64,22 +64,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={false}
headerNode={
<>
{client.attributes.logoUri ? (
<Space align="start" direction="vertical">
<img
src={client.attributes.logoUri}
alt={client.name || client.clientId}
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "20px" }}
/>
<Title level={3}>{msg("loginAccountTitle")}</Title>
</Space>
) : (
<Title level={3}>{msg("loginAccountTitle")}</Title>
)}
</>
}
headerNode={msg("loginAccountTitle")}
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
infoNode={
<Space>
@ -124,6 +109,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
layout="vertical"
requiredMark={false}
onFinish={onFinish}
style={{ margin: "0 15px" }}
>
{!usernameHidden && (
<Form.Item
@ -140,7 +126,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
</Form.Item>
)}
<Row justify="space-between" align="middle">
<Row justify="space-between" align="middle" style={{ paddingTop: "2px" }}>
{realm.rememberMe && !usernameHidden && (
<Col>
<Form.Item name="rememberMe" valuePropName="checked" noStyle>
@ -152,8 +138,6 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
)}
</Row>
<Divider />
<Form.Item>
<Button
type="primary"
@ -165,6 +149,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
disabled={isLoading}
loading={isLoading}
iconPosition="end"
style={{ marginTop: 24 }}
icon={
<img
src={"https://cdn.tombutcher.work/icons/auth/w-right.svg"}

View File

@ -11,8 +11,8 @@ export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { p
const { url, user } = kcContext;
return (
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={{}} displayInfo headerNode={msg("emailVerifyTitle")}>
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={{}} displayInfo={false} headerNode={msg("emailVerifyTitle")}>
<Space direction="vertical" size="large" style={{ width: "100%", padding: "0 15px"}}>
<Text>{msg("emailVerifyInstruction1", user?.email ?? "")}</Text>
<Text>

View File

@ -1,4 +1,4 @@
import { Button, Typography, Divider } from "antd";
import { Button, Typography } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
@ -40,16 +40,11 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
};
return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={false}
classes={props.classes}
headerNode={<Title level={2}>{msg("logoutConfirmTitle")}</Title>}
>
<div id="kc-logout-confirm">
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={props.classes} headerNode={msg("logoutConfirmTitle")}>
<div id="kc-logout-confirm" style={{ margin: "0 15px" }}>
<div style={{ marginBottom: "25px" }}>
<Text className="instruction">{msg("logoutConfirmHeader")}</Text>
<Divider />
</div>
<Button
type="primary"
size="large"

View File

@ -7,6 +7,7 @@ import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFo
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
import { useMediaQuery } from "react-responsive";
const { Title, Text, Link } = Typography;
@ -19,6 +20,7 @@ export default function Register(props: RegisterProps) {
const { kcContext, i18n, UserProfileFormFields, doMakeUserConfirmPassword } = props;
const { url, messagesPerField, recaptchaRequired, recaptchaVisible, recaptchaSiteKey, recaptchaAction, termsAcceptanceRequired } = kcContext;
const { msg, msgStr } = i18n;
const isMobile = useMediaQuery({ maxWidth: 600 });
const [areTermsAccepted, setAreTermsAccepted] = useState(false);
const [form] = Form.useForm();
@ -64,6 +66,7 @@ export default function Register(props: RegisterProps) {
return (
<Form form={form} layout="vertical" name="registerForm" requiredMark onFinish={onFinish}>
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px" }}>
<UserProfileFormFields
layout="vertical"
kcContext={kcContext}
@ -72,7 +75,8 @@ export default function Register(props: RegisterProps) {
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
onIsFormSubmittableValueChange={() => {}}
/>
</div>
<div style={{ padding: "0 15px" }}>
{termsAcceptanceRequired && (
<TermsAcceptance
i18n={i18n}
@ -87,8 +91,7 @@ export default function Register(props: RegisterProps) {
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} data-action={recaptchaAction} />
</Form.Item>
)}
<Divider />
<Form.Item>
<Form.Item style={{ marginTop: 24 }}>
<Space direction="vertical" style={{ width: "100%" }}>
{recaptchaRequired && !recaptchaVisible && recaptchaAction !== undefined ? (
<Button
@ -125,6 +128,7 @@ export default function Register(props: RegisterProps) {
</div>
</Space>
</Form.Item>
</div>
</Form>
);
}

View File

@ -38,7 +38,7 @@ export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageI
return (
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={doUseDefaultCss} classes={classes} headerNode={msg("saml.post-form.title")}>
<Flex align="middle" justify="start" gap={"middle"}>
<Flex align="middle" justify="start" gap={"middle"} style={{ padding: '0 15px'}}>
<Spin indicator={antIcon} />
<Text style={{ margin: 0 }}>{msg("saml.post-form.message")}</Text>

View File

@ -34,7 +34,7 @@ export default function SelectAuthenticator(props: PageProps<Extract<KcContext,
</>
}
>
<form id="kc-select-credential-form" action={url.loginAction} method="post">
<form id="kc-select-credential-form" action={url.loginAction} method="post" style={{ padding: '0 15px'}}>
<List
dataSource={auth.authenticationSelections}
itemLayout="horizontal"

View File

@ -1,4 +1,4 @@
import { Button, Divider, Typography, Card, Flex } from "antd";
import { Button, Typography, Card, Flex } from "antd";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
@ -38,14 +38,14 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
doUseDefaultCss={false}
classes={props.classes}
displayMessage={false}
headerNode={<Title level={2}>{msg("termsTitle")}</Title>}
headerNode={msg("termsTitle")}
>
<div className="terms-container">
<div className="terms-container" style={{ padding: "0 15px" }}>
{/* Scrollable terms box */}
<Card
style={{
marginBottom: 24,
maxHeight: "300px",
marginBottom: 26,
maxHeight: "250px",
overflow: "auto"
}}
bordered={true}
@ -54,7 +54,6 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
<Paragraph>{msg("termsText")}</Paragraph>
</div>
</Card>
<Divider />
<Flex gap="middle">
<Button
type="primary"

View File

@ -1,7 +1,7 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { Button, Checkbox, Flex, Divider } from "antd";
import { Button, Checkbox, Flex } from "antd";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
import type { PageProps } from "keycloakify/login/pages/PageProps";
@ -37,8 +37,8 @@ export default function UpdateEmail(props: UpdateEmailProps) {
displayMessage={messagesPerField.exists("global")}
headerNode={msg("updateEmailTitle")}
>
<form id="kc-update-email-form" action={url.loginAction} method="post">
<div style={{ marginBottom: 24 }} className="kctbform">
<form id="kc-update-email-form" action={url.loginAction} method="post" style={{ padding: '0 15px'}}>
<div style={{ marginBottom: 15 }} className="kctbform">
{/* Keep original UserProfileFormFields component, but wrap in Ant Design styling */}
<UserProfileFormFields
kcContext={kcContext}
@ -54,7 +54,6 @@ export default function UpdateEmail(props: UpdateEmailProps) {
{msg("logoutOtherSessions")}
</Checkbox>
</div>
<Divider />
<Flex gap={"middle"}>
<Button
style={{ flexGrow: 2 }}

View File

@ -1,5 +1,5 @@
import React, { Fragment } from "react";
import { Button, Form, Input, List, Typography, Space, Divider } from "antd";
import { Button, Form, Input, List, Typography, Space } from "antd";
import { DesktopOutlined, MobileOutlined } from "@ant-design/icons";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
@ -24,7 +24,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
const [isLoading, setIsLoading] = React.useState(false);
const { url, realm, registrationDisabled, authenticators, shouldDisplayAuthenticators, client } = kcContext;
const { url, realm, registrationDisabled, authenticators, shouldDisplayAuthenticators, client, message } = kcContext;
const { msg, msgStr, advancedMsg } = i18n;
@ -37,7 +37,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
});
React.useEffect(() => {
if (isLoading) return;
if (isLoading || message != undefined) return;
const timer = setTimeout(() => {
const btn = document.getElementById(authButtonId) as HTMLButtonElement | null;
if (btn && !btn.disabled) {
@ -66,23 +66,9 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
<Link href={url.registrationUrl}>{msg("doRegister")}</Link>
</Space>
}
headerNode={
<>
{client.attributes.logoUri ? (
<Space align="start" direction="vertical">
<img
src={client.attributes.logoUri}
alt={client.name || client.clientId}
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "20px" }}
/>
<Title level={3}>{msg("webauthn-login-title")}</Title>
</Space>
) : (
<Title level={3}>{msg("webauthn-login-title")}</Title>
)}
</>
}
headerNode={msg("webauthn-login-title")}
>
<div style={{ padding: "0 15px" }}>
<Form id="webauth" action={url.loginAction} method="post" layout="vertical">
<Input type="hidden" id="clientDataJSON" name="clientDataJSON" />
<Input type="hidden" id="authenticatorData" name="authenticatorData" />
@ -103,6 +89,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
{shouldDisplayAuthenticators && (
<>
<List
style={{ marginBottom: "30px" }}
bordered
dataSource={authenticators.authenticators as Authenticator[]}
renderItem={(authenticator: Authenticator, i: number) => (
@ -143,9 +130,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
/>
</List.Item>
)}
style={{ marginBottom: 0 }}
/>
<Divider />
</>
)}
</>
@ -172,6 +157,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
<span>{msgStr("webauthn-doAuthenticate")}</span>
</Button>
</Form.Item>
</div>
</Template>
);
}

View File

@ -64,7 +64,7 @@ export default function WebauthnError(props: PageProps<Extract<KcContext, { page
displayMessage
headerNode={msg("webauthn-error-title")}
>
<Form onFinish={handleSubmit}>
<Form onFinish={handleSubmit} style={{ padding: '0 15px'}}>
<Form.Item>
<Flex gap={"middle"}>
<Button

View File

@ -1,12 +1,10 @@
import React, { useState } from "react";
import { Button, Form, Checkbox, Typography, Divider, Flex } from "antd";
import { Button, Form, Checkbox, Flex } from "antd";
import { useScript } from "keycloakify/login/pages/WebauthnRegister.useScript";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
const { Title } = Typography;
interface LogoutOtherSessionsProps {
i18n: I18n;
}
@ -17,7 +15,7 @@ function LogoutOtherSessions(props: LogoutOtherSessionsProps): React.ReactElemen
const { msg } = i18n;
return (
<Form.Item name="logout-sessions" valuePropName="checked" initialValue={true}>
<Form.Item name="logout-sessions" valuePropName="checked" initialValue={true} style={{ paddingBottom: "24px" }}>
<Checkbox id="logout-sessions" name="logout-sessions" value="on">
{msg("logoutOtherSessions")}
</Checkbox>
@ -67,7 +65,8 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
};
return (
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} headerNode={<Title level={2}>{msg("webauthn-login-title")}</Title>}>
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} headerNode={msg("webauthn-login-title")}>
<div style={{ margin: "0 15px" }}>
<Form id="register" layout="vertical" action={url.loginAction} method="post">
{/* Hidden fields for WebAuthn registration data */}
<input type="hidden" id="clientDataJSON" name="clientDataJSON" />
@ -79,7 +78,6 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
<LogoutOtherSessions i18n={i18n} />
</Form>
<Divider />
<Form.Item>
<Flex gap={"middle"}>
<Button
@ -117,6 +115,7 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
)}
</Flex>
</Form.Item>
</div>
</Template>
);
}