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 (
+
+ );
+ },
+});
+
+/**
+ * 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',
+ }),
+ }),
+});