<repo_name>afmihai/site-starter<file_sep>/front/src/Layout/assets/vendor/vendor.js
require('./base.css');
require('./bootstrap.min.css');
<file_sep>/admin/src/modules/Model/templates/imageList/ModelImageListItemTemplate.js
import React from 'react';
import {Link} from 'react-router';
import {connect} from 'react-redux';
import {deleteItem} from '../../ModelActions';
import {I18n} from 'react-redux-i18n';
import {imageUrl} from '../../../../core/utils/filters';

@connect(state=>({
  models: state.main.models
}), {
  deleteItem
})
export default class ModelImageListItemTemplate extends React.Component {
  render() {
    const {modelName, image} = this.props;
    return (
      <Link className="item" to={{name: 'ModelEdit', params: {modelName, modelId: image.id}}}>
        {image.preview ? (
          <div className="image" style={{backgroundImage: `url('${image.preview}')`}} />
        ) : (
          <div className="image" style={{backgroundImage: `url('${imageUrl(image.filename, 'thumbnail')}')`}} />
          ) }
      </Link>
    )
  }
  deleteItem(modelId) {
    const {deleteItem, modelName, getList} = this.props;
    deleteItem(modelName, modelId).then(getList);
  }
}
<file_sep>/admin/src/core/config/translations.js
import assignDeep from 'assign-deep';
const locales = ['en', 'ro'];
const translations = {};

locales.forEach(locale=>{
  require('../../i18n').generate(locale).forEach(link=>{
    translations[locale] = assignDeep(translations[locale], require('../../'+link));
  });
});

export default translations;


<file_sep>/admin/src/routes.js
import MyAccountRoute from 'modules/MyAccount/MyAccountRoute';
import ModelRoutes from 'modules/Model/ModelRoutes';

module.exports = [
  MyAccountRoute,
  ModelRoutes
];
<file_sep>/admin/src/modules/Model/ModelEditScreen.js
import React from 'react';
import {connect} from 'react-redux';
import {initialize, stopAsyncValidation} from 'redux-form';
import {TabsForm, toastr} from '../../core/utils/components/ContentComponents';
import EditTab from './templates/ModelEditTabTemplate';
import { generateValidationErrors, formFieldBuild, getModelPrimaryKey } from 'helpers';
import {I18n} from 'react-redux-i18n';
import {
  getItem,
  createItem,
  updateItem,
  getList
} from './ModelActions';


@connect(state=>({
  models: state.main.models,
  item: state.model.item
}), {
  getItem,
  createItem,
  updateItem,
  getList,
  initialize,
  stopAsyncValidation
})
export default class ModelEditScreen extends React.Component {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };
  componentWillMount() {
    this.getSubmodels();
    this.handleAddMode();
  }
  render() {
    const {models, params: {modelName}} = this.props;
    if(!models) return null;
    const list = models[modelName].structure.edit.map(item=>{
      const fields = item.fields.map(fieldName=>{
        return {
          ...models[modelName].fields[fieldName].config.edit,
          name: fieldName
        }
      });
      return {
        name: I18n.t(`tab.${item.tabName||'details'}`),
        component: <EditTab fields={fields} />
      }
    });
    const validationRules = {};
    models[modelName].structure.edit.forEach(item=>{
      item.fields.forEach(fieldName=>{
        validationRules[fieldName] = models[modelName].fields[fieldName].validation;
      });
    });
    return (
      <div className="model-edit">
          <TabsForm list={list} onSubmit={this.onSubmit.bind(this)} validationRules={validationRules}
                    returnRoute={{name: 'ModelList', params: {modelName}}}
                    onChangeBuild={this.onChangeBuild.bind(this)}/>
      </div>
    )
  }
  componentDidUpdate(prevProps) {
    this.getSubmodels();
    if(prevProps.params.modelId!=this.props.params.modelId) {
      this.handleAddMode();
    }
  }
  handleAddMode() {
    const {params: {modelName, modelId}, getItem, initialize} = this.props;
    this.addMode = !modelId;
    if(!this.addMode) getItem(modelName, modelId).then(response=>{
      initialize('TabsForm', response.value.data);
    })
  }
  getSubmodels() {
    const {models, params: {modelName}, getList, list} = this.props;
    if(models && !this.gotSubmodels) {
      this.gotSubmodels = true;
      const submodels = [];
      models[modelName].structure.edit.forEach(item=>{
        item.fields.forEach(fieldName=>{
          const model = models[modelName].fields[fieldName].config.edit.model;
          if(model && !submodels.includes(model)) submodels.push(model);
        });
      });
      submodels.forEach(submodel=>{
        getList(submodel);
      });
    }
  }
  onSubmit(formData) {
    const {params: {modelName}, createItem, updateItem, initialize, stopAsyncValidation, item} = this.props;
    const catchErr = (err)=>{
      stopAsyncValidation('TabsForm', generateValidationErrors(err.message.invalidAttributes));
    };
    const modelPK = getModelPrimaryKey(modelName);
    if(this.addMode) {
      createItem(modelName, formData).then(response=>{
          toastr.success('Created successfully');
          this.context.router.push({name: 'ModelEdit', params: {modelName, modelId: response.value.data[modelPK]}})
      }).catch(catchErr);
    } else {
      updateItem(modelName, item[modelPK], formData).then(response=>{
        toastr.success('Updated successfully');
        if(item[modelPK]!=formData[modelPK]) {
          this.context.router.push({name: 'ModelEdit', params: {modelName, modelId: response.value.data[modelPK]}})
        } else initialize('TabsForm', response.value.data)
      }).catch(catchErr);
    }
  }
  onChangeBuild(values) {
    const {models, params: {modelName}} = this.props;
    if(models) {
      const buildableFields = [];
      models[modelName].structure.edit.forEach(item=>{
        item.fields.forEach(fieldName=>{
          const build = models[modelName].fields[fieldName].config.edit.build;
          if(build) buildableFields.push({...build, fieldName})
        });
      });
      buildableFields.forEach(build=>{
        formFieldBuild('TabsForm', build, values)
      });
    }
  }
}
<file_sep>/admin/src/core/utils/components/ContentComponents/TabsForm/TabsFormComponent.js
import React from 'react';
import {connect} from 'react-redux';
import {reduxForm} from 'redux-form';
import {SubmitButton, ResetButton, ReturnButton} from 'FormComponents';
import {Tabs, Tab} from 'react-bootstrap';
import {Validate} from '../../FormComponents';


/**
 * Tabs Form Component
 * @description Creates a Redux From with a Tab Set and creates validation for each tab
 * @prop:validate The validation function used be Redux Form
 * @prop:list The list of Tabs
 * @prop:onSubmit The onSubmit function used when clicking on Submit Button
 */
@connect(state=>({
  tabListErrors: state.form.TabsForm && state.form.TabsForm.syncErrors && state.form.TabsForm.syncErrors.tabList
}))
@reduxForm({
  form: 'TabsForm',
  validate: (values, props) => {
    if(props.validationRules) return Validate(props.validationRules)(values);
  },
  shouldValidate: ({values, props}) => {
    if(props.onChangeBuild) props.onChangeBuild(values);
    return true;
  }
})
export default class TabsForm extends React.Component {

  renderTab(item, k) {
    const {tabListErrors, submitFailed} = this.props;
    const isInvalid = tabListErrors && tabListErrors[k];
    const tabName = isInvalid && submitFailed ?
      <span>{item.name}<i className="tf-invalid fa fa-exclamation"/></span> : item.name;
    return <Tab key={k} eventKey={k} title={tabName}>{item.component}</Tab>
  }

  render() {
    const {handleSubmit, onSubmit, reset, getLoading, saveLoading, list, returnRoute } = this.props;
    const tabId = this._reactInternalInstance._debugID;
    return (
      <form onSubmit={handleSubmit(onSubmit)}>
        <fieldset disabled={saveLoading}>
          <Tabs defaultActiveKey={0} id={tabId} className="admin-container">
            { list.map(this.renderTab.bind(this)) }
          </Tabs>
        </fieldset>
        <div className="nav-tab-btn-container">
          <ReturnButton route={returnRoute} />
          <ResetButton reset={reset} />
          <SubmitButton loading={saveLoading}>Save</SubmitButton>
        </div>
        { getLoading && <Loader /> }
      </form>
    )
  }

}
<file_sep>/api/controllers/Image/ImageResize.js
const fs = require('fs');
const path = require('path');
const imageMinify = require('./ImageMinify');
const sharp = require('sharp');
const uploadsPath = typeof sails != 'undefined' ? sails.config.uploadsPath : '';


module.exports = function(res, imagePath, resizeName, cb) {
  if(!imagePath) return res.badRequest('Image resize: no image path provided');
  if(!sails.config.imageSizes[resizeName]) return res.badRequest('Image resize: the image size "'+resizeName+'" does not exists.');
  if(!fs.existsSync(path.join(uploadsPath, imagePath))) return res.badRequest('The original image has been removed from server.');

  const {width, height, crop, quality=100, watermark} = sails.config.imageSizes[resizeName];
  const extname = path.extname(imagePath);
  const fullImagePath = path.join(uploadsPath, imagePath);
  const resizeImagePath = imagePath.replace(extname, '-'+resizeName+extname);
  const resizeFullImagePath = fullImagePath.replace(extname, '-'+resizeName+extname);

  // create image resize object
  const image = sharp(fullImagePath).resize(width, height);

  // choose resize type
  if(crop) image.crop();
  else image.max();

  // output the object to file
  image.toFile(resizeFullImagePath, function (err) {
    if(err) return res.negotiate(err);

    // minify the file
    imageMinify(resizeFullImagePath, uploadsPath, quality).then(function () {

      // return the image path and size
      return cb(resizeImagePath, width + 'x' + height);
    })

  })

};
<file_sep>/front/src/modules/Article/ArticleBrowseScreen.js
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'core/decorators';
import { getPagedList } from './ArticleActions'
import { listenToLocation } from 'core/utils/helpers';
import { I18n } from 'react-redux-i18n'
import {PageHeader, Link, Pagination} from 'core/components/content';
import ArticleItem from './templates/Item/ArticleItem';
import Sidebar from './templates/ArticleSidebar';
import './Article.scss';
import {setSsrRequired, setMetaTags} from 'core/Main/MainActions';


@connect(state => ({
  articleList: state.article.list,
  totalPages: state.article.totalPages,
  loading: state.article.loading
}), {
  getPagedList,
  setSsrRequired,
  setMetaTags
})
export default class ArticleBrowseScreen extends React.Component {

  static contextTypes = {
    router: PropTypes.object.isRequired
  };

  componentWillMount() {
    const {setSsrRequired} = this.props;
    setSsrRequired(['article.list', 'article.totalPages']);
    this.getItemList();
    this.unlistenToLocation = listenToLocation(() => {
      this.getItemList();
    }, this);
  }

  getItemList() {
    const { getPagedList, params, location: {query}, setMetaTags } = this.props;
    const requestParams = {...query, ...params};
    getPagedList(requestParams).then(() => {
      setMetaTags({
        title: I18n.t(`article.browse.${params.tagSlug ? 'titleTag' : 'title'}`, {tag: params.tagSlug && params.tagSlug.replace(/(?:-)/g, ' ')})
        // description: I18n.t('article.browse.metaDescription')
      });
    });
  }


  render() {
    const { articleList, totalPages, params: {tagSlug} } = this.props;
    return (
      <div>

        <PageHeader title="Article List" />

        <div className="container margin-top-40 margin-bottom-80">
          <div className="layout">
            <div className="main">

              {tagSlug && (
                <div className="article-filter">
                  <span>Tags:</span>
                  <ul>
                    <li>
                      <span>TAG:</span>
                      <strong>{tagSlug.replace(/(?:-)/g, ' ')}</strong>
                      <Link to={{name: 'ArticleBrowse'}} className="button-wire icon-times-circle" />
                    </li>
                  </ul>
                </div>
              )}

              {articleList && (articleList.length ? (
                  <ul className="article-list">
                    {articleList.map(article=>(
                      <ArticleItem key={article.id} article={article} />
                    ))}
                  </ul>
                ) : (
                  <h2 className="text-center">{I18n.t('article.browse.noArticles')}</h2>
                ))}

              <div className="text-center">
                <Pagination totalPages={totalPages} />
              </div>

            </div>

            <Sidebar/>

          </div>
        </div>

      </div>
    );
  }

  componentWillUnmount() {
    this.unlistenToLocation();
  }

}<file_sep>/front/config/routes.js
var path = require('path');
var routes = {};

// CONTACT FORM
routes['POST /api/sendContactForm'] =        'ContactForm.sendContactForm';

// ARTICLE
routes['GET  /api/getPagedArticleList'] =    'ArticleController.getPagedList';
routes['GET  /api/getArticle/:slug'] =       'ArticleController.getArticle';
routes['GET  /api/getFeaturedArticleList'] = 'ArticleController.getFeaturedList';
routes['GET  /api/getTagList'] =             'ArticleTagController.getList';

// JOBS
routes['GET  /api/getPagedJobList'] =        'JobController.getPagedList';
routes['GET  /api/getJob/:slug'] =           'JobController.getJob';
routes['POST /api/signupForJob/:jobId'] =    'JobSignupController.signup';

module.exports.routes = routes;
<file_sep>/front/src/core/components/content/Filter/Search/FilterSearchComponent.js
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'core/decorators';
import debounce from 'debounce';
import {setQueryParam} from 'core/utils/helpers';
import {I18n} from 'react-redux-i18n';


@connect((state, {query, param})=>({
  value: query && query[param||'search'] || state.main.location.query[param||'search']
}))
export default class FilterSearchComponent extends React.Component {

  static propTypes = {
    query: PropTypes.object,
    className: PropTypes.string,
    param: PropTypes.string,
    placeholder: PropTypes.string,
    icon: PropTypes.string
  };

  componentWillMount() {
    this.state = {search: this.props.value};
  }

  search(value) {
    this.setState({search: value});
    this.debounceSearch(value);
  }

  debounceSearch = debounce(value=>{
    const {beforeSearch, param} = this.props;
    if(typeof beforeSearch == 'function') beforeSearch();
    this.setState({search: value});
    setQueryParam(param||'search', value||null);
  }, 300);

  render() {
    const {placeholder, value, param, icon} = this.props;
    const {search} = this.state;
    return (
      <div className="search-group search-input">
        <div>
          <input className="form-control" type="text"
                 value={typeof search != 'undefined' ? search : value}
                 placeholder={placeholder||I18n.t('message.searchPlaceholder')}
                 onChange={e=>this.search(e.target.value)} />
        </div>
        {(search || value) && (
          <button onClick={()=>{this.setState({search: ''});setQueryParam(param||'search', null)}}>×</button>
        )}
        {icon && <i><span className={icon} /></i>}
      </div>
    )
  }
}<file_sep>/api/models/Media.js
module.exports = {

  config: {
    defaultField: 'filename',
    crud: {create: false}
  },

  structure: {
    list: ['filename', 'fileSize', 'extension'],
    edit: [
      {fields: ['filename', 'fileSize', 'extension']}
    ]
  },

  attributes: {
    filename: {
      type: 'string',
      required: true
    },
    path: {
      type: 'string',
      required: true
    },
    fileSize: {
      type: 'string',
      required: true
    },
    type: {
      type: 'string',
      required: true
    },
    extension: {
      type: 'string',
      required: true
    }
  },

  afterDestroy: function (destroyedFiles, cb) {
    const fs = require('fs');
    const path = require('path');
    destroyedFiles.forEach(file => {
      const filePath = path.join(sails.config.uploadsPath, file.path);
      fs.stat(filePath, function (err) { // destroy the phisical image from server if it exist
        if (!err) fs.unlink(filePath);
      });
    });
    cb();
  }

};

<file_sep>/admin/src/core/utils/components/ContentComponents/PageHeader/PageHeaderComponent.js
import React from 'react';

require('./PageHeader.scss');

export default React.createClass({
  render() {
    const { children} = this.props;
    return (
      <div className="page-header">
        <div className="container">
          <h1>{children}</h1>
        </div>
      </div>
    )
  }
});
<file_sep>/front/src/core/components/form.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Field as ReduxField} from 'redux-form';
import {I18n} from 'react-redux-i18n';
import {Link} from 'react-router';
import _ from 'lodash';
import {stringToObjectValue, setObjectDeepValue} from 'core/utils/helpers';
import classnames from 'classnames';
import Check from './form/Check/CheckComponent';
import Radio from './form/Radio/RadioComponent';
import DateTime from './form/DateTime/DateTimeComponent';
import Select from './form/Select/SelectComponent'
import File from './form/File/FileComponent'



/**
 * The Generic Field
 * @description An Interface for Form Field
 */
export class Field extends Component {

  static propTypes = {
    name: PropTypes.string.isRequired,
    type: PropTypes.string
  };

    renderFieldGroup({input, type, label, placeholder, onClick, disabled, replaceValue, replaceChecked, isLoading, autoFocus, icon, meta: {touched, error, form}, className, ...rest}) {

    input.placeholder = placeholder;
    input.className = 'form-control';
    input.onClick = onClick;
    input.disabled = disabled;
    input.value = typeof replaceValue != 'undefined' ? replaceValue : input.value;
    input.checked = typeof replaceChecked != 'undefined' ? replaceChecked : input.checked;
    input.autoFocus = autoFocus;

    if (['checkbox', 'radio'].includes(type)) return (
      <label
        className={classnames('field-group', className, `fg-name-${input.name}`, `fg-type-${type}`, {'fg-invalid': error, 'fg-touched': touched})}>
        <strong>{ renderField() }</strong>
        <div>
          { label }
          { touched && error && <span className="field-error">{error}</span> }
        </div>
      </label>
    );

    return (
      <div
        className={classnames('field-group', className, `fg-name-${input.name}`, `fg-type-${type}`, {'fg-invalid': error, 'fg-touched': touched})}>
        { label && (
          <label>
            {label}
            {icon && <i className={icon} />}
          </label>
        ) }
        <div>
          { renderField() }
          { touched && error && <span className="field-error">{error}</span> }
        </div>
      </div>
    );

    function renderField() {

            const inputTypes = ['text', 'password', 'email', 'phone', 'hidden', 'number', 'tel' ];
            type = type || inputTypes[0];
            if (inputTypes.includes(type)) return <input type={type} {...input} {...rest} />; // for all fields in inputFields constant
            else if (type == 'display') return input.value;
            else if (type == 'checkbox') return <Check {...input} {...rest} />;
            else if (type == 'file') return <File {...input} {...rest} form={form} />;
            else if (type == 'radio') return <Radio {...input} {...rest} />;
            else if (type == 'textarea') return <textarea {...input} />;
            else if (type == 'select')return <Select isLoading={isLoading} {...input} {...rest}/>;
            else if (type == 'datetime') return <DateTime {...input} {...rest} />;
        }

  }

  render() {
    return <ReduxField component={this.renderFieldGroup} {...this.props} />;
  }
}


/**
 * FORM SUBMIT BUTTON
 */
export class SubmitButton extends Component {
  static propTypes = {
    children: PropTypes.node.isRequired,
    className: PropTypes.string,
    loading: PropTypes.bool,
    disabled: PropTypes.bool
  };
  render() {
    const {disabled, loading, children, className} = this.props;
    return (
      <button type="submit" className={className} disabled={disabled||loading}>
        { loading && <span className="button-spinner"><i className="icon-spinner icon-spin"/></span> }
        { children }
      </button>
    );
  }
}

/**
 * FORM DELETE BUTTON
 */

export class DeleteButton extends Component {
  render() {
    return (
      <button type="button" className="button" onClick={this.props.onDelete}>
        { this.props.children || I18n.t('button.delete') }
      </button>
    )
  }
}


/**
 * FORM RESET BUTTON
 */
export class ResetButton extends Component {
  render() {
    return (
      <button type="button" className="button" onClick={this.props.reset}>
        { this.props.children || I18n.t('button.reset') }
      </button>
    );
  }
}

/**
 * FORM RETURN BUTTON
 */
export class ReturnButton extends Component {
  render() {
    return (
      <Link className="button" to={this.props.route}>
        <i className="icon-arrow-left"/>
        { this.props.children || I18n.t('button.return') }
      </Link>
    );
  }
}


/**
 * VALIDATION FUNCTION
 * @description Returns validate function required for Redux Form
 * @param:config The configuration object for generating the rules
 */
export function Validate(config) {

  let fields = config.fields || config;
  let tabList = config.tabList || [];

  // Warning - every validation that targets a FieldArray must be inserted here
  const arrayValidationRules = ['surveyValid', 'propsInArrayPropRequired'];

  /**
   * VALIDATION RULES
   */
  let rules = {
    required: function (fieldValue) {
      return !fieldValue;
    },
    wysiwygRequired: function (fieldValue) {
      return !fieldValue || fieldValue == '<p><br></p>' || fieldValue.trim() == '';
    },
    arrayRequired: function (fieldValue) {
      if (!fieldValue) return false;
      return fieldValue.some(arrayItem=>!arrayItem);
    },
    propsInArrayRequired: function (fieldValue) {
      if (!fieldValue) return false;
      return fieldValue.some(arrayItem=> {
        let arrayInvalid = true;
        _.forEach(arrayItem, propValue=> {
          if (propValue) arrayInvalid = false;
        });
        return arrayInvalid;
      });
    },
    propsInArrayPropRequired: function (fieldValue, prop) {
      if (!fieldValue) return false;
      return fieldValue.some(arrayItem=> {
        let arrayInvalid = true;
        _.forEach(arrayItem[prop], propValue=> {
          if (propValue) arrayInvalid = false;
        });
        return arrayInvalid;
      });
    },
    minLength: function (fieldValue, validationValue) {
      if (!fieldValue) return false;
      return fieldValue.length < validationValue;
    },
    maxLength: function (fieldValue, validationValue) {
      if (!fieldValue) return false;
      return fieldValue.length >= validationValue;
    },
    integer: function (fieldValue) {
      if (!fieldValue) return false;
      return typeof fieldValue == 'number';
    },
    email: function (fieldValue) {
      if (!fieldValue) return false;
      let emailRegexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return !emailRegexp.test(fieldValue);
    }
  };


  /**
   * Redux Form validate function returned
   */
  return function (values) {
    const errors = {};

    // validate field function
    let validateField = function (fieldName, validations) {
      let isInvalid, validationType;
      let validationKeys = Object.keys(validations);
      for (let x = 0; x < validationKeys.length; x++) {
        validationType = validationKeys[x];
        let validationValue = validations[validationType];
        if (rules[validationType]) {
          let fieldValue = stringToObjectValue(fieldName, values);
          isInvalid = rules[validationType](fieldValue, validationValue, values);
          if (isInvalid) break;
        }
      }
      return isInvalid ? validationType : false;
    };

    // validate all fields
    _.forEach(fields, (validations, fieldName) => {
      let isInvalid = validateField(fieldName, validations)
      if (isInvalid) {
        const validationMessage = arrayValidationRules.includes(isInvalid) ? {_error: I18n.t('validation.' + isInvalid, {fieldName})} : I18n.t('validation.' + isInvalid, {fieldName});
        setObjectDeepValue(errors, fieldName, validationMessage);
      }
    });

    // validate tabList
    tabList.forEach((tabFields, tabIndex) => {
      errors.tabList = errors.tabList || {};
      let isTabInvalid = false;
      tabFields.forEach(tabField => {
        if (validateField(tabField, fields[tabField])) {
          isTabInvalid = true;
        }
      });
      errors.tabList[tabIndex] = isTabInvalid
    });

    return errors;
  }

}
<file_sep>/front/webpack/loaders/asset.js
const path = require('path');

module.exports = function () {
  this.cacheable();
  const issuerContext = this._module && this._module.issuer && this._module.issuer.context || this.options.context;
  const relativeUrl = issuerContext && path.relative(issuerContext, this.resourcePath).split(path.sep).join('/');
  const filename = relativeUrl && path.basename(relativeUrl);
  return 'module.exports = \'assets/'+filename+'\';';
};
module.exports.raw = true;<file_sep>/admin/src/modules/Dashboard/templates/DashboardResultsComponent.js
import React from 'react';
import { connect } from 'react-redux';
import Select from 'react-select';
import classnames from 'classnames';
import { getResults, clearResults } from '../DashboardActions'
import { I18n } from 'react-redux-i18n';
import Round1Results from './DashboardRound1Results';
import Round2Results from './DashboardRound2Results';
import Round3Results from './DashboardRound3Results';

const DashboardResultsComponent = React.createClass({

  updateParams: function(key, value) {
    if(!(key && typeof value != 'undefined')) return;
    var query = { ...this.props.location.query, [key]: value };
    if(!query[key]) delete query[key];
    if(['session', 'round'].includes(key)) this.props.clearResults();
    this.props.router.push({name: 'Dashboard', query});
  },

  getResults(location) {
    location = location || this.props.location;
    if(!location.query.session || !location.query.round) return;
    this.props.getResults(location.query);
  },

  render() {
    const { sessionList, location, results, error } = this.props;
    const selectedSession = sessionList.filter(i=>i.id==location.query.session)[0];
    const selectedRound = location.query.round;
    return (
      <div className="dashboard-panel results-panel">
        <h2>Rezultate sesiuni per runda</h2>
        <div className="rp-header">
          <div>
            <Select options={sessionList} labelKey="edition" value={selectedSession} onChange={i=>this.updateParams('session', i ? i.id : null)} />
          </div>
          <div>
            <span>Runda</span>
            { [1,2,3].map(k => <button type="button" key={k} className={classnames('button', {'active': selectedRound==k})} onClick={()=>this.updateParams('round', k)}>{k}</button>) }
          </div>
          <div>
            <button type="button" className="button" onClick={()=>this.getResults()}>Generează</button>
          </div>
          <div>
            <label>
              <input type="checkbox" checked={location.query.autoGenerate} onChange={e=>this.updateParams('autoGenerate', e.target.checked)} />
              Auto-generare (5 sec.)
            </label>
          </div>
        </div>
        <div className="rp-content">

          { location.query.session && selectedRound && results ? (
            <div>

              { selectedRound == 1 && <Round1Results results={results} /> }
              { selectedRound == 2 && <Round2Results results={results} /> }
              { selectedRound == 3 && <Round3Results results={results} /> }

            </div>
          ) : (error || null) }


        </div>
      </div>
    )
  },

  componentWillUnmount() {
    clearInterval(this.autoGenerateCheck);
  }

});

export default connect(state=>({
  results: state.dashboard.results,
  error: state.dashboard.error
}), {
  getResults,
  clearResults
})(DashboardResultsComponent);
<file_sep>/admin/src/Main/MainUploadService.js
import axios from 'axios';

export function uploadImage(file) {
  const data = new FormData();
  data.append('image', file);
  return axios.post('/image/upload', data)
}
<file_sep>/admin/src/core/utils/components/ContentComponents/Modal/ModalReducer.js
const initialState = {
  isOpened: false
};

export default function (state = initialState, action) {
  switch (action.type) {
    case 'modalOpen':
      return {
        ...state,
        isOpened: true,
        config: action.payload.config,
        params: action.payload.params
      };
    case 'modalClose':
      return initialState;
    case 'modalStateChange':
      return {
        ...state,
        ...action.payload
      };
    default:
      return state;
  }
}
<file_sep>/front/src/core/components/content/Filter/FilterComponent.js
import Check from './Check/FilterCheckComponent';
import Date from './Date/FilterDateComponent';
import Search from './Search/FilterSearchComponent';
import Select from './Select/FilterSelectComponent';

