Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
3c338e983f | |||
2c11ba6520 | |||
9a21656706 | |||
e96ee5ba53 | |||
b421633a8a | |||
e2e0d62560 | |||
c71fb06940 | |||
e2171af99c | |||
8cebf049d4 | |||
ef139ed1cc | |||
d717de006a | |||
a44f091878 | |||
1b37ba5339 | |||
bbaa90e997 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -45,11 +45,9 @@ jobs:
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
|
||||
run: |
|
||||
yarn build
|
||||
yarn test
|
||||
- if: steps.step1.outputs.npm_or_yarn == 'npm'
|
||||
run: |
|
||||
npm run build
|
||||
npm test
|
||||
check_if_version_upgraded:
|
||||
name: Check if version upgrade
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "6.2.0",
|
||||
"version": "6.3.6",
|
||||
"description": "Keycloak theme generator for Reacts app",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -13,7 +13,8 @@
|
||||
"build:test": "rimraf dist_test/ && tsc -p src/test && yarn copy-files dist_test/",
|
||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||
"copy-files": "copyfiles -u 1 src/**/*.ftl",
|
||||
"test": "yarn build:test && node dist_test/test/bin && node dist_test/test/lib",
|
||||
"pretest": "yarn build:test",
|
||||
"test": "node dist_test/test/bin && node dist_test/test/lib",
|
||||
"generate-messages": "node dist/bin/generate-i18n-messages.js",
|
||||
"link_in_test_app": "node dist/bin/link_in_test_app.js",
|
||||
"_format": "prettier '**/*.{ts,tsx,json,md}'",
|
||||
@ -76,12 +77,12 @@
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"cli-select": "^1.1.2",
|
||||
"evt": "^2.4.0",
|
||||
"evt": "^2.4.2",
|
||||
"memoizee": "^0.4.15",
|
||||
"minimal-polyfills": "^2.2.2",
|
||||
"minimist": "^1.2.6",
|
||||
"path-browserify": "^1.0.1",
|
||||
"powerhooks": "^0.20.15",
|
||||
"powerhooks": "^0.20.17",
|
||||
"react-markdown": "^5.0.3",
|
||||
"scripting-tools": "^0.19.13",
|
||||
"tsafe": "^1.0.1",
|
||||
|
@ -272,6 +272,11 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
|
||||
<#list object as array_item>
|
||||
|
||||
<#if !array_item??>
|
||||
<#local out_seq += ["null,"]>
|
||||
<#continue>
|
||||
</#if>
|
||||
|
||||
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
|
||||
|
||||
<#local i = i + 1>
|
||||
|
@ -28,7 +28,8 @@ export const pageIds = [
|
||||
"login-page-expired.ftl",
|
||||
"login-config-totp.ftl",
|
||||
"logout-confirm.ftl",
|
||||
"update-user-profile.ftl"
|
||||
"update-user-profile.ftl",
|
||||
"idp-review-user-profile.ftl"
|
||||
] as const;
|
||||
|
||||
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
|
||||
|
@ -56,7 +56,7 @@ export function main() {
|
||||
});
|
||||
|
||||
//We want, however, to test in a container running the latest Keycloak version
|
||||
const containerKeycloakVersion = "18.0.2";
|
||||
const containerKeycloakVersion = "19.0.1";
|
||||
|
||||
generateStartKeycloakTestingContainer({
|
||||
keycloakThemeBuildingDirPath,
|
||||
|
46
src/lib/components/IdpReviewUserProfile.tsx
Normal file
46
src/lib/components/IdpReviewUserProfile.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { useState, memo } from "react";
|
||||
import Template from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||
|
||||
const IdpReviewUserProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.IdpReviewUserProfile; i18n: I18n } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const { url } = kcContext;
|
||||
|
||||
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, i18n, ...props }}
|
||||
doFetchDefaultThemeResources={true}
|
||||
headerNode={msg("loginIdpReviewProfileTitle")}
|
||||
formNode={
|
||||
<form id="kc-idp-review-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
||||
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||
<div className={cx(props.kcFormOptionsWrapperClass)} />
|
||||
</div>
|
||||
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||
<input
|
||||
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
|
||||
type="submit"
|
||||
value={msgStr("doSubmit")}
|
||||
disabled={!isFomSubmittable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default IdpReviewUserProfile;
|
@ -21,6 +21,7 @@ const LoginIdpLinkEmail = lazy(() => import("./LoginIdpLinkEmail"));
|
||||
const LoginConfigTotp = lazy(() => import("./LoginConfigTotp"));
|
||||
const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
|
||||
const UpdateUserProfile = lazy(() => import("./UpdateUserProfile"));
|
||||
const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile"));
|
||||
|
||||
const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => {
|
||||
const i18n = (function useClosure() {
|
||||
@ -77,6 +78,8 @@ const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...kcProps }: { kcConte
|
||||
return <LogoutConfirm {...{ kcContext, ...props }} />;
|
||||
case "update-user-profile.ftl":
|
||||
return <UpdateUserProfile {...{ kcContext, ...props }} />;
|
||||
case "idp-review-user-profile.ftl":
|
||||
return <IdpReviewUserProfile {...{ kcContext, ...props }} />;
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
|
@ -32,13 +32,7 @@ const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: K
|
||||
headerNode={msg("registerTitle")}
|
||||
formNode={
|
||||
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
|
||||
<UserProfileFormFields
|
||||
kcContext={kcContext}
|
||||
doInsertPasswordFields={true}
|
||||
onIsFormSubmittableValueChange={setIsFomSubmittable}
|
||||
i18n={i18n}
|
||||
{...props}
|
||||
/>
|
||||
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
||||
{recaptchaRequired && (
|
||||
<div className="form-group">
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
|
@ -11,6 +11,7 @@ import type { I18n } from "../i18n";
|
||||
import memoize from "memoizee";
|
||||
import { useConst } from "powerhooks/useConst";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
|
||||
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
|
||||
|
||||
@ -74,7 +75,7 @@ const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Te
|
||||
headerNode={msg("termsTitle")}
|
||||
formNode={
|
||||
<>
|
||||
<div id="kc-terms-text">{evtTermMarkdown.state}</div>
|
||||
<div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div>
|
||||
<form className="form-actions" action={url.loginAction} method="POST">
|
||||
<input
|
||||
className={cx(
|
||||
|
@ -6,7 +6,7 @@ import { useCssAndCx } from "../tools/useCssAndCx";
|
||||
import type { I18n } from "../i18n";
|
||||
import { UserProfileFormFields } from "./shared/UserProfileCommons";
|
||||
|
||||
const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n } & KcProps) => {
|
||||
const UpdateUserProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
@ -22,13 +22,7 @@ const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcC
|
||||
headerNode={msg("loginProfileTitle")}
|
||||
formNode={
|
||||
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||
<UserProfileFormFields
|
||||
kcContext={kcContext}
|
||||
doInsertPasswordFields={true}
|
||||
onIsFormSubmittableValueChange={setIsFomSubmittable}
|
||||
i18n={i18n}
|
||||
{...props}
|
||||
/>
|
||||
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
|
||||
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||
@ -74,4 +68,4 @@ const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcC
|
||||
);
|
||||
});
|
||||
|
||||
export default LoginUpdateProfile;
|
||||
export default UpdateUserProfile;
|
||||
|
@ -9,9 +9,7 @@ import type { I18n } from "../../i18n";
|
||||
import type { Param0 } from "tsafe/Param0";
|
||||
|
||||
export type UserProfileFormFieldsProps = {
|
||||
//kcContext: KcContextBase.RegisterUserProfile;
|
||||
kcContext: Param0<typeof useFormValidationSlice>["kcContext"];
|
||||
doInsertPasswordFields: boolean;
|
||||
i18n: I18n;
|
||||
} & KcProps &
|
||||
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
|
||||
@ -19,7 +17,7 @@ export type UserProfileFormFieldsProps = {
|
||||
};
|
||||
|
||||
export const UserProfileFormFields = memo(
|
||||
({ kcContext, doInsertPasswordFields, onIsFormSubmittableValueChange, i18n, BeforeField, AfterField, ...props }: UserProfileFormFieldsProps) => {
|
||||
({ kcContext, onIsFormSubmittableValueChange, i18n, BeforeField, AfterField, ...props }: UserProfileFormFieldsProps) => {
|
||||
const { cx, css } = useCssAndCx();
|
||||
|
||||
const { advancedMsg } = i18n;
|
||||
@ -64,7 +62,7 @@ export const UserProfileFormFields = memo(
|
||||
|
||||
return (
|
||||
<>
|
||||
{(doInsertPasswordFields ? attributesWithPassword : kcContext.profile.attributes).map((attribute, i) => {
|
||||
{attributesWithPassword.map((attribute, i) => {
|
||||
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
|
||||
|
||||
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
|
||||
|
@ -26,7 +26,8 @@ export type KcContextBase =
|
||||
| KcContextBase.LoginPageExpired
|
||||
| KcContextBase.LoginConfigTotp
|
||||
| KcContextBase.LogoutConfirm
|
||||
| KcContextBase.UpdateUserProfile;
|
||||
| KcContextBase.UpdateUserProfile
|
||||
| KcContextBase.IdpReviewUserProfile;
|
||||
|
||||
export declare namespace KcContextBase {
|
||||
export type Common = {
|
||||
@ -275,7 +276,15 @@ export declare namespace KcContextBase {
|
||||
export type UpdateUserProfile = Common & {
|
||||
pageId: "update-user-profile.ftl";
|
||||
profile: {
|
||||
context: "REGISTRATION_PROFILE";
|
||||
attributes: Attribute[];
|
||||
attributesByName: Record<string, Attribute>;
|
||||
};
|
||||
};
|
||||
|
||||
export type IdpReviewUserProfile = Common & {
|
||||
pageId: "idp-review-user-profile.ftl";
|
||||
profile: {
|
||||
context: "IDP_REVIEW";
|
||||
attributes: Attribute[];
|
||||
attributesByName: Record<string, Attribute>;
|
||||
};
|
||||
|
@ -47,8 +47,16 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
||||
"source": partialKcContextCustomMock
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock.pageId === "register-user-profile.ftl") {
|
||||
assert(kcContextDefaultMock?.pageId === "register-user-profile.ftl");
|
||||
if (
|
||||
partialKcContextCustomMock.pageId === "register-user-profile.ftl" ||
|
||||
partialKcContextCustomMock.pageId === "update-user-profile.ftl" ||
|
||||
partialKcContextCustomMock.pageId === "idp-review-user-profile.ftl"
|
||||
) {
|
||||
assert(
|
||||
kcContextDefaultMock?.pageId === "register-user-profile.ftl" ||
|
||||
kcContextDefaultMock?.pageId === "update-user-profile.ftl" ||
|
||||
kcContextDefaultMock?.pageId === "idp-review-user-profile.ftl"
|
||||
);
|
||||
|
||||
const { attributes } = kcContextDefaultMock.profile;
|
||||
|
||||
@ -60,8 +68,6 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
||||
].filter(exclude(undefined));
|
||||
|
||||
attributes.forEach(attribute => {
|
||||
console.log("====>", attribute);
|
||||
|
||||
const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name);
|
||||
|
||||
const augmentedAttribute: Attribute = {} as any;
|
||||
|
@ -426,7 +426,15 @@ export const kcContextMocks: KcContextBase[] = [
|
||||
...kcContextCommonMock,
|
||||
"pageId": "update-user-profile.ftl",
|
||||
"profile": {
|
||||
"context": "REGISTRATION_PROFILE" as const,
|
||||
attributes,
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContextBase.IdpReviewUserProfile>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "idp-review-user-profile.ftl",
|
||||
"profile": {
|
||||
context: "IDP_REVIEW",
|
||||
attributes,
|
||||
attributesByName
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import type baseMessages from "./generated_messages/18.0.1/login/en";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { Markdown } from "../tools/Markdown";
|
||||
|
||||
export const fallbackLanguageTag = "en";
|
||||
|
||||
@ -234,9 +234,9 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
||||
})();
|
||||
|
||||
return doRenderMarkdown ? (
|
||||
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
||||
<Markdown allowDangerousHtml renderers={{ "paragraph": "span" }}>
|
||||
{messageWithArgsInjectedIfAny}
|
||||
</ReactMarkdown>
|
||||
</Markdown>
|
||||
) : (
|
||||
messageWithArgsInjectedIfAny
|
||||
);
|
||||
|
3
src/lib/tools/Markdown.ts
Normal file
3
src/lib/tools/Markdown.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Markdown from "react-markdown";
|
||||
|
||||
export { Markdown };
|
@ -304,6 +304,10 @@ export function useGetErrors(params: {
|
||||
return { getErrors };
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: The attributesWithPassword returned is actually augmented with
|
||||
* artificial password related attributes only if kcContext.passwordRequired === true
|
||||
*/
|
||||
export function useFormValidationSlice(params: {
|
||||
kcContext: {
|
||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
||||
|
18
yarn.lock
18
yarn.lock
@ -748,10 +748,10 @@ event-emitter@^0.3.5:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
|
||||
evt@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.0.tgz#5052d3a685bba8f7e7be699b358c1781bd4037bf"
|
||||
integrity sha512-1V8FnExwqzX2fufT793mHfCnQay078kRdAEDHzJz863pgJK9aeDiWXZFs5aKmzJfIgi+svevtApVnleZXz4Jeg==
|
||||
evt@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.2.tgz#8f29f53a71609f1f5ad09ba313ad8cc0b9c63077"
|
||||
integrity sha512-wPGX4eGgxh5NRff+1VkRfdTRaJA5vbr9rC9CRUIgprVDGDFtI0/vF9I0Yp3HAEGuFbUO978QAsaH6lIgpRCFAg==
|
||||
dependencies:
|
||||
minimal-polyfills "^2.2.2"
|
||||
run-exclusive "^2.2.16"
|
||||
@ -1385,12 +1385,12 @@ please-upgrade-node@^3.2.0:
|
||||
dependencies:
|
||||
semver-compare "^1.0.0"
|
||||
|
||||
powerhooks@^0.20.15:
|
||||
version "0.20.15"
|
||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.15.tgz#e5093a11f3ed1badbc09944acf5a3fb15ac3f1d7"
|
||||
integrity sha512-28xNjPTtjPPP8vyi9SWXfn0U9hjxm/UzuarD2uofIXa/CPWmkCeTtikyLoR6yuiE4pOfvdDXVfU5im40GuzJaQ==
|
||||
powerhooks@^0.20.17:
|
||||
version "0.20.17"
|
||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.17.tgz#bf9fd0c4a7f21a61aabbfc249a6d631cd1c06006"
|
||||
integrity sha512-c65WYqmSmjFQBLIgHN5nHDryVSU3ARII2C8nNX2cOYFsvzp+MvQJSAP1i1159U9RvuP1iayuUiQRk4nVwZYSlg==
|
||||
dependencies:
|
||||
evt "^2.4.0"
|
||||
evt "^2.4.2"
|
||||
memoizee "^0.4.15"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
tsafe "^1.0.1"
|
||||
|
Reference in New Issue
Block a user