Compare commits

...

1 Commits

Author SHA1 Message Date
Douglas Hall
66d647f381 feat(header): add new responsive header 2018-12-19 09:20:52 -05:00
8 changed files with 234 additions and 32 deletions

110
package-lock.json generated
View File

@@ -2875,6 +2875,21 @@
}
}
},
"@babel/runtime": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.2.0.tgz",
"integrity": "sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==",
"requires": {
"regenerator-runtime": "0.12.1"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"@babel/template": {
"version": "7.0.0-beta.40",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.40.tgz",
@@ -2965,9 +2980,9 @@
}
},
"@edx/frontend-auth": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-1.2.1.tgz",
"integrity": "sha512-NEc+rAJq5HJ9UACPirezpwTp5yhX9G2AQDPfk1hW4ceEF7BFRMIfzb+nJqmTJkEX/37yRVYUfQFLW+z2j1ZRcw==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-1.3.0.tgz",
"integrity": "sha512-llAoGBvxwZCnGzUMFSHHMxGIrrYLuWlEmrSQs6rqdqT5RXHUkuwNde5iemohqGe/dLGcGjiNzUjr83iGUlN66Q==",
"requires": {
"axios": "^0.18.0",
"jwt-decode": "^2.2.0",
@@ -7968,6 +7983,14 @@
}
}
},
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"requires": {
"@babel/runtime": "7.2.0"
}
},
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@@ -13793,6 +13816,16 @@
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true
},
"lodash.isfunction": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="
},
"lodash.isobject": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
"integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -13856,6 +13889,11 @@
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
"dev": true
},
"lodash.tonumber": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz",
"integrity": "sha1-C5azGzVnJ5Prf1pj7nkfG56QJdk="
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@@ -20129,6 +20167,27 @@
"integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw==",
"dev": true
},
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-popper": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz",
"integrity": "sha1-rypBXqIike3VBGeNev2opu4ylao=",
"requires": {
"popper.js": "1.14.6",
"prop-types": "15.6.1"
},
"dependencies": {
"popper.js": {
"version": "1.14.6",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz",
"integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA=="
}
}
},
"react-proptype-conditional-require": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz",
@@ -20217,6 +20276,51 @@
"prop-types": "^15.6.0"
}
},
"react-transition-group": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.1.tgz",
"integrity": "sha512-8x/CxUL9SjYFmUdzsBPTgtKeCxt7QArjNSte0wwiLtF/Ix/o1nWNJooNy5o9XbHIKS31pz7J5VF2l41TwlvbHQ==",
"requires": {
"dom-helpers": "3.4.0",
"loose-envify": "1.4.0",
"prop-types": "15.6.2",
"react-lifecycles-compat": "3.0.4"
},
"dependencies": {
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "3.0.2"
}
},
"prop-types": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
"requires": {
"loose-envify": "1.4.0",
"object-assign": "4.1.1"
}
}
}
},
"reactstrap": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-6.5.0.tgz",
"integrity": "sha512-dWb3fB/wBAiQloteKlf+j9Nl2VLe6BMZgTEt6hpeTt0t9TwtkeU+2v2NBYONZaF4FZATfMiIKozhWpc2HmLW1g==",
"requires": {
"classnames": "2.2.5",
"lodash.isfunction": "3.0.9",
"lodash.isobject": "3.0.2",
"lodash.tonumber": "4.0.3",
"prop-types": "15.6.1",
"react-lifecycles-compat": "3.0.4",
"react-popper": "0.10.4",
"react-transition-group": "2.5.1"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",

View File

@@ -25,7 +25,7 @@
},
"dependencies": {
"@edx/edx-bootstrap": "^0.4.3",
"@edx/frontend-auth": "^1.2.1",
"@edx/frontend-auth": "^1.3.0",
"@edx/paragon": "^3.7.2",
"babel-polyfill": "^6.26.0",
"classnames": "^2.2.5",
@@ -40,6 +40,7 @@
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.9",
"reactstrap": "^6.5.0",
"redux": "^3.7.2",
"redux-devtools-extension": "^2.13.2",
"redux-logger": "^3.0.6",

View File

@@ -1,30 +1,94 @@
import React from 'react';
import { Hyperlink } from '@edx/paragon';
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem,
NavLink,
UncontrolledDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
import { Icon } from '@edx/paragon';
import PropTypes from 'prop-types';
import apiClient from '../../data/apiClient';
import { configuration } from '../../config';
import EdxLogo from '../../../assets/edx-sm.png';
export default class Header extends React.Component {
class Header extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
mobileNavOpen: false,
};
}
renderLogo() {
return (
<img src={EdxLogo} alt="edX logo" height="30" width="60" />
);
toggle() {
this.setState({
mobileNavOpen: !this.state.mobileNavOpen,
});
}
getUserProfileImageIcon() {
const screenReaderText = `Profile image for ${this.props.username}`;
if (this.props.userProfileImageUrl) {
return <img src={this.props.userProfileImageUrl} alt={screenReaderText} />;
}
return <Icon className={['fa', 'fa-user', 'px-3']} screenReaderText={screenReaderText} />;
}
render() {
return (
<div className="mb-3">
<header className="d-flex justify-content-center align-items-center p-3 border-bottom-blue">
<Hyperlink content={this.renderLogo()} destination="https://www.edx.org" />
<div />
</header>
</div>
<Navbar light expand="md" className="border-bottom">
<NavbarBrand href={configuration.LMS_BASE_URL}>
<img src={EdxLogo} alt="edX logo" height="30" width="60" />
</NavbarBrand>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.mobileNavOpen} navbar>
<Nav className="ml-auto" navbar>
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>
{this.getUserProfileImageIcon()}
{this.props.username}
</DropdownToggle>
<DropdownMenu right>
<DropdownItem href={`${configuration.LMS_BASE_URL}/dashboard`}>
Dashboard
</DropdownItem>
<DropdownItem href={`${configuration.LMS_BASE_URL}/u/${this.props.username}`}>
Profile
</DropdownItem>
<DropdownItem href={`${configuration.LMS_BASE_URL}/account/settings`}>
Account
</DropdownItem>
<DropdownItem divider />
<DropdownItem onClick={() => apiClient.logout()}>
Logout
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
</Nav>
</Collapse>
</Navbar>
);
}
}
Header.defaultProps = {
username: null,
userProfileImageUrl: null,
};
Header.propTypes = {
username: PropTypes.string,
userProfileImageUrl: PropTypes.string,
};
export default Header;

