mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 10:01:40 -05:00
Merge pull request #1589 from comus/account-ui-patch
a make sense modified std:accounts-ui for telescope
This commit is contained in:
commit
8109a8df57
21 changed files with 1779 additions and 19 deletions
231
packages/_accounts-ui/imports/accounts_ui.js
Executable file
231
packages/_accounts-ui/imports/accounts_ui.js
Executable 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;
|
13
packages/_accounts-ui/imports/api/server/servicesListPublication.js
Executable file
13
packages/_accounts-ui/imports/api/server/servicesListPublication.js
Executable 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});
|
||||
});
|
118
packages/_accounts-ui/imports/helpers.js
Executable file
118
packages/_accounts-ui/imports/helpers.js
Executable 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(' ');
|
||||
}
|
107
packages/_accounts-ui/imports/login_session.js
Executable file
107
packages/_accounts-ui/imports/login_session.js
Executable 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();
|
||||
});
|
||||
});
|
||||
}
|
34
packages/_accounts-ui/imports/ui/components/Button.jsx
Executable file
34
packages/_accounts-ui/imports/ui/components/Button.jsx
Executable 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;
|
18
packages/_accounts-ui/imports/ui/components/Buttons.jsx
Executable file
18
packages/_accounts-ui/imports/ui/components/Buttons.jsx
Executable 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;
|
75
packages/_accounts-ui/imports/ui/components/Field.jsx
Executable file
75
packages/_accounts-ui/imports/ui/components/Field.jsx
Executable 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;
|
18
packages/_accounts-ui/imports/ui/components/Fields.jsx
Executable file
18
packages/_accounts-ui/imports/ui/components/Fields.jsx
Executable 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;
|
56
packages/_accounts-ui/imports/ui/components/Form.jsx
Executable file
56
packages/_accounts-ui/imports/ui/components/Form.jsx
Executable 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;
|
20
packages/_accounts-ui/imports/ui/components/FormMessage.jsx
Executable file
20
packages/_accounts-ui/imports/ui/components/FormMessage.jsx
Executable 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;
|
23
packages/_accounts-ui/imports/ui/components/FormMessages.jsx
Executable file
23
packages/_accounts-ui/imports/ui/components/FormMessages.jsx
Executable 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;
|
936
packages/_accounts-ui/imports/ui/components/LoginForm.jsx
Executable file
936
packages/_accounts-ui/imports/ui/components/LoginForm.jsx
Executable 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;
|
31
packages/_accounts-ui/imports/ui/components/PasswordOrService.jsx
Executable file
31
packages/_accounts-ui/imports/ui/components/PasswordOrService.jsx
Executable 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;
|
19
packages/_accounts-ui/imports/ui/components/SocialButtons.jsx
Executable file
19
packages/_accounts-ui/imports/ui/components/SocialButtons.jsx
Executable 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;
|
9
packages/_accounts-ui/main_client.js
Executable file
9
packages/_accounts-ui/main_client.js
Executable 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;
|
10
packages/_accounts-ui/main_server.js
Executable file
10
packages/_accounts-ui/main_server.js
Executable 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;
|
29
packages/_accounts-ui/package.js
Executable file
29
packages/_accounts-ui/package.js
Executable 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');
|
||||
});
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -14,5 +14,6 @@ registerFragment(`
|
|||
emailHash
|
||||
slug
|
||||
groups
|
||||
services
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue