Merge branch 'devel' of https://github.com/VulcanJS/Vulcan into formTest

This commit is contained in:
neobii 2019-01-16 10:37:43 -06:00
commit 37e9937129
16 changed files with 202 additions and 76 deletions

View file

@ -0,0 +1,27 @@
/**
* @Author: Apollinaire Lecocq <apollinaire>
* @Date: 08-01-19
* @Last modified by: apollinaire
* @Last modified time: 10-01-19
*/
import React from 'react';
import {registerComponent, Components, withAccess, Dummy} from 'meteor/vulcan:core';
const RestrictToAdmins = withAccess({groups: ['admins']})(Dummy);
/**
* A simple component that renders the existing layout and checks wether the currentUser is an admin or not.
*/
function AdminLayout({children}) {
return (
<Components.Layout>
<RestrictToAdmins>{children}</RestrictToAdmins>
</Components.Layout>
);
}
registerComponent({
name: 'AdminLayout',
component: AdminLayout,
});

View file

@ -1,3 +1,4 @@
import './fragments.js';
import './routes.js';
import './i18n.js';
import '../components/AdminLayout';

View file

@ -1,5 +1,14 @@
import { addRoute, getDynamicComponent } from 'meteor/vulcan:core';
import {addRoute, getDynamicComponent} from 'meteor/vulcan:core';
import React from 'react';
addRoute({ name: 'admin', path: '/admin', component: () => getDynamicComponent(import('../components/AdminHome.jsx'))});
addRoute({ name: 'admin2', path: '/admin/users', component: () => getDynamicComponent(import('../components/AdminHome.jsx'))});
addRoute({
name: 'admin',
path: '/admin',
component: () => getDynamicComponent(import('../components/AdminHome.jsx')),
layoutName: 'AdminLayout',
});
addRoute({
name: 'admin2',
path: '/admin/users',
component: () => getDynamicComponent(import('../components/AdminHome.jsx')),
});

View file