export default {
  Check,
  Date,
  Search,
  Select
};<file_sep>/admin/src/core/utils/components/FormComponents/JSONValues/templates/JSONValuesListTemplate.js
import React from 'react';
import ContentEditable from './ContentEditable';


export default class JSONValuesListTemplate extends React.Component {

  static propTypes = {
    object: React.PropTypes.bool
  };

  render() {
    const {value, object, renderField} = this.props;
    return (
      <div className="child">
        {value && !!value.length && (
          <div>
            {object ? (
              value.map(this.renderField.bind(this))
            ) : (
              value.map(this.renderItem.bind(this))
            )}
          </div>
        )}
        <button type="button" onClick={this.addRow.bind(this)}>Add</button>
      </div>
    )
  }

  renderItem(item, k) {
    const {value: fieldList} = this.props;
    return (
      <div key={k} className="item">
        <span>"</span>
        <ContentEditable value={item} onChange={e=>this.onChange(k, e.target.value)} />
        <span>"{k !== fieldList.length - 1 && ','}</span>
        <div>
          <button type="button" onClick={()=>this.removeRow(k)}>x</button>
        </div>
      </div>
    )
  }

  renderField(rootValue, k) {
    const {value: fieldList, parent, keys, renderField} = this.props;
    return (
      <div key={k}>
        <span>&#123;</span>
        {keys.map((item, i)=> {
          let value;
          try {
            value = eval(`rootValue.${item.name}`);
          } catch(err) {}
          return (
            <div key={i} className="child">
              {renderField(item, value, `${parent}[${k}]`, i === keys.length - 1)}
            </div>
          );
        })}
        <span>&#125;{k !== fieldList.length - 1 && ','} <button type="button" onClick={()=>this.removeRow(k)}>x</button></span>
      </div>
    );
  }

  addRow() {
    const {value, onChange, object} = this.props;
    const newValue = value ? [...value] : [];
    newValue.push(object ? {} : '');
    onChange(newValue);
  }

  removeRow(index) {
    const {value, onChange} = this.props;
    const newValue = [...value];
    newValue.splice(index, 1);
    onChange(newValue);
  }

  onChange(index, targetValue) {
    const {value, onChange} = this.props;
    const newValue = [...value];
    newValue[index] = targetValue;
    onChange(newValue);
  }

}<file_sep>/front/config/env/development.js
module.exports = {

  host: 'localhost',
  port: 9000,

  publicUrl: 'http://localhost:9000'

};<file_sep>/front/src/core/components/content/SelectFilter/SelectFilterComponent.js
import React from 'react';
import {connect} from 'core/decorators';
import {setQueryParam} from 'core/utils/helpers';
import Select from 'react-select';
import './SelectFilter.scss';


@connect(state=>({
  query: state.main.location.query
}))
export default class SelectFilterComponent extends React.Component {

  render() {
    const {query, className, param, ...props} = this.props;
    return (
      <Select {...props}
              openOnFocus={true}
              autoBlur={true}
              className={className||'Select--filter'}
              value={query[param]}
              onChange={this.handleChange.bind(this)} />
    )
  }

  handleChange(item) {
    const {param, valueKey} = this.props;
    if(item) setQueryParam(param, item[valueKey||'value']);
    else setQueryParam(param, null);
  }

}<file_sep>/admin/src/modules/MyAccount/i18n/MyAccountI18nRo.js
module.exports = {
    admin: {
        specialPermissions: {
            title: 'Permisiuni Speciale'
        }
    },
    button: {
        announcement : {
            add: 'Adauga anunt'
        }
    }
};
<file_sep>/front/src/core/components/content/Filter/Select/FilterSelectComponent.js
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'core/decorators';
import {setQueryParam} from 'core/utils/helpers';
import {I18n} from 'react-redux-i18n';
import Select from 'react-select-plus';
import classnames from 'classnames';


@connect((state, {query, param, valueKey, labelKey})=>({
  value: query && query[param] || state.main.location.query[param],
  valueKey: valueKey || 'value',
  labelKey: labelKey || 'label'
}))
export default class FilterSelectComponent extends React.Component {
  static propTypes = {
    query: PropTypes.object,
    className: PropTypes.string,
    param: PropTypes.string.isRequired,
    placeholder: PropTypes.string,
    icon: PropTypes.string
  };
  render() {
    const {className, placeholder, param, icon, labelKey, valueKey, ...props} = this.props;
    return (
      <div className={classnames('search-group search-select', className)}>
        <Select {...props}
                value={this.getValue()}
                onChange={data=>setQueryParam(param, data && data[valueKey])}
                placeholder={placeholder||I18n.t('message.searchPlaceholder')}
                labelKey={labelKey}
                valueKey={valueKey}
        />
        {icon && <i><span className={icon} /></i>}
      </div>
    )
  }
  getValue() {
    const {options, valueKey, value} = this.props;
    let result = null;
    if(options && value) options.forEach(item => {
      if(item[valueKey] == value) result = item;
      if(item.options) item.options.forEach(item=>{
        if(item[valueKey] == value) result = item;
      })
    });
    return result;
  }
}<file_sep>/front/src/core/components/content/Map/MapComponent.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import GoogleMap from 'react-google-maps/lib/GoogleMap';
import withGoogleMap from 'react-google-maps/lib/withGoogleMap';
import withScriptjs from 'react-google-maps/lib/async/withScriptjs';
import Marker from 'react-google-maps/lib/Marker';


const AsyncGoogleMap = withScriptjs(
  withGoogleMap(props => (
    <GoogleMap {...props}>{props.markers.map(marker => <Marker {...marker} />)}</GoogleMap>
  ))
);

export default class MapComponent extends Component {
  static propTypes = {
    className: PropTypes.string,
    height: PropTypes.number,
    lat: PropTypes.number,
    lng: PropTypes.number,
    markers: PropTypes.array,
    center: PropTypes.object,
    zoom: PropTypes.number,
    stylers: PropTypes.array,
    scrollWheel: PropTypes.bool
  };
  render() {
    const {
      className = 'map-field',
      height=220,
      lat,
      lng,
      markers = [{position: {lat, lng}, key: new Date().getTime()}],
      center = {lat, lng},
      zoom = 14,
      stylers,
      onMapClick = ()=>{},
      scrollWheel = true
    } = this.props;
    if(markers && !(markers.length ? (markers[0].position && markers[0].position.lat && markers[0].position.lng) : true)) return null;
    if(!(center && center.lat && center.lng)) return null;
    return (
      <AsyncGoogleMap
        googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<KEY>"
        loadingElement={<span />}
        containerElement={<div className={className} />}
        mapElement={<div style={{height}}/>}
        markers={markers}
        center={center}
        defaultZoom={zoom}
        onMapClick={onMapClick}
        defaultOptions={{ styles: [{stylers}], scrollwheel: scrollWheel }}
      />
    )
  }
}<file_sep>/front/api/controllers/ContactFormController.js
module.exports = {
  sendContactForm: function (req, res) {

    const notificationEmails = ['<EMAIL>'];
    if (sails.config.environment !== 'development') notificationEmails.push('<EMAIL>');
    const signupData = req.body;
    const content = Object.keys(signupData).map(fieldName => {
      return `<p><strong>${fieldName}</strong>: ${signupData[fieldName].replace(/(?:\r\n|\r|\n)/g, '<br />')}</p>`
    }).join('');

    Promise.all([
      ContactForm.create(signupData),
      MailService.sendMail({
        from: 'Gentlab Admin Panel <<EMAIL>>',
        to: notificationEmails.join(','),
        subject: '[Gentlab] New contact form filled',
        content
      })
    ]).then(() => {
      res.ok();
    }).catch(err => {
      res.negotiate(err);
    });

  }
};
<file_sep>/front/src/core/Main/Main.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'core/decorators';
import {setRouterParams, setMetaTags} from '../Main/MainActions';
import Layout from '../../Layout/Layout';
import {I18n} from 'core/components/content'


@connect(state=>({
  menuOpened: state.main.menuOpened,
  metaTags: state.main.metaTags,
  noBreadcrumb: state.main.noBreadcrumb
}), {
  setRouterParams,
  setMetaTags
})
export default class Main extends Component {

  static contextTypes = {
    router: PropTypes.object.isRequired
  };

  componentWillMount() {
    const {location, params, routes, routeParams, setRouterParams, setMetaTags} = this.props;
    setRouterParams({
      location: {...location, query: location.query || {}},
      params: {...params},
      routes: [...routes],
      routeParams: {...routeParams},
      router: this.context.router
    });
    const routeName = routes[routes.length - 1].name;
    setMetaTags({
      title: I18n.t(`${routeName}.metaTitle`),
      description: I18n.t(`${routeName}.metaDescription`),
    });
    this.unlistenToRouter = this.context.router.listen(location => {
      setRouterParams({location: {...location, query: location.query || {}}});
    });
  }

  render() {
    const {children} = this.props;
    return (
      <Layout>{children}</Layout>
    );
  }

  componentWillUnmount() {
    console.log('unmounting');
    this.unlistenToRouter();
  }

}<file_sep>/admin/src/core/security/Login/LoginReducer.js
const initialState = {
  fields: {},
  message: null,
  loggedIn: false,
  redirectRoute: null
};

export default function(state = initialState, action) {
  switch(action.type) {
    case 'login_loading':
      return {...state,
        message: null,
        loggedIn: false,
        loading: true
      };
    case 'login_success':
      return {...state,
        message: {t: 'login.successMessage', type: 'success'},
        loggedIn: true,
        loading: false
      };
    case 'login_error':
      return {...state,
        message: {t: 'login.errorCredentialsMessage', type: 'error'},
        loggedIn: false,
        loading: false
      };
    case 'setRedirectRoute':
      return {...state,
        redirectRoute: action.payload
      };
    case 'login_reset':
      return initialState;
    default:
      return state;
  }
}
<file_sep>/admin/src/modules/Model/templates/Import/ModelImportConfigRow.js
import React from 'react';
import {connect} from 'react-redux';
import {getList} from '../../ModelActions';
import moment from 'moment';


@connect((state, props)=>({
  importExport: state.main.models[state.main.params.modelName].structure.importExport,
  modelFields: state.main.models[state.main.params.modelName].fields,
  list: state.model.list
}), {
  getList
})
export default class ModelImportConfigRow extends React.Component {

  timeFormatList = [
    {display: '19:00', value: 'H:mm'},
    {display: '7:00PM', value: 'h:mmA'},
    {display: '07PM', value: 'hhA'}
  ];

  dateFormatList = [
    {display: moment().format('MM/DD/YYYY'), value: 'MM/DD/YYYY'},
    {display: moment().format('M/D/YY'), value: 'M/D/YY'},
    {display: moment().format('DD.MM.YYYY'), value: 'DD.MM.YYYY'},
    {display: moment().format('D.M.YY'), value: 'D.M.YY'},
    {display: moment().format('YYYYMMDD'), value: 'YYYYMMDD'}
  ];

  componentWillMount() {
    const {modelFields, fieldName, getList, importItems, sheetName, setFilter} = this.props;
    const config = modelFields[fieldName].config.importExport;
    if(config.parent) getList(config.parent.model);

    // auto find template
    if(importItems[sheetName].length>1) {
      if(['date', 'time'].includes(config.template)) {
        const formatList = this[config.template=='time' ? 'timeFormatList' : 'dateFormatList'].map(i=>i.value);
        const defaultValue = moment(importItems[sheetName][1][fieldName], formatList).creationData().format;
        if(defaultValue) {
          setTimeout(()=>{
            setFilter(config.template=='time' ? 'timeFormat' : 'dateFormat', defaultValue)
          })
        }
      }
    }

  }
  render() {
    const {fieldName, importItems, sheetName, k} = this.props;
    return (
      <tr key={fieldName}>
        <td>{fieldName}</td>
        <td>{importItems[sheetName][0][k] || 'No header'}</td>
        <td>{importItems[sheetName][1][k] || 'No data'}</td>
        <td>{this.renderConfig()}</td>
      </tr>
    )
  }
  renderConfig() {
    const {modelFields, list, fieldName, filter, setFilter} = this.props;
    const config = modelFields[fieldName].config.importExport;

    if(config.parent && list[config.parent.model]) {
      return (
        <select value={filter.parent.object} onChange={e=>console.log('e', e.target)}>
          <option value="" disabled>-- Select --</option>
          {list[config.parent.model].map((item, k)=>(
            <option key={k} value={k}>{item.name}</option>
          ))}
        </select>
      )
    } else if(config.template == 'date') {
      return (
        <select value={filter.dateFormat} onChange={e=>setFilter('dateFormat', e.target.value)}>
          <option value="" disabled={filter.dateFormat}>-- Select --</option>
          {this.dateFormatList.map((item, k)=>(
            <option key={k} value={item.value}>{item.display}</option>
          ))}
        </select>
      )
    } else if(config.template == 'time') return (
      <select value={filter.timeFormat} onChange={e=>setFilter('timeFormat', e.target.value)}>
        <option value="" disabled={filter.dateFormat}>-- Select --</option>
        {this.timeFormatList.map((item, k)=>(
          <option key={k} value={item.value}>{item.display}</option>
        ))}
      </select>
    )
  }
}
<file_sep>/front/config/local.js
const path = require('path');
const {host, port, publicUrl} = require(`./env/${process.env.NODE_ENV == 'production' ? 'production' : 'development'}.js`);

module.exports = {

  host,
  port,
  publicUrl,

  siteName: 'Site Starter',
  uploadsPath: path.join(__dirname, '../uploads')

};
<file_sep>/front/src/Layout/MobileMenu/MobileMenu.js
import React from 'react';
const BurgerMenu = require('react-burger-menu').push;
import {Navigation} from 'core/components/content';
import {connect} from 'core/decorators';
import {closeMenu, openMenu, renderMenu} from 'core/Main/MainActions';
import classnames from 'classnames';
import {mainMenu} from '../LayoutConstants';
import './MobileMenu.scss';


@connect(state=>({
  opened: state.main.menuOpened,
  rendered: state.main.menuRendered,
  router: state.main.router
}), {
  closeMenu,
  openMenu,
  renderMenu
})
export default class MobileMenu extends React.Component {
  componentWillMount() {
    this.removeListener = this.props.router.listen(()=>this.props.closeMenu())
  }
  componentWillReceiveProps({opened, rendered, renderMenu, openMenu}) {
    if(!rendered && opened) {
      renderMenu();
      setTimeout(openMenu);
    }
  }
  handleStateChange(state) {
    if(!state.isOpen) this.props.closeMenu();
  }
  render() {
    const {opened, rendered} = this.props;
    if(!rendered) return null;
    return (
      <div className={classnames({'menu-opened': opened})}>
        <BurgerMenu pageWrapId="main" outerContainerId="app" isOpen={opened} onStateChange={this.handleStateChange.bind(this)}>
          <Navigation links={mainMenu} />
        </BurgerMenu>
      </div>
    );
  }
  componentWillUnmount() {
    this.removeListener();
  }
}<file_sep>/api/controllers/ImportController.js
module.exports = {

  check: function (req, res) {

    const params = req.params.all();
    const Model = req._sails.models[params.model.toLowerCase()];
    const promises = [];
    const response = {unique: {}, submodel: {}};

    _.forEach(params.unique, function(data, fieldName) {
      const where = {};
      where[fieldName] = data;
      //console.log('where', where);
      promises.push(Model.find({where: where, select: [fieldName]}));
    });
    _.forEach(params.submodel, function(data, fieldName) {
      const submodelName = Model.attributes[fieldName].model;
      const selectedField = Model.attributes[fieldName].config.importExport.selectedField;
      const Submodel = req._sails.models[submodelName.toLowerCase()];
      const where = {};
      where[selectedField] = data;
      promises.push(Submodel.find({where: where}));
    });

    Promise.all(promises).then(function(responses) {
      let i = 0;
      _.forEach(params.unique, function(data, fieldName) {
        response.unique[fieldName] = [];
        responses[i].forEach(function(item) {
          response.unique[fieldName].push(item[fieldName]);
        });
        i++;
      });
      _.forEach(params.submodel, function(data, fieldName) {
        const selectedField = Model.attributes[fieldName].config.importExport.selectedField;
        responses[i].forEach(function(item) {
          if(data.indexOf(item[selectedField])>-1) data.splice(data.indexOf(item[selectedField]), 1);
        });
        response.submodel[fieldName] = data;
        i++;
      });
      res.ok(response);
    });

  },

  getCategoryObjects: function (req, res) {

    const params = req.params.all();
    const finalObjects = {};

    _.forEach(params, function (data, fieldName) {

      const Model = req._sails.models[data.model.toLowerCase()];
      const modelFields = HelperService.getParsedModelData(Model).fields;
      const selectedField = data.fieldName || 'name';
      finalObjects[fieldName] = [];

      // 1. Get all items with it's name
      const existentItems = [];
      const neededItems = [];
      const where = {};
      if(data.parent && data.parent.object && data.parent.object.slug && data.createIfNotFound && modelFields.slug) {
        const newItems = [];
        data.items.forEach(function(item) {
          newItems.push(data.parent.object.slug+'-'+titleToSlug(item));
        });
        where.slug = newItems;
      } else {
        where[selectedField] = data.items;
      }
      Model.find({where: where}).exec(function (err, foundItems) {
        if(err) return res.negotiate(err);

        foundItems.forEach(function (item) {
          existentItems.push(item[selectedField]);
          finalObjects[fieldName].push(item);
        });

        // 2. Get neededItems
        data.items.forEach(function (itemName) {
          if (existentItems.indexOf(itemName) == -1) {
            const item = {};
            let slugPrefix = '';
            item[selectedField] = itemName;

            if(data.parent && data.parent.object) {
              item[data.parent.fieldName] = data.parent.object.id;
              slugPrefix = data.parent.object.slug+'-';
            }
            // build slug
            if(modelFields.slug) item.slug = slugPrefix+titleToSlug(itemName);

            neededItems.push(item);
            //console.log('neededItems', neededItems)
          }
        });

        // 3. if there are neededItems, create them, if not, return final objects
        if(data.createIfNotFound && neededItems.length) {
          Model.create(neededItems).exec(function(err, createdItems) {
            if(err) return res.negotiate(err);
            createdItems.forEach(function (item) {
              finalObjects[fieldName].push(item);
            });
            return res.ok(finalObjects);
          });
        } else return res.ok(finalObjects);

      });
    });

  }

};

function titleToSlug(text) {
  return text.toLowerCase().replace(/ă/g, 'a').replace(/â/g, 'a').replace(/î/g, 'i').replace(/ș/g, 's').replace(/ț/g, 't').replace(/[^\w ]+/g, '').replace(/ +/g, '-');
}
<file_sep>/api/models/Role.js
module.exports = {

  structure: {
    list: ['display', 'key'],
    edit: [
      {fields: ['display', 'key']}
    ]
  },

  attributes: {
    display: {
      type: 'string',
      required: true,
    },
    key: {
      type: 'string',
      required: true,
      primaryKey: true,
      config: {
        edit: {build: {items: ['display'], filter: 'toCamelCase'}}
      }
    }
  },

  autoCreatedAt: false,
  autoUpdatedAt: false

};
<file_sep>/front/src/core/components/content/I18n/I18nComponent.js
import React from 'react';
import PropTypes from 'prop-types';
import {I18n} from 'react-redux-i18n';
import {Link} from 'react-router';

function element(Element) {
  return class Translation extends React.Component {

    static propTypes = {
      value: PropTypes.string.isRequired,
      params: PropTypes.object
    };

    render() {
      const {value, params, ...props} = this.props;
      return (
        <Element dangerouslySetInnerHTML={{__html: I18n.t(value, params)}} {...props}/>
      )
    }
  }
}

export default {
  div:        element('div'),
  span:       element('span'),
  h1:         element('h1'),
  h2:         element('h2'),
  h3:         element('h3'),
  h4:         element('h4'),
  h5:         element('h5'),
  h6:         element('h6'),
  a:          element('a'),
  p:          element('p'),
  blockquote: element('blockquote'),
  cite:       element('cite'),
  Link:       element(Link),
  t:          (...params)=>I18n.t(...params)
}<file_sep>/front/src/reducers.js
import ArticleReducer from './modules/Article/ArticleReducer';

export default {
  article: ArticleReducer
}
<file_sep>/front/src/core/config/server/createHistory.js
import { createMemoryHistory } from 'history';
import useNamedRoutes from 'use-named-routes';


export default function(routes) {
  return useNamedRoutes(createMemoryHistory)({ basename:'/', routes });
}<file_sep>/admin/src/core/security/ResetPassword/ResetPasswordActions.js
import { generateResetPasswordTokenResource } from './ResetPasswordResources'

export function generateResetPasswordToken(email) {
  return {type: 'generateResetPasswordToken', payload: new Promise((resolve, reject) => {
    generateResetPasswordTokenResource(email).then(response => {
      resolve(response.data);
    }, reject);
  })};
}
<file_sep>/front/src/core/components/content/Breadcrumbs/BreadcrumbsComponent.js
import React from 'react';
import Breadcrumbs from 'react-router-breadcrumbs';
import {connect} from 'core/decorators';
import {I18n} from 'react-redux-i18n';
import {Link} from 'react-router';
import classnames from 'classnames';
import './Breadcrumbs.scss';


@connect(state=>({
  routes: state.main.routes,
  params: state.main.params,
  state: state // this forces the component to re-render for custom title to be shown (eg: ForumItem => My Grea
}))
export default class BreadcrumbsComponent extends React.Component {
  render() {
    const { routes, params, className } = this.props;
    return (
      <Breadcrumbs className={classnames('breadcrumbs', className)}
                   routes={routes}
                   params={params}
                   resolver={this.resolver.bind(this)}
                   createSeparator={this.separator}
                   createLink={this.createLink}
      />
    )
  }
  resolver(key, text, routePath, route) {
    const {state} = this.props;
    if(route.breadcrumbResolve) return route.breadcrumbResolve(state) || <i className="icon-spin icon-spinner" />;
    return I18n.t(`route.${text}`);
  }
  separator(e, i) {
    return <span key={i}> > </span>;
  }
  createLink(link, key, text, index, routes) {
    const routeNumber = routes.filter(i=>!i.breadcrumbIgnore).length;
    if(index==routeNumber) return <strong key={key}>{text}</strong>;
    return <Link to={link} key={key}>{text}</Link>;
  }
}<file_sep>/front/src/modules/Article/i18n/ArticleI18nEn.js
module.exports = {
  route: {
    Article: 'Articles'
  },
  article: {
    breadcrumbLabel: 'Articles',
    slug: '/articles',
    tagSlug: '/',
    tagListTitle: 'Tag list',
    featuredArticles: 'Featured articles',
    noFeaturedArticles: 'No article found in this category',
    browse: {
      title: 'Latest news in IT',
      titleTag: 'Browse articles for %{tag}',
      metaDescription: 'Latest news in IT',
      pageTitle: 'Browse articles',
      searchPlaceholder: 'Filter articles',
      noArticles: 'No article was found in this category'
    },
    view: {
      title: 'Articles',
      breadcrumbLabel: '{{pageSubtitle}}'
    }
  }
};<file_sep>/api/controllers/MediaController.js
var utils = require('../services/utils/utils');

module.exports = {

    checkIfExists: function(filename, cb) {
        Media.findOne({filename: filename}).then(function(item) {
            cb(item ? true : false);
        })
    },

    getOriginalFilename: function(filename, cb) {
        var checkIfExists = this.checkIfExists;
        var getOriginalFilename = this.getOriginalFilename;
        checkIfExists(filename, function(exists) {
            if(exists) {
                filename = utils.riseCopyIndex(filename);
                getOriginalFilename(filename, cb)
            } else cb(filename);
        })
    },

    upload: function(req, res) {

        var uploadPath = sails.config.appPath+'/uploads';
        var path = require('path');
        var getOriginalFilename = this.getOriginalFilename;

        // UPLOAD THE FILE TO AMAZON
        req.file('file').upload({
            dirname: uploadPath,
            maxBytes: 5 * 1024 * 1024 // MB
        },function (err, uploadedFiles) {
            if (err) {
                if(err.code == "E_EXCEEDS_UPLOAD_LIMIT") return res.badRequest(err);
                return res.negotiate(err);
            }
            if (uploadedFiles.length === 0) return res.badRequest('No file was uploaded');
            var file = uploadedFiles[0];
            var extension = path.extname(file.fd).replace('.', '');
            var imagePath = file.fd.replace(uploadPath, '');


            getOriginalFilename(file.filename, function(filename) {
                var fileFields = {
                    filename: filename,
                    path: imagePath,
                    fileSize: file.size,
                    extension: extension
                };
                updateModel(fileFields);
            });
        });

        // UPDATE THE VIDEO
        var updateModel = function(fileFields) {
            Media.create(fileFields).exec(function(err, fileObject) {
                if (err) return res.negotiate(err);
                return res.ok(fileObject);
            });
        };

    },
    serve: function(req, res) {
        var fileAdapter = require('skipper-disk')();
        var path = require('path');
        var mediaId = req.param('id');
        Media.findOne(mediaId).exec(function(err, file) {
            if(err) return res.negotiate(req);
            if(!file) return res.badRequest({message: 'The image was not found'});
            var fullImagePath = path.join(sails.config.appPath, 'uploads', file.path);
            res.setHeader('Content-Disposition', 'inline; filename="'+file.filename+'"');
            fileAdapter.read(fullImagePath).on('error', function (err){ return res.serverError(err); }).pipe(res);
        });
    }

};
<file_sep>/api/models/User.js
module.exports = {


  config: {
    defaultField: 'fullName'
  },


  structure: {
    list: ['firstName', 'lastName', 'username', 'email'],
    edit: [
      {fields: ['firstName', 'lastName', 'email', 'username', 'password']}
    ]
  },


  attributes: {
    username: {
      type: 'string',
      required: true,
      unique: true,
      config: {
        list: {},
        edit: {}
      }
    },
    email: {
      type: 'email',
      required: true,
      unique: true,
      config: {
        list: {},
        edit: {}
      }
    },
    password: {
      type: 'string',
      config: {
        edit: {template: 'passwordCombo'}
      }
    },
    firstName: {
      type: 'string',
      required: true,
      config: {
        edit: {}
      }
    },
    lastName: {
      type: 'string',
      required: true,
      config: {
        edit: {}
      }
    },
    avatar: {
      model: 'image'
    },
    role: {
      model: 'role'
    },
    token: {
      type: 'string'
    },
    toJSON: function () {
      var obj = this;
      obj.fullName = `${obj.firstName} ${obj.lastName}`;
      delete obj.password;
      return obj;
    }
  },


  i18n: {
    en: {
      slug: 'users',
      singular: 'User',
      plural: 'Users'
    },
    ro: {
      slug: 'useri',
      singular: 'User',
      plural: 'Useri'
    }
  },

  beforeCreate: beforeSave,
  beforeUpdate: beforeSave,

  autoCreatedAt: true,
  autoUpdatedAt: true

};

function beforeSave(newUser, cb) {

  // remove images that are removed on update
  if (newUser.id) {
    User.findOne(newUser.id).populate('avatar').exec(function (err, oldUser) {
      if (oldUser.avatar && newUser.avatar && newUser.avatar != oldUser.avatar.id) {
        Image.destroy({id: oldUser.avatar.id}).exec(cb);
      }
    });
  }

  // handle password change
  delete newUser.password;
  delete newUser.passwordRepeat;
  if (newUser.passwordNew) {
    var bcrypt = require('bcryptjs');
    bcrypt.genSalt(10, function (err, salt) {
      bcrypt.hash(newUser.passwordNew, salt, function (err, hash) {
        if (err) {
          console.log('passwordHashError', err);
          cb(err);
        } else {
          newUser.password = <PASSWORD>;
          delete newUser.passwordNew;
          cb();
        }
      });
    });
  } else cb();

}
<file_sep>/front/config/imageSizes.js
/**
 * IMAGE SIZES
 * Used for image size regeneration
 * @structure { resizeName: {width, height, crop:true, quality:100, watermark:{top,left,path}} }
 * Watermark Images:
 * app/front/images/watermark.png | 170x70px
 */

module.exports.imageSizes = {
  featured: {
    width: 1500,
    height: 450,
    quality: 80,
    crop: true
  },
  list: {
    width: 700,
    height: 400,
    crop: true,
    quality: 90
  }
};<file_sep>/front/webpack/server.js
'use strict';

const path = require('path');
const fs = require('fs');

module.exports = {

  entry: [
    path.join(__dirname, '../src/server.js')
  ],

  output: {
    filename: './index.js',
    libraryTarget: 'commonjs2' // module.exports = <file_content>;
  },

  // keep node_module paths out of the bundle
  externals: fs.readdirSync(path.resolve(__dirname, '../node_modules')).concat([
    'react-dom/server', 'react/addons'
  ]).reduce(function (ext, mod) {
    ext[mod] = 'commonjs ' + mod;
    return ext
  }, {}),

  resolveLoader: {
    alias: {
      'asset-loader': path.join(__dirname, './loaders/asset.js'),
    }
  },

  module: {
    rules: [
      {
        test: /\.(scss|css|html|ejs|woff|woff2|ttf|otf|eot)$/,
        loader: 'null-loader'
      },
      {
        test: /^\.\/.*$/,
        loader: 'null-loader'
      },
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        include: [path.join(__dirname, '../src/Layout/assets/vendor/fonts')],
        loader: 'null-loader'
      },
      {
        test: /\.(svg|png|jpg|gif)$/,
        exclude: [path.join(__dirname, '../src/Layout/assets/vendor/fonts')],
        loader: 'asset-loader'
      },
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        include: [path.join(__dirname, '../src/')]
      }
    ]
  }

}
<file_sep>/api/controllers/Image/ImageWatermark.js
module.exports = function(watermark, image) {
  var defer = $q.defer();
  // check if all required watermark object properties exist
  if(watermark && (watermark.top>=0 || watermark.bottom>=0) && (watermark.left>=0 || watermark.right>=0) && watermark.path) {

    // open watermark image
    lwip.open(path.join(sails.config.appPath, watermark.path), function(err, watermarkImage) {
      if(err) return defer.reject('Watermark image cannot be opened.');

      if(watermark.bottom>=0) {
        var wiHeight = watermarkImage.height();
        var iHeight = image.height();
        watermark.top = iHeight - wiHeight - watermark.bottom;
      }

      if(watermark.right>=0) {
        var wiWidth = watermarkImage.width();
        var iWidth = image.width();
        watermark.left = iWidth - wiWidth - watermark.right;
      }

      // paste the watermark image on top of the original one
      image.paste(watermark.left, watermark.top, watermarkImage, function(err, finalImage) {
        if(err) return defer.reject('The image failed to be watermarked.');
        defer.resolve(finalImage);
      });
    });
  } else defer.resolve(image);
  return defer.promise;
};
<file_sep>/admin/src/core/utils/components/ContentComponents.js
import React, {Component} from 'react';
import PageHeader from './ContentComponents/PageHeader/PageHeaderComponent';
import Loader from './ContentComponents/Loader/LoaderComponent';
import TabsForm from './ContentComponents/TabsForm/TabsFormComponent';
import Pagination from './ContentComponents/Pagination/PaginationComponent';
import Navigation from './ContentComponents/Navigation/NavigationComponent';
import SearchInput from './ContentComponents/SearchInput/SearchInputComponent';
import Layout from '../../../Main/Layout/LayoutComponent';
import { Link } from 'react-router';
import { toastr } from 'react-redux-toastr';


// exports imported components
export {PageHeader, Loader, Layout, Link, TabsForm, toastr, Pagination, Navigation, SearchInput};
<file_sep>/api/controllers/AuthController.js
var passport = require('passport');

