Merge pull request #1589 from comus/account-ui-patch

a make sense modified std:accounts-ui for telescope
This commit is contained in:
Xavier Cazalot 2017-03-21 08:13:46 +01:00 committed by GitHub
commit 8109a8df57
21 changed files with 1779 additions and 19 deletions

View file

@ -0,0 +1,231 @@
import { Accounts } from 'meteor/accounts-base';
import {
redirect,
validatePassword,
validateEmail,
validateUsername,
} from './helpers.js';
/**
* @summary Accounts UI
* @namespace
* @memberOf Accounts
*/
Accounts.ui = {};
Accounts.ui._options = {
requestPermissions: [],
requestOfflineToken: {},
forceApprovalPrompt: {},
requireEmailVerification: false,
passwordSignupFields: 'USERNAME_AND_EMAIL',
minimumPasswordLength: 7,
loginPath: '/',
signUpPath: null,
resetPasswordPath: null,
profilePath: '/',
changePasswordPath: null,
homeRoutePath: '/',
onSubmitHook: () => {},
onPreSignUpHook: () => new Promise(resolve => resolve()),
onPostSignUpHook: () => {},
onEnrollAccountHook: () => redirect(`${Accounts.ui._options.loginPath}`),
onResetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.profilePath}`),
onSignedInHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`),
onSignedOutHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`),
emailPattern: new RegExp('[^@]+@[^@\.]{2,}\.[^\.@]+'),
};
/**
* @summary Configure the behavior of [`<Accounts.ui.LoginForm />`](#react-accounts-ui).
* @anywhere
* @param {Object} options
* @param {Object} options.requestPermissions Which [permissions](#requestpermissions) to request from the user for each external service.
* @param {Object} options.requestOfflineToken To ask the user for permission to act on their behalf when offline, map the relevant external service to `true`. Currently only supported with Google. See [Meteor.loginWithExternalService](#meteor_loginwithexternalservice) for more details.
* @param {Object} options.forceApprovalPrompt If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google.
* @param {String} options.passwordSignupFields Which fields to display in the user creation form. One of '`USERNAME_AND_EMAIL`' (default), '`USERNAME_AND_OPTIONAL_EMAIL`', '`USERNAME_ONLY`', '`EMAIL_ONLY`'.
*/
Accounts.ui.config = function(options) {
// validate options keys
const VALID_KEYS = [
'passwordSignupFields',
'requestPermissions',
'requestOfflineToken',
'forbidClientAccountCreation',
'requireEmailVerification',
'minimumPasswordLength',
'loginPath',
'signUpPath',
'resetPasswordPath',
'profilePath',
'changePasswordPath',
'homeRoutePath',
'onSubmitHook',
'onPreSignUpHook',
'onPostSignUpHook',
'onEnrollAccountHook',
'onResetPasswordHook',
'onVerifyEmailHook',
'onSignedInHook',
'onSignedOutHook',
'validateField',
'emailPattern',
];
_.each(_.keys(options), function (key) {
if (!_.contains(VALID_KEYS, key))
throw new Error("Accounts.ui.config: Invalid key: " + key);
});
// Deal with `passwordSignupFields`
if (options.passwordSignupFields) {
if (_.contains([
"USERNAME_AND_EMAIL",
"USERNAME_AND_OPTIONAL_EMAIL",
"USERNAME_ONLY",
"EMAIL_ONLY",
], options.passwordSignupFields)) {
Accounts.ui._options.passwordSignupFields = options.passwordSignupFields;
}
else {
throw new Error("Accounts.ui.config: Invalid option for `passwordSignupFields`: " + options.passwordSignupFields);
}
}
// Deal with `requestPermissions`
if (options.requestPermissions) {
_.each(options.requestPermissions, function (scope, service) {
if (Accounts.ui._options.requestPermissions[service]) {
throw new Error("Accounts.ui.config: Can't set `requestPermissions` more than once for " + service);
}
else if (!(scope instanceof Array)) {
throw new Error("Accounts.ui.config: Value for `requestPermissions` must be an array");
}
else {
Accounts.ui._options.requestPermissions[service] = scope;
}
});
}
// Deal with `requestOfflineToken`
if (options.requestOfflineToken) {
_.each(options.requestOfflineToken, function (value, service) {
if (service !== 'google')
throw new Error("Accounts.ui.config: `requestOfflineToken` only supported for Google login at the moment.");
if (Accounts.ui._options.requestOfflineToken[service]) {
throw new Error("Accounts.ui.config: Can't set `requestOfflineToken` more than once for " + service);
}
else {
Accounts.ui._options.requestOfflineToken[service] = value;
}
});
}
// Deal with `forceApprovalPrompt`
if (options.forceApprovalPrompt) {
_.each(options.forceApprovalPrompt, function (value, service) {
if (service !== 'google')
throw new Error("Accounts.ui.config: `forceApprovalPrompt` only supported for Google login at the moment.");
if (Accounts.ui._options.forceApprovalPrompt[service]) {
throw new Error("Accounts.ui.config: Can't set `forceApprovalPrompt` more than once for " + service);
}
else {
Accounts.ui._options.forceApprovalPrompt[service] = value;
}
});
}
// Deal with `requireEmailVerification`
if (options.requireEmailVerification) {
if (typeof options.requireEmailVerification != 'boolean') {
throw new Error(`Accounts.ui.config: "requireEmailVerification" not a boolean`);
}
else {
Accounts.ui._options.requireEmailVerification = options.requireEmailVerification;
}
}
// Deal with `minimumPasswordLength`
if (options.minimumPasswordLength) {
if (typeof options.minimumPasswordLength != 'number') {
throw new Error(`Accounts.ui.config: "minimumPasswordLength" not a number`);
}
else {
Accounts.ui._options.minimumPasswordLength = options.minimumPasswordLength;
}
}
// Deal with the hooks.
for (let hook of [
'onSubmitHook',
'onPreSignUpHook',
'onPostSignUpHook',
]) {
if (options[hook]) {
if (typeof options[hook] != 'function') {
throw new Error(`Accounts.ui.config: "${hook}" not a function`);
}
else {
Accounts.ui._options[hook] = options[hook];
}
}
}
// Deal with pattern.
for (let hook of [
'emailPattern',
]) {
if (options[hook]) {
if (!(options[hook] instanceof RegExp)) {
throw new Error(`Accounts.ui.config: "${hook}" not a Regular Expression`);
}
else {
Accounts.ui._options[hook] = options[hook];
}
}
}
// deal with the paths.
for (let path of [
'loginPath',
'signUpPath',
'resetPasswordPath',
'profilePath',
'changePasswordPath',
'homeRoutePath'
]) {
if (typeof options[path] !== 'undefined') {
if (options[path] !== null && typeof options[path] !== 'string') {
throw new Error(`Accounts.ui.config: ${path} is not a string or null`);
}
else {
Accounts.ui._options[path] = options[path];
}
}
}
// deal with redirect hooks.
for (let hook of [
'onEnrollAccountHook',
'onResetPasswordHook',
'onVerifyEmailHook',
'onSignedInHook',
'onSignedOutHook']) {
if (options[hook]) {
if (typeof options[hook] == 'function') {
Accounts.ui._options[hook] = options[hook];
}
else if (typeof options[hook] == 'string') {
Accounts.ui._options[hook] = () => redirect(options[hook]);
}
else {
throw new Error(`Accounts.ui.config: "${hook}" not a function or an absolute or relative path`);
}
}
}
};
export default Accounts;

View file

@ -0,0 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { getLoginServices } from '../../helpers.js';
Meteor.publish('servicesList', function() {
let services = getLoginServices();
if (Package['accounts-password']) {
services.push({name: 'password'});
}
let fields = {};
// Publish the existing services for a user, only name or nothing else.
services.forEach(service => fields[`services.${service.name}.name`] = 1);
return Meteor.users.find({ _id: this.userId }, { fields: fields});
});

View file

@ -0,0 +1,118 @@
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'),
ENROLL_ACCOUNT: Symbol('ENROLL_ACCOUNT')
};
export function getLoginServices() {
// First look for OAuth services.
const services = Package['accounts-oauth'] ? Accounts.oauth.serviceNames() : [];
// Be equally kind to all login services. This also preserves
// backwards-compatibility.
services.sort();
return _.map(services, function(name){
return {name: name};
});
};
// Export getLoginServices using old style globals for accounts-base which
// requires it.
this.getLoginServices = getLoginServices;
export function hasPasswordService() {
// First look for OAuth services.
return !!Package['accounts-password'];
};
export function loginResultCallback(service, err) {
if (!err) {
} else if (err instanceof Accounts.LoginCancelledError) {
// do nothing
} else if (err instanceof ServiceConfiguration.ConfigError) {
} else {
//loginButtonsSession.errorMessage(err.reason || "Unknown error");
}
if (Meteor.isClient) {
if (typeof redirect === 'string'){
window.location.href = '/';
}
if (typeof service === 'function'){
service();
}
}
};
export function passwordSignupFields() {
return Accounts.ui._options.passwordSignupFields || "USERNAME_AND_EMAIL";
};
export function validateEmail(email, showMessage, clearMessage) {
if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '') {
return true;
}
if (Accounts.ui._options.emailPattern.test(email)) {
return true;
} else if (!email || email.length === 0) {
showMessage(T9n.get("error.emailRequired"), 'warning', false, 'email');
return false;
} else {
showMessage(T9n.get("error.accounts.Invalid email"), 'warning', false, 'email');
return false;
}
}
export function validatePassword(password = '', showMessage, clearMessage){
if (password.length >= Accounts.ui._options.minimumPasswordLength) {
return true;
} else {
const errMsg = T9n.get("error.minChar").replace(/7/, Accounts.ui._options.minimumPasswordLength);
showMessage(errMsg, 'warning', false, 'password');
return false;
}
};
export function validateUsername(username, showMessage, clearMessage, formState) {
if ( username ) {
return true;
} else {
const fieldName = (passwordSignupFields() === 'USERNAME_ONLY' || formState === STATES.SIGN_UP) ? 'username' : 'usernameOrEmail';
showMessage(T9n.get("error.usernameRequired"), 'warning', false, fieldName);
return false;
}
}
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 );
}
}, 100);
}
}
}
export function capitalize(string) {
return string.replace(/\-/, ' ').split(' ').map(word => {
return word.charAt(0).toUpperCase() + word.slice(1);
}).join(' ');
}

View file

@ -0,0 +1,107 @@
import {Accounts} from 'meteor/accounts-base';
import {
STATES,
loginResultCallback,
getLoginServices
} from './helpers.js';
const VALID_KEYS = [
'dropdownVisible',
// XXX consider replacing these with one key that has an enum for values.
'inSignupFlow',
'inForgotPasswordFlow',
'inChangePasswordFlow',
'inMessageOnlyFlow',
'errorMessage',
'infoMessage',
// dialogs with messages (info and error)
'resetPasswordToken',
'enrollAccountToken',
'justVerifiedEmail',
'justResetPassword',
'configureLoginServiceDialogVisible',
'configureLoginServiceDialogServiceName',
'configureLoginServiceDialogSaveDisabled',
'configureOnDesktopVisible'
];
export const validateKey = function (key) {
if (!_.contains(VALID_KEYS, key))
throw new Error("Invalid key in loginButtonsSession: " + key);
};
export const KEY_PREFIX = "Meteor.loginButtons.";
// XXX This should probably be package scope rather than exported
// (there was even a comment to that effect here from before we had
// namespacing) but accounts-ui-viewer uses it, so leave it as is for
// now
Accounts._loginButtonsSession = {
set: function(key, value) {
validateKey(key);
if (_.contains(['errorMessage', 'infoMessage'], key))
throw new Error("Don't set errorMessage or infoMessage directly. Instead, use errorMessage() or infoMessage().");
this._set(key, value);
},
_set: function(key, value) {
Session.set(KEY_PREFIX + key, value);
},
get: function(key) {
validateKey(key);
return Session.get(KEY_PREFIX + key);
}
};
if (Meteor.isClient){
// In the login redirect flow, we'll have the result of the login
// attempt at page load time when we're redirected back to the
// application. Register a callback to update the UI (i.e. to close
// the dialog on a successful login or display the error on a failed
// login).
//
Accounts.onPageLoadLogin(function (attemptInfo) {
// Ignore if we have a left over login attempt for a service that is no longer registered.
if (_.contains(_.pluck(getLoginServices(), "name"), attemptInfo.type))
loginResultCallback(attemptInfo.type, attemptInfo.error);
});
let doneCallback;
Accounts.onResetPasswordLink(function (token, done) {
Accounts._loginButtonsSession.set('resetPasswordToken', token);
Session.set(KEY_PREFIX + 'state', 'resetPasswordToken');
doneCallback = done;
Accounts.ui._options.onResetPasswordHook();
});
Accounts.onEnrollmentLink(function (token, done) {
Accounts._loginButtonsSession.set('enrollAccountToken', token);
Session.set(KEY_PREFIX + 'state', 'enrollAccountToken');
doneCallback = done;
Accounts.ui._options.onEnrollAccountHook();
});
Accounts.onEmailVerificationLink(function (token, done) {
Accounts.verifyEmail(token, function (error) {
if (! error) {
Accounts._loginButtonsSession.set('justVerifiedEmail', true);
Session.set(KEY_PREFIX + 'state', 'justVerifiedEmail');
Accounts.ui._options.onSignedInHook();
}
else {
Accounts.ui._options.onVerifyEmailHook();
}
done();
});
});
}

View file

@ -0,0 +1,34 @@
import React from 'react';
import { Accounts } from 'meteor/accounts-base';
let Link;
try { Link = require('react-router').Link; } catch(e) {}
export class Button extends React.Component {
render () {
const {
label,
href = null,
type,
disabled = false,
className,
onClick
} = this.props;
if (type == 'link') {
// Support React Router.
if (Link && href) {
return <Link to={ href } className={ className }>{ label }</Link>;
} else {
return <a href={ href } className={ className } onClick={ onClick }>{ label }</a>;
}
}
return <button className={ className }
type={ type } 
disabled={ disabled }
onClick={ onClick }>{ label }</button>;
}
}
Button.propTypes = {
onClick: React.PropTypes.func
};
Accounts.ui.Button = Button;

View file

@ -0,0 +1,18 @@
import React from 'react';
import './Button.jsx';
import { Accounts } from 'meteor/accounts-base';
export class Buttons extends React.Component {
render () {
let { buttons = {}, className = "buttons" } = this.props;
return (
<div className={ className }>
{Object.keys(buttons).map((id, i) =>
<Accounts.ui.Button {...buttons[id]} key={i} />
)}
</div>
);
}
};
Accounts.ui.Buttons = Buttons;

View file

@ -0,0 +1,75 @@
import React from 'react';
import { Accounts } from 'meteor/accounts-base';
export class Field extends React.Component {
constructor(props) {
super(props);
this.state = {
mount: true
};
}
triggerUpdate() {
// Trigger an onChange on inital load, to support browser prefilled values.
const { onChange } = this.props;
if (this.input && onChange) {
onChange({ target: { value: this.input.value } });
}
}
componentDidMount() {
this.triggerUpdate();
}
componentDidUpdate(prevProps) {
// Re-mount component so that we don't expose browser prefilled passwords if the component was
// a password before and now something else.
if (prevProps.id !== this.props.id) {
this.setState({mount: false});
}
else if (!this.state.mount) {
this.setState({mount: true});
this.triggerUpdate();
}
}
render() {
const {
id,
hint,
label,
type = 'text',
onChange,
required = false,
className = "field",
defaultValue = "",
message,
} = this.props;
const { mount = true } = this.state;
if (type == 'notice') {
return <div className={ className }>{ label }</div>;
}
return mount ? (
<div className={ className }>
<label htmlFor={ id }>{ label }</label>
<input
id={ id }
ref={ (ref) => this.input = ref }
type={ type }
onChange={ onChange }
placeholder={ hint }
defaultValue={ defaultValue }
/>
{message && (
<span className={['message', message.type].join(' ').trim()}>
{message.message}</span>
)}
</div>
) : null;
}
}
Field.propTypes = {
onChange: React.PropTypes.func
};
Accounts.ui.Field = Field;

View file

@ -0,0 +1,18 @@
import React from 'react';
import { Accounts } from 'meteor/accounts-base';
import './Field.jsx';
export class Fields extends React.Component {
render () {
let { fields = {}, className = "fields" } = this.props;
return (
<div className={ className }>
{Object.keys(fields).map((id, i) =>
<Accounts.ui.Field {...fields[id]} key={i} />
)}
</div>
);
}
}
Accounts.ui.Fields = Fields;

View file

@ -0,0 +1,56 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Accounts } from 'meteor/accounts-base';
import './Fields.jsx';
import './Buttons.jsx';
import './FormMessage.jsx';
import './PasswordOrService.jsx';
import './SocialButtons.jsx';
import './FormMessages.jsx';
export class Form extends React.Component {
componentDidMount() {
let form = this.form;
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
});
}
}
render() {
const {
hasPasswordService,
oauthServices,
fields,
buttons,
error,
messages,
ready = true,
className
} = this.props;
return (
<form
ref={(ref) => this.form = ref}
className={[className, ready ? "ready" : null].join(' ')}
className="accounts-ui"
noValidate
>
<Accounts.ui.Fields fields={ fields } />
<Accounts.ui.Buttons buttons={ buttons } />
<Accounts.ui.PasswordOrService oauthServices={ oauthServices } />
<Accounts.ui.SocialButtons oauthServices={ oauthServices } />
<Accounts.ui.FormMessages messages={messages} />
</form>
);
}
}
Form.propTypes = {
oauthServices: React.PropTypes.object,
fields: React.PropTypes.object.isRequired,
buttons: React.PropTypes.object.isRequired,
error: React.PropTypes.string,
ready: React.PropTypes.bool
};
Accounts.ui.Form = Form;

View file

@ -0,0 +1,20 @@
import React from 'react';
import { Accounts } from 'meteor/accounts-base';
export class FormMessage extends React.Component {
render () {
let { message, type, className = "message", style = {}, deprecated } = this.props;
// XXX Check for deprecations.
if (deprecated) {
// Found backwords compatibility issue.
console.warn('You are overriding Accounts.ui.Form and using FormMessage, the use of FormMessage in Form has been depreacted in v1.2.11, update your implementation to use FormMessages: https://github.com/studiointeract/accounts-ui/#deprecations');
}
message = _.isObject(message) ? message.message : message; // If message is object, then try to get message from it
return message ? (
<div style={ style } 
className={[ className, type ].join(' ')}>{ message }</div>
) : null;
}
}
Accounts.ui.FormMessage = FormMessage;

View file

@ -0,0 +1,23 @@
import React, { Component } from 'react';
import { Accounts } from 'meteor/accounts-base';
export class FormMessages extends Component {
render () {
const { messages = [], className = "messages", style = {} } = this.props;
return messages.length > 0 && (
<div className="messages">
{messages
.filter(message => !('field' in message))
.map(({ message, type }, i) =>
<Accounts.ui.FormMessage
message={message}
type={type}
key={i}
/>
)}
</div>
);
}
}
Accounts.ui.FormMessages = FormMessages;

View file

@ -0,0 +1,936 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Tracker from 'tracker-component';
import { Accounts } from 'meteor/accounts-base';
import { T9n } from 'meteor/softwarerero:accounts-t9n';
import { KEY_PREFIX } from '../../login_session.js';
import './Form.jsx';
import {
STATES,
passwordSignupFields,
validateEmail,
validatePassword,
validateUsername,
loginResultCallback,
getLoginServices,
hasPasswordService,
capitalize
} from '../../helpers.js';
const loggingInMessage = 'Logging In...';
export class LoginForm extends Tracker.Component {
constructor(props) {
super(props);
if (props.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.');
}
const user = typeof props.user !== 'undefined'
? props.user : Accounts.user();
// Set inital state.
this.state = {
messages: [],
waiting: true,
formState: props.formState ? props.formState : (user ? STATES.PROFILE : STATES.SIGN_IN),
onSubmitHook: props.onSubmitHook || Accounts.ui._options.onSubmitHook,
onSignedInHook: props.onSignedInHook || Accounts.ui._options.onSignedInHook,
onSignedOutHook: props.onSignedOutHook || Accounts.ui._options.onSignedOutHook,
onPreSignUpHook: props.onPreSignUpHook || Accounts.ui._options.onPreSignUpHook,
onPostSignUpHook: props.onPostSignUpHook || Accounts.ui._options.onPostSignUpHook,
};
}
componentDidMount() {
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(prevState => ({
formState: STATES.PASSWORD_CHANGE
}));
Session.set(KEY_PREFIX + 'state', null);
break;
case 'justVerifiedEmail':
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(),
}));
// Listen for the user to login/logout.
this.autorun(() => {
// Add the services list to the user.
this.subscribe('servicesList');
this.setState({
user: Accounts.user(),
waiting: !Accounts.loginServicesConfigured()
});
});
}
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.formState && nextProps.formState !== this.state.formState) {
this.setState({
formState: nextProps.formState,
...this.getDefaultFieldValues(),
});
}
}
componentDidUpdate(prevProps, prevState) {
if (typeof this.props.user !== 'undefined') {
if (!prevProps.user !== !this.props.user) {
this.setState({
formState: this.props.user ? STATES.PROFILE : STATES.SIGN_IN
});
}
if (this.state.formState == STATES.PROFILE) {
if (!this.props.user && this.state.messages.length === 0) {
this.showMessage(loggingInMessage);
} else if (this.props.user &&
this.state.messages.find(({ message }) => message === loggingInMessage)) {
this.clearMessage(loggingInMessage);
}
} else if (prevState.formState == STATES.PROFILE &&
this.state.messages.find(({ message }) => message === loggingInMessage)) {
this.clearMessage(loggingInMessage);
}
} else {
if (!prevState.user !== !this.state.user) {
this.setState({
formState: this.state.user ? STATES.PROFILE : STATES.SIGN_IN
});
}
}
}
validateField(field, value) {
const { formState } = this.state;
switch(field) {
case 'email':
return validateEmail(value,
this.showMessage.bind(this),
this.clearMessage.bind(this),
);
case 'password':
return validatePassword(value,
this.showMessage.bind(this),
this.clearMessage.bind(this),
);
case 'username':
return validateUsername(value,
this.showMessage.bind(this),
this.clearMessage.bind(this),
formState,
);
}
}
getUsernameOrEmailField() {
return {
id: 'usernameOrEmail',
hint: T9n.get('enterUsernameOrEmail'),
label: T9n.get('usernameOrEmail'),
required: true,
defaultValue: this.state.username || "",
onChange: this.handleChange.bind(this, 'usernameOrEmail'),
message: this.getMessageForField('usernameOrEmail'),
};
}
getUsernameField() {
return {
id: 'username',
hint: T9n.get('enterUsername'),
label: T9n.get('username'),
required: true,
defaultValue: this.state.username || "",
onChange: this.handleChange.bind(this, 'username'),
message: this.getMessageForField('username'),
};
}
getEmailField() {
return {
id: 'email',
hint: T9n.get('enterEmail'),
label: T9n.get('email'),
type: 'email',
required: true,
defaultValue: this.state.email || "",
onChange: this.handleChange.bind(this, 'email'),
message: this.getMessageForField('email'),
};
}
getPasswordField() {
return {
id: 'password',
hint: T9n.get('enterPassword'),
label: T9n.get('password'),
type: 'password',
required: true,
defaultValue: this.state.password || "",
onChange: this.handleChange.bind(this, 'password'),
message: this.getMessageForField('password'),
};
}
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',
hint: T9n.get('enterNewPassword'),
label: T9n.get('newPassword'),
type: 'password',
required: true,
onChange: this.handleChange.bind(this, 'newPassword'),
message: this.getMessageForField('newPassword'),
};
}
handleChange(field, evt) {
let value = evt.target.value;
switch (field) {
case 'password': break;
default:
value = value.trim();
break;
}
this.setState({ [field]: value });
this.setDefaultFieldValues({ [field]: value });
}
fields() {
const loginFields = [];
const { formState } = this.state;
if (!hasPasswordService() && getLoginServices().length == 0) {
loginFields.push({
label: 'No login service added, i.e. accounts-password',
type: 'notice'
});
}
if (hasPasswordService() && formState == STATES.SIGN_IN) {
if (_.contains([
"USERNAME_AND_EMAIL",
"USERNAME_AND_OPTIONAL_EMAIL",
], passwordSignupFields())) {
loginFields.push(this.getUsernameOrEmailField());
}
if (passwordSignupFields() === "USERNAME_ONLY") {
loginFields.push(this.getUsernameField());
}
if (_.contains([
"EMAIL_ONLY",
], passwordSignupFields())) {
loginFields.push(this.getEmailField());
}
loginFields.push(this.getPasswordField());
}
if (hasPasswordService() && formState == STATES.SIGN_UP) {
if (_.contains([
"USERNAME_AND_EMAIL",
"USERNAME_AND_OPTIONAL_EMAIL",
"USERNAME_ONLY",
], passwordSignupFields())) {
loginFields.push(this.getUsernameField());
}
if (_.contains([
"USERNAME_AND_EMAIL",
"EMAIL_ONLY",
], passwordSignupFields())) {
loginFields.push(this.getEmailField());
}
if (_.contains(["USERNAME_AND_OPTIONAL_EMAIL"], passwordSignupFields())) {
loginFields.push(Object.assign(this.getEmailField(), {required: false}));
}
loginFields.push(this.getPasswordField());
}
if (formState == STATES.PASSWORD_RESET) {
loginFields.push(this.getEmailField());
}
if (this.showPasswordChangeForm()) {
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');
}
buttons() {
const {
loginPath = Accounts.ui._options.loginPath,
signUpPath = Accounts.ui._options.signUpPath,
resetPasswordPath = Accounts.ui._options.resetPasswordPath,
changePasswordPath = Accounts.ui._options.changePasswordPath,
profilePath = Accounts.ui._options.profilePath,
} = this.props;
const { formState, waiting } = this.state;
let loginButtons = [];
const user = typeof this.props.user !== 'undefined'
? this.props.user : this.state.user;
if (user && formState == STATES.PROFILE) {
loginButtons.push({
id: 'signOut',
label: T9n.get('signOut'),
disabled: waiting,
onClick: this.signOut.bind(this)
});
}
if (this.showCreateAccountLink()) {
loginButtons.push({
id: 'switchToSignUp',
label: T9n.get('signUp'),
type: 'link',
href: signUpPath,
onClick: this.switchToSignUp.bind(this)
});
}
if (formState == STATES.SIGN_UP || formState == STATES.PASSWORD_RESET) {
loginButtons.push({
id: 'switchToSignIn',
label: T9n.get('signIn'),
type: 'link',
href: loginPath,
onClick: this.switchToSignIn.bind(this)
});
}
if (this.showForgotPasswordLink()) {
loginButtons.push({
id: 'switchToPasswordReset',
label: T9n.get('forgotPassword'),
type: 'link',
href: resetPasswordPath,
onClick: this.switchToPasswordReset.bind(this)
});
}
if (user
&& formState == STATES.PROFILE
&& (user.services && 'password' in user.services)) {
loginButtons.push({
id: 'switchToChangePassword',
label: T9n.get('changePassword'),
type: 'link',
href: changePasswordPath,
onClick: this.switchToChangePassword.bind(this)
});
}
if (formState == STATES.SIGN_UP) {
loginButtons.push({
id: 'signUp',
label: T9n.get('signUp'),
type: hasPasswordService() ? 'submit' : 'link',
className: 'active',
disabled: waiting,
onClick: hasPasswordService() ? this.signUp.bind(this, {}) : null
});
}
if (this.showSignInLink()) {
loginButtons.push({
id: 'signIn',
label: T9n.get('signIn'),
type: hasPasswordService() ? 'submit' : 'link',
className: 'active',
disabled: waiting,
onClick: hasPasswordService() ? this.signIn.bind(this) : null
});
}
if (formState == STATES.PASSWORD_RESET) {
loginButtons.push({
id: 'emailResetLink',
label: T9n.get('resetYourPassword'),
type: 'submit',
disabled: waiting,
onClick: this.passwordReset.bind(this)
});
}
if (this.showPasswordChangeForm() || this.showEnrollAccountForm()) {
loginButtons.push({
id: 'changePassword',
label: (this.showPasswordChangeForm() ? T9n.get('changePassword') : T9n.get('setPassword')),
type: 'submit',
disabled: waiting,
onClick: this.passwordChange.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
// buttons should also come before links.
loginButtons.sort((a, b) => {
return (
b.type == 'submit' &&
a.type != undefined) - (
a.type == 'submit' &&
b.type != undefined);
});
return _.indexBy(loginButtons, 'id');
}
showSignInLink(){
return this.state.formState == STATES.SIGN_IN && Package['accounts-password'];
}
showPasswordChangeForm() {
return(Package['accounts-password']
&& 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.formState == STATES.SIGN_IN && _.contains(
["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "EMAIL_ONLY"],
passwordSignupFields()
);
}
/**
* Helper to store field values while using the form.
*/
setDefaultFieldValues(defaults) {
if (typeof defaults !== 'object') {
throw new Error('Argument to setDefaultFieldValues is not of type object');
} else if (typeof localStorage !== 'undefined' && localStorage) {
localStorage.setItem('accounts_ui', JSON.stringify({
passwordSignupFields: passwordSignupFields(),
...this.getDefaultFieldValues(),
...defaults,
}));
}
}
/**
* Helper to get field values when switching states in the form.
*/
getDefaultFieldValues() {
if (typeof localStorage !== 'undefined' && localStorage) {
const defaultFieldValues = JSON.parse(localStorage.getItem('accounts_ui') || null);
if (defaultFieldValues
&& defaultFieldValues.passwordSignupFields === passwordSignupFields()) {
return defaultFieldValues;
}
}
}
/**
* Helper to clear field values when signing in, up or out.
*/
clearDefaultFieldValues() {
if (typeof localStorage !== 'undefined' && localStorage) {
localStorage.removeItem('accounts_ui');
}
}
switchToSignUp(event) {
event.preventDefault();
this.setState({
formState: STATES.SIGN_UP,
...this.getDefaultFieldValues(),
});
this.clearMessages();
}
switchToSignIn(event) {
event.preventDefault();
this.setState({
formState: STATES.SIGN_IN,
...this.getDefaultFieldValues(),
});
this.clearMessages();
}
switchToPasswordReset(event) {
event.preventDefault();
this.setState({
formState: STATES.PASSWORD_RESET,
...this.getDefaultFieldValues(),
});
this.clearMessages();
}
switchToChangePassword(event) {
event.preventDefault();
this.setState({
formState: STATES.PASSWORD_CHANGE,
...this.getDefaultFieldValues(),
});
this.clearMessages();
}
switchToSignOut(event) {
event.preventDefault();
this.setState({
formState: STATES.PROFILE,
});
this.clearMessages();
}
cancelResetPassword(event) {
event.preventDefault();
Accounts._loginButtonsSession.set('resetPasswordToken', null);
this.setState({
formState: STATES.SIGN_IN,
messages: [],
});
}
signOut() {
Meteor.logout(() => {
this.setState({
formState: STATES.SIGN_IN,
password: null,
});
this.state.onSignedOutHook();
this.clearMessages();
this.clearDefaultFieldValues();
});
}
signIn() {
const {
username = null,
email = null,
usernameOrEmail = null,
password,
formState,
onSubmitHook
} = this.state;
let error = false;
let loginSelector;
this.clearMessages();
if (usernameOrEmail !== null) {
if (!this.validateField('username', usernameOrEmail)) {
if (this.state.formState == STATES.SIGN_UP) {
this.state.onSubmitHook("error.accounts.usernameRequired", this.state.formState);
}
error = true;
}
else {
loginSelector = usernameOrEmail;
}
} else if (username !== null) {
if (!this.validateField('username', username)) {
if (this.state.formState == STATES.SIGN_UP) {
this.state.onSubmitHook("error.accounts.usernameRequired", this.state.formState);
}
error = true;
}
else {
loginSelector = { username: username };
}
}
else if (usernameOrEmail == null) {
if (!this.validateField('email', email)) {
error = true;
}
else {
loginSelector = { email };
}
}
if (!this.validateField('password', password)) {
error = true;
}
if (!error) {
Meteor.loginWithPassword(loginSelector, password, (error, result) => {
onSubmitHook(error,formState);
if (error) {
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
}
else {
loginResultCallback(() => this.state.onSignedInHook());
this.setState({
formState: STATES.PROFILE,
password: null,
});
this.clearDefaultFieldValues();
}
});
}
}
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: 'button',
className: `btn-${service} ${service}`,
onClick: this.oauthSignIn.bind(this, service)
});
});
}
}
return _.indexBy(oauthButtons, 'id');
}
oauthSignIn(serviceName) {
const { formState, waiting, user, onSubmitHook } = this.state;
//Thanks Josh Owens for this one.
function capitalService() {
return serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
}
if(serviceName === 'meteor-developer'){
serviceName = 'meteorDeveloperAccount';
}
const loginWithService = Meteor["loginWith" + capitalService()];
let options = {}; // use default scope unless specified
if (Accounts.ui._options.requestPermissions[serviceName])
options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
if (Accounts.ui._options.requestOfflineToken[serviceName])
options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
if (Accounts.ui._options.forceApprovalPrompt[serviceName])
options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName];
this.clearMessages();
loginWithService(options, (error) => {
onSubmitHook(error,formState);
if (error) {
if (error instanceof Accounts.LoginCancelledError) {
// do nothing
} else {
console.warn(error.message || error)
this.showMessage((error.reason && T9n.get(`error.accounts.${error.reason}`)) || T9n.get('Unknown error'))
}
} else {
this.setState({ formState: STATES.PROFILE });
this.clearDefaultFieldValues();
loginResultCallback(() => {
Meteor.setTimeout(() => this.state.onSignedInHook(), 100);
});
}
});
}
signUp(options = {}) {
const {
username = null,
email = null,
usernameOrEmail = null,
password,
formState,
onSubmitHook
} = this.state;
let error = false;
this.clearMessages();
if (username !== null) {
if ( !this.validateField('username', username) ) {
if (this.state.formState == STATES.SIGN_UP) {
this.state.onSubmitHook("error.accounts.usernameRequired", this.state.formState);
}
error = true;
} else {
options.username = username;
}
} else {
if (_.contains([
"USERNAME_AND_EMAIL",
], passwordSignupFields()) && !this.validateField('username', username) ) {
if (this.state.formState == STATES.SIGN_UP) {
this.state.onSubmitHook("error.accounts.usernameRequired", this.state.formState);
}
error = true;
}
}
if (!this.validateField('email', email)){
error = true;
} else {
options.email = email;
}
if (!this.validateField('password', password)) {
onSubmitHook("Invalid password", formState);
error = true;
} else {
options.password = password;
}
const SignUp = function(_options) {
Accounts.createUser(_options, (error) => {
if (error) {
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
if (T9n.get(`error.accounts.${error.reason}`)) {
onSubmitHook(`error.accounts.${error.reason}`, formState);
}
else {
onSubmitHook("Unknown error", formState);
}
}
else {
onSubmitHook(null, formState);
this.setState({ formState: STATES.PROFILE, password: null });
let user = Accounts.user();
loginResultCallback(this.state.onPostSignUpHook.bind(this, _options, user));
this.clearDefaultFieldValues();
}
this.setState({ waiting: false });
});
};
if (!error) {
this.setState({ waiting: true });
// Allow for Promises to return.
let promise = this.state.onPreSignUpHook(options);
if (promise instanceof Promise) {
promise.then(SignUp.bind(this, options));
}
else {
SignUp(options);
}
}
}
passwordReset() {
const {
email = '',
waiting,
formState,
onSubmitHook
} = this.state;
if (waiting) {
return;
}
this.clearMessages();
if (this.validateField('email', email)) {
this.setState({ waiting: true });
Accounts.forgotPassword({ email: email }, (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.clearDefaultFieldValues();
}
onSubmitHook(error, formState);
this.setState({ waiting: false });
});
}
}
passwordChange() {
const {
password,
newPassword,
formState,
onSubmitHook,
onSignedInHook,
} = this.state;
if (!this.validateField('password', newPassword)){
onSubmitHook('err.minChar',formState);
return;
}
let token = Accounts._loginButtonsSession.get('resetPasswordToken');
if (!token) {
token = Accounts._loginButtonsSession.get('enrollAccountToken');
}
if (token) {
Accounts.resetPassword(token, newPassword, (error) => {
if (error) {
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
onSubmitHook(error, formState);
}
else {
this.showMessage(T9n.get('info.passwordChanged'), 'success', 5000);
onSubmitHook(null, formState);
this.setState({ formState: STATES.PROFILE });
Accounts._loginButtonsSession.set('resetPasswordToken', null);
Accounts._loginButtonsSession.set('enrollAccountToken', null);
onSignedInHook();
}
});
}
else {
Accounts.changePassword(password, newPassword, (error) => {
if (error) {
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
onSubmitHook(error, formState);
}
else {
this.showMessage(T9n.get('info.passwordChanged'), 'success', 5000);
onSubmitHook(null, formState);
this.setState({ formState: STATES.PROFILE });
this.clearDefaultFieldValues();
}
});
}
}
showMessage(message, type, clearTimeout, field){
message = message.trim();
if (message) {
this.setState(({ messages = [] }) => {
messages.push({
message,
type,
...(field && { field }),
});
return { messages };
});
if (clearTimeout) {
this.hideMessageTimout = setTimeout(() => {
// Filter out the message that timed out.
this.clearMessage(message);
}, clearTimeout);
}
}
}
getMessageForField(field) {
const { messages = [] } = this.state;
return messages.find(({ field:key }) => key === field);
}
clearMessage(message) {
if (message) {
this.setState(({ messages = [] }) => ({
messages: messages.filter(({ message:a }) => a !== message),
}));
}
}
clearMessages() {
if (this.hideMessageTimout) {
clearTimeout(this.hideMessageTimout);
}
this.setState({ messages: [] });
}
componentWillMount() {
// XXX Check for backwards compatibility.
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`);
}
}
}
componentWillUnmount() {
if (this.hideMessageTimout) {
clearTimeout(this.hideMessageTimout);
}
}
render() {
this.oauthButtons();
// Backwords compatibility with v1.2.x.
const { messages = [] } = this.state;
const message = {
deprecated: true,
message: messages.map(({ message }) => message).join(', '),
};
return (
<Accounts.ui.Form
oauthServices={this.oauthButtons()}
fields={this.fields()} 
buttons={this.buttons()}
{...this.state}
message={message}
/>
);
}
}
Accounts.ui.LoginForm = LoginForm;

