Merge branch 'master' into devel

This commit is contained in:
Tim Brandin 2017-04-12 11:36:47 +02:00
commit f043cea61e
12 changed files with 269 additions and 134 deletions

104
.versions
View file

@ -1,59 +1,59 @@
accounts-base@1.2.7
allow-deny@1.0.4
babel-compiler@6.6.4
babel-runtime@0.1.8
base64@1.0.8
binary-heap@1.0.8
blaze@2.1.7
blaze-tools@1.0.8
boilerplate-generator@1.0.8
caching-compiler@1.0.4
callback-hook@1.0.8
check@1.2.1
accounts-base@1.2.15
allow-deny@1.0.5
babel-compiler@6.14.1
babel-runtime@1.0.1
base64@1.0.10
binary-heap@1.0.10
blaze@2.1.8
blaze-tools@1.0.9
boilerplate-generator@1.0.11
caching-compiler@1.1.9
callback-hook@1.0.10
check@1.2.5
coffeescript@1.0.17
ddp@1.2.5
ddp-client@1.2.7
ddp-common@1.2.5
ddp-rate-limiter@1.0.4
ddp-server@1.2.6
ddp-client@1.3.3
ddp-common@1.2.8
ddp-rate-limiter@1.0.7
ddp-server@1.3.13
deps@1.0.12
diff-sequence@1.0.5
ecmascript@0.4.3
ecmascript-runtime@0.2.10
ejson@1.0.11
email@1.0.12
geojson-utils@1.0.8
html-tools@1.0.9
htmljs@1.0.9
id-map@1.0.7
jquery@1.11.8
localstorage@1.0.9
logging@1.0.12
meteor@1.1.14
minimongo@1.0.16
modules@0.6.1
modules-runtime@0.6.3
mongo@1.1.7
mongo-id@1.0.4
npm-mongo@1.4.43
observe-sequence@1.0.11
ordered-dict@1.0.7
promise@0.6.7
random@1.0.9
rate-limit@1.0.4
reactive-dict@1.1.7
reactive-var@1.0.9
retry@1.0.7
routepolicy@1.0.10
service-configuration@1.0.9
session@1.1.5
diff-sequence@1.0.7
ecmascript@0.6.3
ecmascript-runtime@0.3.15
ejson@1.0.13
email@1.1.18
geojson-utils@1.0.10
html-tools@1.0.10
htmljs@1.0.10
id-map@1.0.9
jquery@1.11.10
localstorage@1.0.12
logging@1.1.17
meteor@1.6.1
minimongo@1.0.21
modules@0.7.9
modules-runtime@0.7.9
mongo@1.1.16
mongo-id@1.0.6
npm-mongo@2.2.24
observe-sequence@1.0.16
ordered-dict@1.0.9
promise@0.8.8
random@1.0.10
rate-limit@1.0.7
reactive-dict@1.1.8
reactive-var@1.0.11
retry@1.0.9
routepolicy@1.0.12
service-configuration@1.0.11
session@1.1.7
softwarerero:accounts-t9n@1.3.3
spacebars@1.0.11
spacebars-compiler@1.0.11
std:accounts-ui@1.2.11
spacebars@1.0.12
spacebars-compiler@1.0.12
std:accounts-ui@1.2.20
tmeasday:check-npm-versions@0.3.0
tracker@1.0.13
tracker@1.1.2
ui@1.0.11
underscore@1.0.8
webapp@1.2.8
underscore@1.0.10
webapp@1.3.14
webapp-hashing@1.0.9

View file