module.exports = {

  _config: {
    actions: false,
    shortcuts: false,
    rest: false
  },

  login: function (req, res) {
    if(!req.params.all().username) return res.badRequest();
    passport.authenticate('local', function (err, user, info) {
      if (err) return res.serverError(err);
      if (!user) return res.badRequest();
      req.logIn(user, function (err) {
        if (err) return res.ok(err);
        return res.ok({
          message: info.message,
          user: user
        });
      });
    })(req, res);
  },


  logout: function (req, res) {
    req.logout();
    res.ok();
  },

  currentUser: function (req, res) {
    if (!req.user) return res.badRequest();
    User.findOne(req.user.id).exec(function(err, user) {
      if (err) return res.serverError(err);
      res.ok(user)
    })
  },

  checkAuthentication: function (req, res) {
    if (!(req.session.passport && req.session.passport.user)) return res.badRequest();
    res.ok()
  },

  passwordResetStep1: function (req, res) {
    var email = req.param('email');
    User.findOne({email: email}).exec(function (err, user) {
      if (err) return res.serverError(err);
      if (!user) return res.badRequest({status: 'failed'});
      generateToken(user);
    });
    function generateToken(user) {
      require('crypto').randomBytes(10, function (ex, buf) {
        var token = buf.toString('hex');
        User.update(user.id, {token: token}).exec(function (err, user) {
          if (err) return res.serverError(err);
          sendEmailNotification(user[0])
        });
      });
    }

    function sendEmailNotification(user) {
      var siteName = sails.config.frontend.name;
      var currentLocale = sails.config.i18n.currentLocale;
      var link = sails.getBaseurl() + '/' + currentLocale + '/login?setNewPassword=<PASSWORD>&token=' + user.token;
      var html = '<p>You have opted to reset your password on ' + siteName + '</p>' +
        '<p>To reset you password copy the code below:</p>' +
        '<p><strong>' + user.token + '</strong></p>' +
        '<p>or just click this link:</p>' +
        '<p><a href="' + link + '">' + link + '</a>';
      require('nodemailer').createTransport(sails.config.email).sendMail({
        from: siteName + ' <' + sails.config.email.from + '>',
        to: user.email,
        subject: 'Password Reset',
        html: html
      }, function (err) {
        if (err) return res.serverError(err);
        return res.ok();
      });
    }
  },

  passwordResetStep2: function (req, res) {
    var token = req.param('token');
    var password = req.param('<PASSWORD>');
    User.findOne({token: token}).exec(function (err, user) {
      if (err) return res.serverError(err);
      if (!user) return res.badRequest();
      User.update(user.id, {token: null, password: <PASSWORD>}).exec(function (err, users) {
        if (err) return res.serverError(err);
        return res.ok();
      });
    });
  },

  setLocale: function (req, res) {
    var locale = req.param('locale');
    if (!locale) return res.badRequest({message: 'Cannot set locale. No locale sent.'});
    req.session.currentLocale = locale;
    return res.ok();
  }


};
<file_sep>/front/src/core/config/store.js
import rootReducer from './rootReducer';
import { compose, createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import thunk from 'redux-thunk';
import { syncTranslationWithStore } from 'react-redux-i18n';

const initialState = typeof window !== 'undefined' && window.__initial_state__;

const store = compose(
  applyMiddleware(
    promiseMiddleware({
      promiseTypeSuffixes: ['loading', 'success', 'error']
    }),
    thunk
  ),
  typeof window !== 'undefined' && typeof isDev !== 'undefined' && window.devToolsExtension ? window.devToolsExtension() : f => f
)(createStore)(rootReducer, initialState || {});

syncTranslationWithStore(store);

export default store;<file_sep>/api/policies/isAdmin.js
module.exports = function (req, res, next) {

  if (!(req.session.passport && req.session.passport.user)) return res.forbidden('You are not authenticated', 'policy.notAutenticated');

  User.query(`SELECT role FROM user WHERE id = ${req.session.passport.user}`).then(users => {
    const userRole = parseInt(users[0].role);
    if (!userRole) return res.forbidden({code: 'policy.userHasNoRole'});
    if (userRole != 5) return res.forbidden({code: 'policy.isNotAdmin'});
    return next();
  });

};
<file_sep>/front/src/core/components/form/File/FileComponent.js
import React, {Component} from 'react';
import Dropzone from 'react-dropzone';
import {I18n} from 'react-redux-i18n';
import './File.scss';


export default class FileComponent extends Component {

  static propTypes = {
    fileTypes: React.PropTypes.arrayOf(React.PropTypes.oneOf(['pdf', 'word', 'excel', 'ppt']))
  };
  state = {};
  maxSize = 5 * 1024 * 1024;
  mimeTypes = {
    pdf: ['application/pdf'],
    word: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
    excel: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
    ppt: ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation']
  };

  render() {
    const {value, fileTypes, onChange, label, placeholder} = this.props;
    const {errors} = this.state;
    const dropzoneConfig = {
      ref: 'dropzone',
      onDrop: this.onDrop.bind(this),
      multiple: false,
      style: {},
      disableClick: true,
      maxSize: this.maxSize,
      accept: this.getMimeTypes(fileTypes).join(', ')
    };
    return (
      <Dropzone {...dropzoneConfig} className="file-component">
        <div className="file">
          {value ? (
            <div className="file__ready">
              <i className="icon-check" />
              <span>{value.name}</span>
              <button className="button-icon icon-times" onClick={()=>onChange(null)} />
            </div>
          ) : (
            <button type="button" className="file__empty" onClick={this.open.bind(this)}>
              <div>
                <i className="icon-paperclip" />
                <span>{label||placeholder||'Upload file'}</span>
              </div>
            </button>
          )}
        </div>
        {errors && (
          <div className="errors">{errors.map(error=><div key={error.type}>{I18n.t(`fileComponentError.${error.type}`, error.meta)}</div>)}</div>
        )}
      </Dropzone>
    )
  }

  onDrop(acceptedFiles, rejectedFiles) {
    const {onChange} = this.props;
    let errors = null;
    if (acceptedFiles.length) {
      onChange(acceptedFiles[0]);
    } else if(rejectedFiles.length && !rejectedFiles[0].type) { // fix for files with no type
      onChange(rejectedFiles[0]);
    } else {
      errors = this.getErrors(rejectedFiles[0]);
    }
    this.setState({errors});
  }

  open() {
    this.refs.dropzone.open();
  }

  getMimeTypes(fileTypes) {
    if(!fileTypes) return [];
    const mimeTypes = [];
    fileTypes.forEach(fileType=>{
      this.mimeTypes[fileType].forEach(mimeType=>{
        mimeTypes.push(mimeType);
      })
    });
    return mimeTypes;
  }

  getErrors(rejectedFile) {
    const {fileTypes} = this.props;
    const errors = [];
    if(rejectedFile.size > this.maxSize) {
      errors.push({type: 'maxSize', meta: {size: `${this.maxSize/1024/1024}MB`}});
    }
    const mimeTypeList = this.getMimeTypes(fileTypes);
    if(mimeTypeList.length && !mimeTypeList.includes(rejectedFile.type)) {
      errors.push({type: 'type', meta: {types: fileTypes.join(', ')}});
    }
    return errors.length ? errors : null;
  }

}
<file_sep>/api/responses/ok.js
module.exports = function(data) {

  this.res.status(200);
  this.res.jsonx(data);

};
<file_sep>/admin/src/modules/Model/ModelImportScreen.js
import React from 'react';
import {connect} from 'react-redux';
import moment from 'moment';
const XLS = require('xlsx');
import {getJson} from '../../core/utils/ImportService'
import ConfigRow from './templates/Import/ModelImportConfigRow';
import axios from 'axios';
import _ from 'lodash';
import { fieldBuild } from 'core/utils/helpers';
import {I18n} from 'react-redux-i18n';


@connect((state, props)=>({
  importExport: state.main.models[props.params.modelName].structure.importExport,
  modelFields: state.main.models[props.params.modelName].fields,
  modelStructure: state.main.models[props.params.modelName].structure
}))
export default class ModelImportScreen extends React.Component {

  state = {filter: {}};

  render() {
    const {importExport} = this.props;
    const {importItems, sheetName, filter} = this.state;
    return (
      <div>

        <div className="row">
          <div className="col-md-6">

            <div className="margin-bottom-30">
              <h4>1. Select XLS file</h4>
              <input type="file" onChange={this.onFileInputChange.bind(this)} />
            </div>


            {importItems && (
              <div className="margin-bottom-30">
                <h4>2. Select Sheet</h4>
                <select className="form-control" value={sheetName} onChange={e=>this.setState({sheetName: e.target.value})}>
                  <option value="">Select</option>
                  {Object.keys(importItems).map(sheet => (
                    <option key={sheet} value={sheet}>{sheet}</option>
                  ))}
                </select>
              </div>
            )}

            {importItems && sheetName && (
              <div>
                <h4>4. Import</h4>
                <p>There are {importItems[sheetName].length-1} items to be imported</p>
                {importItems[sheetName].length>1 ? (
                  <button className="btn" onClick={this.import.bind(this)}>Import</button>
                ) : (
                  <div className="no-content">There is no data set></div>
                  )}
              </div>
            )}

          </div>

          {importItems && sheetName && (
            <div className="col-md-6">
              <h4>3. Confirm columns</h4>
              <table className="table table-condensed">
                <thead>
                <tr>
                  <th>Field Name</th>
                  <th>Header</th>
                  <th>First row</th>
                  <th>Field config</th>
                </tr>
                </thead>
                <tbody>
                {importExport.map((fieldName, k)=>(
                  <ConfigRow key={k} k={k} fieldName={fieldName} importItems={importItems} sheetName={sheetName}
                             filter={filter} setFilter={this.setFilter.bind(this)} />
                ))}
                </tbody>
              </table>
              {JSON.stringify(filter)}
            </div>
          )}

        </div>

        {this.renderResults()}

      </div>
    )
  }

  renderResults() {
    const {successCount, importStart, importEnd, importItems, sheetName, invalidItems=[]} = this.state;
    return (
      <div>

        {successCount!=null && (
          <div>
            <h4>5. Results</h4>
            <p>{successCount/(importItems[sheetName].length-1)*100}% items imported ({successCount}/{importItems[sheetName].length-1})</p>
            {importStart && importEnd && (
              <p>Import finished in {(importEnd-importStart)/1000}s</p>
            )}
          </div>
        )}

        {!!invalidItems.length && (
          <div className="margin-top-40">
            <h4>6. Errors</h4>
            <p>{(invalidItems.length/(importItems[sheetName].length-1)*100).toFixed(2)}% items failed to import ({invalidItems.length}/{importItems[sheetName].length-1})</p>
            <ul>
              {invalidItems.map((item, k)=>(
                <li key={k}>
                  <span>{k+1+'.'}</span>
                  <strong>{item.name}</strong>
                  {Object.keys(item.errors).map(fieldName=>(
                    <span key={fieldName}> {item.errors[fieldName].map(error=>`[${fieldName}]=${error}`).join(',')}</span>
                  ))}
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>
    )
  }

  onFileInputChange(e) {
    const reader = new FileReader();
    reader.readAsBinaryString(e.target.files[0]);
    reader.onload = e => {
      const results = getJson(XLS.read(e.target.result, {type:'binary'}));
      this.setState({importItems: results});
      if(Object.keys(results).length) {
        this.setState({sheetName: Object.keys(results)[0]});
      }
      // console.log('results', results); // TODO: remove
    };
  }

  setFilter(key, value) {
    const {filter} = this.state;
    this.setState({
      filter: {
        ...filter,
        [key]: value
      }
    });
  }


  import() {

    // 1. Parse Import Items
    const allItems = this.parseImportItems();
    this.setState({invalidItems: [], successCount: 0, importStart: moment().format('x'), importEnd: null}); // reset

    // 2. Build fields
    this.buildFields(allItems, () => {

      // 3. Pre-validate
      this.preValidate(allItems, (allItems, invalidItems) => {
        this.setState({invalidItems});
        // console.log('invalidItems', invalidItems)
        // console.log('allItems', allItems)


        // 4. Get submodels (categories)
        this.getCategories(allItems, () => {

          // console.log('allItems', allItems)

          // 5. Import items (sending chunks)
          this.perChunk = 100;
          this.page = 0;
          this.sendChunk(allItems);

        });

      });

    });


  }



  /**
   * PARSE IMPORT ITEMS
   * parses xls data format to importable data format
   */
  parseImportItems() {
    const {modelFields, modelStructure} = this.props;
    const {importItems, sheetName, filter} = this.state;

    const newItems = [];

    // parse from xls
    importItems[sheetName].forEach(function (itemArray, i) {
      if(i==0) return; // ignore the first row
      const newItem = {};
      itemArray.forEach(function(item, k) {
        newItem[modelStructure.importExport[k]] = item;
      });
      newItems.push(newItem);
    });

    // parse dates; todo: remove hardcoding
    newItems.forEach(function(item) {
      if(modelFields.startDate) {
        const startDateFormat = filter.dateFormat;
        item.startDate = moment(item.startDate, startDateFormat).toDate();
      }
      if(modelFields.endDate) {
        const endDateFormat = filter.dateFormat;
        item.endDate = moment(item.endDate, endDateFormat).toDate();
      }
      if(modelFields.startTime) {
        const startTimeFormat = filter.timeFormat;
        item.startTime = moment(item.startTime, startTimeFormat).toDate();
      }
      if(modelFields.endTime) {
        const endTimeFormat = filter.timeFormat;
        item.endTime = moment(item.endTime, endTimeFormat).toDate();
      }
    });

    return newItems;
  };



  /**
   * BUILD FIELDS
   * builds fields based on config
   */
  buildFields(items, cb) {
    const {modelFields, params: {modelName}} = this.props;

    // get buildable fields
    const buildableFields = {};
    _.forEach(modelFields, function(data, fieldName) {
      if(data.config.edit.build) buildableFields[fieldName] = data.config.edit.build;
    });

    // build fields for each item
    items.forEach(function(item) {
      _.forEach(buildableFields, function(build, fieldName) {
        item[fieldName] = fieldBuild(item, build);
      })
    });

    cb();
  };



  /**
   * PREVALIDATOR
   * validates fields not to go to final import save
   */
  preValidate(items, cb) {

    const {modelFields, params: {modelName}} = this.props;
    const validItems = [];
    const newItems = [];
    const invalidItems = [];

    // get unique fields
    const uniqueFields = {};
    const submodelFields = {};
    _.forEach(modelFields, function(data, fieldName) {
      if(data.validation.unique && fieldName!='id') uniqueFields[fieldName] = [];
      else if(data.validation.model && !data.config.importExport.createIfNotFound) submodelFields[fieldName] = [];
      if(uniqueFields[fieldName] || submodelFields[fieldName] || data.validation.required) items.forEach(function(item) {
        if(data.validation.required && !item[fieldName]) {
          item.errors = item.errors || {};
          item.errors[fieldName] = item.errors[fieldName] || [];
          item.errors[fieldName].push('required');
          if(Object.keys(item.errors).length==1) invalidItems.push(item);
        } else {
          if(uniqueFields[fieldName]) {
            if(uniqueFields[fieldName].indexOf(item[fieldName])==-1) {
              uniqueFields[fieldName].push(item[fieldName]);
              if(!item.added) {
                item.added = true;
                validItems.push(item);
              }
            } else {
              item.errors = item.errors || {};
              item.errors[fieldName] = item.errors[fieldName] || [];
              item.errors[fieldName].push('duplicate');
              if(Object.keys(item.errors).length==1) invalidItems.push(item);
            }
          } else if(submodelFields[fieldName]) {
            if(submodelFields[fieldName].indexOf(item[fieldName])==-1) submodelFields[fieldName].push(item[fieldName]);
            if(!item.added) {
              item.added = true;
              validItems.push(item);
            }
          }
        }
      });
    });
    // console.log('invalidItems', [...invalidItems]);

    // check for duplicate on server
    axios.post('/import/check', {model: modelName, unique: uniqueFields, submodel: submodelFields}).then(function(response) {
      // console.log('response check', response);
      validItems.forEach(function(item) {
        item.errors = {};
        _.forEach(response.data.unique, function(fieldItems, fieldName) {
          if(response.data.unique[fieldName].indexOf(item[fieldName])>-1) {
            item.errors[fieldName] = item.errors[fieldName] || [];
            item.errors[fieldName].push('unique');
          }
        });
        _.forEach(response.data.submodel, function(fieldItems, fieldName) {
          if(response.data.submodel[fieldName].indexOf(item[fieldName])>-1) {
            item.errors[fieldName] = item.errors[fieldName] || [];
            item.errors[fieldName].push('notFound');
          }
        });
        if(!Object.keys(item.errors).length) newItems.push(item);
        else invalidItems.push(item);
      });

      // console.log('newItems', newItems);
      // console.log('invalidItems', invalidItems);
      cb(newItems, invalidItems);
    });

  };



  /**
   * GET CATEGORIES
   * get all items categories from database by slug
   * if the category does not exist, it creates it in backend
   */
  getCategories(items, cb) {
    const {modelFields} = this.props;

    // 1. Get submodel fields
    const submodelFields = {};
    _.forEach(modelFields, function(field, fieldName) {
      if(field.validation.model) {
        submodelFields[fieldName] = {
          createIfNotFound: field.config.importExport.createIfNotFound,
          parent: field.config.importExport.parent,
          fieldName: field.config.importExport.selectedField,
          model: field.validation.model,
          items: []
        };
      }
    });


    // 2. Get submodel field values
    _.forEach(submodelFields, function(v, fieldName) {
      items.forEach(function(item) {
        if(item[fieldName] && submodelFields[fieldName].items.indexOf(item[fieldName])==-1) submodelFields[fieldName].items.push(item[fieldName]);
      });
    });


    // 3. Get category objects form server and populate submodels
    //console.log('submodelFields', submodelFields);
    axios.post('/import/getCategoryObjects', submodelFields).then(function(response) {
      const categoryObjects = response.data;
      items.forEach(function(item) {
        _.forEach(categoryObjects, function(fieldItems, fieldName) {
          fieldItems.forEach(function(fieldItem) {
            if(fieldItem && item[fieldName] == fieldItem[submodelFields[fieldName].fieldName]) item[fieldName] = fieldItem;
          });
        });
      });
      cb();
    });
  };


  /**
   * SEND DATA CHUNK
   */
  sendChunk(allItems) {
    const {params: {modelName}} = this.props;
    const start = this.page * this.perChunk;
    const end = (this.page+1) * this.perChunk;
    const chunkItems = allItems.slice(start, end);
    // console.log('chunkItems', chunkItems);
    axios.post(`/${modelName}/import`, {items: chunkItems}).then(response => {
      // console.log('response', response);
      this.setState({successCount: this.state.successCount + response.data.successCount});
      this.page++;
      if(allItems[this.page * this.perChunk]) setTimeout(this.sendChunk, 1000, allItems);
      else this.importEnd = moment().format('x');
    }).catch(err => {
      console.log('err', err);
    })
  }


}
<file_sep>/front/src/core/config/interceptors.js
export function interceptorSuccess(response) {
  if(response.data.status && response.data.status>=400 && response.data.status<500) {
    return Promise.reject(response.data);
  }
  return response;
}

export function interceptorError(error) {
  return Promise.reject(error);
}
<file_sep>/front/src/modules/Article/ArticleRoutes.js
import ArticleBrowse from './ArticleBrowseScreen';
import ArticleView from './ArticleViewScreen';

export default {
  name: 'Article',
  path: 'articles',
  component: props => props.children,
  indexRoute: {name: 'ArticleBrowse', component: ArticleBrowse, breadcrumbIgnore: true},
  childRoutes: [
    {
      name: 'ArticleBrowseTag',
      path: 'tag/:tagSlug',
      model: {name: 'articletag', param: 'tagSlug', dbParam: 'slug'},
      component: ArticleBrowse,
      breadcrumbResolve: state => `Tag: "${state.main.params.tagSlug}"`
    },
    {
      name: 'ArticleView',
      path: ':slug',
      model: {name: 'article', param: 'slug', dbParam: 'slug'},
      component: ArticleView,
      breadcrumbResolve: state => state.article.item && state.article.item.name
    }
  ]
}<file_sep>/api/services/utils/sendEmail.js
module.exports = function(options, success, error) {

    var from = options.from || sails.config.email.from;
    var to = options.to;
    var subject = options.subject;
    var html = options.content;
    if(to && subject && html) {
        require('nodemailer').createTransport(sails.config.email).sendMail({from: from, to: to, subject: subject, html: html},
        function(err) {
            if(err) {
                console.log(err)
                error(err);
            } else success();
        });
    } else {
        var fields = [];
        if(!to) fields.push('"to"');
        if(!subject) fields.push('"subject"');
        if(!html) fields.push('"content"');
        return error('The following fields are missing: '+fields.join(', '));
    }

};
<file_sep>/front/src/core/decorators.js
import connect from 'react-redux/lib/components/connect';
import {reduxForm} from 'redux-form';
import setMetaTags from './decorators/metaTagsDecorator';


export {
  connect,
  reduxForm,
  setMetaTags
};<file_sep>/admin/src/core/utils/components/ContentComponents/Modal/ModalComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Modal } from 'react-bootstrap';
import { I18n } from 'react-redux-i18n';
import { closeModal } from './ModalActions';
import { reduxForm } from 'redux-form';


class ModalComponent extends Component {

  onButtonClick(button) {
    const { valid, closeModal } = this.props;
    if (valid) {
      button.onClick && button.onClick();
      closeModal();
    }
  }

  render() {
    const { config: { title = I18n.t('modal.genericTitle'), classTitle, content, component, buttons = [] } = {}, isOpened, params, closeModal, handleSubmit } = this.props;
    const Component = component;
    if (!buttons.length) buttons.push({display: I18n.t('button.ok')});
    return (
      <form>
        <Modal show={isOpened} onHide={closeModal.bind(this)} className="community-modal">
          { title && (
            <div className={classTitle}>
              <Modal.Header className='community-header'>
                <div onClick={closeModal.bind(this)} className="close-button-modal">
                  <img src="assets/images/modal/x.svg"/>
                </div>
                <Modal.Title className="title-modal">{title}</Modal.Title>
              </Modal.Header>
            </div>

          ) }
          <Modal.Body>
            { component ?
              <Component closeModal={closeModal.bind(this)} buttons={buttons} params={params || {}}/> : content }
          </Modal.Body>
          { buttons && (
            <Modal.Footer className="community-footer">
              { buttons.map((button, k)=>(
                <button key={k} type="submit"
                        onClick={handleSubmit(()=>this.onButtonClick(button))}>{button.display}</button>
              )) }
            </Modal.Footer>
          ) }
        </Modal>
      </form>
    )
  }
}

ModalComponent = reduxForm({form: 'ModalForm'})(ModalComponent);
export default connect(state=>({
  isOpened: state.modal.isOpened,
  config: state.modal.config,
  params: state.modal.params
}), {
  closeModal
})(ModalComponent);
<file_sep>/front/config/email.js
var mg = require('nodemailer-mailgun-transport');

module.exports.email = mg({

  auth: {
    api_key: 'key-80d73a167c74950762c75ea9b01f12f7',
    domain: 'mg.gentlab.com'
  }

});<file_sep>/admin/src/modules/Dashboard/DashboardActions.js
import { getDataResource, getResultsResource, prevRoundStatusResource, nextRoundStatusResource, resetRoundStatusResource, triggerGetDataResource, generateCurrentRoundResultsResource } from './DashboardResources';
import store from 'core/config/store';


export function getData() {
  return {
    type: 'dashboard/getData',
    payload: new Promise((resolve, reject) => {
      getDataResource().then(response => {

        // if websockets not enabled, dispatch subscription
        if(!store.getState().dashboard.io) store.dispatch(subscribe());

        resolve(response);
      }, reject)
    })
  }
}

export function getResults(params) {
  return {
    type: 'dashboard/getResults',
    payload: getResultsResource(params)
  }
}

export function clearResults() {
  return {
    type: 'dashboard/clearResults'
  }
}

export function prevRoundStatus() {
  prevRoundStatusResource();
}

export function nextRoundStatus() {
  nextRoundStatusResource();
}

export function resetRoundStatus() {
  resetRoundStatusResource();
}

export function triggerGetData() {
  triggerGetDataResource();
}

export function subscribe() {
  return dispatch => {

    function currentRoundChangeHandler(newStatus) {
      if(newStatus=='stopped') generateCurrentRoundResultsResource();
      dispatch(getData());
    }

    var io = require('sails.io.js')( require('socket.io-client') );

    io.socket.on('connect', function() {
      io.socket.get('/api/session/subscribeToCurrentRound'); // subscribe to websocket room
      dispatch(getData());
      io.socket.on('currentRoundChanged', currentRoundChangeHandler);
    });

    io.socket.on('disconnect', function() {
      io.socket.off('currentRoundChanged', currentRoundChangeHandler);
    });

    dispatch({type: 'dashboard/subscribe', payload: io});

  }
}

export function unsubscribe() {
  var io = store.getState().dashboard.io;
  if(io) {
    io.socket.disconnect();
    delete io.sails;
  }
}

export function stateReset() {
  unsubscribe();
  return { type: 'dashboard/stateReset' };
}
<file_sep>/README.md
# Site Starter

#### Description

An application based on SailsJS for the API and ReactJS for the admin panel.

It is a work in progress. Detailed description will come.
<file_sep>/admin/src/core/security/Logout/LogoutActions.js
import { logout } from '../securityResources';

export function doLogout() {
  return {type: 'logout', payload: new Promise(resolve => {
    logout().then(() => resolve());
  })};
}
<file_sep>/admin/src/core/security/securityLayoutComponent.js
import React, { Component } from 'react';
import { I18n } from 'react-redux-i18n';

require('./security.scss');

export default class securityLayout extends Component {
  render() {
    return (
      <section className="page-login">
        <div className="row">
          <div className="col-md-5 col-sm-6">
            <div>{this.props.children}</div>
          </div>
        </div>
      </section>
    )
  }
}
<file_sep>/admin/src/Main/MainActions.js
import { getMainMenuResource, getModelsConfigResource } from './MainResources';

export function getMainMenu() {
  return {
    type: 'main/getMainMenu',
    payload: getMainMenuResource()
  }
}
export function getModelsConfig() {
  return {
    type: 'main/getModelsConfig',
    payload: getModelsConfigResource()
  }
}

export function setRouterParams(params) {
  return {
    type: 'main/setRouterParams',
    payload: params
  }
}
<file_sep>/admin/src/core/utils/components/FormComponents/JSONValues/JSONValuesComponent.js
import React from 'react';
import {connect} from 'react-redux';
import {Field} from 'core/utils/components/FormComponents';
import {firstLetterLowerCase} from 'core/utils/filters';
import List from './templates/JSONValuesListTemplate';
import ContentEditable from './templates/ContentEditable';
import './JSONValues.scss';
import {formValueSelector, change} from 'redux-form';


const selector = formValueSelector('TabsForm');
@connect((state, {form, config: {target}})=>({
  targetData: selector(state, target),
  routeName: selector(state, target === 'template' ? 'routeName' : 'indexRouteName')
}), {
  change
})
export default class JSONValuesComponent extends React.Component {

  render() {
    const {targetData, routeName, config: {target}} = this.props;
    if(!targetData) return null;
    return (
      <div className="json-values">
        {`${firstLetterLowerCase(routeName)}: {`}
        {this.renderChildren(targetData.i18nKeys, `${target}I18n`)}
        {'}'}
      </div>
    )
  }

  renderChildren(keys, parent) {
    if(!keys) return null;
    const {config: {target}} = this.props;
    const rootValue = {[`${target}I18n`]: this.props.value};
    return keys.map((item, k)=> {
      let value;
      try {
        value = eval(`rootValue.${parent}.${item.name}`);
      } catch(err) {}
      return (
        <div key={k} className="child">
          {this.renderField(item, value, parent, k === keys.length - 1)}
        </div>
      );
    });
  }

  onChange(parentName, name, targetValue) {
    const {change} = this.props;
    change('TabsForm', `${parentName}.${name}`, targetValue);
  }

  renderField(item, value, parent, last) {
    if(item.type === 'object') return (
      <div>
        <span>{item.name}:&nbsp;&#123;</span>
        {this.renderChildren(item.children, `${parent}.${item.name}`)}
        <span>&#125;{!last && ','}</span>
      </div>
    );
    if(item.type === 'list') return (
      <div>
        <span>{item.name}:&nbsp;&#91;</span>
        <List value={value} onChange={targetValue=>this.onChange(parent, item.name, targetValue)}/>
        <span>&#93;{!last && ','}</span>
      </div>
    );
    if(item.type === 'objectList') return (
      <div>
        <span>{item.name}:&nbsp;&#91;</span>
        <List value={value} onChange={targetValue=>this.onChange(parent, item.name, targetValue)} object keys={item.children} parent={`${parent}.${item.name}`} renderField={this.renderField.bind(this)}/>
        <span>&#93;{!last && ','}</span>
      </div>
    );
    return (
      <div className="item">
        <span>{item.name}:&nbsp;"</span>
        <ContentEditable value={value} onChange={e=>this.onChange(parent, item.name, e.target.value)} />
        <span>"{!last && ','}</span>
      </div>
    );
  }

}
<file_sep>/front/api/controllers/JobController.js
const excerpt = require('../utils').excerpt;

module.exports = {
  getPagedList: function (req, res) {

    const {page = 1, perPage = 10, isStudentLab = false} = req.params.all();

    const where = {
      status: 'published',
      isStudentLab: !!isStudentLab
    };

    Job.pagify('list', {
      findQuery: {where},
      sort: ['publishedDate DESC'],
      page: page ? parseInt(page) : undefined,
      perPage: perPage ? parseInt(perPage) : undefined
    }).then(function(data) {
      data.list.forEach(item=>{
        item.description = excerpt(item.description, 400);
      });
      res.ok(data);
    }).catch(function(err){
      res.negotiate(err);
    });

  },

  getJob: function (req, res) {
    const slug = req.params.slug;
    if (!slug) return res.badRequest('No job slug provided');
    Job.findOne({slug}).then(job => {
      if(!job) return res.notFound();
      Job.findOne({slug}).populateAll().then(job => {
        res.ok(job);
      })
    }).catch(res.negotiate);
  }

};<file_sep>/admin/src/core/config/app.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { interceptorSuccess, interceptorError } from './config';
import history from './history'
import routes from './routes';
import { Router } from 'react-router';
import axios from 'axios';
import { asyncTransitionCheck } from 'core/security/securityActions';
import { loadTranslations, setLocale } from 'react-redux-i18n';
import store from './store'
import ReduxToastr from 'react-redux-toastr';
import translations from './translations';


// add interceptor to all http responses
axios.interceptors.response.use(interceptorSuccess, interceptorError);
axios.defaults.baseURL = '/api';

// async check on every route if the current user is authenticated
history.listen(route => asyncTransitionCheck(route, history));


// load i18n
store.dispatch(loadTranslations(translations));
store.dispatch(setLocale('en'));

export default class AppComponent extends Component {
  render() {
    return (
      <Provider store={store}>
        <div>
          <Router history={history} routes={routes} />
          <ReduxToastr timeOut={3000} />
        </div>
      </Provider>
    );
  }
}
<file_sep>/index.js
process.chdir(__dirname);
const sails = require('sails');
const rc = require('rc');
sails.lift(rc('sails'));
<file_sep>/admin/src/modules/MyAccount/i18n/MyAccountI18nEn.js
module.exports = {
  permission: {
    edit: 'Edit',
    view: 'View',
    post: 'Post',
    moderate: 'Moderate'
  },
  button: {
    announcement : {
      add: 'New announcement'
    }
  },
  admin: {
    edit: {
      title: {
        community: 'Edit Community - Administration',
        forum: 'Edit Forum - Administration',
        topic: 'Edit Topic - Administration'
      },
      pageTitle: {
        community: 'Admin Edit Community',
        forum: 'Admin Edit Forum',
        topic: 'Admin Edit Topic'
      },
      permissions: {
        addUsersOrGroups: {
          title: 'Add Users or Groups to Permission List'
        }
      },
      noParam: {
        community: 'There is no community parameter set on url',
        forum: 'There is no forum parameter set on url'
      },
      successMessage: 'The Item has been successfully saved!'
    },
    myAccount: {
      pageTitle: 'My Account - Details'
    },
      specialPermissions: {
          title: 'Special Permissions'
      }
  }
};
<file_sep>/front/src/i18n.js
exports.generate = function(locale) {
  const capitalizedLocale = locale.replace( /(^|\s)([a-z])/g , function(m,p1,p2){ return p1+p2.toUpperCase(); } ); // en => En
  return [
    `core/Main/i18n/i18n${capitalizedLocale}`,
    `modules/Article/i18n/ArticleI18n${capitalizedLocale}`
  ]
};
<file_sep>/admin/src/core/utils/components/Validate.js
import {I18n} from 'react-redux-i18n';
import _ from 'lodash';
import { stringToObjectValue, setObjectDeepValue } from 'core/utils/helpers'

/**
 * VALIDATION FUNCTION
 * @description Returns validate function required for Redux Form
 * @param:config The configuration object for generating the rules
 */
export default function Validate(config) {

  var fields = config.fields || config;
  var tabList = config.tabList || [];


  /**
   * VALIDATION RULES
   */
  var rules = {
    required: function (fieldValue) {
      return !fieldValue;
    },
    arrayRequired: function (fieldValue) {
      if (!fieldValue) return false;
      return fieldValue.some(arrayItem=>!arrayItem);
    },
    propsInArrayRequired: function (fieldValue) {
      if (!fieldValue) return false;
      return fieldValue.some(arrayItem=> {
        var arrayInvalid = true;
        _.forEach(arrayItem, propValue=> {
          if (propValue) arrayInvalid = false;
        });
        return arrayInvalid;
      });
    },
    propsInArrayPropRequired: function (fieldValue, prop) {
      if (!fieldValue) return false;
      return fieldValue.some(arrayItem=> {
        var arrayInvalid = true;
        _.forEach(arrayItem[prop], propValue=> {
          if (propValue) arrayInvalid = false;
        });
        return arrayInvalid;
      });
    },
    minLength: function (fieldValue, validationValue) {
      if (!fieldValue) return false;
      return fieldValue.length < validationValue;
    },
    maxLength: function (fieldValue, validationValue) {
      if (!fieldValue) return false;
      return fieldValue.length > validationValue;
    },
    passwordRepeat: function(repeatPasswordValue, initialPasswordName, values) {
      if(!repeatPasswordValue) return false;
      var initialPasswordValue = stringToObjectValue(initialPasswordName, values);
      return initialPasswordValue!=repeatPasswordValue;
    },
    type: function (fieldValue, validationValue) {
      if (!fieldValue) return false;
      if(validationValue=='email') {
        var emailRegexp = /^[_a-zA-Z0-9]+(\.[_a-zA-Z0-9]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,20})$/;
        return !emailRegexp.test(fieldValue);
      } else if(validationValue=='integer') {
        return typeof fieldValue != 'number';
      } else if(validationValue=='string') {
        return typeof fieldValue != 'string';
      } else {
        return false;
      }
    }
  };


  /**
   * Redux Form validate function returned
   */
  return function (values) {
    const errors = {};

    // validate field function
    var validateField = function (fieldName, validations) {
      var isInvalid, validationType;
      var validationKeys = Object.keys(validations);
      for (var x = 0; x < validationKeys.length; x++) {
        validationType = validationKeys[x];
        var validationValue = validations[validationType];
        if (rules[validationType]) {
          var fieldValue = stringToObjectValue(fieldName, values);
          isInvalid = rules[validationType](fieldValue, validationValue, values);
          if (isInvalid) break;
        }
      }
      return isInvalid ? validationType : false;
    };

    // validate all fields
    _.forEach(fields, (validations, fieldName) => {
      var isInvalid = validateField(fieldName, validations)
      if (isInvalid) {
        const validationMessage = ['propsInArrayPropRequired'].includes(isInvalid) ? {_error: I18n.t('validation.' + isInvalid, validations)} : I18n.t('validation.' + isInvalid, validations);
        setObjectDeepValue(errors, fieldName, validationMessage);
      }
    });

    // validate tabList
    tabList.forEach((tabFields, tabIndex) => {
      errors.tabList = errors.tabList || {};
      var isTabInvalid = false;
      tabFields.forEach(tabField => {
        if (validateField(tabField, fields[tabField])) {
          isTabInvalid = true;
        }
      });
      errors.tabList[tabIndex] = isTabInvalid
    });

    return errors;
  }

}
<file_sep>/front/api/controllers/ArticleTagController.js
module.exports = {
  getList: function (req, res) {
    ArticleTag
      .query(`
        SELECT DISTINCT
          at.name,
          at.slug
        FROM articletag AS at
        LEFT JOIN article_tags__articletag_articles AS ata ON ata.articletag_articles = at.id
      `)
      .then(tags => {
        res.ok(tags);
      })
      .catch(res.negotiate);
  }
};<file_sep>/admin/src/core/config/rootReducer.js
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import { reducer as toastrReducer } from 'react-redux-toastr'
import { reducer as formReducer } from 'redux-form';
import { i18nReducer } from 'react-redux-i18n';
import ModalReducer from '../utils/components/ContentComponents/Modal/ModalReducer';
import securityReducer from 'core/security/securityReducer';
import MainReducer from '../../Main/MainReducer';
import DashboardReducer from 'modules/Dashboard/DashboardReducer';
import appReducers from '../../reducers';

const rootReducer = combineReducers({
  router: routerStateReducer,
  toastr: toastrReducer,
  form: formReducer,
  i18n: i18nReducer,
  modal: ModalReducer,
  security: securityReducer,
  main: MainReducer,
  dashboard: DashboardReducer,
  ...appReducers
});

export default rootReducer;
<file_sep>/front/api/controllers/ArticleController.js
const excerpt = require('../utils').excerpt;

module.exports = {
  getPagedList: function (req, res) {

    const {page = 1, perPage = 10, tagSlug} = req.params.all();

    const where = {
      status: 'published'
    };
    const promises = [];
    if(tagSlug) {
      promises.push(ArticleTag.query(`
        SELECT
          ata.article_tags AS id
        FROM articletag AS at
          LEFT JOIN article_tags__articletag_articles AS ata ON ata.articletag_articles = at.id
          WHERE at.slug = '${tagSlug}'
          AND ata.article_tags IS NOT NULL
      `))
    }

    Promise.all(promises).then(data => {
      if(data.length) where.id = data[0].map(i=>i.id);
      Article.pagify('list', {
        findQuery: {where},
        sort: ['publishedDate DESC'],
        populate: ['featuredImage', 'tags'],
        page: page ? parseInt(page) : undefined,
        perPage: perPage ? parseInt(perPage) : undefined
      }).then(function(data) {
        data.list.forEach(item=>{
          item.content = excerpt(item.content, 240);
        });
        res.ok(data);
      }).catch(function(err){
        res.negotiate(err);
      });
    })

  },

  getFeaturedList: function (req, res) {

    Article.find({
      where: {status: 'published', isFeatured: true},
      select: ['id', 'name', 'slug', 'featuredImage', 'publishedDate'],
      sort: 'publishedDate DESC',
      limit: 4
    }).populate('featuredImage').then(article => {
      res.ok(article);
    }).catch(res.negotiate)

  },

  getArticle: function (req, res) {
    const slug = req.params.slug;
    if (!slug) return res.badRequest('No article slug provided');
    Article.findOne({slug}).then(article => {
      if(!article) return res.notFound();
      Article.findOne({slug}).populateAll().then(article => {
        res.ok(article);
      })
    }).catch(res.negotiate);
  }
};<file_sep>/admin/src/core/config/routes.js
import Main from 'Main/MainComponent';
import { syncAuthCheck } from 'core/security/securityActions';
import Dashboard from 'modules/Dashboard/DashboardComponent';
import Login from 'core/security/Login/LoginComponent';
import Logout from 'core/security/Logout/LogoutComponent';
import Register from 'core/security/Register/RegisterComponent';
import ResetPassword from 'core/security/ResetPassword/ResetPasswordComponent';
import TokenNewPassword from 'core/security/TokenNewPassword/TokenNewPasswordComponent';
import appRoutes from '../../routes';
import NotFound from 'Main/Layout/NotFound/NotFoundComponent';


export default {
  path: '/',
  breadcrumbName: 'Home',
  component: Main,
  onEnter: syncAuthCheck,
  indexRoute: {name: 'Dashboard', component: Dashboard},
  childRoutes: [
    {path: 'login', name: 'Login', component: Login},
    {path: 'logout', name: 'Logout', component: Logout},
    {path: 'register', name: 'Register', component: Register},
    {path: 'reset-password', name: 'ResetPassword', component: ResetPassword},
    {path: 'token-new-password', name: 'TokenNewPassword', component: TokenNewPassword},
    ...appRoutes,
    {path: '*', component: NotFound}
  ]
};
<file_sep>/config/routes.js
const path = require('path');
const routes = {};

// Generated assets
routes['GET /sitemap.xml'] =                                    require('./routes/sitemap').default;
routes['GET /robots.txt'] =                                     require('./routes/robots').default;
routes['GET /config.js'] =                                      'ConfigController.getMainConfig';


/**
 * SECURITY
 */
routes['POST /api/security/login'] =                        'AuthController.login';
routes['GET  /api/security/logout'] =                       'AuthController.logout';
routes['GET  /api/security/currentUser'] =                  'AuthController.currentUser';
routes['GET  /api/security/checkAuthentication'] =          'AuthController.checkAuthentication';
routes['POST /api/security/password-reset-step-1'] =        'AuthController.passwordResetStep1';
routes['POST /api/security/password-reset-step-2'] =        'AuthController.passwordResetStep2';


/**
 * CONFIG
 */
routes['GET  /api/config/getMainConfig'] =                  'ConfigController.getMainConfig';
routes['GET  /api/config/models'] =                         'ConfigController.models';
routes['GET  /api/config/mainMenu'] =                       'ConfigController.mainMenu';
routes['GET  /api/config/getData'] =                           'ConfigController.getData';


/**
 * IMAGE
 */
routes['POST /api/image/upload'] =                          'ImageController.upload';
routes['GET  /images/:size/:filename'] =                    'ImageController.serve';
routes['GET  /images/:filename'] =                          'ImageController.serve';


/**
 * ADMIN
 */
routes['GET    /api/:modelName/list/paged'] =                'GenericModelController.getPagedList';
routes['GET    /api/:modelName/list'] =                      'GenericModelController.getList';
routes['GET    /api/:modelName/get/:modelId'] =              'GenericModelController.getItem';
routes['POST   /api/:modelName/create'] =                    'GenericModelController.saveItem';
routes['PUT    /api/:modelName/update/:modelId'] =           'GenericModelController.saveItem';
routes['DELETE /api/:modelName/delete/multiple'] =           'GenericModelController.deleteMultiple';
routes['DELETE /api/:modelName/delete/:modelId'] =           'GenericModelController.deleteItem';
routes['POST   /api/:modelName/import'] =                    'GenericModelController.importMultiple';
routes['POST   /api/import/check'] =                         'ImportController.check';
routes['POST   /api/import/getCategoryObjects'] =            'ImportController.getCategoryObjects';


/**
 * OTHER
 */
// MailChimp
routes['POST   /api/mailchimp/subscribe/:list'] =             'MailChimpController.subscribe';


/**
 * MAIN ENTRIES
 */

routes['GET /admin*'] = (req, res) => {
  res.sendfile(path.join(__dirname, '../admin/dist/'));
};
routes['GET /*']      = (req, res) => {
  require(path.join(sails.config.sitePath, 'dist/index')).route(req, res)
};


module.exports.routes = routes;
<file_sep>/admin/src/core/utils/helpers.js
import { I18n } from 'react-redux-i18n';
import store from 'core/config/store';
import {change} from 'redux-form';



/**
 * CALCULATE TOTAL PAGES
 * @description Receives count and perPage and returns totalPages
 * @param count
 * @param perPage
 * @returns int
 */
export function calculateTotalPages(count, perPage) {
    var totalPages;
    if(count && perPage) totalPages = Math.ceil(count / perPage);
    else totalPages = 1;
    return totalPages;
}


/**
 * String to Object Value
 * @description gets properties of an object by a string
 */
export function stringToObjectValue(s, o) {
  s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
  s = s.replace(/^\./, '');           // strip a leading dot
  var a = s.split('.');
  for (var i = 0, n = a.length; i < n; ++i) {
    var k = a[i];
    if (k in o) {
      o = o[k];
    } else {
      return;
    }
  }
  return o;
}


/**
 * Set object deep value
 */
export function setObjectDeepValue(obj, keys, val) {
  keys = keys.split('.');
  var last = keys.pop(); // get the last element off.
  for(var i in keys) {
    obj[keys[i]] = obj[keys[i]] || {};
    obj = obj[keys[i]]; // updating reference
  }
  obj[last] = val; // set the value
}


/**
 * Converts images to base64 by their url
 */
export function imageUrlToBase64(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.responseType = 'blob';
  xhr.onload = function() {
    var reader = new FileReader();
    reader.onloadend = function() {
      callback(reader.result);
    }
    reader.readAsDataURL(xhr.response);
  };
  xhr.open('GET', url);
  xhr.send();
}


/**
 * Evaluate variable
 * @description If the variable exists, return the value, if not, return undefined
 */
export function evalState(string, state) {
  /* eslint no-unused-vars: 0 */
  try {
    return eval(string)
  } catch(err) {
    return undefined;
  }
}



/**
 * Generate Validation Errors
 * @description Transforms validationAttributes from server to Redux Form Validation Errors
 */
export function generateValidationErrors(invalidAttributes) {
  const errors = {...invalidAttributes};
  Object.keys(errors).forEach(attr=> {
    errors[attr] = I18n.t('validation.'+errors[attr][0].rule);
  });
  return errors;
}


/**
 * Build Fields
 */
export function fieldBuild(values, {items, separator=' ', filter}) {

  function toSlug(text) {
    return text.trim().toLowerCase().replace(/ă/g, 'a').replace(/â/g, 'a').replace(/î/g, 'i').replace(/ș/g, 's').replace(/ț/g, 't').replace(/[^\w ]+/g, '').replace(/ +/g, '-');
  }

  function toCamelCase(text) {
    return toSlug(text).split('-').map((i,k)=>k==0?i:i.charAt(0).toUpperCase()+i.slice(1)).join('');
  }

  function outputFilter(input) {
    if (filter == 'toSlug') return toSlug(input);
    else if (filter == 'toCamelCase') return toCamelCase(input);
    else return input;
  }

  return outputFilter(items.map(i => values[i] || '').join(separator));
}


export function formFieldBuild(formName, {fieldName, separator, items, filter}, oldValues) {

  if(!(store.getState().form[formName] && store.getState().form[formName].values)) return;

  const newValues =  store.getState().form[formName].values;

  let isValid = false;
  items.forEach(item=>{
    if(oldValues[item]!=newValues[item]) isValid = true;
  });

  const output = fieldBuild(newValues, {items, separator, filter});

  if(!isValid) return;

    // function typeFilter(field) {
  //   var type = config.models[model].fields[field].edit && config.models[model].fields[field].edit.type ? config.models[model].fields[field].edit.type  : 'text';
  //   if (type == 'date') return $filter('date')(isolatedModel[field], 'dd.MM.yyyy');
  //   else return isolatedModel[field];
  // }



  if(oldValues[fieldName]!=newValues[fieldName] || newValues[fieldName]==output) return;

  store.dispatch(change(formName, fieldName, output));

}


/**
 * Set query params
 */
export function setQueryParam(name, value) {
  const { routes, params, location, router } = store.getState().main;
  const route = routes[routes.length-1];
  const query = {...location.query};
  if ([undefined, null, false].includes(value)) delete query[name];
  else query[name] = value;
  if(name!='page') delete query.page;
  router.replace({name: route.name, params, query});
}



/**
 * Listen to Location
 * @returns unlistener function
 */
export function listenToLocation(cb, that) {
  const { router, location: {query} } = store.getState().main;
  let lastQuery = {...query};
  return router.listen(location=>{
    if(!_.isEqual(lastQuery, location.query) && lastQuery.type == location.query.type) {
      setTimeout(()=>{ // to wait for props to be updated
        if(!that._calledComponentWillUnmount) cb();
      });
    }
    lastQuery = location.query;
  });
}



export function getModelPrimaryKey(modelName) {
  if(!modelName) return;
  const { models } = store.getState().main;
  return models[modelName].config.primaryKey;
}
<file_sep>/admin/src/core/utils/components/FormComponents/JSONTemplate/templates/JSONTemplateFields.js
import React from 'react';
import {Field} from 'core/utils/components/FormComponents';
import Children from './JSONTemplateChildren';


export default class JSONTemplateFields extends React.Component {

  typeList = [
    {label: 'Text', value: 'text'},
    {label: 'Object', value: 'object'},
    {label: 'List', value: 'list'},
    {label: 'List of Objects', value: 'objectList'}
  ];

  render() {
    const {fields} = this.props;
    return (
      <div className="form-json">
        {fields.map((field, k)=>(
          <div key={k}>
            <div className="item">
              <Field name={`${field}.type`} type="select" options={this.typeList} placeholder="Type" />
              <Field name={`${field}.name`} placeholder="Name" />
              <div><button type="button" onClick={()=>fields.remove(k)}>Remove</button></div>
            </div>
            <Children index={k} field={field} />
          </div>
        ))}
        <button type="button" onClick={()=>fields.push({type: 'text'})}>Add</button>
      </div>
    )
  }

}<file_sep>/front/src/screens.js
import AboutUs from './modules/AboutUs/AboutUsScreen';
import ArticleBrowse from './modules/Article/ArticleBrowseScreen';
import ArticleView from './modules/Article/ArticleViewScreen';
import Contact from './modules/Contact/ContactScreen';


module.exports = {
  AboutUs,
  ArticleBrowse,
  ArticleView,
  Contact
};
<file_sep>/front/src/Layout/NotFound/NotFoundComponent.js
import React, {Component} from 'react';
import {connect} from 'core/decorators';
import { setMetaTags } from 'core/Main/MainActions'
import {PageHeader} from 'core/components/content';


@connect(null, {
  setMetaTags
})
export default class NotFoundComponent extends Component {
  componentWillMount() {
    this.props.setMetaTags({
      title: 'Page Not found'
    });
  }
  render() {
    return (
      <div>
        <PageHeader title="Page not found" hideBreadcrumbs/>
        <div className="content container text-center">
          <h2>The page you are looking for does not exist</h2>
        </div>
      </div>
    );
  }
}
<file_sep>/admin/src/modules/Dashboard/i18n/DashboardI18nRo.js
module.exports = {
  dashboard: {
    title: 'Dashboard',
    pageTitle: 'Dashboard',
    rounds: ['Runda 1', 'Runda 2', 'Runda finală'],
    noRoundStarted: 'Nicio rundă pornită',
    nav: {
      notStarted: 'Deselectează Runda',
      prevRound: 'Runda Precedentă',
      nextRound: 'Runda Următoare',
      speeches: 'Discursuri în curs',
      started: 'Start Vot',
      stopped: 'Stop Vot',
      resultsShown: 'Afișare Rezultate'
    }
  },
  role: {
    jury: 'Juriu',
    trainer: 'Trainer',
    voter: 'Votant'
  }
};
<file_sep>/front/api/controllers/JobSignupController.js
const path = require('path');

module.exports = {
  signup: function (req, res) {
    const jobId = req.param('jobId');
    if(!jobId) return res.badRequest('A job id must be provided');

    const notificationEmails = ['<EMAIL>'];
    if (sails.config.environment !== 'development') notificationEmails.push('<EMAIL>');
    const signupData = req.body;
    delete signupData.attachment;
    signupData.job = jobId;

    // Create JobSignup entry & upload file
    Promise.all([
      JobSignup.create(signupData), // create the signup data
      UploadService.uploadFile(req, Media, 'attachment'), // upload the attachment
      Job.findOne(jobId) // get the job details
    ])
      .then(data => {
        const fileData = data[1];
        const jobName = data[2].name;
        return Promise.all([
          JobSignup.update(signupData.id, {attachment: fileData.id}), // update the signup data with the attachment
          MailService.sendMail({ // send the confirmation email to the staff
            from: 'Gentlab Admin Panel <<EMAIL>>',
            to: notificationEmails.join(','),
            subject: '[Gentlab] Some good fellow applied for a job',
            content: Object.keys(signupData).map(fieldName => {
              console.log('fieldValue = ', signupData[fieldName])
              const fieldValue = fieldName === 'job' ? jobName : signupData[fieldName].replace(/(?:\r\n|\r|\n)/g, '<br />')
              return `<p><strong>${fieldName}</strong>: ${fieldValue}</p>`
            }).join(''),
            attachments: [{
              filename: fileData.filename,
              path: path.join(sails.config.uploadsPath, fileData.path)
            }]
          })
        ]);
      })
      .then(() => {
        res.ok();
      })
      .catch(err => {
        res.negotiate(err);
      });

  }
};<file_sep>/api/services/utils/getOriginalFilename.js
function checkIfImageExists(Model, filename, cb) {
  Model.findOne({filename: filename}).then(function(item) {
    cb(!!item);
  })
}

function getFinalFilenameBody(filenameBody) {

  // test if there is and '-' character
  const indexOfLine = filenameBody.lastIndexOf('-');
  if(indexOfLine === -1) return filenameBody+'-c1';

  // test if the character group is in correct format ('c123')
  const possibleCopyIndex = filenameBody.substring(indexOfLine+1);
  if(!/c\d+/.test(possibleCopyIndex)) return filenameBody+'-c1';

  // rise the index and return the final body
  const currentCopyIndex = parseInt(possibleCopyIndex.replace('c', ''));
  return filenameBody.replace(possibleCopyIndex, 'c' + (currentCopyIndex + 1))

}

function riseCopyIndex(filename) {

  const lastIndexOfPoint = filename.lastIndexOf('.');
  let filenameBody = filename.substring(0, lastIndexOfPoint);
  let finalFilenameBody = getFinalFilenameBody(filenameBody);
  const extension = filename.substring(lastIndexOfPoint);
  return finalFilenameBody + extension;

}

function getOriginalFilename(Model, filename, cb) {
  checkIfImageExists(Model, filename, function(exists) {
    if(exists) {
      filename = riseCopyIndex(filename);
      getOriginalFilename(Model, filename, cb)
    } else cb(filename);
  })
}

module.exports = getOriginalFilename;
<file_sep>/front/src/core/components/form/DateTime/DateTimeComponent.js
import React from 'react';
import moment from 'moment';
import Datetime from 'react-datetime';


export default class DateTimeComponent extends React.Component {
  render() {
    const { disableDatesBefore, disableDatesAfter, onChange, value } = this.props;
    const validAfterAndBefore = function (current) {
      let isValidAfter = true;
      let isValidBefore = true;
      if (disableDatesBefore) {
        isValidAfter = current.isAfter(moment(disableDatesBefore).subtract(1, 'Days'));
      }
      if (disableDatesAfter) {
        isValidBefore = current.isBefore(disableDatesAfter);
      }
      return isValidAfter && isValidBefore;
    };
    return (
      <div>
        <Datetime value={value} dateFormat='DD/MM/YYYY'
                  closeOnSelect={true}
                  onChange={param => onChange(param.toDate())}
                  inputProps={{className: 'form-field'}}
                  isValidDate={validAfterAndBefore}/>
      </div>
    )
  }
}<file_sep>/front/src/core/components/content.js
import Breadcrumbs from './content/Breadcrumbs/BreadcrumbsComponent';
import Pagination from './content/Pagination/PaginationComponent';
import Filter from './content/Filter/FilterComponent';
import SelectFilter from './content/SelectFilter/SelectFilterComponent';
import Map from './content/Map/MapComponent';
import Navigation from './content/Navigation/NavigationComponent';
import TitleStack from './content/TitleStack/TitleStackComponent';
import Carousel from './content/Carousel/CarouselComponent';
import CodeHighlighter from './content/CodeHighlighter/CodeHighlighterComponent';
import Email from './content/Email/EmailComponent';
import PageHeader from './content/PageHeader/PageHeaderComponent';
import I18n from './content/I18n/I18nComponent';
import {Link} from 'react-router';


// exports imported components
export {
  PageHeader,
  Link,
  Breadcrumbs,
  Pagination,
  SelectFilter,
  Map,
  Navigation,
  TitleStack,
  Filter,
  Carousel,
  CodeHighlighter,
  Email,
  I18n
};

import './content/sectionIndent.scss';
import './content/Filter/Search.scss';
import './content/Select.scss';
import './content/button.scss';
import './content/SectionTitle/SectionTitle.scss';
<file_sep>/admin/src/modules/Model/templates/defaultList/ModelDefaultListItemContentTemplate.js
import React from 'react';
import {Link} from 'react-router';
import {connect} from 'react-redux';
import {deleteItem} from '../../ModelActions';
import {I18n} from 'react-redux-i18n';
import {imageUrl, highlight} from 'core/utils/filters';
import {getModelPrimaryKey} from 'core/utils/helpers';
import classnames from 'classnames';


@connect(state=>({
  models: state.main.models,
  query: state.main.location.query
}), {
  deleteItem
})
export default class ModelDefaultListItemContentTemplate extends React.Component {
  render() {
    const {item, k, isChecked, modelName, models, checkOne} = this.props;
    const columns = models[modelName].structure.list;
    return (
      <tr>
        <td className="column-checkbox">
          <input type="checkbox" checked={isChecked} onChange={e=>checkOne(k, e.target.checked)}/>
        </td>
        {columns.map(columnName=><th key={columnName} className={this.getColumnClass(columnName)}>{this.renderColumn(columnName)}</th>)}
        <td className="column-actions">
          <Link className="button-wire" to={{name: 'ModelEdit', params: {modelName, modelId: item[getModelPrimaryKey(modelName)]}}}>[Edit]</Link>
          <button className="button-wire" onClick={()=>this.deleteItem(item[getModelPrimaryKey(modelName)])}>[Delete]</button>
        </td>
      </tr>
    )
  }
  getColumnClass(columnName) {
    const {modelName, models} = this.props;
    return classnames(`column-${models[modelName].fields[columnName].config.list.template}`)
  }
  renderColumn(columnName) {
    const {item, modelName, models, query} = this.props;
    const fieldConfig = models[modelName].fields[columnName].config.list;
    const column = item[columnName];
    switch(fieldConfig.template) {
      case 'choice': {
        return I18n.t(`choice.${column}`)
      }
      case 'boolean': {
        return <i className={`fa fa-${column ? 'check' : 'ban'}`} />
      }
      case 'modelCount': {
        return column && column.length;
      }
      case 'image': {
        return <img src={column ? imageUrl(column.filename, 'thumb') : require('../../../../Main/Layout/assets/images/placeholder/user.svg')} />;
      }
      default: {
        return typeof column == 'string' ? <span dangerouslySetInnerHTML={{__html: highlight(column, query[columnName])}} /> : JSON.stringify(column);
      }
    }
  }
  deleteItem(modelId) {
    const {deleteItem, modelName, getList} = this.props;
    if(confirm(I18n.t('message.areYouSure'))) {
      deleteItem(modelName, modelId).then(getList);
    }
  }
}
<file_sep>/admin/src/core/utils/components/FormComponents.js
import React, { Component } from 'react';
import { Field as ReduxField, FieldArray } from 'redux-form';
import classnames from 'classnames';
import Datetime from 'react-datetime';
import Validate from './Validate';
import moment from 'moment';
import SubmitButton from './FormComponents/buttons/SubmitButtonComponent';
import ResetButton from './FormComponents/buttons/ResetButtonComponent';
import ReturnButton from './FormComponents/buttons/ReturnButtonComponent';
import Slug from './FormComponents/Slug/SlugComponent';
import Wysiwyg from './FormComponents/Wysiwyg/WysiwygComponent';
import Checkbox from './FormComponents/Checkbox/CheckboxComponent';
import Select from './FormComponents/Select/SelectComponent';
import MultiSelect from './FormComponents/Select/MultiSelectComponent';
import Image from './FormComponents/Image/ImageComponent';
import JSONTemplate from './FormComponents/JSONTemplate/JSONTemplateComponent';
import JSONValues from './FormComponents/JSONValues/JSONValuesComponent';
import SitemapModel from './FormComponents/SitemapModel/SitemapModelComponent';

// Export separate fields
export { Select, Validate, SubmitButton, ResetButton, ReturnButton };


/**
 * The Generic Field
 * @description An Interface for Form Field
 */
export class Field extends Component {

  render() {
    if(this.props.type=='passwordCombo') return (
      <div>
        <ReduxField component={this.renderFieldGroup} {...this.props} type="password" name="passwordNew" />
        <ReduxField component={this.renderFieldGroup} {...this.props} type="password" name="passwordRepeat"
                    {...{[this.props.placeholder?'placeholder':'label']: "Password Repeat"}} />
      </div>
    );
    if(['jsonTemplate'].includes(this.props.type)) {
      return <FieldArray name={this.props.name} component={this.renderArrayGroup.bind(this)} {...this.props} />
    }
    return <ReduxField component={this.renderFieldGroup} {...this.props} />;
  }

  renderFieldGroup({ input, name, type, label, placeholder, onClick, disabled, className, meta: { touched, error, form }, config={}, ...other }) {

    input.placeholder = placeholder;
    input.className = classnames('form-field', className);
    input.onClick = onClick;
    input.disabled = disabled;
    input.readOnly = config.readOnly;
    input.onBlur = ()=>{};
    other.onFocus = ()=>{};


    return (
      <div className={classnames('field-group', 'fg-name-'+name, {'fg-invalid': error, 'fg-touched': touched})}>
        { label && <label>{label}</label> }
        <div>
          { renderField() }
          { touched && error && <span className="fg-error">{error}</span> }
        </div>
      </div>
    );

    function renderField() {
      const inputTypes = ['text', 'password', 'email', 'number', 'phone'];
      type = type || inputTypes[0];
      if (inputTypes.includes(type)) return <input type={type} {...input} {...other} />; // for all fields in inputFields constant
      else if (type == 'textarea') return <textarea {...input} />;
      else if (type == 'jsonValues') return <JSONValues {...input} {...other} config={config} form={form} />;
      else if (type == 'sitemapModel') return <SitemapModel {...input} {...other} config={config} form={form} />;
      else if (type == 'slug') return <Slug {...input} {...other} form={form} />;
      else if (type == 'wysiwyg') return <Wysiwyg {...input} />;
      else if (type == 'select') return <Select {...input} {...other} config={config} />;
      else if (type == 'checkbox') return <Checkbox {...input} {...other} config={config} />;
      else if (type == 'multiselect') return <MultiSelect {...input} {...other} config={config} />;
      else if (type == 'image') return <Image {...input} {...other} config={config} />;
      else if (type == 'date') return <Datetime {...input} value={input.value ? moment(input.value) : ''} dateFormat="DD MMM YYYY" timeFormat={false} onFocus={()=>{}} {...other} />;
      else if (type == 'datetime') return <Datetime {...input} value={input.value ? moment(input.value) : ''} dateFormat="DD MMM YYYY"  timeFormat="hh:mm a" onFocus={()=>{}} {...other} />;
    }

  }

  renderArrayGroup({type, ...props}) {
    if(type === 'jsonTemplate') return <JSONTemplate {...props} />
  }

}<file_sep>/admin/src/Main/Layout/Header/HeaderComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {Navigation} from 'ContentComponents';
import { Link } from 'react-router';
import {getMainMenu} from '../../MainActions';

require('./Header.scss');

@connect(state => ({
  currentUser: state.security.currentUser,
  mainMenu: state.main.mainMenu
}), {
  getMainMenu
})
export default class HeaderComponent extends Component {
  componentWillMount() {
    this.props.getMainMenu();
  }
  render() {

    const { currentUser, mainMenu } = this.props;

    const userMenu = [
      {display: currentUser.fullName, route: {name: 'MyAccount'}, subitems: [
        {display: 'Profile', route: {name: 'MyAccount'}},
        {display: 'Logout', route: {name: 'Logout'}}
      ]}
    ];

    return (
      <div className="panel-container">

        <section className="header-top">
          <div className="container">
            <div className="logo">
              Administration Panel
            </div>
            <div className="account">
              <Navigation links={userMenu} />
            </div>
          </div>
        </section>

        <section className="header-navigation">
          <div className="panel">
            <Navigation className="element" links={mainMenu} />
          </div>
        </section>

      </div>
    );

  }
}
<file_sep>/front/src/core/components/content/Carousel/CarouselComponent.js
import React from 'react';
import {Carousel} from 'react-responsive-carousel';
import './Carousel.scss';


export default class CarouselComponent extends React.Component {
  render() {
    const {...props} = this.props;
    return (
      <Carousel infiniteLoop={true}
                showIndicators={false}
                showThumbs={false}
                emulateTouch={true}
                showStatus={false}
                autoPlay={true}
                {...props}
      />
    )
  }
}<file_sep>/admin/src/i18n.js
exports.generate = function(locale) {
  var capitalizedLocale = locale.replace( /(^|\s)([a-z])/g , function(m,p1,p2){ return p1+p2.toUpperCase(); } ); // en => En
  return [
    `core/security/i18n/securityI18n${capitalizedLocale}`,
    `Main/Layout/i18n/layoutI18n${capitalizedLocale}`,
    `modules/Dashboard/i18n/DashboardI18n${capitalizedLocale}`,
    `modules/MyAccount/i18n/MyAccountI18n${capitalizedLocale}`
  ]
};
<file_sep>/api/hooks/appExtender.js
const path = require('path');

module.exports = function (sails) {
  const loader = require('sails-util-mvcsloader')(sails);
  const sitePath = sails.config.sitePath;
  loader.configure({
    config: path.join(sitePath, '/config'),
    policies: path.join(sitePath, '/api/policies')
  });
  return {
    initialize: function (next) {
      loader.inject({
        controllers: path.join(sitePath, '/api/controllers'),
        models: path.join(sitePath, '/api/models'),
        services: path.join(sitePath, '/api/services')
      }, function (err) {
        return next(err);
      });
    }
  };
};
<file_sep>/admin/src/core/utils/components/FormComponents/Wysiwyg/WysiwygComponent.js
import React from 'react';
import { Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';
import { convertFromHTML, ContentState, convertToRaw, EditorState, getSafeBodyFromHTML, DefaultDraftBlockRenderMap } from 'draft-js';
import {uploadImage} from 'Main/MainUploadService';
import {stateFromHTML} from 'draft-js-import-html';
import {stateToHTML} from 'draft-js-export-html';
import createImagePlugin from 'draft-js-image-plugin';



export default class WysiwygComponent extends React.Component {
  toolbarConfig = {
    options: ['inline', 'blockType', 'fontSize', 'list', 'textAlign', 'link', 'image', 'remove'],
    inline: {
      // inDropdown: false,
      options: ['bold', 'italic', 'underline', 'monospace'],
    },
    blockType: {
      options: [ 'Normal', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Blockquote'],
    },
    fontSize: {
      options: [8, 9, 10, 11, 12, 14, 18, 24, 30, 36, 48, 60, 72, 96],
    },
    fontFamily: {
      options: ['Arial', 'Georgia', 'Impact', 'Tahoma', 'Times New Roman', 'Verdana'],
    },
    list: {
      options: ['unordered', 'ordered'],
    },
    textAlign: {
      inDropdown: true,
      options: ['left', 'center', 'right', 'justify'],
    },
    link: {
      // inDropdown: false,
      options: ['link', 'unlink'],
    },
    image: {
      // urlEnabled: true,
      // uploadEnabled: true,
      alignmentEnabled: true,
      uploadCallback: this.uploadCallback,
    }
  };
  componentWillMount() {
    const {value} = this.props;
    this.state = {editorContent: this.fromHTML(value||'')};
  }
  componentWillReceiveProps(nextProps) {
    if(this.internalChange) this.internalChange = false;
    else this.setState({editorContent: this.fromHTML(nextProps.value)});
  }
  onChange = (editorContent) => {
    this.setState({editorContent});
    this.internalChange = true;
    if (this.props.onChange) this.props.onChange(this.toHTML(editorContent));
  };
  render() {
    const { editorContent } = this.state;
    const { disabled, readonly } = this.state;
    return (
     <div>
       <Editor
         wrapperClassName="wrapper-class"
         editorClassName="editor-class"
         toolbarClassName="toolbar-class"
         toolbar={this.toolbarConfig}
         editorState={editorContent}
         onEditorStateChange={this.onChange.bind(this)}
         readOnly={disabled||readonly}
         plugins={[createImagePlugin()]}
       />
     </div>
    )
  }
  uploadCallback(file) {
    return new Promise(
      (resolve, reject) => {
        uploadImage(file).then(response=>{
          resolve({ data: { link: `/images/${response.data.filename}` } });
        }, reject)
      }
    );
  }
  fromHTML(html) {
    return EditorState.createWithContent(ContentState.createFromBlockArray((convertFromHTML(html))));
  }
  toHTML(editorContent) {
    return draftToHtml(convertToRaw(editorContent.getCurrentContent()));
  }
}
<file_sep>/front/src/core/components/content/Modal/ModalActions.js
export function openModal(config) {
  return {
    type: 'modal/open',
    payload: {config}
  }
}

export function closeModal() {
  return dispatch => {
    dispatch({type: 'modal/close'});
    setTimeout(dispatch, 300, {type: 'modal/reset'}); // for not resetting in fadeOut animation
  }
}
<file_sep>/front/src/core/config/getRoutes.js
import Main from 'Main/MainComponent';
import Home from '../../modules/Home/HomeScreen';
import NotFound from 'Main/NotFound/NotFoundComponent';
import appScreens from '../../screens';


export default function getRoutes(rawRoutes) {
  const appRoutes = rawRoutes.map(route=>transformRoute(route));
  return {
    path: '/',
    name: 'Main',
    breadcrumbName: 'Home',
    component: Main,
    indexRoute: {component: Home, name: 'Home', breadcrumbIgnore: true},
    childRoutes: [
      ...appRoutes,
      {path: '*', component: NotFound}
    ]
  };
}

function transformRoute(route) {
  const newRoute = {
    path: route.path,
    name: route.name,
    component: appScreens[route.componentName] || (props=>props.children),
  };
  if(route.indexRoute) {
    newRoute.indexRoute = {
      name: route.indexRoute.name,
      component: appScreens[route.indexRoute.componentName] || (()=>null)
    }
  }
  if(route.redirectTo) {
    newRoute.onEnter = (nextState, replace) => replace({name: route.redirectTo})
  }
  if(route.childRoutes) {
    newRoute.childRoutes = route.childRoutes.map(route=>transformRoute(route));
  }
  return newRoute;
}<file_sep>/admin/src/Main/MainComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Validate } from 'FormComponents';
import {getModelsConfig, setRouterParams} from './MainActions'

if(typeof isDev === 'undefined') { // if if dev, the vendor files are separated, if not, are included in bundle
  require('../Main/Layout/assets/styles/vendor.js');
}
require('../core/utils/components/native.scss');


@connect(state=>({
  validation: state.modal.config && state.modal.config.validation,
  models: state.main.models,
  currentUserId: state.security.currentUser.id
}), {
  getModelsConfig,
  setRouterParams
})
export default class MainComponent extends Component {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };
  componentWillMount() {
    const {currentUserId, getModelsConfig} = this.props;
    if(currentUserId) getModelsConfig();
    this.setParams();
    this.context.router.listen(()=>{
      setTimeout(() => {
        this.setParams();
      })
    })
  }
  componentWillUpdate(nextProps) {
    const {currentUserId, getModelsConfig, models} = nextProps;
    if(currentUserId && !models) getModelsConfig();
  }
  setParams() {
    const { location, params, routes, routeParams, setRouterParams } = this.props;
    setRouterParams({
      location: {...location},
      params: {...params},
      routes: [...routes],
      routeParams: {...routeParams},
      router: this.context.router
    });
  }
  render() {
    const {models, children, location} = this.props;
    if(!models && !['login'].includes(location.pathname)) return null;
    return children;
  }
}
<file_sep>/api/controllers/GenericModel/deleteMultiple.js
module.exports = function(req, res) {

  const {modelName, ids} = req.params.all();
  const Model = sails.models[modelName.toLowerCase()];

  if(!(ids && ids.length)) return res.badRequest();
  Model.destroy({id: ids}).exec(function(err) {
    if(err) return res.negotiate(err);
    res.ok();
  });

};
<file_sep>/admin/src/Main/Layout/assets/styles/vendor.js
require('normalize.css');
require('./bootstrap.min.css');
//require('./google-fonts.css');
require('./react-redux-toastr.css');
require('./font-awesome.min.css');
require('./react-select.css');
require('./react-datepicker.css');
require('./temp.css');
<file_sep>/front/src/core/config/getTranslations.js
import assignDeep from 'assign-deep';


export default function getTranslations(i18nKeys) {
  const locales = ['en'];
  const translations = {};
  locales.forEach(locale=>{
    require('../../i18n').generate(locale).forEach(link=>{
      translations[locale] = assignDeep(translations[locale], require('../../'+link));
    });
    translations[locale] = assignDeep(translations[locale], i18nKeys);
  });
  return translations;
};


<file_sep>/front/config/mailchimp.js
// module.exports.mailchimp = {
//
//   apiKey: '36e6f4b756ecf08bc12335a6740f9a5b-us14',
//   dc: 'us14',
//   lists: {
//     newsletter: 'd8c6b89744'
//   }
//
// };<file_sep>/front/src/index.js
import 'babel-polyfill';
import App from 'core/config/app';
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';

// render the app
axios.get('/api/config/getData').then(response=>{
  ReactDOM.render(<App initialData={response.data} />, document.getElementById('app'));
});<file_sep>/front/src/core/components/content/Email/EmailComponent.js
import React from 'react';


export default class EmailComponent extends React.Component {
  state = {};
  static propTypes = {
    to: function(props, propName, componentName) {
      if (!/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(props[propName])) {
        return new Error(`Invalid prop '${propName}' supplied to '${componentName}'. The prop is not a valid email.`);
      }
    }
  };
  render() {
    const {to, ...props} = this.props;
    const {hovered} = this.state;
    let href, email;
    if(hovered) {
      href = `mailto:${to}`;
      email = to;
    } else {
      href = '#';
      email = to.replace('@', '[a]');
    }
    return (
      <a {...props} href={href} ref="email">{email}</a>
    )
  }
  componentDidMount() {
    this.refs.email.addEventListener('mouseover', this.hoverEventListener.bind(this));
  }
  componentWillUnmount() {
    this.refs.email.removeEventListener('mouseover', this.hoverEventListener.bind(this));
  }
  hoverEventListener() {
    if(this.state.hovered) return;
    this.setState({hovered: true});
  }
}<file_sep>/api/policies/isNotAuthenticated.js
module.exports = function (req, res, next) {

  if (req.session.passport && req.session.passport.user) return res.forbidden('You should not be authenticated!', 'policy.autenticated');

  return next();

};
<file_sep>/config/imageSizes.js
/**
 * IMAGE SIZES
 * Used for image size regeneration
 * @structure { resizeName: {width, height, crop:true, quality:100, watermark:{top,left,path}} }
 * Watermark Images:
 * app/front/images/watermark.png | 170x70px
 */

module.exports.imageSizes = {
  thumbnail: {
    width: 300,
    height: 300,
    crop: true
  },
  thumb: {
    width: 100,
    height: 100,
    crop: true
  }
};
<file_sep>/front/src/modules/Article/ArticleReducer.js
const initialState = {
  list: null,
  item: null,
  featuredList: null,
  loading: false
};

export default function (state = initialState, action) {
  switch (action.type) {


    // ARTICLE LIST
    case 'article/getPagedList_loading':
      return {
        ...state,
        loading: true
      };
    case 'article/getPagedList_success':
      return {
        ...state,
        list: action.payload.data.list,
        totalPages: action.payload.data.meta.paginate.totalPages,
        loading: false
      };
    case 'article/getPagedList_error':
      return {
        ...state,
        list: null,
        loading: false
      };


    // FEATURED ARTICLE LIST
    case 'article/getFeaturedList_success':
      return {
        ...state,
        featuredList: action.payload.data
      };


    // TAG LIST
    case 'article/getTagList_success':
      return {
        ...state,
        tagList: action.payload.data
      };


    // SINGLE ARTICLE
    case 'article/getItem_success':
      return {
        ...state,
        item: action.payload.data
      };
    case 'article/getItem_error': {
      return {
        ...state,
        item: {notFound: true}
      };
    }


    case 'article/resetItem':
      return {
        ...state,
        item: initialState.item
      };


    default:
      return state;
  }
}<file_sep>/front/src/core/components/content/CodeHighlighter/CodeHighlighterComponent.js
import React from 'react';
import ReactDOM from 'react-dom';
import hljs from 'highlight.js';
import './CodeHighlighter.scss';


export default class CodeHighlighterComponent extends React.Component {

  componentDidMount() {
    this.highlightCode();
  }

  componentDidUpdate() {
    this.highlightCode();
  }

  render() {
    const {children} = this.props;
    return (
      <div dangerouslySetInnerHTML={{__html: children}} />
    )
  }

  highlightCode() {
    const domNode = ReactDOM.findDOMNode(this);
    const nodes = [...domNode.querySelectorAll('pre'), ...domNode.querySelectorAll('code')];
    if (nodes.length > 0) {
      for (var i = 0; i < nodes.length; i = i + 1) {
        hljs.highlightBlock(nodes[i]);
      }
    }
  }

}<file_sep>/admin/src/Main/MainResources.js
import axios from 'axios';

export function getMainMenuResource() {
  return axios.get('/config/mainMenu')
}

export function getModelsConfigResource() {
  return axios.get('/config/models')
}
<file_sep>/front/devServer.js
/* eslint no-console:0 */
'use strict';
const config = require('./webpack/webpack.config.js');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');


new WebpackDevServer(webpack(config), config.devServer)
.listen(config.devServer.port, config.devServer.host, (err) => {
  if (err) console.log(err);
  console.log('Listening at http://'+config.devServer.host+':' + config.devServer.port);
});
<file_sep>/api/controllers/Image/getOriginalFilename.js
function checkIfImageExists(filename, cb) {
  Image.findOne({filename: filename}).then(function(item) {
    cb(!!item);
  })
}

function getCopyIndex(filenameBody) {

  var indexOfLine = filenameBody.lastIndexOf('-');
  if(indexOfLine==-1) return;

  var possibleCopyIndex = filenameBody.substring(indexOfLine+1);
  var indexOfC = possibleCopyIndex.indexOf('c');
  if(indexOfC!=0) return;

  var copyIndexString = possibleCopyIndex.substring(indexOfC+1);
  var copyIndexInt = parseInt(copyIndexString);
  if(copyIndexString!=copyIndexInt) return;

  return ++copyIndexInt;

}

function riseCopyIndex(filename) {

  var lastIndexOfPoint = filename.lastIndexOf('.');
  var filenameBody = filename.substring(0, lastIndexOfPoint)
  var extension = filename.substring(lastIndexOfPoint)
  var copyIndex = getCopyIndex(filenameBody);

  if(copyIndex) filenameBody = filenameBody.substring(0, filenameBody.lastIndexOf('-'))
  else copyIndex = 1;

  return filenameBody + '-c' + copyIndex + extension;

}

function getOriginalFilename(filename, cb) {
  checkIfImageExists(filename, function(exists) {
    if(exists) {
      filename = riseCopyIndex(filename);
      getOriginalFilename(filename, cb)
    } else cb(filename);
  })
}

module.exports =  getOriginalFilename;
<file_sep>/admin/webpack/dev.js
'use strict';

let path = require('path');
let webpack = require('webpack');

// Add needed plugins here
let BowerWebpackPlugin = require('bower-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
let extractCSS = new ExtractTextPlugin('vendor.css');


module.exports = function(config) {
  return {
    entry: [
      'webpack-dev-server/client?http://'+config.host+':' + config.port,
      'webpack/hot/only-dev-server',
      path.join(__dirname, '../src/Main/Layout/assets/styles/vendor'),
      path.join(__dirname, '../src/index.js')
    ],
    cache: true,
    devtool: 'cheap-module-eval-source-map',
    plugins: [
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin(),
      new BowerWebpackPlugin({
        searchResolveModulesDirectories: false
      }),
      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery'
      }),
      new CopyWebpackPlugin([{
        from: 'src/Main/Layout/assets/images/favicon.png', to: 'assets/favicon.png'
      }]),
      new HtmlWebpackPlugin({ // generates index.html in output
        template: 'src/index.ejs',
        filename: 'index.html',
        isDev: true
      }),
      extractCSS
    ],
    module: {
      loaders: [
        {
          test: /\.css$/,
          exclude: [path.join(__dirname, '../src/Main/Layout/assets/styles')],
          loader: 'style-loader!css-loader'
        },
        {
          test: /\.css$/,
          include: [path.join(__dirname, '../src/Main/Layout/assets/styles')],
          loader: extractCSS.extract('style','css')
        },
        {
          test: /\.scss$/,
          loader: 'style-loader!css-loader?sourceMap!sass-loader'
        },
        {
          test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
          loader: "url?limit=10000&mimetype=application/font-woff&name=assets/[hash].[ext]"
        }, {
          test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
          loader: "url?limit=10000&mimetype=application/font-woff&name=assets/[hash].[ext]"
        }, {
          test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
          loader: "url?limit=10000&mimetype=application/octet-stream&name=assets/[hash].[ext]"
        }, {
          test: /\.otf(\?v=\d+\.\d+\.\d+)?$/,
          loader: "url?limit=10000&mimetype=font/opentype&name=assets/[hash].[ext]"
        }, {
          test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
          loader: "file?name=assets/[hash].[ext]"
        }, {
          test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
          include: [path.join(__dirname, '../src/Main/Layout/assets/styles/fonts')],
          loader: 'url?limit=10000&mimetype=image/svg+xml&name=assets/[hash].[ext]'
        },
        {
          test: /\.(svg|png|jpg|gif)$/,
          exclude: [path.join(__dirname, '../src/Main/Layout/assets/styles/fonts')],
          loader: 'url-loader?limit=8192&name=assets/[hash].[ext]'
        },
        { test: /\.ejs$/, loader: 'ejs-loader' },
        {
          test: /\.(js|jsx)$/,
          loader: 'react-hot!babel-loader',
          include: [].concat(
            config.additionalPaths,
            [ path.join(__dirname, '/../src') ]
          )
        }
      ]
    }
  }
};
<file_sep>/admin/src/Main/MainReducer.js
const initialState = {
  mainMenu: null
};

export default function (state = initialState, action) {
  switch (action.type) {


    // GET MAIN MENU
    case 'main/getMainMenu_loading':
      return {
        ...state,
        mainMenuLoading: true
      };
    case 'main/getMainMenu_success':
      return {
        ...state,
        mainMenuLoading: false,
        mainMenu: action.payload.data
      };
    case 'main/getMainMenu_error':
      return {
        ...state,
        mainMenuLoading: false
      };



    case 'main/getModelsConfig_success':
      return {
        ...state,
        models: action.payload.data
      };



    case 'main/setRouterParams': {
      return {
        ...state,
        ...action.payload
      }
    }


    // DEFAULT
    default:
      return state;

  }
}
<file_sep>/api/controllers/GenericModel/saveItem.js
module.exports = function (req, res) {

  const modelData = req.params.all();
  const modelName = modelData.modelName;
  const modelId = modelData.modelId;
  const Model = sails.models[modelName.toLowerCase()];
  delete modelData.modelName;
  delete modelData.modelId;
  const primaryKey = Object.keys(Model.attributes).filter(fieldName=>Model.attributes[fieldName].primaryKey)[0];

  const Request = req.method=='POST' ? Model.create(modelData) : Model.update(modelId, modelData);
  Request
    .then(response => {
      const modelId = req.method=='POST' ? response[primaryKey] : modelData[primaryKey];
      return Model.findOne(modelId).populateAll();
    })
    .then(item => {
      res.ok(item);
    })
    .catch(err => {
      res.negotiate(err);
    });

};
<file_sep>/front/src/core/decorators/metaTagsDecorator.js
import PropTypes from 'prop-types';


export default function (metaTags) {
  console.log('metaTags', metaTags);
  return function setMetaTags(Component) {
    Component.contextTypes = {
      store: PropTypes.object.isRequired
    };
    Component.prototype.componentWillUpdate = props => {
      console.log('props', props);
    };
  }
};<file_sep>/front/src/core/config/rootReducer.js
import { combineReducers } from 'redux';
import { i18nReducer } from 'react-redux-i18n';
import { reducer as formReducer } from 'redux-form';
import appReducers from '../../reducers';
import MainReducer from '../Main/MainReducer';
import ModalReducer from 'core/components/content/Modal/ModalReducer';

export default combineReducers({
  i18n: i18nReducer,
  form: formReducer,
  main: MainReducer,
  modal: ModalReducer,
  ...appReducers
});
<file_sep>/admin/src/Main/Layout/i18n/layoutI18nEn.js
module.exports = {
  field: {
    title: 'Title',
    content: 'Content',
    description: 'Description',
    email: 'Email',
    password: '<PASSWORD>',
    repeatPassword: '<PASSWORD>'
  },
  button: {
    ok: 'Ok',
    save: 'Save',
    reset: 'Reset',
    open: 'Open',
    cancel: 'Cancel',
    close: 'Close',
    add: 'Add'
  },
  validation: {
    required: 'The field is required',
    type: 'The field is not a valid %{type}',
    string: 'The field is not a valid string',
    integer: 'The field is not a valid integer',
    email: 'The field is not a valid email',
    repeatPassword: '<PASSWORD>',
    passwordMatch: '<PASSWORD>',
    unique: 'The field value already exists in database'
  },
  modal: {
    genericTitle: 'Confirmation'
  },
  message: {
    errorTitle: 'Error'
  }
};
<file_sep>/config/env/development.js
module.exports  = {
  models: {
    connection: 'developmentMysql'
  }
};
<file_sep>/admin/src/Main/Layout/Footer/FooterComponent.js
import React, { Component } from 'react';

require('./Footer.scss');

class FooterComponent extends Component {
  render() {
    return (
      <section className="footer">

      </section>
    );
  }
}

export default FooterComponent;
<file_sep>/admin/src/core/security/Register/RegisterActions.js
import { registerUserResource } from './RegisterResources';

export function registerUser(formData) {
  return {type: 'registerUser', payload: new Promise((resolve, reject) => {
    registerUserResource(formData).then(response => {
      resolve(response.data);
    }, reject);
  })};
}
<file_sep>/front/src/modules/AboutUs/AboutUsScreen.js
import React, {Component} from 'react';
import {PageHeader, I18n} from 'core/components/content';
import './AboutUs.scss';


export default class AboutUsScreen extends Component {

  render() {
    return (
      <div>

        <PageHeader title="About Us" />

        <div className="content container">
          <h2>About US</h2>
          <p>Lorem ipsum</p>
          <p>Lorem ipsum</p>
          <hr/>

          {I18n.t('ArticleBrowse.Smecheras')}

          <h2>Team</h2>
          <p>Lorem Ipsum</p>
        </div>

      </div>
    );
  }
}<file_sep>/front/webpack/dev.js
'use strict';

let path = require('path');
let webpack = require('webpack');

// Add needed plugins here
var CopyWebpackPlugin = require('copy-webpack-plugin');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
let extractCSS = new ExtractTextPlugin({filename: 'app.css'});

const devConfig = {
  host: 'localhost',
  port: 8000
};


module.exports = {

  devServer: {
    contentBase: './src/',
    historyApiFallback: true,
    hot: true,
    host: devConfig.host,
    port: devConfig.port,
    noInfo: false,
    compress: true,
    proxy: {
      '/api/*': {
        target: 'http://localhost:9000',
        secure: false
      },
      '/images/*': {
        target: 'http://localhost:9000',
        secure: false
      },
      '/admin/*': {
        target: 'http://localhost:9000',
        secure: false
      }
    }
  },

  entry: [
    'webpack-dev-server/client?http://' + devConfig.host + ':' + devConfig.port,
    'webpack/hot/only-dev-server',
    path.join(__dirname, '../src/Layout/assets/vendor/vendor'),
    path.join(__dirname, '../src/index.js')
  ],

  output: {
    publicPath: `http://${devConfig.host}:${devConfig.port}/`,
    filename: 'app.js'
  },

  cache: true,
  devtool: 'cheap-module-eval-source-map',

  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new CopyWebpackPlugin([{
      from: 'src/Layout/assets/images/favicon.png', to: 'assets/favicon.png'
    }]),
    extractCSS
  ],

  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        enforce: 'pre',
        include: path.join(__dirname, '../src'),
        loader: 'eslint-loader'
      },
      {
        test: /\.css$/,
        exclude: [path.join(__dirname, '../src/Layout/assets/vendor')],
        loader: 'style-loader!css-loader'
      },
      {
        test: /\.css$/,
        include: [path.join(__dirname, '../src/Layout/assets/vendor')],
        loader: extractCSS.extract({fallback: 'style-loader', use: 'css-loader'})
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {loader: 'css-loader', options: {sourceMap: true}},
          {loader: 'sass-loader', options: {outputStyle: 'expanded', data: `
            @import "${path.join(__dirname, '../src')}/core/Main/Main.scss";
            @import "${path.join(__dirname, '../src')}/Layout/LayoutVariables.scss";
          `}}
        ]
      },
      {
        test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url-loader?limit=10000&mimetype=application/font-woff&name=fonts/[hash].[ext]'
      }, {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url-loader?limit=10000&mimetype=application/octet-stream&name=fonts/[hash].[ext]'
      }, {
        test: /\.otf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url-loader?limit=10000&mimetype=font/opentype&name=fonts/[hash].[ext]'
      }, {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'file-loader?name=fonts/[hash].[ext]'
      }, {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        include: [path.join(__dirname, '../src/Layout/assets/vendor/fonts')],
        loader: 'url-loader?limit=10000&mimetype=image/svg+xml&name=fonts/[hash].[ext]'
      },
      {
        test: /\.(svg|png|jpg|gif)$/,
        exclude: [path.join(__dirname, '../src/Layout/assets/vendor/fonts')],
        loader: 'file-loader?limit=8192&name=assets/[name].[ext]'
      },
      {
        test: /\.(html|ejs)$/,
        loader: 'null-loader'
      },
      {
        test: /\.(js|jsx)$/,
        loader: 'react-hot-loader!babel-loader',
        include: [path.join(__dirname, '../src')]
      }
    ]
  }

};
<file_sep>/admin/src/core/security/ResetPassword/ResetPasswordComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { Field } from 'FormComponents'
import { Link } from 'react-router';
import { I18n } from 'react-redux-i18n';
import SecurityLayout from '../securityLayoutComponent';
import { generateResetPasswordToken } from './ResetPasswordActions';


