diff --git a/src/App.css b/src/App.css index f29b526..fa6a3c8 100644 --- a/src/App.css +++ b/src/App.css @@ -21,6 +21,16 @@ position: relative; } +/* disable user text highlighting */ +.noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ +} + @media (max-width: 639px) { .hide-xsmall { display: none; diff --git a/src/App.js b/src/App.js index 24fd32b..20fc216 100644 --- a/src/App.js +++ b/src/App.js @@ -15,15 +15,9 @@ class App extends Component { return (
- + - {/* Temporary redirect from /; will use HomePage component */ } + {/* Temporary redirect from /; will use HomePage component */} } /> { return this.props.authenticated @@ -41,10 +35,6 @@ class App extends Component { } } -function mapStateToProps(state) { - return state.user; -} - function mapDispatchToProps(dispatch) { return { removeNotification(id){ @@ -56,4 +46,4 @@ function mapDispatchToProps(dispatch) { } } -export default connect(mapStateToProps, mapDispatchToProps)(App); +export default connect(({ user, currentPursuanceId }) => ({ user, currentPursuanceId }), mapDispatchToProps)(App); diff --git a/src/actions/index.js b/src/actions/index.js index a43724c..dd3ffea 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -46,6 +46,11 @@ export const setTaskFormParentGid = (formId, newParentGid, oldParentGid) => ({ export const getUsers = () => ({ type: 'GET_USERS', payload: getUsersReq() }); +export const setPublicOrder = publicOrder => ({ + type: 'SET_PUBLIC_ORDER', + publicOrder +}); + export const getPursuancesByIds = pursuanceIds => ({ type: 'GET_PURSUANCES_BY_IDS', payload: getPursuancesReq(pursuanceIds) diff --git a/src/components/Content/Pursuance/PursuanceMenu.js b/src/components/Content/Pursuance/PursuanceMenu.js index c856e05..bd75353 100644 --- a/src/components/Content/Pursuance/PursuanceMenu.js +++ b/src/components/Content/Pursuance/PursuanceMenu.js @@ -6,6 +6,7 @@ import PursuanceMenuItem from './PursuanceMenuItem'; import TiFlowChildren from 'react-icons/lib/ti/flow-children'; import FaCheckSquareO from 'react-icons/lib/fa/check-square-o'; import FaCalendar from 'react-icons/lib/fa/calendar'; +import Info from 'react-icons/lib/fa/info-circle'; // import FaSitemap from 'react-icons/lib/fa/sitemap'; import CommentsO from 'react-icons/lib/fa/comments-o'; // import Planet from 'react-icons/lib/io/planet'; @@ -19,6 +20,12 @@ const PursuanceMenu = () => { return (
+ } + /> {/* { /> */} } diff --git a/src/components/Content/Pursuance/PursuancePage.js b/src/components/Content/Pursuance/PursuancePage.js index 2ecc458..3d9c454 100644 --- a/src/components/Content/Pursuance/PursuancePage.js +++ b/src/components/Content/Pursuance/PursuancePage.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import { setCurrentPursuance } from '../../../actions'; import PursuanceMenu from './PursuanceMenu'; +import AboutView from './views/AboutView'; import MyTasksView from './views/MyTasksView'; import TaskListView from './views/TaskListView'; import CalendarView from './views/CalendarView'; @@ -31,6 +32,7 @@ class PursuancePage extends Component {
+ diff --git a/src/components/Content/Pursuance/views/AboutView.css b/src/components/Content/Pursuance/views/AboutView.css new file mode 100644 index 0000000..3e3a9cf --- /dev/null +++ b/src/components/Content/Pursuance/views/AboutView.css @@ -0,0 +1,19 @@ +.about { + width: 100%; + padding: 0 80px 0 30px; + overflow: auto; +} + +.about h1, h2, h4 { + color: #fcfcfc; +} + +.about h1 { + text-align: center; +} + +@media (max-width: 768px) { + .about { + padding: 0 65px 0 15px; + } +} \ No newline at end of file diff --git a/src/components/Content/Pursuance/views/AboutView.js b/src/components/Content/Pursuance/views/AboutView.js new file mode 100644 index 0000000..6985c2c --- /dev/null +++ b/src/components/Content/Pursuance/views/AboutView.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import './AboutView.css'; +import { PROJECT_CAPITAL } from '../../../../constants'; + +class AboutView extends Component { + render() { + const {currentPursuanceId, pursuances} = this.props; + const p = (pursuances[currentPursuanceId] !== undefined) ? + pursuances[currentPursuanceId] : ""; + return ( +
+

About This {PROJECT_CAPITAL}

+

Name: {p.name}

+

Mission: {p.mission}

+
+ ); + } +} + +export default connect(({ currentPursuanceId, pursuances }) => + ({ currentPursuanceId, pursuances }) +)(AboutView); \ No newline at end of file diff --git a/src/components/Content/TaskHierarchy/TaskHierarchy.js b/src/components/Content/TaskHierarchy/TaskHierarchy.js index a9f890b..a49dff6 100644 --- a/src/components/Content/TaskHierarchy/TaskHierarchy.js +++ b/src/components/Content/TaskHierarchy/TaskHierarchy.js @@ -35,6 +35,12 @@ class TaskHierarchy extends Component { } } + componentDidUpdate(prevProps) { + if (prevProps.currentPursuanceId !== this.props.currentPursuanceId) { + this.componentDidMount(); + } + } + componentWillUnmount(){ const { showSuccessToast, removeSuccessToast } = this.props; if (showSuccessToast) { @@ -78,6 +84,13 @@ class TaskHierarchy extends Component { } } + getPursuanceName = (pursuances, id) => { + const rawPursuance = pursuances[id]; + if (rawPursuance !== undefined) { + return rawPursuance.name; + } + } + renderHierarchy = () => { const { currentPursuanceId } = this.props; const { rootTaskGids, taskMap } = this.props.tasks; @@ -102,18 +115,9 @@ class TaskHierarchy extends Component { } render() { - const { pursuances, currentPursuanceId } = this.props; return (
-
-

Tasks: 

-

- { - pursuances[currentPursuanceId] && pursuances[currentPursuanceId].name - } -

-
@@ -142,11 +146,11 @@ class TaskHierarchy extends Component {
+ position="top-center" + type="success" + autoClose={4000} + hideProgressBar={false} + newestOnTop={false} /> {this.renderHierarchy()}
diff --git a/src/components/Content/TaskStatus/TaskStatus.js b/src/components/Content/TaskStatus/TaskStatus.js index 9d9f482..94dd428 100644 --- a/src/components/Content/TaskStatus/TaskStatus.js +++ b/src/components/Content/TaskStatus/TaskStatus.js @@ -40,6 +40,8 @@ class TaskStatus extends Component { {this.displayStatus(statusName)} ); + } else { + return false; } }); } diff --git a/src/components/NavBar/JumpToPursuance/JumpToPursuance.js b/src/components/NavBar/JumpToPursuance/JumpToPursuance.js new file mode 100644 index 0000000..d329bf7 --- /dev/null +++ b/src/components/NavBar/JumpToPursuance/JumpToPursuance.js @@ -0,0 +1,74 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { DropdownButton, MenuItem } from 'react-bootstrap'; +import { setCurrentPursuance } from '../../../actions'; +import { PROJECT } from '../../../constants'; + +const getCurrentPursuanceName = (pursuances, currentPursuanceId) => { + const rawPursuance = pursuances[currentPursuanceId]; + if (rawPursuance !== undefined) { + return rawPursuance.name; + } else { + return "Jump to a " + PROJECT; + } +} + +const produceOptions = (pursuances) => { + const pursuanceArr = Object.values(pursuances); + pursuanceArr.sort((p1, p2) => { + return p1.name.toLowerCase().localeCompare(p2.name.toLowerCase()); + }); + + return pursuanceArr.map((pursuance) => ( + + {pursuance.name} + + )); +} + +const onMenuItemSelectAction = (pursuanceId, onMenuItemSelect, history) => { + history.push({ + pathname: `/pursuance/${pursuanceId}` + }); + onMenuItemSelect(pursuanceId); +} + +const renderDropdown = (props) => { + return ( +
  • + onMenuItemSelectAction(pursuanceId, props.onMenuItemSelect, props.history) } + > + { produceOptions(props.pursuances) } + +
  • + ) +} + +const mapStateToProps = state => { + return { + currentPursuanceId: state.currentPursuanceId, + pursuances: state.pursuances + } +} + +const mapDispatchToProps = dispatch => { + return { + onMenuItemSelect: (pursuanceId) => { + dispatch(setCurrentPursuance(pursuanceId)); + } + } +} + +const JumpToPursuance = connect( + mapStateToProps, + mapDispatchToProps +)(renderDropdown) + +export default JumpToPursuance \ No newline at end of file diff --git a/src/components/NavBar/NavBar.css b/src/components/NavBar/NavBar.css index f7d91d1..85c92b0 100644 --- a/src/components/NavBar/NavBar.css +++ b/src/components/NavBar/NavBar.css @@ -1,62 +1,87 @@ .navbar { - position: fixed; + top: 0; + left: 0; + height: 60px; + width: 100%; + display: flex; + justify-content: center; + width: 100%; background-color: #141414; + position: fixed; border-radius: 0px; border: none; margin: 0; - height: 60px; - width: 100%; - top: 0; z-index: 10; } .navbar .container { - align-items: center; width: 100%; - padding: 0 32px 0 32px; + padding: 0 32px; } -.navbar-collapse { - height: 200px; +.navbar > .container .navbar-header .navbar-brand { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + height: 60px; + color: #fcfcfc; + padding-left: 0; + margin-left: 0; } .navbar-collapse.collapse { background-color: black; } -.modal-title { - text-align: center; +.navbar-collapse.in { + overflow-y: visible; } -.modal-btn-ctn { - text-align: right; - padding-right: 16px; +.navbar-brand:hover { + color: #8662ea !important; } -.modal-btn-ctn .btn { - margin-left: 10px; +nav a:hover, a:active, a:link { + text-decoration: none !important; } -.navbar-header { - height: 100%; +nav li a:hover { + color: #1fc7d3 !important; +} + +.nav-pull-right { + float: right; } -.navbar-brand { +.navbar-nav:last-child { + margin-bottom: 0; +} + +.navbar-nav li { + height: 60px; +} + +.navbar-nav li a { display: flex; align-items: center; justify-content: center; height: 60px; - color: #fcfcfc !important; - padding-left: 0 !important; - margin-left: 0 !important; + color: #efefef !important; } -.navbar-brand:hover { - color: #8662ea !important; +.navbar-nav li:last-child a { + padding-right: 0; } -.navbar-nav { - height: 60px; +.navbar-nav li a:hover { + color: #1fc7d3 !important; +} + +.navbar-nav li a button { + padding-bottom: 2px; } @media (max-width: 768px) { @@ -67,26 +92,133 @@ } } -.navbar-nav li { - height: 60px; +.modal-title { + text-align: center; } -.navbar-nav li a { +.modal-btn-ctn { + text-align: right; + padding-right: 16px; +} + +.modal-btn-ctn .btn { + margin-left: 10px; +} + +.planet-icon { + margin: 0 5px 3px 5px; +} + +/* Pursuance selector dropdown */ + +#header-pursuance-dropdown { + height: 100%; + width: 100%; + padding: 0; + text-align: left; + background: none; + color: #e0e0e0; + border: none; + overflow: hidden; + text-overflow: ellipsis; + max-width: 280px; + min-width: 70px; + position: relative; + padding-right: 13px; +} + +.nav-pursuances { + float: left; + position: relative; + display: block; + padding: 0 15px; +} + +.nav-pursuances .caret { + position: absolute; + top: 50%; + right: -3px; + transform: translate(-50%, -50%); +} + +.nav-pursuances .dropdown { + padding: 20px 0 0; + width: 100%; display: flex; - align-items: center; justify-content: center; - height: 60px; - color: #efefef !important; + align-items: center; + overflow: hidden; } -.navbar-nav li a:hover { - color: #1fc7d3 !important; +.nav-pursuances .dropdown li { + height: auto; } -.navbar-nav li a button { - padding-bottom: 2px; +.nav-pursuances .dropdown > .dropdown-menu { + opacity: 0; + display: block; + transition: opacity 200ms ease-in-out; + max-width: 300px; + overflow: hidden; + padding: 0; } -.planet-icon { - margin: 0 5px 3px 5px; +.nav-pursuances .dropdown.open { + overflow: visible; +} + +.nav-pursuances .dropdown.open > .dropdown-menu { + opacity: 1; +} + +.nav-pursuances .dropdown a { + overflow: hidden; + text-overflow: ellipsis; + color: #262626 !important; + white-space: normal; + height: auto; + padding: 15px 10px; + text-align: center; } + +.nav-pursuances .dropdown a:hover { + color: #262626 !important; +} + +@media (max-width: 768px) { + .nav-pursuances { + float: none; + } + + .navbar-nav .nav-pursuances .dropdown-menu { + position: absolute; + left: 50%; + transform: translateX(-50%); + max-width: 90%; + background-color: #fff; + } +} + +@media (min-width: 768px) and (max-width: 960px) { + .nav .nav-pursuances { + display: none; + } +} + +@media (max-width: 960px) { + #header-pursuance-dropdown { + max-width: none; + width: auto; + padding-right: 25px; + } + + .nav-pursuances { + padding: 0 10px; + } +} + +@media (max-width: 1024px) { + #header-pursuance-dropdown { + max-width: 200px; + } +} \ No newline at end of file diff --git a/src/components/NavBar/NavBar.js b/src/components/NavBar/NavBar.js index 5934fde..7752b63 100644 --- a/src/components/NavBar/NavBar.js +++ b/src/components/NavBar/NavBar.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; -import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { Link, withRouter } from 'react-router-dom'; import { Navbar, NavItem, Nav, OverlayTrigger, Tooltip } from 'react-bootstrap'; import FaBell from 'react-icons/lib/fa/bell'; import SignUp from './SignUp/SignUp'; @@ -8,6 +9,7 @@ import NotificationsModal from './NotificationsModal/NotificationsModal'; import UserSettingsPopover from './UserSettingsPopover'; import Planet from 'react-icons/lib/io/planet'; import { PROJECT_CAPITAL, THIS_PROJECT_NAME } from '../../constants'; +import JumpToPursuance from './JumpToPursuance/JumpToPursuance'; import './NavBar.css'; class NavBar extends Component { @@ -19,7 +21,7 @@ class NavBar extends Component { ); render() { - const { authenticated, username, contributionPoints } = this.props; + const { authenticated, username, contributionPoints } = this.props.user; return ( @@ -59,6 +61,9 @@ class NavBar extends Component { ) } + {authenticated && + + } { authenticated && ( @@ -93,4 +98,4 @@ class NavBar extends Component { } } -export default NavBar; +export default withRouter(connect(({ user }) => ({ user }))(NavBar)); diff --git a/src/components/PublicPursuances/PublicPursuanceList.js b/src/components/PublicPursuances/PublicPursuanceList.js index 8298cca..94b24d6 100644 --- a/src/components/PublicPursuances/PublicPursuanceList.js +++ b/src/components/PublicPursuances/PublicPursuanceList.js @@ -22,6 +22,42 @@ class PublicPursuanceList extends Component { getMemberships({ "user_username" : user.username }); } + sortByDateDesc = (p1, p2) => { + p1["created_parsed"] = Date.parse(p1.created); + p2["created_parsed"] = Date.parse(p2.created); + return p2.created_parsed - p1.created_parsed; + } + + sortByDateAsc = (p1, p2) => { + return p1.created_parsed - p2.created_parsed; + } + + sortByNameAsc = (p1, p2) => { + return p1.name.toLowerCase().localeCompare(p2.name.toLowerCase()); + } + + sortByNameDesc = (p1, p2) => { + return p2.name.toLowerCase().localeCompare(p1.name.toLowerCase()); + } + + sortBy = () => { + switch(this.props.publicOrder) { + case "Most Recent": + return this.sortByDateDesc; + case "Oldest": + return this.sortByDateAsc; + case "A to Z": + return this.sortByNameAsc; + case "Z to A": + return this.sortByNameDesc; + case "Most Popular": + // function + break; + default: + return this.sortByDateDesc; + } + } + onChangeTag = (e) => { this.setState({ searchByTag: e.target.value @@ -31,6 +67,7 @@ class PublicPursuanceList extends Component { getPublicPursuanceList = () => { const { user, publicPursuances, postMembership, memberships, deleteMembership } = this.props; const pursuanceArr = Object.values(publicPursuances); + pursuanceArr.sort(this.sortBy()); return pursuanceArr.map((pursuance) => { if (pursuance.name.toLowerCase().indexOf(this.state.searchByTag) === -1 && pursuance.mission.toLowerCase().indexOf(this.state.searchByTag) === -1) { @@ -108,11 +145,10 @@ class PublicPursuanceList extends Component {
    ) } - } -export default connect(({ publicPursuances, user, memberships }) => - ({ publicPursuances, user, memberships }),{ +export default connect(({ publicPursuances, user, memberships, publicOrder }) => + ({ publicPursuances, user, memberships, publicOrder }),{ postMembership, getMemberships, deleteMembership diff --git a/src/components/PublicPursuances/PublicPursuances.css b/src/components/PublicPursuances/PublicPursuances.css index 1f71271..82befde 100644 --- a/src/components/PublicPursuances/PublicPursuances.css +++ b/src/components/PublicPursuances/PublicPursuances.css @@ -28,3 +28,71 @@ .pursuance-description h3 { color: #50b3fe; } + +/* Sort dropdown */ + +#dashboard .sort { + position: relative; + display: block; + margin: 10px 0 30px; +} + +#dashboard .sort label { + margin-right: 10px; + color: white; + font-weight: normal; + font-weight: bold; +} + +#sort-dropdown { + height: 100%; + width: 100%; + padding: 0; + text-align: left; + background: none; + color: #e0e0e0; + border: none; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + position: relative; + padding-right: 13px; + font-size: 14px; +} + +#dashboard .sort .caret { + position: absolute; + top: 50%; + right: -3px; + transform: translate(-50%, -50%); +} + +#dashboard .sort .dropdown > .dropdown-menu { + opacity: 0; + display: block; + transition: opacity 200ms ease-in-out; + max-width: calc(100vw - 200px); +} + +#dashboard .sort .dropdown { + overflow: hidden; +} + +#dashboard .sort .dropdown.open { + overflow: visible; +} + +#dashboard .sort .dropdown.open > .dropdown-menu { + opacity: 1; +} + +#dashboard .sort .dropdown a { + overflow: hidden; + text-overflow: ellipsis; +} + +@media (max-width: 639px) { + #dashboard .sort .dropdown > .dropdown-menu { + max-width: calc(100vw - 140px); + } +} \ No newline at end of file diff --git a/src/components/PublicPursuances/PublicPursuances.js b/src/components/PublicPursuances/PublicPursuances.js index b0e9624..fab2d33 100644 --- a/src/components/PublicPursuances/PublicPursuances.js +++ b/src/components/PublicPursuances/PublicPursuances.js @@ -1,9 +1,10 @@ import React, { Component } from 'react'; import PublicPursuanceList from './PublicPursuanceList'; -import { getPublicPursuances } from '../../actions'; +import { getPublicPursuances, setPublicOrder } from '../../actions'; import { connect } from 'react-redux'; import { PROJECTS_CAPITAL } from '../../constants'; - +import { DropdownButton, MenuItem } from 'react-bootstrap'; +import './PublicPursuances.css'; class PublicPursuances extends Component { @@ -11,6 +12,10 @@ class PublicPursuances extends Component { this.props.getPublicPursuances(); } + handleChange = (value) => { + this.props.setPublicOrder(value); + } + render () { return (
    @@ -20,6 +25,43 @@ class PublicPursuances extends Component {
    +
    + + + + Most Recent + + + Oldest + + + A to Z + + + Z to A + + +
    @@ -29,4 +71,8 @@ class PublicPursuances extends Component { } } -export default connect(null, { getPublicPursuances })(PublicPursuances); +export default connect(({publicOrder}) => ({publicOrder}), { + setPublicOrder, + getPublicPursuances + } +)(PublicPursuances); diff --git a/src/reducers/index.js b/src/reducers/index.js index 7a71929..fdf9917 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -2,6 +2,7 @@ import { combineReducers } from 'redux'; import taskForm from './taskFormReducer'; import users from './usersReducer'; import pursuances from './pursuancesReducer'; +import publicOrder from './publicOrderReducer'; import publicPursuances from './publicPursuancesReducer'; import currentPursuanceId from './currentPursuanceIdReducer'; import tasks from './tasksReducer'; @@ -24,6 +25,7 @@ const rootReducer = combineReducers({ taskForm, users, pursuances, + publicOrder, publicPursuances, currentPursuanceId, tasks, diff --git a/src/reducers/publicOrderReducer.js b/src/reducers/publicOrderReducer.js new file mode 100644 index 0000000..213c86c --- /dev/null +++ b/src/reducers/publicOrderReducer.js @@ -0,0 +1,8 @@ +export default function(state = 'Most Recent', action) { + switch (action.type) { + case 'SET_PUBLIC_ORDER': + return action.publicOrder; + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/publicPursuancesReducer.js b/src/reducers/publicPursuancesReducer.js index c935071..3535890 100644 --- a/src/reducers/publicPursuancesReducer.js +++ b/src/reducers/publicPursuancesReducer.js @@ -9,11 +9,6 @@ export default function(state = {}, action) { case 'GET_PUBLIC_PURSUANCES_REJECTED': return state; - case 'POST_PUBLIC_PURSUANCE_FULFILLED': - return Object.assign({}, state, { - [action.payload.id]: action.payload - }); - default: return state; }