Compare commits

..

5 Commits

5 changed files with 114 additions and 38 deletions

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "10.0.0-rc.95",
"version": "10.0.0-rc.97",
"description": "Create Keycloak themes using React",
"repository": {
"type": "git",

View File

@ -81,7 +81,8 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
)
)
.toString("utf8")
.replace('import React from "react";\n', "");
.replace('import React from "react";\n', "")
.replace(/from "[./]+dist\//, 'from "keycloakify/');
{
const targetDirPath = pathDirname(targetFilePath);

View File

@ -259,8 +259,16 @@ function createI18nTranslationFunctionsFactory<MessageKey extends string, ExtraM
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
const { key, args, doRenderAsHtml } = props;
if (realmMessageBundleUserProfile !== undefined && key in realmMessageBundleUserProfile) {
const resolvedMessage = realmMessageBundleUserProfile[key];
user_profile: {
if (realmMessageBundleUserProfile === undefined) {
break user_profile;
}
const resolvedMessage = realmMessageBundleUserProfile[key] ?? realmMessageBundleUserProfile["${" + key + "}"];
if (resolvedMessage === undefined) {
break user_profile;
}
return doRenderAsHtml ? (
<span

View File

@ -24,6 +24,7 @@ export default function Register(props: RegisterProps) {
const { msg, msgStr } = i18n;
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
const [areTermsAccepted, setAreTermsAccepted] = useState(false);
return (
<Template
@ -43,7 +44,15 @@ export default function Register(props: RegisterProps) {
onIsFormSubmittableValueChange={setIsFormSubmittable}
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
{termsAcceptanceRequired && <TermsAcceptance i18n={i18n} kcClsx={kcClsx} messagesPerField={messagesPerField} />}
{termsAcceptanceRequired && (
<TermsAcceptance
i18n={i18n}
kcClsx={kcClsx}
messagesPerField={messagesPerField}
areTermsAccepted={areTermsAccepted}
onAreTermsAcceptedValueChange={setAreTermsAccepted}
/>
)}
{recaptchaRequired && (
<div className="form-group">
<div className={kcClsx("kcInputWrapperClass")}>
@ -61,7 +70,7 @@ export default function Register(props: RegisterProps) {
</div>
<div id="kc-form-buttons" className={kcClsx("kcFormButtonsClass")}>
<input
disabled={!isFormSubmittable}
disabled={!isFormSubmittable || (termsAcceptanceRequired && !areTermsAccepted)}
className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonBlockClass", "kcButtonLargeClass")}
type="submit"
value={msgStr("doRegister")}
@ -73,8 +82,14 @@ export default function Register(props: RegisterProps) {
);
}
function TermsAcceptance(props: { i18n: I18n; kcClsx: KcClsx; messagesPerField: Pick<KcContext["messagesPerField"], "existsError" | "get"> }) {
const { i18n, kcClsx, messagesPerField } = props;
function TermsAcceptance(props: {
i18n: I18n;
kcClsx: KcClsx;
messagesPerField: Pick<KcContext["messagesPerField"], "existsError" | "get">;
areTermsAccepted: boolean;
onAreTermsAcceptedValueChange: (areTermsAccepted: boolean) => void;
}) {
const { i18n, kcClsx, messagesPerField, areTermsAccepted, onAreTermsAcceptedValueChange } = props;
const { msg } = i18n;
@ -93,6 +108,8 @@ function TermsAcceptance(props: { i18n: I18n; kcClsx: KcClsx; messagesPerField:
id="termsAccepted"
name="termsAccepted"
className={kcClsx("kcCheckboxInputClass")}
checked={areTermsAccepted}
onChange={e => onAreTermsAcceptedValueChange(e.target.checked)}
aria-invalid={messagesPerField.existsError("termsAccepted")}
/>
<label htmlFor="termsAccepted" className={kcClsx("kcLabelClass")}>

View File

@ -1,6 +1,7 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";
import type { Attribute } from "../../../dist/login";
const { KcPageStory } = createKcPageStory({ pageId: "register.ftl" });
@ -48,23 +49,84 @@ export const WithEmailAlreadyExists: Story = {
)
};
export const WithEmailAsUsername: Story = {
export const WithRestrictedToMITStudents: Story = {
render: () => (
<KcPageStory
kcContext={{
realm: {
registrationEmailAsUsername: true
profile: {
attributesByName: {
email: {
validators: {
pattern: {
pattern: "^[^@]+@([^.]+\\.)*((mit\\.edu)|(berkeley\\.edu))$",
"error-message": "${profile.attributes.email.pattern.error}"
}
},
annotations: {
inputHelperTextBefore: "${profile.attributes.email.inputHelperTextBefore}"
}
}
}
},
"x-keycloakify": {
realmMessageBundleUserProfile: {
"${profile.attributes.email.inputHelperTextBefore}": "Please use your MIT or Berkeley email.",
"${profile.attributes.email.pattern.error}":
"This is not an MIT (<strong>@mit.edu</strong>) nor a Berkeley (<strong>@berkeley.edu</strong>) email."
}
}
}}
/>
)
};
export const WithoutPassword: Story = {
export const WithFavoritePet: Story = {
render: () => (
<KcPageStory
kcContext={{
passwordRequired: false
profile: {
attributesByName: {
favoritePet: {
name: "favorite-pet",
displayName: "${profile.attributes.favoritePet}",
validators: {
options: {
options: ["cat", "dog", "fish"]
}
},
annotations: {
inputOptionLabelsI18nPrefix: "profile.attributes.favoritePet.options"
},
required: false,
readOnly: false
} satisfies Attribute
}
},
"x-keycloakify": {
realmMessageBundleUserProfile: {
"${profile.attributes.favoritePet}": "Favorite Pet",
"${profile.attributes.favoritePet.options.cat}": "Fluffy Cat",
"${profile.attributes.favoritePet.options.dog}": "Loyal Dog",
"${profile.attributes.favoritePet.options.fish}": "Peaceful Fish"
}
}
}}
/>
)
};
export const WithEmailAsUsername: Story = {
render: () => (
<KcPageStory
kcContext={{
realm: {
registrationEmailAsUsername: true
},
profile: {
attributesByName: {
username: undefined
}
}
}}
/>
)
@ -97,31 +159,6 @@ export const WithRecaptchaFrench: Story = {
)
};
export const WithPresets: Story = {
render: () => (
<KcPageStory
kcContext={{
profile: {
attributesByName: {
firstName: {
value: "Max"
},
lastName: {
value: "Mustermann"
},
email: {
value: "max.mustermann@gmail.com"
},
username: {
value: "max.mustermann"
}
}
}
}}
/>
)
};
export const WithPasswordMinLength8: Story = {
render: () => (
<KcPageStory
@ -133,3 +170,16 @@ export const WithPasswordMinLength8: Story = {
/>
)
};
export const WithTermsAcceptance: Story = {
render: () => (
<KcPageStory
kcContext={{
termsAcceptanceRequired: true,
"x-keycloakify": {
realmMessageBundleTermsText: "<a href='https://example.com/terms'>Service Terms of Use</a>"
}
}}
/>
)
};