mirror of
https://github.com/vale981/accounts-ui
synced 2025-03-05 18:01:39 -05:00
Merge branch 'devel'
This commit is contained in:
commit
aaf11254cf
15 changed files with 344 additions and 77 deletions
|
@ -50,7 +50,7 @@ session@1.1.3
|
||||||
softwarerero:accounts-t9n@0.0.20
|
softwarerero:accounts-t9n@0.0.20
|
||||||
spacebars@1.0.9
|
spacebars@1.0.9
|
||||||
spacebars-compiler@1.0.9
|
spacebars-compiler@1.0.9
|
||||||
studiointeract:react-accounts-ui@1.0.20
|
studiointeract:react-accounts-ui@1.0.21-rc.1
|
||||||
tracker@1.0.11
|
tracker@1.0.11
|
||||||
ui@1.0.9
|
ui@1.0.9
|
||||||
underscore@1.0.6
|
underscore@1.0.6
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# ChangeLog
|
# ChangeLog
|
||||||
|
|
||||||
|
### v1.0.21
|
||||||
|
|
||||||
|
* Buttons for oauth services
|
||||||
|
* Option for "NO_PASSWORD" changed to "EMAIL_ONLY_NO_PASSWORD"
|
||||||
|
* Added new options to accounts-password "USERNAME_AND_EMAIL_NO_PASSWORD".
|
||||||
|
|
||||||
### v1.0.20
|
### v1.0.20
|
||||||
|
|
||||||
* Clear the password when logging in or out.
|
* Clear the password when logging in or out.
|
||||||
|
|
17
README.md
17
README.md
|
@ -1,6 +1,6 @@
|
||||||
# React Accounts UI
|
# React Accounts UI
|
||||||
|
|
||||||
Current version 1.0.20
|
Current version 1.0.21
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ Configure the behavior of `<Accounts.ui.LoginForm />`
|
||||||
If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google.
|
If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google.
|
||||||
|
|
||||||
* **passwordSignupFields** String
|
* **passwordSignupFields** String
|
||||||
Which fields to display in the user creation form. One of `'USERNAME_AND_EMAIL'`, `'USERNAME_AND_OPTIONAL_EMAIL'`, `'USERNAME_ONLY'`, or `'EMAIL_ONLY'`, **`'NO_PASSWORD'`** (**default**).
|
Which fields to display in the user creation form. One of `'USERNAME_AND_EMAIL'`, `'USERNAME_AND_OPTIONAL_EMAIL'`, `'USERNAME_ONLY'`, `'EMAIL_ONLY'`, `'USERNAME_AND_EMAIL_NO_PASSWORD'`, **`'EMAIL_ONLY_NO_PASSWORD'`** (**default**).
|
||||||
|
|
||||||
* **loginPath** String
|
* **loginPath** String
|
||||||
Change the default path a user should be redirected to after a clicking a link in a mail provided to them from the accounts system, it could be a mail set to them when they have reset their password, verifying their email if the setting for `sendVerificationEmail` is turned on ([read more on accounts configuration ](http://docs.meteor.com/#/full/accounts_config)).
|
Change the default path a user should be redirected to after a clicking a link in a mail provided to them from the accounts system, it could be a mail set to them when they have reset their password, verifying their email if the setting for `sendVerificationEmail` is turned on ([read more on accounts configuration ](http://docs.meteor.com/#/full/accounts_config)).
|
||||||
|
@ -260,6 +260,8 @@ export { Accounts as default };
|
||||||
* Accounts.ui.Buttons
|
* Accounts.ui.Buttons
|
||||||
* Accounts.ui.Button
|
* Accounts.ui.Button
|
||||||
* Accounts.ui.FormMessage
|
* Accounts.ui.FormMessage
|
||||||
|
* Accounts.ui.PasswordOrService
|
||||||
|
* Accounts.ui.SocialButtons
|
||||||
|
|
||||||
## Extra fields
|
## Extra fields
|
||||||
|
|
||||||
|
@ -297,6 +299,17 @@ class NewLogin extends Accounts.ui.LoginForm {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
And on the server you can store the extra fields like this:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { Accounts } from 'meteor/accounts-base';
|
||||||
|
|
||||||
|
Accounts.onCreateUser(function (options, user) {
|
||||||
|
user.profile = options.profile || {};
|
||||||
|
user.roles = {};
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|
|
@ -9,18 +9,19 @@ import { redirect } from './helpers.js';
|
||||||
Accounts.ui = {};
|
Accounts.ui = {};
|
||||||
|
|
||||||
Accounts.ui._options = {
|
Accounts.ui._options = {
|
||||||
requestPermissions: {},
|
requestPermissions: [],
|
||||||
requestOfflineToken: {},
|
requestOfflineToken: {},
|
||||||
forceApprovalPrompt: {},
|
forceApprovalPrompt: {},
|
||||||
passwordSignupFields: 'NO_PASSWORD',
|
passwordSignupFields: 'NO_PASSWORD',
|
||||||
loginPath: '/login',
|
loginPath: '/',
|
||||||
|
signUpPath: null,
|
||||||
|
resetPasswordPath: null,
|
||||||
|
profilePath: null,
|
||||||
|
changePasswordPath: null,
|
||||||
homeRoutePath: '/',
|
homeRoutePath: '/',
|
||||||
onSubmitHook: () => {},
|
onSubmitHook: () => {},
|
||||||
preSignUpHook: () => new Promise(resolve => resolve()),
|
onPreSignUpHook: () => new Promise(resolve => resolve()),
|
||||||
postSignUpHook: () => {},
|
onPostSignUpHook: () => {},
|
||||||
signUpHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
|
||||||
resetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
|
||||||
changePasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
|
||||||
onEnrollAccountHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
onEnrollAccountHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
||||||
onResetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
onResetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
||||||
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`),
|
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`),
|
||||||
|
@ -45,10 +46,14 @@ Accounts.ui.config = function(options) {
|
||||||
'requestOfflineToken',
|
'requestOfflineToken',
|
||||||
'forbidClientAccountCreation',
|
'forbidClientAccountCreation',
|
||||||
'loginPath',
|
'loginPath',
|
||||||
|
'signUpPath',
|
||||||
|
'resetPasswordPath',
|
||||||
|
'profilePath',
|
||||||
|
'changePasswordPath',
|
||||||
|
'homeRoutePath',
|
||||||
'onSubmitHook',
|
'onSubmitHook',
|
||||||
'preSignUpHook',
|
'onPreSignUpHook',
|
||||||
'postSignUpHook',
|
'onPostSignUpHook',
|
||||||
'resetPasswordHook',
|
|
||||||
'onEnrollAccountHook',
|
'onEnrollAccountHook',
|
||||||
'onResetPasswordHook',
|
'onResetPasswordHook',
|
||||||
'onVerifyEmailHook',
|
'onVerifyEmailHook',
|
||||||
|
@ -68,7 +73,8 @@ Accounts.ui.config = function(options) {
|
||||||
"USERNAME_AND_OPTIONAL_EMAIL",
|
"USERNAME_AND_OPTIONAL_EMAIL",
|
||||||
"USERNAME_ONLY",
|
"USERNAME_ONLY",
|
||||||
"EMAIL_ONLY",
|
"EMAIL_ONLY",
|
||||||
"NO_PASSWORD"
|
"EMAIL_ONLY_NO_PASSWORD",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
], options.passwordSignupFields)) {
|
], options.passwordSignupFields)) {
|
||||||
Accounts.ui._options.passwordSignupFields = options.passwordSignupFields;
|
Accounts.ui._options.passwordSignupFields = options.passwordSignupFields;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,30 @@ import { Accounts } from 'meteor/accounts-base';
|
||||||
|
|
||||||
// Method called by a user to request a password reset email. This is
|
// Method called by a user to request a password reset email. This is
|
||||||
// the start of the reset process.
|
// the start of the reset process.
|
||||||
Meteor.methods({loginWithoutPassword: function (options) {
|
Meteor.methods({loginWithoutPassword: function ({ email, username = null }) {
|
||||||
check(options, {email: String});
|
if (username !== null) {
|
||||||
|
check(username, String);
|
||||||
|
|
||||||
var user = Meteor.users.findOne({"emails.address": options.email});
|
var user = Meteor.users.findOne({ $or: [{
|
||||||
if (!user)
|
"username": username, "emails.address": { $exists: 1 }
|
||||||
throw new Meteor.Error(403, "User not found");
|
}, {
|
||||||
|
"emails.address": email
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
if (!user)
|
||||||
|
throw new Meteor.Error(403, "User not found");
|
||||||
|
|
||||||
Accounts.sendLoginEmail(user._id, options.email);
|
email = user.emails[0].address;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
check(email, String);
|
||||||
|
|
||||||
|
var user = Meteor.users.findOne({ "emails.address": email });
|
||||||
|
if (!user)
|
||||||
|
throw new Meteor.Error(403, "User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
Accounts.sendLoginEmail(user._id, email);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,17 +100,20 @@ Accounts.sendLoginEmail = function (userId, address) {
|
||||||
Email.send(options);
|
Email.send(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
Accounts.emailTemplates.loginNoPassword = {
|
// Check for installed accounts-password dependency.
|
||||||
subject: function(user) {
|
if (Accounts.emailTemplates) {
|
||||||
return "Login on " + Accounts.emailTemplates.siteName;
|
Accounts.emailTemplates.loginNoPassword = {
|
||||||
},
|
subject: function(user) {
|
||||||
text: function(user, url) {
|
return "Login on " + Accounts.emailTemplates.siteName;
|
||||||
var greeting = (user.profile && user.profile.name) ?
|
},
|
||||||
("Hello " + user.profile.name + ",") : "Hello,";
|
text: function(user, url) {
|
||||||
return `${greeting}
|
var greeting = (user.profile && user.profile.name) ?
|
||||||
|
("Hello " + user.profile.name + ",") : "Hello,";
|
||||||
|
return `${greeting}
|
||||||
To login, simply click the link below.
|
To login, simply click the link below.
|
||||||
${url}
|
${url}
|
||||||
Thanks.
|
Thanks.
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,11 @@ export function getLoginServices() {
|
||||||
// requires it.
|
// requires it.
|
||||||
this.getLoginServices = getLoginServices;
|
this.getLoginServices = getLoginServices;
|
||||||
|
|
||||||
|
export function hasPasswordService() {
|
||||||
|
// First look for OAuth services.
|
||||||
|
return !!Package['accounts-password'];
|
||||||
|
};
|
||||||
|
|
||||||
export function loginResultCallback(redirect, error) {
|
export function loginResultCallback(redirect, error) {
|
||||||
if (Meteor.isClient) {
|
if (Meteor.isClient) {
|
||||||
if (typeof redirect === 'string'){
|
if (typeof redirect === 'string'){
|
||||||
|
@ -35,7 +40,7 @@ export function loginResultCallback(redirect, error) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function passwordSignupFields() {
|
export function passwordSignupFields() {
|
||||||
return Accounts.ui._options.passwordSignupFields || "EMAIL_ONLY";
|
return Accounts.ui._options.passwordSignupFields || "EMAIL_ONLY_NO_PASSWORD";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function validatePassword(password){
|
export function validatePassword(password){
|
||||||
|
@ -63,3 +68,9 @@ export function redirect(redirect) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function capitalize(string) {
|
||||||
|
return string.replace(/\-/, ' ').split(' ').map(word => {
|
||||||
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||||
|
}).join(' ');
|
||||||
|
}
|
||||||
|
|
|
@ -4,5 +4,13 @@ T9n.map('en', {
|
||||||
'Enter newPassword': 'Enter new password',
|
'Enter newPassword': 'Enter new password',
|
||||||
'Enter email': 'Enter email',
|
'Enter email': 'Enter email',
|
||||||
'Enter username': 'Enter username',
|
'Enter username': 'Enter username',
|
||||||
'Enter username or email': 'Enter username or email'
|
'Enter username or email': 'Enter username or email',
|
||||||
|
error: {
|
||||||
|
accounts: {
|
||||||
|
"Invalid email or username": "Invalid email or username",
|
||||||
|
"Internal server error": "Internal server error",
|
||||||
|
"undefined": "Service not configured"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'or use': 'Or use'
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,5 +4,11 @@ T9n.map('sv', {
|
||||||
'Enter newPassword': 'Mata in nytt lösenord',
|
'Enter newPassword': 'Mata in nytt lösenord',
|
||||||
'Enter email': 'Mata in e-post',
|
'Enter email': 'Mata in e-post',
|
||||||
'Enter username': 'Mata in användarnamn',
|
'Enter username': 'Mata in användarnamn',
|
||||||
'Enter username or email': 'Mata in användarnamn eller e-post'
|
'Enter username or email': 'Mata in användarnamn eller e-post',
|
||||||
|
error: {
|
||||||
|
accounts: {
|
||||||
|
"Invalid email or username": "Ogiltig e-postadress eller användarnamn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'or use': 'Eller använd'
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,13 +2,23 @@ import React from 'react';
|
||||||
import { Accounts } from 'meteor/accounts-base';
|
import { Accounts } from 'meteor/accounts-base';
|
||||||
|
|
||||||
export class Button extends React.Component {
|
export class Button extends React.Component {
|
||||||
|
handleClick(evt) {
|
||||||
|
let { onClick, href } = this.props;
|
||||||
|
if (!href) {
|
||||||
|
evt.preventDefault();
|
||||||
|
onClick(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { label, type, disabled = false, onClick, className } = this.props;
|
const { label, href = null, type, disabled = false, className } = this.props;
|
||||||
return type == 'link' ? (
|
return type == 'link' ? (
|
||||||
<a className={ className } onClick={ onClick }>{ label }</a>
|
<a href={ href } className={ className } onClick={ this.handleClick.bind(this) }>{ label }</a>
|
||||||
) : (
|
) : (
|
||||||
<button className={ className } type={type} disabled={ disabled }
|
<button className={ className }
|
||||||
onClick={ onClick }>{ label }</button>
|
type={type}
|
||||||
|
disabled={ disabled }
|
||||||
|
onClick={ this.handleClick.bind(this) }>{ label }</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,35 @@ import {Accounts} from 'meteor/accounts-base';
|
||||||
import './Fields.jsx';
|
import './Fields.jsx';
|
||||||
import './Buttons.jsx';
|
import './Buttons.jsx';
|
||||||
import './FormMessage.jsx';
|
import './FormMessage.jsx';
|
||||||
|
import './PasswordOrService.jsx';
|
||||||
|
import './SocialButtons.jsx';
|
||||||
|
|
||||||
export class Form extends React.Component {
|
export class Form extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const { fields, buttons, error, message, ready = true, className } = this.props;
|
const {
|
||||||
|
hasPasswordService,
|
||||||
|
oauthServices,
|
||||||
|
fields,
|
||||||
|
buttons,
|
||||||
|
error,
|
||||||
|
message,
|
||||||
|
ready = true,
|
||||||
|
className
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<form className={[className, ready ? "ready" : null].join(' ')}
|
<form className={[className, ready ? "ready" : null].join(' ')}
|
||||||
onSubmit={ evt => evt.preventDefault() } className="accounts-ui">
|
onSubmit={ evt => evt.preventDefault() } className="accounts-ui">
|
||||||
<Accounts.ui.Fields fields={ fields } />
|
<Accounts.ui.Fields fields={ fields } />
|
||||||
<Accounts.ui.Buttons buttons={ buttons } />
|
<Accounts.ui.Buttons buttons={ buttons } />
|
||||||
<Accounts.ui.FormMessage message={ message } />
|
<Accounts.ui.PasswordOrService oauthServices={ oauthServices } />
|
||||||
|
<Accounts.ui.SocialButtons oauthServices={ oauthServices } />
|
||||||
|
<Accounts.ui.FormMessage {...message} />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Form.propTypes = {
|
Form.propTypes = {
|
||||||
|
oauthServices: React.PropTypes.object,
|
||||||
fields: React.PropTypes.object.isRequired,
|
fields: React.PropTypes.object.isRequired,
|
||||||
buttons: React.PropTypes.object.isRequired,
|
buttons: React.PropTypes.object.isRequired,
|
||||||
error: React.PropTypes.string,
|
error: React.PropTypes.string,
|
||||||
|
|
|
@ -10,17 +10,27 @@ import {
|
||||||
passwordSignupFields,
|
passwordSignupFields,
|
||||||
validatePassword,
|
validatePassword,
|
||||||
loginResultCallback,
|
loginResultCallback,
|
||||||
getLoginServices
|
getLoginServices,
|
||||||
|
hasPasswordService,
|
||||||
|
capitalize
|
||||||
} from '../../helpers.js';
|
} from '../../helpers.js';
|
||||||
|
|
||||||
export class LoginForm extends Tracker.Component {
|
export class LoginForm extends Tracker.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
let {
|
||||||
|
formState = STATES.SIGN_IN,
|
||||||
|
loginPath,
|
||||||
|
signUpPath,
|
||||||
|
resetPasswordPath,
|
||||||
|
profilePath,
|
||||||
|
changePasswordPath
|
||||||
|
} = this.props;
|
||||||
// Set inital state.
|
// Set inital state.
|
||||||
this.state = {
|
this.state = {
|
||||||
message: null,
|
message: null,
|
||||||
waiting: true,
|
waiting: true,
|
||||||
formState: Meteor.user() ? STATES.SIGN_OUT : STATES.SIGN_IN
|
formState: Meteor.user() ? STATES.SIGN_OUT : formState
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen reactively.
|
// Listen reactively.
|
||||||
|
@ -52,21 +62,21 @@ export class LoginForm extends Tracker.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateUsername( username ){
|
validateUsername( username ) {
|
||||||
if ( username.length >= 3 ) {
|
if ( username ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.showMessage(T9n.get("error.usernameTooShort"), 'warning');
|
this.showMessage(T9n.get("error.usernameRequired"), 'warning');
|
||||||
if (formState == STATES.SIGN_UP) {
|
if (this.state.formState == STATES.SIGN_UP) {
|
||||||
Accounts.ui._options.onSubmitHook("error.usernameTooShort", formState);
|
Accounts.ui._options.onSubmitHook("error.accounts.usernameRequired", this.state.formState);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateEmail( email ){
|
validateEmail( email ) {
|
||||||
if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '')
|
if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '')
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -83,7 +93,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsernameOrEmailField(){
|
getUsernameOrEmailField() {
|
||||||
return {
|
return {
|
||||||
id: 'usernameOrEmail',
|
id: 'usernameOrEmail',
|
||||||
hint: T9n.get('Enter username or email'),
|
hint: T9n.get('Enter username or email'),
|
||||||
|
@ -94,7 +104,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsernameField(){
|
getUsernameField() {
|
||||||
return {
|
return {
|
||||||
id: 'username',
|
id: 'username',
|
||||||
hint: T9n.get('Enter username'),
|
hint: T9n.get('Enter username'),
|
||||||
|
@ -105,7 +115,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmailField(){
|
getEmailField() {
|
||||||
return {
|
return {
|
||||||
id: 'email',
|
id: 'email',
|
||||||
hint: T9n.get('Enter email'),
|
hint: T9n.get('Enter email'),
|
||||||
|
@ -117,7 +127,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getPasswordField(){
|
getPasswordField() {
|
||||||
return {
|
return {
|
||||||
id: 'password',
|
id: 'password',
|
||||||
hint: T9n.get('Enter password'),
|
hint: T9n.get('Enter password'),
|
||||||
|
@ -129,7 +139,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getNewPasswordField(){
|
getNewPasswordField() {
|
||||||
return {
|
return {
|
||||||
id: 'newPassword',
|
id: 'newPassword',
|
||||||
hint: T9n.get('Enter newPassword'),
|
hint: T9n.get('Enter newPassword'),
|
||||||
|
@ -155,9 +165,12 @@ export class LoginForm extends Tracker.Component {
|
||||||
const loginFields = [];
|
const loginFields = [];
|
||||||
const { formState } = this.state;
|
const { formState } = this.state;
|
||||||
|
|
||||||
if (formState == STATES.SIGN_IN) {
|
if (hasPasswordService() && formState == STATES.SIGN_IN) {
|
||||||
if (_.contains(["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL"],
|
if (_.contains([
|
||||||
passwordSignupFields())) {
|
"USERNAME_AND_EMAIL",
|
||||||
|
"USERNAME_AND_OPTIONAL_EMAIL",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
loginFields.push(this.getUsernameOrEmailField());
|
loginFields.push(this.getUsernameOrEmailField());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,30 +178,48 @@ export class LoginForm extends Tracker.Component {
|
||||||
loginFields.push(this.getUsernameField());
|
loginFields.push(this.getUsernameField());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.contains(["EMAIL_ONLY", "NO_PASSWORD"], passwordSignupFields())) {
|
if (_.contains([
|
||||||
|
"EMAIL_ONLY",
|
||||||
|
"EMAIL_ONLY_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
loginFields.push(this.getEmailField());
|
loginFields.push(this.getEmailField());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordSignupFields() !== "NO_PASSWORD") {
|
if (!_.contains([
|
||||||
|
"EMAIL_ONLY_NO_PASSWORD",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
loginFields.push(this.getPasswordField());
|
loginFields.push(this.getPasswordField());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formState == STATES.SIGN_UP) {
|
if (hasPasswordService() && formState == STATES.SIGN_UP) {
|
||||||
if (_.contains(["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"],
|
if (_.contains([
|
||||||
passwordSignupFields())) {
|
"USERNAME_AND_EMAIL",
|
||||||
|
"USERNAME_AND_OPTIONAL_EMAIL",
|
||||||
|
"USERNAME_ONLY",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
loginFields.push(this.getUsernameField());
|
loginFields.push(this.getUsernameField());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.contains(["USERNAME_AND_EMAIL", "EMAIL_ONLY", "NO_PASSWORD"], passwordSignupFields())) {
|
if (_.contains([
|
||||||
loginFields.push(Object.assign(this.getEmailField()));
|
"USERNAME_AND_EMAIL",
|
||||||
|
"EMAIL_ONLY",
|
||||||
|
"EMAIL_ONLY_NO_PASSWORD",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
|
loginFields.push(this.getEmailField());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordSignupFields() !== "USERNAME_AND_OPTIONAL_EMAIL") {
|
if (_.contains(["USERNAME_AND_OPTIONAL_EMAIL"], passwordSignupFields())) {
|
||||||
loginFields.push(Object.assign(this.getEmailField(), {required: false}));
|
loginFields.push(Object.assign(this.getEmailField(), {required: false}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordSignupFields() !== "NO_PASSWORD") {
|
if (!_.contains([
|
||||||
|
"EMAIL_ONLY_NO_PASSWORD",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
loginFields.push(this.getPasswordField());
|
loginFields.push(this.getPasswordField());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,6 +257,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
id: 'switchToSignUp',
|
id: 'switchToSignUp',
|
||||||
label: T9n.get('signUp'),
|
label: T9n.get('signUp'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
href: this.props.signUpPath,
|
||||||
onClick: this.switchToSignUp.bind(this)
|
onClick: this.switchToSignUp.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -235,6 +267,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
id: 'switchToSignIn',
|
id: 'switchToSignIn',
|
||||||
label: T9n.get('signIn'),
|
label: T9n.get('signIn'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
href: this.props.loginPath,
|
||||||
onClick: this.switchToSignIn.bind(this)
|
onClick: this.switchToSignIn.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -244,6 +277,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
id: 'switchToPasswordReset',
|
id: 'switchToPasswordReset',
|
||||||
label: T9n.get('forgotPassword'),
|
label: T9n.get('forgotPassword'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
href: this.props.passwordResetPath,
|
||||||
onClick: this.switchToPasswordReset.bind(this)
|
onClick: this.switchToPasswordReset.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -253,6 +287,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
id: 'switchToChangePassword',
|
id: 'switchToChangePassword',
|
||||||
label: T9n.get('changePassword'),
|
label: T9n.get('changePassword'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
href: this.props.changePasswordPath,
|
||||||
onClick: this.switchToChangePassword.bind(this)
|
onClick: this.switchToChangePassword.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -261,9 +296,10 @@ export class LoginForm extends Tracker.Component {
|
||||||
loginButtons.push({
|
loginButtons.push({
|
||||||
id: 'signUp',
|
id: 'signUp',
|
||||||
label: T9n.get('signUp'),
|
label: T9n.get('signUp'),
|
||||||
type: 'submit',
|
type: hasPasswordService() ? 'submit' : 'link',
|
||||||
|
className: 'active',
|
||||||
disabled: waiting,
|
disabled: waiting,
|
||||||
onClick: this.signUp.bind(this)
|
onClick: hasPasswordService() ? this.signUp.bind(this) : null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,9 +307,10 @@ export class LoginForm extends Tracker.Component {
|
||||||
loginButtons.push({
|
loginButtons.push({
|
||||||
id: 'signIn',
|
id: 'signIn',
|
||||||
label: T9n.get('signIn'),
|
label: T9n.get('signIn'),
|
||||||
type: 'submit',
|
type: hasPasswordService() ? 'submit' : 'link',
|
||||||
|
className: 'active',
|
||||||
disabled: waiting,
|
disabled: waiting,
|
||||||
onClick: this.signIn.bind(this)
|
onClick: hasPasswordService() ? this.signIn.bind(this) : null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,12 +337,15 @@ export class LoginForm extends Tracker.Component {
|
||||||
id: 'switchToSignOut',
|
id: 'switchToSignOut',
|
||||||
label: T9n.get('cancel'),
|
label: T9n.get('cancel'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
|
href: this.props.profilePath,
|
||||||
onClick: this.switchToSignOut.bind(this)
|
onClick: this.switchToSignOut.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the button array so that the submit button always comes first.
|
// Sort the button array so that the submit button always comes first.
|
||||||
loginButtons.sort((a, b) => {
|
loginButtons.sort((a, b) => {
|
||||||
|
return a.label.localeCompare(b.label);
|
||||||
|
}).sort((a, b) => {
|
||||||
return (b.type == 'submit') - (a.type == 'submit');
|
return (b.type == 'submit') - (a.type == 'submit');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -378,12 +418,12 @@ export class LoginForm extends Tracker.Component {
|
||||||
loginSelector = { username: username };
|
loginSelector = { username: username };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (email !== null) {
|
else if (email !== null && usernameOrEmail == null) {
|
||||||
if (!this.validateEmail(email)) {
|
if (!this.validateEmail(email)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (passwordSignupFields() === "NO_PASSWORD") {
|
if (_.contains([ "EMAIL_ONLY_NO_PASSWORD" ], passwordSignupFields())) {
|
||||||
this.loginWithoutPassword();
|
this.loginWithoutPassword();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -397,6 +437,10 @@ export class LoginForm extends Tracker.Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (_.contains([ "USERNAME_AND_EMAIL_NO_PASSWORD" ], passwordSignupFields())) {
|
||||||
|
this.loginWithoutPassword();
|
||||||
|
return;
|
||||||
|
}
|
||||||
loginSelector = usernameOrEmail;
|
loginSelector = usernameOrEmail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -417,6 +461,45 @@ export class LoginForm extends Tracker.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oauthButtons() {
|
||||||
|
const { formState, waiting } = this.state;
|
||||||
|
let oauthButtons = [];
|
||||||
|
if (formState == STATES.SIGN_IN || formState == STATES.SIGN_UP ) {
|
||||||
|
if(Accounts.oauth) {
|
||||||
|
Accounts.oauth.serviceNames().map((service) => {
|
||||||
|
oauthButtons.push({
|
||||||
|
id: service,
|
||||||
|
label: capitalize(service),
|
||||||
|
disabled: waiting,
|
||||||
|
type: 'submit',
|
||||||
|
onClick: this.oauthSignIn.bind(this, service)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _.indexBy(oauthButtons, 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthSignIn(service) {
|
||||||
|
const { formState, waiting, user } = this.state;
|
||||||
|
//Thanks Josh Owens for this one.
|
||||||
|
function capitalService() {
|
||||||
|
return service.charAt(0).toUpperCase() + service.slice(1);
|
||||||
|
}
|
||||||
|
login = Meteor["loginWith" + capitalService()];
|
||||||
|
login({requestPermissions: Accounts.ui._options.requestPermissions}, (error) => {
|
||||||
|
if (error) {
|
||||||
|
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"));
|
||||||
|
} else {
|
||||||
|
this.setState({ formState: STATES.SIGN_OUT, message: '' });
|
||||||
|
loginResultCallback(() => {
|
||||||
|
Meteor.setTimeout(() => Accounts.ui._options.onSignedInHook(), 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
signUp(options = {}) {
|
signUp(options = {}) {
|
||||||
const {
|
const {
|
||||||
username = null,
|
username = null,
|
||||||
|
@ -434,6 +517,14 @@ export class LoginForm extends Tracker.Component {
|
||||||
options.username = username;
|
options.username = username;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (_.contains([
|
||||||
|
"USERNAME_AND_EMAIL",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields()) && !this.validateUsername(username) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (email !== null) {
|
if (email !== null) {
|
||||||
if ( !this.validateEmail(email) ){
|
if ( !this.validateEmail(email) ){
|
||||||
|
@ -444,7 +535,10 @@ export class LoginForm extends Tracker.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwordSignupFields() === "NO_PASSWORD") {
|
if (_.contains([
|
||||||
|
"EMAIL_ONLY_NO_PASSWORD",
|
||||||
|
"USERNAME_AND_EMAIL_NO_PASSWORD"
|
||||||
|
], passwordSignupFields())) {
|
||||||
// Generate a random password.
|
// Generate a random password.
|
||||||
options.password = Meteor.uuid();
|
options.password = Meteor.uuid();
|
||||||
}
|
}
|
||||||
|
@ -496,6 +590,7 @@ export class LoginForm extends Tracker.Component {
|
||||||
loginWithoutPassword(){
|
loginWithoutPassword(){
|
||||||
const {
|
const {
|
||||||
email = '',
|
email = '',
|
||||||
|
usernameOrEmail = '',
|
||||||
waiting
|
waiting
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
@ -517,8 +612,27 @@ export class LoginForm extends Tracker.Component {
|
||||||
this.setState({ waiting: false });
|
this.setState({ waiting: false });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else if (this.validateUsername(usernameOrEmail)) {
|
||||||
|
this.setState({ waiting: true });
|
||||||
|
|
||||||
|
Accounts.loginWithoutPassword({ email: usernameOrEmail, username: usernameOrEmail }, (error) => {
|
||||||
|
if (error) {
|
||||||
|
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.showMessage(T9n.get("info.emailSent"), 'success', 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ waiting: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
this.showMessage(T9n.get("error.accounts.Invalid email"), 'warning');
|
if (_.contains([ "USERNAME_AND_EMAIL_NO_PASSWORD" ], passwordSignupFields())) {
|
||||||
|
this.showMessage(T9n.get("error.accounts.Invalid email or username"), 'warning');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.showMessage(T9n.get("error.accounts.Invalid email"), 'warning');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,8 +729,12 @@ export class LoginForm extends Tracker.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Accounts.ui.Form fields={this.fields()} buttons={this.buttons()} {...this.state} />;
|
this.oauthButtons();
|
||||||
|
return <Accounts.ui.Form oauthServices={this.oauthButtons()}
|
||||||
|
fields={this.fields()}
|
||||||
|
buttons={this.buttons()}
|
||||||
|
{...this.state} />;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Accounts.ui.LoginForm = LoginForm;
|
Accounts.ui.LoginForm = LoginForm;
|
||||||
|
|
37
imports/ui/components/PasswordOrService.jsx
Normal file
37
imports/ui/components/PasswordOrService.jsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Accounts } from 'meteor/accounts-base';
|
||||||
|
import { T9n } from 'meteor/softwarerero:accounts-t9n';
|
||||||
|
import { hasPasswordService } from '../../helpers.js';
|
||||||
|
|
||||||
|
export class PasswordOrService extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
hasPasswordService: hasPasswordService()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let {
|
||||||
|
oauthServices = {},
|
||||||
|
className,
|
||||||
|
style = {}
|
||||||
|
} = this.props;
|
||||||
|
let { hasPasswordService } = this.state;
|
||||||
|
let labels = Object.keys(oauthServices).map(service => oauthServices[service].label);
|
||||||
|
if (labels.length > 2) {
|
||||||
|
labels = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPasswordService && Object.keys(oauthServices).length > 0) {
|
||||||
|
return (
|
||||||
|
<div style={ style } className={ className }>
|
||||||
|
{ `${T9n.get('or use')} ${ labels.join(' / ') }` }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Accounts.ui.PasswordOrService = PasswordOrService;
|
19
imports/ui/components/SocialButtons.jsx
Normal file
19
imports/ui/components/SocialButtons.jsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Button.jsx';
|
||||||
|
import {Accounts} from 'meteor/accounts-base';
|
||||||
|
|
||||||
|
|
||||||
|
export class SocialButtons extends React.Component {
|
||||||
|
render() {
|
||||||
|
let { oauthServices = {}, className = "social_buttons" } = this.props;
|
||||||
|
return(
|
||||||
|
<div className={ className }>
|
||||||
|
{Object.keys(oauthServices).map((id, i) => {
|
||||||
|
return <Accounts.ui.Button {...oauthServices[id]} key={i} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Accounts.ui.SocialButtons = SocialButtons;
|
|
@ -1,6 +1,6 @@
|
||||||
Package.describe({
|
Package.describe({
|
||||||
name: 'studiointeract:react-accounts-ui',
|
name: 'std:react-accounts-ui',
|
||||||
version: '1.0.20',
|
version: '1.1.0',
|
||||||
summary: 'Accounts UI for React in Meteor 1.3',
|
summary: 'Accounts UI for React in Meteor 1.3',
|
||||||
git: 'https://github.com/studiointeract/react-accounts-ui',
|
git: 'https://github.com/studiointeract/react-accounts-ui',
|
||||||
documentation: 'README.md'
|
documentation: 'README.md'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "react-accounts",
|
"name": "react-accounts-ui",
|
||||||
"description": "Accounts UI for React Component in Meteor",
|
"description": "Accounts UI for React Component in Meteor",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
Loading…
Add table
Reference in a new issue