View file

@ -0,0 +1,31 @@
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 {
render () {
let { className = "password-or-service", style = {} } = this.props;
const services = Object.keys(this.props.oauthServices).map(service => {
return this.props.oauthServices[service].label;
});
let labels = services;
if (services.length > 2) {
labels = [];
}
if (hasPasswordService() && services.length > 0) {
return (
<div style={ style } className={ className }>
{ `${T9n.get('orUse')} ${ labels.join(' / ') }` }
</div>
);
}
return null;
}
}
PasswordOrService.propTypes = {
oauthServices: React.PropTypes.object
};
Accounts.ui.PasswordOrService = PasswordOrService;

View 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;

View file

@ -0,0 +1,9 @@
import { Accounts } from 'meteor/accounts-base';
import './imports/accounts_ui.js';
import './imports/login_session.js';
import { STATES } from './imports/helpers.js';
import './imports/ui/components/LoginForm.jsx';
export { Accounts, STATES };
export default Accounts;

View file

@ -0,0 +1,10 @@
import { Accounts } from 'meteor/accounts-base';
import './imports/accounts_ui.js';
import './imports/login_session.js';
import { redirect, STATES } from './imports/helpers.js';
import './imports/api/server/servicesListPublication.js';
import './imports/ui/components/LoginForm.jsx';
export { Accounts, redirect, STATES };
export default Accounts;

