diff --git a/packages/unleash-frontend-next/.babelrc b/packages/unleash-frontend-next/.babelrc new file mode 100644 index 0000000000..4d3b7d3676 --- /dev/null +++ b/packages/unleash-frontend-next/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["react", "es2015", "stage-2"], + "env": { + "development": { + "presets": ["react-hmre"] + } + } +} diff --git a/packages/unleash-frontend-next/.eslintrc b/packages/unleash-frontend-next/.eslintrc new file mode 100644 index 0000000000..8f68bb4a87 --- /dev/null +++ b/packages/unleash-frontend-next/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "finn", + "finn/node" + ] +} diff --git a/packages/unleash-frontend-next/.gitignore b/packages/unleash-frontend-next/.gitignore new file mode 100644 index 0000000000..1521c8b765 --- /dev/null +++ b/packages/unleash-frontend-next/.gitignore @@ -0,0 +1 @@ +dist diff --git a/packages/unleash-frontend-next/index.html b/packages/unleash-frontend-next/index.html new file mode 100644 index 0000000000..3ec0888efe --- /dev/null +++ b/packages/unleash-frontend-next/index.html @@ -0,0 +1,13 @@ + + + + Unleash UI + + + + + +
+ + + diff --git a/packages/unleash-frontend-next/index.js b/packages/unleash-frontend-next/index.js new file mode 100644 index 0000000000..a801f347b6 --- /dev/null +++ b/packages/unleash-frontend-next/index.js @@ -0,0 +1,6 @@ +'use strict'; +const path = require('path'); + +module.exports = { + publicFolder: path.join(__dirname, 'dist'), +}; diff --git a/packages/unleash-frontend-next/jest-preprocessor.js b/packages/unleash-frontend-next/jest-preprocessor.js new file mode 100644 index 0000000000..66c8d10d00 --- /dev/null +++ b/packages/unleash-frontend-next/jest-preprocessor.js @@ -0,0 +1,9 @@ +// preprocessor.js +'use strict'; + +const ReactTools = require('react-tools'); +module.exports = { + process (src) { + return ReactTools.transform(src); + }, +}; diff --git a/packages/unleash-frontend-next/package.json b/packages/unleash-frontend-next/package.json new file mode 100644 index 0000000000..a50e697aab --- /dev/null +++ b/packages/unleash-frontend-next/package.json @@ -0,0 +1,78 @@ +{ + "name": "unleash-frontend-next", + "description": "unleash your features", + "version": "1.0.0", + "keywords": [ + "unleash", + "feature toggle", + "feature", + "toggle" + ], + "files": [ + "public" + ], + "repository": { + "type": "git", + "url": "ssh://git@github.com:finn-no/unleash.git" + }, + "bugs": { + "url": "https://github.com/finn-no/unleash/issues" + }, + "engines": { + "node": "6" + }, + "scripts": { + "build": "webpack -p", + "start": "webpack-dev-server --config webpack.config.js --hot --progress --colors --port 3000", + "test": "echo 'no test'", + "test:ci": "npm run test", + "prepublish": "npm run build" + }, + "main": "./index.js", + "dependencies": { + "immutability-helper": "^2.0.0", + "react": "^15.3.1", + "react-addons-css-transition-group": "^15.3.1", + "react-dom": "^15.3.1", + "react-redux": "^4.4.5", + "react-router": "^2.8.0", + "react-toolbox": "^1.2.1", + "redux": "^3.6.0" + }, + "devDependencies": { + "babel-core": "^6.14.0", + "babel-loader": "^6.2.5", + "babel-preset-es2015": "^6.14.0", + "babel-preset-react": "^6.11.1", + "babel-preset-react-hmre": "^1.1.1", + "babel-preset-stage-0": "^6.5.0", + "babel-preset-stage-2": "^6.13.0", + "css-loader": "^0.25.0", + "extract-text-webpack-plugin": "^1.0.1", + "node-sass": "~3.7.0", + "postcss-loader": "^0.13.0", + "redux-devtools": "^3.3.1", + "sass-loader": "^4.0.2", + "style-loader": "^0.13.1", + "toolbox-loader": "0.0.3", + "webpack": "^1.13.2", + "webpack-dev-server": "^1.15.1" + }, + "jest": { + "scriptPreprocessor": "/jest-preprocessor.js", + "modulePathIgnorePatterns": [ + "/node_modules/npm" + ], + "unmockedModulePathPatterns": [ + "/node_modules/react", + "/node_modules/reflux" + ], + "moduleFileExtensions": [ + "jsx", + "js" + ] + }, + "pre-commit": [ + "lint" + ] +} diff --git a/packages/unleash-frontend-next/src/.eslintrc b/packages/unleash-frontend-next/src/.eslintrc new file mode 100644 index 0000000000..b2d0257498 --- /dev/null +++ b/packages/unleash-frontend-next/src/.eslintrc @@ -0,0 +1,24 @@ +{ + "extends": [ + "finn", + "finn-react" + ], + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "rules": { + "react/sort-comp": "off" + }, + "ecmaFeatures": { + "sourceType": "module" + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "experimentalObjectRestSpread": true + } + } +} diff --git a/packages/unleash-frontend-next/src/App.jsx b/packages/unleash-frontend-next/src/App.jsx new file mode 100644 index 0000000000..0457573a27 --- /dev/null +++ b/packages/unleash-frontend-next/src/App.jsx @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; +import { Layout, Panel, NavDrawer, AppBar, IconButton } from 'react-toolbox'; +import style from './style'; + +import Navigation from './Navigation'; + +export default class App extends Component { + constructor (props) { + super(props); + this.state = { drawerActive: false }; + + this.toggleDrawerActive = () => { + this.setState({ drawerActive: !this.state.drawerActive }); + }; + } + + render () { + return ( +
+ + + + + + + + +
+ {this.props.children} +
+ +
+
+
+ ); + } +}; diff --git a/packages/unleash-frontend-next/src/Navigation.jsx b/packages/unleash-frontend-next/src/Navigation.jsx new file mode 100644 index 0000000000..0d1dd4198b --- /dev/null +++ b/packages/unleash-frontend-next/src/Navigation.jsx @@ -0,0 +1,25 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router'; +import { ListSubHeader, List, ListItem, ListDivider } from 'react-toolbox'; + +export default class UnleashNav extends Component { + render () { + return ( + + + + + + + + + + + +

