Improved dark ui, fixed sizing and mobile support.
This commit is contained in:
parent
119eaa09d1
commit
b0ace8b6a0
@ -86,28 +86,13 @@ export default function KcPage(props: { kcContext: KcContext }) {
|
|||||||
);
|
);
|
||||||
case "register.ftl":
|
case "register.ftl":
|
||||||
return (
|
return (
|
||||||
<Template
|
<Register
|
||||||
kcContext={kcContext}
|
{...{ kcContext, i18n, classes }}
|
||||||
i18n={i18n}
|
Template={Template}
|
||||||
doUseDefaultCss={false}
|
doUseDefaultCss={false}
|
||||||
classes={{}}
|
UserProfileFormFields={UserProfileFormFields}
|
||||||
headerNode={
|
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||||
kcContext.messageHeader !== undefined
|
/>
|
||||||
? i18n.advancedMsg(kcContext.messageHeader)
|
|
||||||
: i18n.msg("registerTitle")
|
|
||||||
}
|
|
||||||
displayMessage={kcContext.messagesPerField.exists(
|
|
||||||
"global"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Register
|
|
||||||
{...{ kcContext, i18n, classes }}
|
|
||||||
Template={Template}
|
|
||||||
doUseDefaultCss={false}
|
|
||||||
UserProfileFormFields={UserProfileFormFields}
|
|
||||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
|
||||||
/>
|
|
||||||
</Template>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
case "error.ftl":
|
case "error.ftl":
|
||||||
@ -184,23 +169,13 @@ export default function KcPage(props: { kcContext: KcContext }) {
|
|||||||
);
|
);
|
||||||
case "login-update-profile.ftl":
|
case "login-update-profile.ftl":
|
||||||
return (
|
return (
|
||||||
<Template
|
<LoginUpdateProfile
|
||||||
kcContext={kcContext}
|
{...{ kcContext, i18n, classes }}
|
||||||
i18n={i18n}
|
Template={Template}
|
||||||
doUseDefaultCss={false}
|
doUseDefaultCss={true}
|
||||||
headerNode={i18n.msg("loginProfileTitle")}
|
UserProfileFormFields={UserProfileFormFields}
|
||||||
displayMessage={kcContext.messagesPerField.exists(
|
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||||
"global"
|
/>
|
||||||
)}
|
|
||||||
>
|
|
||||||
<LoginUpdateProfile
|
|
||||||
{...{ kcContext, i18n, classes }}
|
|
||||||
Template={Template}
|
|
||||||
doUseDefaultCss={true}
|
|
||||||
UserProfileFormFields={UserProfileFormFields}
|
|
||||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
|
||||||
/>
|
|
||||||
</Template>
|
|
||||||
);
|
);
|
||||||
case "login-oauth2-device-verify-user-code.ftl":
|
case "login-oauth2-device-verify-user-code.ftl":
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -6,7 +6,11 @@ import { type Container, type ISourceOptions } from "@tsparticles/engine";
|
|||||||
import { loadSlim } from "@tsparticles/slim"; // if you are going to use `loadSlim`, install the "@tsparticles/slim" package too.
|
import { loadSlim } from "@tsparticles/slim"; // if you are going to use `loadSlim`, install the "@tsparticles/slim" package too.
|
||||||
// import { loadBasic } from "@tsparticles/basic"; // if you are going to use `loadBasic`, install the "@tsparticles/basic" package too.
|
// import { loadBasic } from "@tsparticles/basic"; // if you are going to use `loadBasic`, install the "@tsparticles/basic" package too.
|
||||||
|
|
||||||
const ParticlesBackground = () => {
|
type ParticlesBackgroundProps = {
|
||||||
|
darkMode?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ParticlesBackground = ({ darkMode = false }: ParticlesBackgroundProps) => {
|
||||||
const [init, setInit] = useState(false);
|
const [init, setInit] = useState(false);
|
||||||
|
|
||||||
// this should be run only once per application lifetime
|
// this should be run only once per application lifetime
|
||||||
@ -28,7 +32,85 @@ const ParticlesBackground = () => {
|
|||||||
console.log(container);
|
console.log(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: ISourceOptions = useMemo(
|
const darkOptions: ISourceOptions = useMemo(
|
||||||
|
() => ({
|
||||||
|
background: {
|
||||||
|
color: {
|
||||||
|
value: "#FF00A1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fpsLimit: 120,
|
||||||
|
interactivity: {
|
||||||
|
events: {
|
||||||
|
onClick: {
|
||||||
|
enable: false,
|
||||||
|
mode: "push"
|
||||||
|
},
|
||||||
|
onHover: {
|
||||||
|
enable: true,
|
||||||
|
mode: "repulse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modes: {
|
||||||
|
push: {
|
||||||
|
quantity: 4
|
||||||
|
},
|
||||||
|
repulse: {
|
||||||
|
distance: 100,
|
||||||
|
duration: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
particles: {
|
||||||
|
color: {
|
||||||
|
value: [
|
||||||
|
"#210014FF",
|
||||||
|
"#00033BFF",
|
||||||
|
"#001F24FF",
|
||||||
|
"#170035FF",
|
||||||
|
"#000333FF",
|
||||||
|
"#210014FF"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
color: "#ffffff",
|
||||||
|
distance: 150,
|
||||||
|
enable: true,
|
||||||
|
opacity: 0.0,
|
||||||
|
width: 0
|
||||||
|
},
|
||||||
|
move: {
|
||||||
|
direction: "none",
|
||||||
|
enable: true,
|
||||||
|
outModes: {
|
||||||
|
default: "out"
|
||||||
|
},
|
||||||
|
random: true,
|
||||||
|
speed: 1,
|
||||||
|
straight: false
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
density: {
|
||||||
|
enable: true
|
||||||
|
},
|
||||||
|
value: 400
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
type: "circle"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
value: { min: 100, max: 300 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detectRetina: true
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const lightOptions: ISourceOptions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
background: {
|
background: {
|
||||||
color: {
|
color: {
|
||||||
@ -119,11 +201,11 @@ const ParticlesBackground = () => {
|
|||||||
<Particles
|
<Particles
|
||||||
id="tsparticles"
|
id="tsparticles"
|
||||||
particlesLoaded={particlesLoaded}
|
particlesLoaded={particlesLoaded}
|
||||||
options={options}
|
options={darkMode ? darkOptions : lightOptions}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: "rgba(255, 255, 255, 0.0)",
|
background: "rgba(0, 0, 0, 0.0)",
|
||||||
backdropFilter: "blur(50px)",
|
backdropFilter: "blur(50px)",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
|||||||
import { useInitialize } from "keycloakify/login/Template.useInitialize";
|
import { useInitialize } from "keycloakify/login/Template.useInitialize";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import { Layout, Typography, Dropdown, Button, Alert, Space, Divider, Card, ConfigProvider, Flex, theme, Spin } from "antd";
|
import { Layout, Typography, Dropdown, Alert, Space, Divider, Card, ConfigProvider, Flex, theme, Spin } from "antd";
|
||||||
import { GlobalOutlined } from "@ant-design/icons";
|
import { GlobalOutlined } from "@ant-design/icons";
|
||||||
import ParticlesBackground from "./ParticlesBackground";
|
import ParticlesBackground from "./ParticlesBackground";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
const { Title, Text, Link } = Typography;
|
const { Title, Text, Link } = Typography;
|
||||||
|
|
||||||
@ -17,7 +17,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
const {
|
const {
|
||||||
displayInfo = false,
|
displayInfo = false,
|
||||||
displayMessage = true,
|
displayMessage = true,
|
||||||
displayRequiredFields = false,
|
|
||||||
headerNode,
|
headerNode,
|
||||||
infoNode = null,
|
infoNode = null,
|
||||||
documentTitle,
|
documentTitle,
|
||||||
@ -98,11 +97,51 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clientInfo = (
|
||||||
|
<Flex gap={"small"} align="center">
|
||||||
|
{client.attributes.logoUri ? (
|
||||||
|
<img
|
||||||
|
src={client.attributes.logoUri}
|
||||||
|
alt={client.name || client.clientId}
|
||||||
|
style={{ marginTop: "0px", width: "18px", height: "18px" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={"https://cdn.tombutcher.work/icons/auth/c-lock.svg"}
|
||||||
|
alt={client.name || client.clientId}
|
||||||
|
style={{ marginTop: "0px", width: "18px", height: "18px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<Text strong>{client.name}</Text>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
const showTryAnotherWayLink = (
|
||||||
|
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post">
|
||||||
|
<input type="hidden" name="tryAnotherWay" value="on" />
|
||||||
|
|
||||||
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
document.forms["kc-select-try-another-way-form" as never].submit();
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{msg("doTryAnotherWay")}
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
const showMessage = displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction);
|
||||||
|
|
||||||
|
const showImutableUsername = auth !== undefined && auth.showUsername && !auth.showResetCredentials;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
theme={{
|
theme={{
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: "rgba(212, 0, 255, 1)",
|
colorPrimary: darkMode ? "#D20294FF" : "rgba(212, 0, 255, 1)",
|
||||||
colorLink: "#6E00FF",
|
colorLink: "#6E00FF",
|
||||||
colorLinkHover: "#b175ff",
|
colorLinkHover: "#b175ff",
|
||||||
borderRadius: 20
|
borderRadius: 20
|
||||||
@ -111,7 +150,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Layout style={{ minHeight: "var(--unit-100vh)", maxHeight: "var(--unit-100vh)" }}>
|
<Layout style={{ minHeight: "var(--unit-100vh)", maxHeight: "var(--unit-100vh)" }}>
|
||||||
<ParticlesBackground />
|
<ParticlesBackground darkMode={darkMode} />
|
||||||
|
|
||||||
{loading == true ? (
|
{loading == true ? (
|
||||||
<div className="loadingOverlay" style={{ backgroundColor: darkMode ? "#000000" : "#ffffff" }}>
|
<div className="loadingOverlay" style={{ backgroundColor: darkMode ? "#000000" : "#ffffff" }}>
|
||||||
@ -126,20 +165,31 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
>
|
>
|
||||||
<Flex vertical align="center" justify="center" style={{ width: "100vw", height: "var(--unit-100vh)" }} gap={"40px"}>
|
<Flex vertical align="center" justify="center" style={{ width: "100vw", height: "var(--unit-100vh)" }} gap={"40px"}>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<img
|
<>
|
||||||
src="https://cdn.tombutcher.work/logos/logo-horizontal.svg"
|
{darkMode ? (
|
||||||
alt="Logo"
|
<img
|
||||||
style={{ height: "60px", padding: "0 30px", zIndex: 1, marginBottom: 5 }}
|
src="https://cdn.tombutcher.work/logos/logo-auth-horizontal.png"
|
||||||
/>
|
alt="Logo"
|
||||||
|
style={{ height: "60px", padding: "0 30px", zIndex: 1, marginBottom: 10 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src="https://cdn.tombutcher.work/logos/logo-horizontal.svg"
|
||||||
|
alt="Logo"
|
||||||
|
style={{ height: "60px", padding: "0 30px", zIndex: 1, marginBottom: 10 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
|
background: darkMode ? "#00000025" : "#fffffff2",
|
||||||
borderRadius: isMobile ? "0px" : "20px",
|
borderRadius: isMobile ? "0px" : "20px",
|
||||||
width: isMobile ? "100vw" : "450px",
|
width: isMobile ? "100vw" : "450px",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
height: isMobile ? "100vh" : "unset",
|
height: isMobile ? "100vh" : "unset",
|
||||||
padding: "23px 15px 30px 15px",
|
padding: "26px 15px 30px 15px",
|
||||||
boxShadow: "0px 5px 15px 5px rgb(0 0 0 / 10%)"
|
boxShadow: darkMode ? "0px 5px 30px 5px rgb(200 200 200 / 5%)" : "0px 5px 15px 5px rgb(0 0 0 / 10%)"
|
||||||
}}
|
}}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
styles={{
|
styles={{
|
||||||
@ -149,110 +199,95 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex style={{ height: "100%" }} vertical>
|
<Flex style={{ height: "100%" }} vertical className={darkMode ? "darkMode" : ""}>
|
||||||
<div style={{ height: "100%", margin: "0 15px" }}>
|
{isMobile && (
|
||||||
{isMobile && (
|
<Flex style={{ margin: "0 15px", paddingBottom: "16px" }} gap={"middle"} vertical>
|
||||||
|
<img
|
||||||
|
src="https://cdn.tombutcher.work/logos/logo-auth.png"
|
||||||
|
alt="Logo"
|
||||||
|
style={{ width: "60%", margin: "0", marginBottom: "4px" }}
|
||||||
|
/>{" "}
|
||||||
|
<Divider style={{ margin: "4px 0" }} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex style={{ margin: "0 15px", paddingBottom: "18px" }} vertical gap={"middle"}>
|
||||||
|
{headerNode && (
|
||||||
<>
|
<>
|
||||||
<img
|
<Title level={2} style={{ marginBottom: "0", flexGrow: 1 }} className={darkMode ? "dark" : ""}>
|
||||||
src="https://cdn.tombutcher.work/logos/logo-auth.png"
|
{headerNode}
|
||||||
alt="Logo"
|
</Title>
|
||||||
style={{ width: "70%", margin: "0" }}
|
{isMobile && <Divider style={{ margin: "4px 0" }} />}
|
||||||
/>{" "}
|
|
||||||
<Divider style={{ margin: "24px 0" }} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginBottom: "10px" }}>
|
{showImutableUsername && (
|
||||||
<Flex gap={"large"} align="center" style={{ paddingBottom: "8px" }}>
|
<>
|
||||||
<Title level={2} style={{ marginBottom: "0", flexGrow: 1 }}>
|
<Card
|
||||||
{headerNode}
|
style={{ display: "flex", alignItems: "center", margin: 0 }}
|
||||||
</Title>
|
styles={{ body: { padding: "8px 12px" } }}
|
||||||
{client.attributes.logoUri && (
|
|
||||||
<img
|
|
||||||
src={client.attributes.logoUri}
|
|
||||||
alt={client.name || client.clientId}
|
|
||||||
style={{ maxHeight: "64px", maxWidth: "100%", marginBottom: "5px" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
{(() => {
|
|
||||||
const node =
|
|
||||||
auth !== undefined && auth.showUsername && !auth.showResetCredentials ? (
|
|
||||||
<Card
|
|
||||||
style={{ display: "flex", alignItems: "center", marginBottom: "24px", marginTop: "8px" }}
|
|
||||||
styles={{ body: { padding: "8px 12px" } }}
|
|
||||||
>
|
|
||||||
<Flex gap={"small"}>
|
|
||||||
<img
|
|
||||||
src={"https://cdn.tombutcher.work/icons/auth/c-person.svg"}
|
|
||||||
width={14}
|
|
||||||
style={{ marginTop: "0px" }}
|
|
||||||
/>
|
|
||||||
<Text strong>{auth.attemptedUsername}</Text>
|
|
||||||
<Link href={url.loginRestartFlowUrl} style={{ marginLeft: "12px" }}>
|
|
||||||
{msg("restartLoginTooltip")}
|
|
||||||
</Link>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
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")}
|
<Flex gap={"small"}>
|
||||||
</Button>
|
<img
|
||||||
</div>
|
src={"https://cdn.tombutcher.work/icons/auth/c-person.svg"}
|
||||||
</form>
|
width={14}
|
||||||
)}
|
style={{ marginTop: "0px" }}
|
||||||
|
/>
|
||||||
|
<Text strong>{auth.attemptedUsername}</Text>
|
||||||
|
<Link href={url.loginRestartFlowUrl} style={{ marginLeft: "12px" }}>
|
||||||
|
{msg("restartLoginTooltip")}
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
{isMobile && <Divider style={{ margin: "4px 0" }} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
|
||||||
|
{showMessage && (
|
||||||
|
<>
|
||||||
|
<Alert
|
||||||
|
message={<span dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />}
|
||||||
|
type={
|
||||||
|
message.type === "error"
|
||||||
|
? "error"
|
||||||
|
: message.type === "success"
|
||||||
|
? "success"
|
||||||
|
: message.type === "warning"
|
||||||
|
? "warning"
|
||||||
|
: "info"
|
||||||
|
}
|
||||||
|
showIcon
|
||||||
|
style={{ margin: 0, width: "100%" }}
|
||||||
|
/>
|
||||||
|
{isMobile && <Divider style={{ margin: "4px 0" }} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
{displayInfo && <div style={{ marginTop: "24px", textAlign: "center" }}>{infoNode}</div>}
|
<Flex vertical style={{ height: "100%" }} gap={"middle"}>
|
||||||
|
{children}
|
||||||
|
{isMobile && (
|
||||||
|
<Flex gap={"large"} justify="center">
|
||||||
|
{client.name && <FooterCard darkMode={darkMode}>{clientInfo}</FooterCard>}
|
||||||
|
{auth !== undefined && auth.showTryAnotherWayLink && (
|
||||||
|
<FooterCard darkMode={darkMode}>{showTryAnotherWayLink}</FooterCard>
|
||||||
|
)}
|
||||||
|
{displayInfo && <FooterCard darkMode={darkMode}>{infoNode}</FooterCard>}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
{!isMobile && (
|
||||||
|
<Flex gap={"middle"}>
|
||||||
|
{client.name && <FooterCard darkMode={darkMode}>{clientInfo}</FooterCard>}
|
||||||
|
{auth !== undefined && auth.showTryAnotherWayLink && (
|
||||||
|
<FooterCard darkMode={darkMode}>{showTryAnotherWayLink}</FooterCard>
|
||||||
|
)}
|
||||||
|
{displayInfo && <FooterCard darkMode={darkMode}>{infoNode}</FooterCard>}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<Flex style={{ zIndex: 1 }} gap={"large"}>
|
<Flex style={{ zIndex: 1 }} gap={"large"}>
|
||||||
<Text style={{ color: "#ffffff", fontWeight: 700 }}>© 2025</Text>
|
<Text style={{ color: "#ffffff", fontWeight: 700 }}>© 2025</Text>
|
||||||
@ -277,3 +312,27 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FooterCard({ children, darkMode = false }: { children?: ReactNode; darkMode?: boolean }) {
|
||||||
|
const isMobile = useMediaQuery({ maxWidth: 600 });
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
background: isMobile ? "unset" : darkMode ? "#00000025" : "#fffffff2",
|
||||||
|
borderRadius: "20px",
|
||||||
|
zIndex: 1,
|
||||||
|
padding: isMobile ? "unset" : "10px 18px",
|
||||||
|
boxShadow: isMobile ? "unset" : darkMode ? "0px 5px 20px 5px rgb(255 255 255 / 5%)" : "0px 5px 15px 5px rgb(0 0 0 / 10%)"
|
||||||
|
}}
|
||||||
|
variant="borderless"
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
padding: 0,
|
||||||
|
height: "100%"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFo
|
|||||||
import type { Attribute } from "keycloakify/login/KcContext";
|
import type { Attribute } from "keycloakify/login/KcContext";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
import { Form, FormItemProps, Input, InputProps, Typography, Select, Radio, Checkbox, Button, Space, Divider } from "antd";
|
import { Form, FormItemProps, Input, InputProps, Typography, Select, Radio, Checkbox, Button, Space, Divider, Flex } from "antd";
|
||||||
import { EyeOutlined, EyeInvisibleOutlined, PlusOutlined, MinusOutlined } from "@ant-design/icons";
|
import { EyeOutlined, EyeInvisibleOutlined, PlusOutlined, MinusOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
const { Text, Title } = Typography;
|
const { Text, Title } = Typography;
|
||||||
@ -36,68 +36,72 @@ export default function UserProfileFormFields(props: UserProfileFormFieldsProps<
|
|||||||
const groupNameRef = { current: "" };
|
const groupNameRef = { current: "" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex vertical gap={"middle"}>
|
||||||
{formFieldStates.map(({ attribute, displayableErrors, valueOrValues }) => {
|
{formFieldStates.map(({ attribute, displayableErrors, valueOrValues }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GroupLabel attribute={attribute} groupNameRef={groupNameRef} i18n={i18n} />
|
<GroupLabel attribute={attribute} groupNameRef={groupNameRef} i18n={i18n} />
|
||||||
<Form.Item
|
<Flex gap={"5px"} vertical>
|
||||||
label={advancedMsg(attribute.displayName ?? "")}
|
<Text>
|
||||||
validateStatus={displayableErrors.length > 0 ? "error" : undefined}
|
{advancedMsg(attribute.displayName ?? "")}
|
||||||
help={
|
<Text type="danger"> *</Text>
|
||||||
<>
|
</Text>
|
||||||
{attribute.annotations.inputHelperTextBefore !== undefined && (
|
<Form.Item
|
||||||
<div id={`form-help-text-before-${attribute.name}`} aria-live="polite">
|
style={{ margin: 0 }}
|
||||||
{advancedMsg(attribute.annotations.inputHelperTextBefore)}
|
validateStatus={displayableErrors.length > 0 ? "error" : undefined}
|
||||||
</div>
|
rules={[{ required: true }]}
|
||||||
)}
|
name={attribute.name}
|
||||||
<FieldErrors attribute={attribute} displayableErrors={displayableErrors} fieldIndex={undefined} />
|
shouldUpdate
|
||||||
{attribute.annotations.inputHelperTextAfter !== undefined && (
|
key={attribute.name}
|
||||||
<div id={`form-help-text-after-${attribute.name}`} aria-live="polite">
|
{...rest}
|
||||||
{advancedMsg(attribute.annotations.inputHelperTextAfter)}
|
>
|
||||||
</div>
|
{BeforeField !== undefined && (
|
||||||
)}
|
<BeforeField
|
||||||
</>
|
attribute={attribute}
|
||||||
}
|
dispatchFormAction={dispatchFormAction}
|
||||||
rules={[{ required: true }]}
|
displayableErrors={displayableErrors}
|
||||||
name={attribute.name}
|
valueOrValues={valueOrValues}
|
||||||
shouldUpdate
|
kcClsx={kcClsx}
|
||||||
key={attribute.name}
|
i18n={i18n}
|
||||||
{...rest}
|
/>
|
||||||
>
|
)}
|
||||||
{BeforeField !== undefined && (
|
|
||||||
<BeforeField
|
|
||||||
attribute={attribute}
|
|
||||||
dispatchFormAction={dispatchFormAction}
|
|
||||||
displayableErrors={displayableErrors}
|
|
||||||
valueOrValues={valueOrValues}
|
|
||||||
kcClsx={kcClsx}
|
|
||||||
i18n={i18n}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<InputFieldByType
|
<InputFieldByType
|
||||||
attribute={attribute}
|
|
||||||
valueOrValues={valueOrValues}
|
|
||||||
displayableErrors={displayableErrors}
|
|
||||||
dispatchFormAction={dispatchFormAction}
|
|
||||||
i18n={i18n}
|
|
||||||
/>
|
|
||||||
{AfterField !== undefined && (
|
|
||||||
<AfterField
|
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
dispatchFormAction={dispatchFormAction}
|
|
||||||
displayableErrors={displayableErrors}
|
|
||||||
valueOrValues={valueOrValues}
|
valueOrValues={valueOrValues}
|
||||||
kcClsx={kcClsx}
|
displayableErrors={displayableErrors}
|
||||||
|
dispatchFormAction={dispatchFormAction}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
)}
|
{AfterField !== undefined && (
|
||||||
</Form.Item>
|
<AfterField
|
||||||
|
attribute={attribute}
|
||||||
|
dispatchFormAction={dispatchFormAction}
|
||||||
|
displayableErrors={displayableErrors}
|
||||||
|
valueOrValues={valueOrValues}
|
||||||
|
kcClsx={kcClsx}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
<Text type="danger">
|
||||||
|
{attribute.annotations.inputHelperTextBefore !== undefined && (
|
||||||
|
<div id={`form-help-text-before-${attribute.name}`} aria-live="polite">
|
||||||
|
{advancedMsg(attribute.annotations.inputHelperTextBefore)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FieldErrors attribute={attribute} displayableErrors={displayableErrors} fieldIndex={undefined} />
|
||||||
|
{attribute.annotations.inputHelperTextAfter !== undefined && (
|
||||||
|
<div id={`form-help-text-after-${attribute.name}`} aria-live="polite">
|
||||||
|
{advancedMsg(attribute.annotations.inputHelperTextAfter)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,8 +40,20 @@ h2 span {
|
|||||||
background: rgb(110, 0, 255);
|
background: rgb(110, 0, 255);
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
rgba(110, 0, 255, 1) 0%,
|
|
||||||
rgba(212, 0, 255, 1) 100%
|
rgb(255, 0, 153) 0%,
|
||||||
|
rgba(110, 0, 255) 100%
|
||||||
|
);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark > h2, .dark > span {
|
||||||
|
background: rgb(110, 0, 255);
|
||||||
|
background: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
rgb(255, 0, 115) 0%,
|
||||||
|
rgb(170, 0, 255) 100%
|
||||||
);
|
);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
@ -57,8 +69,8 @@ a.ant-typography,
|
|||||||
background: rgb(110, 0, 255) !important;
|
background: rgb(110, 0, 255) !important;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
rgba(110, 0, 255, 1) 0%,
|
rgb(170, 0, 255) 0%,
|
||||||
rgba(212, 0, 255, 1) 100%
|
rgb(255, 0, 115) 100%
|
||||||
) !important ;
|
) !important ;
|
||||||
-webkit-background-clip: text !important;
|
-webkit-background-clip: text !important;
|
||||||
-webkit-text-fill-color: transparent !important;
|
-webkit-text-fill-color: transparent !important;
|
||||||
@ -192,15 +204,36 @@ a.ant-typography,
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:-webkit-autofill,
|
/* Autofill fix for light mode */
|
||||||
input:-webkit-autofill:hover,
|
@media (prefers-color-scheme: light) {
|
||||||
input:-webkit-autofill:focus,
|
input:-webkit-autofill,
|
||||||
input:-webkit-autofill:active,
|
input:-webkit-autofill:hover,
|
||||||
input:-internal-autofill-selected {
|
input:-webkit-autofill:focus,
|
||||||
-webkit-background-clip: text;
|
input:-webkit-autofill:active,
|
||||||
-webkit-text-fill-color: #7e8500;
|
input:-internal-autofill-selected,
|
||||||
box-shadow: inset 0 0 20px 20px #ffffff !important;
|
input:-internal-autofill-previewed {
|
||||||
background-color: green !important
|
box-shadow: inset 0 0 20px 20px #ffffff00 !important;
|
||||||
|
-webkit-text-fill-color: #000 !important; /* black text in light mode */
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Autofill fix for dark mode */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active,
|
||||||
|
input:-internal-autofill-selected,
|
||||||
|
input:-internal-autofill-previewed {
|
||||||
|
box-shadow: inset 0 0 20px 20px #ffffff00 !important;
|
||||||
|
-webkit-text-fill-color: #fff !important; /* white text in dark mode */
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-outlined:has(input:-webkit-autofill) {
|
||||||
|
border-color: rgb(250, 173, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="password"] {
|
input[type="password"] {
|
||||||
|
|||||||
@ -247,6 +247,10 @@
|
|||||||
format("opentype");
|
format("opentype");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
line-height: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
span,
|
span,
|
||||||
h1,
|
h1,
|
||||||
|
|||||||
@ -66,14 +66,14 @@ export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext,
|
|||||||
<Text type="secondary">• {item}</Text>
|
<Text type="secondary">• {item}</Text>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
style={{ margin: "16px 0" }}
|
style={{ margin: "18px 0" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text strong style={{ marginTop: 8 }}>
|
<Text strong style={{ marginTop: 8 }}>
|
||||||
{msg("finalDeletionConfirmation")}
|
{msg("finalDeletionConfirmation")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex gap={"middle"} style={{ marginTop: "20px" }}>
|
<Flex gap={"middle"} style={{ marginTop: "18px" }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ flexGrow: 2 }}
|
style={{ flexGrow: 2 }}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { Button, Flex, Alert } from "antd";
|
import { Button, Flex, Typography } from "antd";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>, I18n>) {
|
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
const { msgStr, msg } = i18n;
|
const { msgStr, msg } = i18n;
|
||||||
@ -18,16 +20,10 @@ export default function DeleteCredential(props: PageProps<Extract<KcContext, { p
|
|||||||
headerNode={msg("deleteCredentialTitle", credentialLabel)}
|
headerNode={msg("deleteCredentialTitle", credentialLabel)}
|
||||||
>
|
>
|
||||||
<div style={{ margin: "0 15px" }}>
|
<div style={{ margin: "0 15px" }}>
|
||||||
<Alert
|
<Text>{msg("deleteCredentialMessage", credentialLabel)}</Text>
|
||||||
message={msg("deleteCredentialMessage", credentialLabel)}
|
|
||||||
type="warning"
|
|
||||||
showIcon
|
|
||||||
style={{
|
|
||||||
marginBottom: 26
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<form action={url.loginAction} method="POST">
|
<form action={url.loginAction} method="POST">
|
||||||
<Flex gap="middle">
|
<Flex gap="18px" style={{ marginTop: "22px" }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ flexGrow: 2 }}
|
style={{ flexGrow: 2 }}
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
|
|||||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
import { Alert, Button, Space} from "antd";
|
import { Flex, Button, Typography } from "antd";
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
|
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
@ -18,16 +20,17 @@ export default function Error(props: PageProps<Extract<KcContext, { pageId: "err
|
|||||||
displayMessage={false}
|
displayMessage={false}
|
||||||
headerNode={msg("errorTitle")}
|
headerNode={msg("errorTitle")}
|
||||||
>
|
>
|
||||||
<div style={{ margin: "0 15px" }}>
|
<Flex style={{ margin: "0 15px" }} vertical>
|
||||||
<Space direction="vertical" size="middle" style={{ marginBottom: 24 }}>
|
<Text type="danger">
|
||||||
<Alert message={<div dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />} type="error" showIcon />
|
<div dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />
|
||||||
</Space>
|
</Text>
|
||||||
|
|
||||||
{!skipLink && client !== undefined && client.baseUrl !== undefined && (
|
{!skipLink && client !== undefined && client.baseUrl !== undefined && (
|
||||||
<Button type="primary" id="backToApplication" size={"large"} block href={client.baseUrl}>
|
<Button type="primary" id="backToApplication" size={"large"} block href={client.baseUrl} style={{ marginTop: "22px" }}>
|
||||||
{msg("backToApplication")}
|
{msg("backToApplication")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Flex>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,15 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: () => <KcPageStory />
|
render: () => (
|
||||||
|
<KcPageStory
|
||||||
|
kcContext={{
|
||||||
|
logout: {
|
||||||
|
logoutRedirectUri: "#"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
};
|
};
|
||||||
export const WithoutRedirectUrl: Story = {
|
export const WithoutRedirectUrl: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Button, List, Typography, Space } from "antd";
|
import { Button, List, Typography, Flex } from "antd";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const { Paragraph } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>, I18n>) {
|
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
@ -26,8 +26,8 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
|
|||||||
documentTitle={msgStr("frontchannel-logout.title")}
|
documentTitle={msgStr("frontchannel-logout.title")}
|
||||||
headerNode={msg("frontchannel-logout.title")}
|
headerNode={msg("frontchannel-logout.title")}
|
||||||
>
|
>
|
||||||
<Space direction="vertical" style={{ width: "100%", padding: "0 15px" }}>
|
<Flex vertical style={{ width: "100%", padding: "0 15px" }} gap={"middle"}>
|
||||||
<Paragraph>{msg("frontchannel-logout.message")}</Paragraph>
|
<Text>{msg("frontchannel-logout.message")}</Text>
|
||||||
|
|
||||||
<List
|
<List
|
||||||
dataSource={logout.clients}
|
dataSource={logout.clients}
|
||||||
@ -41,11 +41,11 @@ export default function FrontchannelLogout(props: PageProps<Extract<KcContext, {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{logout.logoutRedirectUri && (
|
{logout.logoutRedirectUri && (
|
||||||
<Button type="primary" id="continue" href={logout.logoutRedirectUri}>
|
<Button type="primary" id="continue" href={logout.logoutRedirectUri} size="large">
|
||||||
{msg("doContinue")}
|
{msg("doContinue")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Flex>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Typography, Button, Space } from "antd";
|
import { Typography, Button, Flex } from "antd";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
@ -29,7 +29,7 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
|
|||||||
|
|
||||||
if (pageRedirectUri) {
|
if (pageRedirectUri) {
|
||||||
return (
|
return (
|
||||||
<Button type="primary" block size={"large"} href={pageRedirectUri} style={{ marginTop: 24 }}>
|
<Button type="primary" block size={"large"} href={pageRedirectUri}>
|
||||||
{msg("backToApplication")}
|
{msg("backToApplication")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -38,7 +38,7 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
|
|||||||
if (actionUri) {
|
if (actionUri) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button type="primary" block size={"large"} href={actionUri} style={{ marginTop: 24 }}>
|
<Button type="primary" block size={"large"} href={actionUri}>
|
||||||
{msg("proceedWithAction")}
|
{msg("proceedWithAction")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@ -48,7 +48,7 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
|
|||||||
if (client.baseUrl) {
|
if (client.baseUrl) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button type="primary" block size={"large"} href={client.baseUrl} style={{ marginTop: 24 }}>
|
<Button type="primary" block size={"large"} href={client.baseUrl}>
|
||||||
{msg("backToApplication")}
|
{msg("backToApplication")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@ -61,13 +61,12 @@ export default function Info(props: PageProps<Extract<KcContext, { pageId: "info
|
|||||||
return (
|
return (
|
||||||
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} displayMessage={false} headerNode={messageHeader}>
|
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} displayMessage={false} headerNode={messageHeader}>
|
||||||
<div style={{ padding: "0 15px" }}>
|
<div style={{ padding: "0 15px" }}>
|
||||||
<Space direction="vertical" size="middle">
|
<Flex vertical gap="18px">
|
||||||
<Text className="instruction">
|
<Text className="instruction">
|
||||||
<span dangerouslySetInnerHTML={{ __html: getMessageContent() }}></span>
|
<span dangerouslySetInnerHTML={{ __html: getMessageContent() }}></span>
|
||||||
</Text>
|
</Text>
|
||||||
</Space>
|
{renderActionLink()}
|
||||||
|
</Flex>
|
||||||
{renderActionLink()}
|
|
||||||
</div>
|
</div>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -70,12 +70,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
|
|||||||
doUseDefaultCss={false}
|
doUseDefaultCss={false}
|
||||||
headerNode={msg("loginAccountTitle")}
|
headerNode={msg("loginAccountTitle")}
|
||||||
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
|
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
|
||||||
infoNode={
|
infoNode={<Link href={url.registrationUrl}>{msg("doRegister")}</Link>}
|
||||||
<Space>
|
|
||||||
<Text>{msg("noAccount")}</Text>
|
|
||||||
<Link href={url.registrationUrl}>{msg("doRegister")}</Link>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
socialProvidersNode={
|
socialProvidersNode={
|
||||||
<>
|
<>
|
||||||
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
|
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
|
||||||
|
|||||||
@ -20,7 +20,16 @@ export const WithManualSetUp: Story = {
|
|||||||
render: () => (
|
render: () => (
|
||||||
<KcPageStory
|
<KcPageStory
|
||||||
kcContext={{
|
kcContext={{
|
||||||
mode: "manual"
|
mode: "manual",
|
||||||
|
client: {
|
||||||
|
attributes: {
|
||||||
|
logoUri: "https://cdn.tombutcher.work/logos/grafana-logo.png",
|
||||||
|
policyUri: "https://twitter.com/en/tos",
|
||||||
|
tosUri: "https://twitter.com/en/privacy"
|
||||||
|
},
|
||||||
|
name: "Grafana",
|
||||||
|
clientId: "twitter-client-id"
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { getKcClsx, KcClsx } from "keycloakify/login/lib/kcClsx";
|
// Removed unused kcClsx utilities
|
||||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
@ -17,10 +17,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
"cancel-aia": string;
|
"cancel-aia": string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { kcClsx } = getKcClsx({
|
// kcClsx no longer needed after layout changes
|
||||||
doUseDefaultCss,
|
|
||||||
classes
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
||||||
const [isCancelLoading, setIsCancelLoading] = useState(false);
|
const [isCancelLoading, setIsCancelLoading] = useState(false);
|
||||||
@ -79,8 +76,28 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
headerNode={<Title level={3}>{msg("loginTotpTitle")}</Title>}
|
headerNode={<Title level={3}>{msg("loginTotpTitle")}</Title>}
|
||||||
displayMessage={!messagesPerField.existsError("totp", "userLabel")}
|
displayMessage={!messagesPerField.existsError("totp", "userLabel")}
|
||||||
>
|
>
|
||||||
<Form layout="vertical" onFinish={handleSubmit} style={{ margin: "0 auto" }}>
|
<Form
|
||||||
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px", marginBottom: 24 }}>
|
onFinish={handleSubmit}
|
||||||
|
style={{
|
||||||
|
margin: "0 auto",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
|
overflow: "hidden"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
overflowY: "auto",
|
||||||
|
maxHeight: isMobile ? undefined : "175px",
|
||||||
|
padding: "0 15px",
|
||||||
|
marginBottom: "18px",
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Steps
|
<Steps
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
current={3}
|
current={3}
|
||||||
@ -184,73 +201,67 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider style={{ marginTop: "12px" }} />
|
||||||
<Form.Item
|
<Flex vertical gap={"middle"}>
|
||||||
label={
|
<Flex gap="small" vertical>
|
||||||
<>
|
<Text>
|
||||||
{msg("authenticatorCode")} <span className="required">*</span>
|
{msg("authenticatorCode")} <Text type="danger">*</Text>
|
||||||
</>
|
</Text>
|
||||||
}
|
<Flex gap="small" align="center">
|
||||||
name="totp"
|
<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />
|
||||||
validateStatus={messagesPerField.existsError("totp") ? "error" : undefined}
|
<Form.Item
|
||||||
help={
|
name="totp"
|
||||||
messagesPerField.existsError("totp") && (
|
validateStatus={messagesPerField.existsError("totp") ? "error" : undefined}
|
||||||
<span
|
style={{ margin: 0 }}
|
||||||
id="input-error-otp-code"
|
>
|
||||||
dangerouslySetInnerHTML={{
|
<Input.OTP id="totp" size="large" aria-invalid={messagesPerField.existsError("totp")} />
|
||||||
__html: kcSanitize(messagesPerField.get("totp"))
|
</Form.Item>
|
||||||
}}
|
</Flex>
|
||||||
|
{messagesPerField.existsError("totp") && (
|
||||||
|
<Text id="input-error-otp-code" type={"danger"}>
|
||||||
|
{kcSanitize(messagesPerField.get("totp"))}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Flex gap="small" vertical>
|
||||||
|
<Text>
|
||||||
|
{msg("loginTotpDeviceName")} {totp.otpCredentials.length >= 1 && <Text type="danger">*</Text>}
|
||||||
|
</Text>
|
||||||
|
<Form.Item
|
||||||
|
name="userLabel"
|
||||||
|
validateStatus={messagesPerField.existsError("userLabel") ? "error" : undefined}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
}
|
{messagesPerField.existsError("userLabel") && (
|
||||||
>
|
<Text id="input-error-otp-code" type={"danger"}>
|
||||||
<Input
|
{kcSanitize(messagesPerField.get("userLabel"))}
|
||||||
id="totp"
|
</Text>
|
||||||
autoComplete="off"
|
)}
|
||||||
size="large"
|
</Flex>
|
||||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
|
</Flex>
|
||||||
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>
|
||||||
<div style={{ padding: "0 15px" }}>
|
<div style={{ padding: "0 15px" }}>
|
||||||
<Form.Item>
|
<Form.Item style={{ margin: 0 }}>
|
||||||
<LogoutOtherSessions kcClsx={kcClsx} i18n={i18n} />
|
<Checkbox id="logout-sessions" name="logout-sessions" value="on" defaultChecked={true}>
|
||||||
|
{msg("logoutOtherSessions")}
|
||||||
|
</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item style={{ margin: 0 }}>
|
||||||
<Flex gap={"middle"}>
|
<Flex gap={"middle"}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
style={{ flexGrow: 2 }}
|
style={{ flexGrow: 2, marginTop: "22px" }}
|
||||||
size="large"
|
size="large"
|
||||||
id="saveTOTPBtn"
|
id="saveTOTPBtn"
|
||||||
iconPosition="end"
|
iconPosition="end"
|
||||||
@ -287,14 +298,3 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LogoutOtherSessions(props: { kcClsx: KcClsx; i18n: I18n }) {
|
|
||||||
const { i18n } = props;
|
|
||||||
const { msg } = i18n;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Checkbox id="logout-sessions" name="logout-sessions" value="on" defaultChecked={true}>
|
|
||||||
{msg("logoutOtherSessions")}
|
|
||||||
</Checkbox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button, Typography, Form, Input } from "antd";
|
import { Button, Typography, Form, Input, Flex } from "antd";
|
||||||
import { PageProps } from "keycloakify/login/pages/PageProps";
|
import { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { KcContext } from "../KcContext";
|
import { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
@ -44,40 +44,37 @@ export default function LoginOauth2DeviceVerifyUserCode(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={{}} headerNode={msg("oauth2DeviceVerificationTitle")}>
|
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={{}} headerNode={msg("oauth2DeviceVerificationTitle")}>
|
||||||
<Form id="kc-user-verify-device-user-code-form" layout="vertical" onFinish={handleSubmit} style={{ padding: "0 15px" }}>
|
<Form id="kc-user-verify-device-user-code-form" onFinish={handleSubmit} style={{ padding: "0 15px" }}>
|
||||||
<Text>{msg("verifyOAuth2DeviceUserCode")}</Text>
|
<Flex gap="18px" vertical>
|
||||||
<Form.Item label={"Code"} name="device_user_code" rules={[{ required: true, message: "Required" }]}>
|
<Text>{msg("verifyOAuth2DeviceUserCode")}</Text>
|
||||||
<Input
|
<Flex gap="small" align="center" style={{ marginBottom: "10px" }}>
|
||||||
id="device-user-code"
|
<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />
|
||||||
name="device_user_code"
|
<Form.Item name="device_user_code" rules={[{ required: true, message: "Required" }]}>
|
||||||
style={{ marginBottom: "10px" }}
|
<Input.OTP id="device-user-code" autoFocus size="large" />
|
||||||
autoComplete="off"
|
</Form.Item>
|
||||||
autoFocus
|
</Flex>
|
||||||
size="large"
|
|
||||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
block
|
block
|
||||||
size="large"
|
size="large"
|
||||||
iconPosition="end"
|
iconPosition="end"
|
||||||
icon={
|
icon={
|
||||||
<img
|
<img
|
||||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||||
style={{ marginTop: "3px", marginBottom: "0px" }}
|
style={{ marginTop: "3px", marginBottom: "0px" }}
|
||||||
width={14}
|
width={14}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
loading={isSubmitLoading}
|
loading={isSubmitLoading}
|
||||||
disabled={isSubmitLoading}
|
disabled={isSubmitLoading}
|
||||||
>
|
>
|
||||||
{msgStr("doSubmit")}
|
{msgStr("doSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Flex>
|
||||||
</Form>
|
</Form>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -16,8 +16,8 @@ const mockKcContext = {
|
|||||||
policyUri: "https://twitter.com/en/tos",
|
policyUri: "https://twitter.com/en/tos",
|
||||||
tosUri: "https://twitter.com/en/privacy"
|
tosUri: "https://twitter.com/en/privacy"
|
||||||
},
|
},
|
||||||
name: "Twitter",
|
name: "Grafana",
|
||||||
clientId: "twitter-client-id"
|
clientId: "grafana-client-id"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -50,14 +50,14 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
|||||||
headerNode={client.name ? msg("oauthGrantTitle", advancedMsgStr(client.name)) : msg("oauthGrantTitle", client.clientId)}
|
headerNode={client.name ? msg("oauthGrantTitle", advancedMsgStr(client.name)) : msg("oauthGrantTitle", client.clientId)}
|
||||||
>
|
>
|
||||||
<div style={{ margin: "0 15px" }}>
|
<div style={{ margin: "0 15px" }}>
|
||||||
<Alert message={msg("oauthGrantRequest")} type="warning" style={{ marginBottom: "20px" }} showIcon></Alert>
|
<Alert message={msg("oauthGrantRequest")} type="warning" style={{ marginBottom: "18px" }} showIcon></Alert>
|
||||||
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
||||||
<Alert
|
<Alert
|
||||||
message={
|
message={
|
||||||
client.name ? msg("oauthGrantInformation", advancedMsgStr(client.name)) : msg("oauthGrantInformation", client.clientId)
|
client.name ? msg("oauthGrantInformation", advancedMsgStr(client.name)) : msg("oauthGrantInformation", client.clientId)
|
||||||
}
|
}
|
||||||
type="info"
|
type="info"
|
||||||
style={{ marginBottom: "20px" }}
|
style={{ marginBottom: "18px" }}
|
||||||
showIcon
|
showIcon
|
||||||
></Alert>
|
></Alert>
|
||||||
)}
|
)}
|
||||||
@ -84,11 +84,12 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
|||||||
</Space>
|
</Space>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
|
style={{ marginBottom: "18px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
{(client.attributes.policyUri || client.attributes.tosUri) && (
|
||||||
<div style={{ marginTop: 10 }}>
|
<div style={{ marginBottom: "22px" }}>
|
||||||
<Space direction="vertical" style={{ marginTop: "12px" }}>
|
<Space direction="vertical">
|
||||||
{client.attributes.tosUri && (
|
{client.attributes.tosUri && (
|
||||||
<Text>
|
<Text>
|
||||||
{msg("oauthGrantReview")}{" "}
|
{msg("oauthGrantReview")}{" "}
|
||||||
@ -108,7 +109,7 @@ export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pa
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Flex gap="middle" style={{ marginTop: 24 }}>
|
<Flex gap="18px">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Form, Input, Button, Radio, List, Typography, Space } from "antd";
|
import { Form, Input, Button, Radio, List, Typography, Space, Flex } from "antd";
|
||||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
@ -72,30 +72,21 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Flex gap="small" vertical>
|
||||||
label={msg("loginOtpOneTime")}
|
<Text>{msg("authenticatorCode")}</Text>
|
||||||
validateStatus={messagesPerField.existsError("totp") ? "error" : ""}
|
<Flex gap="small" align="center">
|
||||||
name="otp"
|
<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />
|
||||||
help={
|
<Form.Item validateStatus={messagesPerField.existsError("totp") ? "error" : ""} name="otp" style={{ margin: 0 }}>
|
||||||
messagesPerField.existsError("totp") && (
|
<Input.OTP id="otp" autoFocus aria-invalid={messagesPerField.existsError("totp")} size="large" />
|
||||||
<span
|
</Form.Item>
|
||||||
id="input-error-otp-code"
|
</Flex>
|
||||||
dangerouslySetInnerHTML={{
|
{messagesPerField.existsError("totp") && (
|
||||||
__html: kcSanitize(messagesPerField.get("totp"))
|
<Text id="input-error-otp-code" type="danger">
|
||||||
}}
|
{kcSanitize(messagesPerField.get("totp"))}
|
||||||
/>
|
</Text>
|
||||||
)
|
)}
|
||||||
}
|
</Flex>
|
||||||
>
|
|
||||||
<Input
|
|
||||||
id="otp"
|
|
||||||
autoComplete="off"
|
|
||||||
autoFocus
|
|
||||||
aria-invalid={messagesPerField.existsError("totp")}
|
|
||||||
size="large"
|
|
||||||
prefix={<img src={"https://cdn.tombutcher.work/icons/auth/c-hash.svg"} width={14} style={{ marginRight: "3px" }} />}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -106,7 +97,7 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
|
|||||||
size="large"
|
size="large"
|
||||||
disabled={isSubmitLoading}
|
disabled={isSubmitLoading}
|
||||||
loading={isSubmitLoading}
|
loading={isSubmitLoading}
|
||||||
style={{ marginTop: 10 }}
|
style={{ marginTop: "22px" }}
|
||||||
iconPosition={"end"}
|
iconPosition={"end"}
|
||||||
icon={
|
icon={
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
iconPosition="end"
|
iconPosition="end"
|
||||||
style={{ marginTop: 24 }}
|
style={{ marginTop: 27 }}
|
||||||
icon={
|
icon={
|
||||||
<img
|
<img
|
||||||
src={"https://cdn.tombutcher.work/icons/auth/w-right.svg"}
|
src={"https://cdn.tombutcher.work/icons/auth/w-right.svg"}
|
||||||
|
|||||||
@ -34,11 +34,12 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
|||||||
>
|
>
|
||||||
<div style={{ margin: "0 15px" }}>
|
<div style={{ margin: "0 15px" }}>
|
||||||
<Space direction="vertical" size="middle">
|
<Space direction="vertical" size="middle">
|
||||||
<Space direction="vertical" size="large">
|
<Flex vertical>
|
||||||
<Alert message={msg("recovery-code-config-warning-title")} type="warning" showIcon />
|
<Alert message={msg("recovery-code-config-warning-title")} type="warning" showIcon style={{ marginBottom: "18px" }} />
|
||||||
<Text>{msg("recovery-code-config-warning-message")}</Text>
|
<Text>{msg("recovery-code-config-warning-message")}</Text>
|
||||||
</Space>
|
</Flex>
|
||||||
<Flex id={olRecoveryCodesListId} gap="middle" wrap>
|
|
||||||
|
<Flex id={olRecoveryCodesListId} gap="18px" wrap>
|
||||||
{recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList.map((code, index) => (
|
{recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList.map((code, index) => (
|
||||||
<Text key={index} strong code style={{ fontSize: "18px" }}>
|
<Text key={index} strong code style={{ fontSize: "18px" }}>
|
||||||
{formatRecoveryCode(code)}
|
{formatRecoveryCode(code)}
|
||||||
@ -58,58 +59,47 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
|||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Checkbox
|
<Form id="kc-recovery-codes-settings-form" method="post" action={kcContext.url.loginAction}>
|
||||||
id="kcRecoveryCodesConfirmationCheck"
|
<Input
|
||||||
name="kcRecoveryCodesConfirmationCheck"
|
type="hidden"
|
||||||
onChange={e => setIsConfirmed(e.target.checked)}
|
name="generatedRecoveryAuthnCodes"
|
||||||
style={{ marginBottom: "0px" }}
|
value={recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}
|
||||||
>
|
/>
|
||||||
{msg("recovery-codes-confirmation-message")}
|
<Input type="hidden" name="generatedAt" value={recoveryAuthnCodesConfigBean.generatedAt} />
|
||||||
</Checkbox>
|
<Input type="hidden" id="userLabel" name="userLabel" value={msgStr("recovery-codes-label-default")} />
|
||||||
</Space>
|
|
||||||
|
|
||||||
<Form id="kc-recovery-codes-settings-form" method="post" action={kcContext.url.loginAction}>
|
<Checkbox
|
||||||
<Input
|
id="kcRecoveryCodesConfirmationCheck"
|
||||||
type="hidden"
|
name="kcRecoveryCodesConfirmationCheck"
|
||||||
name="generatedRecoveryAuthnCodes"
|
onChange={e => setIsConfirmed(e.target.checked)}
|
||||||
value={recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}
|
style={{ marginBottom: "9px" }}
|
||||||
/>
|
>
|
||||||
<Input type="hidden" name="generatedAt" value={recoveryAuthnCodesConfigBean.generatedAt} />
|
{msg("recovery-codes-confirmation-message")}
|
||||||
<Input type="hidden" id="userLabel" name="userLabel" value={msgStr("recovery-codes-label-default")} />
|
</Checkbox>
|
||||||
|
|
||||||
<LogoutOtherSessions i18n={i18n} />
|
<Checkbox id="logout-sessions" name="logout-sessions" value="on" defaultChecked style={{ marginBottom: "22px" }}>
|
||||||
|
{msg("logoutOtherSessions")}
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
<div style={{ marginTop: 24 }}>
|
<div>
|
||||||
{isAppInitiatedAction ? (
|
{isAppInitiatedAction ? (
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed}>
|
<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")}
|
{msg("recovery-codes-action-complete")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="large" id="cancelRecoveryAuthnCodesBtn" name="cancel-aia" value="true" htmlType="submit">
|
)}
|
||||||
{msg("recovery-codes-action-cancel")}
|
</div>
|
||||||
</Button>
|
</Form>
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
|
||||||
<Button type="primary" size="large" id="saveRecoveryAuthnCodesBtn" htmlType="submit" disabled={!isConfirmed} block>
|
|
||||||
{msg("recovery-codes-action-complete")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LogoutOtherSessions(props: { i18n: I18n }) {
|
|
||||||
const { i18n } = props;
|
|
||||||
const { msg } = i18n;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ marginTop: "16px" }}>
|
|
||||||
<Checkbox id="logout-sessions" name="logout-sessions" value="on" defaultChecked>
|
|
||||||
{msg("logoutOtherSessions")}
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcC
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={msg("auth-recovery-code-prompt", `${recoveryAuthnCodesInputBean.codeNumber}`)}
|
label={msg("auth-recovery-code-prompt", `${recoveryAuthnCodesInputBean.codeNumber}`)}
|
||||||
validateStatus={hasError ? "error" : ""}
|
validateStatus={hasError ? "error" : ""}
|
||||||
|
style={{ margin: 0 }}
|
||||||
help={
|
help={
|
||||||
hasError ? (
|
hasError ? (
|
||||||
<span
|
<span
|
||||||
@ -48,7 +49,7 @@ export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcC
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button type="primary" htmlType="submit" block size="large" id="kc-login" name="login" style={{ marginTop: 12 }}>
|
<Button type="primary" htmlType="submit" block size="large" id="kc-login" name="login" style={{ marginTop: "22px" }}>
|
||||||
{msgStr("doLogIn")}
|
{msgStr("doLogIn")}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Form, Radio, Button, Typography, Space } from "antd";
|
import { Form, Radio, Button, Typography, List, Flex } from "antd";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
@ -20,40 +20,38 @@ export default function LoginResetOtp(props: PageProps<Extract<KcContext, { page
|
|||||||
headerNode={msg("doLogIn")}
|
headerNode={msg("doLogIn")}
|
||||||
>
|
>
|
||||||
<Form id="kc-otp-reset-form" layout="vertical" method="post" action={url.loginAction} style={{ padding: "0 15px" }}>
|
<Form id="kc-otp-reset-form" layout="vertical" method="post" action={url.loginAction} style={{ padding: "0 15px" }}>
|
||||||
<Space direction="vertical" style={{ width: "100%" }}>
|
<Flex vertical style={{ width: "100%" }}>
|
||||||
<Text id="kc-otp-reset-form-description">{msg("otp-reset-description")}</Text>
|
<Flex vertical gap={"middle"} style={{ marginBottom: "22px" }}>
|
||||||
|
<Text id="kc-otp-reset-form-description">{msg("otp-reset-description")}</Text>
|
||||||
<Form.Item name="selectedCredentialId">
|
<Form.Item name="selectedCredentialId">
|
||||||
<Radio.Group defaultValue={configuredOtpCredentials.selectedCredentialId || undefined} style={{ width: "100%" }}>
|
<Radio.Group defaultValue={configuredOtpCredentials.selectedCredentialId} style={{ width: "100%" }}>
|
||||||
{configuredOtpCredentials.userOtpCredentials.map((otpCredential, index) => (
|
<List
|
||||||
<Radio
|
dataSource={configuredOtpCredentials.userOtpCredentials}
|
||||||
key={otpCredential.id}
|
bordered
|
||||||
value={otpCredential.id}
|
style={{ width: "100%", lineHeight: 1 }}
|
||||||
id={`kc-otp-credential-${index}`}
|
renderItem={(otpCredential, index) => (
|
||||||
style={{
|
<List.Item key={index}>
|
||||||
display: "block",
|
<Flex align="center" gap={"small"} justify="space-between">
|
||||||
margin: "8px 0"
|
<Radio id={`kc-otp-credential-${index}`} value={otpCredential.id} style={{ display: "block" }}>
|
||||||
}}
|
<Text style={{ position: "relative", top: "-3px" }}>{otpCredential.userLabel}</Text>
|
||||||
>
|
</Radio>
|
||||||
<Space style={{ paddingBottom: "10px" }}>
|
<img
|
||||||
<img
|
src={"https://cdn.tombutcher.work/icons/auth/c-key.svg"}
|
||||||
src={"https://cdn.tombutcher.work/icons/auth/c-key.svg"}
|
style={{ marginRight: "3px", height: "16px" }}
|
||||||
width={14}
|
/>
|
||||||
style={{ marginRight: "3px", marginBottom: "5px" }}
|
</Flex>
|
||||||
/>
|
</List.Item>
|
||||||
<Text>{otpCredential.userLabel}</Text>
|
)}
|
||||||
</Space>
|
/>
|
||||||
</Radio>
|
</Radio.Group>
|
||||||
))}
|
</Form.Item>
|
||||||
</Radio.Group>
|
</Flex>
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button id="kc-otp-reset-form-submit" type="primary" htmlType="submit" block size="large">
|
<Button id="kc-otp-reset-form-submit" type="primary" htmlType="submit" block size="large">
|
||||||
{msgStr("doSubmit")}
|
{msgStr("doSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Space>
|
</Flex>
|
||||||
</Form>
|
</Form>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Form, Input, Button, Alert, Space, Typography } from "antd";
|
import { Form, Input, Button, Alert, Typography } from "antd";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
@ -43,7 +43,8 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
|
|||||||
kcContext={kcContext}
|
kcContext={kcContext}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
doUseDefaultCss={false}
|
doUseDefaultCss={false}
|
||||||
infoNode={false}
|
infoNode={<Link href={url.loginUrl}>{msg("backToLogin")}</Link>}
|
||||||
|
displayInfo={true}
|
||||||
displayMessage={!messagesPerField.existsError("username")}
|
displayMessage={!messagesPerField.existsError("username")}
|
||||||
headerNode={msg("emailForgotTitle")}
|
headerNode={msg("emailForgotTitle")}
|
||||||
>
|
>
|
||||||
@ -57,7 +58,7 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
|
|||||||
method="post"
|
method="post"
|
||||||
style={{ padding: "0 15px" }}
|
style={{ padding: "0 15px" }}
|
||||||
>
|
>
|
||||||
{displayInfo && displayMessage && <Alert message={infoMessage} type="info" showIcon style={{ marginBottom: 24 }} />}
|
{displayInfo && displayMessage && <Alert message={infoMessage} type="info" showIcon style={{ marginBottom: "18px" }} />}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={inputLabel}
|
label={inputLabel}
|
||||||
@ -84,15 +85,11 @@ export default function LoginResetPassword(props: PageProps<Extract<KcContext, {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item style={{ marginBottom: 22, marginTop: 26 }}>
|
<Form.Item style={{ marginBottom: 22, marginTop: "22px" }}>
|
||||||
<Button type="primary" htmlType="submit" size="large" block>
|
<Button type="primary" htmlType="submit" size="large" block>
|
||||||
{msgStr("doSubmit")}
|
{msgStr("doSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space style={{ width: "100%", justifyContent: "center" }}>
|
|
||||||
<Link href={url.loginUrl}>{msg("backToLogin")}</Link>
|
|
||||||
</Space>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button, Form, Input, Checkbox, Flex } from "antd";
|
import { Button, Form, Input, Checkbox, Flex, Typography } from "antd";
|
||||||
import { EyeOutlined, EyeInvisibleOutlined } from "@ant-design/icons";
|
import { EyeOutlined, EyeInvisibleOutlined } from "@ant-design/icons";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
type FieldType = {
|
type FieldType = {
|
||||||
"password-new": string;
|
"password-new": string;
|
||||||
"password-confirm": string;
|
"password-confirm": string;
|
||||||
@ -57,51 +59,67 @@ export default function LoginUpdatePassword(props: PageProps<Extract<KcContext,
|
|||||||
headerNode={msg("updatePasswordTitle")}
|
headerNode={msg("updatePasswordTitle")}
|
||||||
>
|
>
|
||||||
<Form id="kc-passwd-update-form" layout="vertical" onFinish={handleSubmit} style={{ padding: "0 15px" }}>
|
<Form id="kc-passwd-update-form" layout="vertical" onFinish={handleSubmit} style={{ padding: "0 15px" }}>
|
||||||
<Form.Item
|
<Flex style={{ marginBottom: "22px" }} vertical gap={"middle"}>
|
||||||
label={msg("passwordNew")}
|
<Flex vertical gap={"5px"}>
|
||||||
name="password-new"
|
<Text>
|
||||||
rules={[{ required: true, message: messagesPerField.get("password-confirm") }]}
|
{msg("passwordNew")}
|
||||||
validateStatus={messagesPerField.existsError("password") ? "error" : ""}
|
<Text type="danger"> *</Text>
|
||||||
help={messagesPerField.existsError("password") ? messagesPerField.get("password") : ""}
|
</Text>
|
||||||
>
|
<Form.Item
|
||||||
<Input.Password
|
name="password-new"
|
||||||
autoFocus
|
style={{ margin: 0 }}
|
||||||
autoComplete="new-password"
|
rules={[{ required: true, message: messagesPerField.get("password-confirm") }]}
|
||||||
size="large"
|
validateStatus={messagesPerField.existsError("password") ? "error" : ""}
|
||||||
prefix={<img src="https://cdn.tombutcher.work/icons/auth/c-lock.svg" style={{ marginRight: "3px" }} height={14} />}
|
>
|
||||||
aria-invalid={messagesPerField.existsError("password", "password-confirm")}
|
<Input.Password
|
||||||
iconRender={visible => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
|
autoFocus
|
||||||
/>
|
autoComplete="new-password"
|
||||||
</Form.Item>
|
size="large"
|
||||||
|
prefix={<img src="https://cdn.tombutcher.work/icons/auth/c-lock.svg" style={{ marginRight: "3px" }} height={14} />}
|
||||||
|
aria-invalid={messagesPerField.existsError("password", "password-confirm")}
|
||||||
|
iconRender={visible => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
{messagesPerField.existsError("password") ? <Text type="danger">{messagesPerField.get("password")}</Text> : null}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Form.Item
|
<Flex vertical gap={"5px"}>
|
||||||
label={msg("passwordConfirm")}
|
<Text>
|
||||||
name="password-confirm"
|
{msg("passwordConfirm")}
|
||||||
rules={[{ required: true, message: messagesPerField.get("password-confirm") }]}
|
<Text type="danger"> *</Text>
|
||||||
validateStatus={messagesPerField.existsError("password-confirm") ? "error" : ""}
|
</Text>
|
||||||
help={messagesPerField.existsError("password-confirm") ? messagesPerField.get("password-confirm") : ""}
|
<Form.Item
|
||||||
>
|
name="password-confirm"
|
||||||
<Input.Password
|
style={{ margin: 0 }}
|
||||||
autoComplete="new-password"
|
rules={[{ required: true, message: messagesPerField.get("password-confirm") }]}
|
||||||
size="large"
|
validateStatus={messagesPerField.existsError("password-confirm") ? "error" : ""}
|
||||||
prefix={<img src="https://cdn.tombutcher.work/icons/auth/c-lock.svg" style={{ marginRight: "3px" }} height={14} />}
|
>
|
||||||
aria-invalid={messagesPerField.existsError("password", "password-confirm")}
|
<Input.Password
|
||||||
iconRender={visible => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
|
autoFocus
|
||||||
/>
|
autoComplete="new-password"
|
||||||
</Form.Item>
|
size="large"
|
||||||
|
prefix={<img src="https://cdn.tombutcher.work/icons/auth/c-lock.svg" style={{ marginRight: "3px" }} height={14} />}
|
||||||
<Form.Item name="logout-sessions" valuePropName="checked" initialValue={true}>
|
aria-invalid={messagesPerField.existsError("password", "password-confirm")}
|
||||||
<Checkbox>{msg("logoutOtherSessions")}</Checkbox>
|
iconRender={visible => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
|
||||||
</Form.Item>
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
{messagesPerField.existsError("password-confirm") ? (
|
||||||
|
<Text type="danger">{messagesPerField.get("password-confirm")}</Text>
|
||||||
|
) : null}
|
||||||
|
</Flex>
|
||||||
|
<Form.Item name="logout-sessions" valuePropName="checked" initialValue={true} style={{ margin: 0 }}>
|
||||||
|
<Checkbox>{msg("logoutOtherSessions")}</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Flex gap="middle">
|
<Flex gap="18px">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
iconPosition="end"
|
iconPosition="end"
|
||||||
style={{ marginTop: 6, flexGrow: 2 }}
|
style={{ flexGrow: 2 }}
|
||||||
icon={
|
icon={
|
||||||
<img
|
<img
|
||||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button, Divider, Form, FormProps, Flex } from "antd";
|
import { Button, Form, FormProps, Flex } from "antd";
|
||||||
import type { JSX } from "keycloakify/tools/JSX";
|
import type { JSX } from "keycloakify/tools/JSX";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFieldsProps";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
import { useMediaQuery } from "react-responsive";
|
||||||
|
import Template from "../Template";
|
||||||
|
|
||||||
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>, I18n> & {
|
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>, I18n> & {
|
||||||
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;
|
||||||
@ -16,6 +18,7 @@ export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
|
|||||||
const { kcContext, i18n, UserProfileFormFields, doMakeUserConfirmPassword } = props;
|
const { kcContext, i18n, UserProfileFormFields, doMakeUserConfirmPassword } = props;
|
||||||
const { url, isAppInitiatedAction } = kcContext;
|
const { url, isAppInitiatedAction } = kcContext;
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
const isMobile = useMediaQuery({ maxWidth: 600 });
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@ -59,51 +62,61 @@ export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
<Template
|
||||||
<UserProfileFormFields
|
kcContext={kcContext}
|
||||||
kcContext={kcContext}
|
i18n={i18n}
|
||||||
i18n={i18n}
|
doUseDefaultCss={false}
|
||||||
kcClsx={options => (typeof options === "object" && options !== null ? (options as { classKey?: string }).classKey ?? "" : "")}
|
headerNode={i18n.msg("loginProfileTitle")}
|
||||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
displayMessage={kcContext.messagesPerField.exists("global")}
|
||||||
onIsFormSubmittableValueChange={() => {}}
|
>
|
||||||
/>
|
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||||
|
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px" }}>
|
||||||
|
<UserProfileFormFields
|
||||||
|
kcContext={kcContext}
|
||||||
|
i18n={i18n}
|
||||||
|
kcClsx={options => (typeof options === "object" && options !== null ? (options as { classKey?: string }).classKey ?? "" : "")}
|
||||||
|
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||||
|
onIsFormSubmittableValueChange={() => {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: "0 15px" }}>
|
||||||
|
<Form.Item style={{ marginTop: 22 }}>
|
||||||
|
<Flex gap="18px">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={isSubmitLoading}
|
||||||
|
disabled={isSubmitLoading || isCancelLoading}
|
||||||
|
iconPosition="end"
|
||||||
|
style={{ flexGrow: 2 }}
|
||||||
|
icon={
|
||||||
|
<img
|
||||||
|
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
||||||
|
style={{ marginTop: "3px", marginBottom: "0px" }}
|
||||||
|
width={14}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{msgStr("doSubmit")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Divider />
|
{isAppInitiatedAction && (
|
||||||
<Form.Item>
|
<Button
|
||||||
<Flex gap="middle">
|
size="large"
|
||||||
<Button
|
htmlType="submit"
|
||||||
type="primary"
|
style={{ flexGrow: 2 }}
|
||||||
size="large"
|
loading={isCancelLoading}
|
||||||
htmlType="submit"
|
disabled={isSubmitLoading || isCancelLoading}
|
||||||
loading={isSubmitLoading}
|
onClick={handleCancel}
|
||||||
disabled={isSubmitLoading || isCancelLoading}
|
>
|
||||||
iconPosition="end"
|
{msg("doCancel")}
|
||||||
style={{ flexGrow: 2 }}
|
</Button>
|
||||||
icon={
|
)}
|
||||||
<img
|
</Flex>
|
||||||
src={"https://cdn.tombutcher.work/icons/auth/w-checkmark.svg"}
|
</Form.Item>
|
||||||
style={{ marginTop: "3px", marginBottom: "0px" }}
|
</div>
|
||||||
width={14}
|
</Form>
|
||||||
/>
|
</Template>
|
||||||
}
|
|
||||||
>
|
|
||||||
{msgStr("doSubmit")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isAppInitiatedAction && (
|
|
||||||
<Button
|
|
||||||
size="large"
|
|
||||||
htmlType="submit"
|
|
||||||
style={{ flexGrow: 2 }}
|
|
||||||
loading={isCancelLoading}
|
|
||||||
disabled={isSubmitLoading || isCancelLoading}
|
|
||||||
onClick={handleCancel}
|
|
||||||
>
|
|
||||||
{msg("doCancel")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export const Default: Story = {
|
|||||||
policyUri: "https://twitter.com/en/tos",
|
policyUri: "https://twitter.com/en/tos",
|
||||||
tosUri: "https://twitter.com/en/privacy"
|
tosUri: "https://twitter.com/en/privacy"
|
||||||
},
|
},
|
||||||
name: "Twitter",
|
name: "Gitea",
|
||||||
clientId: "twitter-client-id"
|
clientId: "twitter-client-id"
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -66,12 +66,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
|
|||||||
doUseDefaultCss={false}
|
doUseDefaultCss={false}
|
||||||
headerNode={msg("loginAccountTitle")}
|
headerNode={msg("loginAccountTitle")}
|
||||||
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
|
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
|
||||||
infoNode={
|
infoNode={<Link href={url.registrationUrl}>{msg("doRegister")}</Link>}
|
||||||
<Space>
|
|
||||||
<Text>{msg("noAccount")}</Text>
|
|
||||||
<Link href={url.registrationUrl}>{msg("doRegister")}</Link>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
socialProvidersNode={
|
socialProvidersNode={
|
||||||
<>
|
<>
|
||||||
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
|
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
|
||||||
|
|||||||
@ -40,7 +40,15 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template kcContext={kcContext} i18n={i18n} doUseDefaultCss={false} classes={props.classes} headerNode={msg("logoutConfirmTitle")}>
|
<Template
|
||||||
|
kcContext={kcContext}
|
||||||
|
i18n={i18n}
|
||||||
|
doUseDefaultCss={false}
|
||||||
|
classes={props.classes}
|
||||||
|
headerNode={msg("logoutConfirmTitle")}
|
||||||
|
infoNode={<Link href={client.baseUrl}>{msg("backToApplication")}</Link>}
|
||||||
|
displayInfo={!logoutConfirm.skipLink && client.baseUrl != null}
|
||||||
|
>
|
||||||
<div id="kc-logout-confirm" style={{ margin: "0 15px" }}>
|
<div id="kc-logout-confirm" style={{ margin: "0 15px" }}>
|
||||||
<div style={{ marginBottom: "25px" }}>
|
<div style={{ marginBottom: "25px" }}>
|
||||||
<Text className="instruction">{msg("logoutConfirmHeader")}</Text>
|
<Text className="instruction">{msg("logoutConfirmHeader")}</Text>
|
||||||
@ -64,11 +72,6 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
|
|||||||
>
|
>
|
||||||
{msgStr("doLogout")}
|
{msgStr("doLogout")}
|
||||||
</Button>
|
</Button>
|
||||||
{!logoutConfirm.skipLink && client.baseUrl && (
|
|
||||||
<div style={{ marginTop: 16, textAlign: "center" }}>
|
|
||||||
<Link href={client.baseUrl}>{msg("backToApplication")}</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Form, Button, Checkbox, Typography, Space, FormProps, FormItemProps } from "antd";
|
import { Form, Button, Checkbox, Typography, Flex, FormProps, FormItemProps } from "antd";
|
||||||
import type { JSX } from "keycloakify/tools/JSX";
|
import type { JSX } from "keycloakify/tools/JSX";
|
||||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||||
@ -8,6 +8,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
|
|||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
import { useMediaQuery } from "react-responsive";
|
import { useMediaQuery } from "react-responsive";
|
||||||
|
import Template from "../Template";
|
||||||
|
|
||||||
const { Title, Text, Link } = Typography;
|
const { Title, Text, Link } = Typography;
|
||||||
|
|
||||||
@ -65,71 +66,78 @@ export default function Register(props: RegisterProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form form={form} layout="vertical" name="registerForm" requiredMark onFinish={onFinish}>
|
<Template
|
||||||
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px" }}>
|
kcContext={kcContext}
|
||||||
<UserProfileFormFields
|
i18n={i18n}
|
||||||
layout="vertical"
|
doUseDefaultCss={false}
|
||||||
kcContext={kcContext}
|
classes={{}}
|
||||||
i18n={i18n}
|
headerNode={kcContext.messageHeader !== undefined ? i18n.advancedMsg(kcContext.messageHeader) : i18n.msg("registerTitle")}
|
||||||
kcClsx={options => (typeof options === "object" && options !== null ? (options as { classKey?: string }).classKey ?? "" : "")}
|
displayMessage={kcContext.messagesPerField.exists("global")}
|
||||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
displayInfo={true}
|
||||||
onIsFormSubmittableValueChange={() => {}}
|
infoNode={<Link href={url.loginUrl}>{msg("backToLogin")}</Link>}
|
||||||
/>
|
>
|
||||||
</div>
|
<Form form={form} layout="vertical" name="registerForm" requiredMark onFinish={onFinish}>
|
||||||
<div style={{ padding: "0 15px" }}>
|
<div style={{ overflowY: "scroll", maxHeight: isMobile ? "100%" : "250px", padding: "0 15px" }}>
|
||||||
{termsAcceptanceRequired && (
|
<UserProfileFormFields
|
||||||
<TermsAcceptance
|
layout="vertical"
|
||||||
|
kcContext={kcContext}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
messagesPerField={messagesPerField}
|
kcClsx={options => (typeof options === "object" && options !== null ? (options as { classKey?: string }).classKey ?? "" : "")}
|
||||||
areTermsAccepted={areTermsAccepted}
|
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||||
onAreTermsAcceptedValueChange={setAreTermsAccepted}
|
onIsFormSubmittableValueChange={() => {}}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
|
<div style={{ padding: "0 15px" }}>
|
||||||
|
{termsAcceptanceRequired && (
|
||||||
|
<TermsAcceptance
|
||||||
|
i18n={i18n}
|
||||||
|
messagesPerField={messagesPerField}
|
||||||
|
areTermsAccepted={areTermsAccepted}
|
||||||
|
onAreTermsAcceptedValueChange={setAreTermsAccepted}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{recaptchaRequired && (recaptchaVisible || recaptchaAction === undefined) && (
|
{recaptchaRequired && (recaptchaVisible || recaptchaAction === undefined) && (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} data-action={recaptchaAction} />
|
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} data-action={recaptchaAction} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Form.Item style={{ marginTop: 22 }}>
|
||||||
|
<Flex style={{ width: "100%" }} gap={"middle"}>
|
||||||
|
{recaptchaRequired && !recaptchaVisible && recaptchaAction !== undefined ? (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
style={{ flexGrow: 1 }}
|
||||||
|
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"
|
||||||
|
style={{ flexGrow: 1 }}
|
||||||
|
size="large"
|
||||||
|
loading={isLoading}
|
||||||
|
disabled={!submittable || isLoading || (termsAcceptanceRequired && !areTermsAccepted)}
|
||||||
|
>
|
||||||
|
{msgStr("doRegister")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
</div>
|
||||||
<Form.Item style={{ marginTop: 24 }}>
|
</Form>
|
||||||
<Space direction="vertical" style={{ width: "100%" }}>
|
</Template>
|
||||||
{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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Typography, Button, List, Flex, Space } from "antd";
|
import { Typography, Button, List, Flex } from "antd";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import type { KcContext } from "../KcContext";
|
import type { KcContext } from "../KcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
@ -7,7 +7,7 @@ const { Title, Text } = Typography;
|
|||||||
|
|
||||||
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>, I18n>) {
|
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
const { url, auth, client } = kcContext;
|
const { url, auth } = kcContext;
|
||||||
const { msg, advancedMsg } = i18n;
|
const { msg, advancedMsg } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -17,24 +17,9 @@ export default function SelectAuthenticator(props: PageProps<Extract<KcContext,
|
|||||||
doUseDefaultCss={doUseDefaultCss}
|
doUseDefaultCss={doUseDefaultCss}
|
||||||
classes={classes}
|
classes={classes}
|
||||||
displayInfo={false}
|
displayInfo={false}
|
||||||
headerNode={
|
headerNode={msg("loginChooseAuthenticator")}
|
||||||
<>
|
|
||||||
{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("loginChooseAuthenticator")}</Title>
|
|
||||||
</Space>
|
|
||||||
) : (
|
|
||||||
<Title level={3}>{msg("loginChooseAuthenticator")}</Title>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<form id="kc-select-credential-form" action={url.loginAction} method="post" style={{ padding: '0 15px'}}>
|
<form id="kc-select-credential-form" action={url.loginAction} method="post" style={{ padding: "0 15px" }}>
|
||||||
<List
|
<List
|
||||||
dataSource={auth.authenticationSelections}
|
dataSource={auth.authenticationSelections}
|
||||||
itemLayout="horizontal"
|
itemLayout="horizontal"
|
||||||
@ -42,8 +27,7 @@ export default function SelectAuthenticator(props: PageProps<Extract<KcContext,
|
|||||||
renderItem={authenticationSelection => (
|
renderItem={authenticationSelection => (
|
||||||
<List.Item
|
<List.Item
|
||||||
style={{
|
style={{
|
||||||
padding: 0,
|
padding: 0
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
|
|||||||
{/* Scrollable terms box */}
|
{/* Scrollable terms box */}
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 26,
|
marginBottom: "22px",
|
||||||
maxHeight: "250px",
|
maxHeight: "250px",
|
||||||
overflow: "auto"
|
overflow: "auto"
|
||||||
}}
|
}}
|
||||||
@ -54,7 +54,7 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
|
|||||||
<Paragraph>{msg("termsText")}</Paragraph>
|
<Paragraph>{msg("termsText")}</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Flex gap="middle">
|
<Flex gap="18px">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
|
|||||||
@ -37,8 +37,8 @@ export default function UpdateEmail(props: UpdateEmailProps) {
|
|||||||
displayMessage={messagesPerField.exists("global")}
|
displayMessage={messagesPerField.exists("global")}
|
||||||
headerNode={msg("updateEmailTitle")}
|
headerNode={msg("updateEmailTitle")}
|
||||||
>
|
>
|
||||||
<form id="kc-update-email-form" action={url.loginAction} method="post" style={{ padding: '0 15px'}}>
|
<form id="kc-update-email-form" action={url.loginAction} method="post" style={{ padding: "0 15px" }}>
|
||||||
<div style={{ marginBottom: 15 }} className="kctbform">
|
<Flex style={{ marginBottom: "22px" }} vertical gap={"middle"}>
|
||||||
{/* Keep original UserProfileFormFields component, but wrap in Ant Design styling */}
|
{/* Keep original UserProfileFormFields component, but wrap in Ant Design styling */}
|
||||||
<UserProfileFormFields
|
<UserProfileFormFields
|
||||||
kcContext={kcContext}
|
kcContext={kcContext}
|
||||||
@ -47,13 +47,11 @@ export default function UpdateEmail(props: UpdateEmailProps) {
|
|||||||
onIsFormSubmittableValueChange={setIsFormSubmittable}
|
onIsFormSubmittableValueChange={setIsFormSubmittable}
|
||||||
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: 24 }}>
|
|
||||||
<Checkbox id="logout-sessions" name="logout-sessions" value="on" defaultChecked={true}>
|
<Checkbox id="logout-sessions" name="logout-sessions" value="on" defaultChecked={true}>
|
||||||
{msg("logoutOtherSessions")}
|
{msg("logoutOtherSessions")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</Flex>
|
||||||
<Flex gap={"middle"}>
|
<Flex gap={"middle"}>
|
||||||
<Button
|
<Button
|
||||||
style={{ flexGrow: 2 }}
|
style={{ flexGrow: 2 }}
|
||||||
|
|||||||
@ -18,7 +18,17 @@ export const Default: Story = {
|
|||||||
kcContext={{
|
kcContext={{
|
||||||
auth: {
|
auth: {
|
||||||
attemptedUsername: "max.mustermann@mail.com",
|
attemptedUsername: "max.mustermann@mail.com",
|
||||||
showUsername: true
|
showUsername: true,
|
||||||
|
showTryAnotherWayLink: true
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
attributes: {
|
||||||
|
logoUri: "https://cdn.tombutcher.work/logos/grafana-logo.png",
|
||||||
|
policyUri: "https://twitter.com/en/tos",
|
||||||
|
tosUri: "https://twitter.com/en/privacy"
|
||||||
|
},
|
||||||
|
name: "Grafana",
|
||||||
|
clientId: "twitter-client-id"
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -60,12 +60,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
doUseDefaultCss={false}
|
doUseDefaultCss={false}
|
||||||
displayInfo={realm.registrationAllowed && !registrationDisabled}
|
displayInfo={realm.registrationAllowed && !registrationDisabled}
|
||||||
infoNode={
|
infoNode={<Link href={url.registrationUrl}>{msg("doRegister")}</Link>}
|
||||||
<Space>
|
|
||||||
<Text>{msg("noAccount")}</Text>
|
|
||||||
<Link href={url.registrationUrl}>{msg("doRegister")}</Link>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
headerNode={msg("webauthn-login-title")}
|
headerNode={msg("webauthn-login-title")}
|
||||||
>
|
>
|
||||||
<div style={{ padding: "0 15px" }}>
|
<div style={{ padding: "0 15px" }}>
|
||||||
@ -89,7 +84,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
{shouldDisplayAuthenticators && (
|
{shouldDisplayAuthenticators && (
|
||||||
<>
|
<>
|
||||||
<List
|
<List
|
||||||
style={{ marginBottom: "30px" }}
|
style={{ marginBottom: "18px" }}
|
||||||
bordered
|
bordered
|
||||||
dataSource={authenticators.authenticators as Authenticator[]}
|
dataSource={authenticators.authenticators as Authenticator[]}
|
||||||
renderItem={(authenticator: Authenticator, i: number) => (
|
renderItem={(authenticator: Authenticator, i: number) => (
|
||||||
@ -136,12 +131,13 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item style={{ marginBottom: 0 }}>
|
<Form.Item style={{ margin: 0 }}>
|
||||||
<Button
|
<Button
|
||||||
id={authButtonId}
|
id={authButtonId}
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
block
|
block
|
||||||
|
style={{ marginTop: "4px" }}
|
||||||
autoFocus
|
autoFocus
|
||||||
onClick={() => setIsLoading(true)}
|
onClick={() => setIsLoading(true)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
|||||||
@ -64,9 +64,9 @@ export default function WebauthnError(props: PageProps<Extract<KcContext, { page
|
|||||||
displayMessage
|
displayMessage
|
||||||
headerNode={msg("webauthn-error-title")}
|
headerNode={msg("webauthn-error-title")}
|
||||||
>
|
>
|
||||||
<Form onFinish={handleSubmit} style={{ padding: '0 15px'}}>
|
<Form onFinish={handleSubmit} style={{ padding: "0 15px" }}>
|
||||||
<Form.Item>
|
<Form.Item style={{ margin: 0 }}>
|
||||||
<Flex gap={"middle"}>
|
<Flex gap={"middle"} style={{ marginTop: "4px" }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user