<commit_before>import { SEARCH_OBJECT_TYPES, setSearchQuery } from "actions"
import API, { Settings } from "api"
import { gql } from "apollo-boost"
import AppContext from "components/AppContext"
import ConfirmDelete from "components/ConfirmDelete"
import Fieldset from "components/Fieldset"
import GuidedTour from "components/GuidedTour"
import Messages from "components/Messages"
import {
  jumpToTop,
  mapDispatchToProps as pageMapDispatchToProps,
  propTypes as pagePropTypes,
  useBoilerplate
} from "components/Page"
import SavedSearchTable from "components/SavedSearchTable"
import { LAST_WEEK } from "dateUtils"
import { Person, Report } from "models"
import { superUserTour, userTour } from "pages/HopscotchTour"
import PropTypes from "prop-types"
import React, { useState } from "react"
import {
  Button,
  ControlLabel,
  FormControl,
  FormGroup,
  Grid,
  Row
} from "react-bootstrap"
import { connect } from "react-redux"
import { withRouter } from "react-router-dom"
import { deserializeQueryParams } from "searchUtils"

const GQL_GET_SAVED_SEARCHES = gql`
  query {
    savedSearches: mySearches {
      uuid
      name
      objectType
      query
    }
  }
`

const GQL_DELETE_SAVED_SEARCH = gql`
  mutation($uuid: String!) {
    deleteSavedSearch(uuid: $uuid)
  }
`

const GQL_GET_REPORT_COUNT = gql`
  query($reportQuery: ReportSearchQueryInput) {
    reportList(query: $reportQuery) {
      totalCount
    }
  }
`

const HomeTile = props => {
  const { query, setSearchQuery, history } = props
  const reportQuery = Object.assign({}, query.query, {
    // we're only interested in the totalCount, so just get at most one report
    pageSize: 1
  })
  const { loading, error, data } = API.useApiQuery(GQL_GET_REPORT_COUNT, {
    reportQuery
  })
  const { done, result } = useBoilerplate({
    loading,
    error,
    ...props
  })

  const totalCount = data && data.reportList && data.reportList.totalCount
  return (
    <Button
      bsStyle="link"
      onClick={event => onClickDashboard(query, event)}
      className="home-tile"
    >
      {(done &&
{result}
) ||
{totalCount}
}
      {query.title}
    </Button>
  )

  function onClickDashboard(queryDetails, event) {
    deserializeQueryParams(
      SEARCH_OBJECT_TYPES.REPORTS,
      queryDetails.query,
      deserializeCallback
    )
    event.preventDefault()
    event.stopPropagation()
  }

  function deserializeCallback(objectType, filters, text) {
    // We update the Redux state
    setSearchQuery({
      objectType: objectType,
      filters: filters,
      text: text
    })
    history.push("/search")
  }
}

HomeTile.propTypes = {
  query: PropTypes.object.isRequired,
  setSearchQuery: PropTypes.func.isRequired,
  history: PropTypes.object.isRequired
}

const HomeTiles = props => {
  const { currentUser, setSearchQuery, history } = props
  // queries will contain the queries that will show up on the home tiles
  // Based on the users role. They are all report searches
  const queries = getQueriesForUser(currentUser)

  return (
    <Grid fluid>
      <Row>
        {queries.map((query, index) => (
          <HomeTile
            key={index}
            query={query}
            setSearchQuery={setSearchQuery}
            history={history}
          />
        ))}
      </Row>
    </Grid>
  )

  function getQueriesForUser(currentUser) {
    if (currentUser.isAdmin()) {
      return adminQueries(currentUser)
    } else if (currentUser.position && currentUser.position.isApprover) {
      return approverQueries(currentUser)
    } else {
      return advisorQueries(currentUser)
    }
  }

  function adminQueries(currentUser) {
    return [
      allDraft(),
      allPending(),
      pendingMe(currentUser),
      allPlanned(),
      mySensitiveInfo(),
      allApproved()
    ]
  }

  function approverQueries(currentUser) {
    return [
      myDraft(currentUser),
      pendingMe(currentUser),
      myOrgRecent(currentUser),
      myOrgFuture(currentUser),
      mySensitiveInfo()
    ]
  }

  function advisorQueries(currentUser) {
    return [
      myDraft(currentUser),
      myPending(currentUser),
      myOrgRecent(currentUser),
      myOrgFuture(currentUser),
      mySensitiveInfo()
    ]
  }

  function allDraft() {
    return {
      title: "All draft reports",
      query: { state: [Report.STATE.DRAFT, Report.STATE.REJECTED] }
    }
  }

  function myDraft(currentUser) {
    return {
      title: "My draft reports",
      query: {
        state: [Report.STATE.DRAFT, Report.STATE.REJECTED],
        authorUuid: currentUser.uuid
      }
    }
  }

  function myPending(currentUser) {
    return {
      title: "My reports pending approval",
      query: {
        authorUuid: currentUser.uuid,
        state: [Report.STATE.PENDING_APPROVAL]
      }
    }
  }

  function pendingMe(currentUser) {
    return {
      title: "Reports pending my approval",
      query: { pendingApprovalOf: currentUser.uuid }
    }
  }

  function allPending() {
    return {
      title: "All reports pending approval",
      query: { state: [Report.STATE.PENDING_APPROVAL] }
    }
  }

  function allApproved() {
    return {
      title: "All approved reports",
      query: {
        state: [Report.STATE.APPROVED],
        sortBy: "UPDATED_AT",
        sortOrder: "ASC"
      }
    }
  }

  function myOrgRecent(currentUser) {
    if (!currentUser.position || !currentUser.position.organization) {
      return { query: {} }
    }
    return {
      title:
        currentUser.position.organization.shortName +
        "'s reports in the last 7 days",
      query: {
        orgUuid: currentUser.position.organization.uuid,
        includeOrgChildren: false,
        createdAtStart: LAST_WEEK,
        state: [
          Report.STATE.PUBLISHED,
          Report.STATE.CANCELLED,
          Report.STATE.PENDING_APPROVAL
        ]
      }
    }
  }

  function myOrgFuture(currentUser) {
    if (!currentUser.position || !currentUser.position.organization) {
      return { query: {} }
    }
    return {
      title:
        currentUser.position.organization.shortName + "'s planned engagements",
      query: {
        orgUuid: currentUser.position.organization.uuid,
        includeOrgChildren: false,
        state: [Report.STATE.APPROVED, Report.STATE.PUBLISHED],
        engagementStatus: Report.ENGAGEMENT_STATUS.FUTURE,
        sortOrder: "ASC"
      }
    }
  }

  function allPlanned() {
    return {
      title: "All planned engagements",
      query: {
        state: [Report.STATE.APPROVED, Report.STATE.PUBLISHED],
        engagementStatus: Report.ENGAGEMENT_STATUS.FUTURE,
        sortOrder: "ASC"
      }
    }
  }

  function mySensitiveInfo() {
    return {
      title: "Reports with sensitive information",
      query: { state: [Report.STATE.PUBLISHED], sensitiveInfo: true }
    }
  }
}

HomeTiles.propTypes = {
  ...pagePropTypes,
  setSearchQuery: PropTypes.func.isRequired,
  currentUser: PropTypes.instanceOf(Person)
}

const SavedSearches = props => {
  const [stateError, setStateError] = useState(null)
  const [selectedSearch, setSelectedSearch] = useState(null)
  const { loading, error, data, refetch } = API.useApiQuery(
    GQL_GET_SAVED_SEARCHES
  )
  const { done, result } = useBoilerplate({
    loading,
    error,
    ...props
  })
  if (done) {
    return result
  }

  let savedSearches = []
  if (data) {
    savedSearches = data.savedSearches
    if (savedSearches && savedSearches.length > 0 && !selectedSearch) {
      setSelectedSearch(savedSearches[0])
    }
  }

  return (
    <>
      <Messages error={stateError} />
      <FormGroup controlId="savedSearchSelect">
        <ControlLabel>Select a saved search</ControlLabel>
        <FormControl componentClass="select" onChange={onSaveSearchSelect}>
          {savedSearches &&
            savedSearches.map(savedSearch => (
              <option value={savedSearch.uuid} key={savedSearch.uuid}>
                {savedSearch.name}
              </option>
            ))}
        </FormControl>
      </FormGroup>

      {selectedSearch && (

            <Button style={{ marginRight: 12 }} onClick={showSearch}>
              Show Search
            </Button>
            <ConfirmDelete
              onConfirmDelete={onConfirmDelete}
              objectType="search"
              objectDisplay={selectedSearch.name}
              bsStyle="danger"
              buttonLabel="Delete Search"
            />

          <SavedSearchTable search={selectedSearch} />

      )}
    </>
  )

  function onSaveSearchSelect(event) {
    const uuid = event && event.target ? event.target.value : event
    const search = savedSearches.find(el => el.uuid === uuid)
    setSelectedSearch(search)
  }

  function showSearch() {
    if (selectedSearch) {
      const objType = SEARCH_OBJECT_TYPES[selectedSearch.objectType]
      const queryParams = JSON.parse(selectedSearch.query)
      deserializeQueryParams(objType, queryParams, deserializeCallback)
    }
  }

  function deserializeCallback(objectType, filters, text) {
    // We update the Redux state
    props.setSearchQuery({
      objectType: objectType,
      filters: filters,
      text: text
    })
    props.history.push("/search")
  }

  function onConfirmDelete() {
    return API.mutation(GQL_DELETE_SAVED_SEARCH, { uuid: selectedSearch.uuid })
      .then(data => {
        setSelectedSearch(undefined)
        refetch()
      })
      .catch(error => {
        setStateError(error)
        jumpToTop()
      })
  }
}

SavedSearches.propTypes = {
  setSearchQuery: PropTypes.func.isRequired,
  ...pagePropTypes
}

const BaseHome = props => {
  const { currentUser } = props
  const stateSuccess = props.location.state && props.location.state.success
  const alertStyle = { top: 132, marginBottom: "1rem", textAlign: "center" }
  const supportEmail = Settings.SUPPORT_EMAIL_ADDR
  const supportEmailMessage = supportEmail ? `at ${supportEmail}` : ""

  return (

        <GuidedTour
          title="Take a guided tour of the home page."
          tour={currentUser.isSuperUser() ? superUserTour : userTour}
          autostart={
            localStorage.newUser === "true" &&
            localStorage.hasSeenHomeTour !== "true"
          }
          onEnd={() => (localStorage.hasSeenHomeTour = "true")}
        />

      {!currentUser.hasAssignedPosition() && (

          You are not assigned to a {Settings.fields.advisor.position.name}{" "}
          position.

          Please contact your organization's super user(s) to assign you to a{" "}
          {Settings.fields.advisor.position.name} position.

          If you are unsure, you can also contact the support team{" "}
          {supportEmailMessage}.

      )}
      {currentUser.hasAssignedPosition() && !currentUser.hasActivePosition() && (

          Your {Settings.fields.advisor.position.name} position has an inactive
          status.

          Please contact your organization's super users to change your position
          to an active status.

          If you are unsure, you can also contact the support team{" "}
          {supportEmailMessage}.

      )}

      <Messages success={stateSuccess} />

      <Fieldset className="home-tile-row" title="My ANET snapshot">
        <HomeTiles {...props} />
      </Fieldset>

      <Fieldset title="Saved searches">
        <SavedSearches {...props} />
      </Fieldset>

  )
}

BaseHome.propTypes = {
  setSearchQuery: PropTypes.func.isRequired,
  currentUser: PropTypes.instanceOf(Person),
  ...pagePropTypes
}

const mapDispatchToProps = (dispatch, ownProps) => {
  const pageDispatchToProps = pageMapDispatchToProps(dispatch, ownProps)
  return {
    setSearchQuery: searchQuery => dispatch(setSearchQuery(searchQuery)),
    ...pageDispatchToProps
  }
}

