mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge pull request #170 from Unleash/feat/ui-config
feat: Customisable UI via config
This commit is contained in:
		
						commit
						24d593dce4
					
				| @ -6,6 +6,8 @@ before_install: yarn global add greenkeeper-lockfile@1 | ||||
| before_script: greenkeeper-lockfile-update | ||||
| script: yarn run test:ci | ||||
| after_script: greenkeeper-lockfile-upload | ||||
| cache: | ||||
|     yarn: true | ||||
| env: | ||||
|   global: | ||||
|     secure: F5bMAyZEYW7LBwV+L3sdW+jhA+4H9dKte70GlhtPQdjGuFM0bWb4DIyECOUj0HIVjLKq8lDx4jolbNDvjccecteGEgZEcHKCE/WRpOlB3R8WhQdsIk3j+FaMRQNI2aH6hmRVUysEf6RuTIcYcESIExP1eGcy79++Zon953uM26k6NYKvWjURkn8fLM/Bj6RXKZNBmzdCsvHNPSF6R+VC0rU3NdDBD+r89vA7cz9zkXCe5LYnVzcnzSrsBTExpZrc0PVDPxNBZrJdDSe9KsEWEO+Ag2kZAEmv2xedOCaFCO6PiniZgCBKkw2zQM4ittaP0NnK+QBtYOffhuxoGxqT953sz6UTtDJiYWy+68N57zECQr4TFTAL2F8Cjh2Z/KYO6jxlKGL/1kL2UO/1ovfjJeFfl+2tm/F1bYj7dYA+swS/72cDSH0nrrEuv6mAA5hWoLD3m55bqg9kwjYPO8skzlLNLJ2Q0k0kpd3c3zveNOF9cKZ87pFbShi5sWj3vWm8FMZudjday6MBjghGKGICI8NpQZFnETrthXWqUd5+PfxIqbEov5jZQAS0FrHc2wfIneRmBoP64ST9A5SA+9lA81qK7iw9eDi/e2/C7CsbgWtL4CTvk37HerCPTv2hDxZuUxgJ3p5QoOkdU+TWSvnY7Z+HnniRG0nMh90XLwQiBQU= | ||||
|  | ||||
| @ -9,6 +9,7 @@ The latest version of this document is always available in | ||||
| 
 | ||||
| ## [next] | ||||
| - fix: Use toggle/on/off endoints to ensure correct state | ||||
| - feat: Customisable UI via config | ||||
| 
 | ||||
| ## [3.2.1] | ||||
| - fix: Fixed bug in history view preventing toggle-view. | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|     <meta name="description" content="unleash"> | ||||
| 
 | ||||
|     <title>Unleash UI</title> | ||||
|     <title>Unleash - Enterprise ready feature toggles</title> | ||||
|     <link rel="stylesheet" href="public/bundle.css"> | ||||
|     <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | ||||
|     <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet"> | ||||
|  | ||||
| @ -5,23 +5,13 @@ exports[`renders correctly with details 1`] = ` | ||||
|   logo="Unleash 1.1.0" | ||||
|   type="bottom" | ||||
| > | ||||
|   <react-mdl-FooterLinkList> | ||||
|     <a | ||||
|       href="https://github.com/Unleash/unleash/" | ||||
|       target="_blank" | ||||
|     > | ||||
|       GitHub | ||||
|     </a> | ||||
|     <a | ||||
|       href="https://www.finn.no" | ||||
|       target="_blank" | ||||
|     > | ||||
|       <small> | ||||
|         A product by | ||||
|       </small> | ||||
|        FINN.no | ||||
|     </a> | ||||
|   </react-mdl-FooterLinkList> | ||||
|   <small> | ||||
|     (test) | ||||
|   </small> | ||||
|   <br /> | ||||
|   <small> | ||||
|     We are the best! | ||||
|   </small> | ||||
| </react-mdl-FooterSection> | ||||
| `; | ||||
| 
 | ||||
