diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..618ca60 --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ + { + "presets": [ + "es2015", + "stage-0", + "react" + ] + } diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fffa33e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +[*] +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..ba507ab --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +# Tests +**/*.test.*.js +**/*.test.*.jsx + +# Dummy router +src/services/dummy-router.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..fabc816 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,41 @@ +{ + "env": { + "browser": true, + "node": true, + "es6": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "experimentalObjectRestSpread": true + }, + }, + "plugins": [ + "react" + ], + "rules": { + "comma-dangle": [0], + "no-dupe-args": [2], + "no-dupe-keys": [2], + "no-duplicate-case": [2], + "no-extra-semi": [2], + "no-irregular-whitespace": [0], + "no-undef": [2], + "valid-typeof": [2], + "curly": [0], + "dot-notation": [1], + "eqeqeq": [2], + "semi": [2, "always"], + "quotes": [1, "single"], + "no-unused-vars": [1], + "array-bracket-spacing": [2, "never"], + "brace-style": [2], + "comma-spacing": [2], + "comma-style": [2], + "one-var": [2, "never"], + "no-shadow": 0, + "arrow-body-style": 0, + "react/jsx-max-props-per-line": [2, {"maximum": 5}] + } +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c321a2f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,52 @@ +{ + "extends": "airbnb", + "parser": "babel-eslint", + "env": { + "browser": true, + "node": true + }, + "settings": { + "ecmascript": 6, + "jsx": true + }, + "plugins": [ + "react" + ], + "globals": { + "before": true, + "assert": true, + "path": true + }, + "rules": { + "jsx-a11y/img-uses-alt": 0, + "jsx-a11y/redundant-alt": 0, + "jsx-a11y/valid-aria-role": 0, + "arrow-body-style": [0], + "comma-dangle": [2, "always-multiline"], + "import/extensions": [2, "never"], + "func-names": 2, + "global-require": 1, + "max-len": [1, 120, 2, {"ignoreComments": true}], + "new-cap": [1, { "capIsNew": false }], + "no-unused-expressions": 0, + "no-unused-vars": ["error"], + "no-cond-assign": [2, "except-parens"], + "no-extra-strict": 0, + "no-mixed-operators": ["error", {"allowSamePrecedence": true}], + "no-multi-spaces": 0, + "no-param-reassign": 0, + "no-shadow": [0], + "no-unneeded-ternary": [2, {"defaultAssignment": true}], + "no-unused-vars": [1, {"vars": "local", "args": "none"}], + "no-use-before-define": ["error", "nofunc"], + "no-use-before-define": ["error", { "functions": false, "classes": true }], + "quote-props": [1, "consistent-as-needed"], + "radix": 0, + "react/jsx-indent": [2, 2], + "react/no-array-index-key": 0, + "react/no-unused-prop-types": 1, + "react/prefer-es6-class": 0, + "react/require-extension": [0, { "extensions": [".jsx"] }], + "space-infix-ops": 0 + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b276c35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so +dist +out + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Shellscripts +##################### +*.sh + +# Custom +##################### +/node_modules +/public/static/*.js +/public/static/*.css +/public/static/*.map +/public/static/*.app.js + +# Generated bundle hash file +############################ +.build + +# Editors +######### +.idea +*.iml + + +# ESLint +######## +.jshintrc +eslintrc.json-ci-runner diff --git a/README.md b/README.md new file mode 100644 index 0000000..35e74f0 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +Aurora Core Frontend Demo +========================= + +Demo implementation of a frontend conforming to [aurora-core](https://github.com/soldotno/aurora-core) specifications. + +#### Install dependencies +* [Install node.js (5.x)](https://nodejs.org/) +* `npm install` + +#### Development shellscript example: +```sh +#!/bin/sh +export PORT=3000 +export HOSTNAME="localhost" +export DEBUG="*,-babel" +export NODE_ENV="development" +export API_URL="http://localhost:3001" + +npm run develop +``` + +#### Production shellscript example: +```sh +#!/bin/sh +export PORT=3000 +export HOSTNAME="localhost" +export DEBUG="*,-babel" +export NODE_ENV="production" +export API_URL="http://localhost:3001" + +npm run build && npm start +``` diff --git a/config.js b/config.js new file mode 100644 index 0000000..5491acc --- /dev/null +++ b/config.js @@ -0,0 +1,8 @@ +const config = { + port: process.env.PORT || 3000, + hostname: process.env.HOSTNAME || 'localhost', + apiUrl: process.env.API_URL || 'http://localhost::3001', + nodeEnv: process.env.NODE_ENV || 'development', +}; + +module.exports = config; diff --git a/dev-server.js b/dev-server.js new file mode 100644 index 0000000..d2a4eec --- /dev/null +++ b/dev-server.js @@ -0,0 +1,18 @@ +const port = require('./config').port; +const webpackConfig = require('./webpack.config'); + +/** + * Environment + */ + +/** + * Import / run the dev server + */ +require('aurora-core/dev-server')({ + webpackConfig, + port, + onListen(err) { // eslint-disable-line consistent-return + if (err) return console.log(err); + console.log('Listening at localhost:', port); + }, +}); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..05605ca --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,9 @@ +/** + * Import the existing webpack config + */ +var webpackConfig = require('./webpack.config.js'); + +/** + * Export the karma config (with the webpack config injected) + */ +module.exports = require('aurora-core/karma.conf.js').extend(webpackConfig); diff --git a/package.json b/package.json new file mode 100644 index 0000000..5017cd0 --- /dev/null +++ b/package.json @@ -0,0 +1,110 @@ +{ + "name": "aurora-demo-frontend", + "version": "1.0.0", + "description": "SOL frontpage frontend application", + "main": "app.js", + "engines": { + "node": "7.7.4" + }, + "scripts": { + "test": "npm run test:server && npm run test:client", + "test:client": "karma start", + "test:server": "mocha --require babel-register \"./src/**/*.test.server.js\"", + "watch:test": "karma start --no-single-run --browsers Firefox", + "test:lint": "eslint src/**/*.js*", + "pretest": "npm run test:lint", + "build": "webpack", + "postinstall": "npm run build", + "start": "node ./src/server-bootstrap", + "develop": "node dev-server" + }, + "repository": { + "type": "git", + "url": "https://github.com/soldotno/aurora-demo-frontend.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/soldotno/aurora-demo-frontend/issues" + }, + "homepage": "https://github.com/soldotno/aurora-demo-frontend", + "dependencies": { + "aurora-core": "2.0.2", + "aurora-sass-loader": "github:soldotno/aurora-sass-loader#master", + "autoprefixer": "6.7.7", + "axios": "0.16.0", + "babel": "6.0.15", + "babel-cli": "6.24.0", + "babel-core": "6.24.0", + "babel-eslint": "7.2.1", + "babel-loader": "6.4.1", + "babel-polyfill": "6.23.0", + "babel-preset-es2015": "6.24.0", + "babel-preset-react": "6.23.0", + "babel-preset-stage-0": "6.22.0", + "babel-runtime": "6.23.0", + "bluebird": "3.5.0", + "classnames": "2.2.5", + "compression": "1.6.2", + "cookie-parser": "1.4.3", + "css-loader": "0.28.0", + "debug": "2.6.3", + "es5-shim": "4.5.9", + "express": "4.15.2", + "express-useragent": "1.0.7", + "is-client": "0.0.2", + "isomorphic-ensure": "1.0.1", + "js-cookie": "2.1.4", + "location-bar": "3.0.1", + "lodash": "4.17.4", + "lodash.compose": "2.4.1", + "lodash.set": "4.3.2", + "md5": "2.2.1", + "moment": "2.18.1", + "ms": "1.0.0", + "newrelic": "1.38.2", + "no-op": "1.0.3", + "node-sass": "4.5.2", + "postcss-loader": "1.3.3", + "progress-bar-webpack-plugin": "1.9.3", + "qs": "6.4.0", + "query-string": "4.2.2", + "react": "15.4.2", + "react-dom": "15.4.2", + "react-hot-loader": "1.3.1", + "react-redux": "5.0.3", + "redis": "2.7.1", + "redux": "3.6.0", + "redux-logger": "3.0.1", + "redux-thunk": "2.2.0", + "sass-loader": "6.0.3", + "serve-favicon": "2.4.2", + "stringify-clone": "2.0.0", + "style-loader": "0.16.1", + "url-join": "1.1.0", + "url-parse": "1.1.8", + "uuid": "3.0.1", + "uuid-validate": "0.0.2", + "webpack": "2.3.3" + }, + "devDependencies": { + "eslint": "3.19.0", + "eslint-config-airbnb": "14.1.0", + "eslint-plugin-import": "2.2.0", + "eslint-plugin-jsx-a11y": "4.0.0", + "eslint-plugin-react": "6.10.3", + "expect": "1.20.2", + "expect-jsx": "3.0.0", + "inject-loader": "3.0.0", + "karma": "1.6.0", + "karma-chrome-launcher": "2.0.0", + "karma-firefox-launcher": "1.0.1", + "karma-mocha": "1.3.0", + "karma-mocha-reporter": "2.2.3", + "karma-sourcemap-loader": "0.3.7", + "karma-webpack": "2.0.3", + "mocha": "3.2.0", + "mockery": "2.0.0", + "webpack-dev-server": "2.4.2" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..f0cbc2d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,12 @@ +/* eslint-disable no-var */ +var autoprefixer = require('autoprefixer'); + +module.exports = { + plugins: [ + autoprefixer({ + browsers: [ + 'last 3 versions', + ], + }), + ], +}; diff --git a/public/static/favicon.ico b/public/static/favicon.ico new file mode 100644 index 0000000..b63caf3 Binary files /dev/null and b/public/static/favicon.ico differ diff --git a/public/static/index.html b/public/static/index.html new file mode 100644 index 0000000..72cf307 --- /dev/null +++ b/public/static/index.html @@ -0,0 +1,12 @@ + + + + Aurora Frontend Demo (development) + + + + +
+ + + diff --git a/src/client-bootstrap.js b/src/client-bootstrap.js new file mode 100644 index 0000000..a0db276 --- /dev/null +++ b/src/client-bootstrap.js @@ -0,0 +1,42 @@ +/** + * Polyfills and shims + */ +require('es5-shim/es5-shim'); +require('es5-shim/es5-sham'); +require('babel-polyfill'); + +/** + * Use bluebird for better error handling in development + */ +if (__DEVELOPMENT__) { + /** + * Use bluebird for promises + */ + window.Promise = require('bluebird'); + + /** + * Suppress warnings of runaway Promises. + * Bug when used together with Babel causes + * harmless and non-applicative (but annyoing) warnings.. + */ + Promise.config({ + warnings: false + }); + + /** + * Globally handle unhandled promise rejections + */ + window.addEventListener('unhandledrejection', function(e) { + e.preventDefault(); + throw e.detail.reason; + }); +} + +/** + * Initialize and render the client app + */ +require('aurora-core/dist/render/client')({ + getRoute: require('./services/get-route-config'), + getModule: require('./modules/get-module'), + isVisible: require('./utils/is-visible'), +}); diff --git a/src/modules/DemoAppModule/DemoAppModule.jsx b/src/modules/DemoAppModule/DemoAppModule.jsx new file mode 100644 index 0000000..146d22f --- /dev/null +++ b/src/modules/DemoAppModule/DemoAppModule.jsx @@ -0,0 +1,109 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Aurora specific mixin to conveniently + * render modules for higher order components + */ +const RenderChildrenMixin = require('aurora-core/dist/mixins/render-children-mixin'); + +/** + * Import shared components + */ +const VersionAlert = require('../../shared-components/VersionAlert.jsx'); + +/** + * An app module + * that should be used as a + * top level module + * in the config + */ +const DemoAppModule = React.createClass({ + /** + * These are all Aurora-specific mixins + * to include different functionality + * that the module needs + */ + mixins: [ + RenderChildrenMixin() + ], + + propTypes: { + pagination: React.PropTypes.shape({ + isLoading: React.PropTypes.bool + }), + newVersionAvailable: React.PropTypes.bool, + }, + + getDefaultProps() { + return { + pagination: { + isLoading: false + }, + newVersionAvailable: false, + }; + }, + + render() { + const { + pagination: { isLoading }, + newVersionAvailable, + } = this.props; + + return ( +
+ {newVersionAvailable && } + {/* the function below is exposed by render-modules-mixin */} + {this.renderChildren()} + {isLoading &&
Loading...
} +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-app-styles.scss`, + clientStyles: isBrowser && require('./styles/my-app-styles.scss'), + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }), +); + +/** + * Finally export the module + * extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoAppModule); diff --git a/src/modules/DemoAppModule/components/readme.md b/src/modules/DemoAppModule/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoAppModule/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoAppModule/injects/readme.md b/src/modules/DemoAppModule/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoAppModule/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoAppModule/injects/scripts.js b/src/modules/DemoAppModule/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoAppModule/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoAppModule/readme.md b/src/modules/DemoAppModule/readme.md new file mode 100644 index 0000000..66e2dd1 --- /dev/null +++ b/src/modules/DemoAppModule/readme.md @@ -0,0 +1,78 @@ +Aurora App Module Template +========================== + +A module template for building top level Aurora app modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +__NOTE:__ +An app module means that your module renders other modules passed into it, and has an array of `modules` in `options`. + +#### Include it in the module-map to enable use + +```js +// ./src/modules/module-map.js +const moduleMap = { + 'my-app-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyAppModule/MyAppModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-app-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....', + modules: [...] // other modules + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. +- `modules` - a set of modules + +__Example implementation__ + +```js +render() { + //...... + + let children = this.props.modules.map((module, i) => { + let Module = module.type; + + return ( + + ); + }); + + //...... +} +``` diff --git a/src/modules/DemoAppModule/services/readme.md b/src/modules/DemoAppModule/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoAppModule/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoAppModule/styles/my-app-styles.scss b/src/modules/DemoAppModule/styles/my-app-styles.scss new file mode 100644 index 0000000..38717f9 --- /dev/null +++ b/src/modules/DemoAppModule/styles/my-app-styles.scss @@ -0,0 +1,3 @@ +.awesome-app { + +} diff --git a/src/modules/DemoAppModule/styles/readme.md b/src/modules/DemoAppModule/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoAppModule/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx b/src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx new file mode 100644 index 0000000..06aca5b --- /dev/null +++ b/src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx @@ -0,0 +1,93 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * A higher order module + * that can wrap any number of modules + * + * A flow within the flow (grouping things f.ex) + */ +const DemoHigherOrderModule = React.createClass({ + propTypes: { + element: React.PropTypes.string, + classes: React.PropTypes.string, + modules: React.PropTypes.array, + }, + + getDefaultProps() { + return { + element: 'div', + classes: '', + modules: [], + }; + }, + + render() { + let { + element, + classes, + modules, + } = this.props; + + /** + * This is the higher order part + * (just copy/paste this basically) + * and do whatever you need to + * render stuff the way you want + */ + let children = modules.map((module, i) => { + let Module = module.type; + + return ( + + ); + }); + + return ( + React.createElement(element, { + className: classes + }, children) + ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-container-styles.scss`, + clientStyles: isBrowser && require('./styles/my-container-styles.scss'), + }), +); + +/** + * Finally export the module + * extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoHigherOrderModule); diff --git a/src/modules/DemoHigherOrderModule/components/readme.md b/src/modules/DemoHigherOrderModule/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoHigherOrderModule/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoHigherOrderModule/readme.md b/src/modules/DemoHigherOrderModule/readme.md new file mode 100644 index 0000000..bb6b971 --- /dev/null +++ b/src/modules/DemoHigherOrderModule/readme.md @@ -0,0 +1,78 @@ +Aurora Higher Order Module Template +=================================== + +A module template for building higher order Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +__NOTE:__ +A higher order module means that your module renders other modules passed into it, and has an array of `modules` in its `options`. + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-higher-order-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyHigherOrderModule/MyHigherOrderModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-higher-order-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....', + modules: [...] // other modules + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. +- `modules` - a set of modules + +__Example implementation__ + +```js +render() { + //...... + + let children = this.props.modules.map((module, i) => { + let Module = module.type; + + return ( + + ); + }); + + //...... +} +``` diff --git a/src/modules/DemoHigherOrderModule/styles/my-container-styles.scss b/src/modules/DemoHigherOrderModule/styles/my-container-styles.scss new file mode 100644 index 0000000..d343113 --- /dev/null +++ b/src/modules/DemoHigherOrderModule/styles/my-container-styles.scss @@ -0,0 +1,3 @@ +.awesome-container { + +} diff --git a/src/modules/DemoHigherOrderModule/styles/readme.md b/src/modules/DemoHigherOrderModule/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoHigherOrderModule/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModule/DemoModule.jsx b/src/modules/DemoModule/DemoModule.jsx new file mode 100644 index 0000000..fc2c532 --- /dev/null +++ b/src/modules/DemoModule/DemoModule.jsx @@ -0,0 +1,66 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModule = React.createClass({ + render() { + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }), +); + +/** + * Finally export the module + * extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoModule); diff --git a/src/modules/DemoModule/components/Loading.jsx b/src/modules/DemoModule/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModule/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModule/components/Main.jsx b/src/modules/DemoModule/components/Main.jsx new file mode 100644 index 0000000..ec67385 --- /dev/null +++ b/src/modules/DemoModule/components/Main.jsx @@ -0,0 +1,30 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: { + data: React.PropTypes.shape({ + name: React.PropTypes.string, + age: React.PropTypes.number + }) + }, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModule/components/readme.md b/src/modules/DemoModule/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModule/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModule/injects/readme.md b/src/modules/DemoModule/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModule/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModule/injects/scripts.js b/src/modules/DemoModule/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModule/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModule/readme.md b/src/modules/DemoModule/readme.md new file mode 100644 index 0000000..e34cbd4 --- /dev/null +++ b/src/modules/DemoModule/readme.md @@ -0,0 +1,51 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModule/styles/my-module-styles.scss b/src/modules/DemoModule/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModule/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModule/styles/readme.md b/src/modules/DemoModule/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModule/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithData/DemoModuleWithData.jsx b/src/modules/DemoModuleWithData/DemoModuleWithData.jsx new file mode 100644 index 0000000..0285d15 --- /dev/null +++ b/src/modules/DemoModuleWithData/DemoModuleWithData.jsx @@ -0,0 +1,98 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithData = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + fetchData: require('./services/get-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithData); diff --git a/src/modules/DemoModuleWithData/components/Loading.jsx b/src/modules/DemoModuleWithData/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithData/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithData/components/Main.jsx b/src/modules/DemoModuleWithData/components/Main.jsx new file mode 100644 index 0000000..ec67385 --- /dev/null +++ b/src/modules/DemoModuleWithData/components/Main.jsx @@ -0,0 +1,30 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: { + data: React.PropTypes.shape({ + name: React.PropTypes.string, + age: React.PropTypes.number + }) + }, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithData/components/readme.md b/src/modules/DemoModuleWithData/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithData/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithData/injects/readme.md b/src/modules/DemoModuleWithData/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithData/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithData/injects/scripts.js b/src/modules/DemoModuleWithData/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithData/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithData/readme.md b/src/modules/DemoModuleWithData/readme.md new file mode 100644 index 0000000..e79fba4 --- /dev/null +++ b/src/modules/DemoModuleWithData/readme.md @@ -0,0 +1,51 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules that fetch their own data (both on the server and client-side). + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithData/services/get-data.js b/src/modules/DemoModuleWithData/services/get-data.js new file mode 100644 index 0000000..7b2fb86 --- /dev/null +++ b/src/modules/DemoModuleWithData/services/get-data.js @@ -0,0 +1,13 @@ +/** + * Dependencies + */ +const request = require('axios'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function(options) { + return Promise.resolve(JSON.parse(JSON.stringify(options))); +}; diff --git a/src/modules/DemoModuleWithData/services/readme.md b/src/modules/DemoModuleWithData/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithData/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithData/styles/my-module-styles.scss b/src/modules/DemoModuleWithData/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithData/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithData/styles/readme.md b/src/modules/DemoModuleWithData/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithData/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx b/src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx new file mode 100644 index 0000000..8831e27 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx @@ -0,0 +1,106 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithGeolocation = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + disableServerLoading: true, + fetchData: require('./services/get-geolocated-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithGeolocation); diff --git a/src/modules/DemoModuleWithGeolocation/components/Loading.jsx b/src/modules/DemoModuleWithGeolocation/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithGeolocation/components/Main.jsx b/src/modules/DemoModuleWithGeolocation/components/Main.jsx new file mode 100644 index 0000000..4bbf6d7 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/components/Main.jsx @@ -0,0 +1,25 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: {}, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithGeolocation/components/readme.md b/src/modules/DemoModuleWithGeolocation/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithGeolocation/injects/readme.md b/src/modules/DemoModuleWithGeolocation/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithGeolocation/injects/scripts.js b/src/modules/DemoModuleWithGeolocation/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithGeolocation/readme.md b/src/modules/DemoModuleWithGeolocation/readme.md new file mode 100644 index 0000000..5a45ecf --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/readme.md @@ -0,0 +1,51 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules that is based on geolocated data. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js b/src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js new file mode 100644 index 0000000..6094867 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js @@ -0,0 +1,62 @@ +/** + * Dependencies + */ +const request = require('axios'); +const urlJoin = require('url-join'); + +/** + * Environment + */ +const { apiUrl } = require('../../../../config'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function getGeoLocatedData(options) { + /** + * Feature detect the Geolocation API + * (NOTE: this only works client-side!) + */ + if ( + typeof navigator === 'undefined' && + !(geolocation in navigator) // eslint-disable-line no-undef + ) { + return Promise.reject(new Error('Need browser to perform geolocation')); + } + + /** + * Fetch data from an API/service where you supply + * the geolocation data as part of the input + * + * NOTE: You should only change the end-point here, + * as the rest of the data will be provided by the + * Aurora "framework" using the 'withData' decorator + * + * NOTE: Most often you can just copy/paste this file + * and change the url / API endpoint + * + * NOTE: Using __latitude and __longitude is a convention + * to make things predictable + */ + return new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject, { + maximumAge: 30000, + }); + }) + .then(({ coords: { latitude, longitude } }) => { + return request + .get(urlJoin(apiUrl, 'data', 'geolocated-data'), { + params: Object.assign({}, options, { + __latitude: latitude, + __longitude: longitude, + }), + }) + .then(response => response.data); + }) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); +}; diff --git a/src/modules/DemoModuleWithGeolocation/services/readme.md b/src/modules/DemoModuleWithGeolocation/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss b/src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithGeolocation/styles/readme.md b/src/modules/DemoModuleWithGeolocation/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx b/src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx new file mode 100644 index 0000000..64702ed --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx @@ -0,0 +1,105 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithPersonalization = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + fetchData: require('./services/get-personalized-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithPersonalization); diff --git a/src/modules/DemoModuleWithPersonalization/components/Loading.jsx b/src/modules/DemoModuleWithPersonalization/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithPersonalization/components/Main.jsx b/src/modules/DemoModuleWithPersonalization/components/Main.jsx new file mode 100644 index 0000000..4bbf6d7 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/components/Main.jsx @@ -0,0 +1,25 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: {}, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithPersonalization/components/readme.md b/src/modules/DemoModuleWithPersonalization/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithPersonalization/injects/readme.md b/src/modules/DemoModuleWithPersonalization/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithPersonalization/injects/scripts.js b/src/modules/DemoModuleWithPersonalization/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithPersonalization/readme.md b/src/modules/DemoModuleWithPersonalization/readme.md new file mode 100644 index 0000000..fcb3f8c --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/readme.md @@ -0,0 +1,58 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend +When you want to outsource a module to npm see [aurora-module-template](https://github.com/soldotno/aurora-module-template) + +#### Include it in your module-map and module-includes to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; + +// module-includes.js +module.exports = [ + 'my-module', + ... +]; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js b/src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js new file mode 100644 index 0000000..4799476 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js @@ -0,0 +1,27 @@ +/** + * Dependencies + */ +const request = require('axios'); +const urlJoin = require('url-join'); + +/** + * Environment + */ +const apiUrl = process.env.API_URL; + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function(options) { + return request + .get(urlJoin(apiUrl, 'data', 'personalized-data'), { + params: options + }) + .then(response => response.data) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); +}; diff --git a/src/modules/DemoModuleWithPersonalization/services/readme.md b/src/modules/DemoModuleWithPersonalization/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss b/src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithPersonalization/styles/readme.md b/src/modules/DemoModuleWithPersonalization/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx b/src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx new file mode 100644 index 0000000..76bd2b9 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx @@ -0,0 +1,105 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithRandomization = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + fetchData: require('./services/get-randomized-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithRandomization); diff --git a/src/modules/DemoModuleWithRandomization/components/Loading.jsx b/src/modules/DemoModuleWithRandomization/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithRandomization/components/Main.jsx b/src/modules/DemoModuleWithRandomization/components/Main.jsx new file mode 100644 index 0000000..4bbf6d7 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/components/Main.jsx @@ -0,0 +1,25 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: {}, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithRandomization/components/readme.md b/src/modules/DemoModuleWithRandomization/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithRandomization/injects/readme.md b/src/modules/DemoModuleWithRandomization/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithRandomization/injects/scripts.js b/src/modules/DemoModuleWithRandomization/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithRandomization/readme.md b/src/modules/DemoModuleWithRandomization/readme.md new file mode 100644 index 0000000..fcb3f8c --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/readme.md @@ -0,0 +1,58 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend +When you want to outsource a module to npm see [aurora-module-template](https://github.com/soldotno/aurora-module-template) + +#### Include it in your module-map and module-includes to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; + +// module-includes.js +module.exports = [ + 'my-module', + ... +]; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithRandomization/services/get-randomized-data.js b/src/modules/DemoModuleWithRandomization/services/get-randomized-data.js new file mode 100644 index 0000000..7ba2825 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/services/get-randomized-data.js @@ -0,0 +1,27 @@ +/** + * Dependencies + */ +const request = require('axios'); +const urlJoin = require('url-join'); + +/** + * Environment + */ +const { apiUrl } = require('../../../../config'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function getRandomizedData(options) { + return request + .get(urlJoin(apiUrl, 'data', 'randomized-data'), { + params: options, + }) + .then(response => response.data) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); +}; diff --git a/src/modules/DemoModuleWithRandomization/services/readme.md b/src/modules/DemoModuleWithRandomization/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss b/src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithRandomization/styles/readme.md b/src/modules/DemoModuleWithRandomization/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx b/src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx new file mode 100644 index 0000000..97bca81 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx @@ -0,0 +1,77 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithRedux = React.createClass({ + render() { + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withRedux = require('aurora-core/dist/decorators/with-redux'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Redux state handling + * (optional) + */ + withRedux({ + reducer: require('./reducers').default, + initialState: { + error: null, + isLoading: true, + data: {}, + } + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }), +); + +/** + * Finally export the module extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithRedux); diff --git a/src/modules/DemoModuleWithRedux/actions/index.js b/src/modules/DemoModuleWithRedux/actions/index.js new file mode 100644 index 0000000..59a951f --- /dev/null +++ b/src/modules/DemoModuleWithRedux/actions/index.js @@ -0,0 +1,80 @@ +/** + * Import services we need + * to fetch data from the API + */ +import getData from '../services/get-data'; + +/** + * Constants for naming + * and referencing action types + */ +export const POPULATE_DATA_PENDING = 'POPULATE_DATA_PENDING'; +export const POPULATE_DATA_SUCCESS = 'POPULATE_DATA_SUCCESS'; +export const POPULATE_DATA_ERROR = 'POPULATE_DATA_ERROR'; + +/** + * Pending action for + * populating the data + */ +function populateDataPending() { + return { + type: POPULATE_DATA_PENDING + }; +} + +/** + * Success action for + * populating the data + */ +function populateDataSuccess(data) { + return { + type: POPULATE_DATA_SUCCESS, + data, + }; +} + +/** + * Error action for + * populating the data + */ +function populateDataError(error) { + return { + type: POPULATE_DATA_ERROR, + error, + }; +} + +/** + * Exposed action creator + * for populating the data of our module + */ +export function populateData(options) { + return (dispatch) => { + /** + * Dispatch the pending action for + * retrieving the data + */ + dispatch(populateDataPending()); + + /** + * Initiate the request for retrieving + * the data for our given input options + */ + getData(options) + .then((result) => { + /** + * Dispatch the success action of retrieving + * the data, with the data as payload + */ + dispatch(populateDataSuccess(result)); + }) + .catch((err) => { + /** + * Dispatch the error action of retrieving + * the data when something fails, + * with the error as payload + */ + dispatch(populateDataError(err)); + }); + }; +} diff --git a/src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx b/src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx new file mode 100644 index 0000000..4b7c048 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx @@ -0,0 +1,23 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const ErrorMessage = React.createClass({ + propTypes: { + error: React.PropTypes.object.isRequired + }, + + render() { + const { error } = this.props; + + return ( +
Error: {JSON.stringify(error)}
+ ); + } +}); + +module.exports = ErrorMessage; diff --git a/src/modules/DemoModuleWithRedux/components/Loading.jsx b/src/modules/DemoModuleWithRedux/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithRedux/components/Main.jsx b/src/modules/DemoModuleWithRedux/components/Main.jsx new file mode 100644 index 0000000..992b136 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/Main.jsx @@ -0,0 +1,115 @@ +/** + * Dependencies + */ +const React = require('react'); +const { connect } = require('react-redux'); +const { bindActionCreators } = require('redux'); + +/** + * Actions + */ +const { populateData } = require('../actions'); + +/** + * Sub-components + */ +const Loading = require('./Loading.jsx'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: { + isLoading: React.PropTypes.bool.isRequired, + error: React.PropTypes.object.isRequired, + data: React.PropTypes.object.isRequired, + actions: React.PropTypes.shape({ + populateData: React.PropTypes.func.isRequired, + }), + _dataOptions: React.PropTypes.object + }, + + getDefaultProps() { + return { + _dataOptions: {} + }; + }, + + componentDidMount() { + const { + actions: { populateData }, + _dataOptions + } = this.props; + + populateData(_dataOptions); + }, + + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoading, + data, + error + } = this.props; + + /** + * Show error message + * if there's an error + */ + if (error) { + return ( + + ); + } + + /** + * Until we have data + * show a loading indicator + */ + if (isLoading) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ {JSON.stringify(this.props)} +
+ ); + } +}); + +/** + * A function to map from + * redux state to React props + */ +function mapStateToProps(state) { + return state; +} + +/** + * A function to map from + * redux actions to React props + */ +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + populateData, + }, dispatch) + }; +} + +/** + * Export a redux-"connected" version of the component + */ +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(Main); diff --git a/src/modules/DemoModuleWithRedux/components/readme.md b/src/modules/DemoModuleWithRedux/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithRedux/injects/readme.md b/src/modules/DemoModuleWithRedux/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithRedux/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithRedux/injects/scripts.js b/src/modules/DemoModuleWithRedux/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithRedux/readme.md b/src/modules/DemoModuleWithRedux/readme.md new file mode 100644 index 0000000..fcb3f8c --- /dev/null +++ b/src/modules/DemoModuleWithRedux/readme.md @@ -0,0 +1,58 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend +When you want to outsource a module to npm see [aurora-module-template](https://github.com/soldotno/aurora-module-template) + +#### Include it in your module-map and module-includes to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; + +// module-includes.js +module.exports = [ + 'my-module', + ... +]; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithRedux/reducers/index.js b/src/modules/DemoModuleWithRedux/reducers/index.js new file mode 100644 index 0000000..0a0585c --- /dev/null +++ b/src/modules/DemoModuleWithRedux/reducers/index.js @@ -0,0 +1,37 @@ +/** + * Import all the constants + * we are going to use + */ +import { + POPULATE_DATA_PENDING, + POPULATE_DATA_SUCCESS, + POPULATE_DATA_ERROR +} from '../actions'; + +/** + * Create and export our state reducer + */ +export default function rootReducer(state, action) { + switch (action.type) { + case POPULATE_DATA_PENDING: + return Object.assign({}, state, { + isLoading: true, + error: null, + }); + + case POPULATE_DATA_SUCCESS: + return Object.assign({}, state, { + isLoading: false, + data: action.data, + error: null, + }); + + case POPULATE_DATA_ERROR: + return Object.assign({}, state, { + error: action.error + }); + + default: + return state; + } +} diff --git a/src/modules/DemoModuleWithRedux/services/get-data.js b/src/modules/DemoModuleWithRedux/services/get-data.js new file mode 100644 index 0000000..7b2fb86 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/services/get-data.js @@ -0,0 +1,13 @@ +/** + * Dependencies + */ +const request = require('axios'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function(options) { + return Promise.resolve(JSON.parse(JSON.stringify(options))); +}; diff --git a/src/modules/DemoModuleWithRedux/services/readme.md b/src/modules/DemoModuleWithRedux/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithRedux/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithRedux/styles/my-module-styles.scss b/src/modules/DemoModuleWithRedux/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithRedux/styles/readme.md b/src/modules/DemoModuleWithRedux/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithRedux/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DoesNotExist/DoesNotExist.jsx b/src/modules/DoesNotExist/DoesNotExist.jsx new file mode 100644 index 0000000..71ef045 --- /dev/null +++ b/src/modules/DoesNotExist/DoesNotExist.jsx @@ -0,0 +1,15 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Default empty component for graceful failing + */ +const DoesNotExist = React.createClass({ + render() { + return null; + } +}); + +module.exports = DoesNotExist; diff --git a/src/modules/get-module.js b/src/modules/get-module.js new file mode 100644 index 0000000..bece3f7 --- /dev/null +++ b/src/modules/get-module.js @@ -0,0 +1,98 @@ +/** + * Dependencies + */ +const debug = require('debug')('aurora-frontend:get-module'); + +/** + * Shim require.ensure in node.js + * (check the webpack docs - async chunking) + */ +if (typeof require.ensure !== 'function') { + require.ensure = require('isomorphic-ensure')({ + dirname: __dirname, + }); +} + +/** + * The map of modules available to resolve to + */ +const moduleMap = { + 'demo-app': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoAppModule/DemoAppModule.jsx')); + }); + }, + 'demo-hom': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoHigherOrderModule/DemoHigherOrderModule.jsx')); + }); + }, + 'demo-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModule/DemoModule.jsx')); + }); + }, + 'demo-module-with-data': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithData/DemoModuleWithData.jsx')); + }); + }, + 'demo-module-with-geolocation': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx')); + }); + }, + 'demo-module-with-personalization': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx')); + }); + }, + 'demo-module-with-randomization': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithRandomization/DemoModuleWithRandomization.jsx')); + }); + }, + 'demo-module-with-redux': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithRedux/DemoModuleWithRedux.jsx')); + }); + }, + 'does-not-exist': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DoesNotExist/DoesNotExist.jsx')); + }); + }, +}; + +/** + * Export a function that can resolve + * a module from a string representation + * to a React component / Aurora module + * (asynchronously) + */ +module.exports = (type) => { + /** + * Return a Promise of the React component + * resolved from the string type definition + */ + return new Promise((resolve, reject) => { + setImmediate(() => { + if (!moduleMap[type]) { + debug(`You are trying to render a module with type *${type}* which does not exist`); + } + + /** + * Pull out the specified component getter function + * (or default gracefully) + */ + const getModule = moduleMap[type] || moduleMap['does-not-exist']; + + /** + * Fetch the specified component and the promise to it + */ + getModule((err, module) => { + return err ? reject(err) : resolve(module); + }); + }); + }); +}; diff --git a/src/server-app.js b/src/server-app.js new file mode 100644 index 0000000..a3e59c6 --- /dev/null +++ b/src/server-app.js @@ -0,0 +1,75 @@ +/** + * Dependencies + */ +const debug = require('debug')('aurora-frontend:app'); // eslint-disable-line no-unused-vars +const express = require('express'); +const compression = require('compression'); +const cookieParser = require('cookie-parser'); +const favicon = require('serve-favicon'); +const useragent = require('express-useragent'); +const ms = require('ms'); + +/** + * Environment + */ +const { port } = require('../config'); + +/** + * Import the server-rendering function + * from aurora core and create an instance + */ +/* eslint-disable global-require */ +const renderServer = require('aurora-core/dist/render/server')({ + cacheHTML: { + get: () => Promise.reject(new Error('Cache miss')), + set: function set() {}, + }, + createHTML: require('./utils/create-html'), + getRoute: require('./services/get-route-config'), + getUserSettings: require('./utils/get-user-settings'), + getPaginationSettings: require('./utils/get-pagination-settings'), + getModule: require('./modules/get-module'), + isVisible: require('./utils/is-visible'), +}); +/* eslint-enable global-require */ + +/** + * Create express app + */ +const app = express(); + +/** + * Use gzip compression + */ +app.use(compression()); + +/** + * Add useragent parsing + */ +app.use(useragent.express()); + +/** + * Parse incoming cookies + */ +app.use(cookieParser()); + +/** + * Serve favicon + */ +app.use(favicon(`${__dirname}/../public/static/favicon.ico`)); + +/** + * Serve static files + */ +app.use(express.static(`${__dirname}/../public`, { maxAge: ms('7 days') })); + +/** + * Use our server rendering function + * as the main route handler + */ +app.use('/', renderServer); + +/** + * Start server + */ +app.listen(port, () => console.log(`Listening on port ${port}`)); // eslint-disable-line no-console diff --git a/src/server-bootstrap.js b/src/server-bootstrap.js new file mode 100644 index 0000000..5432af5 --- /dev/null +++ b/src/server-bootstrap.js @@ -0,0 +1,38 @@ +/** + * Include babel polyfill + */ +require('babel-polyfill'); + +const { nodeEnv } = require('../config'); + +/** + * Add a global var to conform + * to webpack config + */ +global.__DEVELOPMENT__ = nodeEnv !== 'production'; // eslint-disable-line no-underscore-dangle + +/** + * Run all requires in our server-side + * app through babel, so we can use + * ES2015 everywhere + */ +require('babel-core/register')({ + sourceMap: !__DEVELOPMENT__, // eslint-disable-line no-underscore-dangle, no-undef +}); + +/** + * Use bluebird for as promise library + */ +global.Promise = require('bluebird'); + +/** + * Handle unhandled promise rejections + */ +process.on('unhandledRejection', (error) => { + throw error; +}); + +/** + * Run the actual app + */ +require('./server-app.js'); diff --git a/src/services/dummy-router.js b/src/services/dummy-router.js new file mode 100644 index 0000000..2ef3343 --- /dev/null +++ b/src/services/dummy-router.js @@ -0,0 +1,245 @@ +const { slice, merge } = require('aurora-deep-slice-merge'); +const clone = require('stringify-clone'); +const deepEqual = require('deep-equal'); + +const demo = { + pageInfo: { + title: 'Demo Aurora config', + }, + app: { + type: 'demo-app', + options: { + modules: [{ + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-data', + visibility: ['small'], + options: { + _dataOptions: { + name: 'A', + age: 1 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'B', + age: 2 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['large'], + options: { + _dataOptions: { + name: 'C', + age: 3 + } + } + }, { + type: 'demo-module-with-data', + options: { + _dataOptions: { + name: 'D', + age: 4 + } + } + }, { + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-data', + visibility: ['small'], + options: { + _dataOptions: { + name: 'E', + age: 5 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'F', + age: 6 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['large'], + options: { + _dataOptions: { + name: 'G', + age: 7 + } + } + }, { + type: 'demo-module-with-data', + options: { + _dataOptions: { + name: 'H', + age: 8 + } + } + }] + } + }] + } + }] + } + } +}; + +const redux = { + pageInfo: { + title: 'Demo Aurora config', + }, + app: { + type: 'demo-app', + options: { + modules: [{ + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-redux', + visibility: ['small'], + options: { + _dataOptions: { + name: 'AA-redux', + age: 11 + } + } + }, { + type: 'demo-module-with-redux', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'BB-redux', + age: 22 + } + } + }, { + type: 'demo-module-with-redux', + visibility: ['large'], + options: { + _dataOptions: { + name: 'CC-redux', + age: 33 + } + } + }, { + type: 'demo-module-with-redux', + options: { + _dataOptions: { + name: 'DD-redux', + age: 44 + } + } + }] + } + }] + } + } +}; + +const geo = { + pageInfo: { + title: 'Demo Aurora config', + }, + app: { + type: 'demo-app', + options: { + modules: [{ + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-geolocation', + visibility: ['small'], + options: { + _dataOptions: { + name: 'EE-geo', + age: 55 + } + } + }, { + type: 'demo-module-with-geolocation', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'FF-geo', + age: 66 + } + } + }, { + type: 'demo-module-with-geolocation', + visibility: ['large'], + options: { + _dataOptions: { + name: 'GG-geo', + age: 77 + } + } + }, { + type: 'demo-module-with-geolocation', + options: { + _dataOptions: { + name: 'HH-geo', + age: 88 + } + } + }] + } + }] + } + } +}; + +const routes = { + '/demo': demo, + '/demo/': demo, + '/redux': redux, + '/redux/': redux, + '/geo': geo, + '/geo/': geo, +}; + +module.exports = function({ + path, + query, + skip, + limit, + page, + version, + settings, +}) { + if (!routes[path]) { + return Promise.reject(new Error('Route requested not found')); + } + + let config = slice(routes[path], skip, limit); + + let hasMore = !deepEqual( + slice(routes[path], skip, limit), + slice(routes[path], skip, limit + 1) + ); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + meta: { + pagination: { + hasMore + }, + version: 123 + }, + data: { + config + } + }); + }, 1000); + }); +}; diff --git a/src/services/get-route-config.js b/src/services/get-route-config.js new file mode 100644 index 0000000..1d8936e --- /dev/null +++ b/src/services/get-route-config.js @@ -0,0 +1,71 @@ +/** + * Dependencies + */ +const isBrowser = require('is-client')(); +const axios = require('axios'); +const qs = require('qs'); +const urlJoin = require('url-join'); +const debug = require('debug')('aurora-frontend:get-page-config'); + +/** + * Environment + */ +const apiUrl = process.env.API_URL; + +/** + * Create request module + * that stringifies nested + * objects as query strings + * that the qs module can + * parse into valid objects + * on the other end + */ +const request = axios.create({ + paramsSerializer(params) { + return qs.stringify(params); + }, +}); + +/** + * Export a function that fetches + * a page config based on input parameters + */ +module.exports = function getRouteConfig({ + path = '/', + query = {}, + page = 0, + skip = 0, + limit = Infinity, + version = '', + settings = {}, +}) { + /** + * Set up the query we want to + * supply to the API endpoint + */ + const apiQuery = { + params: { + options: { ...query }, + page, + skip, + limit, + version, + settings, + }, + }; + + /** + * Create a closure for returning + * the requested path from the API + */ + const requestFromApi = () => { + return request + .get(urlJoin(apiUrl, 'routes', path), apiQuery) + .then(response => response.data); + }; + + /** + * Use either the cache or the API directly + */ + return requestFromApi(); +}; diff --git a/src/shared-components/VersionAlert.jsx b/src/shared-components/VersionAlert.jsx new file mode 100644 index 0000000..68bcc76 --- /dev/null +++ b/src/shared-components/VersionAlert.jsx @@ -0,0 +1,78 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Aurora module React component + */ +const VersionAlert = React.createClass({ + /** + * Set some initial state + */ + getInitialState() { + return { + loaded: false, + href: null, + }; + }, + + /** + * Initialize on mount + */ + componentDidMount() { + this.flagLoaded(); + }, + + flagLoaded() { + this.setState({ + loaded: true, + href: window.location.origin + (window.location.pathname || ''), + }); + }, + + /** + * Render the children (which in our case will be the App) + */ + render() { + /** + * Inline styles + */ + const styles = { + position: 'fixed', + top: 0, + left: 0, + width: '100%', + padding: '1em', + background: '#fffea1', + border: '1px solid #fc0', + textAlign: 'center', + }; + + const { loaded, href } = this.state; + + /** + * Only render if loaded on client + */ + if (!loaded) { + return null; + } + + /** + * Render a fixed header telling + * you that a newer version of + * this site is available + */ + return ( +
+

En ny versjon av denne siden er tilgjengelig

+ Klikk her for å laste +
+ ); + }, +}); + +/** + * Export component + */ +module.exports = VersionAlert; diff --git a/src/tests/placeholder.test.client.js b/src/tests/placeholder.test.client.js new file mode 100644 index 0000000..c97aa66 --- /dev/null +++ b/src/tests/placeholder.test.client.js @@ -0,0 +1,5 @@ +describe('Placeholder test (client)', () => { + it('should work', () => { + + }); +}) diff --git a/src/tests/placeholder.test.server.js b/src/tests/placeholder.test.server.js new file mode 100644 index 0000000..a2dad0a --- /dev/null +++ b/src/tests/placeholder.test.server.js @@ -0,0 +1,5 @@ +describe('Placeholder test (server)', () => { + it('should work', () => { + + }); +}) diff --git a/src/utils/create-html.js b/src/utils/create-html.js new file mode 100644 index 0000000..82a5619 --- /dev/null +++ b/src/utils/create-html.js @@ -0,0 +1,107 @@ +/** + * Utils + */ +const escapeHtml = require('../utils/escape-html'); + +/** + * Export a function that creates + * the initial HTML markup for the application + */ +module.exports = function createHtml({ + appMarkup = '
missing
', + criticalStyles = '', + pagination = {}, + settings = {}, + flags = {}, + experiments = {}, + version = '', + latestVersion = '', + hash = '', + config = {}, +}) { + /** + * Pull out the page information + * from the configuration + */ + const { + pageInfo: { + title = 'Default Page Title', + ogUrl = '', + ogImage = '', + ogDescription = '', + ogSiteName = '', + ogType = '', + }, + } = config; + + /** + * Create and return the actual HTML + */ + return ` + + + + + ${title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
${appMarkup}
+ + + + + + + + + + + + + + + + `; +}; diff --git a/src/utils/escape-html.js b/src/utils/escape-html.js new file mode 100644 index 0000000..8e3a47c --- /dev/null +++ b/src/utils/escape-html.js @@ -0,0 +1,8 @@ +/** + * Escape in the config object, so it won't destroy html + * + * TODO: This should be done somewhere else preferably! (In the API - or whoever delivers it) + */ +module.exports = function escapeHtml(configString) { + return configString.replace(/<\/script>/gi, '<\\/script>'); +}; diff --git a/src/utils/get-device-id.js b/src/utils/get-device-id.js new file mode 100644 index 0000000..83865a0 --- /dev/null +++ b/src/utils/get-device-id.js @@ -0,0 +1,77 @@ +/** + * Dependencies + */ +const Cookie = require('js-cookie'); +const ms = require('ms'); +const uuid = require('uuid'); +const isValidUuid = require('uuid-validate'); + +/** + * Configuration + */ +const cookieName = process.env.DEVICE_ID_COOKIE_NAME || 'SOL-User'; +const cookieDays = parseInt(process.env.DEVICE_ID_COOKIE_DAYS, 10) || 180; +const cookieExpire = ms(`${cookieDays} days`); + +/** + * Client side: Uses window.document.cookie + */ +function processClient() { + /** + * Fetch the existing cookie + */ + const userCookie = Cookie.get(cookieName); + + /** + * Validate the cookie as a UUID v4 or create a new one + */ + const deviceId = userCookie && isValidUuid(userCookie, 4) ? userCookie : uuid.v4(); + + /** + * Refresh the cookie value and expiry date + */ + Cookie.set(cookieName, deviceId, { + path: '/', + expires: new Date(Date.now() + cookieExpire), + }); + + /** + * Return the current device id + */ + return deviceId; +} + +/** + * Server side: get cookie from request, set it on the response + */ +function processServer(req, res) { + /** + * Fetch the existing cookie + */ + const userCookie = (req.cookies || {})[cookieName]; + + /** + * Validate the cookie as a UUID v4 or create a new one + */ + const deviceId = userCookie && isValidUuid(userCookie, 4) ? userCookie : uuid.v4(); + + /** + * Refresh the cookie value and expiry date + */ + res.cookie(cookieName, deviceId, { + path: '/', + expires: new Date(Date.now() + cookieExpire), + }); + + /** + * Return the current device id + */ + return deviceId; +} + +/** + * Create or retrieve the deviceID and its cookie, either on the server or client side. + */ +module.exports = function getDeviceId(req, res) { + return (!req && !res && window) ? processClient() : processServer(req, res); +}; diff --git a/src/utils/get-geolocation.js b/src/utils/get-geolocation.js new file mode 100644 index 0000000..79bf614 --- /dev/null +++ b/src/utils/get-geolocation.js @@ -0,0 +1,36 @@ +/* global navigator */ + +/** + * Get the current geolocation + * of a user (browser) + */ +module.exports = function getGeoLocation() { + /** + * Check if the geolocation API exists + * (if it doesn't we default to a pre-set value) + */ + if ( + typeof navigator === 'undefined' && + !('geolocation' in navigator) + ) { + return Promise.resolve({ + coords: { latitude: 0, longitude: 0 }, + timestamp: Date.now(), + }); + } + + /** + * Return a Promise of the current location + * (if it fails we default to a pre-set value) + */ + return new Promise((resolve) => { + navigator.geolocation.getCurrentPosition(resolve, () => { + resolve({ + coords: { latitude: 0, longitude: 0 }, + timestamp: Date.now(), + }); + }, { + maximumAge: 30000, + }); + }); +}; diff --git a/src/utils/get-pagination-settings.js b/src/utils/get-pagination-settings.js new file mode 100644 index 0000000..fdde4c2 --- /dev/null +++ b/src/utils/get-pagination-settings.js @@ -0,0 +1,14 @@ +/** + * Export a function that extends the + * default pagination settings with + * user defined ones + */ +module.exports = function getPaginationSettings(req) { + /** + * Set user defined pagination settings + */ + return { + perPage: 8, + initialLimit: 10, + }; +}; diff --git a/src/utils/get-user-settings.js b/src/utils/get-user-settings.js new file mode 100644 index 0000000..d855182 --- /dev/null +++ b/src/utils/get-user-settings.js @@ -0,0 +1,26 @@ +/** + * Import dependencies + */ +const getDeviceId = require('./get-device-id'); + +/** + * Export a function that returns the user settings + * we want to include in our application + */ +module.exports = function getUserSettings(req, res) { + /** + * Create some global settings + * + * - seed for deterministic random randomization + * - user identification + * - request ip address + * - etc.. + */ + return { + seed: JSON.stringify(Date.now()), + user: req.get('user') || 'not-logged-in', + ip: req.ip || '127.0.0.1', + deviceId: getDeviceId(req, res), + ua: req.useragent || '', + }; +}; diff --git a/src/utils/is-visible.js b/src/utils/is-visible.js new file mode 100644 index 0000000..230c4f7 --- /dev/null +++ b/src/utils/is-visible.js @@ -0,0 +1,95 @@ +/** + * Function for determining the visibility + * of a module given a visibility object in the form of: + * + * { + * include: ['small', 'medium', 'large'] + * } + * + * or + * + * { + * exclude: ['app'] + * } + * + * ..returning either true or false + */ +module.exports = function isVisible( + settings = {}, + query = {}, + visibility = {} +) { + /** + * Destructure visibility object + * + * NOTE: Should contain + * an array of includes + * or excludes + */ + const { + include, + exclude, + } = visibility; + + /** + * Destructure settings + * + * NOTE: Could include + * user agent, etc.. + * It's up to you really! + */ + const { + ua = {}, + } = settings; + + /** + * EXAMPLE: + * + * Default browser size/device + */ + let size = 'large'; + + /** + * Handle medium size browsers + */ + if (window.innerWidth < 768) { + size = 'medium'; + } + + /** + * Handle small size browsers + */ + if (window.innerWidth < 480) { + size = 'small'; + } + + /** + * Handle request query + */ + if ( + query.device === 'android' || + query.device === 'ios' + ) { + size = 'app'; + } + + /** + * Handle includes (precedence) + */ + if ((include || []).length) { + return !!~include.indexOf(size); // eslint-disable-line no-bitwise + } + + /** + * Handle excludes + */ + if ((exclude || []).length) { + return !~exclude.indexOf(size); // eslint-disable-line no-bitwise + } + + /** + * If no includes or excludes are provided + * we default to showing the element + */ + return true; +}; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..b70499c --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,71 @@ +/** + * Import dependencies + */ +const auroraWebpackConfig = require('aurora-core/webpack.config.js'); +const ProgressBarPlugin = require('progress-bar-webpack-plugin'); +const path = require('path'); +const webpack = require('webpack'); +const { + apiUrl, + hostname, + nodeEnv, +} = require('./config'); + +/** + * Environment + */ +const isProduction = nodeEnv === 'production'; + +/** + * Export a webpack plugin that extends the one from aurora-core + */ +module.exports = Object.assign({}, auroraWebpackConfig, { + /** + * Source maps + */ + devtool: isProduction ? 'cheap-source-map' : 'cheap-module-source-map', + + /** + * Replace the entry file name + */ + entry: auroraWebpackConfig.entry.slice().reverse().slice(1).reverse() + .concat([ + './src/client-bootstrap', + ]), + + /** + * Extend the plugin array + */ + plugins: auroraWebpackConfig.plugins.concat([ + new webpack.DefinePlugin({ + '__DEVELOPMENT__': JSON.stringify(!isProduction), + 'process.env.NODE_ENV': JSON.stringify(nodeEnv), + 'process.env.API_URL': JSON.stringify(apiUrl), + 'process.env.HOSTNAME': JSON.stringify(hostname), + }), + ]), + + /** + * Extend the output to make it work with the dev-server + */ + output: Object.assign({}, auroraWebpackConfig.output, { + path: path.join(__dirname, '/public/static/'), + publicPath: '/static/', + }), + + /** + * Add resolver aliases that + * we want to ignore in our + * webpack bundle + * + * NOTE: We just replace any + * server specific modules + * with a no-op (from npm) + */ + + resolve: Object.assign({}, auroraWebpackConfig.resolve, { + alias: Object.assign({}, (auroraWebpackConfig.resolve || {}).alias, { + redis: 'no-op', + }), + }), +});