const Home = props => (
  <AppContext.Consumer>
    {context => <BaseHome currentUser={context.currentUser} {...props} />}
  </AppContext.Consumer>
)

export default connect(
  null,
  mapDispatchToProps
)(withRouter(Home))
<commit_msg>Just use null<commit_after>import { SEARCH_OBJECT_TYPES, setSearchQuery } from "actions"
import API, { Settings } from "api"
import { gql } from "apollo-boost"
import AppContext from "components/AppContext"
import ConfirmDelete from "components/ConfirmDelete"
import Fieldset from "components/Fieldset"
import GuidedTour from "components/GuidedTour"
import Messages from "components/Messages"
import {
  jumpToTop,
  mapDispatchToProps as pageMapDispatchToProps,
  propTypes as pagePropTypes,
  useBoilerplate
} from "components/Page"
import SavedSearchTable from "components/SavedSearchTable"
import { LAST_WEEK } from "dateUtils"
import { Person, Report } from "models"
import { superUserTour, userTour } from "pages/HopscotchTour"
import PropTypes from "prop-types"
import React, { useState } from "react"
import {
  Button,
  ControlLabel,
  FormControl,
  FormGroup,
  Grid,
  Row
} from "react-bootstrap"
import { connect } from "react-redux"
import { withRouter } from "react-router-dom"
import { deserializeQueryParams } from "searchUtils"

const GQL_GET_SAVED_SEARCHES = gql`
  query {
    savedSearches: mySearches {
      uuid
      name
      objectType
      query
    }
  }
`

const GQL_DELETE_SAVED_SEARCH = gql`
  mutation($uuid: String!) {
    deleteSavedSearch(uuid: $uuid)
  }
`

const GQL_GET_REPORT_COUNT = gql`
  query($reportQuery: ReportSearchQueryInput) {
    reportList(query: $reportQuery) {
      totalCount
    }
  }
`

const HomeTile = props => {
  const { query, setSearchQuery, history } = props
  const reportQuery = Object.assign({}, query.query, {
    // we're only interested in the totalCount, so just get at most one report
    pageSize: 1
  })
  const { loading, error, data } = API.useApiQuery(GQL_GET_REPORT_COUNT, {
    reportQuery
  })
  const { done, result } = useBoilerplate({
    loading,
    error,
    ...props
  })

  const totalCount = data && data.reportList && data.reportList.totalCount
  return (
    <Button
      bsStyle="link"
      onClick={event => onClickDashboard(query, event)}
      className="home-tile"
    >
      {(done &&
{result}
) ||
{totalCount}
}
      {query.title}
    </Button>
  )

  function onClickDashboard(queryDetails, event) {
    deserializeQueryParams(
      SEARCH_OBJECT_TYPES.REPORTS,
      queryDetails.query,
      deserializeCallback
    )
    event.preventDefault()
    event.stopPropagation()
  }

  function deserializeCallback(objectType, filters, text) {
    // We update the Redux state
    setSearchQuery({
      objectType: objectType,
      filters: filters,
      text: text
    })
    history.push("/search")
  }
}

HomeTile.propTypes = {
  query: PropTypes.object.isRequired,
  setSearchQuery: PropTypes.func.isRequired,
  history: PropTypes.object.isRequired
}

const HomeTiles = props => {
  const { currentUser, setSearchQuery, history } = props
  // queries will contain the queries that will show up on the home tiles
  // Based on the users role. They are all report searches
  const queries = getQueriesForUser(currentUser)

  return (
    <Grid fluid>
      <Row>
        {queries.map((query, index) => (
          <HomeTile
            key={index}
            query={query}
            setSearchQuery={setSearchQuery}
            history={history}
          />
        ))}
      </Row>
    </Grid>
  )

  function getQueriesForUser(currentUser) {
    if (currentUser.isAdmin()) {
      return adminQueries(currentUser)
    } else if (currentUser.position && currentUser.position.isApprover) {
      return approverQueries(currentUser)
    } else {
      return advisorQueries(currentUser)
    }
  }

  function adminQueries(currentUser) {
    return [
      allDraft(),
      allPending(),
      pendingMe(currentUser),
      allPlanned(),
      mySensitiveInfo(),
      allApproved()
    ]
  }

  function approverQueries(currentUser) {
    return [
      myDraft(currentUser),
      pendingMe(currentUser),
      myOrgRecent(currentUser),
      myOrgFuture(currentUser),
      mySensitiveInfo()
    ]
  }

  function advisorQueries(currentUser) {
    return [
      myDraft(currentUser),
      myPending(currentUser),
      myOrgRecent(currentUser),
      myOrgFuture(currentUser),
      mySensitiveInfo()
    ]
  }

  function allDraft() {
    return {
      title: "All draft reports",
      query: { state: [Report.STATE.DRAFT, Report.STATE.REJECTED] }
    }
  }

  function myDraft(currentUser) {
    return {
      title: "My draft reports",
      query: {
        state: [Report.STATE.DRAFT, Report.STATE.REJECTED],
        authorUuid: currentUser.uuid
      }
    }
  }

  function myPending(currentUser) {
    return {
      title: "My reports pending approval",
      query: {
        authorUuid: currentUser.uuid,
        state: [Report.STATE.PENDING_APPROVAL]
      }
    }
  }

  function pendingMe(currentUser) {
    return {
      title: "Reports pending my approval",
      query: { pendingApprovalOf: currentUser.uuid }
    }
  }

  function allPending() {
    return {
      title: "All reports pending approval",
      query: { state: [Report.STATE.PENDING_APPROVAL] }
    }
  }

  function allApproved() {
    return {
      title: "All approved reports",
      query: {
        state: [Report.STATE.APPROVED],
        sortBy: "UPDATED_AT",
        sortOrder: "ASC"
      }
    }
  }

  function myOrgRecent(currentUser) {
    if (!currentUser.position || !currentUser.position.organization) {
      return { query: {} }
    }
    return {
      title:
        currentUser.position.organization.shortName +
        "'s reports in the last 7 days",
      query: {
        orgUuid: currentUser.position.organization.uuid,
        includeOrgChildren: false,
        createdAtStart: LAST_WEEK,
        state: [
          Report.STATE.PUBLISHED,
          Report.STATE.CANCELLED,
          Report.STATE.PENDING_APPROVAL
        ]
      }
    }
  }

  function myOrgFuture(currentUser) {
    if (!currentUser.position || !currentUser.position.organization) {
      return { query: {} }
    }
    return {
      title:
        currentUser.position.organization.shortName + "'s planned engagements",
      query: {
        orgUuid: currentUser.position.organization.uuid,
        includeOrgChildren: false,
        state: [Report.STATE.APPROVED, Report.STATE.PUBLISHED],
        engagementStatus: Report.ENGAGEMENT_STATUS.FUTURE,
        sortOrder: "ASC"
      }
    }
  }

  function allPlanned() {
    return {
      title: "All planned engagements",
      query: {
        state: [Report.STATE.APPROVED, Report.STATE.PUBLISHED],
        engagementStatus: Report.ENGAGEMENT_STATUS.FUTURE,
        sortOrder: "ASC"
      }
    }
  }

  function mySensitiveInfo() {
    return {
      title: "Reports with sensitive information",
      query: { state: [Report.STATE.PUBLISHED], sensitiveInfo: true }
    }
  }
}

HomeTiles.propTypes = {
  ...pagePropTypes,
  setSearchQuery: PropTypes.func.isRequired,
  currentUser: PropTypes.instanceOf(Person)
}

const SavedSearches = props => {
  const [stateError, setStateError] = useState(null)
  const [selectedSearch, setSelectedSearch] = useState(null)
  const { loading, error, data, refetch } = API.useApiQuery(
    GQL_GET_SAVED_SEARCHES
  )
  const { done, result } = useBoilerplate({
    loading,
    error,
    ...props
  })
  if (done) {
    return result
  }

  let savedSearches = []
  if (data) {
    savedSearches = data.savedSearches
    if (savedSearches && savedSearches.length > 0 && !selectedSearch) {
      setSelectedSearch(savedSearches[0])
    }
  }

  return (
    <>
      <Messages error={stateError} />
      <FormGroup controlId="savedSearchSelect">
        <ControlLabel>Select a saved search</ControlLabel>
        <FormControl componentClass="select" onChange={onSaveSearchSelect}>
          {savedSearches &&
            savedSearches.map(savedSearch => (
              <option value={savedSearch.uuid} key={savedSearch.uuid}>
                {savedSearch.name}
              </option>
            ))}
        </FormControl>
      </FormGroup>

      {selectedSearch && (

            <Button style={{ marginRight: 12 }} onClick={showSearch}>
              Show Search
            </Button>
            <ConfirmDelete
              onConfirmDelete={onConfirmDelete}
              objectType="search"
              objectDisplay={selectedSearch.name}
              bsStyle="danger"
              buttonLabel="Delete Search"
            />

          <SavedSearchTable search={selectedSearch} />

      )}
    </>
  )

  function onSaveSearchSelect(event) {
    const uuid = event && event.target ? event.target.value : event
    const search = savedSearches.find(el => el.uuid === uuid)
    setSelectedSearch(search)
  }

  function showSearch() {
    if (selectedSearch) {
      const objType = SEARCH_OBJECT_TYPES[selectedSearch.objectType]
      const queryParams = JSON.parse(selectedSearch.query)
      deserializeQueryParams(objType, queryParams, deserializeCallback)
    }
  }

  function deserializeCallback(objectType, filters, text) {
    // We update the Redux state
    props.setSearchQuery({
      objectType: objectType,
      filters: filters,
      text: text
    })
    props.history.push("/search")
  }

  function onConfirmDelete() {
    return API.mutation(GQL_DELETE_SAVED_SEARCH, { uuid: selectedSearch.uuid })
      .then(data => {
        setSelectedSearch(null)
        refetch()
      })
      .catch(error => {
        setStateError(error)
        jumpToTop()
      })
  }
}

SavedSearches.propTypes = {
  setSearchQuery: PropTypes.func.isRequired,
  ...pagePropTypes
}

const BaseHome = props => {
  const { currentUser } = props
  const stateSuccess = props.location.state && props.location.state.success
  const alertStyle = { top: 132, marginBottom: "1rem", textAlign: "center" }
  const supportEmail = Settings.SUPPORT_EMAIL_ADDR
  const supportEmailMessage = supportEmail ? `at ${supportEmail}` : ""

  return (

        <GuidedTour
          title="Take a guided tour of the home page."
          tour={currentUser.isSuperUser() ? superUserTour : userTour}
          autostart={
            localStorage.newUser === "true" &&
            localStorage.hasSeenHomeTour !== "true"
          }
          onEnd={() => (localStorage.hasSeenHomeTour = "true")}
        />

      {!currentUser.hasAssignedPosition() && (

          You are not assigned to a {Settings.fields.advisor.position.name}{" "}
          position.

          Please contact your organization's super user(s) to assign you to a{" "}
          {Settings.fields.advisor.position.name} position.

          If you are unsure, you can also contact the support team{" "}
          {supportEmailMessage}.

      )}
      {currentUser.hasAssignedPosition() && !currentUser.hasActivePosition() && (

          Your {Settings.fields.advisor.position.name} position has an inactive
          status.

          Please contact your organization's super users to change your position
          to an active status.

          If you are unsure, you can also contact the support team{" "}
          {supportEmailMessage}.

      )}

      <Messages success={stateSuccess} />

      <Fieldset className="home-tile-row" title="My ANET snapshot">
        <HomeTiles {...props} />
      </Fieldset>

      <Fieldset title="Saved searches">
        <SavedSearches {...props} />
      </Fieldset>

  )
}

BaseHome.propTypes = {
  setSearchQuery: PropTypes.func.isRequired,
  currentUser: PropTypes.instanceOf(Person),
  ...pagePropTypes
}