View File

@@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import { fetchUserProfile } from '@edx/frontend-auth';
import Header from '../../components/Header';
const mapStateToProps = state => ({
username: state.userProfile.username,
userProfileImageUrl: state.userProfile.userProfileImageUrl,
});
export default connect(mapStateToProps)(Header);

View File

@@ -4,6 +4,7 @@ import { configuration } from '../config';
const apiClient = getAuthenticatedAPIClient({
appBaseUrl: configuration.BASE_URL,
authBaseUrl: configuration.LMS_BASE_URL,
loginUrl: configuration.LOGIN_URL,
logoutUrl: configuration.LOGOUT_URL,
csrfTokenApiPath: process.env.CSRF_TOKEN_API_PATH,

View File

@@ -1,11 +1,21 @@
import { combineReducers } from 'redux';
import { userProfile } from '@edx/frontend-auth';
import cohorts from './cohorts';
import grades from './grades';
import tracks from './tracks';
import assignmentTypes from './assignmentTypes';
const identityReducer = (state) => {
const newState = { ...state };
return newState;
};
const rootReducer = combineReducers({
// The authentication state is added as initialState when
// creating the store in data/store.js.
authentication: identityReducer,
userProfile,
grades,
cohorts,
tracks,

View File

@@ -3,12 +3,15 @@ import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
import { createLogger } from 'redux-logger';
import apiClient from './apiClient';
import reducers from './reducers';
const loggerMiddleware = createLogger();
const initialState = apiClient.getAuthenticationState();
const store = createStore(
reducers,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware, loggerMiddleware)),
);

View File

@@ -3,29 +3,37 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { fetchUserProfile } from '@edx/frontend-auth';
import apiClient from './data/apiClient';
import Footer from './components/Gradebook/footer';
import GradebookPage from './containers/GradebookPage';
import Header from './components/Header';
import Header from './containers/Header';
import store from './data/store';
import './App.scss';
const App = () => (
<Provider store={store}>
<Router>
<div>
<Header />
<main>
<Switch>
<Route exact path="/:courseId" component={GradebookPage} />
</Switch>
</main>
<Footer />
</div>
</Router>
</Provider>
);
class App extends React.Component {
componentDidMount() {
const username = store.getState().authentication.username;
store.dispatch(fetchUserProfile(apiClient, username));
}
render() {
return <Provider store={store}>
<Router>
<div>
<Header />
<main>
<Switch>
<Route exact path="/:courseId" component={GradebookPage} />
</Switch>
</main>
<Footer />
</div>
</Router>
</Provider>;
}
}
if (apiClient.ensurePublicOrAuthencationAndCookies(window.location.pathname)) {
ReactDOM.render(<App />, document.getElementById('root'));