mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02: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