const mapDispatchToProps = (dispatch, ownProps) => {
  const pageDispatchToProps = pageMapDispatchToProps(dispatch, ownProps)
  return {
    setSearchQuery: searchQuery => dispatch(setSearchQuery(searchQuery)),
    ...pageDispatchToProps
  }
}

const Home = props => (
  <AppContext.Consumer>
    {context => <BaseHome currentUser={context.currentUser} {...props} />}
  </AppContext.Consumer>
)

export default connect(
  null,
  mapDispatchToProps
)(withRouter(Home))
<|endoftext|>


<commit_before>django.jQuery(function ($) {
    $(document).ready(function(){
        var status = document.getElementById("id_status");
        var resolution = document.getElementById("id_resolution");

        var savebtn = $('input[name="_save"]');
        var savenewbtn = $('input[name="_saveasnew"]');
        var savecontbtn = $('input[name="_continue"]');

        $('#id_status, #id_resolution').change(function(){
            if (!status.options[status.selectedIndex].defaultSelected || !resolution.options[resolution.selectedIndex].defaultSelected) {
                savebtn.val('Save and email user');
                savenewbtn.val('Save as new and email user');
                savecontbtn.val('Save, email user, and continue editing');
            } else {
                savebtn.val('SAVE');
                savenewbtn.val('Save as new');
                savecontbtn.val('Save and continue editing');
            }
        });

        // Commented out as content team does not want to deal with popup.
        // $('form').submit(function() {
        //     if (!status.options[status.selectedIndex].defaultSelected || !resolution.options[resolution.selectedIndex].defaultSelected) {
        //         var c = confirm("User who sent this errata will receive an email. Do you still want to continue?");
        //         return c;
        //     } else {
        //         return true;
        //     }
        // });
    })
})<commit_msg>Update errata-admin-ui.js<commit_after>django.jQuery(function ($) {
    $(document).ready(function(){
        var status = document.getElementById("id_status");
        var resolution = document.getElementById("id_resolution");

        var savebtn = $('input[name="_save"]');
        var savenewbtn = $('input[name="_saveasnew"]');
        var savecontbtn = $('input[name="_continue"]');

        $('#id_status, #id_resolution').change(function(){
            if ((status.options[status.selectedIndex].value == 'Reviewed' && (resolution.options[status.selectedIndex].value == 'Will Not Fix' || resolution.options[status.selectedIndex].value == 'Duplicate' || resolution.options[status.selectedIndex].value == 'Not An Error' || resolution.options[status.selectedIndex].value == 'Major Book Revision' || resolution.options[status.selectedIndex].value == 'Approved')) || (status.options[status.selectedIndex].value == 'Completed' && resolution.options[status.selectedIndex].value == 'Sent to Customer Support') || (resolution.options[status.selectedIndex].value == 'More Information Requested')) {
                savebtn.val('Save and email user');
                savenewbtn.val('Save as new and email user');
                savecontbtn.val('Save, email user, and continue editing');
            } else {
                savebtn.val('SAVE');
                savenewbtn.val('Save as new');
                savecontbtn.val('Save and continue editing');
            }
        });

        // Commented out as content team does not want to deal with popup.
        // $('form').submit(function() {
        //     if (!status.options[status.selectedIndex].defaultSelected || !resolution.options[resolution.selectedIndex].defaultSelected) {
        //         var c = confirm("User who sent this errata will receive an email. Do you still want to continue?");
        //         return c;
        //     } else {
        //         return true;
        //     }
        // });
    })
})
<|endoftext|>


<commit_before>import fetch from 'isomorphic-fetch';
import Config from '../../server/config';

export const API_URL = (typeof window === 'undefined' || process.env.NODE_ENV === 'test') ?
  process.env.BASE_URL || (`http://localhost:${process.env.PORT || Config.port}/api`) :
  '/api';

export default function callApi(endpoint, method = 'get', body) {
  return fetch(`${API_URL}/${endpoint}`, {
    headers: { 'content-type': 'application/json' },
    method,
    body: JSON.stringify(body),
  })
  .then(response => response.json().then(json => ({ json, response })))
  .then(({ json, response }) => {
    if (!response.ok) {
      return Promise.reject(json);
    }

    return json;
  })
  .then(
    response => response,
    error => error
  );
}
<commit_msg>just returns json res<commit_after>import fetch from 'isomorphic-fetch';
import Config from '../../server/config';

export const API_URL = (typeof window === 'undefined' || process.env.NODE_ENV === 'test') ?
  process.env.BASE_URL || (`http://localhost:${process.env.PORT || Config.port}/api`) :
  '/api';

export default function callApi(endpoint, method = 'get', body) {
  return fetch(`${API_URL}/${endpoint}`, {
    headers: { 'content-type': 'application/json' },
    method,
    body: JSON.stringify(body),
  })
  .then(response => response.json())
};
<|endoftext|>


<commit_before>
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
let isDev = process.env.NODE_ENV === 'develop'; // 是否是开发环境

module.exports = {
  entry: {
    vendor: ['babel-polyfill', 'react', 'react-dom', 'redux', 'react-redux', 'react-router-dom'],
    main: './main/main.js',
    fullPage: './template/fullPage/index.js'
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    // publicPath: '/',
    publicPath: isDev ? 'http://localhost:13333/' : '/',
    filename: 'js/[name].bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(jsx|js)$/,
        exclude: path.resolve(__dirname, '../node_modules/'),
        use: 'babel-loader'
      },
      {
        test: /\.(less|css)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
      }
    ]
  },
  resolve: {
    alias: {
      '@': path.join(__dirname)
    },
    extensions: ['.js', '.jsx']
  },
  devtool: isDev ? 'cheap-module-eval-source-map' : '',
  context: __dirname,
  devServer: {
    hot: true,
    port: 13333,
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    proxy: {
      '/api': {
        target: 'http://localhost:16666/api',
        pathRewrite: {"^/api" : ""}
      },
      '/login': {
        target: 'http://localhost:16666/login',
        pathRewrite: {"^/login" : ""}
      }
    }
  },
  plugins: [
    new OpenBrowserPlugin({ url: `http://${"localhost"}:13333/` }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV':
        isDev ? JSON.stringify('develop') : JSON.stringify('production')
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'js/[name].bundle.js',
      minChunks: Infinity
    }),
    new HtmlWebpackPlugin({
      template: './template/index.html',
      filename: 'index.html',
      chunks: ['vendor', 'main'],
      inject: true,
      miniify:{
        collapseWhitespace:true
      }
    }),
  ]
};

<commit_msg>update<commit_after>
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
let isDev = process.env.NODE_ENV === 'develop'; // 是否是开发环境

module.exports = {
  entry: {
    vendor: ['babel-polyfill', 'react', 'react-dom', 'redux', 'react-redux', 'react-router-dom'],
    main: './main/main.js',
    fullPage: './template/fullPage/index.js'
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    // publicPath: '/',
    publicPath: isDev ? 'http://localhost:13333/' : '/',
    filename: 'js/[name].bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(jsx|js)$/,
        exclude: path.resolve(__dirname, '../node_modules/'),
        use: 'babel-loader'
      },
      {
        test: /\.(less|css)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
      }
    ]
  },
  resolve: {
    alias: {
      '@': path.join(__dirname)
    },
    extensions: ['.js', '.jsx']
  },
  devtool: isDev ? 'cheap-module-eval-source-map' : '',
  context: __dirname,
  devServer: {
    hot: true,
    port: 13333,
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    proxy: {
      '/api': {
        target: 'http://localhost:19999/api',
        pathRewrite: {"^/api" : ""}
      },
      '/login': {
        target: 'http://localhost:19999/login',
        pathRewrite: {"^/login" : ""}
      }
    }
  },
  plugins: [
    new OpenBrowserPlugin({ url: `http://${"localhost"}:13333/` }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV':
        isDev ? JSON.stringify('develop') : JSON.stringify('production')
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'js/[name].bundle.js',
      minChunks: Infinity
    }),
    new HtmlWebpackPlugin({
      template: './template/index.html',
      filename: 'index.html',
      chunks: ['vendor', 'main'],
      inject: true,
      miniify:{
        collapseWhitespace:true
      }
    }),
  ]
};

<|endoftext|>


<commit_before>//	***********************************************
//	WINE LIFE
//	PROFILE WINDOW  - ProfileWindow.js
//
//	MEN+1
//	DAVID WELLS
//	***********************************************

function ProfileWindow(title) {
	var global = require('ui/common/globals');

	// Creates the default window with global color scheme
	var self = global.createWindow(title);

	// User Image field - upper left of view
	// Will be populated with image data from user's account in database.
	var image = Ti.UI.createImageView({

  		height: 100,
  		width: 100,
  		top: 10,
  		left: 10,
  		borderColor: 'black',
		borderWidth: 1,
  		contentMode: 'aspectfill',
  		clipsToBounds: false,
  		image:'/images/user_image.png',
  		layout:'vertical'
	});
	self.add(image)

	// NEW TEST TEXT FIELD
	var userFullName = Ti.UI.createLabel({
  		color: global.colors.dark,
  		font: { fontSize:28 },
  		//shadowColor: '#aaa',
  		//shadowOffset: {x:5, y:5},
  		text: 'User Name',
  		textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
  		top: 10,
  		right: 10,
 		width: 'auto',
 		//height: 25,
	});
	self.add(userFullName)

	var aboutMe = Ti.UI.createLabel({
  		color: global.colors.dark,
  		font: { fontSize:18 },
  		//shadowColor: '#aaa',
  		//shadowOffset: {x:5, y:5},
  		text: 'About Me',
  		textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
  		top: 40 + userFullName.getBottom,
  		right: 10,
 		width: 'auto',
 		//height: 'auto',
	});
	self.add(aboutMe)

	var aboutMe_Text = Ti.UI.createLabel({
  		color: global.colors.dark,
  		font: { fontSize:12 },
  		//shadowColor: '#aaa',
  		//shadowOffset: {x:5, y:5},
  		text: 'About me text goes here... ',
  		textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
  		top: 60,
  		right: 10,
 		width: 'auto',
 		height: 'auto',
	});
	self.add(aboutMe_Text)

	return self;
};

module.exports = ProfileWindow;<commit_msg>Minor Text Edits<commit_after>//	***********************************************
//	WINE LIFE
//	PROFILE WINDOW  - ProfileWindow.js
//
//	MEN+1
//	Programmer:  David Wells
//	***********************************************

function ProfileWindow(title) {
	var global = require('ui/common/globals');

	// Creates the default window with global color scheme
	var self = global.createWindow(title);

	// USER IMAGE - upper left of view
	// Will be populated with image data from user's account in database.
	var image = Ti.UI.createImageView({

  		height: 100,
  		width: 100,
  		top: 10,
  		left: 10,
  		borderColor: 'black',
		borderWidth: 1,
  		contentMode: 'aspectfill',
  		clipsToBounds: false,
  		image:'/images/user_image.png',
  		layout:'vertical'
	});
	self.add(image)

	// NEW TEST TEXT FIELD
	var userFullName = Ti.UI.createLabel({
  		color: global.colors.dark,
  		font: { fontSize:28 },
  		//shadowColor: '#aaa',
  		//shadowOffset: {x:5, y:5},
  		text: 'User Name',
  		textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
  		top: 10,
  		right: 10,
 		width: 'auto',
 		//height: 25,
	});
	self.add(userFullName)

	var aboutMe = Ti.UI.createLabel({
  		color: global.colors.dark,
  		font: { fontSize:18 },
  		//shadowColor: '#aaa',
  		//shadowOffset: {x:5, y:5},
  		text: 'About Me',
  		textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
  		top: 40 + userFullName.getBottom,
  		right: 10,
 		width: 'auto',
 		//height: 'auto',
	});
	self.add(aboutMe)

	var aboutMe_Text = Ti.UI.createLabel({
  		color: global.colors.dark,
  		font: { fontSize:12 },
  		//shadowColor: '#aaa',
  		//shadowOffset: {x:5, y:5},
  		text: 'About me text goes here... ',
  		textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
  		top: 60,
  		right: 10,
 		width: 'auto',
 		height: 'auto',
	});
	self.add(aboutMe_Text)

	return self;
};