class ResetPasswordComponent extends Component {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };

  componentWillMount() {
    document.title = I18n.t('resetPassword.title');
  }

  componentWillUnmount() {
    this.props.dispatch({type: 'resetPassword_reset'});
  }

  onSubmit(formData) {
    this.props.generateResetPasswordToken(formData.email)
      .then(() => this.context.router.replace({name: 'Login'}));
  }

  render() {
    const { handleSubmit, message} = this.props;
    //console.log(message);
    return (
      <SecurityLayout>
          <div className="security-box">
            <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
              <h2>{I18n.t('resetPassword.title')}</h2>
              <Field type="email" name="email" placeholder={I18n.t('login.placeholder.email')} />
              { message && message.type == 'error' && <div className="security-message-error">{I18n.t(message.t)}</div> }
              <button type="submit">{I18n.t('button.recoverPassword')}</button>
            </form>
            <div className="login-meta">
              Back to <Link to={{name: 'Login'}}>{I18n.t('button.login')}</Link>
            </div>
          </div>
      </SecurityLayout>
    );
  }
}

function validate(values) {
  const errors = {};

  if(!values.email)
    errors.email = 'Please enter your email';
  else if (!/^[_a-zA-Z0-9]+(\.[_a-zA-Z0-9]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,20})$/.test(values.email))
    errors.email = 'The email entered is not valid';

  return errors;
}

export default connect(state => ({
  message: state.security.resetPassword.message
}), { generateResetPasswordToken })(reduxForm({form: 'ResetPasswordForm', validate})(ResetPasswordComponent));
<file_sep>/admin/src/modules/Model/ModelReducer.js
import {getModelPrimaryKey} from 'helpers';

const initialState = {
  pagedList: {},
  totalPages: {},
  list: {},
  listLoading: false,
  item: null,
  itemLoading: false,
  saveLoading: false,
  deleteLoading: false
};

export default function (state = initialState, action) {
  let modelName;
  let modelPK;
  switch (action.type) {


    // GET PAGED LIST
    case 'model/getPagedList_loading': {
      return {
        ...state,
        listLoading: true
      }
    }
    case 'model/getPagedList_success': {
      return {
        ...state,
        listLoading: false,
        pagedList: {
          ...state.pagedList,
          [action.meta.modelName]: action.payload.data.list
        },
        totalPages: {
          ...state.totalPages,
          [action.meta.modelName]: action.payload.data.meta.paginate.totalPages
        }
      }
    }
    case 'model/getPagedList_error': {
      return {
        ...state,
        listLoading: false
      }
    }



    // GET LIST
    case 'model/getList_loading': {
      return {
        ...state,
        listLoading: true
      }
    }
    case 'model/getList_success': {
      return {
        ...state,
        listLoading: false,
        list: {
          ...state.list,
          [action.meta.modelName]: action.payload.data
        }
      }
    }
    case 'model/getList_error': {
      return {
        ...state,
        listLoading: false
      }
    }



    // APPEND ITEM TO LIST
    case 'model/appendItemToList': {
      return {
        ...state,
        pagedList: {
          ...state.pagedList,
          [action.payload.modelName]: [
            ...state.pagedList[action.payload.modelName],
            action.payload.item
          ]
        }
      }
    }


    // REPLACE ITEM IN LIST
    case 'model/replaceItemInList': {
      modelName = action.payload.modelName;
      modelPK = getModelPrimaryKey(modelName);
      return {
        ...state,
        pagedList: {
          ...state.pagedList,
          [modelName]: state.pagedList[modelName].map(item=>{
            if(item[modelPK]==action.payload[modelPK]) return action.payload.item;
            else return {...item};
          })
        }
      }
    }


    // REMOVE ITEM FROM
    case 'model/removeItemFromList': {
      modelName = action.payload.modelName;
      modelPK = getModelPrimaryKey(modelName);
      return {
        ...state,
        pagedListw: {
          ...state.pagedList,
          [action.payload.modelName]: state.pagedList[action.payload.modelName].filter(item=>{
            return item[modelPK]!=action.payload[modelPK]
          })
        }
      }
    }


    // GET ITEM
    case 'model/getItem_loading': {
      return {
        ...state,
        itemLoading: true
      }
    }
    case 'model/getItem_success': {
      return {
        ...state,
        itemLoading: false,
        item: action.payload.data
      }
    }
    case 'model/getItem_error': {
      return {
        ...state,
        itemLoading: false
      }
    }


    // SAVE ITEM
    case 'model/saveModel_loading': {
      return {
        ...state,
        saveLoading: true
      }
    }
    case 'model/saveModel_success': {
      return {
        ...state,
        saveLoading: false,
        item: action.payload.data
      }
    }
    case 'model/saveModel_error': {
      return {
        ...state,
        saveLoading: false
      }
    }


    // DELETE ITEM
    case 'model/deleteItem_loading': {
      return {
        ...state,
        deleteLoading: true
      }
    }
    case 'model/deleteItem_success': {
      return {
        ...state,
        deleteLoading: false
      }
    }
    case 'model/deleteItem_error': {
      return {
        ...state,
        deleteLoading: false
      }
    }



    case 'model/resetPagedList': {
      return {
        ...state,
        pagedList: initialState.pagedList
      }
    }




    // DEFAULT
    default:
      return state;

  }
}
<file_sep>/admin/src/modules/Model/templates/imageList/ModelImageListTemplate.js
import React from 'react';
import {Link} from 'react-router';
import {connect} from 'react-redux';
import {deleteItem, appendItemToList, replaceItemInList, removeItemFromList} from '../../ModelActions';
import {I18n} from 'react-redux-i18n';
import Item from './ModelImageListItemTemplate';
import Dropzone from 'react-dropzone';
import {uploadImage} from '../../../../Main/MainUploadService';

require('./ModelImageList.scss');


@connect((state, props)=>({
  models: state.main.models,
  list: state.model.pagedList[props.modelName],
}), {
  deleteItem,
  appendItemToList,
  replaceItemInList,
  removeItemFromList
})
export default class ModelImageListTemplate extends React.Component {

  state = { rejectedFiles: [] };

  render() {
    const {checked, list, deleteItems, modelName} = this.props;
    const dropzoneConfig = {
      disableClick: true,
      maxSize: 10 * 1024 * 1024,
      onDrop: this.onDrop.bind(this),
      style: {},
      activeClassName: 'active'
    };
    return (
      <div>

        <div className="list-actions">
          {!!Object.keys(checked).length && (
            <div className="check-actions">
              <button className="button" onClick={deleteItems}>Delete</button>
            </div>
          )}
          <div className="permanent-actions">
            <button className="button" onClick={()=>this.dropzone.open()}>Upload</button>
          </div>
        </div>

        <Dropzone className="model-image-list" ref={dz=>this.dropzone=dz} {...dropzoneConfig}>
          {list && list.map((image, k)=>(
            <div key={image.id}>
              <Item k={k} image={image} modelName={modelName} />
            </div>
          ))}
        </Dropzone>

      </div>
    )
  }

  onDrop(acceptedFiles, rejectedFiles) {
    const {modelName, appendItemToList, replaceItemInList, removeItemFromList} = this.props;
    acceptedFiles.forEach(file=>{
      const fileItem = {
        id: new Date().getTime(),
        fileName: file.name,
        preview: file.preview
      };
      appendItemToList(modelName, fileItem);
      uploadImage(file)
        .then(function (res) {
          // replaceItemInList()
        })
        .catch(function (err) {
          this.setState({
            rejectedFiles: [
              ...this.state.rejectedFiles,
              file
            ]
          });
        });
    });
    this.setState({
      rejectedFiles: [
        ...this.state.rejectedFiles,
        ...rejectedFiles
      ]
    });
  }

}
<file_sep>/admin/src/reducers.js
import MyAccountReducer from 'modules/MyAccount/MyAccountReducer';
import ModelReducer from 'modules/Model/ModelReducer';

export default {
  myAccount: MyAccountReducer,
  model: ModelReducer,
}
<file_sep>/api/services/HelperService.js
module.exports = {
  getParsedModelData: function(model) {

    if(!model.globalId) return;

    const modelName = model.globalId;
    const modelAttributes = Object.assign({}, model.attributes);
    const modelConfig = Object.assign({}, model.config);
    const modelStructure = Object.assign({}, model.structure);

    const modelData = {};

    // FIELDS
    modelData.fields = {};
    _.forEach(modelAttributes, function(data, fieldName) {
      if(fieldName == 'i18n') return;
      modelData.fields[fieldName] = {
        config: {
          list: data.config && data.config.list || {},
          edit: data.config && data.config.edit || {},
          importExport: data.config && data.config.importExport || {},
        },
        validation: _.clone(data) || {}
      };
      if(data.config) delete modelData.fields[fieldName].validation.config;
    });


    // CONFIG DEFAULTS
    modelData.config = modelConfig || {};
    const primaryKey = Object.keys(modelData.fields).filter(fieldName=>modelData.fields[fieldName].validation.primaryKey)[0];
    modelData.config.primaryKey = primaryKey;
    const defaultField = modelData.fields.name ? 'name' : modelData.config.primaryKey;
    if(!modelData.config.defaultField) modelData.config.defaultField = defaultField;
    if(!modelData.config.defaultParams) modelData.config.defaultParams = {};
    if(!modelData.config.crud) modelData.config.crud = {};
    if(!modelData.config.crud.create) modelData.config.crud.create = true;
    if(!modelData.config.crud.read) modelData.config.crud.read = true;
    if(!modelData.config.crud.update) modelData.config.crud.update = true;
    if(!modelData.config.crud.delete) modelData.config.crud.delete = true;
    if(!modelData.config.list) modelData.config.list = {};
    if(!modelData.config.list.template) modelData.config.list.template = 'default';
    if(!modelData.config.viewOnSite) modelData.config.viewOnSite = null;


    // STRUCTURE
    modelData.structure = modelStructure || {};
    if(!modelData.structure.list) modelData.structure.list = [defaultField];
    if(!modelData.structure.edit) modelData.structure.edit = [{fields: [defaultField]}];


    // I18N FIELDS INTO MAIN FIELDS
    if(modelData.config.hasI18n) {
      const Submodel = sails.models[modelName.toLowerCase()+'i18n'];
      const submodelData = this.getParsedModelData(Submodel);
      _.forEach(submodelData.fields, function(v, k) {
        if(['id', 'i18nParent', 'locale'].indexOf(k)>-1) return;
        v.config.isI18n = true;
        modelData.fields[k] = v;
      })
    }

    return modelData;

  },
  getPageData() {
    return new Promise((resolve, reject)=>{
      Page.query(`
      SELECT
      p.id,
      p.name,
      p.metaTitle,
      p.metaDescription,
      p.breadcrumbName,
      p.parent,
      p.slug,
      p.routeName,
      pt.componentName AS componentName,
      p.templateI18n,
      p.redirectTo,
      p.indexRouteName,
      pit.componentName AS indexComponentName,
      p.indexTemplateI18n,
      p.sitemapModel
      FROM page AS p
      LEFT JOIN pagetemplate AS pt ON p.template = pt.id 
      LEFT JOIN pagetemplate AS pit ON p.indexTemplate = pit.id
    `).then(pages=>{

        const i18nKeys = {};
        const pagesByParent = {};
        pages.forEach(page=>{
          const pageParent = page.parent || '-';
          pagesByParent[pageParent] = pagesByParent[pageParent] || [];
          if(page.routeName) {
            if(!['Layout', 'Home'].includes(page.routeName)) pagesByParent[pageParent].push(page);
            const i18nRouteName = page.routeName.charAt(0).toLowerCase() + page.routeName.slice(1);
            i18nKeys[i18nRouteName] = page.templateI18n ? JSON.parse(page.templateI18n) : {};
          }
          if(page.indexRouteName) {
            const i18nIndexRouteName = page.indexRouteName.charAt(0).toLowerCase() + page.indexRouteName.slice(1);
            i18nKeys[i18nIndexRouteName] = page.indexTemplateI18n ? JSON.parse(page.indexTemplateI18n) : {};
          }
        });

        const appRoutes = pagesByParent['-'].map(route=>constructRoute(route));

        resolve({appRoutes, i18nKeys});

        function constructRoute(route) {
          const newRoute = {
            path: route.slug,
            name: route.routeName,
            componentName: route.componentName,
            breadcrumbName: route.breadcrumbName
          };
          if(route.sitemapModel) {
            newRoute.sitemapModel = JSON.parse(route.sitemapModel);
          }
          if(route.redirectTo) {
            newRoute.redirectTo = route.redirectTo;
          }
          if(route.indexRouteName) {
            newRoute.indexRoute = {
              name: route.indexRouteName,
              componentName: route.indexComponentName,
              metaTitle: route.metaTitle,
              metaDescription: route.metaDescription
            }
          } else {
            newRoute.metaTitle = route.metaTitle;
            newRoute.metaDescription = route.metaDescription;
          }
          if(pagesByParent[route.id]) {
            newRoute.childRoutes = pagesByParent[route.id].map(route=>constructRoute(route));
          }
          return newRoute;
        }

      }).catch(reject);
    })
  }
};
<file_sep>/admin/src/core/utils/components/FormComponents/JSONTemplate/templates/JSONTemplateChildren.js
import React from 'react';
import {connect} from 'react-redux';
import {Field} from 'core/utils/components/FormComponents';
import {formValueSelector, FieldArray} from 'redux-form';
import Fields from './JSONTemplateFields';


@connect((state, {field})=>({
  type: formValueSelector('TabsForm')(state, `${field}.type`)
}))
export default class JSONTemplateObjectType extends React.Component {
  render() {
    const {field, type} = this.props;
    if(!['object', 'objectList'].includes(type)) return null;
    return (
      <FieldArray name={`${field}.children`} component={({fields})=><Fields fields={fields} />} />
    )
  }

}<file_sep>/admin/src/core/utils/components/FormComponents/JSONTemplate/JSONTemplateComponent.js
import React from 'react';
import {Field} from 'core/utils/components/FormComponents';
import classnames from 'classnames';
import './JSONTemplate.scss';
import Fields from './templates/JSONTemplateFields';


export default class JSONTemplate extends React.Component {
  render() {
    const {label, fields, meta: {error, touched}} = this.props;
    return (
      <div className={classnames('field-group', 'fg-name-'+name, {'fg-invalid': error, 'fg-touched': touched})}>
        { label && <label>{label}</label> }
        <div>
          <Fields fields={fields} />
          { touched && error && <span className="fg-error">{error}</span> }
        </div>
      </div>
    )
  }
}
<file_sep>/api/controllers/GenericModel/getItem.js
module.exports = function (req, res) {

  const {modelName, modelId} = req.params.all();

  const Model = sails.models[modelName.toLowerCase()];
  const modelConfig = HelperService.getParsedModelData(Model).config.edit || {};

  Model.findOne(modelId).populateAll().then(data => {
    res.ok(data);
  }).catch(err => {
    res.negotiate(err);
  });

};
<file_sep>/admin/src/core/security/Register/RegisterResources.js
import axios from 'axios';