@ -1,5 +1,49 @@
# ChangeLog
### v1.2.20
13-March-2017
* Fixed an issue with imports when using react router.
### v1.2.19
16-February-2017
* Fixed an issue with imports when using latest version of react router.
### v1.2.18
6-February-2017
* #94 - Better support for Server-Side Rendering & client-only code in React client-only lifecycle hook
### v1.2.17
7-January-2017
* #55 - Create new form state for Enroll Account
### v1.2.16
7-January-2017
* Added warning on misconfigured LoginForm usage, that could prevent users from
resetting their password.
### v1.2.15
7-January-2017
* #91 - Fixed: localStorage not defined in server side
* Improving experience on successful reset password.
* Reset faulty redirect to reset-password.
### v1.2.14
7-January-2017
* Fixed issue with troublesome redirect in React Router when clicking link to
reset password.
### v1.2.13
6-January-2017
* Fixed issue with faulty duplicate use of componentDidMount in LoginForm.
### v1.2.12
6-January-2017
@ -10,7 +54,9 @@
18-December-2016
* #61 - BUG: Error «Need to set a username or email» when email is set
* Solved #61 by adding functionality to remember entered values in localStorage, which also makes it possible to remember values between routes (i.e. when switching between /login and /register).
* Solved #61 by adding functionality to remember entered values in localStorage,
which also makes it possible to remember values between routes (i.e. when
switching between /login and /register).
### v1.2.10
14-December-2016
@ -36,7 +82,8 @@
### v1.2.7
19-October-2016
* Make sure `nextProps.formState` actually exists before overwriting `state.formState`.
* Make sure `nextProps.formState` actually exists before overwriting
`state.formState`.
### v1.2.6
2-June-2016
@ -67,7 +114,8 @@
### v1.1.19
* Improving hooks for server side rendered pages.
* Improving so that browser pre-filled input values are pushed back to the form state.
* Improving so that browser pre-filled input values are pushed back to the form
state.
### v1.1.18
@ -87,9 +135,12 @@
### v1.1.14
* @SachaG added tmeasday:check-npm-versions to check for the correct version of npm packages.
* @ArthurPai updated T9n, which adds the Chinese language for accounts, so we can update it to v1.3.3
* @ArthurPai fixed a forgotten update T9n translation in the PasswordOrService component.
* @SachaG added tmeasday:check-npm-versions to check for the correct version of
npm packages.
* @ArthurPai updated T9n, which adds the Chinese language for accounts, so we
can update it to v1.3.3
* @ArthurPai fixed a forgotten update T9n translation in the PasswordOrService
component.
* @PolGuixe fixed the faulty meteor-developer account integration.
### v1.1.13
@ -157,7 +208,9 @@ https://github.com/studiointeract/accounts-ui/issues/16
### v1.0.19
* Added defaultValue to fields, so that when switching formState the form keeps the value it had from the other form. Which has always been a really great experience with Meteor accounts-ui.
* Added defaultValue to fields, so that when switching formState the form keeps
the value it had from the other form. Which has always been a really great
experience with Meteor accounts-ui.
### v1.0.18

View file

