mirror of
https://github.com/vale981/accounts-ui
synced 2025-03-05 09:51:40 -05:00
Merge branch 'devel'
This commit is contained in:
commit
1fa65b4162
9 changed files with 115 additions and 108 deletions
14
README.md
14
README.md
|
@ -62,16 +62,16 @@ Configure the behavior of `<Accounts.ui.LoginForm />`
|
|||
Set the path to where you would like the user to be redirected after a successful login or sign out.
|
||||
|
||||
* **onSubmitHook** function(error, state) **client**
|
||||
Called when the LoginForm is being submitted: allows for custom actions to be taken on form submission. error contains possible errors occurred during the submission process, state specifies the LoginForm internal state from which the submission was triggered. A nice use case might be closing the modal or side-menu or dropdown showing LoginForm.
|
||||
Called when the LoginForm is being submitted: allows for custom actions to be taken on form submission. error contains possible errors occurred during the submission process, state specifies the LoginForm internal state from which the submission was triggered. A nice use case might be closing the modal or side-menu or dropdown showing LoginForm. You can get all the possible states by import `STATES` from this package.
|
||||
|
||||
* **onPreSignUpHook** function(options) **client**
|
||||
Called just before submitting the LoginForm for sign-up: allows for custom actions on the data being submitted. A nice use could be extending the user profile object accessing options.profile. to be taken on form submission. The plain text password is also provided for any reasonable use. If you return a promise, the submission will wait until you resolve it.
|
||||
|
||||
* **onPostSignUpHook** func(userId, info) **server**
|
||||
Called server side, just after a successful user account creation, post submitting the pwdForm for sign-up: allows for custom actions on the data being submitted after we are sure a new user was successfully created. A common use might be applying roles to the user, as this is only possible after fully completing user creation in `alanning:roles`. The userId is available as the first parameter, so that user user object may be retrieved.
|
||||
* **onPostSignUpHook** func(user) **client**
|
||||
Called client side, just after a successful user account creation, post submitting the form for sign-up: allows for custom actions on the data being submitted after we are sure a new user was successfully created. Default is **loginPath**.
|
||||
|
||||
* **onPostSignUpHook** func(userId, info) **client**
|
||||
Called client side, just after a successful user account creation, post submitting the pwdForm for sign-up: allows for custom actions on the data being submitted after we are sure a new user was successfully created. Default is **loginPath**.
|
||||
* **onPostSignUpHook** func(options, user) **server**
|
||||
Called server side, just after a successful user account creation, post submitting the pwdForm for sign-up: allows for custom actions on the data being submitted after we are sure a new user was successfully created. A common use might be applying roles to the user, as this is only possible after fully completing user creation in `alanning:roles`. Any extra fields added to the form is available as the first parameter, and the user is available as the second argument. *If you return the user object, this will also update the user document.*
|
||||
|
||||
* **onResetPasswordHook** function()
|
||||
Change the default redirect behavior when the user clicks the link to reset their email sent from the system, i.e. you want a custom path for the reset password form. Default is **loginPath**.
|
||||
|
@ -338,14 +338,14 @@ class NewLogin extends Accounts.ui.LoginForm {
|
|||
return super.fields();
|
||||
}
|
||||
|
||||
signUp(options = {}) {
|
||||
signUp(event, options = {}) {
|
||||
const { firstname = null } = this.state;
|
||||
if (firstname !== null) {
|
||||
options.profile = Object.assign(options.profile || {}, {
|
||||
firstname: firstname
|
||||
});
|
||||
}
|
||||
super.signUp(options);
|
||||
super.signUp(event, options);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -17,7 +17,7 @@ Accounts.ui._options = {
|
|||
loginPath: '/',
|
||||
signUpPath: null,
|
||||
resetPasswordPath: null,
|
||||
profilePath: null,
|
||||
profilePath: '/',
|
||||
changePasswordPath: null,
|
||||
homeRoutePath: '/',
|
||||
onSubmitHook: () => {},
|
||||
|
@ -25,8 +25,8 @@ Accounts.ui._options = {
|
|||
onPostSignUpHook: () => {},
|
||||
onEnrollAccountHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
||||
onResetPasswordHook: () => redirect(`${Accounts.ui._options.loginPath}`),
|
||||
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`),
|
||||
onSignedInHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`),
|
||||
onVerifyEmailHook: () => redirect(`${Accounts.ui._options.profilePath}`),
|
||||
onSignedInHook: () => redirect(`${Accounts.ui._options.profilePath}`),
|
||||
onSignedOutHook: () => redirect(`${Accounts.ui._options.homeRoutePath}`)
|
||||
};
|
||||
|
||||
|
@ -46,6 +46,7 @@ Accounts.ui.config = function(options) {
|
|||
'requestPermissions',
|
||||
'requestOfflineToken',
|
||||
'forbidClientAccountCreation',
|
||||
'minimumPasswordLength',
|
||||
'loginPath',
|
||||
'signUpPath',
|
||||
'resetPasswordPath',
|
||||
|
@ -129,8 +130,18 @@ Accounts.ui.config = function(options) {
|
|||
});
|
||||
}
|
||||
|
||||
// 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', 'preSignUpHook', 'postSignUpHook']) {
|
||||
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`);
|
||||
|
@ -141,20 +152,27 @@ Accounts.ui.config = function(options) {
|
|||
}
|
||||
}
|
||||
|
||||
// deal with `loginPath`.
|
||||
if (options.loginPath) {
|
||||
if (typeof options.loginPath != 'string') {
|
||||
throw new Error(`Accounts.ui.config: "loginPath" not an absolute or relative path`);
|
||||
}
|
||||
else {
|
||||
Accounts.ui._options.loginPath = options.loginPath;
|
||||
// deal with the paths.
|
||||
for (let path of [
|
||||
'loginPath',
|
||||
'signUpPath',
|
||||
'resetPasswordPath',
|
||||
'profilePath',
|
||||
'changePasswordPath',
|
||||
'homeRoutePath'
|
||||
]) {
|
||||
if (options[path]) {
|
||||
if (typeof options[path] != 'string') {
|
||||
throw new Error(`Accounts.ui.config: ${path} is not a string`);
|
||||
}
|
||||
else {
|
||||
Accounts.ui._options[path] = options[path];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deal with redirect hooks.
|
||||
for (let hook of [
|
||||
'resetPasswordHook',
|
||||
'changePasswordHook',
|
||||
'onEnrollAccountHook',
|
||||
'onResetPasswordHook',
|
||||
'onVerifyEmailHook',
|
||||
|
|
7
imports/api/server/onPostSignUpHook.js
Normal file
7
imports/api/server/onPostSignUpHook.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
Accounts.onCreateUser(function(options, user) {
|
||||
if (Accounts.ui._options.onPostSignUpHook) {
|
||||
let _user = Accounts.ui._options.onPostSignUpHook(options, user);
|
||||
return _user || user;
|
||||
}
|
||||
return user;
|
||||
});
|
|
@ -1,30 +1,13 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { getLoginServices } from '../../helpers.js';
|
||||
|
||||
Meteor.publish('servicesList', function() {
|
||||
let startup = true;
|
||||
let cursor = Meteor.users.find({ _id: this.userId }, { fields: {
|
||||
"services": 1
|
||||
}});
|
||||
const publishServices = (user) => {
|
||||
let services = {};
|
||||
Object.keys(user.services || []).forEach(service => services[service] = {});
|
||||
this.changed('users', this.userId, { services: services });
|
||||
this.ready();
|
||||
};
|
||||
cursor.observe({
|
||||
changed(user) {
|
||||
publishServices(user);
|
||||
},
|
||||
removed(user) {
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// Publish initial.
|
||||
let user = (cursor.fetch() || [])[0];
|
||||
if (user && user.services) {
|
||||
publishServices(user);
|
||||
let services = getLoginServices();
|
||||
if (Package['accounts-password']) {
|
||||
services.push({name: 'password'});
|
||||
}
|
||||
startup = false;
|
||||
return;
|
||||
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});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export const STATES = {
|
||||
SIGN_IN: Symbol('SIGN_IN'),
|
||||
SIGN_UP: Symbol('SIGN_UP'),
|
||||
SIGN_OUT: Symbol('SIGN_OUT'),
|
||||
PROFILE: Symbol('PROFILE'),
|
||||
PASSWORD_CHANGE: Symbol('PASSWORD_CHANGE'),
|
||||
PASSWORD_RESET: Symbol('PASSWORD_RESET')
|
||||
};
|
||||
|
@ -47,7 +47,6 @@ export function validatePassword(password){
|
|||
if (password.length >= Accounts.ui._options.minimumPasswordLength) {
|
||||
return true;
|
||||
} else {
|
||||
this.showMessage(T9n.get("error.minChar").replace(/7/, Accounts.ui._options.minimumPasswordLength), 'warning');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -75,11 +74,3 @@ export function capitalize(string) {
|
|||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
export function getUserServices() {
|
||||
if(Meteor.user() && Meteor.user().services) {
|
||||
return Object.keys(Meteor.user().services);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,15 @@ import React from 'react';
|
|||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
export class Button extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.props.href == nextProps.href;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { label, href = null, type, disabled = false, className, onClick } = this.props;
|
||||
return type == 'link' ? (
|
||||
<a href={ href } className={ className } onClick={ onClick }>{ label }</a>
|
||||
) : (
|
||||
<button className={ className }
|
||||
type={type}
|
||||
disabled={ disabled }
|
||||
onClick={ onClick }>{ label }</button>
|
||||
);
|
||||
if (type == 'link') {
|
||||
return <a href={ href } className={ className } onClick={ onClick }>{ label }</a>;
|
||||
}
|
||||
return <button className={ className }
|
||||
type={type}
|
||||
disabled={ disabled }
|
||||
onClick={ onClick }>{ label }</button>;
|
||||
}
|
||||
}
|
||||
Button.propTypes = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {Accounts} from 'meteor/accounts-base';
|
||||
import './Fields.jsx';
|
||||
import './Buttons.jsx';
|
||||
|
@ -7,6 +8,13 @@ import './PasswordOrService.jsx';
|
|||
import './SocialButtons.jsx';
|
||||
|
||||
export class Form extends React.Component {
|
||||
componentDidMount() {
|
||||
let node = ReactDOM.findDOMNode(this);
|
||||
node.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
hasPasswordService,
|
||||
|
@ -19,8 +27,7 @@ export class Form extends React.Component {
|
|||
className
|
||||
} = this.props;
|
||||
return (
|
||||
<form className={[className, ready ? "ready" : null].join(' ')}
|
||||
onSubmit={ evt => evt.preventDefault() } className="accounts-ui">
|
||||
<form className={[className, ready ? "ready" : null].join(' ')} className="accounts-ui">
|
||||
<Accounts.ui.Fields fields={ fields } />
|
||||
<Accounts.ui.Buttons buttons={ buttons } />
|
||||
<Accounts.ui.PasswordOrService oauthServices={ oauthServices } />
|
||||
|
|
|
@ -12,8 +12,7 @@ import {
|
|||
loginResultCallback,
|
||||
getLoginServices,
|
||||
hasPasswordService,
|
||||
capitalize,
|
||||
getUserServices
|
||||
capitalize
|
||||
} from '../../helpers.js';
|
||||
|
||||
export class LoginForm extends Tracker.Component {
|
||||
|
@ -31,20 +30,15 @@ export class LoginForm extends Tracker.Component {
|
|||
this.state = {
|
||||
message: null,
|
||||
waiting: true,
|
||||
formState: Meteor.user() ? STATES.SIGN_OUT : formState
|
||||
formState: Accounts.user() ? STATES.PROFILE : formState
|
||||
};
|
||||
|
||||
//adds the services list to the user document reactively
|
||||
this.autorun(() => {
|
||||
if(Meteor.user()) {
|
||||
Meteor.subscribe('servicesList');
|
||||
}
|
||||
});
|
||||
|
||||
// Listen reactively.
|
||||
// Listen for the user to login/logout.
|
||||
this.autorun(() => {
|
||||
// Add the services list to the user.
|
||||
this.subscribe('servicesList');
|
||||
this.setState({
|
||||
user: Meteor.user()
|
||||
user: Accounts.user()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -63,7 +57,7 @@ export class LoginForm extends Tracker.Component {
|
|||
|
||||
case 'justVerifiedEmail':
|
||||
this.setState({
|
||||
formState: STATES.SIGN_OUT
|
||||
formState: STATES.PROFILE
|
||||
});
|
||||
Session.set(KEY_PREFIX + 'state', null);
|
||||
break;
|
||||
|
@ -251,7 +245,7 @@ export class LoginForm extends Tracker.Component {
|
|||
const { formState, waiting, user } = this.state;
|
||||
let loginButtons = [];
|
||||
|
||||
if (user && formState == STATES.SIGN_OUT) {
|
||||
if (user && formState == STATES.PROFILE) {
|
||||
loginButtons.push({
|
||||
id: 'signOut',
|
||||
label: T9n.get('signOut'),
|
||||
|
@ -265,7 +259,7 @@ export class LoginForm extends Tracker.Component {
|
|||
id: 'switchToSignUp',
|
||||
label: T9n.get('signUp'),
|
||||
type: 'link',
|
||||
href: this.props.signUpPath,
|
||||
href: this.props.signUpPath || Accounts.ui._options.signUpPath,
|
||||
onClick: this.switchToSignUp.bind(this)
|
||||
});
|
||||
}
|
||||
|
@ -275,7 +269,7 @@ export class LoginForm extends Tracker.Component {
|
|||
id: 'switchToSignIn',
|
||||
label: T9n.get('signIn'),
|
||||
type: 'link',
|
||||
href: this.props.loginPath,
|
||||
href: this.props.loginPath || Accounts.ui._options.loginPath,
|
||||
onClick: this.switchToSignIn.bind(this)
|
||||
});
|
||||
}
|
||||
|
@ -285,17 +279,17 @@ export class LoginForm extends Tracker.Component {
|
|||
id: 'switchToPasswordReset',
|
||||
label: T9n.get('forgotPassword'),
|
||||
type: 'link',
|
||||
href: this.props.passwordResetPath,
|
||||
href: this.props.resetPasswordPath || Accounts.ui._options.resetPasswordPath,
|
||||
onClick: this.switchToPasswordReset.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
if (user && formState == STATES.SIGN_OUT && Package['accounts-password'] && getUserServices().indexOf("password") >= 0) {
|
||||
if (user && formState == STATES.PROFILE && (user && 'password' in user.services)) {
|
||||
loginButtons.push({
|
||||
id: 'switchToChangePassword',
|
||||
label: T9n.get('changePassword'),
|
||||
type: 'link',
|
||||
href: this.props.changePasswordPath,
|
||||
href: this.props.changePasswordPath || Accounts.ui._options.changePasswordPath,
|
||||
onClick: this.switchToChangePassword.bind(this)
|
||||
});
|
||||
}
|
||||
|
@ -345,16 +339,19 @@ export class LoginForm extends Tracker.Component {
|
|||
id: 'switchToSignOut',
|
||||
label: T9n.get('cancel'),
|
||||
type: 'link',
|
||||
href: this.props.profilePath,
|
||||
href: this.props.profilePath || Accounts.ui._options.profilePath,
|
||||
onClick: this.switchToSignOut.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
// Sort the button array so that the submit button always comes first.
|
||||
// Sort the button array so that the submit button always comes first, and
|
||||
// buttons should also come before links.
|
||||
loginButtons.sort((a, b) => {
|
||||
return a.label.localeCompare(b.label);
|
||||
}).sort((a, b) => {
|
||||
return (b.type == 'submit') - (a.type == 'submit');
|
||||
return (
|
||||
b.type == 'submit' &&
|
||||
a.type != undefined) - (
|
||||
a.type == 'submit' &&
|
||||
b.type != undefined);
|
||||
});
|
||||
|
||||
return _.indexBy(loginButtons, 'id');
|
||||
|
@ -377,7 +374,8 @@ export class LoginForm extends Tracker.Component {
|
|||
passwordSignupFields());
|
||||
}
|
||||
|
||||
switchToSignUp() {
|
||||
switchToSignUp(event) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
formState: STATES.SIGN_UP,
|
||||
message: null,
|
||||
|
@ -385,20 +383,24 @@ export class LoginForm extends Tracker.Component {
|
|||
});
|
||||
}
|
||||
|
||||
switchToSignIn() {
|
||||
switchToSignIn(event) {
|
||||
event.preventDefault();
|
||||
this.setState({ formState: STATES.SIGN_IN, message: null });
|
||||
}
|
||||
|
||||
switchToPasswordReset() {
|
||||
switchToPasswordReset(event) {
|
||||
event.preventDefault();
|
||||
this.setState({ formState: STATES.PASSWORD_RESET, message: null });
|
||||
}
|
||||
|
||||
switchToChangePassword() {
|
||||
switchToChangePassword(event) {
|
||||
event.preventDefault();
|
||||
this.setState({ formState: STATES.PASSWORD_CHANGE, message: null });
|
||||
}
|
||||
|
||||
switchToSignOut() {
|
||||
this.setState({ formState: STATES.SIGN_OUT, message: null });
|
||||
switchToSignOut(event) {
|
||||
event.preventDefault();
|
||||
this.setState({ formState: STATES.PROFILE, message: null });
|
||||
}
|
||||
|
||||
signOut() {
|
||||
|
@ -461,7 +463,7 @@ export class LoginForm extends Tracker.Component {
|
|||
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"), 'error');
|
||||
}
|
||||
else {
|
||||
this.setState({ formState: STATES.SIGN_OUT, message: null, password: null });
|
||||
this.setState({ formState: STATES.PROFILE, message: null, password: null });
|
||||
loginResultCallback(() => {
|
||||
Meteor.setTimeout(() => Accounts.ui._options.onSignedInHook(), 100);
|
||||
});
|
||||
|
@ -499,7 +501,7 @@ export class LoginForm extends Tracker.Component {
|
|||
if (error) {
|
||||
this.showMessage(T9n.get(`error.accounts.${error.reason}`) || T9n.get("Unknown error"));
|
||||
} else {
|
||||
this.setState({ formState: STATES.SIGN_OUT, message: '' });
|
||||
this.setState({ formState: STATES.PROFILE, message: '' });
|
||||
loginResultCallback(() => {
|
||||
Meteor.setTimeout(() => Accounts.ui._options.onSignedInHook(), 100);
|
||||
});
|
||||
|
@ -508,7 +510,7 @@ export class LoginForm extends Tracker.Component {
|
|||
|
||||
}
|
||||
|
||||
signUp(options = {}) {
|
||||
signUp(event, options = {}) {
|
||||
const {
|
||||
username = null,
|
||||
email = null,
|
||||
|
@ -551,6 +553,7 @@ export class LoginForm extends Tracker.Component {
|
|||
options.password = Meteor.uuid();
|
||||
}
|
||||
else if (!validatePassword(password)) {
|
||||
this.showMessage(T9n.get("error.minChar").replace(/7/, Accounts.ui._options.minimumPasswordLength), 'warning');
|
||||
Accounts.ui._options.onSubmitHook("error.minChar", formState);
|
||||
return;
|
||||
}
|
||||
|
@ -573,11 +576,12 @@ export class LoginForm extends Tracker.Component {
|
|||
}
|
||||
else {
|
||||
this.setState({
|
||||
formState: STATES.SIGN_OUT,
|
||||
formState: STATES.PROFILE,
|
||||
message: null,
|
||||
password: null
|
||||
});
|
||||
loginResultCallback(Accounts.ui._options.postSignUpHook);
|
||||
let user = Accounts.user();
|
||||
loginResultCallback(Accounts.ui._options.onPostSignUpHook.bind(this, user));
|
||||
}
|
||||
|
||||
this.setState({ waiting: false });
|
||||
|
@ -643,7 +647,7 @@ export class LoginForm extends Tracker.Component {
|
|||
}
|
||||
}
|
||||
|
||||
passwordReset(){
|
||||
passwordReset() {
|
||||
const {
|
||||
email = '',
|
||||
waiting
|
||||
|
@ -679,6 +683,7 @@ export class LoginForm extends Tracker.Component {
|
|||
} = this.state;
|
||||
|
||||
if ( !validatePassword(newPassword) ){
|
||||
this.showMessage(T9n.get("error.minChar").replace(/7/, Accounts.ui._options.minimumPasswordLength), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -693,7 +698,7 @@ export class LoginForm extends Tracker.Component {
|
|||
}
|
||||
else {
|
||||
this.showMessage(T9n.get('info.passwordChanged'), 'success', 5000);
|
||||
this.setState({ formState: STATES.SIGN_OUT });
|
||||
this.setState({ formState: STATES.PROFILE });
|
||||
Accounts._loginButtonsSession.set('resetPasswordToken', null);
|
||||
Accounts._loginButtonsSession.set('enrollAccountToken', null);
|
||||
}
|
||||
|
@ -706,7 +711,7 @@ export class LoginForm extends Tracker.Component {
|
|||
}
|
||||
else {
|
||||
this.showMessage(T9n.get('info.passwordChanged'), 'success', 5000);
|
||||
this.setState({ formState: STATES.SIGN_OUT });
|
||||
this.setState({ formState: STATES.PROFILE });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -728,7 +733,7 @@ export class LoginForm extends Tracker.Component {
|
|||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.user !== !this.state.user) {
|
||||
this.setState({
|
||||
formState: this.state.user ? STATES.SIGN_OUT : STATES.SIGN_IN
|
||||
formState: this.state.user ? STATES.PROFILE : STATES.SIGN_IN
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import './imports/accounts_ui.js';
|
|||
import './imports/login_session.js';
|
||||
import { redirect, STATES } from './imports/helpers.js';
|
||||
import './imports/api/server/loginWithoutPassword.js';
|
||||
import './imports/api/server/onPostSignUpHook.js';
|
||||
import './imports/api/server/servicesListPublication.js';
|
||||
import './imports/startup/extra_translations.js';
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue