keycloak-aplcache
Changes
themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts 10(+5 -5)
themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/AccountPage.tsx 155(+107 -48)
themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/PasswordPage.tsx 131(+127 -4)
Details
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts
index adf2970..1d34576 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts
@@ -17,13 +17,13 @@
//import {KeycloakNotificationService} from '../notification/keycloak-notification.service';
import {KeycloakService} from '../keycloak-service/keycloak.service';
-import Axios, {AxiosRequestConfig, AxiosResponse, AxiosPromise} from 'axios';
+import Axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
//import {NotificationType} from 'patternfly-ng/notification';*/
-type AxiosResolve = (AxiosResponse) => void;
-type ConfigResolve = (AxiosRequestConfig) => void;
-type ErrorReject = (Error) => void;
+type AxiosResolve = (response: AxiosResponse) => void;
+type ConfigResolve = (config: AxiosRequestConfig) => void;
+type ErrorReject = (error: Error) => void;
/**
*
@@ -88,7 +88,7 @@ export class AccountServiceClient {
console.log(error);
}
- private makeConfig(endpoint: string, config?: AxiosRequestConfig): Promise<AxiosRequestConfig> {
+ private makeConfig(endpoint: string, config: AxiosRequestConfig = {}): Promise<AxiosRequestConfig> {
return new Promise( (resolve: ConfigResolve, reject: ErrorReject) => {
this.kcSvc.getToken()
.then( (token: string) => {
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/App.tsx b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/App.tsx
index a461d53..8f103fb 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/App.tsx
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/App.tsx
@@ -17,6 +17,8 @@
import * as React from 'react';
import {Route, Link} from 'react-router-dom';
+import * as moment from 'moment';
+
import {KeycloakService} from './keycloak-service/keycloak.service';
import {Logout} from './widgets/Logout';
@@ -29,6 +31,8 @@ import {ExtensionPages} from './content/extensions/ExtensionPages';
declare function toggleReact():void;
declare function isWelcomePage(): boolean;
+declare const locale: string;
+
export interface AppProps {};
export class App extends React.Component<AppProps> {
@@ -48,6 +52,9 @@ export class App extends React.Component<AppProps> {
this.kcSvc.login();
}
+ // globally set up locale for date formatting
+ moment.locale(locale);
+
return (
<span>
<nav>
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/AccountPage.tsx b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/AccountPage.tsx
index 6a75165..fb8f488 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/AccountPage.tsx
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/account-page/AccountPage.tsx
@@ -18,91 +18,150 @@ import * as React from 'react';
import {AxiosResponse} from 'axios';
import {AccountServiceClient} from '../../account-service/account.service';
+import {Features} from '../../page/features';
import {Msg} from '../../widgets/Msg';
+
+declare const features: Features;
interface AccountPageProps {
}
+interface FormFields {
+ readonly username?: string;
+ readonly firstName?: string;
+ readonly lastName?: string;
+ readonly email?: string;
+ readonly emailVerified?: boolean;
+}
+
interface AccountPageState {
- readonly changed: boolean,
- readonly username: string,
- readonly firstName?: string,
- readonly lastName?: string,
- readonly email?: string,
- readonly emailVerified?: boolean
+ readonly canSubmit: boolean;
+ readonly formFields: FormFields
}
/**
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
*/
export class AccountPage extends React.Component<AccountPageProps, AccountPageState> {
- readonly state: AccountPageState = {
- changed: false,
- username: '',
- firstName: '',
- lastName: '',
- email: ''
+ private isRegistrationEmailAsUsername: boolean = features.isRegistrationEmailAsUsername;
+ private isEditUserNameAllowed: boolean = features.isEditUserNameAllowed;
+
+ state: AccountPageState = {
+ canSubmit: false,
+ formFields: {username: '',
+ firstName: '',
+ lastName: '',
+ email: ''}
};
constructor(props: AccountPageProps) {
super(props);
AccountServiceClient.Instance.doGet("/")
- .then((response: AxiosResponse<AccountPageState>) => {
- this.setState(response.data);
+ .then((response: AxiosResponse<FormFields>) => {
+ this.setState({formFields: response.data});
console.log({response});
});
-
- this.handleChange = this.handleChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.makeTextInput = this.makeTextInput.bind(this);
}
- private handleChange(event: React.ChangeEvent<HTMLInputElement>): void {
+ private handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const target: HTMLInputElement = event.target;
const value: string = target.value;
const name: string = target.name;
this.setState({
- changed: true,
- username: this.state.username,
- [name]: value
- } as AccountPageState);
+ canSubmit: this.requiredFieldsHaveData(name, value),
+ formFields: {...this.state.formFields, [name]:value}
+ });
}
- private handleSubmit(event: React.FormEvent<HTMLFormElement>): void {
+ private handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
- const reqData = {...this.state};
- delete reqData.changed;
+ const reqData: FormFields = {...this.state.formFields};
AccountServiceClient.Instance.doPost("/", {data: reqData})
- .then((response: AxiosResponse<AccountPageState>) => {
- this.setState({changed: false});
+ .then((response: AxiosResponse<FormFields>) => {
+ this.setState({canSubmit: false});
alert('Data posted');
});
}
- private makeTextInput(name: string,
- disabled = false): React.ReactElement<any> {
- return (
- <label><Msg msgKey={name}/>:
- <input disabled={disabled} type="text" name={name} value={this.state[name]} onChange={this.handleChange} />
- </label>
- );
+ private requiredFieldsHaveData(fieldName: string, newValue: string): boolean {
+ const fields: FormFields = {...this.state.formFields};
+ fields[fieldName] = newValue;
+ for (const field of Object.keys(fields)) {
+ if (!fields[field]) return false;
+ }
+
+ return true;
}
render() {
+ const fields: FormFields = this.state.formFields;
return (
- <form onSubmit={this.handleSubmit}>
- {this.makeTextInput('username', true)}
- <br/>
- {this.makeTextInput('firstName')}
- <br/>
- {this.makeTextInput('lastName')}
- <br/>
- {this.makeTextInput('email')}
- <br/>
- <button className="btn btn-primary btn-lg btn-sign"
- disabled={!this.state.changed}
- value="Submit"><Msg msgKey="doSave"/></button>
- </form>
+<span>
+<div className="page-header">
+ <h1 id="pageTitle"><Msg msgKey="personalInfoHtmlTitle"/></h1>
+</div>
+
+<div className="col-sm-12 card-pf">
+ <div className="card-pf-body row">
+ <div className="col-sm-4 col-md-4">
+ <div className="card-pf-subtitle" id="personalSubTitle">
+ <Msg msgKey="personalSubTitle"/>
+ </div>
+ <div className="introMessage" id="personalSubMessage">
+ <p><Msg msgKey="personalSubMessage"/></p>
+ </div>
+ <div className="subtitle" id="requiredFieldMessage"><span className="required">*</span> <Msg msgKey="requiredFields"/></div>
+ </div>
+
+ <div className="col-sm-6 col-md-6">
+ <form onSubmit={this.handleSubmit} className="form-horizontal">
+
+ { !this.isRegistrationEmailAsUsername &&
+ <div className="form-group ">
+ <label htmlFor="username" className="control-label"><Msg msgKey="username" /></label>{this.isEditUserNameAllowed && <span className="required">*</span>}
+ {this.isEditUserNameAllowed && <this.UsernameInput/>}
+ {!this.isEditUserNameAllowed && <this.RestrictedUsernameInput/>}
+ </div>
+ }
+
+ <div className="form-group ">
+ <label htmlFor="email" className="control-label"><Msg msgKey="email"/></label> <span className="required">*</span>
+ <input type="email" className="form-control" id="email" name="email" required autoFocus onChange={this.handleChange} value={fields.email}/>
+ </div>
+
+ <div className="form-group ">
+ <label htmlFor="firstName" className="control-label"><Msg msgKey="firstName"/></label> <span className="required">*</span>
+ <input className="form-control" id="firstName" required name="firstName" type="text" onChange={this.handleChange} value={fields.firstName}/>
+ </div>
+
+ <div className="form-group ">
+ <label htmlFor="lastName" className="control-label"><Msg msgKey="lastName"/></label> <span className="required">*</span>
+ <input className="form-control" id="lastName" required name="lastName" type="text" onChange={this.handleChange} value={fields.lastName}/>
+ </div>
+
+ <div className="form-group">
+ <div id="kc-form-buttons" className="submit">
+ <div className="">
+ <button disabled={!this.state.canSubmit}
+ type="submit" className="btn btn-primary btn-lg"
+ name="submitAction"><Msg msgKey="doSave"/></button>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</span>
);
}
+
+ UsernameInput = () => (
+ <input type="text" className="form-control" required id="username" name="username" onChange={this.handleChange} value={this.state.formFields.username} />
+ );
+
+ RestrictedUsernameInput = () => (
+ <div className="non-edit" id="username">{this.state.formFields.username}</div>
+ );
+
};
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/PasswordPage.tsx b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/PasswordPage.tsx
index 59b5f8b..5db72c4 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/PasswordPage.tsx
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/PasswordPage.tsx
@@ -15,21 +15,144 @@
*/
import * as React from 'react';
+import * as moment from 'moment';
+import {AxiosResponse} from 'axios';
+
+import {AccountServiceClient} from '../../account-service/account.service';
+import {Msg} from '../../widgets/Msg';
export interface PasswordPageProps {
}
-
-export class PasswordPage extends React.Component<PasswordPageProps> {
+
+interface FormFields {
+ readonly currentPassword?: string;
+ readonly newPassword?: string;
+ readonly confirmation?: string;
+}
+
+interface PasswordPageState {
+ readonly canSubmit: boolean,
+ readonly registered: boolean;
+ readonly lastUpdate: number;
+ readonly formFields: FormFields;
+}
+
+export class PasswordPage extends React.Component<PasswordPageProps, PasswordPageState> {
+ state: PasswordPageState = {
+ canSubmit: false,
+ registered: false,
+ lastUpdate: -1,
+ formFields: {currentPassword: '',
+ newPassword: '',
+ confirmation: ''}
+ }
constructor(props: PasswordPageProps) {
super(props);
+
+ AccountServiceClient.Instance.doGet("/credentials/password")
+ .then((response: AxiosResponse<PasswordPageState>) => {
+ this.setState({...response.data});
+ console.log({response});
+ });
+ }
+
+ private handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
+ const target: HTMLInputElement = event.target;
+ const value: string = target.value;
+ const name: string = target.name;
+ this.setState({
+ canSubmit: this.requiredFieldsHaveData(name, value),
+ registered: this.state.registered,
+ lastUpdate: this.state.lastUpdate,
+ formFields: {...this.state.formFields, [name]: value}
+ });
+ }
+
+ private handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
+ event.preventDefault();
+ const reqData: FormFields = {...this.state.formFields};
+ AccountServiceClient.Instance.doPost("/credentials/password", {data: reqData})
+ .then((response: AxiosResponse<FormFields>) => {
+ this.setState({canSubmit: false});
+ alert('Data posted');
+ });
+ }
+
+ private requiredFieldsHaveData(fieldName: string, newValue: string): boolean {
+ const fields: FormFields = {...this.state.formFields};
+ fields[fieldName] = newValue;
+ for (const field of Object.keys(fields)) {
+ if (!fields[field]) return false;
+ }
+
+ return true;
}
render() {
+ const displayNone = {display: 'none'};
+
return (
- <div>
- <h2>Hello Password Page</h2>
+<div>
+ <div className="page-header">
+ <h1 id="pageTitle"><Msg msgKey="changePasswordHtmlTitle"/></h1>
+ </div>
+
+ <div className="col-sm-12 card-pf">
+ <div className="card-pf-body p-b" id="passwordLastUpdate">
+ <span className="i pficon pficon-info"></span>
+ <Msg msgKey="passwordLastUpdateMessage" /> <strong>{moment(this.state.lastUpdate).format('LLLL')}</strong>
+ </div>
+ </div>
+
+ <div className="col-sm-12 card-pf">
+ <div className="card-pf-body row">
+ <div className="col-sm-4 col-md-4">
+ <div className="card-pf-subtitle" id="updatePasswordSubTitle">
+ <Msg msgKey="updatePasswordTitle"/>
+ </div>
+ <div className="introMessage" id="updatePasswordSubMessage">
+ <strong><Msg msgKey="updatePasswordMessageTitle"/></strong>
+ <p><Msg msgKey="updatePasswordMessage"/></p>
+ </div>
+ <div className="subtitle"><span className="required">*</span> <Msg msgKey="requiredFields"/></div>
</div>
+ <div className="col-sm-6 col-md-6">
+ <form onSubmit={this.handleSubmit} className="form-horizontal">
+ <input readOnly value="this is not a login form" style={displayNone} type="text"/>
+ <input readOnly value="this is not a login form" style={displayNone} type="password"/>
+ <div className="form-group">
+ <label htmlFor="password" className="control-label"><Msg msgKey="currentPassword"/></label><span className="required">*</span>
+ <input onChange={this.handleChange} className="form-control" name="currentPassword" autoFocus autoComplete="off" type="password"/>
+ </div>
+
+ <div className="form-group">
+ <label htmlFor="password-new" className="control-label"><Msg msgKey="passwordNew"/></label><span className="required">*</span>
+ <input onChange={this.handleChange} className="form-control" id="newPassword" name="newPassword" autoComplete="off" type="password"/>
+ </div>
+
+ <div className="form-group">
+ <label htmlFor="password-confirm" className="control-label"><Msg msgKey="passwordConfirm"/></label><span className="required">*</span>
+ <input onChange={this.handleChange} className="form-control" id="confirmation" name="confirmation" autoComplete="off" type="password"/>
+ </div>
+
+ <div className="form-group">
+ <div id="kc-form-buttons" className="submit">
+ <div className="">
+ <button disabled={!this.state.canSubmit}
+ type="submit"
+ className="btn btn-primary btn-lg"
+ name="submitAction"><Msg msgKey="doSave"/></button>
+ </div>
+ </div>
+ </div>
+ </form>
+
+ </div>
+
+ </div>
+ </div>
+</div>
);
}
};
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts
index fbf3f14..afdded7 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/keycloak-service/keycloak.service.ts
@@ -49,7 +49,7 @@ export class KeycloakService {
* for details.
* @returns {Promise<T>}
*/
- static init(configOptions?: string|{}, initOptions?: InitOptions): Promise<any> {
+ static init(configOptions?: string|{}, initOptions: InitOptions = {}): Promise<any> {
KeycloakService.keycloakAuth = Keycloak(configOptions);
return new Promise((resolve, reject) => {
@@ -64,7 +64,7 @@ export class KeycloakService {
}
authenticated(): boolean {
- return KeycloakService.keycloakAuth.authenticated;
+ return KeycloakService.keycloakAuth.authenticated ? KeycloakService.keycloakAuth.authenticated : false;
}
login(options?: KeycloakLoginOptions) {
@@ -79,11 +79,11 @@ export class KeycloakService {
KeycloakService.keycloakAuth.accountManagement();
}
- authServerUrl(): string {
+ authServerUrl(): string | undefined {
return KeycloakService.keycloakAuth.authServerUrl;
}
- realm(): string {
+ realm(): string | undefined {
return KeycloakService.keycloakAuth.realm;
}
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/widgets/Logout.tsx b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/widgets/Logout.tsx
index b041dcb..4ccf718 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/widgets/Logout.tsx
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/widgets/Logout.tsx
@@ -20,7 +20,7 @@ import {Link} from 'react-router-dom';
import {Msg} from './Msg';
import {KeycloakService} from '../keycloak-service/keycloak.service';
-declare const baseUrl;
+declare const baseUrl: string;
export interface LogoutProps {
}
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/package.json b/themes/src/main/resources/theme/keycloak-preview/account/resources/package.json
index 9d79dec..cd94720 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/package.json
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/package.json
@@ -3,8 +3,8 @@
"version": "1.0.0",
"description": "keycloak-preview account management written in React",
"scripts": {
- "build": "tsc --jsx react -p ./",
- "build:watch": "tsc --jsx react -p ./ -w"
+ "build": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./",
+ "build:watch": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ -w"
},
"keywords": [],
"author": "Stan Silvert",
@@ -12,6 +12,7 @@
"dependencies": {
"axios": "^0.18.0",
"bootstrap": "^4.1.0",
+ "moment": "^2.22.2",
"patternfly": "^3.23.2",
"react": "^16.5.2",
"react-dom": "^16.5.2",
@@ -22,6 +23,7 @@
"devDependencies": {
"@types/react": "^16.4.14",
"@types/react-dom": "^16.0.8",
+ "@types/react-router-dom": "^4.3.1",
"typescript": "^3.1.1"
},
"repository": {}
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/package-lock.json b/themes/src/main/resources/theme/keycloak-preview/account/resources/package-lock.json
index d90b324..f7cc76d 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/package-lock.json
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/package-lock.json
@@ -262,6 +262,12 @@
"integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q==",
"optional": true
},
+ "@types/history": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.2.tgz",
+ "integrity": "sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q==",
+ "dev": true
+ },
"@types/node": {
"version": "10.11.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.3.tgz",
@@ -294,6 +300,27 @@
"@types/react": "*"
}
},
+ "@types/react-router": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-4.4.1.tgz",
+ "integrity": "sha512-CtQfdcXyMye3vflnQQ2sHU832iDJRoAr4P+7f964KlLYupXU1I5crP1+d/WnCMo6mmtjBjqQvxrtbAbodqerMA==",
+ "dev": true,
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*"
+ }
+ },
+ "@types/react-router-dom": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.1.tgz",
+ "integrity": "sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A==",
+ "dev": true,
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"axios": {
"version": "0.18.0",
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js b/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js
index 7f1f5d6..6898f1f 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js
@@ -18,6 +18,8 @@
'react-dom': 'npm:react-dom/umd/react-dom.development.js',
'react-router-dom': 'npm:react-router-dom/umd/react-router-dom.js',
+ 'moment': 'npm:moment/min/moment-with-locales.min.js',
+
'axios': 'npm:axios/dist/axios.min.js',
},
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/tsconfig.json b/themes/src/main/resources/theme/keycloak-preview/account/resources/tsconfig.json
index 99f35dc..f835db0 100644
--- a/themes/src/main/resources/theme/keycloak-preview/account/resources/tsconfig.json
+++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/tsconfig.json
@@ -7,7 +7,9 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
- "noImplicitAny": false,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "jsx": "react",
"suppressImplicitAnyIndexErrors": true
}
}