A product by FINN.no

+
+
+ + ); + } +}; diff --git a/packages/unleash-frontend-next/src/action/index.js b/packages/unleash-frontend-next/src/action/index.js new file mode 100644 index 0000000000..69e53f0e88 --- /dev/null +++ b/packages/unleash-frontend-next/src/action/index.js @@ -0,0 +1,11 @@ +let nextFeatureId = 0; +export const addFeatureToggle = (featureName) => ({ + type: 'ADD_FEATURE_TOGGLE', + id: nextFeatureId++, + featureName, +}); + +export const toggleFeature = (id) => ({ + type: 'TOGGLE_FEATURE_TOGGLE', + id, +}); diff --git a/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx b/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx new file mode 100644 index 0000000000..1f569edf2c --- /dev/null +++ b/packages/unleash-frontend-next/src/component/feature/AddFeatureToggle.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { addFeatureToggle } from '../../action'; + + +let AddFeatureToggle = ({ dispatch }) => { + let input; + + return ( +
+
{ + e.preventDefault(); + if (!input.value.trim()) { + return; + } + dispatch(addFeatureToggle(input.value)); + input.value = ''; + }}> + { + input = node; + }} /> + +
+
+ ); +}; +AddFeatureToggle = connect()(AddFeatureToggle); + +export default AddFeatureToggle; diff --git a/packages/unleash-frontend-next/src/component/feature/Feature.jsx b/packages/unleash-frontend-next/src/component/feature/Feature.jsx new file mode 100644 index 0000000000..0b41e9c9f0 --- /dev/null +++ b/packages/unleash-frontend-next/src/component/feature/Feature.jsx @@ -0,0 +1,16 @@ +import React, { PropTypes } from 'react'; + +const Feature = ({ onClick, featureName, enabled }) => ( +
  • + {featureName} is {enabled ? 'enabled ' : 'disabled '} + toggle +
  • +); + +Feature.propTypes = { + onClick: PropTypes.func.isRequired, + enabled: PropTypes.bool.isRequired, + featureName: PropTypes.string.isRequired, +}; + +export default Feature; diff --git a/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx b/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx new file mode 100644 index 0000000000..55347eaab3 --- /dev/null +++ b/packages/unleash-frontend-next/src/component/feature/FeatureList.jsx @@ -0,0 +1,21 @@ +import React, { PropTypes } from 'react'; +import Feature from './Feature'; + +const FeatureList = ({ features, onFeatureClick }) => ( +
      + {features.map(featureToggle => + onFeatureClick(featureToggle.id)} + /> + )} +
    +); + +FeatureList.propTypes = { + onFeatureClick: PropTypes.func.isRequired, + features: PropTypes.array.isRequired, +}; + +export default FeatureList; diff --git a/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx b/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx new file mode 100644 index 0000000000..416fa3a5fa --- /dev/null +++ b/packages/unleash-frontend-next/src/component/feature/FeatureListContainer.jsx @@ -0,0 +1,21 @@ +import { connect } from 'react-redux'; +import { toggleFeature } from '../../action'; +import FeatureList from './FeatureList'; + +const mapStateToProps = (state) => ({ + features: state.features, +}); + +const mapDispatchToProps = (dispatch) => ({ + onFeatureClick: (id) => { + dispatch(toggleFeature(id)); + }, +}); + + +const FeatureListContainer = connect( + mapStateToProps, + mapDispatchToProps +)(FeatureList); + +export default FeatureListContainer; diff --git a/packages/unleash-frontend-next/src/index.jsx b/packages/unleash-frontend-next/src/index.jsx new file mode 100644 index 0000000000..c2897ae9e6 --- /dev/null +++ b/packages/unleash-frontend-next/src/index.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Router, Route, IndexRoute, hashHistory } from 'react-router'; +import { Provider } from 'react-redux'; +import { createStore } from 'redux'; + +import store from './store'; +import App from './App'; + +import Features from './page/features'; +import Strategies from './page/strategies'; +import Logs from './page/logs'; +import Archive from './page/archive'; + +const unleashStore = createStore(store); + +ReactDOM.render( + + + + + + + + + + , document.getElementById('app')); diff --git a/packages/unleash-frontend-next/src/page/archive/index.js b/packages/unleash-frontend-next/src/page/archive/index.js new file mode 100644 index 0000000000..7abb59def9 --- /dev/null +++ b/packages/unleash-frontend-next/src/page/archive/index.js @@ -0,0 +1,28 @@ +import React, { Component } from 'react'; +import { Card, CardTitle, CardText } from 'react-toolbox'; + + +export default class Archive extends Component { + render () { + return ( + + Archived Feture Toggles + +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pharetra finibus + ullamcorper. Proin laoreet faucibus venenatis. Aenean quis leo finibus, maximus + nisi finibus, fringilla ex. Nam mollis congue orci eu consectetur. Aliquam a + massa quis tortor vestibulum lacinia. Phasellus nisi velit, mattis vel nulla + a, rhoncus porta dui. Vestibulum viverra augue in pellentesque tincidunt. + Aliquam rhoncus nunc ipsum, sed vehicula leo dictum in. Phasellus accumsan + elis sem, in ullamcorper nisi accumsan vitae. Nullam vitae consectetur mi, + sed vulputate augue. In quis augue tellus. Duis convallis cursus elit, in + interdum nisl pulvinar viverra. Sed vel ornare sapien, eu consectetur metus. + Vivamus at porta nisl. Nullam in aliquam nisl. +

    +
    +
    + + ); + } +}; diff --git a/packages/unleash-frontend-next/src/page/features/index.js b/packages/unleash-frontend-next/src/page/features/index.js new file mode 100644 index 0000000000..812e601d9b --- /dev/null +++ b/packages/unleash-frontend-next/src/page/features/index.js @@ -0,0 +1,17 @@ +import React, { Component } from 'react'; +import FeatureListContainer from '../../component/feature/FeatureListContainer'; +import AddFeatureToggle from '../../component/feature/AddFeatureToggle'; + +export default class Features extends Component { + render () { + return ( +
    +

    Feature Toggles

    + + + +
    + + ); + } +}; diff --git a/packages/unleash-frontend-next/src/page/strategies/index.js b/packages/unleash-frontend-next/src/page/strategies/index.js new file mode 100644 index 0000000000..7c493453fa --- /dev/null +++ b/packages/unleash-frontend-next/src/page/strategies/index.js @@ -0,0 +1,11 @@ +import React, { Component } from 'react'; + +export default class Strategies extends Component { + render () { + return ( +
    +

    Strategies

    +
    + ); + } +}; diff --git a/packages/unleash-frontend-next/src/store/features.js b/packages/unleash-frontend-next/src/store/features.js new file mode 100644 index 0000000000..e4a838ea43 --- /dev/null +++ b/packages/unleash-frontend-next/src/store/features.js @@ -0,0 +1,39 @@ +const feature = (state = {}, action) => { + switch (action.type) { + case 'ADD_FEATURE_TOGGLE': + return { + id: action.id, + featureName: action.featureName, + enabled: false, + }; + case 'TOGGLE_FEATURE_TOGGLE': + if (state.id !== action.id) { + return state; + } + + return Object.assign({}, state, { + enabled: !state.enabled, + }); + + default: + return state; + } +}; + +const features = (state = [{ id: 1, featureName: 'test', enabled: true }], action) => { + switch (action.type) { + case 'ADD_FEATURE_TOGGLE': + return [ + ...state, + feature(undefined, action), + ]; + case 'TOGGLE_FEATURE_TOGGLE': + return state.map(t => + feature(t, action) + ); + default: + return state; + } +}; + +export default features; diff --git a/packages/unleash-frontend-next/src/store/index.js b/packages/unleash-frontend-next/src/store/index.js new file mode 100644 index 0000000000..bebe5c7167 --- /dev/null +++ b/packages/unleash-frontend-next/src/store/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import features from './features'; + +const unleashStore = combineReducers({ + features, +}); + +export default unleashStore; diff --git a/packages/unleash-frontend-next/src/style.scss b/packages/unleash-frontend-next/src/style.scss new file mode 100644 index 0000000000..a2a9af46ad --- /dev/null +++ b/packages/unleash-frontend-next/src/style.scss @@ -0,0 +1,8 @@ +.container { + height: auto; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} diff --git a/packages/unleash-frontend-next/src/theme/_config.scss b/packages/unleash-frontend-next/src/theme/_config.scss new file mode 100644 index 0000000000..2b301c05f3 --- /dev/null +++ b/packages/unleash-frontend-next/src/theme/_config.scss @@ -0,0 +1,22 @@ +@import "~react-toolbox/lib/colors"; +@import "~react-toolbox/lib/globals"; +@import "~react-toolbox/lib/mixins"; +@import "~react-toolbox/lib/commons"; + + +$color-primary:$palette-blue-400; +$color-primary-dark: $palette-blue-700; + +$appbar-height-m-portrait: 5.6 * $unit !default; +$appbar-height-m-landscape: 4.8 * $unit !default; + +$navigation-drawer-desktop-width: 3 * $standard-increment-desktop !default; +$navigation-drawer-max-desktop-width: 40 * $unit !default; + +// Mobile: +// Width = Screen width − 56 dp +// Maximum width: 320dp +$navigation-drawer-mobile-width: 5 * $standard-increment-mobile !default; + +// sass doesn't like use of variable here: calc(100% - $standard-increment-mobile); +$navigation-drawer-max-mobile-width: calc(100% - 5.6rem) !default; \ No newline at end of file diff --git a/packages/unleash-frontend-next/webpack.config.js b/packages/unleash-frontend-next/webpack.config.js new file mode 100644 index 0000000000..99165d864a --- /dev/null +++ b/packages/unleash-frontend-next/webpack.config.js @@ -0,0 +1,60 @@ +// docs: http://webpack.github.io/docs/configuration.html + +'use strict'; + +const path = require('path'); +const webpack = require('webpack'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +module.exports = { + entry: [ + 'webpack-dev-server/client?http://localhost:3000', + 'webpack/hot/only-dev-server', + './src/index', + ], + + resolve: { + root: [path.join(__dirname, 'src')], + extensions: ['', '.scss', '.css', '.js', '.jsx', '.json'], + modulesDirectories: ['web_modules', 'node_modules'], + }, + + output: { + path: path.join(__dirname, 'dist'), + filename: 'bundle.js', + publicPath: '/static/', + }, + + module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + loaders: ['babel'], + include: path.join(__dirname, 'src'), + }, + { + test: /(\.scss|\.css)$/, + loader: ExtractTextPlugin.extract('style', + 'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass'), + }, + ], + }, + + plugins: [ + new ExtractTextPlugin('bundle.css', { allChunks: true }), + new webpack.HotModuleReplacementPlugin(), + ], + + sassLoader: { + data: '@import "theme/_config.scss";', + includePaths: [path.resolve(__dirname, './src')], + }, + + devtool: 'source-map', + + externals: { + // stuff not in node_modules can be resolved here. + }, + +};