module.exports = ProfileWindow;<|endoftext|>


<commit_before>var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// multiple extract instances
var extractCSS = new ExtractTextPlugin('[name].css');
module.exports = [
  {
    entry: {
      app: './js/index.js',
      vendor: ['whatwg-fetch']
    },
    output: {
      path: '../app/assets/javascripts/webpack',
      filename: '[name].js'
    },
    module: {
      loaders: [{
        test: /\.(jsx|js)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-2']
        }
      }]
    },
    plugins:[
      // vendor.bundle.jsを入れてないものはエラーになる
      new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"),
    ],
    resolve: {
      extensions: ['', '.js', '.jsx']
    },
    devServer: {
      // dev-serverのcontext root
      contentBase: 'public',
      port: 3001
    }
  },
  {
    entry: {
      app: './css/index.scss'
    },
    output: {
      path: '../app/assets/stylesheets/webpack',
      filename: '[name].css'
    },
    module: {
      loaders: [
        {
          test: /\.css$/,
          loader: extractCSS.extract(['css'])
        },{
          test: /\.scss$/,
          loader: extractCSS.extract(['css','sass'])
        }
      ]
    },
    plugins:[
      extractCSS
    ],
    resolve: {
      extensions: ['', '.css', '.scss']
    }
  }
]
<commit_msg>rename to jsx<commit_after>var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
// multiple extract instances
var extractCSS = new ExtractTextPlugin('[name].css');
module.exports = [
  {
    entry: {
      app: './js/index.jsx',
      vendor: ['whatwg-fetch']
    },
    output: {
      path: '../app/assets/javascripts/webpack',
      filename: '[name].js'
    },
    module: {
      loaders: [{
        test: /\.(jsx|js)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-2']
        }
      }]
    },
    plugins:[
      // vendor.bundle.jsを入れてないものはエラーになる
      new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"),
    ],
    resolve: {
      extensions: ['', '.js', '.jsx']
    },
    devServer: {
      // dev-serverのcontext root
      contentBase: 'public',
      port: 3001
    }
  },
  {
    entry: {
      app: './css/index.scss'
    },
    output: {
      path: '../app/assets/stylesheets/webpack',
      filename: '[name].css'
    },
    module: {
      loaders: [
        {
          test: /\.css$/,
          loader: extractCSS.extract(['css'])
        },{
          test: /\.scss$/,
          loader: extractCSS.extract(['css','sass'])
        }
      ]
    },
    plugins:[
      extractCSS
    ],
    resolve: {
      extensions: ['', '.css', '.scss']
    }
  }
]
<|endoftext|>


<commit_before><commit_msg>Angular.js Tool Chain Tests<commit_after>suite('Angular.js Tool Chain', function () {
    // Test case
});
<|endoftext|>


<commit_before>var CrossTabClient = require('logux-client/cross-tab-client')
var isFirstOlder = require('logux-core/is-first-older')
var createStore = require('redux').createStore

function hackReducer (reducer) {
  return function (state, action) {
    if (action.type === 'logux/state') {
      return action.state
    } else {
      return reducer(state, action)
    }
  }
}

function warnBadUndo (id) {
  var json = JSON.stringify(id)
  console.warn(
    'Logux can not find ' + json + ' to undo it. Maybe action was cleaned.'
  )
}

/**
 * Creates Logux client and connect it to Redux createStore function.
 *
 * @param {object} config Logux Client config.
 * @param {string|Connection} config.server Server URL.
 * @param {string} config.subprotocol Client subprotocol version
 *                                    in SemVer format.
 * @param {number|string|false} config.userId User ID. Pass `false` if no user.
 * @param {any} [config.credentials] Client credentials for authentication.
 * @param {string} [config.prefix="logux"] Prefix for `IndexedDB` database
 *                                         to run multiple Logux instances
 *                                         in the same browser.
 * @param {number} [config.timeout=20000] Timeout in milliseconds
 *                                        to break connection.
 * @param {number} [config.ping=10000] Milliseconds since last message to test
 *                                     connection by sending ping.
 * @param {Store} [config.store] Store to save log data. `IndexedStore`
 *                               by default (if available)
 * @param {number} [config.minDelay=1000] Minimum delay between reconnections.
 * @param {number} [config.maxDelay=5000] Maximum delay between reconnections.
 * @param {number} [config.attempts=Infinity] Maximum reconnection attempts.
 * @param {bool} [config.allowDangerousProtocol=false] Do not show warning
 *                                                     when using 'ws://'
 *                                                     in production.
 * @param {number} [config.dispatchHistory=1000] How many actions, added by
 *                                              {@link LoguxStore#dispatch}
 *                                              will be kept.
 * @param {number} [config.saveStateEvery=50] How often save state to history.
 * @param {checker} [config.onMissedHistory] Callback when there is no history
 *                                           to replay actions accurate.
 *
 * @return {storeCreator} Redux createStore compatible function.
 */
function createLoguxCreator (config) {
  if (!config) config = { }

  var dispatchHistory = config.dispatchHistory || 1000
  delete config.dispatchHistory
  var saveStateEvery = config.saveStateEvery || 50
  delete config.saveStateEvery
  var onMissedHistory = config.onMissedHistory
  delete config.onMissedHistory

  var client = new CrossTabClient(config)
  var log = client.log

  /**
   * Creates Redux store and connect Logux Client to it.
   *
   * @param {function} reducer Redux reducer.
   * @param {any} preloadedState Initial Redux state.
   * @param {function} enhancer Redux middleware.
   *
   * @return {object} Redux store with Logux hacks.
   */
  return function createLoguxStore (reducer, preloadedState, enhancer) {
    var store = createStore(hackReducer(reducer), preloadedState)

    store.client = client
    store.log = log
    var history = { }

    var actionCount = 0
    function saveHistory (meta) {
      actionCount += 1
      if (saveStateEvery === 1 || actionCount % saveStateEvery === 1) {
        history[meta.id.join('\t')] = store.getState()
      }
    }

    var originReplace = store.replaceReducer
    store.replaceReducer = function replaceReducer (newReducer) {
      reducer = newReducer
      return originReplace(hackReducer(newReducer))
    }

    var init
    store.initialize = new Promise(function (resolve) {
      init = resolve
    })

    var prevMeta
    var originDispatch = store.dispatch
    function dispatch (action) {
      var meta = {
        id: log.generateId(),
        tab: store.client.id,
        reasons: ['tab' + store.client.id],
        dispatch: true
      }
      log.add(action, meta)

      prevMeta = meta
      originDispatch(action)
      saveHistory(meta)
    }

    if (enhancer) {
      var middlewared = enhancer(function () {
        return {
          getState: store.getState,
          dispatch: dispatch
        }
      })(reducer, preloadedState)
      store.dispatch = middlewared.dispatch
    } else {
      store.dispatch = dispatch
    }

    store.dispatch.local = function local (action, meta) {
      meta.tab = client.id
      return log.add(action, meta)
    }

    store.dispatch.crossTab = function crossTab (action, meta) {
      return log.add(action, meta)
    }

    store.dispatch.sync = function sync (action, meta) {
      if (!meta) meta = { }
      if (!meta.reasons) meta.reasons = []

      meta.sync = true
      meta.reasons.push('waitForSync')

      return log.add(action, meta)
    }

    var now = log.generateId()
    var started = { id: now, time: now[0] }

    function replaceState (state, actions) {
      var newState = actions.reduceRight(function (prev, i) {
        var changed = reducer(prev, i[0])
        if (history[i[1]]) history[i[1]] = changed
        return changed
      }, state)
      originDispatch({ type: 'logux/state', state: newState })
    }

    var replaying
    function replay (actionId, replayIsSafe) {
      var until = actionId.join('\t')

      var ignore = { }
      var actions = []
      var replayed = false
      var newAction
      var collecting = true

      replaying = new Promise(function (resolve) {
        log.each(function (action, meta) {
          if (meta.tab && meta.tab !== client.id) return true
          var id = meta.id.join('\t')

          if (collecting || !history[id]) {
            if (action.type === 'logux/undo') {
              ignore[action.id.join('\t')] = true
              return true
            }

            if (!ignore[id]) actions.push([action, id])
            if (id === until) {
              newAction = action
              collecting = false
            }

            return true
          } else {
            replayed = true
            replaceState(history[id], actions)
            return false
          }
        }).then(function () {
          if (!replayed) {
            if (onMissedHistory) onMissedHistory(newAction)

            var full
            if (replayIsSafe) {
              full = actions
            } else {
              full = actions.slice(0)
              while (actions.length > 0) {
                var last = actions[actions.length - 1]
                actions.pop()
                if (history[last[1]]) {
                  replayed = true
                  replaceState(history[last[1]], actions.concat([
                    [newAction, until]
                  ]))
                  break
                }
              }
            }

            if (!replayed) {
              replaceState(preloadedState, full)
            }
          }

          replaying = false
          resolve()
        })
      })

      return replaying
    }

    log.on('preadd', function (action, meta) {
      if (action.type === 'logux/undo' && meta.reasons.length === 0) {
        meta.reasons.push('reasonsLoading')
      }
      if (!isFirstOlder(prevMeta, meta) && meta.reasons.length === 0) {
        meta.reasons.push('replay')
      }
    })

    var wait = { }

    function process (action, meta, replayIsSafe) {
      if (replaying) {
        var key = meta.id.join('\t')
        wait[key] = true
        replaying.then(function () {
          if (wait[key]) {
            process(action, meta, replayIsSafe)
            delete wait[key]
          }
        })
        return
      }
      if (action.type === 'logux/undo') {
        var reasons = meta.reasons
        log.byId(action.id).then(function (result) {
          if (result[0]) {
            if (reasons.length === 1 && reasons[0] === 'reasonsLoading') {
              log.changeMeta(meta.id, { reasons: result[1].reasons })
            }
            delete history[action.id.join('\t')]
            replay(action.id)
          } else {
            log.changeMeta(meta.id, { reasons: [] })
            warnBadUndo(action.id)
          }
        })
      } else if (isFirstOlder(prevMeta, meta)) {
        prevMeta = meta
        originDispatch(action)
        if (meta.added) saveHistory(meta)
      } else {
        replay(meta.id, replayIsSafe).then(function () {
          if (meta.reasons.indexOf('replay') !== -1) {
            log.changeMeta(meta.id, {
              reasons: meta.reasons.filter(function (i) {
                return i !== 'replay'
              })
            })
          }
        })
      }
    }

    var lastAdded = 0
    var dispatchCalls = 0
    client.on('add', function (action, meta) {
      if (meta.added > lastAdded) lastAdded = meta.added
      if (meta.dispatch) {
        dispatchCalls += 1
        if (lastAdded > dispatchHistory && dispatchCalls % 25 === 0) {
          log.removeReason('tab' + store.client.id, {
            maxAdded: lastAdded - dispatchHistory
          })
        }
        return
      }

      process(action, meta, isFirstOlder(meta, started))
    })

    client.on('clean', function (action, meta) {
      var key = meta.id.join('\t')
      delete wait[key]
      delete history[key]
    })

    client.sync.on('state', function () {
      if (client.sync.state === 'synchronized') {
        log.removeReason('waitForSync', { maxAdded: client.sync.lastSent })
      }
    })

    var previous = []
    var ignores = { }
    log.each(function (action, meta) {
      if (!meta.tab) {
        if (action.type === 'logux/undo') {
          ignores[action.id.join('\t')] = true
        } else if (!ignores[meta.id.join('\t')]) {
          previous.push([action, meta])
        }
      }
    }).then(function () {
      if (previous.length > 0) {
        previous.forEach(function (i) {
          process(i[0], i[1], true)
        })
      }

      init()
    })

    return store
  }
}

module.exports = createLoguxCreator

/**
 * @callback storeCreator
 * @param {Function} reducer A function that returns the next state tree,
 *                           given the current state tree and the action
 *                           to handle.
 * @param {any} [preloadedState] The initial state.
 * @param {Function} [enhancer] The store enhancer.
 * @return {LoguxStore} Redux store with Logux extensions.
 */

/**
 * @callback checker
 * @param {Action} action The new action.
 */

/**
 * Redux store with Logux extensions.
 * @name LoguxStore
 * @class
 */
/**
 * Logux synchronization client.
 *
 * @name client
 * @type {CrossTabClient}
 * @memberof LoguxStore#
 */
/**
 * The Logux log.
 *
 * @name log
 * @type {Log}
 * @memberof LoguxStore#
 */
/**
 * Reads the state tree managed by the store.
 *
 * @return {any} The current state tree of your application.
 *
 * @name getState
 * @function
 * @memberof LoguxStore#
 */
/**
 * Adds a store change listener.
 *
 * @param {Function} listener A callback to be invoked on every new action.
 *
 * @returns {Function} A function to remove this change listener.
 *
 * @name subscribe
 * @function
 * @memberof LoguxStore#
 */
/**
 * Add action to log with Redux compatible API.
 *
 * You should use it only in legacy code.
 * There is noway to set metadata in this method.
 * As result there is no way to cleaning control.
 *
 * Use {@link dispatchLocal}, {@link dispatchCrossTab} or {@link dispatchSync}
 * instead.
 *
 * @param {Object} action A plain object representing “what changed”.
 *
 * @return {Object} For convenience, the same action object you dispatched.
 *
 * @property {dispatchLocal} local Add sync action to log and update
 *                                 store state. This action will be visible
 *                                 only for current tab.
 * @property {dispatchCrossTab} crossTab Add sync action to log and update
 *                                       store state. This action will be
 *                                       visible for all tabs.
 * @property {dispatchSync} sync Add sync action to log and update store state.
 *                               This action will be visible for server
 *                               and all browser tabs.
 *
 * @name dispatch
 * @function
 * @memberof LoguxStore#
 */
/**
 * Replaces the reducer currently used by the store to calculate the state.
 *
 * @param {Function} nextReducer The reducer for the store to use instead.
 *
 * @return {void}
 *
 * @name replaceReducer
 * @function
 * @memberof LoguxStore#
 */
/**
 * Interoperability point for observable/reactive libraries.
 *
 * @returns {observable} A minimal observable of state changes.
 *
 * @name observable
 * @function
 * @memberof LoguxStore#
 */

/**
 * Add local action to log and update store state.
 * This action will be visible only for current tab.
 *
 * @param {Action} action The new action.
 * @param {Meta} meta Action’s metadata.
 * @param {string[]} meta.reasons Code of reasons, why action should
 *                                be kept in log.
 *
 * @return {Promise} Promise when action will be saved to the log.
 *
 * @example
 * store.dispatch.local(
 *   { type: 'OPEN_MENU' },
 *   { reasons: ['lastMenu'] }
 * ).then(meta => {
 *   store.log.removeReason('lastMenu', { maxAdded: meta.added - 1 })
 * })
 *
 * @callback dispatchLocal
 */
/**
 * Add cross-tab action to log and update store state.
 * This action will be visible only for all tabs.
 *
 * @param {Action} action The new action.
 * @param {Meta} meta Action’s metadata.
 * @param {string[]} meta.reasons Code of reasons, why action should
 *                                be kept in log.
 *
 * @return {Promise} Promise when action will be saved to the log.
 *
 * @example
 * store.dispatch.crossTab(
 *   { type: 'CHANGE_FAVICON', favicon },
 *   { reasons: ['lastFavicon'] }
 * ).then(meta => {
 *   store.log.removeReason('lastFavicon', { maxAdded: meta.added - 1 })
 * })
 *
 * @callback dispatchCrossTab
 */
/**
 * Add sync action to log and update store state.
 * This action will be visible only for server and all browser tabs.
 *
 * @param {Action} action The new action.
 * @param {Meta} meta Action’s metadata.
 * @param {string[]} meta.reasons Code of reasons, why action should
 *                                be kept in log.
 *
 * @return {Promise} Promise when action will be saved to the log.
 *
 * @example
 * store.dispatch.crossTab(
 *   { type: 'CHANGE_NAME', name },
 *   { reasons: ['lastName'] }
 * ).then(meta => {
 *   store.log.removeReason('lastName', { maxAdded: meta.added - 1 })
 * })
 *
 * @callback dispatchSync
 */
<commit_msg>Add init action on replaying all history<commit_after>var CrossTabClient = require('logux-client/cross-tab-client')
var isFirstOlder = require('logux-core/is-first-older')
var createStore = require('redux').createStore

function hackReducer (reducer) {
  return function (state, action) {
    if (action.type === 'logux/state') {
      return action.state
    } else {
      return reducer(state, action)
    }
  }
}

function warnBadUndo (id) {
  var json = JSON.stringify(id)
  console.warn(
    'Logux can not find ' + json + ' to undo it. Maybe action was cleaned.'
  )
}

/**
 * Creates Logux client and connect it to Redux createStore function.
 *
 * @param {object} config Logux Client config.
 * @param {string|Connection} config.server Server URL.
 * @param {string} config.subprotocol Client subprotocol version
 *                                    in SemVer format.
 * @param {number|string|false} config.userId User ID. Pass `false` if no user.
 * @param {any} [config.credentials] Client credentials for authentication.
 * @param {string} [config.prefix="logux"] Prefix for `IndexedDB` database
 *                                         to run multiple Logux instances
 *                                         in the same browser.
 * @param {number} [config.timeout=20000] Timeout in milliseconds
 *                                        to break connection.
 * @param {number} [config.ping=10000] Milliseconds since last message to test
 *                                     connection by sending ping.
 * @param {Store} [config.store] Store to save log data. `IndexedStore`
 *                               by default (if available)
 * @param {number} [config.minDelay=1000] Minimum delay between reconnections.
 * @param {number} [config.maxDelay=5000] Maximum delay between reconnections.
 * @param {number} [config.attempts=Infinity] Maximum reconnection attempts.
 * @param {bool} [config.allowDangerousProtocol=false] Do not show warning
 *                                                     when using 'ws://'
 *                                                     in production.
 * @param {number} [config.dispatchHistory=1000] How many actions, added by
 *                                              {@link LoguxStore#dispatch}
 *                                              will be kept.
 * @param {number} [config.saveStateEvery=50] How often save state to history.
 * @param {checker} [config.onMissedHistory] Callback when there is no history
 *                                           to replay actions accurate.
 *
 * @return {storeCreator} Redux createStore compatible function.
 */
function createLoguxCreator (config) {
  if (!config) config = { }

  var dispatchHistory = config.dispatchHistory || 1000
  delete config.dispatchHistory
  var saveStateEvery = config.saveStateEvery || 50
  delete config.saveStateEvery
  var onMissedHistory = config.onMissedHistory
  delete config.onMissedHistory

  var client = new CrossTabClient(config)
  var log = client.log

  /**
   * Creates Redux store and connect Logux Client to it.
   *
   * @param {function} reducer Redux reducer.
   * @param {any} preloadedState Initial Redux state.
   * @param {function} enhancer Redux middleware.
   *
   * @return {object} Redux store with Logux hacks.
   */
  return function createLoguxStore (reducer, preloadedState, enhancer) {
    var store = createStore(hackReducer(reducer), preloadedState)

    store.client = client
    store.log = log
    var history = { }

    var actionCount = 0
    function saveHistory (meta) {
      actionCount += 1
      if (saveStateEvery === 1 || actionCount % saveStateEvery === 1) {
        history[meta.id.join('\t')] = store.getState()
      }
    }

    var originReplace = store.replaceReducer
    store.replaceReducer = function replaceReducer (newReducer) {
      reducer = newReducer
      return originReplace(hackReducer(newReducer))
    }

    var init
    store.initialize = new Promise(function (resolve) {
      init = resolve
    })

    var prevMeta
    var originDispatch = store.dispatch
    function dispatch (action) {
      var meta = {
        id: log.generateId(),
        tab: store.client.id,
        reasons: ['tab' + store.client.id],
        dispatch: true
      }
      log.add(action, meta)

      prevMeta = meta
      originDispatch(action)
      saveHistory(meta)
    }

    if (enhancer) {
      var middlewared = enhancer(function () {
        return {
          getState: store.getState,
          dispatch: dispatch
        }
      })(reducer, preloadedState)
      store.dispatch = middlewared.dispatch
    } else {
      store.dispatch = dispatch
    }

    store.dispatch.local = function local (action, meta) {
      meta.tab = client.id
      return log.add(action, meta)
    }

    store.dispatch.crossTab = function crossTab (action, meta) {
      return log.add(action, meta)
    }

    store.dispatch.sync = function sync (action, meta) {
      if (!meta) meta = { }
      if (!meta.reasons) meta.reasons = []

      meta.sync = true
      meta.reasons.push('waitForSync')

      return log.add(action, meta)
    }

    var now = log.generateId()
    var started = { id: now, time: now[0] }

    function replaceState (state, actions) {
      var newState = actions.reduceRight(function (prev, i) {
        var changed = reducer(prev, i[0])
        if (history[i[1]]) history[i[1]] = changed
        return changed
      }, state)
      originDispatch({ type: 'logux/state', state: newState })
    }

    var replaying
    function replay (actionId, replayIsSafe) {
      var until = actionId.join('\t')

      var ignore = { }
      var actions = []
      var replayed = false
      var newAction
      var collecting = true

      replaying = new Promise(function (resolve) {
        log.each(function (action, meta) {
          if (meta.tab && meta.tab !== client.id) return true
          var id = meta.id.join('\t')

          if (collecting || !history[id]) {
            if (action.type === 'logux/undo') {
              ignore[action.id.join('\t')] = true
              return true
            }

            if (!ignore[id]) actions.push([action, id])
            if (id === until) {
              newAction = action
              collecting = false
            }

            return true
          } else {
            replayed = true
            replaceState(history[id], actions)
            return false
          }
        }).then(function () {
          if (!replayed) {
            if (onMissedHistory) onMissedHistory(newAction)

            var full
            if (replayIsSafe) {
              full = actions
            } else {
              full = actions.slice(0)
              while (actions.length > 0) {
                var last = actions[actions.length - 1]
                actions.pop()
                if (history[last[1]]) {
                  replayed = true
                  replaceState(history[last[1]], actions.concat([
                    [newAction, until]
                  ]))
                  break
                }
              }
            }

            if (!replayed) {
              var start = [[{ type: '@@redux/INIT' }]]
              replaceState(preloadedState, start.concat(full))
            }
          }

          replaying = false
          resolve()
        })
      })

      return replaying
    }

    log.on('preadd', function (action, meta) {
      if (action.type === 'logux/undo' && meta.reasons.length === 0) {
        meta.reasons.push('reasonsLoading')
      }
      if (!isFirstOlder(prevMeta, meta) && meta.reasons.length === 0) {
        meta.reasons.push('replay')
      }
    })

    var wait = { }

    function process (action, meta, replayIsSafe) {
      if (replaying) {
        var key = meta.id.join('\t')
        wait[key] = true
        replaying.then(function () {
          if (wait[key]) {
            process(action, meta, replayIsSafe)
            delete wait[key]
          }
        })
        return
      }
      if (action.type === 'logux/undo') {
        var reasons = meta.reasons
        log.byId(action.id).then(function (result) {
          if (result[0]) {
            if (reasons.length === 1 && reasons[0] === 'reasonsLoading') {
              log.changeMeta(meta.id, { reasons: result[1].reasons })
            }
            delete history[action.id.join('\t')]
            replay(action.id)
          } else {
            log.changeMeta(meta.id, { reasons: [] })
            warnBadUndo(action.id)
          }
        })
      } else if (isFirstOlder(prevMeta, meta)) {
        prevMeta = meta
        originDispatch(action)
        if (meta.added) saveHistory(meta)
      } else {
        replay(meta.id, replayIsSafe).then(function () {
          if (meta.reasons.indexOf('replay') !== -1) {
            log.changeMeta(meta.id, {
              reasons: meta.reasons.filter(function (i) {
                return i !== 'replay'
              })
            })
          }
        })
      }
    }

    var lastAdded = 0
    var dispatchCalls = 0
    client.on('add', function (action, meta) {
      if (meta.added > lastAdded) lastAdded = meta.added
      if (meta.dispatch) {
        dispatchCalls += 1
        if (lastAdded > dispatchHistory && dispatchCalls % 25 === 0) {
          log.removeReason('tab' + store.client.id, {
            maxAdded: lastAdded - dispatchHistory
          })
        }
        return
      }

      process(action, meta, isFirstOlder(meta, started))
    })

    client.on('clean', function (action, meta) {
      var key = meta.id.join('\t')
      delete wait[key]
      delete history[key]
    })

    client.sync.on('state', function () {
      if (client.sync.state === 'synchronized') {
        log.removeReason('waitForSync', { maxAdded: client.sync.lastSent })
      }
    })

    var previous = []
    var ignores = { }
    log.each(function (action, meta) {
      if (!meta.tab) {
        if (action.type === 'logux/undo') {
          ignores[action.id.join('\t')] = true
        } else if (!ignores[meta.id.join('\t')]) {
          previous.push([action, meta])
        }
      }
    }).then(function () {
      if (previous.length > 0) {
        previous.forEach(function (i) {
          process(i[0], i[1], true)
        })
      }

      init()
    })

    return store
  }
}

module.exports = createLoguxCreator

/**
 * @callback storeCreator
 * @param {Function} reducer A function that returns the next state tree,
 *                           given the current state tree and the action
 *                           to handle.
 * @param {any} [preloadedState] The initial state.
 * @param {Function} [enhancer] The store enhancer.
 * @return {LoguxStore} Redux store with Logux extensions.
 */

/**
 * @callback checker
 * @param {Action} action The new action.
 */

/**
 * Redux store with Logux extensions.
 * @name LoguxStore
 * @class
 */
/**
 * Logux synchronization client.
 *
 * @name client
 * @type {CrossTabClient}
 * @memberof LoguxStore#
 */
/**
 * The Logux log.
 *
 * @name log
 * @type {Log}
 * @memberof LoguxStore#
 */
/**
 * Reads the state tree managed by the store.
 *
 * @return {any} The current state tree of your application.
 *
 * @name getState
 * @function
 * @memberof LoguxStore#
 */
/**
 * Adds a store change listener.
 *
 * @param {Function} listener A callback to be invoked on every new action.
 *
 * @returns {Function} A function to remove this change listener.
 *
 * @name subscribe
 * @function
 * @memberof LoguxStore#
 */
/**
 * Add action to log with Redux compatible API.
 *
 * You should use it only in legacy code.
 * There is noway to set metadata in this method.
 * As result there is no way to cleaning control.
 *
 * Use {@link dispatchLocal}, {@link dispatchCrossTab} or {@link dispatchSync}
 * instead.
 *
 * @param {Object} action A plain object representing “what changed”.
 *
 * @return {Object} For convenience, the same action object you dispatched.
 *
 * @property {dispatchLocal} local Add sync action to log and update
 *                                 store state. This action will be visible
 *                                 only for current tab.
 * @property {dispatchCrossTab} crossTab Add sync action to log and update
 *                                       store state. This action will be
 *                                       visible for all tabs.
 * @property {dispatchSync} sync Add sync action to log and update store state.
 *                               This action will be visible for server
 *                               and all browser tabs.
 *
 * @name dispatch
 * @function
 * @memberof LoguxStore#
 */
/**
 * Replaces the reducer currently used by the store to calculate the state.
 *
 * @param {Function} nextReducer The reducer for the store to use instead.
 *
 * @return {void}
 *
 * @name replaceReducer
 * @function
 * @memberof LoguxStore#
 */
/**
 * Interoperability point for observable/reactive libraries.
 *
 * @returns {observable} A minimal observable of state changes.
 *
 * @name observable
 * @function
 * @memberof LoguxStore#
 */

/**
 * Add local action to log and update store state.
 * This action will be visible only for current tab.
 *
 * @param {Action} action The new action.
 * @param {Meta} meta Action’s metadata.
 * @param {string[]} meta.reasons Code of reasons, why action should
 *                                be kept in log.
 *
 * @return {Promise} Promise when action will be saved to the log.
 *
 * @example
 * store.dispatch.local(
 *   { type: 'OPEN_MENU' },
 *   { reasons: ['lastMenu'] }
 * ).then(meta => {
 *   store.log.removeReason('lastMenu', { maxAdded: meta.added - 1 })
 * })
 *
 * @callback dispatchLocal
 */
/**
 * Add cross-tab action to log and update store state.
 * This action will be visible only for all tabs.
 *
 * @param {Action} action The new action.
 * @param {Meta} meta Action’s metadata.
 * @param {string[]} meta.reasons Code of reasons, why action should
 *                                be kept in log.
 *
 * @return {Promise} Promise when action will be saved to the log.
 *
 * @example
 * store.dispatch.crossTab(
 *   { type: 'CHANGE_FAVICON', favicon },
 *   { reasons: ['lastFavicon'] }
 * ).then(meta => {
 *   store.log.removeReason('lastFavicon', { maxAdded: meta.added - 1 })
 * })
 *
 * @callback dispatchCrossTab
 */
/**
 * Add sync action to log and update store state.
 * This action will be visible only for server and all browser tabs.
 *
 * @param {Action} action The new action.
 * @param {Meta} meta Action’s metadata.
 * @param {string[]} meta.reasons Code of reasons, why action should
 *                                be kept in log.
 *
 * @return {Promise} Promise when action will be saved to the log.
 *
 * @example
 * store.dispatch.crossTab(
 *   { type: 'CHANGE_NAME', name },
 *   { reasons: ['lastName'] }
 * ).then(meta => {
 *   store.log.removeReason('lastName', { maxAdded: meta.added - 1 })
 * })
 *
 * @callback dispatchSync
 */
<|endoftext|>


<commit_before>import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { map, filter, find, size } from "lodash";
import Theme from "theme";
import { browserHistory } from "react-router";

import {
  firebaseConnect,
  isLoaded,
  pathToJS,
  dataToJS // needed for full list and once
  // orderedToJS, // needed for ordered list
  // populatedDataToJS // needed for populated list
} from "react-redux-firebase";
import CircularProgress from "material-ui/CircularProgress";
import Snackbar from "material-ui/Snackbar";
import RaisedButton from "material-ui/RaisedButton";
import Subheader from "material-ui/Subheader";

import ScoreWithOptions from "../components/ScoreWithOptions";

import classes from "./HomeContainer.scss";

// const populates = [{ child: 'owner', root: 'users', keyProp: 'uid' }]

@firebaseConnect([
  { path: "votes" },
  { path: "optionsNumber" },
  { path: "options", queryParams: ["orderByKey"] }
])
@connect(({ firebase }) => ({
  auth: pathToJS(firebase, "auth"),
  account: pathToJS(firebase, "profile"),
  votes: dataToJS(firebase, "votes"),
  options: dataToJS(firebase, "options"),
  optionsNumber: dataToJS(firebase, "optionsNumber")
}))
export default class Home extends Component {
  static propTypes = {
    votes: PropTypes.oneOfType([
      PropTypes.object, // object if using dataToJS
      PropTypes.array // array if using orderedToJS
    ]),
    options: PropTypes.oneOfType([
      PropTypes.object, // object if using dataToJS
      PropTypes.array // array if using orderedToJS
    ]),
    firebase: PropTypes.shape({
      set: PropTypes.func.isRequired,
      remove: PropTypes.func.isRequired,
      push: PropTypes.func.isRequired,
      database: PropTypes.oneOfType([PropTypes.object, PropTypes.func])
    }),
    auth: PropTypes.shape({
      uid: PropTypes.string
    })
  };

  state = {
    error: null,
    currentVote: {},
    submitting: false
  };

  handleChange = (score, option) => {
    let newVote = { ...this.state.currentVote };
    newVote[score] = option;
    this.setState({
      currentVote: newVote
    });
  };

  handleSubmit = (score, option) => {
    const { auth, firebase, optionsNumber, votes } = this.props;
    const currentVoteSize = size(this.state.currentVote);

    this.setState({ submitting: true });

    const myVotes = votes && votes[auth.uid] ? votes[auth.uid] : {};

    if (size(myVotes) > 0) {
      return this.setState({
        error: "Sorry, you can vote one time only",
        submitting: false
      });
    }

    if (currentVoteSize !== parseInt(optionsNumber)) {
      return this.setState({
        error: "One or more votes are missing",
        submitting: false
      });
    }

    if (!auth || !auth.uid) {
      return this.setState({
        error: "You must be Logged into Toggle Done",
        submitting: false
      });
    }

    return firebase.set(`/votes/${auth.uid}/`, this.state.currentVote, err => {
      if (err) {
        return this.setState({
          error: "ERROR! please try again :-(",
          submitting: false
        });
      }
      this.setState({
        error: "Your votes submitted successfully",
        submitting: false
      });
    });
  };

  handleClean() {
    const { auth, firebase } = this.props;
    return firebase.remove(`/votes/${auth.uid}`).catch(err => {
      console.error("Error cleaning votes: ", err); // eslint-disable-line no-console
      this.setState({ error: "Error cleaning todo" });
      return Promise.reject(err);
    });
  }
  componentWillMount() {
    const { auth } = this.props;
    if (!auth || !auth.uid) {
      browserHistory.push("/login");
    }
  }
  render() {
    const { votes, options, auth, optionsNumber } = this.props;
    const { error, currentVote } = this.state;

    if (!auth || !auth.uid) {
      return <Subheader>Please login</Subheader>;
    }

    const optionsArray = map(options, (option, id) => option);

    const scoresArray = optionsNumber
      ? [...Array(parseInt(optionsNumber))]
      : [];

    return isLoaded(optionsNumber) ? (

        {scoresArray.map((x, i) => (

            <ScoreWithOptions
              key={scoresArray.length - i}
              value={scoresArray.length - i}
              selectedOption={currentVote[scoresArray.length - i]}
              options={optionsArray}
              handleChange={this.handleChange}
              currentVote={currentVote}
            />

        ))}

          {this.state.submitting ? (
            <CircularProgress />
          ) : (
            <RaisedButton
              label="Submit"
              primary={true}
              disabled={this.state.submitting}
              onClick={() => this.handleSubmit()}
            />
          )}

        {error ? (
          <Snackbar
            open={!!error}
            message={error}
            autoHideDuration={4000}
            onRequestClose={() => this.setState({ error: null })}
          />
        ) : null}

    ) : null;
  }
}
<commit_msg>Adding "Thank you for your vote" msg.<commit_after>import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { map, filter, find, size } from "lodash";
import Theme from "theme";
import { browserHistory } from "react-router";

import {
  firebaseConnect,
  isLoaded,
  pathToJS,
  dataToJS // needed for full list and once
  // orderedToJS, // needed for ordered list
  // populatedDataToJS // needed for populated list
} from "react-redux-firebase";
import CircularProgress from "material-ui/CircularProgress";
import Snackbar from "material-ui/Snackbar";
import RaisedButton from "material-ui/RaisedButton";
import Subheader from "material-ui/Subheader";

