From 6da788f09a4f71ae3710a6f257beb9caa074579d Mon Sep 17 00:00:00 2001 From: misund Date: Sun, 9 Apr 2017 12:48:07 +0200 Subject: [PATCH] Introduce aurora-core --- .babelrc | 7 + .editorconfig | 9 + .eslintignore | 6 + .eslintrc | 41 +++ .eslintrc.json | 52 ++++ .gitignore | 66 +++++ README.md | 32 +++ config.js | 8 + dev-server.js | 18 ++ karma.conf.js | 9 + package.json | 110 ++++++++ postcss.config.js | 12 + public/static/favicon.ico | Bin 0 -> 2862 bytes public/static/index.html | 12 + src/client-bootstrap.js | 42 +++ src/modules/DemoAppModule/DemoAppModule.jsx | 109 ++++++++ .../DemoAppModule/components/readme.md | 4 + src/modules/DemoAppModule/injects/readme.md | 8 + src/modules/DemoAppModule/injects/scripts.js | 28 ++ src/modules/DemoAppModule/readme.md | 78 ++++++ src/modules/DemoAppModule/services/readme.md | 8 + .../DemoAppModule/styles/my-app-styles.scss | 3 + src/modules/DemoAppModule/styles/readme.md | 4 + .../DemoHigherOrderModule.jsx | 93 +++++++ .../components/readme.md | 4 + src/modules/DemoHigherOrderModule/readme.md | 78 ++++++ .../styles/my-container-styles.scss | 3 + .../DemoHigherOrderModule/styles/readme.md | 4 + src/modules/DemoModule/DemoModule.jsx | 66 +++++ src/modules/DemoModule/components/Loading.jsx | 17 ++ src/modules/DemoModule/components/Main.jsx | 30 +++ src/modules/DemoModule/components/readme.md | 4 + src/modules/DemoModule/injects/readme.md | 8 + src/modules/DemoModule/injects/scripts.js | 28 ++ src/modules/DemoModule/readme.md | 51 ++++ .../DemoModule/styles/my-module-styles.scss | 23 ++ src/modules/DemoModule/styles/readme.md | 4 + .../DemoModuleWithData/DemoModuleWithData.jsx | 98 +++++++ .../DemoModuleWithData/components/Loading.jsx | 17 ++ .../DemoModuleWithData/components/Main.jsx | 30 +++ .../DemoModuleWithData/components/readme.md | 4 + .../DemoModuleWithData/injects/readme.md | 8 + .../DemoModuleWithData/injects/scripts.js | 28 ++ src/modules/DemoModuleWithData/readme.md | 51 ++++ .../DemoModuleWithData/services/get-data.js | 13 + .../DemoModuleWithData/services/readme.md | 8 + .../styles/my-module-styles.scss | 23 ++ .../DemoModuleWithData/styles/readme.md | 4 + .../DemoModuleWithGeolocation.jsx | 106 ++++++++ .../components/Loading.jsx | 17 ++ .../components/Main.jsx | 25 ++ .../components/readme.md | 4 + .../injects/readme.md | 8 + .../injects/scripts.js | 28 ++ .../DemoModuleWithGeolocation/readme.md | 51 ++++ .../services/get-geolocated-data.js | 62 +++++ .../services/readme.md | 8 + .../styles/my-module-styles.scss | 23 ++ .../styles/readme.md | 4 + .../DemoModuleWithPersonalization.jsx | 105 ++++++++ .../components/Loading.jsx | 17 ++ .../components/Main.jsx | 25 ++ .../components/readme.md | 4 + .../injects/readme.md | 8 + .../injects/scripts.js | 28 ++ .../DemoModuleWithPersonalization/readme.md | 58 +++++ .../services/get-personalized-data.js | 27 ++ .../services/readme.md | 8 + .../styles/my-module-styles.scss | 23 ++ .../styles/readme.md | 4 + .../DemoModuleWithRandomization.jsx | 105 ++++++++ .../components/Loading.jsx | 17 ++ .../components/Main.jsx | 25 ++ .../components/readme.md | 4 + .../injects/readme.md | 8 + .../injects/scripts.js | 28 ++ .../DemoModuleWithRandomization/readme.md | 58 +++++ .../services/get-randomized-data.js | 27 ++ .../services/readme.md | 8 + .../styles/my-module-styles.scss | 23 ++ .../styles/readme.md | 4 + .../DemoModuleWithRedux.jsx | 77 ++++++ .../DemoModuleWithRedux/actions/index.js | 80 ++++++ .../components/ErrorMessage.jsx | 23 ++ .../components/Loading.jsx | 17 ++ .../DemoModuleWithRedux/components/Main.jsx | 115 ++++++++ .../DemoModuleWithRedux/components/readme.md | 4 + .../DemoModuleWithRedux/injects/readme.md | 8 + .../DemoModuleWithRedux/injects/scripts.js | 28 ++ src/modules/DemoModuleWithRedux/readme.md | 58 +++++ .../DemoModuleWithRedux/reducers/index.js | 37 +++ .../DemoModuleWithRedux/services/get-data.js | 13 + .../DemoModuleWithRedux/services/readme.md | 8 + .../styles/my-module-styles.scss | 23 ++ .../DemoModuleWithRedux/styles/readme.md | 4 + src/modules/DoesNotExist/DoesNotExist.jsx | 15 ++ src/modules/get-module.js | 98 +++++++ src/server-app.js | 75 ++++++ src/server-bootstrap.js | 38 +++ src/services/dummy-router.js | 245 ++++++++++++++++++ src/services/get-route-config.js | 71 +++++ src/shared-components/VersionAlert.jsx | 78 ++++++ src/tests/placeholder.test.client.js | 5 + src/tests/placeholder.test.server.js | 5 + src/utils/create-html.js | 107 ++++++++ src/utils/escape-html.js | 8 + src/utils/get-device-id.js | 77 ++++++ src/utils/get-geolocation.js | 36 +++ src/utils/get-pagination-settings.js | 14 + src/utils/get-user-settings.js | 26 ++ src/utils/is-visible.js | 95 +++++++ webpack.config.js | 71 +++++ 112 files changed, 3872 insertions(+) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.js create mode 100644 dev-server.js create mode 100644 karma.conf.js create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/static/favicon.ico create mode 100644 public/static/index.html create mode 100644 src/client-bootstrap.js create mode 100644 src/modules/DemoAppModule/DemoAppModule.jsx create mode 100644 src/modules/DemoAppModule/components/readme.md create mode 100644 src/modules/DemoAppModule/injects/readme.md create mode 100644 src/modules/DemoAppModule/injects/scripts.js create mode 100644 src/modules/DemoAppModule/readme.md create mode 100644 src/modules/DemoAppModule/services/readme.md create mode 100644 src/modules/DemoAppModule/styles/my-app-styles.scss create mode 100644 src/modules/DemoAppModule/styles/readme.md create mode 100644 src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx create mode 100644 src/modules/DemoHigherOrderModule/components/readme.md create mode 100644 src/modules/DemoHigherOrderModule/readme.md create mode 100644 src/modules/DemoHigherOrderModule/styles/my-container-styles.scss create mode 100644 src/modules/DemoHigherOrderModule/styles/readme.md create mode 100644 src/modules/DemoModule/DemoModule.jsx create mode 100644 src/modules/DemoModule/components/Loading.jsx create mode 100644 src/modules/DemoModule/components/Main.jsx create mode 100644 src/modules/DemoModule/components/readme.md create mode 100644 src/modules/DemoModule/injects/readme.md create mode 100644 src/modules/DemoModule/injects/scripts.js create mode 100644 src/modules/DemoModule/readme.md create mode 100644 src/modules/DemoModule/styles/my-module-styles.scss create mode 100644 src/modules/DemoModule/styles/readme.md create mode 100644 src/modules/DemoModuleWithData/DemoModuleWithData.jsx create mode 100644 src/modules/DemoModuleWithData/components/Loading.jsx create mode 100644 src/modules/DemoModuleWithData/components/Main.jsx create mode 100644 src/modules/DemoModuleWithData/components/readme.md create mode 100644 src/modules/DemoModuleWithData/injects/readme.md create mode 100644 src/modules/DemoModuleWithData/injects/scripts.js create mode 100644 src/modules/DemoModuleWithData/readme.md create mode 100644 src/modules/DemoModuleWithData/services/get-data.js create mode 100644 src/modules/DemoModuleWithData/services/readme.md create mode 100644 src/modules/DemoModuleWithData/styles/my-module-styles.scss create mode 100644 src/modules/DemoModuleWithData/styles/readme.md create mode 100644 src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx create mode 100644 src/modules/DemoModuleWithGeolocation/components/Loading.jsx create mode 100644 src/modules/DemoModuleWithGeolocation/components/Main.jsx create mode 100644 src/modules/DemoModuleWithGeolocation/components/readme.md create mode 100644 src/modules/DemoModuleWithGeolocation/injects/readme.md create mode 100644 src/modules/DemoModuleWithGeolocation/injects/scripts.js create mode 100644 src/modules/DemoModuleWithGeolocation/readme.md create mode 100644 src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js create mode 100644 src/modules/DemoModuleWithGeolocation/services/readme.md create mode 100644 src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss create mode 100644 src/modules/DemoModuleWithGeolocation/styles/readme.md create mode 100644 src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx create mode 100644 src/modules/DemoModuleWithPersonalization/components/Loading.jsx create mode 100644 src/modules/DemoModuleWithPersonalization/components/Main.jsx create mode 100644 src/modules/DemoModuleWithPersonalization/components/readme.md create mode 100644 src/modules/DemoModuleWithPersonalization/injects/readme.md create mode 100644 src/modules/DemoModuleWithPersonalization/injects/scripts.js create mode 100644 src/modules/DemoModuleWithPersonalization/readme.md create mode 100644 src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js create mode 100644 src/modules/DemoModuleWithPersonalization/services/readme.md create mode 100644 src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss create mode 100644 src/modules/DemoModuleWithPersonalization/styles/readme.md create mode 100644 src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx create mode 100644 src/modules/DemoModuleWithRandomization/components/Loading.jsx create mode 100644 src/modules/DemoModuleWithRandomization/components/Main.jsx create mode 100644 src/modules/DemoModuleWithRandomization/components/readme.md create mode 100644 src/modules/DemoModuleWithRandomization/injects/readme.md create mode 100644 src/modules/DemoModuleWithRandomization/injects/scripts.js create mode 100644 src/modules/DemoModuleWithRandomization/readme.md create mode 100644 src/modules/DemoModuleWithRandomization/services/get-randomized-data.js create mode 100644 src/modules/DemoModuleWithRandomization/services/readme.md create mode 100644 src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss create mode 100644 src/modules/DemoModuleWithRandomization/styles/readme.md create mode 100644 src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx create mode 100644 src/modules/DemoModuleWithRedux/actions/index.js create mode 100644 src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx create mode 100644 src/modules/DemoModuleWithRedux/components/Loading.jsx create mode 100644 src/modules/DemoModuleWithRedux/components/Main.jsx create mode 100644 src/modules/DemoModuleWithRedux/components/readme.md create mode 100644 src/modules/DemoModuleWithRedux/injects/readme.md create mode 100644 src/modules/DemoModuleWithRedux/injects/scripts.js create mode 100644 src/modules/DemoModuleWithRedux/readme.md create mode 100644 src/modules/DemoModuleWithRedux/reducers/index.js create mode 100644 src/modules/DemoModuleWithRedux/services/get-data.js create mode 100644 src/modules/DemoModuleWithRedux/services/readme.md create mode 100644 src/modules/DemoModuleWithRedux/styles/my-module-styles.scss create mode 100644 src/modules/DemoModuleWithRedux/styles/readme.md create mode 100644 src/modules/DoesNotExist/DoesNotExist.jsx create mode 100644 src/modules/get-module.js create mode 100644 src/server-app.js create mode 100644 src/server-bootstrap.js create mode 100644 src/services/dummy-router.js create mode 100644 src/services/get-route-config.js create mode 100644 src/shared-components/VersionAlert.jsx create mode 100644 src/tests/placeholder.test.client.js create mode 100644 src/tests/placeholder.test.server.js create mode 100644 src/utils/create-html.js create mode 100644 src/utils/escape-html.js create mode 100644 src/utils/get-device-id.js create mode 100644 src/utils/get-geolocation.js create mode 100644 src/utils/get-pagination-settings.js create mode 100644 src/utils/get-user-settings.js create mode 100644 src/utils/is-visible.js create mode 100644 webpack.config.js 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 0000000000000000000000000000000000000000..b63caf3aa46915f386f40ff25ccc600a60e8ec89 GIT binary patch literal 2862 zcmeH}Yiv{J8OQ%l=>m$Bnm89H!H!97V~i8W!G0x!xu(0zf6+Z8}G7r62O@0a5s+x^nQ zxI9;0Bd@(TPK&ck@Ae_r|J}|n8i%sj2W8153e*!QEc2nDbP9##(^FYMCE} znpqTSMQAF0(CDX7tP7!Zc>o%NA8M|b>BCrN3`1uahq-zR#;O4HRU%AQ5tiB+tf~#5 zdbJ-`$1GObLa26#usNo&x^5aZYl5h8MqqCcVQ-2+Yl=d*N<^!)HX=4H^)%Zl;kpjwd+D~ZU|%Tqe;}aMA5i0fk(C^v0=*$ zw(kz2tv!H_&H&o?i#U8lM9Y>0w(Lz{Z&wl>-JjqSA% zaXNvM&%|-&Yy!`mOW^5qpW^KBXE^^{9MAuF4lj&ch2Z@sE^>MC#p@6*eFE>#uj0~6 z*Vw+mFJAc^FaPp0y!`6tc=gwF_|ja1@YlcJ#E1X5iE-a8_$E^j#T3GkG$PX1kYefk zU6pV9zm3412xN7Rn?2i_TJ{`T=`fec!pY~$}7;Q)#}_Fg`yFU8;vSe|M6pkg9A%)6#2cIO(vD)WSyn7@`Of_o3l=D zHkpijj}HwE?aRwkn*YN5^FNCHWTYKvHlZl^8C=r@Yum#G2^3o zV?@z@ATnB_3(Xf-h@@T~B-SuuEuwedzd`T5cY|1)h&76|`bm1IXq;Hv=gROG|vTy=~fWc7#&~TZ|sA zj?r?9MDku(?`dKxMXbM>efWI0h;^TeG=3^5o4MnoRQpk?dTltx{31gw`>!MVs6{%1YWD8K?GWJnELj|SNR9ya1YDY)JZ(b88lOs+>_MxxI`jF$n%p&F$ z$23gLWGb!*@*dcShU3XT);3Q14?PtVn3p6Q^OKmP#Oxww3o&bGwKGP{D`J-M?<4yV zsYn|j=05G-H!B=HA=zu3u{86RR@L*pX-*QehM1M~(ks{L(&g*K49&!pS)BQeGxtR2 zPq*(6+IMwIiYJGnqcx4m`D*9K#GIz_i4-vt*;j&?S;UOX#FKeX<@yNU4{^S|Gcf9K z1~TtL_drb9cT^fRKXEuB^bSPq>$isQd4KQzmRs=ize@SLZsD8m8!!@WF#rGn literal 0 HcmV?d00001 diff --git a/public/static/index.html b/public/static/index.html new file mode 100644 index 0000000..72cf307 --- /dev/null +++ b/public/static/index.html @@ -0,0 +1,12 @@ + + + + Aurora Frontend Demo (development) + + + + +
+ + + diff --git a/src/client-bootstrap.js b/src/client-bootstrap.js new file mode 100644 index 0000000..a0db276 --- /dev/null +++ b/src/client-bootstrap.js @@ -0,0 +1,42 @@ +/** + * Polyfills and shims + */ +require('es5-shim/es5-shim'); +require('es5-shim/es5-sham'); +require('babel-polyfill'); + +/** + * Use bluebird for better error handling in development + */ +if (__DEVELOPMENT__) { + /** + * Use bluebird for promises + */ + window.Promise = require('bluebird'); + + /** + * Suppress warnings of runaway Promises. + * Bug when used together with Babel causes + * harmless and non-applicative (but annyoing) warnings.. + */ + Promise.config({ + warnings: false + }); + + /** + * Globally handle unhandled promise rejections + */ + window.addEventListener('unhandledrejection', function(e) { + e.preventDefault(); + throw e.detail.reason; + }); +} + +/** + * Initialize and render the client app + */ +require('aurora-core/dist/render/client')({ + getRoute: require('./services/get-route-config'), + getModule: require('./modules/get-module'), + isVisible: require('./utils/is-visible'), +}); diff --git a/src/modules/DemoAppModule/DemoAppModule.jsx b/src/modules/DemoAppModule/DemoAppModule.jsx new file mode 100644 index 0000000..146d22f --- /dev/null +++ b/src/modules/DemoAppModule/DemoAppModule.jsx @@ -0,0 +1,109 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Aurora specific mixin to conveniently + * render modules for higher order components + */ +const RenderChildrenMixin = require('aurora-core/dist/mixins/render-children-mixin'); + +/** + * Import shared components + */ +const VersionAlert = require('../../shared-components/VersionAlert.jsx'); + +/** + * An app module + * that should be used as a + * top level module + * in the config + */ +const DemoAppModule = React.createClass({ + /** + * These are all Aurora-specific mixins + * to include different functionality + * that the module needs + */ + mixins: [ + RenderChildrenMixin() + ], + + propTypes: { + pagination: React.PropTypes.shape({ + isLoading: React.PropTypes.bool + }), + newVersionAvailable: React.PropTypes.bool, + }, + + getDefaultProps() { + return { + pagination: { + isLoading: false + }, + newVersionAvailable: false, + }; + }, + + render() { + const { + pagination: { isLoading }, + newVersionAvailable, + } = this.props; + + return ( +
+ {newVersionAvailable && } + {/* the function below is exposed by render-modules-mixin */} + {this.renderChildren()} + {isLoading &&
Loading...
} +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-app-styles.scss`, + clientStyles: isBrowser && require('./styles/my-app-styles.scss'), + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }), +); + +/** + * Finally export the module + * extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoAppModule); diff --git a/src/modules/DemoAppModule/components/readme.md b/src/modules/DemoAppModule/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoAppModule/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoAppModule/injects/readme.md b/src/modules/DemoAppModule/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoAppModule/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoAppModule/injects/scripts.js b/src/modules/DemoAppModule/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoAppModule/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoAppModule/readme.md b/src/modules/DemoAppModule/readme.md new file mode 100644 index 0000000..66e2dd1 --- /dev/null +++ b/src/modules/DemoAppModule/readme.md @@ -0,0 +1,78 @@ +Aurora App Module Template +========================== + +A module template for building top level Aurora app modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +__NOTE:__ +An app module means that your module renders other modules passed into it, and has an array of `modules` in `options`. + +#### Include it in the module-map to enable use + +```js +// ./src/modules/module-map.js +const moduleMap = { + 'my-app-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyAppModule/MyAppModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-app-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....', + modules: [...] // other modules + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. +- `modules` - a set of modules + +__Example implementation__ + +```js +render() { + //...... + + let children = this.props.modules.map((module, i) => { + let Module = module.type; + + return ( + + ); + }); + + //...... +} +``` diff --git a/src/modules/DemoAppModule/services/readme.md b/src/modules/DemoAppModule/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoAppModule/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoAppModule/styles/my-app-styles.scss b/src/modules/DemoAppModule/styles/my-app-styles.scss new file mode 100644 index 0000000..38717f9 --- /dev/null +++ b/src/modules/DemoAppModule/styles/my-app-styles.scss @@ -0,0 +1,3 @@ +.awesome-app { + +} diff --git a/src/modules/DemoAppModule/styles/readme.md b/src/modules/DemoAppModule/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoAppModule/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx b/src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx new file mode 100644 index 0000000..06aca5b --- /dev/null +++ b/src/modules/DemoHigherOrderModule/DemoHigherOrderModule.jsx @@ -0,0 +1,93 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * A higher order module + * that can wrap any number of modules + * + * A flow within the flow (grouping things f.ex) + */ +const DemoHigherOrderModule = React.createClass({ + propTypes: { + element: React.PropTypes.string, + classes: React.PropTypes.string, + modules: React.PropTypes.array, + }, + + getDefaultProps() { + return { + element: 'div', + classes: '', + modules: [], + }; + }, + + render() { + let { + element, + classes, + modules, + } = this.props; + + /** + * This is the higher order part + * (just copy/paste this basically) + * and do whatever you need to + * render stuff the way you want + */ + let children = modules.map((module, i) => { + let Module = module.type; + + return ( + + ); + }); + + return ( + React.createElement(element, { + className: classes + }, children) + ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-container-styles.scss`, + clientStyles: isBrowser && require('./styles/my-container-styles.scss'), + }), +); + +/** + * Finally export the module + * extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoHigherOrderModule); diff --git a/src/modules/DemoHigherOrderModule/components/readme.md b/src/modules/DemoHigherOrderModule/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoHigherOrderModule/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoHigherOrderModule/readme.md b/src/modules/DemoHigherOrderModule/readme.md new file mode 100644 index 0000000..bb6b971 --- /dev/null +++ b/src/modules/DemoHigherOrderModule/readme.md @@ -0,0 +1,78 @@ +Aurora Higher Order Module Template +=================================== + +A module template for building higher order Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +__NOTE:__ +A higher order module means that your module renders other modules passed into it, and has an array of `modules` in its `options`. + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-higher-order-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyHigherOrderModule/MyHigherOrderModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-higher-order-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....', + modules: [...] // other modules + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. +- `modules` - a set of modules + +__Example implementation__ + +```js +render() { + //...... + + let children = this.props.modules.map((module, i) => { + let Module = module.type; + + return ( + + ); + }); + + //...... +} +``` diff --git a/src/modules/DemoHigherOrderModule/styles/my-container-styles.scss b/src/modules/DemoHigherOrderModule/styles/my-container-styles.scss new file mode 100644 index 0000000..d343113 --- /dev/null +++ b/src/modules/DemoHigherOrderModule/styles/my-container-styles.scss @@ -0,0 +1,3 @@ +.awesome-container { + +} diff --git a/src/modules/DemoHigherOrderModule/styles/readme.md b/src/modules/DemoHigherOrderModule/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoHigherOrderModule/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModule/DemoModule.jsx b/src/modules/DemoModule/DemoModule.jsx new file mode 100644 index 0000000..fc2c532 --- /dev/null +++ b/src/modules/DemoModule/DemoModule.jsx @@ -0,0 +1,66 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModule = React.createClass({ + render() { + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }), +); + +/** + * Finally export the module + * extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoModule); diff --git a/src/modules/DemoModule/components/Loading.jsx b/src/modules/DemoModule/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModule/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModule/components/Main.jsx b/src/modules/DemoModule/components/Main.jsx new file mode 100644 index 0000000..ec67385 --- /dev/null +++ b/src/modules/DemoModule/components/Main.jsx @@ -0,0 +1,30 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: { + data: React.PropTypes.shape({ + name: React.PropTypes.string, + age: React.PropTypes.number + }) + }, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModule/components/readme.md b/src/modules/DemoModule/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModule/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModule/injects/readme.md b/src/modules/DemoModule/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModule/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModule/injects/scripts.js b/src/modules/DemoModule/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModule/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModule/readme.md b/src/modules/DemoModule/readme.md new file mode 100644 index 0000000..e34cbd4 --- /dev/null +++ b/src/modules/DemoModule/readme.md @@ -0,0 +1,51 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModule/styles/my-module-styles.scss b/src/modules/DemoModule/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModule/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModule/styles/readme.md b/src/modules/DemoModule/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModule/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithData/DemoModuleWithData.jsx b/src/modules/DemoModuleWithData/DemoModuleWithData.jsx new file mode 100644 index 0000000..0285d15 --- /dev/null +++ b/src/modules/DemoModuleWithData/DemoModuleWithData.jsx @@ -0,0 +1,98 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithData = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + fetchData: require('./services/get-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithData); diff --git a/src/modules/DemoModuleWithData/components/Loading.jsx b/src/modules/DemoModuleWithData/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithData/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithData/components/Main.jsx b/src/modules/DemoModuleWithData/components/Main.jsx new file mode 100644 index 0000000..ec67385 --- /dev/null +++ b/src/modules/DemoModuleWithData/components/Main.jsx @@ -0,0 +1,30 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: { + data: React.PropTypes.shape({ + name: React.PropTypes.string, + age: React.PropTypes.number + }) + }, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithData/components/readme.md b/src/modules/DemoModuleWithData/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithData/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithData/injects/readme.md b/src/modules/DemoModuleWithData/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithData/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithData/injects/scripts.js b/src/modules/DemoModuleWithData/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithData/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithData/readme.md b/src/modules/DemoModuleWithData/readme.md new file mode 100644 index 0000000..e79fba4 --- /dev/null +++ b/src/modules/DemoModuleWithData/readme.md @@ -0,0 +1,51 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules that fetch their own data (both on the server and client-side). + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithData/services/get-data.js b/src/modules/DemoModuleWithData/services/get-data.js new file mode 100644 index 0000000..7b2fb86 --- /dev/null +++ b/src/modules/DemoModuleWithData/services/get-data.js @@ -0,0 +1,13 @@ +/** + * Dependencies + */ +const request = require('axios'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function(options) { + return Promise.resolve(JSON.parse(JSON.stringify(options))); +}; diff --git a/src/modules/DemoModuleWithData/services/readme.md b/src/modules/DemoModuleWithData/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithData/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithData/styles/my-module-styles.scss b/src/modules/DemoModuleWithData/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithData/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithData/styles/readme.md b/src/modules/DemoModuleWithData/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithData/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx b/src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx new file mode 100644 index 0000000..8831e27 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx @@ -0,0 +1,106 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithGeolocation = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + disableServerLoading: true, + fetchData: require('./services/get-geolocated-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithGeolocation); diff --git a/src/modules/DemoModuleWithGeolocation/components/Loading.jsx b/src/modules/DemoModuleWithGeolocation/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithGeolocation/components/Main.jsx b/src/modules/DemoModuleWithGeolocation/components/Main.jsx new file mode 100644 index 0000000..4bbf6d7 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/components/Main.jsx @@ -0,0 +1,25 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: {}, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithGeolocation/components/readme.md b/src/modules/DemoModuleWithGeolocation/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithGeolocation/injects/readme.md b/src/modules/DemoModuleWithGeolocation/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithGeolocation/injects/scripts.js b/src/modules/DemoModuleWithGeolocation/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithGeolocation/readme.md b/src/modules/DemoModuleWithGeolocation/readme.md new file mode 100644 index 0000000..5a45ecf --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/readme.md @@ -0,0 +1,51 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules that is based on geolocated data. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend + +#### Include it in the module-map to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js b/src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js new file mode 100644 index 0000000..6094867 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/services/get-geolocated-data.js @@ -0,0 +1,62 @@ +/** + * Dependencies + */ +const request = require('axios'); +const urlJoin = require('url-join'); + +/** + * Environment + */ +const { apiUrl } = require('../../../../config'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function getGeoLocatedData(options) { + /** + * Feature detect the Geolocation API + * (NOTE: this only works client-side!) + */ + if ( + typeof navigator === 'undefined' && + !(geolocation in navigator) // eslint-disable-line no-undef + ) { + return Promise.reject(new Error('Need browser to perform geolocation')); + } + + /** + * Fetch data from an API/service where you supply + * the geolocation data as part of the input + * + * NOTE: You should only change the end-point here, + * as the rest of the data will be provided by the + * Aurora "framework" using the 'withData' decorator + * + * NOTE: Most often you can just copy/paste this file + * and change the url / API endpoint + * + * NOTE: Using __latitude and __longitude is a convention + * to make things predictable + */ + return new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject, { + maximumAge: 30000, + }); + }) + .then(({ coords: { latitude, longitude } }) => { + return request + .get(urlJoin(apiUrl, 'data', 'geolocated-data'), { + params: Object.assign({}, options, { + __latitude: latitude, + __longitude: longitude, + }), + }) + .then(response => response.data); + }) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); +}; diff --git a/src/modules/DemoModuleWithGeolocation/services/readme.md b/src/modules/DemoModuleWithGeolocation/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss b/src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithGeolocation/styles/readme.md b/src/modules/DemoModuleWithGeolocation/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithGeolocation/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx b/src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx new file mode 100644 index 0000000..64702ed --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx @@ -0,0 +1,105 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithPersonalization = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + fetchData: require('./services/get-personalized-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithPersonalization); diff --git a/src/modules/DemoModuleWithPersonalization/components/Loading.jsx b/src/modules/DemoModuleWithPersonalization/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithPersonalization/components/Main.jsx b/src/modules/DemoModuleWithPersonalization/components/Main.jsx new file mode 100644 index 0000000..4bbf6d7 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/components/Main.jsx @@ -0,0 +1,25 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: {}, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithPersonalization/components/readme.md b/src/modules/DemoModuleWithPersonalization/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithPersonalization/injects/readme.md b/src/modules/DemoModuleWithPersonalization/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithPersonalization/injects/scripts.js b/src/modules/DemoModuleWithPersonalization/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithPersonalization/readme.md b/src/modules/DemoModuleWithPersonalization/readme.md new file mode 100644 index 0000000..fcb3f8c --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/readme.md @@ -0,0 +1,58 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend +When you want to outsource a module to npm see [aurora-module-template](https://github.com/soldotno/aurora-module-template) + +#### Include it in your module-map and module-includes to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; + +// module-includes.js +module.exports = [ + 'my-module', + ... +]; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js b/src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js new file mode 100644 index 0000000..4799476 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/services/get-personalized-data.js @@ -0,0 +1,27 @@ +/** + * Dependencies + */ +const request = require('axios'); +const urlJoin = require('url-join'); + +/** + * Environment + */ +const apiUrl = process.env.API_URL; + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function(options) { + return request + .get(urlJoin(apiUrl, 'data', 'personalized-data'), { + params: options + }) + .then(response => response.data) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); +}; diff --git a/src/modules/DemoModuleWithPersonalization/services/readme.md b/src/modules/DemoModuleWithPersonalization/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss b/src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithPersonalization/styles/readme.md b/src/modules/DemoModuleWithPersonalization/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithPersonalization/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx b/src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx new file mode 100644 index 0000000..76bd2b9 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/DemoModuleWithRandomization.jsx @@ -0,0 +1,105 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Loading = require('./components/Loading.jsx'); +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithRandomization = React.createClass({ + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoadingData, + data + } = this.props; + + /** + * Until we have data + * show a loading indicator + */ + if (isLoadingData) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withData = require('aurora-core/dist/decorators/with-data'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Aurora + * data fetching + * (optional) + */ + withData({ + fetchData: require('./services/get-randomized-data'), + dataProp: 'data', + loadingProp: 'isLoadingData' + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }) +); + +/** + * Finally export the module + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithRandomization); diff --git a/src/modules/DemoModuleWithRandomization/components/Loading.jsx b/src/modules/DemoModuleWithRandomization/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithRandomization/components/Main.jsx b/src/modules/DemoModuleWithRandomization/components/Main.jsx new file mode 100644 index 0000000..4bbf6d7 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/components/Main.jsx @@ -0,0 +1,25 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: {}, + + render() { + const { + data = {} + } = this.props; + + return ( +
+ {JSON.stringify(data)} +
+ ); + } +}); + +module.exports = Main; diff --git a/src/modules/DemoModuleWithRandomization/components/readme.md b/src/modules/DemoModuleWithRandomization/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithRandomization/injects/readme.md b/src/modules/DemoModuleWithRandomization/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithRandomization/injects/scripts.js b/src/modules/DemoModuleWithRandomization/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithRandomization/readme.md b/src/modules/DemoModuleWithRandomization/readme.md new file mode 100644 index 0000000..fcb3f8c --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/readme.md @@ -0,0 +1,58 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend +When you want to outsource a module to npm see [aurora-module-template](https://github.com/soldotno/aurora-module-template) + +#### Include it in your module-map and module-includes to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; + +// module-includes.js +module.exports = [ + 'my-module', + ... +]; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithRandomization/services/get-randomized-data.js b/src/modules/DemoModuleWithRandomization/services/get-randomized-data.js new file mode 100644 index 0000000..7ba2825 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/services/get-randomized-data.js @@ -0,0 +1,27 @@ +/** + * Dependencies + */ +const request = require('axios'); +const urlJoin = require('url-join'); + +/** + * Environment + */ +const { apiUrl } = require('../../../../config'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function getRandomizedData(options) { + return request + .get(urlJoin(apiUrl, 'data', 'randomized-data'), { + params: options, + }) + .then(response => response.data) + .catch((error) => { + console.error(error); + return Promise.reject(error); + }); +}; diff --git a/src/modules/DemoModuleWithRandomization/services/readme.md b/src/modules/DemoModuleWithRandomization/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss b/src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithRandomization/styles/readme.md b/src/modules/DemoModuleWithRandomization/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithRandomization/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx b/src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx new file mode 100644 index 0000000..97bca81 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/DemoModuleWithRedux.jsx @@ -0,0 +1,77 @@ +/** + * Dependencies + */ +const React = require('react'); +const isBrowser = require('is-client')(); +const compose = require('lodash.compose'); + +/** + * Components for this module + */ +const Main = require('./components/Main.jsx'); + +/** + * Aurora module React component + */ +const DemoModuleWithRedux = React.createClass({ + render() { + return ( +
+ ); + } +}); + +/** + * Aurora decorators + */ +const withVisibility = require('aurora-core/dist/decorators/with-visibility'); +const withStyles = require('aurora-core/dist/decorators/with-styles'); +const withRedux = require('aurora-core/dist/decorators/with-redux'); +const withScripts = require('aurora-core/dist/decorators/with-scripts'); + +/** + * Create a function that extends the + * component with all the Aurora specific functionality + */ +const extendWithAuroraFunctionality = compose( + /** + * Wrap the module in Aurora + * visibility resolving + * (required) + */ + withVisibility, + /** + * Wrap the module in Aurora + * style injection + * (required) + */ + withStyles({ + serverPath: `${__dirname}/styles/my-module-styles.scss`, + clientStyles: isBrowser && require('./styles/my-module-styles.scss'), + }), + /** + * Wrap the module in Redux state handling + * (optional) + */ + withRedux({ + reducer: require('./reducers').default, + initialState: { + error: null, + isLoading: true, + data: {}, + } + }), + /** + * Wrap the module in Aurora + * script injection + * (optional) + */ + withScripts({ + scripts: require('./injects/scripts'), + }), +); + +/** + * Finally export the module extended with Aurora functionality + */ +module.exports = extendWithAuroraFunctionality(DemoModuleWithRedux); diff --git a/src/modules/DemoModuleWithRedux/actions/index.js b/src/modules/DemoModuleWithRedux/actions/index.js new file mode 100644 index 0000000..59a951f --- /dev/null +++ b/src/modules/DemoModuleWithRedux/actions/index.js @@ -0,0 +1,80 @@ +/** + * Import services we need + * to fetch data from the API + */ +import getData from '../services/get-data'; + +/** + * Constants for naming + * and referencing action types + */ +export const POPULATE_DATA_PENDING = 'POPULATE_DATA_PENDING'; +export const POPULATE_DATA_SUCCESS = 'POPULATE_DATA_SUCCESS'; +export const POPULATE_DATA_ERROR = 'POPULATE_DATA_ERROR'; + +/** + * Pending action for + * populating the data + */ +function populateDataPending() { + return { + type: POPULATE_DATA_PENDING + }; +} + +/** + * Success action for + * populating the data + */ +function populateDataSuccess(data) { + return { + type: POPULATE_DATA_SUCCESS, + data, + }; +} + +/** + * Error action for + * populating the data + */ +function populateDataError(error) { + return { + type: POPULATE_DATA_ERROR, + error, + }; +} + +/** + * Exposed action creator + * for populating the data of our module + */ +export function populateData(options) { + return (dispatch) => { + /** + * Dispatch the pending action for + * retrieving the data + */ + dispatch(populateDataPending()); + + /** + * Initiate the request for retrieving + * the data for our given input options + */ + getData(options) + .then((result) => { + /** + * Dispatch the success action of retrieving + * the data, with the data as payload + */ + dispatch(populateDataSuccess(result)); + }) + .catch((err) => { + /** + * Dispatch the error action of retrieving + * the data when something fails, + * with the error as payload + */ + dispatch(populateDataError(err)); + }); + }; +} diff --git a/src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx b/src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx new file mode 100644 index 0000000..4b7c048 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/ErrorMessage.jsx @@ -0,0 +1,23 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const ErrorMessage = React.createClass({ + propTypes: { + error: React.PropTypes.object.isRequired + }, + + render() { + const { error } = this.props; + + return ( +
Error: {JSON.stringify(error)}
+ ); + } +}); + +module.exports = ErrorMessage; diff --git a/src/modules/DemoModuleWithRedux/components/Loading.jsx b/src/modules/DemoModuleWithRedux/components/Loading.jsx new file mode 100644 index 0000000..6ee8149 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/Loading.jsx @@ -0,0 +1,17 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Component + */ +const Loading = React.createClass({ + render() { + return ( +
Loading...
+ ); + } +}); + +module.exports = Loading; diff --git a/src/modules/DemoModuleWithRedux/components/Main.jsx b/src/modules/DemoModuleWithRedux/components/Main.jsx new file mode 100644 index 0000000..992b136 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/Main.jsx @@ -0,0 +1,115 @@ +/** + * Dependencies + */ +const React = require('react'); +const { connect } = require('react-redux'); +const { bindActionCreators } = require('redux'); + +/** + * Actions + */ +const { populateData } = require('../actions'); + +/** + * Sub-components + */ +const Loading = require('./Loading.jsx'); + +/** + * Component + */ +const Main = React.createClass({ + propTypes: { + isLoading: React.PropTypes.bool.isRequired, + error: React.PropTypes.object.isRequired, + data: React.PropTypes.object.isRequired, + actions: React.PropTypes.shape({ + populateData: React.PropTypes.func.isRequired, + }), + _dataOptions: React.PropTypes.object + }, + + getDefaultProps() { + return { + _dataOptions: {} + }; + }, + + componentDidMount() { + const { + actions: { populateData }, + _dataOptions + } = this.props; + + populateData(_dataOptions); + }, + + render() { + /** + * Handle data loading if you want + * a loading spinner and not "dead" data + */ + const { + isLoading, + data, + error + } = this.props; + + /** + * Show error message + * if there's an error + */ + if (error) { + return ( + + ); + } + + /** + * Until we have data + * show a loading indicator + */ + if (isLoading) { + return ( + + ); + } + + /** + * The actual stuff you want to show + */ + return ( +
+ {JSON.stringify(this.props)} +
+ ); + } +}); + +/** + * A function to map from + * redux state to React props + */ +function mapStateToProps(state) { + return state; +} + +/** + * A function to map from + * redux actions to React props + */ +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + populateData, + }, dispatch) + }; +} + +/** + * Export a redux-"connected" version of the component + */ +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(Main); diff --git a/src/modules/DemoModuleWithRedux/components/readme.md b/src/modules/DemoModuleWithRedux/components/readme.md new file mode 100644 index 0000000..f6cdc2c --- /dev/null +++ b/src/modules/DemoModuleWithRedux/components/readme.md @@ -0,0 +1,4 @@ +Module Components +================= + +Here you put all your React components for the module. diff --git a/src/modules/DemoModuleWithRedux/injects/readme.md b/src/modules/DemoModuleWithRedux/injects/readme.md new file mode 100644 index 0000000..9b3791b --- /dev/null +++ b/src/modules/DemoModuleWithRedux/injects/readme.md @@ -0,0 +1,8 @@ +Module Injects +============== + +This module contains globals that should be injected (mostly used for global script dependencies) + +__The pre-existing ones are__: + +- `scripts` (used to inject script tags) diff --git a/src/modules/DemoModuleWithRedux/injects/scripts.js b/src/modules/DemoModuleWithRedux/injects/scripts.js new file mode 100644 index 0000000..edbc273 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/injects/scripts.js @@ -0,0 +1,28 @@ +/** + * Examples of script injections + * + * Just paste all your snippets here + */ +module.exports = function(context) { + (function(w, d, s, id) { + /** + * Do config and add stuff to + * the global (window) scope + * here + */ + w.myConfig_REPLACE_ME = { + option: 'hello' + }; + + /** + * The part that injects + * the script with a url + * (only change the REPLACE_ME parts) + */ + var js, fjs = d.getElementsByTagName(s)[0]; + if (d.getElementById(id)) return; + js = d.createElement(s); js.id = id; + js.src = "//url-to-script-here_REPLACE_ME.js"; + fjs.parentNode.insertBefore(js, fjs); + }(window, document, 'script', 'unique-id-for-script-here_REPLACE_ME')); +}; diff --git a/src/modules/DemoModuleWithRedux/readme.md b/src/modules/DemoModuleWithRedux/readme.md new file mode 100644 index 0000000..fcb3f8c --- /dev/null +++ b/src/modules/DemoModuleWithRedux/readme.md @@ -0,0 +1,58 @@ +Aurora Module Template +====================== + +A module template for building Aurora modules. + +__NOTE:__ +This readme is for building modules inside of Aurora Frontend +When you want to outsource a module to npm see [aurora-module-template](https://github.com/soldotno/aurora-module-template) + +#### Include it in your module-map and module-includes to enable use + +```js +// module-map.js +const moduleMap = { + 'my-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./MyModule/MyModule.jsx')); + }); + }, + ... +}; + +// module-includes.js +module.exports = [ + 'my-module', + ... +]; +``` + +#### Document how the JSON-config looks for this module + +```js +{ + type: 'my-module', + visible: ['mobile', 'tablet', 'desktop'], + options: { + a: '...', + b: '....', + c: '.....' + } +} +``` + +#### Document the module interface + +```js + +``` + +#### Document the different options and how it works + +- `a` - this options does.. +- `b` - this options does.. +- `c` - this options does.. diff --git a/src/modules/DemoModuleWithRedux/reducers/index.js b/src/modules/DemoModuleWithRedux/reducers/index.js new file mode 100644 index 0000000..0a0585c --- /dev/null +++ b/src/modules/DemoModuleWithRedux/reducers/index.js @@ -0,0 +1,37 @@ +/** + * Import all the constants + * we are going to use + */ +import { + POPULATE_DATA_PENDING, + POPULATE_DATA_SUCCESS, + POPULATE_DATA_ERROR +} from '../actions'; + +/** + * Create and export our state reducer + */ +export default function rootReducer(state, action) { + switch (action.type) { + case POPULATE_DATA_PENDING: + return Object.assign({}, state, { + isLoading: true, + error: null, + }); + + case POPULATE_DATA_SUCCESS: + return Object.assign({}, state, { + isLoading: false, + data: action.data, + error: null, + }); + + case POPULATE_DATA_ERROR: + return Object.assign({}, state, { + error: action.error + }); + + default: + return state; + } +} diff --git a/src/modules/DemoModuleWithRedux/services/get-data.js b/src/modules/DemoModuleWithRedux/services/get-data.js new file mode 100644 index 0000000..7b2fb86 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/services/get-data.js @@ -0,0 +1,13 @@ +/** + * Dependencies + */ +const request = require('axios'); + +/** + * Export a function that returns + * a Promise of the data needed for + * the module. + */ +module.exports = function(options) { + return Promise.resolve(JSON.parse(JSON.stringify(options))); +}; diff --git a/src/modules/DemoModuleWithRedux/services/readme.md b/src/modules/DemoModuleWithRedux/services/readme.md new file mode 100644 index 0000000..362759e --- /dev/null +++ b/src/modules/DemoModuleWithRedux/services/readme.md @@ -0,0 +1,8 @@ +Module Services +=============== + +Here you'll put all the interfaces that communicates with services and expose them as simple functions + +__Example__: + +- Communicating with API's diff --git a/src/modules/DemoModuleWithRedux/styles/my-module-styles.scss b/src/modules/DemoModuleWithRedux/styles/my-module-styles.scss new file mode 100644 index 0000000..c62c527 --- /dev/null +++ b/src/modules/DemoModuleWithRedux/styles/my-module-styles.scss @@ -0,0 +1,23 @@ +.awesome { + background-color: whitesmoke; + + &.one { + color: yellow + } + + &.two { + color: red + } + + &.three { + color: blue + } + + &.four { + color: hotpink + } + + &.five { + color: cyan + } +} diff --git a/src/modules/DemoModuleWithRedux/styles/readme.md b/src/modules/DemoModuleWithRedux/styles/readme.md new file mode 100644 index 0000000..cfcf0ad --- /dev/null +++ b/src/modules/DemoModuleWithRedux/styles/readme.md @@ -0,0 +1,4 @@ +Module Styles +============= + +This folder should contain your styles (sass). diff --git a/src/modules/DoesNotExist/DoesNotExist.jsx b/src/modules/DoesNotExist/DoesNotExist.jsx new file mode 100644 index 0000000..71ef045 --- /dev/null +++ b/src/modules/DoesNotExist/DoesNotExist.jsx @@ -0,0 +1,15 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Default empty component for graceful failing + */ +const DoesNotExist = React.createClass({ + render() { + return null; + } +}); + +module.exports = DoesNotExist; diff --git a/src/modules/get-module.js b/src/modules/get-module.js new file mode 100644 index 0000000..bece3f7 --- /dev/null +++ b/src/modules/get-module.js @@ -0,0 +1,98 @@ +/** + * Dependencies + */ +const debug = require('debug')('aurora-frontend:get-module'); + +/** + * Shim require.ensure in node.js + * (check the webpack docs - async chunking) + */ +if (typeof require.ensure !== 'function') { + require.ensure = require('isomorphic-ensure')({ + dirname: __dirname, + }); +} + +/** + * The map of modules available to resolve to + */ +const moduleMap = { + 'demo-app': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoAppModule/DemoAppModule.jsx')); + }); + }, + 'demo-hom': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoHigherOrderModule/DemoHigherOrderModule.jsx')); + }); + }, + 'demo-module': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModule/DemoModule.jsx')); + }); + }, + 'demo-module-with-data': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithData/DemoModuleWithData.jsx')); + }); + }, + 'demo-module-with-geolocation': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithGeolocation/DemoModuleWithGeolocation.jsx')); + }); + }, + 'demo-module-with-personalization': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithPersonalization/DemoModuleWithPersonalization.jsx')); + }); + }, + 'demo-module-with-randomization': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithRandomization/DemoModuleWithRandomization.jsx')); + }); + }, + 'demo-module-with-redux': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DemoModuleWithRedux/DemoModuleWithRedux.jsx')); + }); + }, + 'does-not-exist': (callback) => { + require.ensure([], (require) => { + callback(null, require('./DoesNotExist/DoesNotExist.jsx')); + }); + }, +}; + +/** + * Export a function that can resolve + * a module from a string representation + * to a React component / Aurora module + * (asynchronously) + */ +module.exports = (type) => { + /** + * Return a Promise of the React component + * resolved from the string type definition + */ + return new Promise((resolve, reject) => { + setImmediate(() => { + if (!moduleMap[type]) { + debug(`You are trying to render a module with type *${type}* which does not exist`); + } + + /** + * Pull out the specified component getter function + * (or default gracefully) + */ + const getModule = moduleMap[type] || moduleMap['does-not-exist']; + + /** + * Fetch the specified component and the promise to it + */ + getModule((err, module) => { + return err ? reject(err) : resolve(module); + }); + }); + }); +}; diff --git a/src/server-app.js b/src/server-app.js new file mode 100644 index 0000000..a3e59c6 --- /dev/null +++ b/src/server-app.js @@ -0,0 +1,75 @@ +/** + * Dependencies + */ +const debug = require('debug')('aurora-frontend:app'); // eslint-disable-line no-unused-vars +const express = require('express'); +const compression = require('compression'); +const cookieParser = require('cookie-parser'); +const favicon = require('serve-favicon'); +const useragent = require('express-useragent'); +const ms = require('ms'); + +/** + * Environment + */ +const { port } = require('../config'); + +/** + * Import the server-rendering function + * from aurora core and create an instance + */ +/* eslint-disable global-require */ +const renderServer = require('aurora-core/dist/render/server')({ + cacheHTML: { + get: () => Promise.reject(new Error('Cache miss')), + set: function set() {}, + }, + createHTML: require('./utils/create-html'), + getRoute: require('./services/get-route-config'), + getUserSettings: require('./utils/get-user-settings'), + getPaginationSettings: require('./utils/get-pagination-settings'), + getModule: require('./modules/get-module'), + isVisible: require('./utils/is-visible'), +}); +/* eslint-enable global-require */ + +/** + * Create express app + */ +const app = express(); + +/** + * Use gzip compression + */ +app.use(compression()); + +/** + * Add useragent parsing + */ +app.use(useragent.express()); + +/** + * Parse incoming cookies + */ +app.use(cookieParser()); + +/** + * Serve favicon + */ +app.use(favicon(`${__dirname}/../public/static/favicon.ico`)); + +/** + * Serve static files + */ +app.use(express.static(`${__dirname}/../public`, { maxAge: ms('7 days') })); + +/** + * Use our server rendering function + * as the main route handler + */ +app.use('/', renderServer); + +/** + * Start server + */ +app.listen(port, () => console.log(`Listening on port ${port}`)); // eslint-disable-line no-console diff --git a/src/server-bootstrap.js b/src/server-bootstrap.js new file mode 100644 index 0000000..5432af5 --- /dev/null +++ b/src/server-bootstrap.js @@ -0,0 +1,38 @@ +/** + * Include babel polyfill + */ +require('babel-polyfill'); + +const { nodeEnv } = require('../config'); + +/** + * Add a global var to conform + * to webpack config + */ +global.__DEVELOPMENT__ = nodeEnv !== 'production'; // eslint-disable-line no-underscore-dangle + +/** + * Run all requires in our server-side + * app through babel, so we can use + * ES2015 everywhere + */ +require('babel-core/register')({ + sourceMap: !__DEVELOPMENT__, // eslint-disable-line no-underscore-dangle, no-undef +}); + +/** + * Use bluebird for as promise library + */ +global.Promise = require('bluebird'); + +/** + * Handle unhandled promise rejections + */ +process.on('unhandledRejection', (error) => { + throw error; +}); + +/** + * Run the actual app + */ +require('./server-app.js'); diff --git a/src/services/dummy-router.js b/src/services/dummy-router.js new file mode 100644 index 0000000..2ef3343 --- /dev/null +++ b/src/services/dummy-router.js @@ -0,0 +1,245 @@ +const { slice, merge } = require('aurora-deep-slice-merge'); +const clone = require('stringify-clone'); +const deepEqual = require('deep-equal'); + +const demo = { + pageInfo: { + title: 'Demo Aurora config', + }, + app: { + type: 'demo-app', + options: { + modules: [{ + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-data', + visibility: ['small'], + options: { + _dataOptions: { + name: 'A', + age: 1 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'B', + age: 2 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['large'], + options: { + _dataOptions: { + name: 'C', + age: 3 + } + } + }, { + type: 'demo-module-with-data', + options: { + _dataOptions: { + name: 'D', + age: 4 + } + } + }, { + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-data', + visibility: ['small'], + options: { + _dataOptions: { + name: 'E', + age: 5 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'F', + age: 6 + } + } + }, { + type: 'demo-module-with-data', + visibility: ['large'], + options: { + _dataOptions: { + name: 'G', + age: 7 + } + } + }, { + type: 'demo-module-with-data', + options: { + _dataOptions: { + name: 'H', + age: 8 + } + } + }] + } + }] + } + }] + } + } +}; + +const redux = { + pageInfo: { + title: 'Demo Aurora config', + }, + app: { + type: 'demo-app', + options: { + modules: [{ + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-redux', + visibility: ['small'], + options: { + _dataOptions: { + name: 'AA-redux', + age: 11 + } + } + }, { + type: 'demo-module-with-redux', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'BB-redux', + age: 22 + } + } + }, { + type: 'demo-module-with-redux', + visibility: ['large'], + options: { + _dataOptions: { + name: 'CC-redux', + age: 33 + } + } + }, { + type: 'demo-module-with-redux', + options: { + _dataOptions: { + name: 'DD-redux', + age: 44 + } + } + }] + } + }] + } + } +}; + +const geo = { + pageInfo: { + title: 'Demo Aurora config', + }, + app: { + type: 'demo-app', + options: { + modules: [{ + type: 'demo-hom', + options: { + modules: [{ + type: 'demo-module-with-geolocation', + visibility: ['small'], + options: { + _dataOptions: { + name: 'EE-geo', + age: 55 + } + } + }, { + type: 'demo-module-with-geolocation', + visibility: ['medium'], + options: { + _dataOptions: { + name: 'FF-geo', + age: 66 + } + } + }, { + type: 'demo-module-with-geolocation', + visibility: ['large'], + options: { + _dataOptions: { + name: 'GG-geo', + age: 77 + } + } + }, { + type: 'demo-module-with-geolocation', + options: { + _dataOptions: { + name: 'HH-geo', + age: 88 + } + } + }] + } + }] + } + } +}; + +const routes = { + '/demo': demo, + '/demo/': demo, + '/redux': redux, + '/redux/': redux, + '/geo': geo, + '/geo/': geo, +}; + +module.exports = function({ + path, + query, + skip, + limit, + page, + version, + settings, +}) { + if (!routes[path]) { + return Promise.reject(new Error('Route requested not found')); + } + + let config = slice(routes[path], skip, limit); + + let hasMore = !deepEqual( + slice(routes[path], skip, limit), + slice(routes[path], skip, limit + 1) + ); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + meta: { + pagination: { + hasMore + }, + version: 123 + }, + data: { + config + } + }); + }, 1000); + }); +}; diff --git a/src/services/get-route-config.js b/src/services/get-route-config.js new file mode 100644 index 0000000..1d8936e --- /dev/null +++ b/src/services/get-route-config.js @@ -0,0 +1,71 @@ +/** + * Dependencies + */ +const isBrowser = require('is-client')(); +const axios = require('axios'); +const qs = require('qs'); +const urlJoin = require('url-join'); +const debug = require('debug')('aurora-frontend:get-page-config'); + +/** + * Environment + */ +const apiUrl = process.env.API_URL; + +/** + * Create request module + * that stringifies nested + * objects as query strings + * that the qs module can + * parse into valid objects + * on the other end + */ +const request = axios.create({ + paramsSerializer(params) { + return qs.stringify(params); + }, +}); + +/** + * Export a function that fetches + * a page config based on input parameters + */ +module.exports = function getRouteConfig({ + path = '/', + query = {}, + page = 0, + skip = 0, + limit = Infinity, + version = '', + settings = {}, +}) { + /** + * Set up the query we want to + * supply to the API endpoint + */ + const apiQuery = { + params: { + options: { ...query }, + page, + skip, + limit, + version, + settings, + }, + }; + + /** + * Create a closure for returning + * the requested path from the API + */ + const requestFromApi = () => { + return request + .get(urlJoin(apiUrl, 'routes', path), apiQuery) + .then(response => response.data); + }; + + /** + * Use either the cache or the API directly + */ + return requestFromApi(); +}; diff --git a/src/shared-components/VersionAlert.jsx b/src/shared-components/VersionAlert.jsx new file mode 100644 index 0000000..68bcc76 --- /dev/null +++ b/src/shared-components/VersionAlert.jsx @@ -0,0 +1,78 @@ +/** + * Dependencies + */ +const React = require('react'); + +/** + * Aurora module React component + */ +const VersionAlert = React.createClass({ + /** + * Set some initial state + */ + getInitialState() { + return { + loaded: false, + href: null, + }; + }, + + /** + * Initialize on mount + */ + componentDidMount() { + this.flagLoaded(); + }, + + flagLoaded() { + this.setState({ + loaded: true, + href: window.location.origin + (window.location.pathname || ''), + }); + }, + + /** + * Render the children (which in our case will be the App) + */ + render() { + /** + * Inline styles + */ + const styles = { + position: 'fixed', + top: 0, + left: 0, + width: '100%', + padding: '1em', + background: '#fffea1', + border: '1px solid #fc0', + textAlign: 'center', + }; + + const { loaded, href } = this.state; + + /** + * Only render if loaded on client + */ + if (!loaded) { + return null; + } + + /** + * Render a fixed header telling + * you that a newer version of + * this site is available + */ + return ( +
+

En ny versjon av denne siden er tilgjengelig

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