export function registerUserResource(formData) {
  return axios({
    method: 'POST',
    url: '/user/create',
    data: formData
  });
}
<file_sep>/admin/src/modules/Model/ModelActions.js
import { getPagedListResource, getListResource, getItemResource, createItemResource, updateItemResource, deleteItemResource, deleteItemsResource } from './ModelResources';


export function getPagedList(modelName, query) {
  return {
    type: 'model/getPagedList',
    payload: getPagedListResource(modelName, query),
    meta: { modelName }
  }
}

export function getList(modelName) {
  return {
    type: 'model/getList',
    payload: getListResource(modelName),
    meta: { modelName }
  }
}

export function appendItemToList(modelName, item) {
  return {
    type: 'model/appendItemToList',
    payload: {modelName, item}
  }
}

export function replaceItemInList(modelName, id, item) {
  return {
    type: 'model/replaceItemInList',
    payload: {modelName, id, item}
  }
}

export function removeItemFromList(modelName, id) {
  return {
    type: 'model/removeItemFromList',
    payload: {modelName, id}
  }
}

export function getItem(modelName, modelId) {
  return {
    type: 'model/getItem',
    payload: getItemResource(modelName, modelId)
  }
}

export function createItem(modelName, data) {
  return {
    type: 'model/saveItem',
    payload: createItemResource(modelName, data)
  }
}

export function updateItem(modelName, modelId, data) {
  return {
    type: 'model/saveItem',
    payload: updateItemResource(modelName, modelId, data)
  }
}

export function deleteItem(modelName, modelId) {
  return {
    type: 'model/deleteItem',
    payload: deleteItemResource(modelName, modelId)
  }
}

export function deleteItems(modelName, modelIds) {
  return {
    type: 'model/deleteItems',
    payload: deleteItemsResource(modelName, modelIds)
  }
}

export function resetPagedList() {
  return {
    type: 'model/resetPagedList'
  }
}
<file_sep>/api/controllers/Image/ImageMinify.js
const fs = require('fs');
const imagemin = require('imagemin');
const imageminJpeg = require('imagemin-jpeg-recompress');
const imageminPng = require('imagemin-optipng');


module.exports = function(src, dest, quality) {
  if(!fs.existsSync(src)) return res.badRequest('There is no image on server to be minified.')
  if(!quality) quality = 100;
  return imagemin([src], dest, {
    use: [
      imageminJpeg({max: quality, method: 'smallfry'}),
      imageminPng({optimizationLevel: 4})
    ]
  });
};
<file_sep>/front/src/modules/Article/templates/ArticleSocial.js
import React from 'react';
import PropTypes from 'prop-types';
import {ShareButtons} from 'react-share';
import classnames from 'classnames';
import {excerpt} from 'core/utils/filters';
const {FacebookShareButton, GooglePlusShareButton, LinkedinShareButton, TwitterShareButton} = ShareButtons;

export default class ArticleSocial extends React.Component {
  static propTypes = {
    url: PropTypes.string,
    title: PropTypes.string,
    description: PropTypes.string,
    big: PropTypes.bool
  };
  socialShares = [
    {icon: 'icon-facebook', Component: FacebookShareButton},
    {icon: 'icon-linkedin', Component: LinkedinShareButton},
    {icon: 'icon-twitter', Component: TwitterShareButton},
    {icon: 'icon-google-plus', Component: GooglePlusShareButton}
  ]

  render() {
    const {url, title, description, big} = this.props;
    return (
      <div className={classnames('article-social', {big})}>
        { this.socialShares.map((i, k) => (
          <i.Component key={k}
                       url={url}
                       title={title}
                       description={excerpt(description, 200)}><i className={i.icon}/></i.Component>
        )) }
      </div>
    )
  }
}<file_sep>/admin/webpack/webpack.config.js
'use strict';

/**
 * Set the environment
 */
const args = require('minimist')(process.argv.slice(2));
const allowedEnvs = ['dev', 'dist', 'test'];
let env;
if (args._.length > 0 && args._.indexOf('start') !== -1) {
  env = 'test';
} else if (args.env && args.env && args.env.length > 0 && allowedEnvs.indexOf(args.env) !== -1) {
  env = args.env;
} else {
  env = 'dev';
}
process.env.REACT_WEBPACK_ENV = env;



/**
 * Build the webpack configuration
 * @param  {String} wantedEnv The wanted environment
 * @return {Object} Webpack config
 */
const path = require('path');
const _ = require('lodash');
const config = {
  srcPath: path.join(__dirname, '../src'),
  //host: require('ip').address(),
  host: 'localhost',
  port: args.port || 8001,
  proxyPort: args.proxyPort || 9000,
  additionalPaths: []
};


module.exports = _.merge(
  {},
  config,
  {
    port: config.port,
    debug: true,
    output: {
      path: path.join(__dirname, '../dist'),
      filename: 'app.js',
      libraryTarget: 'var',
      library: 'XLSX'
    },
    devServer: {
      contentBase: './src/',
      historyApiFallback: true,
      hot: true,
      port: config.port,
      noInfo: false,
      compress: true,
      proxy: {
        '/api/*' : {
          target: `http://localhost:${config.proxyPort}`,
          secure: false
        },
        '/images/*' : {
          target: `http://localhost:${config.proxyPort}`,
          secure: false
        },
      }
    },
    resolve: {
      extensions: ['', '.js', '.jsx'],
      alias: {
        core: `${config.srcPath}/core`,
        Main: `${config.srcPath}/Main`,
        Layout: `${config.srcPath}/Main/Layout`,
        modules: `${config.srcPath}/modules`,
        ContentComponents: `${config.srcPath}/core/utils/components/ContentComponents`,
        FormComponents: `${config.srcPath}/core/utils/components/FormComponents`,
        filters: `${config.srcPath}/core/utils/filters`,
        helpers: `${config.srcPath}/core/utils/helpers`
      }
    },
    node: {
      fs: false,
      Buffer: false
    },
    externals: [
      {
        './cptable': 'var cptable',
        '../xlsx.js': 'var _XLSX'
      }
    ],
    module: {
      noParse: [/jszip.js$/]
      // preLoaders: [
      //   {
      //     test: /\.(js|jsx)$/,
      //     include: config.srcPath,
      //     loader: 'eslint-loader'
      //   }
      // ]
    }
  },
  require(path.join(__dirname, './' + env))(config)
);
<file_sep>/front/src/modules/Contact/ContactScreen.js
import React, { Component } from 'react';
import {connect, reduxForm} from 'core/decorators';
import axios from 'axios';
import {Validate, Field, SubmitButton} from 'core/components/form';
import {PageHeader, Map} from 'core/components/content';
import './Contact.scss';


@connect()
@reduxForm({form: 'ContactForm', validate: Validate({
  name: {required: true},
  email: {required: true, email: true},
  message: {required: true}
})})
export default class ContactScreen extends Component {

  componentWillMount() {
    this.state = {loading: false};
  }

  render() {
    const { handleSubmit } = this.props;
    const { sending, successMessage } = this.state;
    return (
      <div>

        <PageHeader title="Contact" />

        <div className="content contact-form">
          <div className="container-600">

            <form onSubmit={handleSubmit(this.onSubmit.bind(this))} noValidate>
              <div className="row row-sm">
                <div className="col-xs-6">
                  <Field name="name" placeholder="Name*" />
                  <Field name="email" placeholder="Email*" />
                  <Field name="phone" placeholder="Phone number" />
                </div>
                <div className="col-xs-6">
                  <Field name="company" placeholder="Company name" />
                  <Field name="message" type="textarea" placeholder="Message*" />
                </div>
              </div>
              <div className="text-center">
                <SubmitButton className="button button-normal" loading={sending}>SEND</SubmitButton>
              </div>
              <div className="message">
                {successMessage && 'The message has been sent !'}
              </div>
            </form>

          </div>
        </div>

        <Map className="contact-map" lat={44.4340583} lng={26.0677452} zoom={15} height={400} scrollWheel={false} />

      </div>
    );
  }

  onSubmit(formData) {
    this.setState({sending: true});
    axios.post('/sendContactForm', formData).then(()=>{
      this.setState({sending: false, successMessage: true});
      setTimeout(() => {
        this.setState({successMessage: false});
      }, 3000);
      this.props.reset();
    });
  }

}<file_sep>/admin/src/Main/Layout/Header/templates/LanguageSwitcherComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setLocale } from 'react-redux-i18n';
import { DropdownButton, MenuItem } from 'react-bootstrap';
import ReactDOM from 'react-dom';
import App from 'core/config/app';


class LanguageSwitcherComponent extends Component {

  changeLocale(locale) {
    this.props.setLocale(locale);
    // Rerender whole App
    ReactDOM.unmountComponentAtNode(document.querySelector('#app'));
    ReactDOM.render(<App />, document.querySelector('#app'));
  }

  render() {
    return (
      <DropdownButton title={this.props.locale} className="language-switcher" id="language-switcher" pullRight onSelect={this.changeLocale.bind(this)}>
        <MenuItem eventKey="ro">Ro</MenuItem>
        <MenuItem eventKey="en">En</MenuItem>
      </DropdownButton>
    )
  }

}

export default connect(state=>({
  locale: state.i18n.locale
}), {
  setLocale
})(LanguageSwitcherComponent);
<file_sep>/admin/src/core/utils/components/ContentComponents/Navigation/NavigationComponent.js
import React, { Component } from 'react';
import { I18n } from 'react-redux-i18n';
import { Link } from 'react-router';
import classNames from 'classnames';

require('./Navigation.scss');

export default class NavigationComponent extends Component {
  renderItem(items) {
    return (
      items.map((item, k) => {
        if(typeof item.if !== 'undefined' && !item.if) return null;
        var Element;
        var props = {};
        if(item.route) {
          Element = Link;
          props.to = item.route;
          props.activeClassName = 'active';
        } else {
          Element = 'a';
          if(item.href) props.href = item.href;
          else if(item.click) props.onClick = item.click;
        }
        return (
          <li key={k} className={classNames(item.class, {'has-submenu': item.subitems})}>
            <Element {...props}>
              { item.icon && <i className={classnames('menu-icon', item.icon)} /> }
              <span>{ item.translate ? I18n.t(item.translate) : item.display }</span>
              { item.subitems && <span className="menu-arrow" /> }
            </Element>
            { item.subitems && <ul className="submenu">{this.renderItem(item.subitems)}</ul> }
          </li>
        );
      })
    );
  }
  render() {
    const {links} = this.props;
    if(!links) return null;
    return <ul className="navigation">{this.renderItem(links)}</ul>;
  }
}
<file_sep>/admin/src/core/security/i18n/securityI18nEn.js
module.exports = {
  button: {
    login: 'Login',
    signUp: 'Sign Up',
    recoverPassword: '<PASSWORD>',
    resetPassword: '<PASSWORD>'
  },
  login: {
    title: 'Login',
    welcomeTitle: 'Welcome to',
    placeholder: {
      email: 'type your email',
      password: '<PASSWORD>'
    },
    errorCredentialsMessage: 'The email or password are invalid',
    errorGenericMessage: 'Generic Login Fail. Please try again later.',
    successMessage: 'You have successfully logged in!',
    logoutSuccessMessage: 'You have successfully logged out!',
    logoutByTimeoutMessage: 'You have been logged out. Your session has expired.'
  },
  register: {
    title: 'Register'
  },
  resetPassword: {
    title: 'Reset Password',
    message: {
      emailNotExist: 'The email does not exist'
    }
  },
  tokenNewPassword: {
    title: 'New Password',
    placeholder: {
      passwordConfirmation: '<PASSWORD>'
    }
  }
};
<file_sep>/front/src/modules/Article/templates/Item/ArticleItem.js
import React from 'react';
import PropTypes from 'prop-types';
import { I18n } from 'react-redux-i18n';
import { Link } from 'react-router';
import { imageUrl } from 'core/utils/filters';
import moment from 'moment';
import ArticleSocial from '../ArticleSocial';
import ArticleImage from './ArticleItemImage';
import './ArticleItem.scss';


export default class ArticleItem extends React.Component {
  static contextTypes = {
    router: PropTypes.object
  };
  render() {
    const { article } = this.props;
    const { router } = this.context;
    const articleImage = article.featuredImage ? imageUrl(article.featuredImage.filename, 'list') : require('../../assets/article-placeholder-list.jpg');
    const articleUrl = typeof window !== 'undefined' ? window.location.origin.replace('https', 'http') + router.createHref({name: 'ArticleView', params: {slug: article.slug}}) : '';
    return (
      <li className="article">

        <div className="article-helper-white"><div/></div>
        <div className="article-helper-blue"><div/></div>

        <div className="article-meta">
          <h3>
            <Link to={{name: 'ArticleView', params: {slug: article.slug}}}>{article.name}</Link>
          </h3>
          <div className="article-date">
            <span>{moment(article.publishedDate).format('YYYY')}</span>
            <span>{moment(article.publishedDate).format('MMM')}</span>
            <span>{moment(article.publishedDate).format('DD')}</span>
          </div>
          <div className="article-author">
            <i className="icon-user"/>
            <span>Gentlab</span>
          </div>
        </div>

        <div className="row row-sm">
          <div className="col-xs-6">
            <Link to={{name: 'ArticleView', params: {slug: article.slug}}}>
              <ArticleImage src={articleImage}/>
            </Link>
          </div>
          <div className="col-xs-6">
            {!!article.tags.length && (
              <div className="article-tags">
                Tags:
                {article.tags.map((tag, k)=>(
                  <span key={k}>
                    <Link to={{name: 'ArticleBrowseTag', params: {tagSlug: tag.slug}}}>{tag.name}</Link>
                    {k !== article.tags.length-1 && ', '}
                  </span>
                ))}
              </div>
            )}
            <p>{article.content}</p>
            <div className="article-actions">
              <Link className="button" to={{name: 'ArticleView', params: {slug: article.slug}}}>{I18n.t('button.readMore')}</Link>
              <ArticleSocial url={articleUrl} title={article.name} description={article.content}/>
            </div>
          </div>
        </div>
      </li>
    )
  }
}<file_sep>/front/src/core/config/getHistory.js
import { createHistory } from 'history';
import { useRouterHistory } from 'react-router';
import useNamedRoutes from 'use-named-routes';

export default function getHistory(routes) {
  const history = useNamedRoutes(useRouterHistory(createHistory))({ basename: '/', routes });
  history.listen(location => location.action === 'PUSH' && window.scrollTo(0, 0)); // scroll to top on location change
  return history
}
<file_sep>/front/src/Layout/Header/Header.js
import React from 'react';
import {Link} from 'react-router';
import {connect} from 'core/decorators';
import { openMenu } from 'core/Main/MainActions';
import {Navigation} from 'core/components/content';
import {mainMenu} from '../LayoutConstants';
import './Header.scss';


@connect(state=>({
  menuOpened: state.main.menuOpened,
  headerContent: state.main.headerContent
}), {
  openMenu
})
export default class Header extends React.Component {

  render() {
    const {openMenu} = this.props;
    return (
      <div>
        <div className="header">
          <div className="container">

            <button className="mobile-menu-button" onClick={openMenu}>Menu</button>

            <Link to="/" className="header-logo">
              <img src={require('./assets/logo-gentlab-white.svg')} alt="White Logo Gentlab" />
            </Link>

            <Navigation className="main-menu" links={mainMenu} />

          </div>
        </div>
        <div className="section-indent si-main-menu"><div className="container"><div /></div></div>
      </div>
    );
  }

}
<file_sep>/admin/src/modules/Model/ModelResources.js
import axios from 'axios';

export function getPagedListResource(modelName, {page=1, perPage=30, sort, ...where}) {
  return axios({
    method: 'GET',
    url: `${modelName}/list/paged`,
    params: { page, perPage, sort, where: JSON.stringify(where) }
  })
}

export function getListResource(modelName) {
  return axios({
    method: 'GET',
    url: `${modelName}/list`
  })
}

export function getItemResource(modelName, modelId) {
  return axios({
    method: 'GET',
    url: `${modelName}/get/${modelId}`
  })
}

export function createItemResource(modelName, data) {
  return axios({
    method: 'POST',
    url: `${modelName}/create`,
    data
  })
}

export function updateItemResource(modelName, modelId, data) {
  return axios({
    method: 'PUT',
    url: `${modelName}/update/${modelId}`,
    data
  })
}

export function deleteItemResource(modelName, modelId) {
  return axios({
    method: 'DELETE',
    url: `${modelName}/delete/${modelId}`
  })
}

export function deleteItemsResource(modelName, modelIds) {
  return axios({
    method: 'DELETE',
    url: `${modelName}/delete/multiple`,
    data: { ids: modelIds }
  })
}
<file_sep>/admin/src/modules/Model/ModelRoutes.js
import Model from './ModelModule';
import ModelList from './ModelListScreen';
import ModelEdit from './ModelEditScreen';
import ModelImport from './ModelImportScreen';

export default {
  path: 'model/:modelName',
  name: 'Model',
  component: Model,
  indexRoute: {
    name: 'ModelList',
    component: ModelList
  },
  childRoutes: [
    {
      path: 'add',
      name: 'ModelAdd',
      component: ModelEdit
    },
    {
      path: 'import',
      name: 'ModelImport',
      component: ModelImport
    },
    {
      path: ':modelId',
      name: 'ModelEdit',
      component: ModelEdit
    }
  ]
}
<file_sep>/admin/src/core/security/Register/RegisterComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { Field, SubmitButton } from 'FormComponents';
import { Link } from 'react-router';
import { I18n } from 'react-redux-i18n';
import SecurityLayout from '../securityLayoutComponent';
import { registerUser } from './RegisterActions';


class RegisterComponent extends Component {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };

  componentWillMount() {
    document.title = 'Register';
  }

  onSubmit(formData) {
    this.props.registerUser(formData).then(() => {
      this.context.router.replace({name: 'Login'});
    });
  }

  render() {
    const { handleSubmit } = this.props;

    return (
      <SecurityLayout>

          <div className="security-box">
            <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
              <h2>{I18n.t('register.title')}</h2>
              <Field type="text" name="firstName" placeholder={I18n.t('field.firstName')} />
              <Field type="text" name="lastName" placeholder={I18n.t('field.lastName')} />
              <Field type="email" name="email" placeholder={I18n.t('field.email')} />
              <Field type="password" name="password" placeholder={I18n.t('field.password')} />
              <Field type="password" name="repeatedPassword" placeholder={I18n.t('field.repeatPassword')} />
              <SubmitButton>{I18n.t('button.register')}</SubmitButton>
            </form>
            <div className="login-meta">
              Back to <Link to={{name: 'Login'}}>{I18n.t('button.login')}</Link>
            </div>
          </div>

      </SecurityLayout>
    );
  }
}

function validate(values) {
  const errors = {};

  if(!values.firstName) errors.firstName = I18n.t('validation.required');
  if(!values.lastName) errors.lastName = I18n.t('validation.required');
  if(!values.email) errors.email = I18n.t('validation.required');
  else if (!/^[_a-zA-Z0-9]+(\.[_a-zA-Z0-9]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,20})$/.test(values.email)) errors.email = I18n.t('validation.email');
  if(!values.password) errors.password = <PASSWORD>('validation.required');
  else if(!values.repeatedPassword) errors.repeatedPassword = I18n.t('validation.repeatPassword');
  else if(values.password != values.repeatedPassword) errors.repeatedPassword = <PASSWORD>.t('validation.passwordMatch');

  return errors;
}

export default connect(null, { registerUser })(reduxForm({form: 'RegisterForm', validate})(RegisterComponent));
<file_sep>/front/webpack/webpack.config.js
'use strict';

/**
 * Set the environment
 */
const args = require('minimist')(process.argv.slice(2));
const allowedEnvs = ['dev', 'dist', 'server'];
const env = args.env && allowedEnvs.indexOf(args.env) !== -1 ? args.env : 'dev';


/**
 * Build the webpack configuration
 * @param  {String} wantedEnv The wanted environment
 * @return {Object} Webpack config
 */
const path = require('path');
const _ = require('lodash');


module.exports = _.merge(
  {},
  {
    output: {
      path: path.join(__dirname, '../dist')
    },
    resolve: {
      extensions: ['.js', '.jsx'],
      alias: {
        core: path.join(__dirname, '../src/core'),
        Layout: path.join(__dirname, '../src/Layout'),
        modules: path.join(__dirname, '../src/modules'),
        node_modules: path.join(__dirname, '../node_modules')
      }
    }
  },
  require(path.join(__dirname, './' + env))
);
<file_sep>/admin/src/core/security/ResetPassword/ResetPasswordReducer.js
const initialState = {
  loading: false,
  message: null
};

export default function(state = initialState, action) {
  switch(action.type) {
    case 'generateResetPasswordToken_loading':
      return {...state, loading: true, message: null};
    case 'generateResetPasswordToken_success':
      return {...state, loading: false, message: null};
    case 'generateResetPasswordToken_error':
      return {...state, loading: false, message: {type: 'error', t: 'resetPassword.message.emailNotExist'}};
    case 'resetPassword_reset':
      return initialState;
    default:
      return state;
  }
}
<file_sep>/front/src/modules/Article/templates/Item/ArticleItemImage.js
import React from 'react';
import PropTypes from 'prop-types';

export default class ArticleItemImage extends React.Component {

  static propTypes = {
    src: PropTypes.string.isRequired,
    straight: PropTypes.bool
  }

  render() {
    const {} = this.props;
    return (
      <div className="article-image-container">
        <canvas className="article-image" ref="image"/>
        <canvas className="ai-shadow" ref="shadow"/>
      </div>
    )
  }

  componentDidMount() {
    const {src, straight} = this.props;

    const img = new Image,
      imageCanvas = this.refs.image,
      imageCtx = imageCanvas.getContext('2d'),
      shadowCanvas = this.refs.shadow,
      shadowCtx = shadowCanvas.getContext('2d');

    img.onload = straight !== undefined ? drawStraight : draw;
    img.src = src;

    function draw() {
      const w = imageCanvas.width = shadowCanvas.width = this.naturalWidth;
      const h = imageCanvas.height = shadowCanvas.height = this.naturalHeight;
      imageCtx.moveTo(0, 0);
      imageCtx.lineTo(w, 0);
      imageCtx.lineTo(w * 0.9, h);
      imageCtx.lineTo(0, h);
      imageCtx.fill();
      imageCtx.globalCompositeOperation = 'source-in';
      imageCtx.drawImage(this, 0, 0);

      shadowCtx.moveTo(0, 0);
      shadowCtx.lineTo(w, 0);
      shadowCtx.lineTo(w * 0.9, h);
      shadowCtx.lineTo(0, h);
      shadowCtx.fillStyle = '#428bca'
      shadowCtx.fill();
      imageCtx.globalCompositeOperation = 'source-in';
    }

    function drawStraight() {
      const w = imageCanvas.width = shadowCanvas.width = this.naturalWidth;
      const h = imageCanvas.height = shadowCanvas.height = this.naturalHeight;
      imageCtx.drawImage(this, 0, 0);

      shadowCtx.moveTo(0, 0);
      shadowCtx.lineTo(w, 0);
      shadowCtx.lineTo(w, h);
      shadowCtx.lineTo(0, h);
      shadowCtx.fillStyle = '#428bca';
      shadowCtx.fill();
    }

  }

}<file_sep>/admin/src/modules/Dashboard/templates/DashboardCurrentSessionComponent.js
import React from 'react';
import { connect } from 'react-redux';
import { prevRoundStatus, nextRoundStatus, resetRoundStatus, triggerGetData } from '../DashboardActions'
import { I18n } from 'react-redux-i18n';

const DashboardCurrentSessionComponent = React.createClass({
  roundStatuses: ['speeches', 'started', 'stopped', 'resultsShown'],
  renderNav(direction) {
    if(!['right', 'left'].includes(direction)) return null;
    const { currentRound: {number, status} } = this.props;
    const statusIndex = this.roundStatuses.indexOf(status);
    var statusI18n = 'invalid';
    var invalid = false;
    if(direction=='left') {
      if(number==0 && !status) invalid = true; // if is the first one
      else if(number==1 && status==this.roundStatuses[0]) statusI18n = 'notStarted';
      else if(status==this.roundStatuses[0]) statusI18n = 'prevRound';
      else statusI18n = this.roundStatuses[statusIndex-1];
    } else {
      if(!status) statusI18n = this.roundStatuses[0];
      else if(number==3 && status==this.roundStatuses[this.roundStatuses.length-1]) invalid = true;
      else if(status==this.roundStatuses[this.roundStatuses.length-1]) statusI18n = 'nextRound';
      else statusI18n = this.roundStatuses[statusIndex+1];
    }
    return !invalid ? (
      <button type="button" onClick={direction=='left' ? prevRoundStatus : nextRoundStatus}>
        <div>
          <i className={`fa fa-caret-${direction}`} />
          <span>{I18n.t(`dashboard.nav.${statusI18n}`)}</span>
        </div>
      </button>
    ) : null;
  },
  render() {
    const { currentRound } = this.props;
    if(!currentRound) return null;
    return (
      <div className="dashboard-panel current-session-manager">
        <h2>Administrare status sesiune curentă</h2>
        <div className="csm-top-left">
          { currentRound.number>0 && <button type="button" className="fa fa-backward" onClick={resetRoundStatus} title="Resetează Sesiune" /> }
          <button type="button" className="fa fa-refresh" onClick={triggerGetData} title="Declanșează aducerea datelor la votanți" />
        </div>
        <div className="csm-nav">
          {this.renderNav('left')}
        </div>
        <div className="csm-status">
          <h3>{currentRound.number ? I18n.t(`dashboard.rounds.${currentRound.number-1}`) : I18n.t('dashboard.noRoundStarted')}</h3>
          <i className={currentRound.icon} />
          <h4>{currentRound.status && I18n.t(`dashboard.nav.${currentRound.status}`)}</h4>
        </div>
        <div className="csm-nav">
          {this.renderNav('right')}
        </div>
      </div>
    )
  }

});

export default connect(state=>({
  currentRound: state.dashboard.currentRound
}))(DashboardCurrentSessionComponent);
<file_sep>/front/config/env/production.js
module.exports = {

  host: 'localhost',
  port: 8051,

  publicUrl: 'https://www.gentlab.com'

};<file_sep>/admin/src/modules/Dashboard/templates/DashboardRound2Results.js
import React from 'react';
import { imageUrl } from 'filters';
import { Collapse } from 'react-bootstrap';
import { I18n } from 'react-redux-i18n';