View file

@ -0,0 +1,29 @@
Package.describe({
name: 'std:accounts-ui',
version: '1.2.19',
summary: 'Accounts UI for React in Meteor 1.3+',
git: 'https://github.com/studiointeract/accounts-ui',
documentation: 'README.md'
});
Package.onUse(function(api) {
api.versionsFrom('1.3');
api.use('ecmascript');
api.use('tracker');
api.use('underscore');
api.use('accounts-base');
api.use('check');
api.use('random');
api.use('email');
api.use('session');
api.use('softwarerero:accounts-t9n');
api.imply('accounts-base');
api.imply('softwarerero:accounts-t9n@1.3.3');
api.use('accounts-oauth', {weak: true});
api.use('accounts-password', {weak: true});
api.mainModule('main_client.js', 'client');
api.mainModule('main_server.js', 'server');
});

View file

@ -2,16 +2,17 @@ import React, { PropTypes, Component } from 'react';
import { Button, FormControl } from 'react-bootstrap';
import { Accounts } from 'meteor/std:accounts-ui';
import { withApollo } from 'react-apollo';
import { registerComponent } from 'meteor/nova:core';
import { registerComponent, withCurrentUser } from 'meteor/nova:core';
Accounts.ui.config({
passwordSignupFields: 'USERNAME_AND_EMAIL',
});
const AccountsForm = ({client}) => {
const AccountsForm = ({client, currentUser}) => {
return (
<div>
<Accounts.ui.LoginForm
<Accounts.ui.LoginForm
user={currentUser}
onPostSignUpHook={() => client.resetStore()}
onSignedInHook={() => client.resetStore()}
onSignedOutHook={() => client.resetStore()}
@ -26,7 +27,7 @@ class AccountsButton extends Accounts.ui.Button {
if (type === 'link') {
return <a href={ href } className={ className } onClick={ onClick }>{ label }</a>;
}
return <Button
return <Button
bsStyle="primary"
className={ className }
type={ type }
@ -56,4 +57,4 @@ class AccountsField extends Accounts.ui.Field {
Accounts.ui.Button = AccountsButton;
Accounts.ui.Field = AccountsField;
registerComponent('AccountsForm', AccountsForm, withApollo);
registerComponent('AccountsForm', AccountsForm, withCurrentUser, withApollo);

View file

@ -1,12 +1,13 @@
import { registerComponent } from 'meteor/nova:core';
import { registerComponent, withCurrentUser } from 'meteor/nova:core';
import React, { PropTypes, Component } from 'react';
import { Button, FormControl } from 'react-bootstrap';
import { Accounts } from 'meteor/std:accounts-ui';
import { withApollo } from 'react-apollo';
const UsersAccountForm = ({client}) => {
const UsersAccountForm = ({client, currentUser}) => {
return (
<Accounts.ui.LoginForm
<Accounts.ui.LoginForm
user={currentUser}
onPostSignUpHook={() => client.resetStore()}
onSignedInHook={() => client.resetStore()}
onSignedOutHook={() => client.resetStore()}
@ -14,7 +15,7 @@ const UsersAccountForm = ({client}) => {
)
};
registerComponent('UsersAccountForm', UsersAccountForm, withApollo);
registerComponent('UsersAccountForm', UsersAccountForm, withCurrentUser, withApollo);
// customize Accounts.ui

View file

@ -14,5 +14,6 @@ registerFragment(`
emailHash
slug
groups
services
}
`);
`);

View file

@ -8,7 +8,17 @@ const specificResolvers = {
},
Query: {
currentUser(root, args, context) {
return context && context.userId ? context.Users.findOne(context.userId) : null;
let user = null;
if (context && context.userId) {
user = context.Users.findOne(context.userId);
if (user.services) {
Object.keys(user.services).forEach((key) => {
user.services[key] = {}
});
}
}
return user;
},
},
};
@ -23,38 +33,38 @@ const resolvers = {
resolver(root, {terms}, context, info) {
let {selector, options} = context.Users.getParameters(terms);
options.limit = (terms.limit < 1 || terms.limit > 100) ? 100 : terms.limit;
options.skip = terms.offset;
options.fields = context.getViewableFields(context.currentUser, context.Users);
return context.Users.find(selector, options).fetch();
},
},
single: {
name: 'usersSingle',
resolver(root, {documentId, slug}, context) {
const selector = documentId ? {_id: documentId} : {'slug': slug};
// get the user first so we can get a list of viewable fields specific to this user document
const user = context.Users.findOne(selector);
return context.Users.keepViewableFields(context.currentUser, context.Users, user);
},
},
total: {
name: 'usersTotal',
resolver(root, {terms}, context) {
const {selector} = context.Users.getParameters(terms);
return context.Users.find(selector).count();
},
}
};