@ -1,6 +1,6 @@
# React Accounts UI
Current version 1.2.12
Current version 1.2.20
## Features
@ -25,9 +25,6 @@ This package does not by standard come with any styling, you can easily [extend
* [**Bootstrap 3/4**](https://atmospherejs.com/std/accounts-bootstrap) `std:accounts-bootstrap`
* [**Ionic**](https://atmospherejs.com/std/accounts-ionic) `std:accounts-ionic`
* [**Material UI**](https://atmospherejs.com/zetoff/accounts-material-ui) `zetoff:accounts-material-ui`
* Material UI
Help out on this: [http://github.com/studiointeract/accounts-material](http://github.com/studiointeract/accounts-material)
* Add your styled version here [Learn how](#create-your-own-styled-version)
@ -41,7 +38,7 @@ We support the standard [configuration in the account-ui package](http://docs.me
### Accounts.ui.config(options)
`import { Accounts } from 'meteor/std:accounts-ui`
`import { Accounts } from 'meteor/std:accounts-ui'`
Configure the behavior of `<Accounts.ui.LoginForm />`
@ -68,10 +65,9 @@ Accounts.ui.config({
### Version 1.2 also supports passing hooks through props to the component.
```js
import { Accounts, STATES } from 'meteor/std:accounts-ui';
import { Accounts } from 'meteor/std:accounts-ui';
<Accounts.ui.LoginForm
state={ STATES.SIGN_IN }
onSignedInHook={ () => console.log('user signed in') }
/>
```
@ -196,7 +192,7 @@ Meteor.startup( () => {
<Router history={ browserHistory }>
<Route path="/" component={ App }>
<IndexRoute component={ Index } />
<Route path="/signin" component={() => <Accounts.ui.LoginForm formState={STATES.SIGN_IN} />} />
<Route path="/signin" component={() => <Accounts.ui.LoginForm />} />
<Route path="/signup" component={() => <Accounts.ui.LoginForm formState={STATES.SIGN_UP} />} />
<Route path="/hello/:name" component={ Hello } />
</Route>
@ -321,7 +317,6 @@ FlowRouter.route("/login", {
action(params) {
mount(MainLayout, {
content: <Accounts.ui.LoginForm {...{
formState: STATES.SIGN_IN,
signUpPath: '/signup'
}} />
});
@ -523,4 +518,5 @@ See example: [Field.jsx#L](imports/ui/components/Field.jsx#L64-L67)
## Credits
Made by the [creative folks at Studio Interact](http://studiointeract.com)
Made by the [creative folks at Studio Interact](http://studiointeract.com) and
[all the wonderful people using and improving this package](https://github.com/studiointeract/accounts-ui/graphs/contributors).

View file

@ -1,10 +1,13 @@
let browserHistory
try { browserHistory = require('react-router').browserHistory } catch(e) {}
export const loginButtonsSession = Accounts._loginButtonsSession;
export const STATES = {
SIGN_IN: Symbol('SIGN_IN'),
SIGN_UP: Symbol('SIGN_UP'),
PROFILE: Symbol('PROFILE'),
PASSWORD_CHANGE: Symbol('PASSWORD_CHANGE'),
PASSWORD_RESET: Symbol('PASSWORD_RESET')
PASSWORD_RESET: Symbol('PASSWORD_RESET'),
ENROLL_ACCOUNT: Symbol('ENROLL_ACCOUNT')
};
export function getLoginServices() {
@ -79,11 +82,12 @@ export function validatePassword(password = '', showMessage, clearMessage){
}
};
export function validateUsername(username, showMessage, clearMessage) {
export function validateUsername(username, showMessage, clearMessage, formState) {
if ( username ) {
return true;
} else {
showMessage(T9n.get("error.usernameRequired"), 'warning', false, 'username');
const fieldName = (passwordSignupFields() === 'USERNAME_ONLY' || formState === STATES.SIGN_UP) ? 'username' : 'usernameOrEmail';
showMessage(T9n.get("error.usernameRequired"), 'warning', false, fieldName);
return false;
}
}
@ -91,15 +95,18 @@ export function validateUsername(username, showMessage, clearMessage) {
export function redirect(redirect) {
if (Meteor.isClient) {
if (window.history) {
// Run after all app specific redirects, i.e. to the login screen.
Meteor.setTimeout(() => {
if (Package['kadira:flow-router']) {
Package['kadira:flow-router'].FlowRouter.go(redirect);
} else if (Package['kadira:flow-router-ssr']) {
Package['kadira:flow-router-ssr'].FlowRouter.go(redirect);
} else if (browserHistory) {
browserHistory.push(redirect);
} else {
window.history.pushState( {} , 'redirect', redirect );
}
}, 500);
}, 100);
}
}
}

View file

@ -1,6 +1,7 @@
import React from 'react';
import { Accounts } from 'meteor/accounts-base';
try { import { Link } from 'react-router'; } catch(e) {}
let Link;
try { Link = require('react-router').Link; } catch(e) {}
export class Button extends React.Component {
render () {

View file

@ -12,7 +12,7 @@ export class Field extends React.Component {
triggerUpdate() {
// Trigger an onChange on inital load, to support browser prefilled values.
const { onChange } = this.props;
if (this.input && this.input.value) {
if (this.input && onChange) {
onChange({ target: { value: this.input.value } });
}
}

View file

@ -34,7 +34,7 @@ export class Form extends React.Component {
ref={(ref) => this.form = ref}
className={[className, ready ? "ready" : null].join(' ')}
className="accounts-ui"
novalidate
noValidate
>
<Accounts.ui.Fields fields={ fields } />
<Accounts.ui.Buttons buttons={ buttons } />

View file

@ -1,6 +1,6 @@
import React from 'react';
import React, { PropTypes, Component } from 'react';
import ReactDOM from 'react-dom';
import Tracker from 'tracker-component';
import { createContainer } from 'meteor/react-meteor-data';
import { Accounts } from 'meteor/accounts-base';
import { T9n } from 'meteor/softwarerero:accounts-t9n';
import { KEY_PREFIX } from '../../login_session.js';
@ -18,7 +18,7 @@ import {
capitalize
} from '../../helpers.js';
export class LoginForm extends Tracker.Component {
class LoginForm extends Component {
constructor(props) {
super(props);
let {
@ -29,6 +29,11 @@ export class LoginForm extends Tracker.Component {
profilePath,
changePasswordPath
} = props;
if (formState === STATES.SIGN_IN && Package['accounts-password']) {
console.warn('Do not force the state to SIGN_IN on Accounts.ui.LoginForm, it will make it impossible to reset password in your app, this state is also the default state if logged out, so no need to force it.');
}
// Set inital state.
this.state = {
messages: [],
@ -39,39 +44,38 @@ export class LoginForm extends Tracker.Component {
onSignedOutHook: props.onSignedOutHook || Accounts.ui._options.onSignedOutHook,
onPreSignUpHook: props.onPreSignUpHook || Accounts.ui._options.onPreSignUpHook,
onPostSignUpHook: props.onPostSignUpHook || Accounts.ui._options.onPostSignUpHook,
// Add default field values.
...this.getDefaultFieldValues(),
};
// Listen for the user to login/logout.
this.autorun(() => {
// Add the services list to the user.
this.subscribe('servicesList');
this.setState({
user: Accounts.user()
});
});
}
componentDidMount() {
this.setState({ waiting: false, ready: true });
this.setState(prevState => ({ waiting: false, ready: true }));
let changeState = Session.get(KEY_PREFIX + 'state');
switch (changeState) {
case 'enrollAccountToken':
this.setState(prevState => ({
formState: STATES.ENROLL_ACCOUNT
}));
Session.set(KEY_PREFIX + 'state', null);
break;
case 'resetPasswordToken':
this.setState({
this.setState(prevState => ({
formState: STATES.PASSWORD_CHANGE
});
}));
Session.set(KEY_PREFIX + 'state', null);
break;
case 'justVerifiedEmail':
this.setState({
this.setState(prevState => ({
formState: STATES.PROFILE
});
}));
Session.set(KEY_PREFIX + 'state', null);
break;
}
// Add default field values once the form did mount on the client
this.setState(prevState => ({
...this.getDefaultFieldValues(),
}));
}
componentWillReceiveProps(nextProps, nextContext) {
@ -84,14 +88,15 @@ export class LoginForm extends Tracker.Component {
}
componentDidUpdate(prevProps, prevState) {
if (!prevState.user !== !this.state.user) {
if (!prevProps.user !== !this.props.user) {
this.setState({
formState: this.state.user ? STATES.PROFILE : STATES.SIGN_IN
formState: this.props.user ? STATES.PROFILE : STATES.SIGN_IN
});
}
}
validateField(field, value) {
const { formState } = this.state;
switch(field) {
case 'email':
return validateEmail(value,
@ -107,6 +112,7 @@ export class LoginForm extends Tracker.Component {
return validateUsername(value,
this.showMessage.bind(this),
this.clearMessage.bind(this),
formState,
);
}
}
@ -161,6 +167,17 @@ export class LoginForm extends Tracker.Component {
};
}
getSetPasswordField() {
return {
id: 'newPassword',
hint: T9n.get('enterPassword'),
label: T9n.get('choosePassword'),
type: 'password',
required: true,
onChange: this.handleChange.bind(this, 'newPassword')
};
}
getNewPasswordField() {
return {
id: 'newPassword',
@ -260,13 +277,16 @@ export class LoginForm extends Tracker.Component {
}
if (this.showPasswordChangeForm()) {
if (Meteor.isClient && !Accounts._loginButtonsSession.get('resetPasswordToken')
&& !Accounts._loginButtonsSession.get('enrollAccountToken')) {
if (Meteor.isClient && !Accounts._loginButtonsSession.get('resetPasswordToken')) {
loginFields.push(this.getPasswordField());
}
loginFields.push(this.getNewPasswordField());
}
if (this.showEnrollAccountForm()) {
loginFields.push(this.getSetPasswordField());
}
return _.indexBy(loginFields, 'id');
}
@ -278,7 +298,8 @@ export class LoginForm extends Tracker.Component {
changePasswordPath = Accounts.ui._options.changePasswordPath,
profilePath = Accounts.ui._options.profilePath,
} = this.props;
const { formState, waiting, user } = this.state;
const { user } = this.props;
const { formState, waiting } = this.state;
let loginButtons = [];
if (user && formState == STATES.PROFILE) {
@ -367,22 +388,31 @@ export class LoginForm extends Tracker.Component {
});
}
if (this.showPasswordChangeForm()) {
if (this.showPasswordChangeForm() || this.showEnrollAccountForm()) {
loginButtons.push({
id: 'changePassword',
label: T9n.get('changePassword'),
label: (this.showPasswordChangeForm() ? T9n.get('changePassword') : T9n.get('setPassword')),
type: 'submit',
disabled: waiting,
onClick: this.passwordChange.bind(this)
});
loginButtons.push({
id: 'switchToSignOut',
label: T9n.get('cancel'),
type: 'link',
href: profilePath,
onClick: this.switchToSignOut.bind(this)
});
if (Accounts.user()) {
loginButtons.push({
id: 'switchToSignOut',
label: T9n.get('cancel'),
type: 'link',
href: profilePath,
onClick: this.switchToSignOut.bind(this)
});
} else {
loginButtons.push({
id: 'cancelResetPassword',
label: T9n.get('cancel'),
type: 'link',
onClick: this.cancelResetPassword.bind(this),
});
}
}
// Sort the button array so that the submit button always comes first, and
@ -407,12 +437,17 @@ export class LoginForm extends Tracker.Component {
&& this.state.formState == STATES.PASSWORD_CHANGE);
}
showEnrollAccountForm() {
return(Package['accounts-password']
&& this.state.formState == STATES.ENROLL_ACCOUNT);
}
showCreateAccountLink() {
return this.state.formState == STATES.SIGN_IN && !Accounts._options.forbidClientAccountCreation && Package['accounts-password'];
}
showForgotPasswordLink() {
return !this.state.user
return !this.props.user
&& this.state.formState == STATES.SIGN_IN
&& _.contains(
["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "EMAIL_ONLY"],
@ -425,7 +460,7 @@ export class LoginForm extends Tracker.Component {
setDefaultFieldValues(defaults) {
if (typeof defaults !== 'object') {
throw new Error('Argument to setDefaultFieldValues is not of type object');
} else if (localStorage) {
} else if (typeof localStorage !== 'undefined' && localStorage) {
localStorage.setItem('accounts_ui', JSON.stringify({
passwordSignupFields: passwordSignupFields(),
...this.getDefaultFieldValues(),
@ -438,7 +473,7 @@ export class LoginForm extends Tracker.Component {
* Helper to get field values when switching states in the form.
*/
getDefaultFieldValues() {
if (localStorage) {
if (typeof localStorage !== 'undefined' && localStorage) {
const defaultFieldValues = JSON.parse(localStorage.getItem('accounts_ui') || null);
if (defaultFieldValues
&& defaultFieldValues.passwordSignupFields === passwordSignupFields()) {
@ -451,7 +486,7 @@ export class LoginForm extends Tracker.Component {
* Helper to clear field values when signing in, up or out.
*/
clearDefaultFieldValues() {
if (localStorage) {
if (typeof localStorage !== 'undefined' && localStorage) {
localStorage.removeItem('accounts_ui');
}
}
@ -500,6 +535,15 @@ export class LoginForm extends Tracker.Component {
this.clearMessages();
}
cancelResetPassword(event) {
event.preventDefault();
Accounts._loginButtonsSession.set('resetPasswordToken', null);
this.setState({
formState: STATES.SIGN_IN,
messages: [],
});
}
signOut() {
Meteor.logout(() => {
this.setState({
@ -608,7 +652,8 @@ export class LoginForm extends Tracker.Component {
}
oauthSignIn(serviceName) {
const { formState, waiting, user, onSubmitHook } = this.state;
const { user } = this.props;
const { formState, waiting, onSubmitHook } = this.state;
//Thanks Josh Owens for this one.
function capitalService() {
return serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
@ -821,7 +866,8 @@ export class LoginForm extends Tracker.Component {
password,
newPassword,
formState,
onSubmitHook
onSubmitHook,
onSignedInHook,
} = this.state;
if (!this.validateField('password', newPassword)){
@ -845,6 +891,7 @@ export class LoginForm extends Tracker.Component {
this.setState({ formState: STATES.PROFILE });
Accounts._loginButtonsSession.set('resetPasswordToken', null);
Accounts._loginButtonsSession.set('enrollAccountToken', null);
onSignedInHook();
}
});
}
@ -904,14 +951,16 @@ export class LoginForm extends Tracker.Component {
this.setState({ messages: [] });
}
componentDidMount() {
componentWillMount() {
// XXX Check for backwards compatibility.
const container = document.createElement('div');
ReactDOM.render(<Accounts.ui.Field message="test" />, container);
if (container.getElementsByClassName('message').length == 0) {
// Found backwards compatibility issue with 1.3.x
console.warn(`Implementations of Accounts.ui.Field must render message in v1.2.11.
https://github.com/studiointeract/accounts-ui/#deprecations`);
if (Meteor.isClient) {
const container = document.createElement('div');
ReactDOM.render(<Accounts.ui.Field message="test" />, container);
if (container.getElementsByClassName('message').length == 0) {
// Found backwards compatibility issue with 1.3.x
console.warn(`Implementations of Accounts.ui.Field must render message in v1.2.11.
https://github.com/studiointeract/accounts-ui/#deprecations`);
}
}
}
@ -939,6 +988,30 @@ export class LoginForm extends Tracker.Component {
/>
);
}
}
LoginForm.propTypes = {
formState: PropTypes.string,
loginPath: PropTypes.string,
signUpPath: PropTypes.string,
resetPasswordPath: PropTypes.string,
profilePath: PropTypes.string,
changePasswordPath: PropTypes.string,
};
LoginForm.defaultProps = {
formState: null,
loginPath: null,
signUpPath: null,
resetPasswordPath: null,
profilePath: null,
changePasswordPath: null,
};
Accounts.ui.LoginForm = LoginForm;
export default createContainer(() => {
// Listen for the user to login/logout and the services list to the user.
Meteor.subscribe('servicesList');
return ({
user: Accounts.user(),
});
}, LoginForm);

View file

@ -3,8 +3,10 @@ import './imports/accounts_ui.js';
import './imports/login_session.js';
import { STATES } from './imports/helpers.js';
import './imports/api/client/loginWithoutPassword.js';
import LoginForm from './imports/ui/components/LoginForm.jsx';
import './imports/ui/components/LoginForm.jsx';
export { Accounts, STATES };
export default Accounts;
export {
LoginForm as default,
Accounts,
STATES,
};

View file

@ -4,8 +4,10 @@ import './imports/login_session.js';
import { redirect, STATES } from './imports/helpers.js';
import './imports/api/server/loginWithoutPassword.js';
import './imports/api/server/servicesListPublication.js';
import LoginForm from './imports/ui/components/LoginForm.jsx';
import './imports/ui/components/LoginForm.jsx';
export { Accounts, redirect, STATES };
export default Accounts;
export {
LoginForm as default,
Accounts,
STATES,
};

View file

@ -1,6 +1,6 @@
Package.describe({
name: 'std:accounts-ui',
version: '1.2.12',
version: '1.2.20',
summary: 'Accounts UI for React in Meteor 1.3+',
git: 'https://github.com/studiointeract/accounts-ui',
documentation: 'README.md'
@ -16,6 +16,7 @@ Package.onUse(function(api) {
api.use('random');
api.use('email');
api.use('session');
api.use('react-meteor-data');
api.use('softwarerero:accounts-t9n');
api.use('tmeasday:check-npm-versions@0.3.0');

View file

@ -18,8 +18,8 @@
"homepage": "https://github.com/studiointeract/accounts-ui",
"dependencies": {},
"peerDependencies": {
"react": ">=0.14.7 || ^15.0.0",
"react-dom": ">=0.14.7 || ^15.0.0",
"tracker-component": "^1.3.16"
"react": "^15.0.0",
"react-dom": "^15.0.0",
"react-addons-pure-render-mixin": "^15.0.0"
}
}