export default React.createClass({

  getInitialState: ()=>({}),

  renderItem(person, winner, role) {
    const openClass = `open${role+person.id}`;
    return (
      <div>
        <div className="rl-item">
          <div>
            <div className="image" style={{backgroundImage: 'url("'+imageUrl(person.avatar, 'thumb')+'")'}}></div>
          </div>
          <div>
            <h4>
              {person.fullName}
            </h4>
            {person.percentage} % | {person.totalVotes} voturi { winner == person.id && <i className="icon-checked"/> }
          </div>
          <div>
            { role && person.votes && (
              <button type="button" onClick={()=>this.setState({[openClass]: !this.state[openClass]})} disabled={!person.votes.length}>
                <i className={this.state[openClass] ? 'fa fa-chevron-up' : 'fa fa-chevron-down'} />
              </button>
            ) }
          </div>
        </div>
        { role && person.votes && (
          <Collapse in={this.state[openClass]}>
            <div>
              { person.votes.length > 10 ? (
                person.votes.map(i=>i.username).join(', ')
              ) : (
                person.votes.map((i,k)=><div key={k}>{i.username}</div>)
              ) }
            </div>
          </Collapse>
        ) }
      </div>
    )
  },

  renderChallenge(challenge, k, role) {
    return (
      <li key={k} className="col-md-12 ">
        <div className="row">
          { this.renderItem(challenge.candidate1, challenge.winner, role) }
          { this.renderItem(challenge.candidate2, challenge.winner, role) }
        </div>
      </li>
    )
  },

  render() {
    const { results } = this.props;
    return (
      <div className="row result-list">
        { results.byRole.map((role, k) => (
          <div key={k} className="col-md-3">
            <h3>Rezultate <strong>{I18n.t(`role.${role.role}`)}</strong> <small>(~{Math.round(role.totalVotes / (results.defaultChallengeNo))} pers.)</small></h3>
            <ul>
              { role.candidates.map((p, k) => this.renderChallenge(p, k, role.role)) }
            </ul>
          </div>
        )) }
        <div className="col-md-3">
          <h3><strong>Rezultate Finale</strong></h3>
          <ul>
            { results.final.map((p, k) => this.renderChallenge(p, k)) }
          </ul>
        </div>
      </div>
    );
  }

});
<file_sep>/admin/src/core/security/Logout/LogoutComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { doLogout } from './LogoutActions';
import { toastr } from 'react-redux-toastr';
import { I18n } from 'react-redux-i18n';


class LogoutComponent extends Component {

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };

  componentWillMount() {
    this.props.doLogout();
  }

  componentDidUpdate() {
    if(!this.props.currentUserId) {
      toastr.success(I18n.t('login.logoutSuccessMessage'));
      this.context.router.replace({name: 'Login'});
    }
  }

  render() {
    return null;
  }

}

export default connect(state => ({
  currentUserId: state.security.currentUser.id
}), {
  doLogout
})(LogoutComponent);
<file_sep>/api/controllers/ImageController.js
var path = require('path');
var sharp = require('sharp');
var $q = require('q');
var imageResize = require('./Image/ImageResize');
var imageMinify = require('./Image/ImageMinify');
var getOriginalFilename = require('./Image/getOriginalFilename');
var uploadsPath = typeof sails != 'undefined' ? sails.config.uploadsPath : '';


var updateModel = function (fileFields) {
  var defer = $q.defer();
  Image.create(fileFields).exec(function (err, fileObject) {
    if (err) return res.negotiate(err);
    defer.resolve(fileObject);
  });
  return defer.promise;
};

function generateImageObject(file, cb) {

  // Get the image object properties
  sharp(file.fd).toBuffer(function (err, buffer, image) {
    if (err) return res.negotiate(err);

    // get original filename (checks database)
    getOriginalFilename(file.filename, function (filename) {
      const extension = path.extname(file.fd).replace('.', '');
      const imagePath = file.fd.replace(uploadsPath, '');
      cb({
        filename: filename,
        path: imagePath,
        fileSize: file.size,
        resizeName: 'original',
        imageSize: image.width + 'x' + image.height,
        extension: extension
      })
    });
  });
}

module.exports = {

  upload: function (req, res) {

    // upload the image from request
    req.file('image').upload({
      dirname: uploadsPath,
      maxBytes: 10 * 1024 * 1024 // MB
    }, function (err, uploadedFiles) {
      if (err) return res.negotiate(err);
      if (uploadedFiles.length === 0) return res.badRequest('No file was uploaded');

      // generate database-ready image object from original image object
      const file = uploadedFiles[0];
      generateImageObject(file, function (fileFields) {

        // when the model is updated and the image is minified
        $q.all([
          updateModel(fileFields),
          imageMinify(file.fd, uploadsPath)
        ]).then(function (data) {

          // return the model received from database
          return res.ok(data[0]);
        })
      });
    });
  },

  serve: function (req, res) {

    var fileAdapter = require('skipper-disk')();
    var id = req.param('id');
    var filename = req.param('filename');
    var resizeName = req.param('size') || 'original';
    var resizeConfig = sails.config.imageSizes[resizeName];
    var where = {resizeName: 'original'};

    if(id) where.id = id;
    else if(filename) where.filename = filename;


    // find original image first
    Image.findOne(where).exec(function (err, originalImage) {
      if (err) return res.negotiate(err);
      if (!originalImage) return res.notFound('The filename requested is invalid'); // if the requested filename is invalid

      /**
       * Original size
       */
      if (resizeName == 'original') {
        fileAdapter

          // reads the file
          .read(path.join(uploadsPath, originalImage.path))

          // if the physical image doesn't exist
          .on('error', function () {
            return res.badRequest('The original image doesn\'t exist on server');
          })

          // if if it founds the image, pipe it
          .pipe(res);
      }

      /**
       * Other sizes
       */
      else {

        // Find the resized image on database
        Image.findOne({parentId: originalImage.id, resizeName: resizeName}).exec(function (err, image) {
          if (err) return res.negotiate(err);

          // if the image size is generated
          if (image) {

            // check if the image sizes are the same
            if (true && image.imageSize == resizeConfig.width + 'x' + resizeConfig.height) {

              fileAdapter
                .read(path.join(uploadsPath, image.path))
                .on('error', function () {

                  // if the file doesn't exist, generate the file again
                  imageResize(res, originalImage.path, resizeName, function (newImagePath) {
                    fileAdapter.read(path.join(uploadsPath, newImagePath)).pipe(res);
                  });

                })
                .pipe(res);
            }

            // if not, resize, and update the image details in database
            else {
              imageResize(res, originalImage.path, resizeName, function (newImagePath, imageSize) {
                Image.update(image.id, {imageSize: imageSize}).exec(function (err) {
                  if (err) return res.negotiate(err);
                  fileAdapter.read(path.join(uploadsPath, newImagePath)).pipe(res);
                });
              });
            }
          }

          // if the image size is not generated and needs to be generated
          else {
            imageResize(res, originalImage.path, resizeName, function (newImagePath, imageSize) {
              const imageFields = {
                parentId: originalImage.id,
                resizeName: resizeName,
                path: newImagePath,
                imageSize: imageSize
              };
              Image.create(imageFields).exec(function (err) {
                if (err) return res.negotiate(err);
                fileAdapter.read(path.join(uploadsPath, newImagePath)).on('error', function (err) {
                  return res.serverError(err); // server error if the image has not been saved properly
                }).pipe(res); // else pipe the image into result
              });
            });
          }
        });
      }
    });
  }

};

<file_sep>/api/policies/isAuthenticated.js
module.exports = function (req, res, next) {

  if (!(req.session.passport && req.session.passport.user)) return res.forbidden('You are not authenticated', 'policy.notAutenticated');

  return next();

};
<file_sep>/front/api/models/ArticleTag.js
module.exports = {

  structure: {
    list: ['name', 'slug', 'articles'],
    edit: [
      {fields: ['name', 'slug']}
    ]
  },

  attributes: {
    name: {
      type: 'string',
      required: true
    },
    slug: {
      type: 'string',
      required: true,
      unique: true,
      config: {
        edit: {build: {items: ['name'], filter: 'toSlug'}}
      }
    },
    articles: {
      collection: 'article',
      via: 'tags',
      config: {
        list: {template: 'modelCount'}
      }
    }
  }

};

<file_sep>/front/src/Layout/Footer/templates/FooterNewsletter.js
import React, {Component} from 'react';
import {connect, reduxForm} from 'core/decorators';
import {Field, Validate, SubmitButton} from 'core/components/form';
import {I18n} from 'core/components/content';
import axios from 'axios';


@connect() // reduxForm doesn't work without connect
@reduxForm({form: 'FooterNewsletterForm', validate: Validate({
  EMAIL: {required: true, email: true}
})})
export default class FooterNewsletter extends Component {
  state = {};
  render() {
    const {handleSubmit} = this.props;
    const {loading, message, messageType} = this.state;
    return (
      <form onSubmit={handleSubmit(this.onSubmit.bind(this))} noValidate>
        <h4 className="margin-top-0">{I18n.t('footer.newsletter.description')}</h4>
        <div className="footer-newsletter">
          <Field type="email" name="EMAIL" placeholder="Subscribe to our newsletter" />
          <SubmitButton>
            {loading ? (
              <i className="icon-spinner icon-spin" />
            ) : (
              <i className="icon-arrow-right" />
            )}
          </SubmitButton>
          {message && (
            <div className={`message message-${messageType}`}>{message}</div>
          )}
        </div>
      </form>
    );
  }
  onSubmit(formData) {
    const {reset} = this.props;
    this.setState({loading: true});
    axios.post('/mailchimp/subscribe/newsletter', {fields: formData}).then(() => {
      reset();
      this.setState({loading: false, message: 'Congratulations! You are subscribed!', messageType: 'success'});
      setTimeout(()=>{
        this.setState({message: '', messageType: ''});
      }, 5000)
    }).catch(response => {
      this.setState({loading: false});
      if (response.data.title === 'Member Exists') {
        this.setState({message: 'The member already exist', messageType: 'error'});
      } else {
        this.setState({message: response.data.detail, messageType: 'error'});
      }
    });
  }
}<file_sep>/admin/src/core/utils/components/FormComponents/Image/ImageComponent.js
import React from 'react';
import {uploadImage} from 'Main/MainUploadService';
import Dropzone from 'react-dropzone';
import {imageUrl} from 'core/utils/filters';
import {toastr} from 'react-redux-toastr';


export default class ImageComponent extends React.Component {
  render() {
    const { config, ...props } = this.props;

    const dropzoneConfig = {
      disableClick: true,
      maxSize: 2 * 1024 * 1024,
      multiple: false,
      onDrop: this.onDrop.bind(this),
      style: {},
      activeClassName: 'active'
    };
    const image = props.value;
    return (
      <Dropzone className="model-image" ref={dz=>this.dropzone=dz} {...dropzoneConfig}>
        {image ? (image.preview ? (
            <div className="image" style={{backgroundImage: `url('${image.preview}')`}} />
          ) : (
            <div className="image" style={{backgroundImage: `url('${imageUrl(image.filename, 'thumbnail')}')`}} />
          )) : (
            <div>
              NO IMAGE
            </div>
          ) }
          <button type="button" onClick={()=>this.dropzone.open()}>ADD ONE</button>
      </Dropzone>
    )
  }


  onDrop(files, rejectedFiles) {
    const {onChange} = this.props;
    const file = files[0];
    onChange({
      id: new Date().getTime(),
      fileName: file.name,
      preview: file.preview
    });
    uploadImage(file)
      .then(function (response) {
        onChange(response.data)
      })
      .catch(function (err) {
        toastr.error('Upload failed', 'See console for more details.')
      });
  }
}
<file_sep>/api/responses/jsVariable.js
module.exports = function(variableName, variable) {

  this.res.status(200);
  this.res.send(`var ${variableName} = ${JSON.stringify(variable)};`);

};
<file_sep>/admin/src/core/security/securityActions.js
import { checkAuthentication, getCurrentUserResource } from './securityResources';
import store from 'core/config/store';
const { dispatch } = store;
import history from 'core/config/history';
import { I18n } from 'react-redux-i18n';
import { toastr } from 'react-redux-toastr';
const authPaths = ['login', 'register', 'reset-password', 'token-new-<PASSWORD>'];
const isOnAuthPaths = path => authPaths.indexOf(path) > -1;
const redirect = where => history.replace({name: where == 'login' ? 'Login' : 'Dashboard'});


/**
 * Set redirect route on logout
 * @description If the user is not authenticated, it sets the initial route to redux state
 */
export function setRedirectRoute(route) {
  return {
    type: 'setRedirectRoute',
    payload: route
  }
}


/**
 * On Location Transition Async Auth Check
 * @description Every time the location is changing, check if authenticated
 */
export function asyncTransitionCheck(route) {
  document.title = ''; // reset document title
  var path = route.pathname;
  if(path=='logout' || !store.getState().security.currentUser.id) return;
  checkAuthentication()
    .then(() => { // if is authenticated and on auth routes redirect to app
      if (isOnAuthPaths(path)) redirect('app');
    }).catch(() => { // if is not authenticated and not on auth routes redirect to login
      dispatch({type: 'logout_success'}); // clearing current user object
      if (!isOnAuthPaths(path)) {
        toastr.success(I18n.t('login.logoutByTimeoutMessage'));
        dispatch(setRedirectRoute(route));
        redirect('login');
      }
    });
}


/**
 * On App Boot Sync Auth Check
 * @description The application will not boot until the currentUser is resolved
 */
export function syncAuthCheck(nextState, replace, cb) {
  var path = nextState.location.pathname;
  checkAuthentication().then(() => { // if is authenticated, get User, and continue or redirect to app if on auth routes
    getCurrentUserResource().then(response => {
      dispatch({type: 'getCurrentUser_success', payload: response.data});
      if (!isOnAuthPaths(path)) cb();
      else redirect('app');
    });
  }).catch(() => { // if is not authenticated and not on auth routes redirect to login
    if (isOnAuthPaths(path)) cb();
    else {
      dispatch(setRedirectRoute(nextState.location));
      redirect('login');
    }
  });
}
<file_sep>/front/src/core/components/form/Select/SelectComponent.js
import React from 'react';


export default class SelectComponent extends React.Component {
  render() {
    const {options, ...props} = this.props;
    return (
      <select {...props}>
        {options.map(option => (
          <option key={option.value} value={option.value}>{option.label}</option>
        ))}
      </select>
    );
  }
}
<file_sep>/admin/src/core/security/securityResources.js
import axios from 'axios';

export function login(formData) {
  return axios({
    method: 'POST',
    url: '/security/login',
    data: formData
  });
}

export function logout() {
  return axios({
    method: 'GET',
    url: '/security/logout'
  });
}

export function checkAuthentication() {
  return axios({
    method: 'GET',
    url: '/security/checkAuthentication'
  });
}

export function getCurrentUserResource() {
    return axios({
      method: 'GET',
      url: '/security/currentUser'
    });
}
<file_sep>/admin/src/modules/Model/templates/ModelEditTabTemplate.js
import React from 'react';
import {Field} from '../../../core/utils/components/FormComponents';
import {I18n} from 'react-redux-i18n';


export default class ModelEditTabTemplate extends React.Component {
  render() {
    const {fields} = this.props;
    return (
      <div>
        {fields.map(field => (
          <Field key={field.name}
                 name={field.name}
                 type={field.template}
                 label={I18n.t(`field.${field.name}`)}
                 config={field}
          />
        ))}
      </div>
    )
  }
}
<file_sep>/admin/src/core/utils/components/ContentComponents/Modal/ModalActions.js
import store from '../../../../config/store';
import { initialize } from 'redux-form';

export function changeModalState(data) {
  return {
    type: 'modalStateChange',
    payload: data
  }
}

export function openModal(config, params) {
  return {
    type: 'modalOpen',
    payload: {config, params}
  }
}

export function closeModal() {
  store.dispatch(initialize('ModalForm', null)); // reset modal form
  return {
    type: 'modalClose'
  }
}
<file_sep>/front/src/core/config/server/createApp.js
import React from 'react';
import { Provider } from 'react-redux';
import axios from 'axios';
import { loadTranslations, setLocale } from 'react-redux-i18n';
import getTranslations from '../getTranslations';
const { RouterContext } = require('react-router');
import {interceptorSuccess, interceptorError} from '../interceptors';



export default function (store, renderProps, i18nKeys) {

  const translations = getTranslations(i18nKeys);

  // default axios baseURL
  axios.defaults.baseURL = `${sails.config.publicUrl}/api`;
  axios.interceptors.response.use(interceptorSuccess, interceptorError);

  // load i18n
  store.dispatch(loadTranslations(translations));
  store.dispatch(setLocale('en'));

  return function () {
    return (
      <Provider store={store}>
        <RouterContext {...renderProps} />
      </Provider>
    );
  };
}
<file_sep>/api/controllers/GenericModel/importMultiple.js
module.exports = function (req, res) {

  const params = req.params.all();
  const Model = req._sails.models[params.modelName.toLowerCase()];

  Model.create(params.items).exec(function (err, createdItems) {
    if (err) return res.badRequest(err);
    return res.ok({successCount: createdItems.length});
  });

};
<file_sep>/api/responses/badRequest.js
module.exports = function(message, code, displayNotification) {

  this.res.status(200);
  if(typeof message === 'object') {
    this.res.jsonx({status: 400, message: message.message, code: message.code});
  } else {
    this.res.jsonx({status: 400, message, code});
  }

};<file_sep>/admin/src/modules/Model/templates/defaultList/ModelDefaultListItemFilterTemplate.js
import React from 'react';
import {connect} from 'react-redux';
import {deleteItem} from '../../ModelActions';
import {I18n} from 'react-redux-i18n';
import {imageUrl} from 'core/utils/filters';
import {setQueryParam} from 'core/utils/helpers';
import {getModelPrimaryKey} from 'core/utils/helpers';
import classnames from 'classnames';
import Select from 'react-select';
import {SearchInput} from 'core/utils/components/ContentComponents';


@connect(state=>({
  models: state.main.models,
  query: state.main.location.query
}), {
  deleteItem
})
export default class ModelDefaultListItemFilterTemplate extends React.Component {
  render() {
    const {models, modelName, columnName, query} = this.props;
    const column = models[modelName].fields[columnName];
    switch(column.config.list.template) {
      case 'choice': {
        return <Select options={column.validation.enum.map(i=>({label: I18n.t(`field.${i}`), value: i}))}
                       value={query[columnName]} onChange={data=>setQueryParam(columnName, data ? data.value: data)} placeholder="Select" />
      }
      case 'boolean': {
        return <Select options={[{label: <i className="fa fa-check" />, value: 'true'}, {label: <i className="fa fa-ban" />, value: 'false'}]}
                       value={query[columnName]} onChange={data=>setQueryParam(columnName, data ? data.value: data)} placeholder="Select" />
      }
      case 'modelCount': {
        return null
      }
      case 'image': {
        return null;
      }
      default: {
        return <SearchInput param={columnName} />
      }
    }
  }
}
<file_sep>/admin/src/core/security/TokenNewPassword/TokenNewPasswordComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { Field } from 'FormComponents';
import { I18n } from 'react-redux-i18n';
import SecurityLayout from '../securityLayoutComponent';
import { newPasswordWithToken } from './TokenNewPasswordActions';

class TokenNewPasswordComponent extends Component {
  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };

  componentWillMount() {
    document.title = I18n.t('tokenNewPassword.title');
  }

  onSubmit(formData) {
    const token = this.props.location.query.token;

    if (token) {
      this.props.newPasswordWithToken(formData, token).then(() => {
        this.context.router.replace({name: 'Login'});
      });
    }
  }

  render() {
    const { handleSubmit } = this.props;

    return (
      <SecurityLayout>
        <div className="security-box">
          <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
            <h2>{I18n.t('button.resetPassword')}</h2>
            <Field type="password" name="password" placeholder={I18n.t('login.placeholder.password')} />
            <Field type="password" name="passwordConfirmation" placeholder={I18n.t('tokenNewPassword.placeholder.passwordConfirmation')} />
            <button type="submit">{I18n.t('button.resetPassword')}</button>
          </form>
        </div>
      </SecurityLayout>
    );
  }
}

function validate(values) {
  const errors = {};

  if(!values.password)
    errors.password = 'Please enter your password';
  else if (!values.passwordConfirmation)
    errors.passwordConfirmation = 'Please confirm the password';
  else if (values.password !== values.passwordConfirmation)
    errors.passwordConfirmation = 'The two passwords must match';

  return errors;
}

export default connect(null, { newPasswordWithToken })(reduxForm({form: 'TokenNewPasswordForm', validate})(TokenNewPasswordComponent));
<file_sep>/api/controllers/GenericModelController.js
const deleteItem = require('./GenericModel/deleteItem');
const deleteMultiple = require('./GenericModel/deleteMultiple');
const getItem = require('./GenericModel/getItem');
const getList = require('./GenericModel/getList');
const getPagedList = require('./GenericModel/getPagedList');
const importMultiple = require('./GenericModel/importMultiple');
const saveItem = require('./GenericModel/saveItem');


module.exports = {
  deleteItem,
  deleteMultiple,
  getItem,
  getList,
  getPagedList,
  importMultiple,
  saveItem
};
<file_sep>/admin/src/modules/Dashboard/i18n/DashboardI18nEn.js
module.exports = {
  dashboard: {
    title: 'Dashboard',
    pageTitle: 'Dashboard',
    rounds: ['Round 1', 'Round 2', 'Round 3'],
    noRoundStarted: 'No round started'
  }
};
<file_sep>/admin/src/core/config/config.js
/* eslint no-console:0 */
import history from 'core/config/history';
import store from 'core/config/store';
import { toastr } from 'react-redux-toastr';

export function interceptorSuccess(response) {
  if(response.data && response.data.status && response.data.status>=400 && response.data.status<500) {
    if(response.data.code=='policy.notAutenticated' && !['/login', '/admin/login'].includes(window.location.pathname)) {
      store.dispatch({type: 'logout_success'}); // clearing current user object
      history.push({name: 'Login'});
    } else return Promise.reject(response.data);
  }
  return response;
}

export function interceptorError(error) {
  toastr.error(`${error.response.statusText} (${error.response.status})`, error.response.data);
  return Promise.reject(error);
}
<file_sep>/front/src/core/components/content/Pagination/PaginationComponent.js
import React from 'react';
import {connect} from 'core/decorators';
import { setQueryParam } from 'core/utils/helpers';
import {Pagination} from 'react-bootstrap';

require('./Pagination.scss');

@connect(state=>({
  location: state.main.location
}))
export default class PaginationComponent extends React.Component {
  render() {
    const { location: { query }, totalPages, activePage, onSelect } = this.props;
    if(totalPages<=1) return null;
    return (
      <Pagination
        prev={'Previous'}
        next={'Next'}
        bsSize="medium"
        boundaryLinks
        maxButtons={6}
        items={totalPages}
        activePage={activePage||parseInt(query.page)||1}
        onSelect={onSelect||this.handleOnSelect}/>
    )
  }
  handleOnSelect(page) {
    if(page==1) page = null;
    setQueryParam('page', page);
  }
}<file_sep>/config/http.js
/**
 * HTTP Server Settings
 * (sails.config.http)
 *
 * Configuration for the underlying HTTP server in Sails.
 * Only applies to HTTP requests (not WebSockets)
 *
 * For more information on configuration, check out:
 * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.http.html
 */


module.exports.http = {

  /****************************************************************************
   *                                                                           *
   * Express middleware to use for every Sails request. To add custom          *
   * middleware to the mix, add a function to the middleware config object and *
   * add its key to the "order" array. The $custom key is reserved for         *
   * backwards-compatibility with Sails v0.9.x apps that use the               *
   * `customMiddleware` config option.                                         *
   *                                                                           *
   ****************************************************************************/


  customMiddleware: function (app) {
    const bodyParser = require('body-parser');
    const express = require('express');
    const path = require('path');
    const sitePath = sails.config.sitePath;

    // CONFIG
    app.set('views', path.join(sitePath, '/src'));
    app.use(bodyParser({limit: '10mb', extended: true})); // enable gzip compression
    app.use(require('compression')()); // enable gzip compression
    app.use(function (req, res, next) { // add favicon
      if (req.url === '/favicon.ico') return res.send(200);
      next();
    });
    app.use(function (req, res, next) { // add cache to assets
      if (req.url.indexOf('/assets/') === 0 || req.url.indexOf('/images/') === 0 || req.url.indexOf('/api/image/') === 0 || req.url.indexOf('/app.') === 0) {
        res.setHeader('Cache-Control', 'public, max-age=2592000');
        res.setHeader('Expires', new Date(Date.now() + 2592000000).toUTCString());
      }
      next();
    });

    // ASSETS
    app.use('/admin/', express.static(path.join(__dirname, '../admin/dist/'), {maxage: '30d'})); // static files
    app.use('/', express.static(path.join(sitePath, 'dist/'), {maxage: '30d'}));

  },

  middleware: {

    redirects: function (req, res, next) {
      if(sails.config.redirects) {
        sails.config.redirects.middleware(req, res, next);
      } else next();
    },
    passportInit: require('passport').initialize(),
    passportSession: require('passport').session(),

    /***************************************************************************
     *                                                                          *
     * The order in which middleware should be run for HTTP request. (the Sails *
     * router is invoked by the "router" middleware below.)                     *
     *                                                                          *
     ***************************************************************************/

    order: [
      'redirects',
      'startRequestTimer',
      'cookieParser',
      'session',
      'passportInit',
      'passportSession',
      'customMiddleware',
      'bodyParser',
      'handleBodyParserError',
      'compress',
      'methodOverride',
      'poweredBy',
      '$custom',
      'router',
      'www',
      'favicon',
      '404',
      '500'
    ]

    /****************************************************************************
     *                                                                           *
     * Example custom middleware; logs each request to the console.              *
     *                                                                           *
     ****************************************************************************/

    //myRequestLogger: function (req, res, next) {
    //     console.log("Requested :: ", req.method, req.url);
    //     return next();
    //},


    /***************************************************************************
     *                                                                          *
     * The body parser that will handle incoming multipart HTTP requests. By    *
     * default as of v0.10, Sails uses                                          *
     * [skipper](http://github.com/balderdashy/skipper). See                    *
     * http://www.senchalabs.org/connect/multipart.html for other options.      *
     *                                                                          *
     ***************************************************************************/

    //bodyParser: require('skipper')

  },

  /***************************************************************************
   *                                                                          *
   * The number of seconds to cache flat files on disk being served by        *
   * Express static middleware (by default, these files are in `.tmp/public`) *
   *                                                                          *
   * The HTTP static cache is only active in a 'production' environment,      *
   * since that's the only time Express will cache flat-files.                *
   *                                                                          *
   ***************************************************************************/

  cache: 31557600000
};
<file_sep>/admin/src/core/utils/components/FormComponents/Select/MultiSelectComponent.js
import React from 'react';
import {connect} from 'react-redux';
import Select from 'react-select';
import { I18n } from 'react-redux-i18n';



@connect((state, props)=>({
  list: state.model.list[props.config.model],
  models: state.main.models
}))
export default class MultiSelectComponent extends React.Component {
  render() {
    const {config, list, models, ...props} = this.props;
    let configOptions = [];
    if(config.options) {
      configOptions = config.options.map(option=>({label: I18n.t(`option.${option}`), value: option}));
    } else if(props.options) {
      configOptions = props.options;
    } else if(models && config.model) {
      configOptions = list;
      props.labelKey = models[config.model].config.defaultField;
      props.valueKey = models[config.model].config.primaryKey;
    }
    return (
      <Select {...props} multi={true} options={configOptions} onBlur={()=>{}} />
    )
  }
}
<file_sep>/admin/src/modules/Dashboard/DashboardComponent.js
import React from 'react';
import { PageHeader, Loader, Layout } from 'ContentComponents';

require('./Dashboard.scss');

export default class DashboardComponent extends React.Component {

  componentWillMount() {
    document.title = 'Dashboard';
  }

  render() {
    return (
      <Layout>
        Dashboard
      </Layout>
    );
  }

}