import ScoreWithOptions from "../components/ScoreWithOptions";

import classes from "./HomeContainer.scss";

// const populates = [{ child: 'owner', root: 'users', keyProp: 'uid' }]

@firebaseConnect([
  { path: "votes" },
  { path: "optionsNumber" },
  { path: "options", queryParams: ["orderByKey"] }
])
@connect(({ firebase }) => ({
  auth: pathToJS(firebase, "auth"),
  account: pathToJS(firebase, "profile"),
  votes: dataToJS(firebase, "votes"),
  options: dataToJS(firebase, "options"),
  optionsNumber: dataToJS(firebase, "optionsNumber")
}))
export default class Home extends Component {
  static propTypes = {
    votes: PropTypes.oneOfType([
      PropTypes.object, // object if using dataToJS
      PropTypes.array // array if using orderedToJS
    ]),
    options: PropTypes.oneOfType([
      PropTypes.object, // object if using dataToJS
      PropTypes.array // array if using orderedToJS
    ]),
    firebase: PropTypes.shape({
      set: PropTypes.func.isRequired,
      remove: PropTypes.func.isRequired,
      push: PropTypes.func.isRequired,
      database: PropTypes.oneOfType([PropTypes.object, PropTypes.func])
    }),
    auth: PropTypes.shape({
      uid: PropTypes.string
    })
  };

  state = {
    error: null,
    currentVote: {},
    submitting: false
  };

  handleChange = (score, option) => {
    let newVote = { ...this.state.currentVote };
    newVote[score] = option;
    this.setState({
      currentVote: newVote
    });
  };

  getUserVotesSize = () => {
    const { votes, auth } = this.props;
    if (!votes || !votes[auth.uid]) {
      return 0;
    }
    const userVotesSize = size(votes[auth.uid]);
    console.log(`Current user votes size: ${userVotesSize}`);
    return userVotesSize;
  };

  handleSubmit = (score, option) => {
    const { auth, firebase, optionsNumber, votes } = this.props;
    const currentVoteSize = size(this.state.currentVote);

    this.setState({ submitting: true });

    if (this.getUserVotesSize() > 0) {
      return this.setState({
        error: "Sorry, you can vote one time only",
        submitting: false
      });
    }

    if (currentVoteSize !== parseInt(optionsNumber)) {
      return this.setState({
        error: "One or more votes are missing",
        submitting: false
      });
    }

    if (!auth || !auth.uid) {
      return this.setState({
        error: "You must be Logged into Toggle Done",
        submitting: false
      });
    }

    return firebase.set(`/votes/${auth.uid}/`, this.state.currentVote, err => {
      if (err) {
        return this.setState({
          error: "ERROR! please try again :-(",
          submitting: false
        });
      }
      this.setState({
        error: "Your votes submitted successfully",
        submitting: false
      });
    });
  };