| @ -30,22 +20,25 @@ exports[`renders correctly with empty api details 1`] = ` | ||||
|   logo="Unleash " | ||||
|   type="bottom" | ||||
| > | ||||
|   <react-mdl-FooterLinkList> | ||||
|     <a | ||||
|       href="https://github.com/Unleash/unleash/" | ||||
|       target="_blank" | ||||
|     > | ||||
|       GitHub | ||||
|     </a> | ||||
|     <a | ||||
|       href="https://www.finn.no" | ||||
|       target="_blank" | ||||
|     > | ||||
|       <small> | ||||
|         A product by | ||||
|       </small> | ||||
|        FINN.no | ||||
|     </a> | ||||
|   </react-mdl-FooterLinkList> | ||||
|   <small> | ||||
|     (test) | ||||
|   </small> | ||||
|   <br /> | ||||
|   <small> | ||||
|     We are the best! | ||||
|   </small> | ||||
| </react-mdl-FooterSection> | ||||
| `; | ||||
| 
 | ||||
| exports[`renders correctly without uiConfig 1`] = ` | ||||
| <react-mdl-FooterSection | ||||
|   logo="Unleash 1.1.0" | ||||
|   type="bottom" | ||||
| > | ||||
|   <small> | ||||
|      | ||||
|   </small> | ||||
|   <br /> | ||||
|   <small /> | ||||
| </react-mdl-FooterSection> | ||||
| `; | ||||
|  | ||||
| @ -5,14 +5,28 @@ import renderer from 'react-test-renderer'; | ||||
| 
 | ||||
| jest.mock('react-mdl'); | ||||
| 
 | ||||
| const uiConfig = { | ||||
|     slogan: 'We are the best!', | ||||
|     environment: 'test', | ||||
| }; | ||||
| 
 | ||||
| test('renders correctly with empty api details', () => { | ||||
|     const tree = renderer.create(<ShowApiDetailsComponent fetchAll={jest.fn()} apiDetails={{}} />).toJSON(); | ||||
|     const tree = renderer | ||||
|         .create(<ShowApiDetailsComponent fetchAll={jest.fn()} apiDetails={{}} uiConfig={uiConfig} />) | ||||
|         .toJSON(); | ||||
|     expect(tree).toMatchSnapshot(); | ||||
| }); | ||||
| 
 | ||||
| test('renders correctly with details', () => { | ||||
|     const tree = renderer | ||||
|         .create(<ShowApiDetailsComponent fetchAll={jest.fn()} apiDetails={{ version: '1.1.0' }} />) | ||||
|         .create(<ShowApiDetailsComponent fetchAll={jest.fn()} apiDetails={{ version: '1.1.0' }} uiConfig={uiConfig} />) | ||||
|         .toJSON(); | ||||
|     expect(tree).toMatchSnapshot(); | ||||
| }); | ||||
| 
 | ||||
| test('renders correctly without uiConfig', () => { | ||||
|     const tree = renderer | ||||
|         .create(<ShowApiDetailsComponent fetchAll={jest.fn()} apiDetails={{ version: '1.1.0' }} uiConfig={{}} />) | ||||
|         .toJSON(); | ||||
|     expect(tree).toMatchSnapshot(); | ||||
| }); | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { FooterSection, FooterLinkList } from 'react-mdl'; | ||||
| import { FooterSection } from 'react-mdl'; | ||||
| 
 | ||||
