mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Customisable UI via config
This feature enables overrides of certain UI elements from the API such as setting a different background color for the header. This will make it easier to customise the UI in different environemnt.
This commit is contained in:
		
							parent
							
								
									4acc854a65
								
							
						
					
					
						commit
						c4900262f2
					
				| @ -6,7 +6,7 @@ | |||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|     <meta name="description" content="unleash"> |     <meta name="description" content="unleash"> | ||||||
| 
 | 
 | ||||||
|     <title>Unleash UI</title> |     <title>Unleash - Enterprise ready feature toggles</title> | ||||||
|     <link rel="stylesheet" href="public/bundle.css"> |     <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/icon?family=Material+Icons" rel="stylesheet"> | ||||||
|     <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" 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" |   logo="Unleash 1.1.0" | ||||||
|   type="bottom" |   type="bottom" | ||||||
| > | > | ||||||
|   <react-mdl-FooterLinkList> |   <small> | ||||||
|     <a |     (test) | ||||||
|       href="https://github.com/Unleash/unleash/" |   </small> | ||||||
|       target="_blank" |   <br /> | ||||||
|     > |   <small> | ||||||
|       GitHub |     We are the best! | ||||||
|     </a> |   </small> | ||||||
|     <a |  | ||||||
|       href="https://www.finn.no" |  | ||||||
|       target="_blank" |  | ||||||
|     > |  | ||||||
|       <small> |  | ||||||
|         A product by |  | ||||||
|       </small> |  | ||||||
|        FINN.no |  | ||||||
|     </a> |  | ||||||
|   </react-mdl-FooterLinkList> |  | ||||||
| </react-mdl-FooterSection> | </react-mdl-FooterSection> | ||||||
| `; | `; | ||||||
| 
 | 
 | ||||||
| @ -30,22 +20,25 @@ exports[`renders correctly with empty api details 1`] = ` | |||||||
|   logo="Unleash " |   logo="Unleash " | ||||||
|   type="bottom" |   type="bottom" | ||||||
| > | > | ||||||
|   <react-mdl-FooterLinkList> |   <small> | ||||||
|     <a |     (test) | ||||||
|       href="https://github.com/Unleash/unleash/" |   </small> | ||||||
|       target="_blank" |   <br /> | ||||||
|     > |   <small> | ||||||
|       GitHub |     We are the best! | ||||||
|     </a> |   </small> | ||||||
|     <a | </react-mdl-FooterSection> | ||||||
|       href="https://www.finn.no" | `; | ||||||
|       target="_blank" | 
 | ||||||
