Implement register
This commit is contained in:
parent
09c7b6ac03
commit
2ee12abc43
@ -54,7 +54,7 @@
|
||||
"dependencies": {
|
||||
"scripting-tools": "^0.19.13",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"evt": "^1.9.12",
|
||||
"evt": "2.0.0-beta.15",
|
||||
"minimal-polyfills": "^2.1.6",
|
||||
"powerhooks": "^0.0.14",
|
||||
"tss-react": "^0.0.9"
|
||||
|
@ -6,7 +6,9 @@
|
||||
"resourcesCommonPath": "${url.resourcesCommonPath}",
|
||||
"loginRestartFlowUrl": "${url.loginRestartFlowUrl}",
|
||||
"loginResetCredentialsUrl": "${url.loginResetCredentialsUrl}",
|
||||
"registrationUrl": "${url.registrationUrl}"
|
||||
"registrationUrl": "${url.registrationUrl}",
|
||||
"registrationAction": "${url.registrationUrl}",
|
||||
"loginUrl": "${url.loginUrl}"
|
||||
},
|
||||
"realm": {
|
||||
"displayName": "${realm.displayName!''}" || undefined,
|
||||
@ -168,6 +170,45 @@
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
})
|
||||
}),
|
||||
"messagesPerField": {
|
||||
"printIfExists": function (key, x) {
|
||||
switch(key){
|
||||
case "userLabel": "${messagesPerField.printIfExists('userLabel','1'}" ? x : undefined;
|
||||
case "username": "${messagesPerField.printIfExists('username','1'}" ? x : undefined;
|
||||
case "email": "${messagesPerField.printIfExists('email','1'}" ? x : undefined;
|
||||
case "firstName": "${messagesPerField.printIfExists('firstName','1'}" ? x : undefined;
|
||||
case "lastName": "${messagesPerField.printIfExists('lastName','1'}" ? x : undefined;
|
||||
case "password": "${messagesPerField.printIfExists('password','1'}" ? x : undefined;
|
||||
case "password-confirm": "${messagesPerField.printIfExists('password-confirm','1'}" ? x : undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"formData": {
|
||||
"firstName": "${register.formData.firstName!''}" || undefined,
|
||||
"displayName": "${register.formData.displayName!''}" || undefined,
|
||||
"lastName": "${register.formData.lastName!''}" || undefined,
|
||||
"email": "${register.formData.email!''}" || undefined,
|
||||
"username": "${register.formData.username!''}" || undefined
|
||||
}
|
||||
},
|
||||
"passwordRequired": (function (){
|
||||
|
||||
<#if passwordRequired??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
}),
|
||||
"recaptchaRequired": (function (){
|
||||
|
||||
<#if passwordRequired??>
|
||||
return true;
|
||||
</#if>
|
||||
return false;
|
||||
|
||||
}),
|
||||
"recaptchaSiteKey": "${recaptchaSiteKey}"
|
||||
}
|
||||
</script>
|
@ -4,6 +4,7 @@ import { kcContext } from "../kcContext";
|
||||
import { assert } from "evt/tools/typeSafety/assert";
|
||||
import type { KcPagesProperties } from "./KcProperties";
|
||||
import { Login } from "./Login";
|
||||
import { Register } from "./Register";
|
||||
|
||||
export type KcAppProps = {
|
||||
kcProperties?: KcPagesProperties;
|
||||
@ -16,10 +17,8 @@ export const KcApp = memo((props: KcAppProps) => {
|
||||
assert(kcContext !== undefined, "App is not currently served by a Keycloak server");
|
||||
|
||||
switch (kcContext.pageBasename) {
|
||||
case "login.ftl": return <Login kcProperties={kcProperties} />
|
||||
case "register.ftl":
|
||||
alert(`TODO: Implement ${kcContext.pageBasename}`);
|
||||
return null;
|
||||
case "login.ftl": return <Login kcProperties={kcProperties} />;
|
||||
case "register.ftl": return <Register kcProperties={kcProperties} />;
|
||||
}
|
||||
|
||||
});
|
@ -46,7 +46,6 @@ export const Login = memo((props: LoginProps) => {
|
||||
displayWide={realm.password && social.providers !== undefined}
|
||||
kcProperties={kcProperties}
|
||||
headerNode={t("doLogIn")}
|
||||
showUsernameNode={null}
|
||||
formNode={
|
||||
<div
|
||||
id="kc-form"
|
||||
|
148
src/lib/components/Register.tsx
Normal file
148
src/lib/components/Register.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
|
||||
|
||||
import { useState, memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcPagesProperties } from "./KcProperties";
|
||||
import { defaultKcPagesProperties } from "./KcProperties";
|
||||
import { assert } from "evt/tools/typeSafety/assert";
|
||||
import { kcContext } from "../kcContext";
|
||||
import { useKcTranslation } from "../i18n/useKcTranslation";
|
||||
import { cx } from "tss-react";
|
||||
|
||||
export type RegisterPageProps = {
|
||||
kcProperties?: KcPagesProperties;
|
||||
};
|
||||
|
||||
export const Register = memo((props: RegisterPageProps) => {
|
||||
|
||||
const { kcProperties = {} } = props;
|
||||
|
||||
const { t, tStr } = useKcTranslation();
|
||||
|
||||
Object.assign(kcProperties, defaultKcPagesProperties);
|
||||
|
||||
const [{
|
||||
url,
|
||||
messagesPerField,
|
||||
register,
|
||||
realm,
|
||||
passwordRequired,
|
||||
recaptchaRequired,
|
||||
recaptchaSiteKey
|
||||
}] = useState(() => (
|
||||
assert(
|
||||
kcContext !== undefined,
|
||||
"App is currently being served by keycloak"
|
||||
),
|
||||
kcContext
|
||||
));
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcProperties={kcProperties}
|
||||
headerNode={t("registerTitle")}
|
||||
formNode={
|
||||
<form id="kc-register-form" className={cx(kcProperties.kcFormClass)} action={url.registrationAction} method="post">
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists('firstName', kcProperties.kcFormGroupErrorClass))}>
|
||||
<div className={cx(kcProperties.kcLabelWrapperClass)}>
|
||||
<label htmlFor="firstName" className={cx(kcProperties.kcLabelClass)}>{t("firstName")}</label>
|
||||
</div>
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<input type="text" id="firstName" className={cx(kcProperties.kcInputClass)} name="firstName"
|
||||
value={register.formData.firstName ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProperties.kcFormGroupErrorClass))}>
|
||||
<div className={cx(kcProperties.kcLabelWrapperClass)}>
|
||||
<label htmlFor="lastName" className={cx(kcProperties.kcLabelClass)}>{t("lastName")}</label>
|
||||
</div>
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<input type="text" id="lastName" className={cx(kcProperties.kcInputClass)} name="lastName"
|
||||
value={register.formData.lastName ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists('email', kcProperties.kcFormGroupErrorClass))}>
|
||||
<div className={cx(kcProperties.kcLabelWrapperClass)}>
|
||||
<label htmlFor="email" className={cx(kcProperties.kcLabelClass)}>{t("email")}</label>
|
||||
</div>
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<input type="text" id="email" className={cx(kcProperties.kcInputClass)} name="email"
|
||||
value={register.formData.email ?? ""} autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
!realm.registrationEmailAsUsername &&
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists('username', kcProperties.kcFormGroupErrorClass))}>
|
||||
<div className={cx(kcProperties.kcLabelWrapperClass)}>
|
||||
<label htmlFor="username" className={cx(kcProperties.kcLabelClass)}>{t("username")}</label>
|
||||
</div>
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<input type="text" id="username" className={cx(kcProperties.kcInputClass)} name="username"
|
||||
value={register.formData.username ?? ""} autoComplete="username" />
|
||||
</div>
|
||||
</div >
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
passwordRequired &&
|
||||
<>
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists("password", kcProperties.kcFormGroupErrorClass))}>
|
||||
<div className={cx(kcProperties.kcLabelWrapperClass)}>
|
||||
<label htmlFor="password" className={cx(kcProperties.kcLabelClass)}>{t("password")}</label>
|
||||
</div>
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<input type="password" id="password" className={cx(kcProperties.kcInputClass)} name="password" autoComplete="new-password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProperties.kcFormGroupErrorClass))}>
|
||||
<div className={cx(kcProperties.kcLabelWrapperClass)}>
|
||||
<label htmlFor="password-confirm" className={cx(kcProperties.kcLabelClass)}>{t("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<input type="password" id="password-confirm" className={cx(kcProperties.kcInputClass)} name="password-confirm" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
}
|
||||
{
|
||||
recaptchaRequired &&
|
||||
<div className="form-group">
|
||||
<div className={cx(kcProperties.kcInputWrapperClass)}>
|
||||
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
<div className={cx(kcProperties.kcFormGroupClass)}>
|
||||
<div id="kc-form-options" className={cx(kcProperties.kcFormOptionsClass)}>
|
||||
<div className={cx(kcProperties.kcFormOptionsWrapperClass)}>
|
||||
<span><a href={url.loginUrl}>{t("backToLogin")}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" className={cx(kcProperties.kcFormButtonsClass)}>
|
||||
<input className={cx(kcProperties.kcButtonClass, kcProperties.kcButtonPrimaryClass, kcProperties.kcButtonBlockClass, kcProperties.kcButtonLargeClass)} type="submit"
|
||||
value={tStr("doRegister")} />
|
||||
</div>
|
||||
</div>
|
||||
</form >
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -23,9 +23,9 @@ export type TemplateProps = {
|
||||
displayWide?: boolean;
|
||||
showAnotherWayIfPresent?: boolean;
|
||||
headerNode: ReactNode;
|
||||
showUsernameNode: ReactNode;
|
||||
showUsernameNode?: ReactNode;
|
||||
formNode: ReactNode;
|
||||
displayInfoNode: ReactNode;
|
||||
displayInfoNode?: ReactNode;
|
||||
};
|
||||
|
||||
|
||||
@ -39,9 +39,9 @@ export const Template = memo((props: TemplateProps) => {
|
||||
showAnotherWayIfPresent = true,
|
||||
kcProperties = {},
|
||||
headerNode,
|
||||
showUsernameNode,
|
||||
showUsernameNode = null,
|
||||
formNode,
|
||||
displayInfoNode
|
||||
displayInfoNode = null
|
||||
} = props;
|
||||
|
||||
const { t } = useKcTranslation();
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { ftlValuesGlobalName } from "../bin/build-keycloak-theme/ftlValuesGlobalName";
|
||||
import type { generateFtlFilesCodeFactory } from "../bin/build-keycloak-theme/generateFtl";
|
||||
import { id } from "evt/tools/typeSafety/id";
|
||||
import type { KcLanguageTag } from "./i18n/KcLanguageTag";
|
||||
import type { KcLanguageTag } from "./i18n/KcLanguageTag";
|
||||
|
||||
|
||||
export type KcContext = {
|
||||
@ -14,6 +14,9 @@ export type KcContext = {
|
||||
loginRestartFlowUrl: string;
|
||||
loginResetCredentialsUrl: string;
|
||||
registrationUrl: string;
|
||||
//Specific to register
|
||||
registrationAction: string;
|
||||
loginUrl: string;
|
||||
};
|
||||
realm: {
|
||||
displayName?: string;
|
||||
@ -67,6 +70,32 @@ export type KcContext = {
|
||||
rememberMe: boolean;
|
||||
};
|
||||
registrationDisabled: boolean;
|
||||
//Specific to register
|
||||
messagesPerField: {
|
||||
printIfExists<T>(
|
||||
key:
|
||||
"userLabel" |
|
||||
"username" |
|
||||
"email" |
|
||||
"firstName" |
|
||||
"lastName" |
|
||||
"password" |
|
||||
"password-confirm",
|
||||
x: T
|
||||
): T | undefined;
|
||||
};
|
||||
register: {
|
||||
formData: {
|
||||
firstName?: string;
|
||||
displayName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
username?: string;
|
||||
}
|
||||
};
|
||||
passwordRequired: boolean;
|
||||
recaptchaRequired: boolean;
|
||||
recaptchaSiteKey: string;
|
||||
};
|
||||
|
||||
export const kcContext = id<KcContext | undefined>((window as any)[ftlValuesGlobalName]);
|
||||
|
125
src/lib/kcContext.ts.disabled
Normal file
125
src/lib/kcContext.ts.disabled
Normal file
@ -0,0 +1,125 @@
|
||||
|
||||
import { ftlValuesGlobalName } from "../bin/build-keycloak-theme/ftlValuesGlobalName";
|
||||
import type { generateFtlFilesCodeFactory } from "../bin/build-keycloak-theme/generateFtl";
|
||||
import { id } from "evt/tools/typeSafety/id";
|
||||
import type { KcLanguageTag } from "./i18n/KcLanguageTag";
|
||||
import { doExtends } from "evt/tools/typeSafety/doExtends";
|
||||
|
||||
export type KcContext = KcContext.Login | KcContext.Register;
|
||||
|
||||
export declare namespace KcContext {
|
||||
|
||||
export type Template = {};
|
||||
|
||||
export type Login = Template & {
|
||||
pageBasename: "login.ftl";
|
||||
};
|
||||
|
||||
export type Register = Template & {
|
||||
pageBasename: "register.ftl";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
type T = KcContext["pageBasename"];
|
||||
type U = Parameters<ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"]>[0]["pageBasename"];
|
||||
|
||||
doExtends<T, U>();
|
||||
doExtends<U, T>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
export type KcContext = {
|
||||
pageBasename: Parameters<ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"]>[0]["pageBasename"];
|
||||
url: {
|
||||
loginAction: string;
|
||||
resourcesPath: string;
|
||||
resourcesCommonPath: string;
|
||||
loginRestartFlowUrl: string;
|
||||
loginResetCredentialsUrl: string;
|
||||
registrationUrl: string;
|
||||
registrationAction: string;
|
||||
loginUrl: string;
|
||||
};
|
||||
realm: {
|
||||
displayName?: string;
|
||||
displayNameHtml?: string;
|
||||
internationalizationEnabled: boolean;
|
||||
password: boolean;
|
||||
loginWithEmailAllowed: boolean;
|
||||
registrationEmailAsUsername: boolean;
|
||||
rememberMe: boolean;
|
||||
resetPasswordAllowed: boolean;
|
||||
};
|
||||
/** Undefined if !realm.internationalizationEnabled */
|
||||
locale?: {
|
||||
supported: {
|
||||
//url: string;
|
||||
languageTag: KcLanguageTag;
|
||||
/** Is determined by languageTag. Ex: languageTag === "en" => label === "English"
|
||||
* or getLanguageLabel(languageTag) === label
|
||||
*/
|
||||
//label: LanguageLabel;
|
||||
}[];
|
||||
//NOTE: We do not expose this because the language is managed
|
||||
//client side. We use this value however to set the default.
|
||||
//current: LanguageLabel;
|
||||
},
|
||||
auth?: {
|
||||
showUsername: boolean;
|
||||
showResetCredentials: boolean;
|
||||
showTryAnotherWayLink: boolean;
|
||||
attemptedUsername?: boolean;
|
||||
selectedCredential?: string;
|
||||
};
|
||||
scripts: string[];
|
||||
message?: {
|
||||
type: "success" | "warning" | "error" | "info";
|
||||
summary: string;
|
||||
};
|
||||
isAppInitiatedAction: boolean;
|
||||
social: {
|
||||
displayInfo: boolean;
|
||||
providers?: {
|
||||
loginUrl: string;
|
||||
alias: string;
|
||||
providerId: string;
|
||||
displayName: string;
|
||||
}[]
|
||||
};
|
||||
usernameEditDisabled: boolean;
|
||||
login: {
|
||||
username?: string;
|
||||
rememberMe: boolean;
|
||||
};
|
||||
registrationDisabled: boolean;
|
||||
messagesPerField: {
|
||||
printIfExists<T>(
|
||||
key:
|
||||
"userLabel" |
|
||||
"username" |
|
||||
"email" |
|
||||
"firstName" |
|
||||
"lastName" |
|
||||
"password" |
|
||||
"password-confirm",
|
||||
x: T
|
||||
): T | undefined;
|
||||
};
|
||||
register: {
|
||||
formData: {
|
||||
firstName?: string;
|
||||
displayName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
username?: string;
|
||||
}
|
||||
};
|
||||
passwordRequired: boolean;
|
||||
recaptchaRequired: boolean;
|
||||
recaptchaSiteKey: string;
|
||||
};
|
||||
|
||||
export const kcContext = id<KcContext | undefined>((window as any)[ftlValuesGlobalName]);
|
10
yarn.lock
10
yarn.lock
@ -623,7 +623,15 @@ evt@2.0.0-beta.12:
|
||||
minimal-polyfills "^2.1.5"
|
||||
run-exclusive "^2.2.14"
|
||||
|
||||
evt@^1.9.12, evt@^1.9.2:
|
||||
evt@2.0.0-beta.15:
|
||||
version "2.0.0-beta.15"
|
||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.0.0-beta.15.tgz#a34ef224c827152c06f29157a9af5a41644e1a36"
|
||||
integrity sha512-AZIuA8ujBsDX/rPC9mJ2sGvrBJAvV2OGj/yzACbuT4pRLKii5PMjYPitNk4xJMKuXSDwvwCsQzuGPDCcbRc60A==
|
||||
dependencies:
|
||||
minimal-polyfills "^2.1.5"
|
||||
run-exclusive "^2.2.14"
|
||||
|
||||
evt@^1.9.2:
|
||||
version "1.9.12"
|
||||
resolved "https://registry.yarnpkg.com/evt/-/evt-1.9.12.tgz#8d06177259cbcb09ef936e18945bf7ddd087170c"
|
||||
integrity sha512-u8wC4Xif2pcDJ9cEm0wzWCIQb+Y214m1eUgsgm2hVIuXuvC6LToryA0Ecl1O8Slii2E9l6USLsyxXWntjlnIbw==
|
||||
|
Loading…
x
Reference in New Issue
Block a user