  handleClean() {
    const { auth, firebase } = this.props;
    return firebase.remove(`/votes/${auth.uid}`).catch(err => {
      console.error("Error cleaning votes: ", err); // eslint-disable-line no-console
      this.setState({ error: "Error cleaning todo" });
      return Promise.reject(err);
    });
  }
  componentWillMount() {
    const { auth } = this.props;
    if (!auth || !auth.uid) {
      browserHistory.push("/login");
    }
  }
  render() {
    const { votes, options, auth, optionsNumber } = this.props;
    const { error, currentVote } = this.state;

    if (!auth || !auth.uid) {
      return
Please login first.
;
    }

    if (this.getUserVotesSize() > 0) {
      return
Thank you for your vote!
;
    }

    const optionsArray = map(options, (option, id) => option);

    const scoresArray = optionsNumber
      ? [...Array(parseInt(optionsNumber))]
      : [];

    return isLoaded(optionsNumber) ? (

        {scoresArray.map((x, i) => (

            <ScoreWithOptions
              key={scoresArray.length - i}
              value={scoresArray.length - i}
              selectedOption={currentVote[scoresArray.length - i]}
              options={optionsArray}
              handleChange={this.handleChange}
              currentVote={currentVote}
            />

        ))}

          {this.state.submitting ? (
            <CircularProgress />
          ) : (
            <RaisedButton
              label="Submit"
              primary={true}
              disabled={this.state.submitting}
              onClick={() => this.handleSubmit()}
            />
          )}

        {error ? (
          <Snackbar
            open={!!error}
            message={error}
            autoHideDuration={4000}
            onRequestClose={() => this.setState({ error: null })}
          />
        ) : null}

    ) : null;
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { inject, observer } from 'mobx-react'
import { runInAction } from 'mobx'
import { Link } from 'mobx-little-router-react'
import styled from 'styled-components'

class ShowsRoute extends Component {
  componentDidMount() {
    const { query } = this.props
    this.onSearch(query.q || 'gundam')
  }

  onSearch = async (query) => {
    const { ShowsStore } = this.props
    const res = await fetch(`https://api.tvmaze.com/search/shows?q=${query}`)
    const data = await res.json()

    runInAction(() => {
      ShowsStore.collection = data.map(({ show }) => show)
    })
  }

  render() {
    const { ShowsStore } = this.props

    return (
      <Container>
        <SearchHeader>
          <SearchInput onChange={ev => this.onSearch(ev.target.value)} defaultValue="gundam" />
        </SearchHeader>
        <SearchResults>
          {ShowsStore.collection.map(show =>
            <Show key={show.id}>
              <CoverImage to={`/shows/${show.id}`} style={{backgroundImage: `url(${show.image && show.image.medium})` }}/>
              <Abstract>
                <Network>{show.network && show.network.name}</Network>
                <ShowName to={`/shows/${show.id}`}>{show.name}</ShowName>
              </Abstract>
            </Show>
          )}
        </SearchResults>
      </Container>
    )
  }
}

const Container = styled.div`
  margin: 0 auto;
  max-width: 900px;
  width: 100%;
`

const SearchResults = styled.div`
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: left;
`

const Show = styled.div`
  width: 30%;
  height: 360px;
  background-color: white;
  margin: 9px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.2);
  position: relative;
  border-radius: 2px;
  overflow: hidden;
`

const Abstract = styled.div`
  padding: 9px;
  position: absolute;
  bottom: 0;
`

const Network = styled.div`
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.8);
  font-size: 12px;
`

const ShowName = styled(Link)`
  color: white;
  line-height: 21px;
  text-decoration: none;
  &:hover {
    text-decoration: underline;
  }
`

const SearchHeader = styled.div`
  margin: 18px auto;
  max-width: 600px;
  width: 100%;
`

const CoverImage = styled(Link)`
  display: block;
  background-size: cover;
  background-position: 50% 50%;
  background-color: #eee;
  box-shadow: 0 1px 8px rgba(0,0,0,0.2) inset;
  width: 100%;
  height: 100%;

  &::after {
    content: "";
    position: absolute;
    bottom: 0;
    background-image: linear-gradient(transparent, rgba(0,0,0,0.8));
    width: 100%;
    height: 50%;
  }
`

const SearchInput = styled.input.attrs({
  type: 'text',
  placeholder: 'Search shows...'
})`
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  border: 0;
  height: 45px;
  border: 1px solid #eee;
  width: 600px;

  font-size: 21px;
  padding: 0 9px;
`

const SearchButton = styled.button`

`

export default inject('ShowsStore')(observer(ShowsRoute))
<commit_msg>Update example to except q query param in shows route.<commit_after>import React, { Component } from 'react'
import { inject, observer } from 'mobx-react'
import { runInAction, extendObservable } from 'mobx'
import { Link } from 'mobx-little-router-react'
import styled from 'styled-components'

class ShowsRoute extends Component {
  constructor(props) {
    super(props)

    extendObservable(this, {
      query: ''
    })
  }

  componentDidMount() {
    const { query } = this.props
    this.onSearch(query.q || 'gundam')
  }

  onSearch = async (query) => {
    const { ShowsStore } = this.props
    const res = await fetch(`https://api.tvmaze.com/search/shows?q=${query}`)
    const data = await res.json()

    runInAction(() => {
      this.query = query
      ShowsStore.collection = data.map(({ show }) => show)
    })
  }

  render() {
    const { ShowsStore } = this.props

    return (
      <Container>
        <SearchHeader>
          <SearchInput onChange={ev => this.onSearch(ev.target.value)} value={this.query} />
        </SearchHeader>
        <SearchResults>
          {ShowsStore.collection.map(show =>
            <Show key={show.id}>
              <CoverImage to={`/shows/${show.id}`} style={{backgroundImage: `url(${show.image && show.image.medium})` }}/>
              <Abstract>
                <Network>{show.network && show.network.name}</Network>
                <ShowName to={`/shows/${show.id}`}>{show.name}</ShowName>
              </Abstract>
            </Show>
          )}
        </SearchResults>
      </Container>
    )
  }
}

const Container = styled.div`
  margin: 0 auto;
  max-width: 900px;
  width: 100%;
`

const SearchResults = styled.div`
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: left;
`

const Show = styled.div`
  width: 30%;
  height: 360px;
  background-color: white;
  margin: 9px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.2);
  position: relative;
  border-radius: 2px;
  overflow: hidden;
`

const Abstract = styled.div`
  padding: 9px;
  position: absolute;
  bottom: 0;
`

const Network = styled.div`
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.8);
  font-size: 12px;
`

const ShowName = styled(Link)`
  color: white;
  line-height: 21px;
  text-decoration: none;
  &:hover {
    text-decoration: underline;
  }
`

const SearchHeader = styled.div`
  margin: 18px auto;
  max-width: 600px;
  width: 100%;
`

const CoverImage = styled(Link)`
  display: block;
  background-size: cover;
  background-position: 50% 50%;
  background-color: #eee;
  box-shadow: 0 1px 8px rgba(0,0,0,0.2) inset;
  width: 100%;
  height: 100%;

  &::after {
    content: "";
    position: absolute;
    bottom: 0;
    background-image: linear-gradient(transparent, rgba(0,0,0,0.8));
    width: 100%;
    height: 50%;
  }
`

const SearchInput = styled.input.attrs({
  type: 'text',
  placeholder: 'Search shows...'
})`
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  border: 0;
  height: 45px;
  border: 1px solid #eee;
  width: 600px;

  font-size: 21px;
  padding: 0 9px;
`

const SearchButton = styled.button`

`

export default inject('ShowsStore')(observer(ShowsRoute))
<|endoftext|>


<commit_before>var crypto = require("crypto"),
    events = require('events'),
    _ = require('underscore')._,
    express = require('express'),
    converter = require('./referenceConverter');

var config = require('./config.json');
var parameters = require('./parameters');

var basePath = "/epayments",
    cancelPath = basePath + "/cancel",
    okPath = basePath + "/ok",
    rejectPath = basePath + "/reject";

var buttonTemplate = _.template(
  '<form id="<%= id %>-payment-form" method="POST" action="<%= paymentUrl %>" class="payment-button">'+
  '<div id="<%= id %>-payment" style="cursor: pointer">' +
  '
<img src="<%= imgPath %>" alt="<%= name %>">
' +
  '
<%= name %>
' +
  '
' +
  '<% _.each(_.pairs(bankParams), function (pair) { %><input name="<%= pair[0] %>" value="<%= pair[1] %>" type="hidden"><% }) %>'+
  '' +
  '</form>'
);

exports.create = function (globalOptions, bankOptions) {
  initLogger(globalOptions);
  parameters.requireParams(globalOptions, ['appHandler', 'hostUrl']);

  var paymentGen = Object.create(events.EventEmitter.prototype);
  var hostOptions = _.clone(globalOptions);
  var banks = setupBanks(bankOptions, globalOptions.hostUrl);

  paymentGen.buildRequestParams = function (bankId, options) {
    var bank = findBank(bankId, banks);
    return requestParams(bank, options);
  };

  paymentGen.paymentButton = function (bankId, options) {
    var bank = findBank(bankId, banks);
    return createButton(bank, options);
  };

  paymentGen.referenceNumbers = converter;
  paymentGen.banks = _.pluck(banks, 'id');

  bindReturnUrlsToHandler(paymentGen, hostOptions.appHandler, banks);
  hostOptions.appHandler.use(express.static(__dirname + '/public'));

  return paymentGen;
};

exports.referenceNumbers = converter;

function initLogger(globalOptions) {
  var logger = require('./logger');
  if(globalOptions.logger){
    logger.setLogger(globalOptions.logger);
  }
}

function requestParams (bank, options) {
  var provider = bank.provider;
  if (provider && bank) {
    var formParams = provider.mapParams(_.extend({}, bank, options));
    var nonEmptyParams = removeIf(formParams, function (k, v) {
      return !v;
    });

    nonEmptyParams[provider.macFormName] =
      macForRequest(provider, nonEmptyParams, bank);

    return nonEmptyParams;
  } else {
    throw new Error("No provider or configuration found for id '" + bank.id + "'.");
  }
}

function createButton (bank, options) {
  var bankParams = requestParams(bank, options);
  var params = _.extend(commonParams(bank), {
    bankParams: bankParams
  });

  return buttonTemplate(params);
}

function setupBanks(bankOptions, hostUrl) {
  var banksWithoutUrls = extendDefaults(bankOptions);
  var bankIds = _.pluck(banksWithoutUrls, 'id');
  var returnUrls = returnUrlConfig(hostUrl, bankIds);
  return addProviders(addReturnUrls(banksWithoutUrls, returnUrls));
}

function addProviders (banks) {
  return _.map(banks, function (bank) {
    return _.extend({}, bank, { provider: require('./providers/'+bank.id) });
  });
}

function macForRequest (provider, providerParams, bankConfig) {
  var paramsForMac = provider.requestMacParams(bankConfig, providerParams);

  return generateMac(paramsForMac, provider.algorithmType(bankConfig), bankConfig.macSeparator);
}

function generateMac(params, algorithmType, separator) {
  var joinedParams = params.join(separator) + separator;
  return crypto.createHash(algorithmType.toLowerCase()).update(joinedParams).digest('hex').toUpperCase();
}

function removeIf (params, condition) {
  var cloned = _.clone(params);
  _.each(_.keys(cloned), function (key) {
    if (condition(key, cloned[key])) {
      delete cloned[key];
    }
  });

  return cloned;
}

function commonParams(bank) {
  return {
    id : bank.id,
    name : bank.name,
    paymentUrl : bank.paymentUrl,
    imgPath: bank.imgPath
  };
}

function findBank (bankId, bankConfigs) {
  return _.find(bankConfigs, function (bank) {
    return bank.id == bankId;
  });
}

function bindReturnUrlsToHandler (eventEmitter, handler, banks) {
  bindOkUrls(handler, banks, function (req, res, bank) {
    var provider = bank.provider;
    var queryData = provider.renameQueryParams(req.query);

    if (_.isEmpty(req.query) || checkMac(bank, req.query, queryData.mac)) {
      eventEmitter.emit('success', req, res, queryData);
    } else {
      eventEmitter.emit('mac-check-failed', req, res, queryData);
    }
  });

  handler.get(cancelPath, function (req, res) {
    eventEmitter.emit('cancel', req, res);
  });

  handler.get(rejectPath, function (req, res) {
    eventEmitter.emit('reject', req, res);
  });
}

function bindOkUrls (handler, banks, okCallback) {
  _.each(banks, function (bank) {
    var callback = function (req, res) {
      okCallback(req, res, bank);
    };

    var okPath = bank.returnUrls.ok.path;
    handler.post(okPath, callback);
    handler.get(okPath, callback);
  });
}

function checkMac (bank, query, expected) {
  var macParams = bank.provider.returnMacParams(bank, query);
  var mac = generateMac(macParams, bank.provider.algorithmType(bank), bank.macSeparator);
  return mac === expected;
}

function returnUrlConfig (hostUrl, bankIds) {
  var okUrls = {};
  _.each(bankIds, function (bankId) {
    var bankPath = okPath + '/' + bankId;
    okUrls[bankId] = {
      url: hostUrl + bankPath,
      path: bankPath
    };
  });

  return {
    ok: okUrls,
    cancel: hostUrl + cancelPath,
    reject: hostUrl + rejectPath
  };
}

function extendDefaults (bankOpts) {
  return _.map(config.banks, function (bank) {
    var bankWithDefaults = _.extend({}, config.defaultBank, bank);
    var vendorOpts = _.find(bankOpts, function (bankConf) {
      return bankConf.id == bankWithDefaults.id;
    });

    if (vendorOpts) {
      return _.extend({}, bankWithDefaults, vendorOpts);
    } else {
      return bankWithDefaults;
    }
  });
}

function addReturnUrls (bankConfigs, returnUrls) {
  return _.map(bankConfigs, function (bank) {
    return _.extend({}, bank, {
      returnUrls: {
        reject: returnUrls.reject,
        cancel: returnUrls.cancel,
        ok: returnUrls.ok[bank.id]
      }
    });
  });
}
<commit_msg>filter undefined parameters from mac checking<commit_after>var crypto = require("crypto"),
    events = require('events'),
    _ = require('underscore')._,
    express = require('express'),
    converter = require('./referenceConverter');

var config = require('./config.json');
var parameters = require('./parameters');

var basePath = "/epayments",
    cancelPath = basePath + "/cancel",
    okPath = basePath + "/ok",
    rejectPath = basePath + "/reject";

var buttonTemplate = _.template(
  '<form id="<%= id %>-payment-form" method="POST" action="<%= paymentUrl %>" class="payment-button">'+
  '<div id="<%= id %>-payment" style="cursor: pointer">' +
  '
<img src="<%= imgPath %>" alt="<%= name %>">
' +
  '
<%= name %>
' +
  '
' +
  '<% _.each(_.pairs(bankParams), function (pair) { %><input name="<%= pair[0] %>" value="<%= pair[1] %>" type="hidden"><% }) %>'+
  '' +
  '</form>'
);

exports.create = function (globalOptions, bankOptions) {
  initLogger(globalOptions);
  parameters.requireParams(globalOptions, ['appHandler', 'hostUrl']);

  var paymentGen = Object.create(events.EventEmitter.prototype);
  var hostOptions = _.clone(globalOptions);
  var banks = setupBanks(bankOptions, globalOptions.hostUrl);

  paymentGen.buildRequestParams = function (bankId, options) {
    var bank = findBank(bankId, banks);
    return requestParams(bank, options);
  };

  paymentGen.paymentButton = function (bankId, options) {
    var bank = findBank(bankId, banks);
    return createButton(bank, options);
  };

  paymentGen.referenceNumbers = converter;
  paymentGen.banks = _.pluck(banks, 'id');

  bindReturnUrlsToHandler(paymentGen, hostOptions.appHandler, banks);
  hostOptions.appHandler.use(express.static(__dirname + '/public'));

  return paymentGen;
};

exports.referenceNumbers = converter;

function initLogger(globalOptions) {
  var logger = require('./logger');
  if(globalOptions.logger){
    logger.setLogger(globalOptions.logger);
  }
}

function requestParams (bank, options) {
  var provider = bank.provider;
  if (provider && bank) {
    var formParams = provider.mapParams(_.extend({}, bank, options));
    var nonEmptyParams = removeIf(formParams, function (k, v) {
      return !v;
    });

    nonEmptyParams[provider.macFormName] =
      macForRequest(provider, nonEmptyParams, bank);

    return nonEmptyParams;
  } else {
    throw new Error("No provider or configuration found for id '" + bank.id + "'.");
  }
}

function createButton (bank, options) {
  var bankParams = requestParams(bank, options);
  var params = _.extend(commonParams(bank), {
    bankParams: bankParams
  });

  return buttonTemplate(params);
}

function setupBanks(bankOptions, hostUrl) {
  var banksWithoutUrls = extendDefaults(bankOptions);
  var bankIds = _.pluck(banksWithoutUrls, 'id');
  var returnUrls = returnUrlConfig(hostUrl, bankIds);
  return addProviders(addReturnUrls(banksWithoutUrls, returnUrls));
}

function addProviders (banks) {
  return _.map(banks, function (bank) {
    return _.extend({}, bank, { provider: require('./providers/'+bank.id) });
  });
}

function macForRequest (provider, providerParams, bankConfig) {
  var paramsForMac = provider.requestMacParams(bankConfig, providerParams);

  return generateMac(paramsForMac, provider.algorithmType(bankConfig), bankConfig.macSeparator);
}

function generateMac(params, algorithmType, separator) {
  var joinedParams = params.join(separator) + separator;
  return crypto.createHash(algorithmType.toLowerCase()).update(joinedParams).digest('hex').toUpperCase();
}

function removeIf (params, condition) {
  var cloned = _.clone(params);
  _.each(_.keys(cloned), function (key) {
    if (condition(key, cloned[key])) {
      delete cloned[key];
    }
  });

  return cloned;
}

function commonParams(bank) {
  return {
    id : bank.id,
    name : bank.name,
    paymentUrl : bank.paymentUrl,
    imgPath: bank.imgPath
  };
}

function findBank (bankId, bankConfigs) {
  return _.find(bankConfigs, function (bank) {
    return bank.id == bankId;
  });
}

function bindReturnUrlsToHandler (eventEmitter, handler, banks) {
  bindOkUrls(handler, banks, function (req, res, bank) {
    var provider = bank.provider;
    var queryData = provider.renameQueryParams(req.query);

    if (_.isEmpty(req.query) || checkMac(bank, req.query, queryData.mac)) {
      eventEmitter.emit('success', req, res, queryData);
    } else {
      eventEmitter.emit('mac-check-failed', req, res, queryData);
    }
  });

  handler.get(cancelPath, function (req, res) {
    eventEmitter.emit('cancel', req, res);
  });

  handler.get(rejectPath, function (req, res) {
    eventEmitter.emit('reject', req, res);
  });
}

function bindOkUrls (handler, banks, okCallback) {
  _.each(banks, function (bank) {
    var callback = function (req, res) {
      okCallback(req, res, bank);
    };

    var okPath = bank.returnUrls.ok.path;
    handler.post(okPath, callback);
    handler.get(okPath, callback);
  });
}

function checkMac (bank, query, expected) {
  var macParams = _.filter(bank.provider.returnMacParams(bank, query), function(param){
    return typeof param !== "undefined";
  });
  var mac = generateMac(macParams, bank.provider.algorithmType(bank), bank.macSeparator);
  return mac === expected;
}

function returnUrlConfig (hostUrl, bankIds) {
  var okUrls = {};
  _.each(bankIds, function (bankId) {
    var bankPath = okPath + '/' + bankId;
    okUrls[bankId] = {
      url: hostUrl + bankPath,
      path: bankPath
    };
  });

  return {
    ok: okUrls,
    cancel: hostUrl + cancelPath,
    reject: hostUrl + rejectPath
  };
}

function extendDefaults (bankOpts) {
  return _.map(config.banks, function (bank) {
    var bankWithDefaults = _.extend({}, config.defaultBank, bank);
    var vendorOpts = _.find(bankOpts, function (bankConf) {
      return bankConf.id == bankWithDefaults.id;
    });

    if (vendorOpts) {
      return _.extend({}, bankWithDefaults, vendorOpts);
    } else {
      return bankWithDefaults;
    }
  });
}

function addReturnUrls (bankConfigs, returnUrls) {
  return _.map(bankConfigs, function (bank) {
    return _.extend({}, bank, {
      returnUrls: {
        reject: returnUrls.reject,
        cancel: returnUrls.cancel,
        ok: returnUrls.ok[bank.id]
      }
    });
  });
}
<|endoftext|>