|     > | exports[`renders correctly without uiConfig 1`] = ` | ||||||
|       <small> | <react-mdl-FooterSection | ||||||
|         A product by |   logo="Unleash 1.1.0" | ||||||
|       </small> |   type="bottom" | ||||||
|        FINN.no | > | ||||||
|     </a> |   <small> | ||||||
|   </react-mdl-FooterLinkList> |      | ||||||
|  |   </small> | ||||||
|  |   <br /> | ||||||
|  |   <small /> | ||||||
| </react-mdl-FooterSection> | </react-mdl-FooterSection> | ||||||
| `; | `; | ||||||
|  | |||||||
| @ -5,14 +5,28 @@ import renderer from 'react-test-renderer'; | |||||||
| 
 | 
 | ||||||
| jest.mock('react-mdl'); | jest.mock('react-mdl'); | ||||||
| 
 | 
 | ||||||
|  | const uiConfig = { | ||||||
|  |     slogan: 'We are the best!', | ||||||
|  |     environment: 'test', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| test('renders correctly with empty api details', () => { | 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(); |     expect(tree).toMatchSnapshot(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('renders correctly with details', () => { | test('renders correctly with details', () => { | ||||||
|     const tree = renderer |     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(); |         .toJSON(); | ||||||
|     expect(tree).toMatchSnapshot(); |     expect(tree).toMatchSnapshot(); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { FooterSection, FooterLinkList } from 'react-mdl'; | import { FooterSection } from 'react-mdl'; | ||||||
| 
 | 
 | ||||||
| class ShowApiDetailsComponent extends Component { | class ShowApiDetailsComponent extends Component { | ||||||
|     static propTypes = { |     static propTypes = { | ||||||
|         apiDetails: PropTypes.object.isRequired, |         apiDetails: PropTypes.object.isRequired, | ||||||
|  |         uiConfig: PropTypes.object.isRequired, | ||||||
|         fetchAll: PropTypes.func.isRequired, |         fetchAll: PropTypes.func.isRequired, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
| @ -14,16 +15,13 @@ class ShowApiDetailsComponent extends Component { | |||||||
| 
 | 
 | ||||||
|     render() { |     render() { | ||||||
|         const version = this.props.apiDetails.version || ''; |         const version = this.props.apiDetails.version || ''; | ||||||
|  |         const { slogan, environment } = this.props.uiConfig; | ||||||
|  | 
 | ||||||
|         return ( |         return ( | ||||||
|             <FooterSection type="bottom" logo={`Unleash ${version}`}> |             <FooterSection type="bottom" logo={`Unleash ${version}`}> | ||||||
|                 <FooterLinkList> |                 <small>{environment ? `(${environment})` : ''}</small> | ||||||
|                     <a href="https://github.com/Unleash/unleash/" target="_blank"> |                 <br /> | ||||||
|                         GitHub |                 <small>{slogan}</small> | ||||||
|                     </a> |  | ||||||
|                     <a href="https://www.finn.no" target="_blank"> |  | ||||||
|                         <small>A product by</small> FINN.no |  | ||||||
|                     </a> |  | ||||||
|                 </FooterLinkList> |  | ||||||
|             </FooterSection> |             </FooterSection> | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -8,6 +8,10 @@ const mapDispatchToProps = { | |||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|     apiDetails: state.api.toJS(), |     apiDetails: state.api.toJS(), | ||||||
|  |     uiConfig: state.uiConfig.toJS(), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, mapDispatchToProps)(ShowApiDetailsComponent); | 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 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 { Route, Redirect, Switch } from 'react-router-dom'; | ||||||
| import styles from './styles.scss'; | import styles from './styles.scss'; | ||||||
| import ErrorContainer from './error/error-container'; | import ErrorContainer from './error/error-container'; | ||||||
| 
 | import Header from './menu/header'; | ||||||
| import AuthenticationContainer from './user/authentication-container'; | import AuthenticationContainer from './user/authentication-container'; | ||||||
| import ShowUserContainer from './user/show-user-container'; | 
 | ||||||
| import ShowApiDetailsContainer from './api/show-api-details-container'; | import ShowApiDetailsContainer from './api/show-api-details-container'; | ||||||
| import Features from '../page/features'; | import Features from '../page/features'; | ||||||
| import { DrawerMenu } from './menu/drawer'; | 
 | ||||||
| import { FooterMenu } from './menu/footer'; | import { FooterMenu } from './menu/footer'; | ||||||
| import Breadcrum from './menu/breadcrumb'; |  | ||||||
| import { routes } from './menu/routes'; | import { routes } from './menu/routes'; | ||||||
| 
 | 
 | ||||||
| export default class App extends Component { | export default class App extends PureComponent { | ||||||
|     static propTypes = { |     static propTypes = { | ||||||
|         location: PropTypes.object.isRequired, |         location: PropTypes.object.isRequired, | ||||||
|         match: 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() { |     render() { | ||||||
|         return ( |         return ( | ||||||
|             <div className={styles.container}> |             <div className={styles.container}> | ||||||
|                 <AuthenticationContainer /> |                 <AuthenticationContainer /> | ||||||
|                 <Layout fixedHeader> |                 <Layout fixedHeader> | ||||||
|                     <Header title={<Route path="/:path" component={Breadcrum} />}> |                     <Header location={this.props.location} /> | ||||||
|                         <Navigation> |  | ||||||
|                             <ShowUserContainer /> |  | ||||||
|                         </Navigation> |  | ||||||
|                     </Header> |  | ||||||
|                     <DrawerMenu /> |  | ||||||
|                     <Content className="mdl-color--grey-50"> |                     <Content className="mdl-color--grey-50"> | ||||||
|                         <Grid noSpacing className={styles.content}> |                         <Grid noSpacing className={styles.content}> | ||||||
|                             <Cell col={12}> |                             <Cell col={12}> | ||||||
|  | |||||||
| @ -9,6 +9,9 @@ const mapStateToProps = state => { | |||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const HistoryListContainer = connect(mapStateToProps, { fetchHistory })(HistoryComponent); | const HistoryListContainer = connect( | ||||||
|  |     mapStateToProps, | ||||||
|  |     { fetchHistory } | ||||||
|  | )(HistoryComponent); | ||||||
| 
 | 
 | ||||||
| export default HistoryListContainer; | export default HistoryListContainer; | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|   > |   > | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/features" |       href="/features" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -38,7 +38,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/strategies" |       href="/strategies" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -51,7 +51,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/history" |       href="/history" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -64,7 +64,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/archive" |       href="/archive" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -77,7 +77,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/applications" |       href="/applications" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -90,7 +90,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/logout" |       href="/logout" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -107,7 +107,7 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|     className="navigation" |     className="navigation" | ||||||
|   > |   > | ||||||
|     <a |     <a | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="https://unleash.github.io" |       href="https://unleash.github.io" | ||||||
|       target="_blank" |       target="_blank" | ||||||
|     > |     > | ||||||
| @ -118,14 +118,14 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|        User documentation |        User documentation | ||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="https://github.com/Unleash" |       href="https://github.com/Unleash" | ||||||
|       target="_blank" |       target="_blank" | ||||||
|     > |     > | ||||||
|       <i |       <i | ||||||
|         className="material-icons navigationIcon iconGitHub" |         className="material-icons navigationIcon iconGitHub" | ||||||
|       /> |       /> | ||||||
|       GitHub |        GitHub | ||||||
|     </a> |     </a> | ||||||
|   </react-mdl-Navigation> |   </react-mdl-Navigation> | ||||||
| </react-mdl-Drawer> | </react-mdl-Drawer> | ||||||
| @ -156,7 +156,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|   > |   > | ||||||
|     <a |     <a | ||||||
|       aria-current="page" |       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" |       href="/features" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|       style={Object {}} |       style={Object {}} | ||||||
| @ -170,7 +170,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/strategies" |       href="/strategies" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -183,7 +183,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/history" |       href="/history" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -196,7 +196,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/archive" |       href="/archive" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -209,7 +209,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/applications" |       href="/applications" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -222,7 +222,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       aria-current={null} |       aria-current={null} | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="/logout" |       href="/logout" | ||||||
|       onClick={[Function]} |       onClick={[Function]} | ||||||
|     > |     > | ||||||
| @ -239,7 +239,7 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|     className="navigation" |     className="navigation" | ||||||
|   > |   > | ||||||
|     <a |     <a | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="https://unleash.github.io" |       href="https://unleash.github.io" | ||||||
|       target="_blank" |       target="_blank" | ||||||
|     > |     > | ||||||
| @ -250,14 +250,14 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|        User documentation |        User documentation | ||||||
|     </a> |     </a> | ||||||
|     <a |     <a | ||||||
|       className="navigationLink mdl-color-text--grey-600" |       className="navigationLink mdl-color-text--grey-900" | ||||||
|       href="https://github.com/Unleash" |       href="https://github.com/Unleash" | ||||||
|       target="_blank" |       target="_blank" | ||||||
|     > |     > | ||||||
|       <i |       <i | ||||||
|         className="material-icons navigationIcon iconGitHub" |         className="material-icons navigationIcon iconGitHub" | ||||||
|       /> |       /> | ||||||
|       GitHub |        GitHub | ||||||
|     </a> |     </a> | ||||||
|   </react-mdl-Navigation> |   </react-mdl-Navigation> | ||||||
| </react-mdl-Drawer> | </react-mdl-Drawer> | ||||||
|  | |||||||
| @ -50,6 +50,12 @@ exports[`should render DrawerMenu 1`] = ` | |||||||
|       > |       > | ||||||
|         Sign out |         Sign out | ||||||
|       </a> |       </a> | ||||||
|  |       <a | ||||||
|  |         href="https://github.com/Unleash/unleash/" | ||||||
|  |         target="_blank" | ||||||
|  |       > | ||||||
|  |         GitHub | ||||||
|  |       </a> | ||||||
|     </react-mdl-FooterLinkList> |     </react-mdl-FooterLinkList> | ||||||
|   </react-mdl-FooterDropDownSection> |   </react-mdl-FooterDropDownSection> | ||||||
|   <react-mdl-FooterDropDownSection |   <react-mdl-FooterDropDownSection | ||||||
| @ -138,6 +144,12 @@ exports[`should render DrawerMenu with "features" selected 1`] = ` | |||||||
|       > |       > | ||||||
|         Sign out |         Sign out | ||||||
|       </a> |       </a> | ||||||
|  |       <a | ||||||
|  |         href="https://github.com/Unleash/unleash/" | ||||||
|  |         target="_blank" | ||||||
|  |       > | ||||||
|  |         GitHub | ||||||
|  |       </a> | ||||||
|     </react-mdl-FooterLinkList> |     </react-mdl-FooterLinkList> | ||||||
|   </react-mdl-FooterDropDownSection> |   </react-mdl-FooterDropDownSection> | ||||||
|   <react-mdl-FooterDropDownSection |   <react-mdl-FooterDropDownSection | ||||||
|  | |||||||
| @ -17,8 +17,8 @@ export const DrawerMenu = () => ( | |||||||
|                 <NavLink |                 <NavLink | ||||||
|                     key={item.path} |                     key={item.path} | ||||||
|                     to={item.path} |                     to={item.path} | ||||||
|                     className={[styles.navigationLink, 'mdl-color-text--grey-600'].join(' ')} |                     className={[styles.navigationLink, 'mdl-color-text--grey-900'].join(' ')} | ||||||
|                     activeClassName={[styles.navigationLink, 'mdl-color-text--black', 'mdl-color--light-blue-50'].join( |                     activeClassName={[styles.navigationLink, 'mdl-color-text--black', 'mdl-color--blue-grey-100'].join( | ||||||
|                         ' ' |                         ' ' | ||||||
|                     )} |                     )} | ||||||
|                 > |                 > | ||||||
| @ -31,16 +31,16 @@ export const DrawerMenu = () => ( | |||||||
|             <a |             <a | ||||||
|                 href="https://unleash.github.io" |                 href="https://unleash.github.io" | ||||||
|                 target="_blank" |                 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 |                 <Icon name="library_books" className={styles.navigationIcon} /> User documentation | ||||||
|             </a> |             </a> | ||||||
|             <a |             <a | ||||||
|                 href="https://github.com/Unleash" |                 href="https://github.com/Unleash" | ||||||
|                 target="_blank" |                 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> |             </a> | ||||||
|         </Navigation> |         </Navigation> | ||||||
|     </Drawer> |     </Drawer> | ||||||
|  | |||||||
| @ -13,6 +13,9 @@ export const FooterMenu = () => ( | |||||||
|                         {item.title} |                         {item.title} | ||||||
|                     </NavLink> |                     </NavLink> | ||||||
|                 ))} |                 ))} | ||||||
|  |                 <a href="https://github.com/Unleash/unleash/" target="_blank"> | ||||||
|  |                     GitHub | ||||||
|  |                 </a> | ||||||
|             </FooterLinkList> |             </FooterLinkList> | ||||||
|         </FooterDropDownSection> |         </FooterDropDownSection> | ||||||
|         <FooterDropDownSection title="Clients"> |         <FooterDropDownSection title="Clients"> | ||||||
|  | |||||||
							
								
								
									
										55
									
								
								frontend/src/component/menu/header.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								frontend/src/component/menu/header.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | 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); | ||||||
| @ -13,4 +13,7 @@ const mapStateToProps = state => ({ | |||||||
|     location: state.settings ? state.settings.toJS().location : {}, |     location: state.settings ? state.settings.toJS().location : {}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, mapDispatchToProps)(ShowUserComponent); | export default connect( | ||||||
|  |     mapStateToProps, | ||||||
|  |     mapDispatchToProps | ||||||
|  | )(ShowUserComponent); | ||||||
|  | |||||||
							
								
								
									
										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 'whatwg-fetch'; | ||||||
| import 'react-mdl/extra/material.css'; |  | ||||||
| import 'react-mdl/extra/material.js'; | import 'react-mdl/extra/material.js'; | ||||||
| 
 | 
 | ||||||
|  | import 'react-mdl/extra/css/material.blue_grey-pink.min.css'; | ||||||
|  | 
 | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import ReactDOM from 'react-dom'; | ||||||
| import { HashRouter, Route } from 'react-router-dom'; | import { HashRouter, Route } from 'react-router-dom'; | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ import settings from './settings'; | |||||||
| import user from './user'; | import user from './user'; | ||||||
| import api from './api'; | import api from './api'; | ||||||
| import applications from './application'; | import applications from './application'; | ||||||
|  | import uiConfig from './ui-config'; | ||||||
| 
 | 
 | ||||||
| const unleashStore = combineReducers({ | const unleashStore = combineReducers({ | ||||||
|     features, |     features, | ||||||
| @ -24,6 +25,7 @@ const unleashStore = combineReducers({ | |||||||
|     settings, |     settings, | ||||||
|     user, |     user, | ||||||
|     applications, |     applications, | ||||||
|  |     uiConfig, | ||||||
|     api, |     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,29 @@ | |||||||
|  | import { AssertionError } from 'assert'; | ||||||
|  | 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', () => { | ||||||
|  |     localStorage.clear(); | ||||||
|  |     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