Improved design
This commit is contained in:
parent
f7789858fa
commit
e23ac4cc27
@ -26,11 +26,11 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
i18n,
|
||||
doUseDefaultCss,
|
||||
children
|
||||
} = props;
|
||||
} = props;
|
||||
|
||||
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 });
|
||||
|
||||
@ -53,26 +53,26 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
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);
|
||||
// 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(() => {
|
||||
@ -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,130 +113,171 @@ 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'}}>
|
||||
<Spin/>
|
||||
</div>) : null}
|
||||
{loading == true ? (
|
||||
<div className="loadingOverlay" style={{ backgroundColor: darkMode ? "#000000" : "#ffffff" }}>
|
||||
<Spin />
|
||||
</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%" }}>
|
||||
<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" }} /></>}
|
||||
|
||||
|
||||
|
||||
|
||||
<div style={{ marginBottom: "24px" }}>
|
||||
{(() => {
|
||||
const node = !(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
|
||||
<Title level={2} style={{ marginBottom: "16px" }}>
|
||||
{headerNode}
|
||||
</Title>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ display: "flex", alignItems: "center", marginBottom: "24px" }}>
|
||||
<Flex gap={"small"}>
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/c-person.svg"}
|
||||
width={14}
|
||||
style={{ marginTop: "3px" }}
|
||||
/>
|
||||
<Text strong>{auth.attemptedUsername}</Text>
|
||||
</Flex>
|
||||
<Button
|
||||
type="link"
|
||||
href={url.loginRestartFlowUrl}
|
||||
title={msgStr("restartLoginTooltip")}
|
||||
style={{ marginLeft: "12px" }}
|
||||
>
|
||||
{msg("restartLoginTooltip")}
|
||||
</Button>
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
|
||||
if (displayRequiredFields) {
|
||||
return (
|
||||
<>
|
||||
|
||||
{node}
|
||||
<div style={{ marginBottom: "12px" }}>
|
||||
<Text type="secondary">
|
||||
<span style={{ color: "#ff4d4f" }}>*</span> {msg("requiredFields")}
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return node;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
||||
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
|
||||
<Alert
|
||||
message={<span dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />}
|
||||
type={
|
||||
message.type === "error"
|
||||
? "error"
|
||||
: message.type === "success"
|
||||
? "success"
|
||||
: message.type === "warning"
|
||||
? "warning"
|
||||
: "info"
|
||||
}
|
||||
showIcon
|
||||
style={{ marginBottom: "24px" }}
|
||||
/>
|
||||
)}
|
||||
</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">
|
||||
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||
<div style={{ textAlign: "center", margin: "16px 0" }}>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => {
|
||||
document.forms["kc-select-try-another-way-form" as never].submit();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{msg("doTryAnotherWay")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
{displayInfo && <div style={{ marginTop: "24px", textAlign: "center" }}>{infoNode}</div>}
|
||||
</div>
|
||||
|
||||
{enabledLanguages.length > 1 && (
|
||||
<div style={{ margin: "0 15px"}}>
|
||||
<Divider style={{ margin: "24px 0" }} />
|
||||
<Dropdown menu={languageItems} trigger={["click"]}>
|
||||
<Button style={{ width: "100%", textAlign: "left" }}>
|
||||
<Space>
|
||||
{currentLanguage.label}
|
||||
<GlobalOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<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 }}
|
||||
/>
|
||||
)}
|
||||
</div></Card></Flex></Content>
|
||||
<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" }} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
{(() => {
|
||||
const node = !(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
|
||||
<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" }}>
|
||||
<Flex gap={"small"}>
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/c-person.svg"}
|
||||
width={14}
|
||||
style={{ marginTop: "3px" }}
|
||||
/>
|
||||
<Text strong>{auth.attemptedUsername}</Text>
|
||||
</Flex>
|
||||
<Button
|
||||
type="link"
|
||||
href={url.loginRestartFlowUrl}
|
||||
title={msgStr("restartLoginTooltip")}
|
||||
style={{ marginLeft: "12px" }}
|
||||
>
|
||||
{msg("restartLoginTooltip")}
|
||||
</Button>
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
|
||||
if (displayRequiredFields) {
|
||||
return (
|
||||
<>
|
||||
{node}
|
||||
<div style={{ marginBottom: "12px" }}>
|
||||
<Text type="secondary">
|
||||
<span style={{ color: "#ff4d4f" }}>*</span> {msg("requiredFields")}
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return node;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
||||
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
|
||||
<Alert
|
||||
message={<span dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />}
|
||||
type={
|
||||
message.type === "error"
|
||||
? "error"
|
||||
: message.type === "success"
|
||||
? "success"
|
||||
: message.type === "warning"
|
||||
? "warning"
|
||||
: "info"
|
||||
}
|
||||
showIcon
|
||||
style={{ marginBottom: "26px" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
{auth !== undefined && auth.showTryAnotherWayLink && (
|
||||
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
|
||||
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||
<div style={{ textAlign: "center", margin: "16px 0" }}>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => {
|
||||
document.forms["kc-select-try-another-way-form" as never].submit();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{msg("doTryAnotherWay")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{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 && (
|
||||
<>
|
||||
<Text style={{ color: "#ffffff" }}>|</Text>
|
||||
<Dropdown menu={languageItems} trigger={["hover"]}>
|
||||
<Text style={{ color: "#ffffff", fontWeight: 700 }}>
|
||||
<Space>
|
||||
{currentLanguage.label}
|
||||
<GlobalOutlined />
|
||||
</Space>
|
||||
</Text>
|
||||
</Dropdown>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Content>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -19,34 +19,36 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
|
||||
classes={classes}
|
||||
headerNode={code.success ? msg("codeSuccessTitle") : msg("codeErrorTitle", code.error)}
|
||||
>
|
||||
{code.success ? (
|
||||
<>
|
||||
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
|
||||
<Space>
|
||||
<Paragraph style={{ margin: 0 }}>{msg("copyCodeInstruction")}</Paragraph>
|
||||
</Space>
|
||||
<div style={{ margin: "0 15px" }}>
|
||||
{code.success ? (
|
||||
<>
|
||||
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
|
||||
<Space>
|
||||
<Paragraph style={{ margin: 0 }}>{msg("copyCodeInstruction")}</Paragraph>
|
||||
</Space>
|
||||
|
||||
<Text strong code style={{ fontSize: "18px" }}>
|
||||
{code.code}
|
||||
</Text>
|
||||
</Space>
|
||||
</>
|
||||
) : (
|
||||
code.error && (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message={
|
||||
<div
|
||||
id="error"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(code.error)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Text strong code style={{ fontSize: "18px" }}>
|
||||
{code.code}
|
||||
</Text>
|
||||
</Space>
|
||||
</>
|
||||
) : (
|
||||
code.error && (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
message={
|
||||
<div
|
||||
id="error"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(code.error)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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,35 +17,36 @@ export default function DeleteCredential(props: PageProps<Extract<KcContext, { p
|
||||
displayMessage={false}
|
||||
headerNode={msg("deleteCredentialTitle", credentialLabel)}
|
||||
>
|
||||
<Alert
|
||||
message={msg("deleteCredentialMessage", credentialLabel)}
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{
|
||||
marginBottom: 24
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<form action={url.loginAction} method="POST">
|
||||
<Flex gap="middle">
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ flexGrow: 2 }}
|
||||
danger
|
||||
size="large"
|
||||
htmlType="submit"
|
||||
name="accept"
|
||||
icon={<img src={"/w-bin.svg"} width={14} style={{ marginTop: "0px", marginBottom: "3px" }} />}
|
||||
id="kc-accept"
|
||||
>
|
||||
{msgStr("doConfirmDelete")}
|
||||
</Button>
|
||||
<div style={{ margin: "0 15px" }}>
|
||||
<Alert
|
||||
message={msg("deleteCredentialMessage", credentialLabel)}
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{
|
||||
marginBottom: 26
|
||||
}}
|
||||
/>
|
||||
<form action={url.loginAction} method="POST">
|
||||
<Flex gap="middle">
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ flexGrow: 2 }}
|
||||
danger
|
||||
size="large"
|
||||
htmlType="submit"
|
||||
name="accept"
|
||||
icon={<img src={"/w-bin.svg"} width={14} style={{ marginTop: "0px", marginBottom: "3px" }} />}
|
||||
id="kc-accept"
|
||||
>
|
||||
{msgStr("doConfirmDelete")}
|
||||
</Button>
|
||||
|
||||
<Button size="large" style={{ flexGrow: 1 }} htmlType="submit" name="cancel-aia" id="kc-decline">
|
||||
{msgStr("doCancel")}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
<Button size="large" style={{ flexGrow: 1 }} htmlType="submit" name="cancel-aia" id="kc-decline">
|
||||
{msgStr("doCancel")}
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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">
|
||||
<Alert message={<div dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />} type="error" showIcon />
|
||||
</Space>
|
||||
{!skipLink && client !== undefined && client.baseUrl !== undefined && (
|
||||
<>
|
||||
<Divider />
|
||||
<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 && (
|
||||
<Button type="primary" id="backToApplication" size={"large"} block href={client.baseUrl}>
|
||||
{msg("backToApplication")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")}
|
||||
|
||||
@ -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}>
|
||||
{msg("backToApplication")}
|
||||
</Button>
|
||||
</>
|
||||
<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>}
|
||||
>
|
||||
<Space direction="vertical" size="middle">
|
||||
<Text className="instruction">
|
||||
<span dangerouslySetInnerHTML={{ __html: getMessageContent() }}></span>
|
||||
</Text>
|
||||
</Space>
|
||||
{renderActionLink()}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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"}
|
||||
|
||||
@ -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,208 +79,210 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
||||
headerNode={<Title level={3}>{msg("loginTotpTitle")}</Title>}
|
||||
displayMessage={!messagesPerField.existsError("totp", "userLabel")}
|
||||
>
|
||||
<Steps
|
||||
direction="vertical"
|
||||
current={3}
|
||||
items={[
|
||||
{
|
||||
title: msg("loginTotpStep1"),
|
||||
icon: <img src={"https://cdn.tombutcher.work/icons/auth/c-download-128.svg"} height={14} />,
|
||||
description: (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Paragraph>{msg("loginTotpStep1")}</Paragraph>
|
||||
<List
|
||||
id="kc-totp-supported-apps"
|
||||
size="small"
|
||||
bordered
|
||||
dataSource={totp.supportedApplications}
|
||||
renderItem={app => (
|
||||
<List.Item>
|
||||
<Space>
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/c-phone.svg"}
|
||||
width={14}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
{advancedMsg(app)}
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: mode === "manual" ? msg("loginTotpManualStep2") : msg("loginTotpStep2"),
|
||||
icon: <img src={"https://cdn.tombutcher.work/icons/auth/c-qrcode-128.svg"} height={14} />,
|
||||
description: (
|
||||
<>
|
||||
{mode === "manual" ? (
|
||||
<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}
|
||||
items={[
|
||||
{
|
||||
title: msg("loginTotpStep1"),
|
||||
icon: <img src={"https://cdn.tombutcher.work/icons/auth/c-download-128.svg"} height={14} />,
|
||||
description: (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Paragraph>{msg("loginTotpManualStep2")}</Paragraph>
|
||||
<Paragraph>
|
||||
<Text code>{totp.totpSecretEncoded}</Text>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Link href={totp.qrUrl} id="mode-barcode">
|
||||
{msg("loginTotpScanBarcode")}
|
||||
</Link>
|
||||
</Paragraph>
|
||||
<Divider />
|
||||
<Paragraph>{msg("loginTotpManualStep3")}</Paragraph>
|
||||
<List size="small" bordered>
|
||||
<List.Item id="kc-totp-type">
|
||||
<Text strong>{msg("loginTotpType")}:</Text> {msg(`loginTotp.${totp.policy.type}`)}
|
||||
</List.Item>
|
||||
<List.Item id="kc-totp-algorithm">
|
||||
<Text strong>{msg("loginTotpAlgorithm")}:</Text> {totp.policy.getAlgorithmKey()}
|
||||
</List.Item>
|
||||
<List.Item id="kc-totp-digits">
|
||||
<Text strong>{msg("loginTotpDigits")}:</Text> {totp.policy.digits}
|
||||
</List.Item>
|
||||
{totp.policy.type === "totp" ? (
|
||||
<List.Item id="kc-totp-period">
|
||||
<Text strong>{msg("loginTotpInterval")}:</Text> {totp.policy.period}
|
||||
</List.Item>
|
||||
) : (
|
||||
<List.Item id="kc-totp-counter">
|
||||
<Text strong>{msg("loginTotpCounter")}:</Text> {totp.policy.initialCounter}
|
||||
<Paragraph>{msg("loginTotpStep1")}</Paragraph>
|
||||
<List
|
||||
id="kc-totp-supported-apps"
|
||||
size="small"
|
||||
bordered
|
||||
dataSource={totp.supportedApplications}
|
||||
renderItem={app => (
|
||||
<List.Item>
|
||||
<Space>
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/c-phone.svg"}
|
||||
width={14}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
{advancedMsg(app)}
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
</List>
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Paragraph>{msg("loginTotpStep2")}</Paragraph>
|
||||
<div style={{ textAlign: "center", margin: "20px 0" }}>
|
||||
<img
|
||||
id="kc-totp-secret-qr-code"
|
||||
src={`data:image/png;base64, ${totp.totpSecretQrCode}`}
|
||||
alt="QR Code"
|
||||
style={{ maxWidth: "200px" }}
|
||||
/>
|
||||
</div>
|
||||
<Paragraph>
|
||||
<Link href={totp.manualUrl} id="mode-manual">
|
||||
{msg("loginTotpUnableToScan")}
|
||||
</Link>
|
||||
</Paragraph>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: mode === "manual" ? msg("loginTotpManualStep2") : msg("loginTotpStep2"),
|
||||
icon: <img src={"https://cdn.tombutcher.work/icons/auth/c-qrcode-128.svg"} height={14} />,
|
||||
description: (
|
||||
<>
|
||||
{mode === "manual" ? (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Paragraph>{msg("loginTotpManualStep2")}</Paragraph>
|
||||
<Paragraph>
|
||||
<Text code>{totp.totpSecretEncoded}</Text>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Link href={totp.qrUrl} id="mode-barcode">
|
||||
{msg("loginTotpScanBarcode")}
|
||||
</Link>
|
||||
</Paragraph>
|
||||
<Divider />
|
||||
<Paragraph>{msg("loginTotpManualStep3")}</Paragraph>
|
||||
<List size="small" bordered>
|
||||
<List.Item id="kc-totp-type">
|
||||
<Text strong>{msg("loginTotpType")}:</Text> {msg(`loginTotp.${totp.policy.type}`)}
|
||||
</List.Item>
|
||||
<List.Item id="kc-totp-algorithm">
|
||||
<Text strong>{msg("loginTotpAlgorithm")}:</Text> {totp.policy.getAlgorithmKey()}
|
||||
</List.Item>
|
||||
<List.Item id="kc-totp-digits">
|
||||
<Text strong>{msg("loginTotpDigits")}:</Text> {totp.policy.digits}
|
||||
</List.Item>
|
||||
{totp.policy.type === "totp" ? (
|
||||
<List.Item id="kc-totp-period">
|
||||
<Text strong>{msg("loginTotpInterval")}:</Text> {totp.policy.period}
|
||||
</List.Item>
|
||||
) : (
|
||||
<List.Item id="kc-totp-counter">
|
||||
<Text strong>{msg("loginTotpCounter")}:</Text> {totp.policy.initialCounter}
|
||||
</List.Item>
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Paragraph>{msg("loginTotpStep2")}</Paragraph>
|
||||
<div style={{ textAlign: "center", margin: "20px 0" }}>
|
||||
<img
|
||||
id="kc-totp-secret-qr-code"
|
||||
src={`data:image/png;base64, ${totp.totpSecretQrCode}`}
|
||||
alt="QR Code"
|
||||
style={{ maxWidth: "200px" }}
|
||||
/>
|
||||
</div>
|
||||
<Paragraph>
|
||||
<Link href={totp.manualUrl} id="mode-manual">
|
||||
{msg("loginTotpUnableToScan")}
|
||||
</Link>
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: msg("loginTotpStep3"),
|
||||
icon: <img src={"https://cdn.tombutcher.work/icons/auth/c-check-128.svg"} height={14} />,
|
||||
description: (
|
||||
<div>
|
||||
<Paragraph>{msg("loginTotpStep3")}</Paragraph>
|
||||
<Text>{msg("loginTotpStep3DeviceName")}</Text>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: msg("loginTotpStep3"),
|
||||
icon: <img src={"https://cdn.tombutcher.work/icons/auth/c-check-128.svg"} height={14} />,
|
||||
description: (
|
||||
<div>
|
||||
<Paragraph>{msg("loginTotpStep3")}</Paragraph>
|
||||
<Text>{msg("loginTotpStep3DeviceName")}</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<Divider />
|
||||
<Form layout="vertical" onFinish={handleSubmit} style={{ margin: "0 auto" }}>
|
||||
<Form.Item
|
||||
label={
|
||||
<>
|
||||
{msg("authenticatorCode")} <span className="required">*</span>
|
||||
</>
|
||||
}
|
||||
name="totp"
|
||||
validateStatus={messagesPerField.existsError("totp") ? "error" : undefined}
|
||||
help={
|
||||
messagesPerField.existsError("totp") && (
|
||||
<span
|
||||
id="input-error-otp-code"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(messagesPerField.get("totp"))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="totp"
|
||||
autoComplete="off"
|
||||
size="large"
|
||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
|
||||
aria-invalid={messagesPerField.existsError("totp")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<>
|
||||
{msg("loginTotpDeviceName")} {totp.otpCredentials.length >= 1 && <span className="required">*</span>}
|
||||
</>
|
||||
}
|
||||
name="userLabel"
|
||||
validateStatus={messagesPerField.existsError("userLabel") ? "error" : undefined}
|
||||
help={
|
||||
messagesPerField.existsError("userLabel") && (
|
||||
<span
|
||||
id="input-error-otp-label"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(messagesPerField.get("userLabel"))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="userLabel"
|
||||
autoComplete="off"
|
||||
size="large"
|
||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-phone.svg"} width={14} style={{ marginRight: "3px" }} />}
|
||||
aria-invalid={messagesPerField.existsError("userLabel")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<LogoutOtherSessions kcClsx={kcClsx} i18n={i18n} />
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<Form.Item>
|
||||
<Flex gap={"middle"}>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{ flexGrow: 2 }}
|
||||
size="large"
|
||||
id="saveTOTPBtn"
|
||||
iconPosition="end"
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||
style={{ marginTop: "0px", marginBottom: "3px" }}
|
||||
width={14}
|
||||
/>
|
||||
)
|
||||
}
|
||||
loading={isSubmitLoading}
|
||||
disabled={isCancelLoading || isSubmitLoading}
|
||||
>
|
||||
{msgStr("doSubmit")}
|
||||
</Button>
|
||||
{isAppInitiatedAction ? (
|
||||
]}
|
||||
/>
|
||||
<Divider />
|
||||
<Form.Item
|
||||
label={
|
||||
<>
|
||||
{msg("authenticatorCode")} <span className="required">*</span>
|
||||
</>
|
||||
}
|
||||
name="totp"
|
||||
validateStatus={messagesPerField.existsError("totp") ? "error" : undefined}
|
||||
help={
|
||||
messagesPerField.existsError("totp") && (
|
||||
<span
|
||||
id="input-error-otp-code"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(messagesPerField.get("totp"))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="totp"
|
||||
autoComplete="off"
|
||||
size="large"
|
||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
|
||||
aria-invalid={messagesPerField.existsError("totp")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<>
|
||||
{msg("loginTotpDeviceName")} {totp.otpCredentials.length >= 1 && <span className="required">*</span>}
|
||||
</>
|
||||
}
|
||||
name="userLabel"
|
||||
validateStatus={messagesPerField.existsError("userLabel") ? "error" : undefined}
|
||||
help={
|
||||
messagesPerField.existsError("userLabel") && (
|
||||
<span
|
||||
id="input-error-otp-label"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(messagesPerField.get("userLabel"))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="userLabel"
|
||||
autoComplete="off"
|
||||
size="large"
|
||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-phone.svg"} width={14} style={{ marginRight: "3px" }} />}
|
||||
aria-invalid={messagesPerField.existsError("userLabel")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ padding: "0 15px" }}>
|
||||
<Form.Item>
|
||||
<LogoutOtherSessions kcClsx={kcClsx} i18n={i18n} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Flex gap={"middle"}>
|
||||
<Button
|
||||
type="default"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{ flexGrow: 2 }}
|
||||
size="large"
|
||||
style={{ flexGrow: 1 }}
|
||||
id="cancelTOTPBtn"
|
||||
onClick={handleCancel}
|
||||
loading={isCancelLoading}
|
||||
id="saveTOTPBtn"
|
||||
iconPosition="end"
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||
style={{ marginTop: "0px", marginBottom: "3px" }}
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
loading={isSubmitLoading}
|
||||
disabled={isCancelLoading || isSubmitLoading}
|
||||
>
|
||||
{msg("doCancel")}
|
||||
{msgStr("doSubmit")}
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
{isAppInitiatedAction ? (
|
||||
<Button
|
||||
type="default"
|
||||
htmlType="submit"
|
||||
size="large"
|
||||
style={{ flexGrow: 1 }}
|
||||
id="cancelTOTPBtn"
|
||||
onClick={handleCancel}
|
||||
loading={isCancelLoading}
|
||||
disabled={isCancelLoading || isSubmitLoading}
|
||||
>
|
||||
{msg("doCancel")}
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
</Template>
|
||||
);
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,124 +47,106 @@ 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)}
|
||||
>
|
||||
<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)}
|
||||
type="info"
|
||||
style={{ marginBottom: "20px" }}
|
||||
showIcon
|
||||
></Alert>
|
||||
)}
|
||||
{oauth.clientScopesRequested.length > 0 && (
|
||||
<List
|
||||
dataSource={oauth.clientScopesRequested}
|
||||
bordered={true}
|
||||
renderItem={clientScope => (
|
||||
<List.Item key={clientScope.consentScreenText}>
|
||||
<Space>
|
||||
<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)
|
||||
}
|
||||
type="info"
|
||||
style={{ marginBottom: "20px" }}
|
||||
showIcon
|
||||
></Alert>
|
||||
)}
|
||||
{oauth.clientScopesRequested.length > 0 && (
|
||||
<List
|
||||
dataSource={oauth.clientScopesRequested}
|
||||
bordered={true}
|
||||
renderItem={clientScope => (
|
||||
<List.Item key={clientScope.consentScreenText}>
|
||||
<Space>
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/c-check-128.svg"}
|
||||
style={{ marginTop: 0, marginBottom: "3px" }}
|
||||
width={14}
|
||||
/>
|
||||
<Text>
|
||||
{advancedMsg(clientScope.consentScreenText)}
|
||||
{clientScope.dynamicScopeParameter && (
|
||||
<>
|
||||
: <Text strong>{clientScope.dynamicScopeParameter}</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Space direction="vertical" style={{ marginTop: "12px" }}>
|
||||
{client.attributes.tosUri && (
|
||||
<Text>
|
||||
{msg("oauthGrantReview")}{" "}
|
||||
<Link href={client.attributes.tosUri} target="_blank">
|
||||
{msg("oauthGrantTos")}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
{client.attributes.policyUri && (
|
||||
<Text>
|
||||
{msg("oauthGrantReview")}{" "}
|
||||
<Link href={client.attributes.policyUri} target="_blank">
|
||||
{msg("oauthGrantPolicy")}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
<Flex gap="middle" style={{ marginTop: 24 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ flexGrow: 1 }}
|
||||
size="large"
|
||||
id="kc-login"
|
||||
loading={isAcceptLoading}
|
||||
disabled={isAcceptLoading || isDeclineLoading}
|
||||
iconPosition="end"
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/c-check-128.svg"}
|
||||
style={{ marginTop: 0, marginBottom: "3px" }}
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||
style={{ marginTop: "0px", marginBottom: "3px" }}
|
||||
width={14}
|
||||
/>
|
||||
<Text>
|
||||
{advancedMsg(clientScope.consentScreenText)}
|
||||
{clientScope.dynamicScopeParameter && (
|
||||
<>
|
||||
: <Text strong>{clientScope.dynamicScopeParameter}</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
||||
<>
|
||||
<Space direction="vertical" style={{ marginTop: "12px" }}>
|
||||
{client.attributes.tosUri && (
|
||||
<Text>
|
||||
{msg("oauthGrantReview")}{" "}
|
||||
<Link href={client.attributes.tosUri} target="_blank">
|
||||
{msg("oauthGrantTos")}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
{client.attributes.policyUri && (
|
||||
<Text>
|
||||
{msg("oauthGrantReview")}{" "}
|
||||
<Link href={client.attributes.policyUri} target="_blank">
|
||||
{msg("oauthGrantPolicy")}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
</Space>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<Flex gap="middle">
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ flexGrow: 1 }}
|
||||
size="large"
|
||||
id="kc-login"
|
||||
loading={isAcceptLoading}
|
||||
disabled={isAcceptLoading || isDeclineLoading}
|
||||
iconPosition="end"
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||
style={{ marginTop: "0px", marginBottom: "3px" }}
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
setIsAcceptLoading(true);
|
||||
handleSubmit("accept");
|
||||
}}
|
||||
>
|
||||
{msgStr("doYes")}
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ flexGrow: 1 }}
|
||||
size="large"
|
||||
id="kc-cancel"
|
||||
loading={isDeclineLoading}
|
||||
disabled={isAcceptLoading || isDeclineLoading}
|
||||
onClick={() => {
|
||||
setIsDeclineLoading(true);
|
||||
handleSubmit("cancel");
|
||||
}}
|
||||
>
|
||||
{msgStr("doNo")}
|
||||
</Button>
|
||||
</Flex>
|
||||
}
|
||||
onClick={() => {
|
||||
setIsAcceptLoading(true);
|
||||
handleSubmit("accept");
|
||||
}}
|
||||
>
|
||||
{msgStr("doYes")}
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ flexGrow: 1 }}
|
||||
size="large"
|
||||
id="kc-cancel"
|
||||
loading={isDeclineLoading}
|
||||
disabled={isAcceptLoading || isDeclineLoading}
|
||||
onClick={() => {
|
||||
setIsDeclineLoading(true);
|
||||
handleSubmit("cancel");
|
||||
}}
|
||||
>
|
||||
{msgStr("doNo")}
|
||||
</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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"}
|
||||
|
||||
@ -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,65 +32,71 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
||||
classes={classes}
|
||||
headerNode={msg("recovery-code-config-header")}
|
||||
>
|
||||
<Space direction="vertical" size="middle">
|
||||
<Space direction="vertical" size="large">
|
||||
<Alert message={msg("recovery-code-config-warning-title")} type="warning" showIcon />
|
||||
<Text>{msg("recovery-code-config-warning-message")}</Text>
|
||||
</Space>
|
||||
<Flex id={olRecoveryCodesListId} gap="middle" wrap>
|
||||
{recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList.map((code, index) => (
|
||||
<Text key={index} strong code style={{ fontSize: "18px" }}>
|
||||
{formatRecoveryCode(code)}
|
||||
</Text>
|
||||
))}
|
||||
</Flex>
|
||||
<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 />
|
||||
<Text>{msg("recovery-code-config-warning-message")}</Text>
|
||||
</Space>
|
||||
<Flex id={olRecoveryCodesListId} gap="middle" wrap>
|
||||
{recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList.map((code, index) => (
|
||||
<Text key={index} strong code style={{ fontSize: "18px" }}>
|
||||
{formatRecoveryCode(code)}
|
||||
</Text>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
<Space>
|
||||
<Button type="link" icon={<PrinterOutlined />} id="printRecoveryCodes">
|
||||
{msg("recovery-codes-print")}
|
||||
</Button>
|
||||
<Button type="link" icon={<SaveOutlined />} id="downloadRecoveryCodes">
|
||||
{msg("recovery-codes-download")}
|
||||
</Button>
|
||||
<Button type="link" icon={<CopyOutlined />} id="copyRecoveryCodes">
|
||||
{msg("recovery-codes-copy")}
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Checkbox
|
||||
id="kcRecoveryCodesConfirmationCheck"
|
||||
name="kcRecoveryCodesConfirmationCheck"
|
||||
onChange={e => setIsConfirmed(e.target.checked)}
|
||||
style={{ marginBottom: "0px" }}
|
||||
>
|
||||
{msg("recovery-codes-confirmation-message")}
|
||||
</Checkbox>
|
||||
</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="generatedAt" value={recoveryAuthnCodesConfigBean.generatedAt} />
|
||||
<Input type="hidden" id="userLabel" name="userLabel" value={msgStr("recovery-codes-label-default")} />
|
||||
|
||||
<LogoutOtherSessions i18n={i18n} />
|
||||
|
||||
<Divider />
|
||||
|
||||
{isAppInitiatedAction ? (
|
||||
<Space>
|
||||
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed}>
|
||||
{msg("recovery-codes-action-complete")}
|
||||
<Button type="link" icon={<PrinterOutlined />} id="printRecoveryCodes">
|
||||
{msg("recovery-codes-print")}
|
||||
</Button>
|
||||
<Button size="large" id="cancelRecoveryAuthnCodesBtn" name="cancel-aia" value="true" htmlType="submit">
|
||||
{msg("recovery-codes-action-cancel")}
|
||||
<Button type="link" icon={<SaveOutlined />} id="downloadRecoveryCodes">
|
||||
{msg("recovery-codes-download")}
|
||||
</Button>
|
||||
<Button type="link" icon={<CopyOutlined />} id="copyRecoveryCodes">
|
||||
{msg("recovery-codes-copy")}
|
||||
</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed} block>
|
||||
{msg("recovery-codes-action-complete")}
|
||||
</Button>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
<Checkbox
|
||||
id="kcRecoveryCodesConfirmationCheck"
|
||||
name="kcRecoveryCodesConfirmationCheck"
|
||||
onChange={e => setIsConfirmed(e.target.checked)}
|
||||
style={{ marginBottom: "0px" }}
|
||||
>
|
||||
{msg("recovery-codes-confirmation-message")}
|
||||
</Checkbox>
|
||||
</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="generatedAt" value={recoveryAuthnCodesConfigBean.generatedAt} />
|
||||
<Input type="hidden" id="userLabel" name="userLabel" value={msgStr("recovery-codes-label-default")} />
|
||||
|
||||
<LogoutOtherSessions i18n={i18n} />
|
||||
|
||||
<div style={{ marginTop: 24 }}>
|
||||
{isAppInitiatedAction ? (
|
||||
<Space>
|
||||
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed}>
|
||||
{msg("recovery-codes-action-complete")}
|
||||
</Button>
|
||||
<Button size="large" id="cancelRecoveryAuthnCodesBtn" name="cancel-aia" value="true" htmlType="submit">
|
||||
{msg("recovery-codes-action-cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed} block>
|
||||
{msg("recovery-codes-action-complete")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
<Text className="instruction">{msg("logoutConfirmHeader")}</Text>
|
||||
<Divider />
|
||||
<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>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
|
||||
@ -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,67 +66,69 @@ export default function Register(props: RegisterProps) {
|
||||
|
||||
return (
|
||||
<Form form={form} layout="vertical" name="registerForm" requiredMark onFinish={onFinish}>
|
||||
<UserProfileFormFields
|
||||
layout="vertical"
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
kcClsx={options => (typeof options === "object" && options !== null ? (options as { classKey?: string }).classKey ?? "" : "")}
|
||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||
onIsFormSubmittableValueChange={() => {}}
|
||||
/>
|
||||
|
||||
{termsAcceptanceRequired && (
|
||||
<TermsAcceptance
|
||||
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px" }}>
|
||||
<UserProfileFormFields
|
||||
layout="vertical"
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
messagesPerField={messagesPerField}
|
||||
areTermsAccepted={areTermsAccepted}
|
||||
onAreTermsAcceptedValueChange={setAreTermsAccepted}
|
||||
kcClsx={options => (typeof options === "object" && options !== null ? (options as { classKey?: string }).classKey ?? "" : "")}
|
||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||
onIsFormSubmittableValueChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ padding: "0 15px" }}>
|
||||
{termsAcceptanceRequired && (
|
||||
<TermsAcceptance
|
||||
i18n={i18n}
|
||||
messagesPerField={messagesPerField}
|
||||
areTermsAccepted={areTermsAccepted}
|
||||
onAreTermsAcceptedValueChange={setAreTermsAccepted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{recaptchaRequired && (recaptchaVisible || recaptchaAction === undefined) && (
|
||||
<Form.Item>
|
||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} data-action={recaptchaAction} />
|
||||
{recaptchaRequired && (recaptchaVisible || recaptchaAction === undefined) && (
|
||||
<Form.Item>
|
||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} data-action={recaptchaAction} />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item style={{ marginTop: 24 }}>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{recaptchaRequired && !recaptchaVisible && recaptchaAction !== undefined ? (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
size="large"
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
className="g-recaptcha"
|
||||
data-sitekey={recaptchaSiteKey}
|
||||
data-callback={() => {
|
||||
form.submit();
|
||||
}}
|
||||
data-action={recaptchaAction}
|
||||
>
|
||||
{msg("doRegister")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
size="large"
|
||||
loading={isLoading}
|
||||
disabled={!submittable || isLoading || (termsAcceptanceRequired && !areTermsAccepted)}
|
||||
>
|
||||
{msgStr("doRegister")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 16, textAlign: "center" }}>
|
||||
<Link href={url.loginUrl}>{msg("backToLogin")}</Link>
|
||||
</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Divider />
|
||||
<Form.Item>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{recaptchaRequired && !recaptchaVisible && recaptchaAction !== undefined ? (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
size="large"
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
className="g-recaptcha"
|
||||
data-sitekey={recaptchaSiteKey}
|
||||
data-callback={() => {
|
||||
form.submit();
|
||||
}}
|
||||
data-action={recaptchaAction}
|
||||
>
|
||||
{msg("doRegister")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
size="large"
|
||||
loading={isLoading}
|
||||
disabled={!submittable || isLoading || (termsAcceptanceRequired && !areTermsAccepted)}
|
||||
>
|
||||
{msgStr("doRegister")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 16, textAlign: "center" }}>
|
||||
<Link href={url.loginUrl}>{msg("backToLogin")}</Link>
|
||||
</div>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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,112 +66,98 @@ 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")}
|
||||
>
|
||||
<Form id="webauth" action={url.loginAction} method="post" layout="vertical">
|
||||
<Input type="hidden" id="clientDataJSON" name="clientDataJSON" />
|
||||
<Input type="hidden" id="authenticatorData" name="authenticatorData" />
|
||||
<Input type="hidden" id="signature" name="signature" />
|
||||
<Input type="hidden" id="credentialId" name="credentialId" />
|
||||
<Input type="hidden" id="userHandle" name="userHandle" />
|
||||
<Input type="hidden" id="error" name="error" />
|
||||
</Form>
|
||||
<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" />
|
||||
<Input type="hidden" id="signature" name="signature" />
|
||||
<Input type="hidden" id="credentialId" name="credentialId" />
|
||||
<Input type="hidden" id="userHandle" name="userHandle" />
|
||||
<Input type="hidden" id="error" name="error" />
|
||||
</Form>
|
||||
|
||||
{authenticators && (
|
||||
<>
|
||||
<Form id="authn_select">
|
||||
{authenticators.authenticators.map((authenticator: Authenticator, index: number) => (
|
||||
<Input key={index} type="hidden" name="authn_use_chk" value={authenticator.credentialId} />
|
||||
))}
|
||||
</Form>
|
||||
{authenticators && (
|
||||
<>
|
||||
<Form id="authn_select">
|
||||
{authenticators.authenticators.map((authenticator: Authenticator, index: number) => (
|
||||
<Input key={index} type="hidden" name="authn_use_chk" value={authenticator.credentialId} />
|
||||
))}
|
||||
</Form>
|
||||
|
||||
{shouldDisplayAuthenticators && (
|
||||
<>
|
||||
<List
|
||||
bordered
|
||||
dataSource={authenticators.authenticators as Authenticator[]}
|
||||
renderItem={(authenticator: Authenticator, i: number) => (
|
||||
<List.Item key={i} id={`kc-webauthn-authenticator-item-${i}`}>
|
||||
<List.Item.Meta
|
||||
avatar={getAuthenticatorIcon(authenticator.transports.iconClass as string)}
|
||||
title={
|
||||
<Text strong id={`kc-webauthn-authenticator-label-${i}`} style={{ marginBottom: 0 }}>
|
||||
{advancedMsg(authenticator.label)}
|
||||
</Text>
|
||||
}
|
||||
description={
|
||||
<Space direction="vertical" size={1}>
|
||||
{authenticator.transports.displayNameProperties &&
|
||||
authenticator.transports.displayNameProperties.length > 0 && (
|
||||
<Text id={`kc-webauthn-authenticator-transport-${i}`}>
|
||||
{authenticator.transports.displayNameProperties
|
||||
.map((displayNameProperty: string, j: number, arr: string[]) => ({
|
||||
displayNameProperty,
|
||||
hasNext: j !== arr.length - 1
|
||||
}))
|
||||
.map(({ displayNameProperty, hasNext }) => (
|
||||
<Fragment key={displayNameProperty}>
|
||||
{advancedMsg(displayNameProperty)}
|
||||
{hasNext && <span>, </span>}
|
||||
</Fragment>
|
||||
))}
|
||||
</Text>
|
||||
)}
|
||||
<Text type="secondary">
|
||||
<span id={`kc-webauthn-authenticator-createdlabel-${i}`}>
|
||||
{msg("webauthn-createdAt-label")}
|
||||
</span>
|
||||
<span id={`kc-webauthn-authenticator-created-${i}`}> {authenticator.createdAt}</span>
|
||||
{shouldDisplayAuthenticators && (
|
||||
<>
|
||||
<List
|
||||
style={{ marginBottom: "30px" }}
|
||||
bordered
|
||||
dataSource={authenticators.authenticators as Authenticator[]}
|
||||
renderItem={(authenticator: Authenticator, i: number) => (
|
||||
<List.Item key={i} id={`kc-webauthn-authenticator-item-${i}`}>
|
||||
<List.Item.Meta
|
||||
avatar={getAuthenticatorIcon(authenticator.transports.iconClass as string)}
|
||||
title={
|
||||
<Text strong id={`kc-webauthn-authenticator-label-${i}`} style={{ marginBottom: 0 }}>
|
||||
{advancedMsg(authenticator.label)}
|
||||
</Text>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
style={{ marginBottom: 0 }}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
}
|
||||
description={
|
||||
<Space direction="vertical" size={1}>
|
||||
{authenticator.transports.displayNameProperties &&
|
||||
authenticator.transports.displayNameProperties.length > 0 && (
|
||||
<Text id={`kc-webauthn-authenticator-transport-${i}`}>
|
||||
{authenticator.transports.displayNameProperties
|
||||
.map((displayNameProperty: string, j: number, arr: string[]) => ({
|
||||
displayNameProperty,
|
||||
hasNext: j !== arr.length - 1
|
||||
}))
|
||||
.map(({ displayNameProperty, hasNext }) => (
|
||||
<Fragment key={displayNameProperty}>
|
||||
{advancedMsg(displayNameProperty)}
|
||||
{hasNext && <span>, </span>}
|
||||
</Fragment>
|
||||
))}
|
||||
</Text>
|
||||
)}
|
||||
<Text type="secondary">
|
||||
<span id={`kc-webauthn-authenticator-createdlabel-${i}`}>
|
||||
{msg("webauthn-createdAt-label")}
|
||||
</span>
|
||||
<span id={`kc-webauthn-authenticator-created-${i}`}> {authenticator.createdAt}</span>
|
||||
</Text>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Item style={{ marginBottom: 0 }}>
|
||||
<Button
|
||||
id={authButtonId}
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
autoFocus
|
||||
onClick={() => setIsLoading(true)}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-passkey.svg"}
|
||||
width={24}
|
||||
style={{ marginTop: "3px", marginBottom: "0px" }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<span>{msgStr("webauthn-doAuthenticate")}</span>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<Form.Item style={{ marginBottom: 0 }}>
|
||||
<Button
|
||||
id={authButtonId}
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
autoFocus
|
||||
onClick={() => setIsLoading(true)}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-passkey.svg"}
|
||||
width={24}
|
||||
style={{ marginTop: "3px", marginBottom: "0px" }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<span>{msgStr("webauthn-doAuthenticate")}</span>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,56 +65,57 @@ 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>}>
|
||||
<Form id="register" layout="vertical" action={url.loginAction} method="post">
|
||||
{/* Hidden fields for WebAuthn registration data */}
|
||||
<input type="hidden" id="clientDataJSON" name="clientDataJSON" />
|
||||
<input type="hidden" id="attestationObject" name="attestationObject" />
|
||||
<input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId" />
|
||||
<input type="hidden" id="authenticatorLabel" name="authenticatorLabel" />
|
||||
<input type="hidden" id="transports" name="transports" />
|
||||
<input type="hidden" id="error" name="error" />
|
||||
<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" />
|
||||
<input type="hidden" id="attestationObject" name="attestationObject" />
|
||||
<input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId" />
|
||||
<input type="hidden" id="authenticatorLabel" name="authenticatorLabel" />
|
||||
<input type="hidden" id="transports" name="transports" />
|
||||
<input type="hidden" id="error" name="error" />
|
||||
|
||||
<LogoutOtherSessions i18n={i18n} />
|
||||
</Form>
|
||||
<Divider />
|
||||
<Form.Item>
|
||||
<Flex gap={"middle"}>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-passkey.svg"}
|
||||
style={{ marginTop: "4px", marginBottom: 0 }}
|
||||
width={24}
|
||||
/>
|
||||
}
|
||||
style={{ flexGrow: 1 }}
|
||||
id={authButtonId}
|
||||
onClick={() => setIsSubmitLoading(true)}
|
||||
htmlType="submit"
|
||||
loading={isSubmitLoading}
|
||||
disabled={isSubmitLoading || isCancelLoading}
|
||||
>
|
||||
{msgStr("doRegisterSecurityKey")}
|
||||
</Button>
|
||||
|
||||
{!isSetRetry && isAppInitiatedAction && (
|
||||
<LogoutOtherSessions i18n={i18n} />
|
||||
</Form>
|
||||
<Form.Item>
|
||||
<Flex gap={"middle"}>
|
||||
<Button
|
||||
type="default"
|
||||
type="primary"
|
||||
size="large"
|
||||
id="cancelWebAuthnAIA"
|
||||
onClick={handleCancel}
|
||||
icon={
|
||||
<img
|
||||
src={"https://cdn.tombutcher.work/icons/auth/w-passkey.svg"}
|
||||
style={{ marginTop: "4px", marginBottom: 0 }}
|
||||
width={24}
|
||||
/>
|
||||
}
|
||||
style={{ flexGrow: 1 }}
|
||||
loading={isCancelLoading}
|
||||
id={authButtonId}
|
||||
onClick={() => setIsSubmitLoading(true)}
|
||||
htmlType="submit"
|
||||
loading={isSubmitLoading}
|
||||
disabled={isSubmitLoading || isCancelLoading}
|
||||
>
|
||||
{msg("doCancel")}
|
||||
{msgStr("doRegisterSecurityKey")}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
|
||||
{!isSetRetry && isAppInitiatedAction && (
|
||||
<Button
|
||||
type="default"
|
||||
size="large"
|
||||
id="cancelWebAuthnAIA"
|
||||
onClick={handleCancel}
|
||||
style={{ flexGrow: 1 }}
|
||||
loading={isCancelLoading}
|
||||
disabled={isSubmitLoading || isCancelLoading}
|
||||
>
|
||||
{msg("doCancel")}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user