@ -17,8 +17,6 @@ import { withApollo } from 'react-apollo';
import { withCookies } from 'react-cookie';
import moment from 'moment';
const DummyErrorCatcher = ({ children }) => children;
class App extends PureComponent {
constructor(props) {
super(props);
@ -114,7 +112,7 @@ class App extends PureComponent {
const LayoutComponent = currentRoute.layoutName ? Components[currentRoute.layoutName] : Components.Layout;
// if defined, use ErrorCatcher component to wrap layout contents
const ErrorCatcher = Components.ErrorCatcher ? Components.ErrorCatcher : DummyErrorCatcher;
const ErrorCatcher = Components.ErrorCatcher ? Components.ErrorCatcher : Components.Dummy;
return (
<IntlProvider locale={this.getLocale()} key={this.getLocale()} messages={Strings[this.getLocale()]}>
@ -125,7 +123,7 @@ class App extends PureComponent {
{this.props.currentUserLoading ? (
<Components.Loading />
) : this.props.children ? (
<ErrorCatcher siteData={this.props.siteData}>{this.props.children}</ErrorCatcher>
<ErrorCatcher>{this.props.children}</ErrorCatcher>
) : (
<Components.Welcome />
)}

View file

@ -0,0 +1,10 @@
import React from 'react';
import {registerComponent} from 'meteor/vulcan:lib';
function Dummy({children}) {
return children;
}
Dummy.displayName = 'Dummy';
registerComponent({name: 'Dummy', component: Dummy});
export default Dummy;

View file

@ -1,31 +1,63 @@
import React, { PureComponent } from 'react';
import { withCurrentUser } from 'meteor/vulcan:core';
import { withRouter } from 'react-router';
import React, {PureComponent} from 'react';
import {Components} from 'meteor/vulcan:lib';
import withCurrentUser from './withCurrentUser';
import {withRouter} from 'react-router';
import Users from 'meteor/vulcan:users';
export default function withAccess (options) {
/**
* withAccess - description
*
* @param {Object} options the options that define the hoc
* @param {string[]} options.groups the groups that have access to this component
* @param {string} options.redirect the link to redirect to in case the access is not granted (optional)
* @param {string} options.failureComponentName the name of a component to display if access is not granted (optional)
* @param {Component} options.failureComponent the component to display if access is not granted (optional)
* @return {PureComponent} a React component that will display only if the acces is granted
*/
const { groups, redirect } = options;
export default function withAccess(options) {
const {
groups = [],
redirect = null,
failureComponent = null,
failureComponentName = null,
} = options;
// we return a function that takes a component and itself returns a component
return WrappedComponent => {
class AccessComponent extends PureComponent {
// if there are any groups defined check if user belongs, else just check if user exists
canAccess = currentUser => {
return groups ? Users.isMemberOf(currentUser, groups) : currentUser;
}
};
// redirect on constructor if user cannot access
constructor(props) {
super(props);
if(!this.canAccess(props.currentUser) && typeof redirect === 'string') {
if (
!this.canAccess(props.currentUser) &&
typeof redirect === 'string'
) {
props.router.push(redirect);
}
}
renderFailureComponent() {
if (failureComponentName) {
const FailureComponent = Components[failureComponentName];
return <FailureComponent {...this.props} />;
} else if (failureComponent) {
const FailureComponent = failureComponent; // necesary because jsx components must be uppercase
return <FailureComponent {...this.props} />;
} else return null;
}
render() {
return this.canAccess(this.props.currentUser) ? <WrappedComponent {...this.props}/> : null;
return this.canAccess(this.props.currentUser) ? (
<WrappedComponent {...this.props} />
) : (
this.renderFailureComponent()
);
}
}

View file

@ -207,11 +207,12 @@ const registerCollectionCallbacks = (typeName, options) => {
if (options.create) {
registerCallback({
name: `${typeName}.create.validate`,
iterator: { document: 'The document being inserted' },
iterator: { validationErrors: 'An array that can be used to accumulate validation errors' },
properties: [
{ document: 'The document being inserted' },
{ currentUser: 'The current user' },
{ validationErrors: 'An object that can be used to accumulate validation errors' },
{ collection: 'The collection the document belongs to' },
{ context: 'The context of the mutation'},
],
runs: 'sync',
returns: 'document',
@ -248,11 +249,13 @@ const registerCollectionCallbacks = (typeName, options) => {
if (options.update) {
registerCallback({
name: `${typeName}.update.validate`,
iterator: { data: 'The client data' },
iterator: { validationErrors: 'An object that can be used to accumulate validation errors' },
properties: [
{ document: 'The document being edited' },
{ data: 'The client data' },
{ currentUser: 'The current user' },
{ validationErrors: 'An object that can be used to accumulate validation errors' },
{ collection: 'The collection the document belongs to' },
{ context: 'The context of the mutation'},
],
runs: 'sync',
returns: 'modifier',
@ -296,10 +299,12 @@ const registerCollectionCallbacks = (typeName, options) => {
if (options.delete) {
registerCallback({
name: `${typeName}.delete.validate`,
iterator: { document: 'The document being removed' },
iterator: { validationErrors: 'An object that can be used to accumulate validation errors' },
properties: [
{ currentUser: 'The current user' },
{ validationErrors: 'An object that can be used to accumulate validation errors' },
{ document: 'The document being removed' },
{ collection: 'The collection the document belongs to'},
{ context: 'The context of this mutation'}
],
runs: 'sync',
returns: 'document',

View file

@ -24,6 +24,7 @@ export { default as Flash } from './components/Flash.jsx';
export { default as HelloWorld } from './components/HelloWorld.jsx';
export { default as Welcome } from './components/Welcome.jsx';
export { default as RouterHook } from './components/RouterHook.jsx';
export { default as Dummy } from './components/Dummy.jsx';
export { default as withAccess } from './containers/withAccess.js';
export { default as withMessages } from './containers/withMessages.js';
@ -35,6 +36,7 @@ export { default as withDelete } from './containers/withDelete.js';
export { default as withCurrentUser } from './containers/withCurrentUser.js';
export { default as withMutation } from './containers/withMutation.js';
export { default as withUpsert } from './containers/withUpsert.js';
export { default as withSiteData } from './containers/withSiteData.js';
export { default as withComponents } from './containers/withComponents';

View file

@ -1,10 +0,0 @@
import React from 'react';
import { Components, registerComponent } from 'meteor/vulcan:lib';
const adminStyles = {
padding: '20px'
};
const AdminLayout = props => <div className="admin-layout" style={adminStyles}>{props.children}</div>;
registerComponent('AdminLayout', AdminLayout);

View file

@ -0,0 +1,10 @@
import React from 'react';
import { Components, registerComponent } from 'meteor/vulcan:lib';
const debugStyles = {
padding: '20px'
};
const DebugLayout = props => <div className="debug-layout" style={debugStyles}>{props.children}</div>;
registerComponent('DebugLayout', DebugLayout);

View file

@ -1,26 +1,31 @@
import React from 'react';
import { registerComponent, Components, Routes } from 'meteor/vulcan:lib';
import { Link } from 'react-router';
import {registerComponent, Components, Routes} from 'meteor/vulcan:lib';
import {Link} from 'react-router';
const RoutePath = ({ document }) =>
<Link to={document.path}>{document.path}</Link>;
const RoutePath = ({document}) => (
<Link to={document.path}>{document.path}</Link>
);
const RoutesDashboard = props =>
<div className="routes">
<Components.Datatable
showSearch={false}
showNew={false}
showEdit={false}
data={Object.values(Routes)}
columns={[
'name',
{
name: 'path',
component: RoutePath
},
'componentName',
]}
/>
</div>;
const RoutesDashboard = props => {
return (
<div className="routes">
<Components.Datatable
showSearch={false}
showNew={false}
showEdit={false}
data={Object.values(Routes)}
columns={[
'name',
{
name: 'path',
component: RoutePath,
},
'componentName',
'layoutName'
]}
/>
</div>
);
};
registerComponent('Routes', RoutesDashboard);
registerComponent('Routes', RoutesDashboard);

View file

@ -1,4 +1,4 @@
import '../components/AdminLayout.jsx';
import '../components/DebugLayout.jsx';
import '../components/Emails.jsx';
import '../components/Groups.jsx';

View file

@ -1,14 +1,54 @@
import { addRoute, getDynamicComponent } from 'meteor/vulcan:lib';
import {addRoute, getDynamicComponent} from 'meteor/vulcan:lib';
addRoute([
// {name: 'cheatsheet', path: '/cheatsheet', component: import('./components/Cheatsheet.jsx')},
{ name: 'debug', path: '/debug', componentName: 'DebugDashboard', layoutName: 'AdminLayout' },
{ name: 'debugGroups', path: '/debug/groups', component: () => getDynamicComponent(import('../components/Groups.jsx')), layoutName: 'AdminLayout' },
{ name: 'debugSettings', path: '/debug/settings', componentName: 'Settings', layoutName: 'AdminLayout' },
{ name: 'debugCallbacks', path: '/debug/callbacks', componentName: 'Callbacks', layoutName: 'AdminLayout' },
{
name: 'debug',
path: '/debug',
componentName: 'DebugDashboard',
layoutName: 'DebugLayout',
},
{
name: 'debugGroups',
path: '/debug/groups',
component: () => getDynamicComponent(import('../components/Groups.jsx')),
layoutName: 'DebugLayout',
},
{
name: 'debugSettings',
path: '/debug/settings',
componentName: 'Settings',
layoutName: 'DebugLayout',
},
{
name: 'debugCallbacks',
path: '/debug/callbacks',
componentName: 'Callbacks',
layoutName: 'DebugLayout',
},
// {name: 'emails', path: '/emails', component: () => getDynamicComponent(import('./components/Emails.jsx'))},
{ name: 'debugEmails', path: '/debug/emails', componentName: 'Emails', layoutName: 'AdminLayout' },
{ name: 'debugRoutes', path: '/debug/routes', componentName: 'Routes', layoutName: 'AdminLayout' },
{ name: 'debugComponents', path: '/debug/components', componentName: 'Components', layoutName: 'AdminLayout' },
{ name: 'debugI18n', path: '/debug/i18n', componentName: 'I18n', layoutName: 'AdminLayout' },
{
name: 'debugEmails',
path: '/debug/emails',
componentName: 'Emails',
layoutName: 'DebugLayout',
},
{
name: 'debugRoutes',
path: '/debug/routes',
componentName: 'Routes',
layoutName: 'DebugLayout',
},
{
name: 'debugComponents',
path: '/debug/components',
componentName: 'Components',
layoutName: 'DebugLayout',
},
{
name: 'debugI18n',
path: '/debug/i18n',
componentName: 'I18n',
layoutName: 'DebugLayout',
},
]);

View file

@ -10,7 +10,7 @@ Usage:
*/
import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core';
import { Components, registerComponent, withCurrentUser, withSiteData } from 'meteor/vulcan:core';
import React, { Component } from 'react';
import { Errors } from '../modules/errors.js';
@ -43,4 +43,4 @@ class ErrorCatcher extends Component {
}
}
registerComponent('ErrorCatcher', ErrorCatcher, withCurrentUser);
registerComponent('ErrorCatcher', ErrorCatcher, withCurrentUser, withSiteData);

View file

@ -97,15 +97,12 @@ class FormNestedArray extends PureComponent {
)
),
(!maxCount || arrayLength < maxCount) && (
<Components.Button
<Components.FormNestedFoot
key="add-button"
size="small"
variant="success"
onClick={this.addItem}
className="form-nested-button"
>
<Components.IconAdd height={12} width={12} />
</Components.Button>
addItem={this.addItem}
label={this.props.label}
className="form-nested-foot"
/>
),
hasErrors ? (
<FormComponents.FieldErrors

View file

@ -2,8 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Components, registerComponent } from 'meteor/vulcan:core';
const FormNestedFoot = ({ label, addItem }) => (
<Components.Button size="small" variant="success" iconButton onClick={addItem} className="form-nested-button">
const FormNestedFoot = ({ addItem }) => (
<Components.Button size="small" variant="success" onClick={addItem} className="form-nested-button">
<Components.IconAdd height={12} width={12} />
</Components.Button>
);