aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/keycloak-theme/pages/LoginPassword.stories.tsx16
-rw-r--r--src/keycloak-theme/pages/LoginPassword.tsx86
-rw-r--r--src/keycloak-theme/pages/LoginResetPassword.stories.tsx17
-rw-r--r--src/keycloak-theme/pages/LoginResetPassword.tsx69
-rw-r--r--src/keycloak-theme/pages/LoginUpdatePassword.stories.tsx16
-rw-r--r--src/keycloak-theme/pages/LoginUpdatePassword.tsx117
6 files changed, 321 insertions, 0 deletions
diff --git a/src/keycloak-theme/pages/LoginPassword.stories.tsx b/src/keycloak-theme/pages/LoginPassword.stories.tsx
new file mode 100644
index 0000000..6a53efa
--- /dev/null
+++ b/src/keycloak-theme/pages/LoginPassword.stories.tsx
@@ -0,0 +1,16 @@
+import {ComponentMeta} from '@storybook/react';
+import KcApp from '../KcApp';
+import {template} from '../../../.storybook/util'
+
+export default {
+ kind: 'Page',
+ title: 'Theme/Pages/Login/Password Only',
+ component: KcApp,
+ parameters: {
+ layout: 'fullscreen',
+ },
+} as ComponentMeta<typeof KcApp>;
+
+const bind = template('login-password.ftl');
+
+export const Default = bind({})
diff --git a/src/keycloak-theme/pages/LoginPassword.tsx b/src/keycloak-theme/pages/LoginPassword.tsx
new file mode 100644
index 0000000..ff3798e
--- /dev/null
+++ b/src/keycloak-theme/pages/LoginPassword.tsx
@@ -0,0 +1,86 @@
+import React, { useState } from "react";
+import { clsx } from "keycloakify/lib/tools/clsx";
+import { useConstCallback } from "keycloakify/lib/tools/useConstCallback";
+import type { FormEventHandler } from "react";
+import type { PageProps } from "keycloakify";
+import type { KcContext } from "../kcContext";
+import type { I18n } from "../i18n";
+
+export default function LoginPassword(props: PageProps<Extract<KcContext, { "pageId": "login-password.ftl" }>, I18n>) {
+ const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
+
+ const { realm, url, login } = kcContext;
+
+ const { msg, msgStr } = i18n;
+
+ const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
+
+ const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
+ e.preventDefault();
+
+ setIsLoginButtonDisabled(true);
+
+ const formElement = e.target as HTMLFormElement;
+
+ formElement.submit();
+ });
+
+ return (
+ <Template
+ {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
+ headerNode={msg("doLogIn")}
+ formNode={
+ <div id="kc-form">
+ <div id="kc-form-wrapper">
+ <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
+ <div className={clsx(kcProps.kcFormGroupClass)}>
+ <hr />
+ <label htmlFor="password" className={clsx(kcProps.kcLabelClass)}>
+ {msg("password")}
+ </label>
+ <input
+ tabIndex={2}
+ id="password"
+ className={clsx(kcProps.kcInputClass)}
+ name="password"
+ type="password"
+ autoFocus={true}
+ autoComplete="on"
+ defaultValue={login.password ?? ""}
+ />
+ </div>
+ <div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
+ <div id="kc-form-options" />
+ <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
+ {realm.resetPasswordAllowed && (
+ <span>
+ <a tabIndex={5} href={url.loginResetCredentialsUrl}>
+ {msg("doForgotPassword")}
+ </a>
+ </span>
+ )}
+ </div>
+ </div>
+ <div id="kc-form-buttons" className={clsx(kcProps.kcFormGroupClass)}>
+ <input
+ tabIndex={4}
+ className={clsx(
+ kcProps.kcButtonClass,
+ kcProps.kcButtonPrimaryClass,
+ kcProps.kcButtonBlockClass,
+ kcProps.kcButtonLargeClass
+ )}
+ name="login"
+ id="kc-login"
+ type="submit"
+ value={msgStr("doLogIn")}
+ disabled={isLoginButtonDisabled}
+ />
+ </div>
+ </form>
+ </div>
+ </div>
+ }
+ />
+ );
+} \ No newline at end of file
diff --git a/src/keycloak-theme/pages/LoginResetPassword.stories.tsx b/src/keycloak-theme/pages/LoginResetPassword.stories.tsx
new file mode 100644
index 0000000..5d3aa02
--- /dev/null
+++ b/src/keycloak-theme/pages/LoginResetPassword.stories.tsx
@@ -0,0 +1,17 @@
+import {ComponentMeta} from '@storybook/react';
+import KcApp from '../KcApp';
+import {template} from '../../../.storybook/util'
+
+export default {
+ kind: 'Page',
+ title: 'Theme/Pages/Login/Password Reset',
+ component: KcApp,
+ parameters: {
+ layout: 'fullscreen',
+ },
+} as ComponentMeta<typeof KcApp>;
+
+const bind = template('login-reset-password.ftl');
+
+export const Default = bind({})
+export const WithEmailAsUsername = bind({realm: {loginWithEmailAllowed: true, registrationEmailAsUsername: true}})
diff --git a/src/keycloak-theme/pages/LoginResetPassword.tsx b/src/keycloak-theme/pages/LoginResetPassword.tsx
new file mode 100644
index 0000000..ddeea69
--- /dev/null
+++ b/src/keycloak-theme/pages/LoginResetPassword.tsx
@@ -0,0 +1,69 @@
+import React from "react";
+import { clsx } from "keycloakify/lib/tools/clsx";
+import type { PageProps } from "keycloakify/lib/KcProps";
+import type { KcContext } from "../kcContext";
+import type { I18n } from "../i18n";
+
+export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {
+ const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
+
+ const { url, realm, auth } = kcContext;
+
+ const { msg, msgStr } = i18n;
+
+ return (
+ <Template
+ {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
+ displayMessage={false}
+ headerNode={msg("emailForgotTitle")}
+ formNode={
+ <form id="kc-reset-password-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
+ <div className={clsx(kcProps.kcFormGroupClass)}>
+ <div className={clsx(kcProps.kcLabelWrapperClass)}>
+ <label htmlFor="username" className={clsx(kcProps.kcLabelClass)}>
+ {!realm.loginWithEmailAllowed
+ ? msg("username")
+ : !realm.registrationEmailAsUsername
+ ? msg("usernameOrEmail")
+ : msg("email")}
+ </label>
+ </div>
+ <div className={clsx(kcProps.kcInputWrapperClass)}>
+ <input
+ type="text"
+ id="username"
+ name="username"
+ className={clsx(kcProps.kcInputClass)}
+ autoFocus
+ defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
+ />
+ </div>
+ </div>
+ <div className={clsx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
+ <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
+ <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
+ <span>
+ <a href={url.loginUrl}>{msg("backToLogin")}</a>
+ </span>
+ </div>
+ </div>
+
+ <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
+ <input
+ className={clsx(
+ kcProps.kcButtonClass,
+ kcProps.kcButtonPrimaryClass,
+ kcProps.kcButtonBlockClass,
+ kcProps.kcButtonLargeClass
+ )}
+ type="submit"
+ value={msgStr("doSubmit")}
+ />
+ </div>
+ </div>
+ </form>
+ }
+ infoNode={msg("emailInstruction")}
+ />
+ );
+} \ No newline at end of file
diff --git a/src/keycloak-theme/pages/LoginUpdatePassword.stories.tsx b/src/keycloak-theme/pages/LoginUpdatePassword.stories.tsx
new file mode 100644
index 0000000..9a68d43
--- /dev/null
+++ b/src/keycloak-theme/pages/LoginUpdatePassword.stories.tsx
@@ -0,0 +1,16 @@
+import {ComponentMeta} from '@storybook/react';
+import KcApp from '../KcApp';
+import {template} from '../../../.storybook/util'
+
+export default {
+ kind: 'Page',
+ title: 'Theme/Pages/Login/Password Update',
+ component: KcApp,
+ parameters: {
+ layout: 'fullscreen',
+ },
+} as ComponentMeta<typeof KcApp>;
+
+const bind = template('login-update-password.ftl');
+
+export const Default = bind({})
diff --git a/src/keycloak-theme/pages/LoginUpdatePassword.tsx b/src/keycloak-theme/pages/LoginUpdatePassword.tsx
new file mode 100644
index 0000000..d7d375c
--- /dev/null
+++ b/src/keycloak-theme/pages/LoginUpdatePassword.tsx
@@ -0,0 +1,117 @@
+import React from "react";
+import { clsx } from "keycloakify/lib/tools/clsx";
+import type { PageProps } from "keycloakify/lib/KcProps";
+import type { KcContext } from "../kcContext";
+import type { I18n } from "../i18n";
+
+export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>, I18n>) {
+ const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props;
+
+ const { msg, msgStr } = i18n;
+
+ const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
+
+ return (
+ <Template
+ {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
+ headerNode={msg("updatePasswordTitle")}
+ formNode={
+ <form id="kc-passwd-update-form" className={clsx(kcProps.kcFormClass)} action={url.loginAction} method="post">
+ <input
+ type="text"
+ id="username"
+ name="username"
+ value={username}
+ readOnly={true}
+ autoComplete="username"
+ style={{ display: "none" }}
+ />
+ <input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
+
+ <div className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}>
+ <div className={clsx(kcProps.kcLabelWrapperClass)}>
+ <label htmlFor="password-new" className={clsx(kcProps.kcLabelClass)}>
+ {msg("passwordNew")}
+ </label>
+ </div>
+ <div className={clsx(kcProps.kcInputWrapperClass)}>
+ <input
+ type="password"
+ id="password-new"
+ name="password-new"
+ autoFocus
+ autoComplete="new-password"
+ className={clsx(kcProps.kcInputClass)}
+ />
+ </div>
+ </div>
+
+ <div
+ className={clsx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass))}
+ >
+ <div className={clsx(kcProps.kcLabelWrapperClass)}>
+ <label htmlFor="password-confirm" className={clsx(kcProps.kcLabelClass)}>
+ {msg("passwordConfirm")}
+ </label>
+ </div>
+ <div className={clsx(kcProps.kcInputWrapperClass)}>
+ <input
+ type="password"
+ id="password-confirm"
+ name="password-confirm"
+ autoComplete="new-password"
+ className={clsx(kcProps.kcInputClass)}
+ />
+ </div>
+ </div>
+
+ <div className={clsx(kcProps.kcFormGroupClass)}>
+ <div id="kc-form-options" className={clsx(kcProps.kcFormOptionsClass)}>
+ <div className={clsx(kcProps.kcFormOptionsWrapperClass)}>
+ {isAppInitiatedAction && (
+ <div className="checkbox">
+ <label>
+ <input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
+ {msgStr("logoutOtherSessions")}
+ </label>
+ </div>
+ )}
+ </div>
+ </div>
+
+ <div id="kc-form-buttons" className={clsx(kcProps.kcFormButtonsClass)}>
+ {isAppInitiatedAction ? (
+ <>
+ <input
+ className={clsx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
+ type="submit"
+ defaultValue={msgStr("doSubmit")}
+ />
+ <button
+ className={clsx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
+ type="submit"
+ name="cancel-aia"
+ value="true"
+ >
+ {msg("doCancel")}
+ </button>
+ </>
+ ) : (
+ <input
+ className={clsx(
+ kcProps.kcButtonClass,
+ kcProps.kcButtonPrimaryClass,
+ kcProps.kcButtonBlockClass,
+ kcProps.kcButtonLargeClass
+ )}
+ type="submit"
+ defaultValue={msgStr("doSubmit")}
+ />
+ )}
+ </div>
+ </div>
+ </form>
+ }
+ />
+ );
+} \ No newline at end of file