| class ShowApiDetailsComponent extends Component { | ||||
|     static propTypes = { | ||||
|         apiDetails: PropTypes.object.isRequired, | ||||
|         uiConfig: PropTypes.object.isRequired, | ||||
|         fetchAll: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
| @ -14,16 +15,13 @@ class ShowApiDetailsComponent extends Component { | ||||
| 
 | ||||
|     render() { | ||||
|         const version = this.props.apiDetails.version || ''; | ||||
|         const { slogan, environment } = this.props.uiConfig; | ||||
| 
 | ||||
|         return ( | ||||
|             <FooterSection type="bottom" logo={`Unleash ${version}`}> | ||||
|                 <FooterLinkList> | ||||
|                     <a href="https://github.com/Unleash/unleash/" target="_blank"> | ||||
|                         GitHub | ||||
|                     </a> | ||||
|                     <a href="https://www.finn.no" target="_blank"> | ||||
|                         <small>A product by</small> FINN.no | ||||
|                     </a> | ||||
|                 </FooterLinkList> | ||||
|                 <small>{environment ? `(${environment})` : ''}</small> | ||||
|                 <br /> | ||||
|                 <small>{slogan}</small> | ||||
|             </FooterSection> | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -8,6 +8,7 @@ const mapDispatchToProps = { | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|     apiDetails: state.api.toJS(), | ||||
|     uiConfig: state.uiConfig.toJS(), | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(ShowApiDetailsComponent); | ||||
|  | ||||
| @ -1,50 +1,30 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import React, { PureComponent } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Layout, Header, Navigation, Content, Footer, Grid, Cell } from 'react-mdl'; | ||||
| import { Layout, Content, Footer, Grid, Cell } from 'react-mdl'; | ||||
| import { Route, Redirect, Switch } from 'react-router-dom'; | ||||
| import styles from './styles.scss'; | ||||
| import ErrorContainer from './error/error-container'; | ||||
| 
 | ||||
| import Header from './menu/header'; | ||||
| import AuthenticationContainer from './user/authentication-container'; | ||||
| import ShowUserContainer from './user/show-user-container'; | ||||
| 
 | ||||
| import ShowApiDetailsContainer from './api/show-api-details-container'; | ||||
| import Features from '../page/features'; | ||||
| import { DrawerMenu } from './menu/drawer'; | ||||
| 
 | ||||
| import { FooterMenu } from './menu/footer'; | ||||
| import Breadcrum from './menu/breadcrumb'; | ||||
| import { routes } from './menu/routes'; | ||||
| 
 | ||||
| export default class App extends Component { | ||||
| export default class App extends PureComponent { | ||||
|     static propTypes = { | ||||
|         location: PropTypes.object.isRequired, | ||||
|         match: PropTypes.object.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     componentWillReceiveProps(nextProps) { | ||||
|         if (this.props.location.pathname !== nextProps.location.pathname) { | ||||
|             clearTimeout(this.timer); | ||||
|             this.timer = setTimeout(() => { | ||||
|                 const layout = document.querySelector('.mdl-js-layout'); | ||||
|                 const drawer = document.querySelector('.mdl-layout__drawer'); | ||||
|                 // hack, might get a built in alternative later | ||||
|                 if (drawer.classList.contains('is-visible')) { | ||||
|                     layout.MaterialLayout.toggleDrawer(); | ||||
|                 } | ||||
|             }, 10); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         return ( | ||||
|             <div className={styles.container}> | ||||
|                 <AuthenticationContainer /> | ||||
|                 <Layout fixedHeader> | ||||
|                     <Header title={<Route path="/:path" component={Breadcrum} />}> | ||||
|                         <Navigation> | ||||
|                             <ShowUserContainer /> | ||||
|                         </Navigation> | ||||
|                     </Header> | ||||
|                     <DrawerMenu /> | ||||
|                     <Header location={this.props.location} /> | ||||
|                     <Content className="mdl-color--grey-50"> | ||||
|                         <Grid noSpacing className={styles.content}> | ||||
|                             <Cell col={12}> | ||||
|  | ||||
| @ -69,12 +69,14 @@ DataTableHeader.propTypes = { | ||||
| export const FormButtons = ({ submitText = 'Create', onCancel }) => ( | ||||
|     <div> | ||||
|         <Button type="submit" ripple raised primary icon="add"> | ||||
|             <Icon name="add" />    | ||||
|             <Icon name="add" /> | ||||
|                 | ||||
|             {submitText} | ||||
|         </Button> | ||||
|           | ||||
|         <Button type="cancel" ripple raised onClick={onCancel} style={{ float: 'right' }}> | ||||
|             <Icon name="cancel" />    Cancel | ||||
|             <Icon name="cancel" /> | ||||
|                 Cancel | ||||
|         </Button> | ||||
|     </div> | ||||
| ); | ||||
|  | ||||
| @ -13,7 +13,8 @@ class ViewFeatureComponent extends Component { | ||||
|                 <StrategiesSection configuredStrategies={configuredStrategies} /> | ||||
|                 <br /> | ||||
|                 <Button type="cancel" ripple raised onClick={onCancel} style={{ float: 'right' }}> | ||||
|                     <Icon name="cancel" />    Cancel | ||||
|                     <Icon name="cancel" /> | ||||
|                         Cancel | ||||
|                 </Button> | ||||
|             </section> | ||||
|         ); | ||||
|  | ||||
| @ -162,7 +162,9 @@ class StrategyConfigure extends React.Component { | ||||
|             item = ( | ||||
|                 <Card shadow={0} className={styles.card} style={{ opacity: isDragging ? '0.1' : '1' }}> | ||||
|                     <CardTitle className={styles.cardTitle}> | ||||
|                         <Icon name="extension" /> {name} | ||||
|                         <Icon name="extension" /> | ||||
|                           | ||||
|                         {name} | ||||
|                     </CardTitle> | ||||
|                     <CardText>{this.props.strategyDefinition.description}</CardText> | ||||
|                     {inputFields && ( | ||||
|  | ||||
| @ -106,7 +106,8 @@ class UpdateVariantComponent extends Component { | ||||
|                     supports variants. You should read more about variants in the  | ||||
|                     <a target="_blank" href="https://unleash.github.io/docs/beta_features"> | ||||
|                         user documentation | ||||
|                     </a>. | ||||
|                     </a> | ||||
|                     . | ||||
|                 </p> | ||||
|                 <p style={{ backgroundColor: 'rgba(255, 229, 100, 0.3)', padding: '5px' }}> | ||||
|                     The sum of variants weights needs to be a constant number to guarantee consistent hashing in the | ||||
|  | ||||
| @ -25,7 +25,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|   > | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/features" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -38,7 +38,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/strategies" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -51,7 +51,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/history" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -64,7 +64,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/archive" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -77,7 +77,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/applications" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -90,7 +90,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/logout" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -107,7 +107,7 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|     className="navigation" | ||||
|   > | ||||
|     <a | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="https://unleash.github.io" | ||||
|       target="_blank" | ||||
|     > | ||||
| @ -118,14 +118,14 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|        User documentation | ||||
|     </a> | ||||
|     <a | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="https://github.com/Unleash" | ||||
|       target="_blank" | ||||
|     > | ||||
|       <i | ||||
|         className="material-icons navigationIcon iconGitHub" | ||||
|       /> | ||||
|       GitHub | ||||
|        GitHub | ||||
|     </a> | ||||
|   </react-mdl-Navigation> | ||||
| </react-mdl-Drawer> | ||||
| @ -156,7 +156,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|   > | ||||
|     <a | ||||
|       aria-current="page" | ||||
|       className="navigationLink mdl-color-text--grey-600 navigationLink mdl-color-text--black mdl-color--light-blue-50" | ||||
|       className="navigationLink mdl-color-text--grey-900 navigationLink mdl-color-text--black mdl-color--blue-grey-100" | ||||
|       href="/features" | ||||
|       onClick={[Function]} | ||||
|       style={Object {}} | ||||
| @ -170,7 +170,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/strategies" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -183,7 +183,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/history" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -196,7 +196,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/archive" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -209,7 +209,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/applications" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -222,7 +222,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|     </a> | ||||
|     <a | ||||
|       aria-current={null} | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="/logout" | ||||
|       onClick={[Function]} | ||||
|     > | ||||
| @ -239,7 +239,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|     className="navigation" | ||||
|   > | ||||
|     <a | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="https://unleash.github.io" | ||||
|       target="_blank" | ||||
|     > | ||||
| @ -250,14 +250,14 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|        User documentation | ||||
|     </a> | ||||
|     <a | ||||
|       className="navigationLink mdl-color-text--grey-600" | ||||
|       className="navigationLink mdl-color-text--grey-900" | ||||
|       href="https://github.com/Unleash" | ||||
|       target="_blank" | ||||
|     > | ||||
|       <i | ||||
|         className="material-icons navigationIcon iconGitHub" | ||||
|       /> | ||||
|       GitHub | ||||
|        GitHub | ||||
|     </a> | ||||
|   </react-mdl-Navigation> | ||||
| </react-mdl-Drawer> | ||||
|  | ||||
| @ -50,6 +50,12 @@ exports[`should render DrawerMenu 1`] = ` | ||||
|       > | ||||
|         Sign out | ||||
|       </a> | ||||
|       <a | ||||
|         href="https://github.com/Unleash/unleash/" | ||||
|         target="_blank" | ||||
|       > | ||||
|         GitHub | ||||
|       </a> | ||||
|     </react-mdl-FooterLinkList> | ||||
|   </react-mdl-FooterDropDownSection> | ||||
|   <react-mdl-FooterDropDownSection | ||||
| @ -138,6 +144,12 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | ||||
|       > | ||||
|         Sign out | ||||
|       </a> | ||||
|       <a | ||||
|         href="https://github.com/Unleash/unleash/" | ||||
|         target="_blank" | ||||
|       > | ||||
|         GitHub | ||||
|       </a> | ||||
|     </react-mdl-FooterLinkList> | ||||
|   </react-mdl-FooterDropDownSection> | ||||
|   <react-mdl-FooterDropDownSection | ||||
|  | ||||
| @ -17,8 +17,8 @@ export const DrawerMenu = () => ( | ||||
|                 <NavLink | ||||
|                     key={item.path} | ||||
|                     to={item.path} | ||||
|                     className={[styles.navigationLink, 'mdl-color-text--grey-600'].join(' ')} | ||||
|                     activeClassName={[styles.navigationLink, 'mdl-color-text--black', 'mdl-color--light-blue-50'].join( | ||||
|                     className={[styles.navigationLink, 'mdl-color-text--grey-900'].join(' ')} | ||||
|                     activeClassName={[styles.navigationLink, 'mdl-color-text--black', 'mdl-color--blue-grey-100'].join( | ||||
|                         ' ' | ||||
|                     )} | ||||
|                 > | ||||
| @ -31,16 +31,16 @@ export const DrawerMenu = () => ( | ||||
|             <a | ||||
|                 href="https://unleash.github.io" | ||||
|                 target="_blank" | ||||
|                 className={[styles.navigationLink, 'mdl-color-text--grey-600'].join(' ')} | ||||
|                 className={[styles.navigationLink, 'mdl-color-text--grey-900'].join(' ')} | ||||
|             > | ||||
|                 <Icon name="library_books" className={styles.navigationIcon} /> User documentation | ||||
|             </a> | ||||
|             <a | ||||
|                 href="https://github.com/Unleash" | ||||
|                 target="_blank" | ||||
|                 className={[styles.navigationLink, 'mdl-color-text--grey-600'].join(' ')} | ||||
|                 className={[styles.navigationLink, 'mdl-color-text--grey-900'].join(' ')} | ||||
|             > | ||||
|                 <i className={['material-icons', styles.navigationIcon, styles.iconGitHub].join(' ')} />GitHub | ||||
|                 <i className={['material-icons', styles.navigationIcon, styles.iconGitHub].join(' ')} /> GitHub | ||||
|             </a> | ||||
|         </Navigation> | ||||
|     </Drawer> | ||||
|  | ||||
| @ -13,6 +13,9 @@ export const FooterMenu = () => ( | ||||
|                         {item.title} | ||||
|                     </NavLink> | ||||
|                 ))} | ||||
|                 <a href="https://github.com/Unleash/unleash/" target="_blank"> | ||||
|                     GitHub | ||||
|                 </a> | ||||
|             </FooterLinkList> | ||||
|         </FooterDropDownSection> | ||||
|         <FooterDropDownSection title="Clients"> | ||||
|  | ||||
							
								
								
									
										52
									
								
								frontend/src/component/menu/header.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/src/component/menu/header.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| import React, { PureComponent } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { Header, Navigation } from 'react-mdl'; | ||||
| import { Route } from 'react-router-dom'; | ||||
| import { DrawerMenu } from './drawer'; | ||||
| import Breadcrum from './breadcrumb'; | ||||
| import ShowUserContainer from '../user/show-user-container'; | ||||
| import { fetchUIConfig } from './../../store/ui-config/actions'; | ||||
| 
 | ||||
| class HeaderComponent extends PureComponent { | ||||
|     static propTypes = { | ||||
|         uiConfig: PropTypes.object.isRequired, | ||||
|         fetchUIConfig: PropTypes.func.isRequired, | ||||
|         location: PropTypes.object.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         this.props.fetchUIConfig(); | ||||
|     } | ||||
| 
 | ||||
|     componentWillReceiveProps(nextProps) { | ||||
|         if (this.props.location.pathname !== nextProps.location.pathname) { | ||||
|             clearTimeout(this.timer); | ||||
|             this.timer = setTimeout(() => { | ||||
|                 const layout = document.querySelector('.mdl-js-layout'); | ||||
|                 const drawer = document.querySelector('.mdl-layout__drawer'); | ||||
|                 // hack, might get a built in alternative later | ||||
|                 if (drawer.classList.contains('is-visible')) { | ||||
|                     layout.MaterialLayout.toggleDrawer(); | ||||
|                 } | ||||
|             }, 10); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const { headerBackground } = this.props.uiConfig; | ||||
|         const style = headerBackground ? { background: headerBackground } : {}; | ||||
|         return ( | ||||
|             <React.Fragment> | ||||
|                 <Header title={<Route path="/:path" component={Breadcrum} />} style={style}> | ||||
|                     <Navigation> | ||||
|                         <ShowUserContainer /> | ||||
|                     </Navigation> | ||||
|                 </Header> | ||||
|                 <DrawerMenu /> | ||||
|             </React.Fragment> | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default connect(state => ({ uiConfig: state.uiConfig.toJS() }), { fetchUIConfig })(HeaderComponent); | ||||
| @ -46,7 +46,8 @@ export default class ShowUserComponent extends React.Component { | ||||
|             <div className={styles.showUserSettings}> | ||||
|                 <div className={styles.showLocale}> | ||||
|                     <img src={imageLocale} title={locale} alt={locale} onClick={this.updateLocale.bind(this)} /> | ||||
|                 </div>  | ||||
|                 </div> | ||||
|                   | ||||
|                 <div className={styles.showUser}> | ||||
|                     <img src={imageUrl} title={email} alt={email} /> | ||||
|                 </div> | ||||
|  | ||||
							
								
								
									
										13
									
								
								frontend/src/data/config-api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/data/config-api.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| import { throwIfNotSuccess } from './helper'; | ||||
| 
 | ||||
| const URI = 'api/admin/ui-config'; | ||||
| 
 | ||||
| function fetchConfig() { | ||||
|     return fetch(URI, { credentials: 'include' }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| export default { | ||||
|     fetchConfig, | ||||
| }; | ||||
| @ -1,7 +1,8 @@ | ||||
| import 'whatwg-fetch'; | ||||
| import 'react-mdl/extra/material.css'; | ||||
| import 'react-mdl/extra/material.js'; | ||||
| 
 | ||||
| import 'react-mdl/extra/css/material.blue_grey-pink.min.css'; | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import { HashRouter, Route } from 'react-router-dom'; | ||||
|  | ||||
| @ -11,6 +11,7 @@ import settings from './settings'; | ||||
| import user from './user'; | ||||
| import api from './api'; | ||||
| import applications from './application'; | ||||
| import uiConfig from './ui-config'; | ||||
| 
 | ||||
| const unleashStore = combineReducers({ | ||||
|     features, | ||||
| @ -24,6 +25,7 @@ const unleashStore = combineReducers({ | ||||
|     settings, | ||||
|     user, | ||||
|     applications, | ||||
|     uiConfig, | ||||
|     api, | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,25 @@ | ||||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
| 
 | ||||
| exports[`should be default state 1`] = ` | ||||
| Object { | ||||
|   "environment": undefined, | ||||
|   "headerBackground": undefined, | ||||
|   "slogan": "A product originally created by FINN.no.", | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`should be merged state all 1`] = ` | ||||
| Object { | ||||
|   "environment": "dev", | ||||
|   "headerBackground": "red", | ||||
|   "slogan": "hello", | ||||
| } | ||||
| `; | ||||
| 
 | ||||
| exports[`should only update headerBackground 1`] = ` | ||||
| Object { | ||||
|   "environment": undefined, | ||||
|   "headerBackground": "black", | ||||
|   "slogan": "A product originally created by FINN.no.", | ||||
| } | ||||
| `; | ||||
| @ -0,0 +1,27 @@ | ||||
| import reducer from '../index'; | ||||
| import { receiveConfig } from '../actions'; | ||||
| 
 | ||||
| test('should be default state', () => { | ||||
|     const state = reducer(undefined, {}); | ||||
|     expect(state.toJS()).toMatchSnapshot(); | ||||
| }); | ||||
| 
 | ||||
| test('should be merged state all', () => { | ||||
|     const uiConfig = { | ||||
|         headerBackground: 'red', | ||||
|         slogan: 'hello', | ||||
|         environment: 'dev', | ||||
|     }; | ||||
| 
 | ||||
|     const state = reducer(undefined, receiveConfig(uiConfig)); | ||||
|     expect(state.toJS()).toMatchSnapshot(); | ||||
| }); | ||||
| 
 | ||||
| test('should only update headerBackground', () => { | ||||
|     const uiConfig = { | ||||
|         headerBackground: 'black', | ||||
|     }; | ||||
| 
 | ||||
|     const state = reducer(undefined, receiveConfig(uiConfig)); | ||||
|     expect(state.toJS()).toMatchSnapshot(); | ||||
| }); | ||||
							
								
								
									
										18
									
								
								frontend/src/store/ui-config/actions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/src/store/ui-config/actions.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| import api from '../../data/config-api'; | ||||
| import { dispatchAndThrow } from '../util'; | ||||
| 
 | ||||
| export const RECEIVE_CONFIG = 'RECEIVE_CONFIG'; | ||||
| export const ERROR_RECEIVE_CONFIG = 'ERROR_RECEIVE_CONFIG'; | ||||
| 
 | ||||
| export const receiveConfig = json => ({ | ||||
|     type: RECEIVE_CONFIG, | ||||
|     value: json, | ||||
| }); | ||||
| 
 | ||||
| export function fetchUIConfig() { | ||||
|     return dispatch => | ||||
|         api | ||||
|             .fetchConfig() | ||||
|             .then(json => dispatch(receiveConfig(json))) | ||||
|             .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_CONFIG)); | ||||
| } | ||||
							
								
								
									
										41
									
								
								frontend/src/store/ui-config/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								frontend/src/store/ui-config/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| import { Map as $Map } from 'immutable'; | ||||
| import { RECEIVE_CONFIG } from './actions'; | ||||
| 
 | ||||
| const localStorage = window.localStorage || { | ||||
|     setItem: () => {}, | ||||
|     getItem: () => {}, | ||||
| }; | ||||
| 
 | ||||
| const basePath = location ? location.pathname : '/'; | ||||
| const UI_CONFIG = `${basePath}:ui_config`; | ||||
| 
 | ||||
| const DEFAULT = new $Map({ | ||||
|     headerBackground: undefined, | ||||
|     environment: undefined, | ||||
|     slogan: 'A product originally created by FINN.no.', | ||||
| }); | ||||
| 
 | ||||
| function getInitState() { | ||||
|     try { | ||||
|         const state = JSON.parse(localStorage.getItem(UI_CONFIG)); | ||||
|         return state ? DEFAULT.merge(state) : DEFAULT; | ||||
|     } catch (e) { | ||||
|         return DEFAULT; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function updateConfig(state, config) { | ||||
|     localStorage.setItem(UI_CONFIG, JSON.stringify(config)); | ||||
|     return state.merge(config); | ||||
| } | ||||
| 
 | ||||
| const strategies = (state = getInitState(), action) => { | ||||
|     switch (action.type) { | ||||
|         case RECEIVE_CONFIG: | ||||
|             return updateConfig(state, action.value); | ||||
|         default: | ||||
|             return state; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export default strategies; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user