<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import qs from 'query-string';
import Card from '../components/Card';
import HelpBubble from '../components/HelpBubble';
import GenericLoader from '../components/GenericLoader';
import {eventNoteTypeIdForAbsenceSupportMeeting} from '../helpers/PerDistrict';
import {eventNoteTypeTextMini} from '../helpers/eventNoteType';
import {apiFetchJson} from '../helpers/apiFetchJson';

// Show teachers their students who have high absences
// but don't have any notes in Insights recently (eg, haven't been brought up in SST).
class CheckStudentsWithHighAbsences extends React.Component {
  constructor(props) {
    super(props);
    this.fetchStudents = this.fetchStudents.bind(this);
    this.renderStudents = this.renderStudents.bind(this);
  }

  fetchStudents() {
    const {educatorId, limit} = this.props;
    const params = educatorId ? {limit, educator_id: educatorId} : {limit};
    const url = `/api/home/students_with_high_absences_json?${qs.stringify(params)}`;
    return apiFetchJson(url);
  }

  render() {
    return (

        <GenericLoader
          style={styles.card}
          promiseFn={this.fetchStudents}
          render={this.renderStudents} />

    );
  }

  renderStudents(json) {
    const props = {
      limit: json.limit,
      totalStudents: json.total_students,
      studentsWithHighAbsences: json.students_with_high_absences
    };
    return <CheckStudentsWithHighAbsencesView {...props} />;
  }
}
CheckStudentsWithHighAbsences.propTypes = {
  educatorId: PropTypes.number.isRequired,
  limit: PropTypes.number // limit the data returned, not the query itself
};
CheckStudentsWithHighAbsences.defaultProps = {
  limit: 100
};

// Pure UI component.
export class CheckStudentsWithHighAbsencesView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      uiLimit: 4
    };
    this.renderHelpContent = this.renderHelpContent.bind(this);
    this.onMoreStudents = this.onMoreStudents.bind(this);
  }

  onMoreStudents(e) {
    e.preventDefault();
    const {uiLimit} = this.state;
    this.setState({ uiLimit: uiLimit + 4 });
  }

  render() {
    const {studentsWithHighAbsences, totalStudents} = this.props;
    const {uiLimit} = this.state;
    const truncatedStudentsWithHighAbsences = studentsWithHighAbsences.slice(0, uiLimit);

    return (

          Students missing school
          <HelpBubble
            teaser={what does this mean?}
            title="Students missing school"
            content={this.renderHelpContent()} />

        <Card style={{border: 'none'}}>
          {this.renderCopy(totalStudents)}
          {this.renderList(truncatedStudentsWithHighAbsences)}
          {this.renderMore(truncatedStudentsWithHighAbsences)}
        </Card>

    );
  }

  // Branching on singular and plural and zero.
  renderCopy(totalStudents) {
    const now = this.context.nowFn();
    const dateText = now.clone().subtract(45, 'days').format('MMMM Do');
    const sinceEl = Since {dateText}:;
    if (totalStudents === 0) {
      return
There are no students missing school recently who {"haven't"} been mentioned.
;
    } else if (totalStudents === 1) {
      return
There is one student missing school recently who {"hasn't"} been mentioned.  {sinceEl}
;
    } else {
      return
There are {totalStudents} students missing school recently who {"haven't"} been mentioned.  {sinceEl}
;
    }
  }

  renderList(truncatedStudentsWithHighAbsences) {
    if (truncatedStudentsWithHighAbsences.length === 0) return null;
    return (

        {truncatedStudentsWithHighAbsences.map(studentsWithHighAbsences => {
          const {student, count} = studentsWithHighAbsences;
          return (

                <a
                  style={styles.person}
                  href={`/students/${student.id}?column=attendance#absences`}>
                    {student.first_name} {student.last_name}
                   has missed {count} days of school

          );
        })}

    );
  }

  renderMore(truncatedStudentsWithHighAbsences) {
    const {totalStudents, limit, studentsWithHighAbsences} = this.props;

    if (truncatedStudentsWithHighAbsences.length !== studentsWithHighAbsences.length) {
      return
See more
;
    }

    if (studentsWithHighAbsences.length < totalStudents) {
      return
There are {totalStudents} students total.  Start with checking in on these first {limit} students.
;
    }

    return null;
  }

  renderHelpContent() {
    const {districtKey} = this.context;
    const eventNoteTypeId = eventNoteTypeIdForAbsenceSupportMeeting(districtKey);
    const shortMeetingText = eventNoteTypeTextMini(eventNoteTypeId);
    return (

These are all the students that you have access to who have a high number of absences over the last 45 days, but {"haven't"} been mentioned recently.

This means there aren't any notes about them from {shortMeetingText} meetings, parent conversations, or anything else in Student Insights.  The threshold for being included in this list is to have 4 or more absences over the last 45 calendar days.

If you work directly with this student, you could talk with them or reach out to the family.  Or you could connect with a colleague providing support services (eg, SST, attendance officers, counselors, redirect).  If the student in still missing school, attendance contracts might be a next step.

    );
  }
}
CheckStudentsWithHighAbsencesView.contextTypes = {
  districtKey: PropTypes.string.isRequired,
  nowFn: PropTypes.func.isRequired
};
CheckStudentsWithHighAbsencesView.propTypes = {
  limit: PropTypes.number.isRequired,
  totalStudents: PropTypes.number.isRequired,
  studentsWithHighAbsences: PropTypes.arrayOf(PropTypes.shape({
    count: PropTypes.number.isRequired,
    student: PropTypes.shape({
      id: PropTypes.number.isRequired,
      first_name: PropTypes.string.isRequired,
      last_name: PropTypes.string.isRequired,
      grade: PropTypes.string.isRequired,
      house: PropTypes.string
    }).isRequired
  })).isRequired
};

const styles = {
  root: {
    fontSize: 14
  },
  card: {
    margin: 10,
    marginTop: 20,
    border: '1px solid #ccc',
    borderRadius: 3
  },
  helpTeaser: {
    display: 'inline-block',
    paddingLeft: 5,
    paddingRight: 5
  },
  helpContent: {
    margin: 10
  },
  cardTitle: {
    backgroundColor: '#eee',
    padding: 10,
    color: 'black',
    borderBottom: '1px solid #ccc',
    display: 'flex',
    justifyContent: 'space-between'
  },
  person: {
    fontWeight: 'bold'
  },
  line: {
    paddingLeft: 10,
    paddingRight: 10,
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap'
  },
  middleText: {
    display: 'inline-block',
    paddingLeft: 5,
    paddingRight: 5
  }
};

export default CheckStudentsWithHighAbsences;<commit_msg>Update help copy for insight absence box<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import qs from 'query-string';
import Card from '../components/Card';
import HelpBubble from '../components/HelpBubble';
import GenericLoader from '../components/GenericLoader';
import {eventNoteTypeIdForAbsenceSupportMeeting} from '../helpers/PerDistrict';
import {eventNoteTypeTextMini} from '../helpers/eventNoteType';
import {apiFetchJson} from '../helpers/apiFetchJson';

// Show teachers their students who have high absences
// but don't have any notes in Insights recently (eg, haven't been brought up in SST or BBST).
class CheckStudentsWithHighAbsences extends React.Component {
  constructor(props) {
    super(props);
    this.fetchStudents = this.fetchStudents.bind(this);
    this.renderStudents = this.renderStudents.bind(this);
  }

  fetchStudents() {
    const {educatorId, limit} = this.props;
    const params = educatorId ? {limit, educator_id: educatorId} : {limit};
    const url = `/api/home/students_with_high_absences_json?${qs.stringify(params)}`;
    return apiFetchJson(url);
  }

  render() {
    return (

        <GenericLoader
          style={styles.card}
          promiseFn={this.fetchStudents}
          render={this.renderStudents} />

    );
  }

  renderStudents(json) {
    const props = {
      limit: json.limit,
      totalStudents: json.total_students,
      studentsWithHighAbsences: json.students_with_high_absences
    };
    return <CheckStudentsWithHighAbsencesView {...props} />;
  }
}
CheckStudentsWithHighAbsences.propTypes = {
  educatorId: PropTypes.number.isRequired,
  limit: PropTypes.number // limit the data returned, not the query itself
};
CheckStudentsWithHighAbsences.defaultProps = {
  limit: 100
};

// Pure UI component.
export class CheckStudentsWithHighAbsencesView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      uiLimit: 4
    };
    this.renderHelpContent = this.renderHelpContent.bind(this);
    this.onMoreStudents = this.onMoreStudents.bind(this);
  }

  onMoreStudents(e) {
    e.preventDefault();
    const {uiLimit} = this.state;
    this.setState({ uiLimit: uiLimit + 4 });
  }

  render() {
    const {studentsWithHighAbsences, totalStudents} = this.props;
    const {uiLimit} = this.state;
    const truncatedStudentsWithHighAbsences = studentsWithHighAbsences.slice(0, uiLimit);

    return (

          Students missing school
          <HelpBubble
            teaser={what does this mean?}
            title="Students missing school"
            content={this.renderHelpContent()} />

        <Card style={{border: 'none'}}>
          {this.renderCopy(totalStudents)}
          {this.renderList(truncatedStudentsWithHighAbsences)}
          {this.renderMore(truncatedStudentsWithHighAbsences)}
        </Card>

    );
  }

  // Branching on singular and plural and zero.
  renderCopy(totalStudents) {
    const now = this.context.nowFn();
    const dateText = now.clone().subtract(45, 'days').format('MMMM Do');
    const sinceEl = Since {dateText}:;
    if (totalStudents === 0) {
      return
There are no students missing school recently who {"haven't"} been mentioned.
;
    } else if (totalStudents === 1) {
      return
There is one student missing school recently who {"hasn't"} been mentioned.  {sinceEl}
;
    } else {
      return
There are {totalStudents} students missing school recently who {"haven't"} been mentioned.  {sinceEl}
;
    }
  }

  renderList(truncatedStudentsWithHighAbsences) {
    if (truncatedStudentsWithHighAbsences.length === 0) return null;
    return (

        {truncatedStudentsWithHighAbsences.map(studentsWithHighAbsences => {
          const {student, count} = studentsWithHighAbsences;
          return (

                <a
                  style={styles.person}
                  href={`/students/${student.id}?column=attendance#absences`}>
                    {student.first_name} {student.last_name}
                   has missed {count} days of school

          );
        })}

    );
  }

  renderMore(truncatedStudentsWithHighAbsences) {
    const {totalStudents, limit, studentsWithHighAbsences} = this.props;

    if (truncatedStudentsWithHighAbsences.length !== studentsWithHighAbsences.length) {
      return
See more
;
    }

    if (studentsWithHighAbsences.length < totalStudents) {
      return
There are {totalStudents} students total.  Start with checking in on these first {limit} students.
;
    }

    return null;
  }

  renderHelpContent() {
    const {districtKey} = this.context;
    const eventNoteTypeId = eventNoteTypeIdForAbsenceSupportMeeting(districtKey);
    const shortMeetingText = eventNoteTypeTextMini(eventNoteTypeId);
    return (

These are all the students that you have access to who have a high number of absences over the last 45 days, but {"haven't"} been mentioned recently.

This means there aren't any notes about them from {shortMeetingText} meetings, parent conversations, or anything else in Student Insights.  The threshold for being included in this list is to have 4 or more absences over the last 45 calendar days.

If you work directly with this student, you could talk with them or reach out to the family.  Or you could connect with a colleague providing support services (eg, {shortMeetingText}, attendance officers, counselors, redirect).  If the student in still missing school, attendance contracts might be a next step.

    );
  }
}
CheckStudentsWithHighAbsencesView.contextTypes = {
  districtKey: PropTypes.string.isRequired,
  nowFn: PropTypes.func.isRequired
};
CheckStudentsWithHighAbsencesView.propTypes = {
  limit: PropTypes.number.isRequired,
  totalStudents: PropTypes.number.isRequired,
  studentsWithHighAbsences: PropTypes.arrayOf(PropTypes.shape({
    count: PropTypes.number.isRequired,
    student: PropTypes.shape({
      id: PropTypes.number.isRequired,
      first_name: PropTypes.string.isRequired,
      last_name: PropTypes.string.isRequired,
      grade: PropTypes.string.isRequired,
      house: PropTypes.string
    }).isRequired
  })).isRequired
};

const styles = {
  root: {
    fontSize: 14
  },
  card: {
    margin: 10,
    marginTop: 20,
    border: '1px solid #ccc',
    borderRadius: 3
  },
  helpTeaser: {
    display: 'inline-block',
    paddingLeft: 5,
    paddingRight: 5
  },
  helpContent: {
    margin: 10
  },
  cardTitle: {
    backgroundColor: '#eee',
    padding: 10,
    color: 'black',
    borderBottom: '1px solid #ccc',
    display: 'flex',
    justifyContent: 'space-between'
  },
  person: {
    fontWeight: 'bold'
  },
  line: {
    paddingLeft: 10,
    paddingRight: 10,
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap'
  },
  middleText: {
    display: 'inline-block',
    paddingLeft: 5,
    paddingRight: 5
  }
};

export default CheckStudentsWithHighAbsences;<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var Steam = require('../../steam_client');

var EMsg = Steam.EMsg;
var schema = Steam.Internal;

var protoMask = 0x80000000;

function SteamGameCoordinator(steamClient, appid) {
  this._client = steamClient;
  this._appid = appid;

  this._jobs = {};
  this._currentJobID = 0;

  this._client.on('message', function(header, body, callback) {
    if (header.msg in handlers)
      handlers[header.msg].call(this, body, callback);
  }.bind(this));

  this._client.on('logOnResponse', function() {
    this._jobs = {};
    this._currentJobID = 0;
  }.bind(this));
}

require('util').inherits(SteamGameCoordinator, EventEmitter);

// Methods

SteamGameCoordinator.prototype._send = function(header, body, callback) {
  if (callback) {
    var sourceJobID = ++this._currentJobID;
    this._jobs[sourceJobID] = callback;
  }

  if (header.proto) {
    header.proto.job_id_source = sourceJobID;
    header = new schema.MsgGCHdrProtoBuf(header);
  } else {
    header.sourceJobID = sourceJobID;
    header = new schema.MsgGCHdr(header);
  }

  this._client.send({
    msg: EMsg.ClientToGC,
    proto: {
      routing_appid: this._appid
    }
  }, new schema.CMsgGCClient({
    msgtype: header.proto ? header.msg | protoMask : header.msg,
    appid: this._appid,
    payload: Buffer.concat([header.toBuffer(), body])
  }).toBuffer());
};

SteamGameCoordinator.prototype.send = function(header, body, callback) {
  // ignore any target job ID
  if (header.proto)
    delete header.proto.job_id_target;
  else
    delete header.targetJobID;
  this._send(header, body, callback);
};

// Handlers

var handlers = {};

handlers[EMsg.ClientFromGC] = function(data, jobid) {
  var msg = schema.CMsgGCClient.decode(data);

  if (msg.appid != this._appid)
    return;

  var header, sourceJobID, targetJobID;
  if (msg.msgtype & protoMask) {
    header = schema.MsgGCHdrProtoBuf.decode(msg.payload);
    header.proto = Steam._processProto(header.proto);
    sourceJobID = header.proto.job_id_source;
    targetJobID = header.proto.job_id_target;
  } else {
    header = schema.MsgGCHdr.decode(msg.payload);
    sourceJobID = header.sourceJobID;
    targetJobID = header.targetJobID;
  }

  if (sourceJobID != '18446744073709551615') {
    var callback = function(header, body, callback) {
      if (header.proto)
        header.proto.job_id_target = sourceJobID;
      else
        header.targetJobID = sourceJobID;
      this._send(header, body, callback);
    }.bind(this);
  }

  if (targetJobID in this._jobs)
    this._jobs[targetJobID](header, msg.payload.toBuffer(), callback);
  else
    this.emit('message', header, msg.payload.toBuffer(), callback);
};

Steam.SteamGameCoordinator = SteamGameCoordinator;
<commit_msg>Fix sending non-proto GC messages<commit_after>var EventEmitter = require('events').EventEmitter;
var Steam = require('../../steam_client');

var EMsg = Steam.EMsg;
var schema = Steam.Internal;

var protoMask = 0x80000000;

function SteamGameCoordinator(steamClient, appid) {
  this._client = steamClient;
  this._appid = appid;

  this._jobs = {};
  this._currentJobID = 0;

  this._client.on('message', function(header, body, callback) {
    if (header.msg in handlers)
      handlers[header.msg].call(this, body, callback);
  }.bind(this));

  this._client.on('logOnResponse', function() {
    this._jobs = {};
    this._currentJobID = 0;
  }.bind(this));
}

require('util').inherits(SteamGameCoordinator, EventEmitter);

// Methods

SteamGameCoordinator.prototype._send = function(header, body, callback) {
  if (callback) {
    var sourceJobID = ++this._currentJobID;
    this._jobs[sourceJobID] = callback;
  }

  var type = header.msg;

  if (header.proto) {
    header.proto.job_id_source = sourceJobID;
    header = new schema.MsgGCHdrProtoBuf(header);
  } else {
    header.sourceJobID = sourceJobID;
    header = new schema.MsgGCHdr(header);
  }

  this._client.send({
    msg: EMsg.ClientToGC,
    proto: {
      routing_appid: this._appid
    }
  }, new schema.CMsgGCClient({
    msgtype: header.proto ? type | protoMask : type,
    appid: this._appid,
    payload: Buffer.concat([header.toBuffer(), body])
  }).toBuffer());
};

SteamGameCoordinator.prototype.send = function(header, body, callback) {
  // ignore any target job ID
  if (header.proto)
    delete header.proto.job_id_target;
  else
    delete header.targetJobID;
  this._send(header, body, callback);
};

// Handlers

var handlers = {};

handlers[EMsg.ClientFromGC] = function(data, jobid) {
  var msg = schema.CMsgGCClient.decode(data);

  if (msg.appid != this._appid)
    return;

  var header, sourceJobID, targetJobID;
  if (msg.msgtype & protoMask) {
    header = schema.MsgGCHdrProtoBuf.decode(msg.payload);
    header.proto = Steam._processProto(header.proto);
    sourceJobID = header.proto.job_id_source;
    targetJobID = header.proto.job_id_target;
  } else {
    header = schema.MsgGCHdr.decode(msg.payload);
    sourceJobID = header.sourceJobID;
    targetJobID = header.targetJobID;
  }

  if (sourceJobID != '18446744073709551615') {
    var callback = function(header, body, callback) {
      if (header.proto)
        header.proto.job_id_target = sourceJobID;
      else
        header.targetJobID = sourceJobID;
      this._send(header, body, callback);
    }.bind(this);
  }

  if (targetJobID in this._jobs)
    this._jobs[targetJobID](header, msg.payload.toBuffer(), callback);
  else
    this.emit('message', header, msg.payload.toBuffer(), callback);
};

Steam.SteamGameCoordinator = SteamGameCoordinator;
<|endoftext|>


<commit_before>(function(ko, $) {
  "use strict";

  ko.components.register('confirmation-button', {
      viewModel: function(params) {
          this.text = ko.observable(params.initialText || '');
          this.css  = params.css || '';
      },
      template: '<!-- ko template: { nodes: $componentTemplateNodes } --><!-- /ko -->'
  });

})(ko, $);

(function(ko, $) {
  "use strict";

  ko.components.register('confirmation-button', {
      viewModel: {
        createViewModel: function createViewModel(params, componentInfo) {
          var ConfirmationButtonVm;

          ConfirmationButtonVm = function ConfirmationButtonVm(params) {

          };

          return new ConfirmationButtonVm(params);
        }
      },
      template: 'x'
  });

})(ko, $);<commit_msg>Remove duplicated component registration<commit_after>(function(ko, $) {
  "use strict";

  ko.components.register('confirmation-button', {
      viewModel: function(params) {
          this.text = ko.observable(params.initialText || '');
          this.css  = params.css || '';
      },
      template: '<!-- ko template: { nodes: $componentTemplateNodes } --><!-- /ko -->'
  });

})(ko, $);

// (function(ko, $) {
//   "use strict";

//   ko.components.register('confirmation-button', {
//       viewModel: {
//         createViewModel: function createViewModel(params, componentInfo) {
//           var ConfirmationButtonVm;

//           ConfirmationButtonVm = function ConfirmationButtonVm(params) {

//           };

//           return new ConfirmationButtonVm(params);
//         }
//       },
//       template: 'x'
//   });

// })(ko, $);<|endoftext|>


<commit_before>// In production, we register a service worker to serve assets from local cache.

// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.

// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.

export default function register() {
  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
      navigator.serviceWorker
        .register(swUrl)
        .then(registration => {
          registration.onupdatefound = () => {
            const installingWorker = registration.installing;
            installingWorker.onstatechange = () => {
              if (installingWorker.state === 'installed') {
                if (navigator.serviceWorker.controller) {
                  // At this point, the old content will have been purged and
                  // the fresh content will have been added to the cache.
                  // It's the perfect time to display a "New content is
                  // available; please refresh." message in your web app.
                  console.log('New content is available; please refresh.');
                } else {
                  // At this point, everything has been precached.
                  // It's the perfect time to display a
                  // "Content is cached for offline use." message.
                  console.log('Content is cached for offline use.');
                }
              }
            };
          };
        })
        .catch(error => {
          console.error('Error during service worker registration:', error);
        });
    });
  }
}

export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready.then(registration => {
      registration.unregister();
    });
  }
}
<commit_msg>Determines whether PUBLIC_URL is same-origin before registering SW. (#2432)<commit_after>// In production, we register a service worker to serve assets from local cache.

// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.

// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.

export default function register() {
  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    // The URL constructor is available in all browsers that support SW.
    const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
    if (publicUrl.origin !== window.location.origin) {
      // Our service worker won't work if PUBLIC_URL is on a different origin
      // from what our page is served on. This might happen if a CDN is used to
      // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
      return;
    }

    window.addEventListener('load', () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
      navigator.serviceWorker
        .register(swUrl)
        .then(registration => {
          registration.onupdatefound = () => {
            const installingWorker = registration.installing;
            installingWorker.onstatechange = () => {
              if (installingWorker.state === 'installed') {
                if (navigator.serviceWorker.controller) {
                  // At this point, the old content will have been purged and
                  // the fresh content will have been added to the cache.
                  // It's the perfect time to display a "New content is
                  // available; please refresh." message in your web app.
                  console.log('New content is available; please refresh.');
                } else {
                  // At this point, everything has been precached.
                  // It's the perfect time to display a
                  // "Content is cached for offline use." message.
                  console.log('Content is cached for offline use.');
                }
              }
            };
          };
        })
        .catch(error => {
          console.error('Error during service worker registration:', error);
        });
    });
  }
}

export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready.then(registration => {
      registration.unregister();
    });
  }
}
<|endoftext|>


<commit_before>(function() {
  tinymce.PluginManager.requireLangPack('uploadimage');

  tinymce.create('tinymce.plugins.UploadImage', {
    UploadImage: function(ed, url) {
      var form,
          iframe,
          win,
          throbber,
          editor = ed;

      function showDialog() {
        win = editor.windowManager.open({
          title: ed.translate('Insert an image from your computer'),
          width:  500 + parseInt(editor.getLang('uploadimage.delta_width', 0), 10),
          height: 180 + parseInt(editor.getLang('uploadimage.delta_height', 0), 10),
          body: [
            {type: 'iframe',  url: 'javascript:void(0)'},
            {type: 'textbox', name: 'file', label: ed.translate('Choose an image'), subtype: 'file'},
            {type: 'textbox', name: 'alt',  label: ed.translate('Image description')},
            {type: 'container', classes: 'error', html: "

"},

            // Trick TinyMCE to add a empty div that "preloads" the throbber image
            {type: 'container', classes: 'throbber'},
          ],
          buttons: [
            {
              text: ed.translate('Insert'),
              onclick: insertImage,
              subtype: 'primary'
            },
            {
              text: ed.translate('Cancel'),
              onclick: ed.windowManager.close
            }
          ],
        }, {
          plugin_url: url
        });

        // TinyMCE likes pointless submit handlers
        win.off('submit');
        win.on('submit', insertImage);

        /* WHY DO YOU HATE <form>, TINYMCE!? */
        iframe = win.find("iframe")[0];
        form = createElement('form', {
          action: ed.getParam("uploadimage_form_url", "/tinymce_assets"),
          target: iframe._id,
          method: "POST",
          enctype: 'multipart/form-data',
          accept_charset: "UTF-8",
        });

        // Might have several instances on the same page,
        // so we TinyMCE create unique IDs and use those.
        iframe.getEl().name = iframe._id;

        // Create some needed hidden inputs
        form.appendChild(createElement('input', {type: "hidden", name: "utf8", value: "✓"}));
        form.appendChild(createElement('input', {type: 'hidden', name: 'authenticity_token', value: getMetaContents('csrf-token')}));
        form.appendChild(createElement('input', {type: 'hidden', name: 'hint', value: ed.getParam("uploadimage_hint", "")}));

        var el = win.getEl();
        var body = document.getElementById(el.id + "-body");

        // Copy everything TinyMCE made into our form
        var containers = body.getElementsByClassName('mce-container');
        for(var i = 0; i < containers.length; i++) {
          form.appendChild(containers[i]);
        }

        // Fix inputs, since TinyMCE hates HTML and forms
        var inputs = form.getElementsByTagName('input');
        for(var i = 0; i < inputs.length; i++) {
          var ctrl = inputs[i];

          if(ctrl.tagName.toLowerCase() == 'input' && ctrl.type != "hidden") {
            if(ctrl.type == "file") {
              ctrl.name = "file";

              // Hack styles
              tinymce.DOM.setStyles(ctrl, {
                'border': 0,
                'boxShadow': 'none',
                'webkitBoxShadow': 'none',
              });
            } else {
              ctrl.name = "alt";
            }
          }
        }

        body.appendChild(form);
      }

      function insertImage() {
        if(getInputValue("file") == "") {
          return handleError('You must choose a file');
        }

        throbber = new top.tinymce.ui.Throbber(win.getEl());
        throbber.show();

        clearErrors();

        /* Add event listeners.
         * We remove the existing to avoid them being called twice in case
         * of errors and re-submitting afterwards.
         */
        var target = iframe.getEl();
        if(target.attachEvent) {
          target.detachEvent('onload', uploadDone);
          target.attachEvent('onload', uploadDone);
        } else {
          target.removeEventListener('load', uploadDone);
          target.addEventListener('load', uploadDone, false);
        }

        form.submit();
      }

      function uploadDone() {
        if(throbber) {
          throbber.hide();
        }

        var target = iframe.getEl();
        if(target.document || target.contentDocument) {
          var doc = target.contentDocument || target.contentWindow.document;
          handleResponse(doc.getElementsByTagName("body")[0].innerHTML);
        } else {
          handleError("Didn't get a response from the server");
        }
      }

      function handleResponse(ret) {
        try {
          var json = tinymce.util.JSON.parse(ret);

          if(json["error"]) {
            handleError(json["error"]["message"]);
          } else {
            ed.execCommand('mceInsertContent', false, buildHTML(json));
            ed.windowManager.close();
          }
        } catch(e) {
          handleError('Got a bad response from the server');
        }
      }

      function clearErrors() {
        var message = win.find(".error")[0].getEl();

        if(message)
          message.getElementsByTagName("p")[0].innerHTML = " ";
      }

      function handleError(error) {
        var message = win.find(".error")[0].getEl();

        if(message)
          message.getElementsByTagName("p")[0].innerHTML = ed.translate(error);
      }

      function createElement(element, attributes) {
        var el = document.createElement(element);
        for(var property in attributes) {
          if (!(attributes[property] instanceof Function)) {
            el[property] = attributes[property];
          }
        }

        return el;
      }

      function buildHTML(json) {
        var default_class = ed.getParam("uploadimage_default_img_class", "");
        var alt_text = getInputValue("alt");

        var imgstr = "";

        var figstr = "
"
            figstr += imgstr;
            figstr += "
" + alt_text + "
"
            figstr += "
"

        return figstr;
      }

      function getInputValue(name) {
        var inputs = form.getElementsByTagName("input");

        for(var i in inputs)
          if(inputs[i].name == name)
            return inputs[i].value;

        return "";
      }

      function getMetaContents(mn) {
        var m = document.getElementsByTagName('meta');

        for(var i in m)
          if(m[i].name == mn)
            return m[i].content;

        return null;
      }

      // Add a button that opens a window
      editor.addButton('uploadimage', {
        tooltip: ed.translate('Insert an image from your computer'),
        icon : 'image',
        onclick: showDialog
      });

      // Adds a menu item to the tools menu
      editor.addMenuItem('uploadimage', {
        text: ed.translate('Insert an image from your computer'),
        icon : 'image',
        context: 'insert',
        onclick: showDialog
      });
    }
  });

  tinymce.PluginManager.add('uploadimage', tinymce.plugins.UploadImage);
})();
<commit_msg>Made wrapping in figure optional<commit_after>(function() {
  tinymce.PluginManager.requireLangPack('uploadimage');

  tinymce.create('tinymce.plugins.UploadImage', {
    UploadImage: function(ed, url) {
      var form,
          iframe,
          win,
          throbber,
          editor = ed;

      function showDialog() {
        win = editor.windowManager.open({
          title: ed.translate('Insert an image from your computer'),
          width:  500 + parseInt(editor.getLang('uploadimage.delta_width', 0), 10),
          height: 180 + parseInt(editor.getLang('uploadimage.delta_height', 0), 10),
          body: [
            {type: 'iframe',  url: 'javascript:void(0)'},
            {type: 'textbox', name: 'file', label: ed.translate('Choose an image'), subtype: 'file'},
            {type: 'textbox', name: 'alt',  label: ed.translate('Image description')},
            {type: 'container', classes: 'error', html: "

"},

            // Trick TinyMCE to add a empty div that "preloads" the throbber image
            {type: 'container', classes: 'throbber'},
          ],
          buttons: [
            {
              text: ed.translate('Insert'),
              onclick: insertImage,
              subtype: 'primary'
            },
            {
              text: ed.translate('Cancel'),
              onclick: ed.windowManager.close
            }
          ],
        }, {
          plugin_url: url
        });

        // TinyMCE likes pointless submit handlers
        win.off('submit');
        win.on('submit', insertImage);

        /* WHY DO YOU HATE <form>, TINYMCE!? */
        iframe = win.find("iframe")[0];
        form = createElement('form', {
          action: ed.getParam("uploadimage_form_url", "/tinymce_assets"),
          target: iframe._id,
          method: "POST",
          enctype: 'multipart/form-data',
          accept_charset: "UTF-8",
        });

        // Might have several instances on the same page,
        // so we TinyMCE create unique IDs and use those.
        iframe.getEl().name = iframe._id;

        // Create some needed hidden inputs
        form.appendChild(createElement('input', {type: "hidden", name: "utf8", value: "✓"}));
        form.appendChild(createElement('input', {type: 'hidden', name: 'authenticity_token', value: getMetaContents('csrf-token')}));
        form.appendChild(createElement('input', {type: 'hidden', name: 'hint', value: ed.getParam("uploadimage_hint", "")}));

        var el = win.getEl();
        var body = document.getElementById(el.id + "-body");

        // Copy everything TinyMCE made into our form
        var containers = body.getElementsByClassName('mce-container');
        for(var i = 0; i < containers.length; i++) {
          form.appendChild(containers[i]);
        }

        // Fix inputs, since TinyMCE hates HTML and forms
        var inputs = form.getElementsByTagName('input');
        for(var i = 0; i < inputs.length; i++) {
          var ctrl = inputs[i];

          if(ctrl.tagName.toLowerCase() == 'input' && ctrl.type != "hidden") {
            if(ctrl.type == "file") {
              ctrl.name = "file";

              // Hack styles
              tinymce.DOM.setStyles(ctrl, {
                'border': 0,
                'boxShadow': 'none',
                'webkitBoxShadow': 'none',
              });
            } else {
              ctrl.name = "alt";
            }
          }
        }

        body.appendChild(form);
      }

      function insertImage() {
        if(getInputValue("file") == "") {
          return handleError('You must choose a file');
        }

        throbber = new top.tinymce.ui.Throbber(win.getEl());
        throbber.show();

        clearErrors();

        /* Add event listeners.
         * We remove the existing to avoid them being called twice in case
         * of errors and re-submitting afterwards.
         */
        var target = iframe.getEl();
        if(target.attachEvent) {
          target.detachEvent('onload', uploadDone);
          target.attachEvent('onload', uploadDone);
        } else {
          target.removeEventListener('load', uploadDone);
          target.addEventListener('load', uploadDone, false);
        }

        form.submit();
      }

      function uploadDone() {
        if(throbber) {
          throbber.hide();
        }

        var target = iframe.getEl();
        if(target.document || target.contentDocument) {
          var doc = target.contentDocument || target.contentWindow.document;
          handleResponse(doc.getElementsByTagName("body")[0].innerHTML);
        } else {
          handleError("Didn't get a response from the server");
        }
      }

      function handleResponse(ret) {
        try {
          var json = tinymce.util.JSON.parse(ret);

          if(json["error"]) {
            handleError(json["error"]["message"]);
          } else {
            ed.execCommand('mceInsertContent', false, buildHTML(json));
            ed.windowManager.close();
          }
        } catch(e) {
          handleError('Got a bad response from the server');
        }
      }

      function clearErrors() {
        var message = win.find(".error")[0].getEl();

        if(message)
          message.getElementsByTagName("p")[0].innerHTML = " ";
      }

      function handleError(error) {
        var message = win.find(".error")[0].getEl();

        if(message)
          message.getElementsByTagName("p")[0].innerHTML = ed.translate(error);
      }

      function createElement(element, attributes) {
        var el = document.createElement(element);
        for(var property in attributes) {
          if (!(attributes[property] instanceof Function)) {
            el[property] = attributes[property];
          }
        }

        return el;
      }

      function buildHTML(json) {
        var default_class = ed.getParam("uploadimage_default_img_class", "");
        var figure = ed.getParam("uploadimage_figure", false);
        var alt_text = getInputValue("alt");

        var imgstr = "";

        if(figure) {
          var figureClass = ed.getParam("uploadimage_figure_class", "figure");
          var figcaptionClass = ed.getParam("uploadimage_figcaption_class", "figcaption");

          var figstr = "
" + imgstr;
          figstr += "
" + alt_text + "
";
          figstr += "
";

          return figstr;
        } else {
          return imgstr;
        }
      }

      function getInputValue(name) {
        var inputs = form.getElementsByTagName("input");

        for(var i in inputs)
          if(inputs[i].name == name)
            return inputs[i].value;

        return "";
      }

      function getMetaContents(mn) {
        var m = document.getElementsByTagName('meta');

        for(var i in m)
          if(m[i].name == mn)
            return m[i].content;

        return null;
      }

      // Add a button that opens a window
      editor.addButton('uploadimage', {
        tooltip: ed.translate('Insert an image from your computer'),
        icon : 'image',
        onclick: showDialog
      });

      // Adds a menu item to the tools menu
      editor.addMenuItem('uploadimage', {
        text: ed.translate('Insert an image from your computer'),
        icon : 'image',
        context: 'insert',
        onclick: showDialog
      });
    }
  });

  tinymce.PluginManager.add('uploadimage', tinymce.plugins.UploadImage);
})();
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

'use strict';

const React = require('react');

const {
  Alert,
  Linking,
  Picker,
  StyleSheet,
  Text,
  TouchableHighlight,
  View,
} = require('react-native');

const BINARY_TYPES = {
  String,
  ArrayBuffer,
  Int8Array,
  Uint8Array,
  Uint8ClampedArray,
  Int16Array,
  Uint16Array,
  Int32Array,
  Uint32Array,
  Float32Array,
  Float64Array,
  DataView,
};

const SAMPLE_TEXT = `
I am the spirit that negates.
And rightly so, for all that comes to be
Deserves to perish wretchedly;
'Twere better nothing would begin.
Thus everything that that your terms, sin,
Destruction, evil represent—
That is my proper element.

--Faust, JW Goethe
`;

class XHRExampleBinaryUpload extends React.Component<{...}, $FlowFixMeState> {
  static handlePostTestServerUpload(xhr: XMLHttpRequest) {
    if (xhr.status !== 200) {
      Alert.alert(
        'Upload failed',
        'Expected HTTP 200 OK response, got ' + xhr.status,
      );
      return;
    }
    if (!xhr.responseText) {
      Alert.alert('Upload failed', 'No response payload.');
      return;
    }
    const index = xhr.responseText.indexOf('http://www.posttestserver.com/');
    if (index === -1) {
      Alert.alert('Upload failed', 'Invalid response payload.');
      return;
    }
    const url = xhr.responseText.slice(index).split('\n')[0];
    console.log('Upload successful: ' + url);
    Linking.openURL(url);
  }

  state: $FlowFixMe | {|type: $TEMPORARY$string<'Uint8Array'>|} = {
    type: 'Uint8Array',
  };

  _upload = () => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://posttestserver.com/post.php');
    xhr.onload = () => XHRExampleBinaryUpload.handlePostTestServerUpload(xhr);
    xhr.setRequestHeader('Content-Type', 'text/plain');

    if (this.state.type === 'String') {
      xhr.send(SAMPLE_TEXT);
      return;
    }

    const arrayBuffer = new ArrayBuffer(256);
    const asBytes = new Uint8Array(arrayBuffer);
    for (let i = 0; i < SAMPLE_TEXT.length; i++) {
      asBytes[i] = SAMPLE_TEXT.charCodeAt(i);
    }
    if (this.state.type === 'ArrayBuffer') {
      xhr.send(arrayBuffer);
      return;
    }
    if (this.state.type === 'Uint8Array') {
      xhr.send(asBytes);
      return;
    }

    const TypedArrayClass = BINARY_TYPES[this.state.type];
    xhr.send(new TypedArrayClass(arrayBuffer));
  };

  render(): React.Node {
    return (
      <View>
        <Text>Upload 255 bytes as...</Text>
        <Picker
          selectedValue={this.state.type}
          onValueChange={type => this.setState({type})}>
          {Object.keys(BINARY_TYPES).map(type => (
            <Picker.Item key={type} label={type} value={type} />
          ))}
        </Picker>
        <View style={styles.uploadButton}>
          <TouchableHighlight onPress={this._upload}>
            <View style={styles.uploadButtonBox}>
              <Text style={styles.uploadButtonLabel}>Upload</Text>
            </View>
          </TouchableHighlight>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  uploadButton: {
    marginTop: 16,
  },
  uploadButtonBox: {
    flex: 1,
    paddingVertical: 12,
    alignItems: 'center',
    backgroundColor: 'blue',
    borderRadius: 4,
  },
  uploadButtonLabel: {
    color: 'white',
    fontSize: 16,
    fontWeight: '500',
  },
});

module.exports = XHRExampleBinaryUpload;
<commit_msg>Fix XHR multipart form upload example<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

'use strict';

const React = require('react');

const {
  Alert,
  Linking,
  Picker,
  StyleSheet,
  Text,
  TouchableHighlight,
  View,
} = require('react-native');

const BINARY_TYPES = {
  String,
  ArrayBuffer,
  Int8Array,
  Uint8Array,
  Uint8ClampedArray,
  Int16Array,
  Uint16Array,
  Int32Array,
  Uint32Array,
  Float32Array,
  Float64Array,
  DataView,
};

const SAMPLE_TEXT = `
I am the spirit that negates.
And rightly so, for all that comes to be
Deserves to perish wretchedly;
'Twere better nothing would begin.
Thus everything that that your terms, sin,
Destruction, evil represent—
That is my proper element.

--Faust, JW Goethe
`;

class XHRExampleBinaryUpload extends React.Component<{...}, $FlowFixMeState> {
  static handlePostTestServerUpload(xhr: XMLHttpRequest) {
    if (xhr.status !== 200) {
      Alert.alert(
        'Upload failed',
        'Expected HTTP 200 OK response, got ' + xhr.status,
      );
      return;
    }
    if (!xhr.responseText) {
      Alert.alert('Upload failed', 'No response payload.');
      return;
    }
    const index = xhr.responseText.indexOf(
      'http://ptsv2.com/t/react-native/d/',
    );
    if (index === -1) {
      Alert.alert('Upload failed', 'Invalid response payload.');
      return;
    }
    const url = xhr.responseText.slice(index).split('\n')[0];
    console.log('Upload successful: ' + url);
    Linking.openURL(url);
  }

  state: $FlowFixMe | {|type: $TEMPORARY$string<'Uint8Array'>|} = {
    type: 'Uint8Array',
  };

  _upload = () => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://ptsv2.com/t/react-native/post');
    xhr.onload = () => XHRExampleBinaryUpload.handlePostTestServerUpload(xhr);
    xhr.setRequestHeader('Content-Type', 'text/plain');

    if (this.state.type === 'String') {
      xhr.send(SAMPLE_TEXT);
      return;
    }

    const arrayBuffer = new ArrayBuffer(256);
    const asBytes = new Uint8Array(arrayBuffer);
    for (let i = 0; i < SAMPLE_TEXT.length; i++) {
      asBytes[i] = SAMPLE_TEXT.charCodeAt(i);
    }
    if (this.state.type === 'ArrayBuffer') {
      xhr.send(arrayBuffer);
      return;
    }
    if (this.state.type === 'Uint8Array') {
      xhr.send(asBytes);
      return;
    }

    const TypedArrayClass = BINARY_TYPES[this.state.type];
    xhr.send(new TypedArrayClass(arrayBuffer));
  };

  render(): React.Node {
    return (
      <View>
        <Text>Upload 255 bytes as...</Text>
        <Picker
          selectedValue={this.state.type}
          onValueChange={type => this.setState({type})}>
          {Object.keys(BINARY_TYPES).map(type => (
            <Picker.Item key={type} label={type} value={type} />
          ))}
        </Picker>
        <View style={styles.uploadButton}>
          <TouchableHighlight onPress={this._upload}>
            <View style={styles.uploadButtonBox}>
              <Text style={styles.uploadButtonLabel}>Upload</Text>
            </View>
          </TouchableHighlight>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  uploadButton: {
    marginTop: 16,
  },
  uploadButtonBox: {
    flex: 1,
    paddingVertical: 12,
    alignItems: 'center',
    backgroundColor: 'blue',
    borderRadius: 4,
  },
  uploadButtonLabel: {
    color: 'white',
    fontSize: 16,
    fontWeight: '500',
  },
});

module.exports = XHRExampleBinaryUpload;
<|endoftext|>


<commit_before>import React, { useEffect, useReducer } from 'react';
import axios from 'axios';
import { camelCase, get, omit, upperFirst } from 'lodash';
import { Redirect, useRouteMatch, useHistory } from 'react-router-dom';
import { auth, useQuery } from 'strapi-helper-plugin';
import { Padded } from '@buffetjs/core';
import PropTypes from 'prop-types';
import BaselineAlignment from '../../components/BaselineAlignement';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import PageTitle from '../../components/PageTitle';
import LocaleToggle from '../LocaleToggle';
import checkFormValidity from '../../utils/checkFormValidity';
import formatAPIErrors from '../../utils/formatAPIErrors';
import { forms } from './utils';
import init from './init';
import { initialState, reducer } from './reducer';

const AuthPage = ({ hasAdmin }) => {
  const { push } = useHistory();
  const {
    params: { authType },
  } = useRouteMatch('/auth/:authType');
  const query = useQuery();
  const registrationToken = query.get('registrationToken');
  const { Component, endPoint, fieldsToDisable, fieldsToOmit, inputsPrefix, schema } = get(
    forms,
    authType,
    {}
  );
  const [{ formErrors, modifiedData, requestError }, dispatch] = useReducer(
    reducer,
    initialState,
    init
  );
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();

  useEffect(() => {
    // Cancel request on unmount
    return () => {
      source.cancel('Component unmounted');
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Reset the state on navigation change
  useEffect(() => {
    dispatch({
      type: 'RESET_PROPS',
    });
  }, [authType]);

  useEffect(() => {
    if (authType === 'register') {
      const getData = async () => {
        try {
          const {
            data: { data },
          } = await axios.get(
            `${strapi.backendURL}/admin/registration-info?registrationToken=${registrationToken}`
          );

          dispatch({
            type: 'SET_DATA',
            data: { registrationToken, userInfo: data },
          });
        } catch (err) {
          const errorMessage = get(err, ['response', 'data', 'message'], 'An error occured');

          strapi.notification.error(errorMessage);

          // Redirect to the oops page in case of an invalid token
          // @alexandrebodin @JAB I am not sure it is the wanted behavior
          push(`/auth/oops?info=${encodeURIComponent(errorMessage)}`);
        }
      };

      // TODO API call
      getData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [authType]);

  const handleChange = ({ target: { name, value } }) => {
    dispatch({
      type: 'ON_CHANGE',
      keys: name,
      value,
    });
  };

  const handleSubmit = async e => {
    e.preventDefault();

    dispatch({
      type: 'SET_ERRORS',
      errors: {},
    });

    const errors = await checkFormValidity(modifiedData, schema);

    dispatch({
      type: 'SET_ERRORS',
      errors: errors || {},
    });

    if (!errors) {
      const body = omit(modifiedData, fieldsToOmit);
      const requestURL = `/admin/${endPoint}`;

      if (authType === 'login') {
        await loginRequest(body, requestURL);
      }

      if (authType === 'register' || authType === 'register-admin') {
        await registerRequest(body, requestURL);
      }

      if (authType === 'forgot-password') {
        await forgotPasswordRequest(body, requestURL);
      }

      if (authType === 'reset-password') {
        await resetPasswordRequest(body, requestURL);
      }
    }
  };

  const forgotPasswordRequest = async (body, requestURL) => {
    try {
      await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      push('/auth/forgot-password-success');
    } catch (err) {
      console.error(err);

      strapi.notification.error('notification.error');
    }
  };

  const loginRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, modifiedData.rememberMe);
      auth.setUserInfo(user, modifiedData.rememberMe);

      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        if (camelCase(errorMessage).toLowerCase() === 'usernotactive') {
          push('/auth/oops');

          dispatch({
            type: 'RESET_PROPS',
          });

          return;
        }

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  const registerRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      if (
        (authType === 'register' && modifiedData.userInfo.news === true) ||
        (authType === 'register-admin' && modifiedData.news === true)
      ) {
        axios({
          method: 'POST',
          url: 'https://analytics.strapi.io/register',
          data: omit(modifiedData, [
            'userInfo.password',
            'userInfo.confirmPassword',
            'password',
            'confirmPassword',
          ]),
        });
      }
      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const { data } = err.response;
        const apiErrors = formatAPIErrors(data);

        dispatch({
          type: 'SET_ERRORS',
          errors: apiErrors,
        });
      }
    }
  };

  const resetPasswordRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: { ...body, resetPasswordToken: query.get('code') },
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  // Redirect the user to the login page if the endpoint does not exist
  if (!forms[authType]) {
    return <Redirect to="/" />;
  }

  // Redirect the user to the login page if there is already an admin user
  if (hasAdmin && authType === 'register-admin') {
    return <Redirect to="/" />;
  }

  // Redirect the user to the register-admin if it is the first user
  if (!hasAdmin && authType !== 'register-admin') {
    return <Redirect to="/auth/register-admin" />;
  }

  // Redirect the user to the homepage if he is logged in
  if (auth.getToken()) {
    return <Redirect to="/" />;
  }

  return (
    <>
      <Padded bottom size="md">
        <PageTitle title={upperFirst(authType)} />
        <NavTopRightWrapper>
          <LocaleToggle isLogged className="localeDropdownMenuNotLogged" />
        </NavTopRightWrapper>
        <BaselineAlignment top size="78px">
          <Component
            fieldsToDisable={fieldsToDisable}
            formErrors={formErrors}
            inputsPrefix={inputsPrefix}
            modifiedData={modifiedData}
            onChange={handleChange}
            onSubmit={handleSubmit}
            requestError={requestError}
          />
        </BaselineAlignment>
      </Padded>
    </>
  );
};

AuthPage.defaultProps = {
  hasAdmin: false,
};

AuthPage.propTypes = {
  hasAdmin: PropTypes.bool,
};

export default AuthPage;
<commit_msg>Fix AuthPage conflicts<commit_after>import React, { useEffect, useReducer } from 'react';
import axios from 'axios';
import { camelCase, get, omit, upperFirst, pick } from 'lodash';
import { Redirect, useRouteMatch, useHistory } from 'react-router-dom';
import { auth, useQuery } from 'strapi-helper-plugin';
import { Padded } from '@buffetjs/core';
import PropTypes from 'prop-types';
import BaselineAlignment from '../../components/BaselineAlignement';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import PageTitle from '../../components/PageTitle';
import LocaleToggle from '../LocaleToggle';
import checkFormValidity from '../../utils/checkFormValidity';
import formatAPIErrors from '../../utils/formatAPIErrors';
import { forms } from './utils';
import init from './init';
import { initialState, reducer } from './reducer';

const AuthPage = ({ hasAdmin }) => {
  const { push } = useHistory();
  const {
    params: { authType },
  } = useRouteMatch('/auth/:authType');
  const query = useQuery();
  const registrationToken = query.get('registrationToken');
  const { Component, endPoint, fieldsToDisable, fieldsToOmit, inputsPrefix, schema } = get(
    forms,
    authType,
    {}
  );
  const [{ formErrors, modifiedData, requestError }, dispatch] = useReducer(
    reducer,
    initialState,
    init
  );
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();

  useEffect(() => {
    // Cancel request on unmount
    return () => {
      source.cancel('Component unmounted');
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Reset the state on navigation change
  useEffect(() => {
    dispatch({
      type: 'RESET_PROPS',
    });
  }, [authType]);

  useEffect(() => {
    if (authType === 'register') {
      const getData = async () => {
        try {
          const {
            data: { data },
          } = await axios.get(
            `${strapi.backendURL}/admin/registration-info?registrationToken=${registrationToken}`
          );

          dispatch({
            type: 'SET_DATA',
            data: { registrationToken, userInfo: data },
          });
        } catch (err) {
          const errorMessage = get(err, ['response', 'data', 'message'], 'An error occured');

          strapi.notification.error(errorMessage);

          // Redirect to the oops page in case of an invalid token
          // @alexandrebodin @JAB I am not sure it is the wanted behavior
          push(`/auth/oops?info=${encodeURIComponent(errorMessage)}`);
        }
      };

      // TODO API call
      getData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [authType]);

  const handleChange = ({ target: { name, value } }) => {
    dispatch({
      type: 'ON_CHANGE',
      keys: name,
      value,
    });
  };

  const handleSubmit = async e => {
    e.preventDefault();

    dispatch({
      type: 'SET_ERRORS',
      errors: {},
    });

    const errors = await checkFormValidity(modifiedData, schema);

    dispatch({
      type: 'SET_ERRORS',
      errors: errors || {},
    });

    if (!errors) {
      const body = omit(modifiedData, fieldsToOmit);
      const requestURL = `/admin/${endPoint}`;

      if (authType === 'login') {
        await loginRequest(body, requestURL);
      }

      if (authType === 'register' || authType === 'register-admin') {
        await registerRequest(body, requestURL);
      }

      if (authType === 'forgot-password') {
        await forgotPasswordRequest(body, requestURL);
      }

      if (authType === 'reset-password') {
        await resetPasswordRequest(body, requestURL);
      }
    }
  };

  const forgotPasswordRequest = async (body, requestURL) => {
    try {
      await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      push('/auth/forgot-password-success');
    } catch (err) {
      console.error(err);

      strapi.notification.error('notification.error');
    }
  };

  const loginRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, modifiedData.rememberMe);
      auth.setUserInfo(user, modifiedData.rememberMe);

      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        if (camelCase(errorMessage).toLowerCase() === 'usernotactive') {
          push('/auth/oops');

          dispatch({
            type: 'RESET_PROPS',
          });

          return;
        }

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  const registerRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      if (
        (authType === 'register' && modifiedData.userInfo.news === true) ||
        (authType === 'register-admin' && modifiedData.news === true)
      ) {
        axios({
          method: 'POST',
          url: 'https://analytics.strapi.io/register',
          data: pick(modifiedData, ['userInfo.email', 'userInfo.firstname']),
        });
      }
      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const { data } = err.response;
        const apiErrors = formatAPIErrors(data);

        dispatch({
          type: 'SET_ERRORS',
          errors: apiErrors,
        });
      }
    }
  };

  const resetPasswordRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: { ...body, resetPasswordToken: query.get('code') },
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  // Redirect the user to the login page if the endpoint does not exist
  if (!forms[authType]) {
    return <Redirect to="/" />;
  }

  // Redirect the user to the login page if there is already an admin user
  if (hasAdmin && authType === 'register-admin') {
    return <Redirect to="/" />;
  }

  // Redirect the user to the register-admin if it is the first user
  if (!hasAdmin && authType !== 'register-admin') {
    return <Redirect to="/auth/register-admin" />;
  }

  // Redirect the user to the homepage if he is logged in
  if (auth.getToken()) {
    return <Redirect to="/" />;
  }

  return (
    <>
      <Padded bottom size="md">
        <PageTitle title={upperFirst(authType)} />
        <NavTopRightWrapper>
          <LocaleToggle isLogged className="localeDropdownMenuNotLogged" />
        </NavTopRightWrapper>
        <BaselineAlignment top size="78px">
          <Component
            fieldsToDisable={fieldsToDisable}
            formErrors={formErrors}
            inputsPrefix={inputsPrefix}
            modifiedData={modifiedData}
            onChange={handleChange}
            onSubmit={handleSubmit}
            requestError={requestError}
          />
        </BaselineAlignment>
      </Padded>
    </>
  );
};

AuthPage.defaultProps = {
  hasAdmin: false,
};

AuthPage.propTypes = {
  hasAdmin: PropTypes.bool,
};

export default AuthPage;
<|endoftext|>


<commit_before>'use strict';

function manageDojosCtrl($scope, dojoManagementService, alertService, auth, tableUtils, cdDojoService, $location, cdCountriesService) {
  $scope.filter = {};
  $scope.filter.verified = 1;
  $scope.itemsPerPage = 10;

  $scope.pageChanged = function(){
    $scope.loadPage($scope.filter, false);
  };

  var verficationStates = [
    {label: 'Unverified', value: 0},
    {label: 'Verified', value: 1},
    {label: 'Previous', value: 2}
  ];

  var changedDojos = [];

  $scope.getVerificationStates = function(isSigned){
    return isSigned ? verficationStates : [verficationStates[0], verficationStates[2]];
  };

  $scope.editDojo = function(dojo) {
    cdDojoService.setDojo(dojo, function(response) {
      $location.path('/edit-dojo');
    }, function (err){
      if(err){
        alertService.showError(
          'An error has occurred while editing dojo:
 '+
          (err.error || JSON.stringify(err))
        );
      }
    });
  };

  $scope.resetFilter = function(){
    $scope.filter = {};
    $scope.filter.verified = 1;

    $scope.loadPage($scope.filter, true);
  };

  $scope.loadPage = function(query, resetFlag, cb){
    cb = cb || function(){};

    if(query.country){
      query.countryName = query.country.countryName;
      delete query.country;
    }

    var loadPageData = tableUtils.loadPage(resetFlag, $scope.itemsPerPage, $scope.pageNo, query);
    $scope.pageNo = loadPageData.pageNo;
    $scope.dojos = [];
    loadPageData.config = _.extend(loadPageData.config, query);

    dojoManagementService.loadDojos(loadPageData.config,  function(err, results){
      if(err){
        alertService.showError('An error has occurred while loading Dojos:
' +
          (err.error || JSON.stringify(err))
        );
        return;
      }

      $scope.dojos = _.map(results.dojos, function(dojo){
        dojo.verified = _.findWhere(verficationStates, {value: dojo.verified});
        return dojo;
      });

      $scope.totalItems = results.totalItems;

      return cb();
    });
  };

  $scope.filterDojos =  function(){
    $scope.loadPage($scope.filter, true);
    changedDojos = [];
  };

  $scope.processDojos = function(event){

    changedDojos = _.map(changedDojos, function(dojo){
      if(dojo.creatorEmail){
        delete dojo.creatorEmail;
      }

      if(dojo.agreements){
        delete dojo.agreements;
      }

      return dojo;
    });

    $scope.dojosToBeDeleted = _.filter(changedDojos, function(changedDojo){
      return changedDojo.toBeDeleted;
    });

    $scope.dojosToBeUpdated = _.filter(changedDojos, function(changedDojo){
      return !changedDojo.toBeDeleted;
    });

    function updateDojos(cb){
      if(!_.isEmpty($scope.dojosToBeUpdated)){
        var dojosToBeUpdated = _.map($scope.dojosToBeUpdated, function(dojo){
          dojo.verified = dojo.verified.value;

          return dojo;
        });

        dojoManagementService.bulkUpdate(dojosToBeUpdated, function(err, response){
          if(err){
            alertService.showError('An error has occurred while updating Dojos:
' +
              (err.error || JSON.stringify(err)));

            return cb(err);
          }

          cb(null, response);

        });
      } else {
        cb();
      }
    }

    function deleteDojos(cb){
      if(!_.isEmpty($scope.dojosToBeDeleted)){
        var dojos = _.map($scope.dojosToBeDeleted, function(dojo){
          return {id: dojo.id, creator: dojo.creator};
        });

        dojoManagementService.bulkDelete(dojos, function(err, response){
          if(err){
            alertService.showError('An error has occurred while deleting Dojos:
' +
                      (err.error || JSON.stringify(err)));

            return cb(err);
          }

          cb(null, response);
        });

      } else {
        cb();
      }
    }

    async.series([updateDojos, deleteDojos], function(err){
      delete $scope.dojosToBeDeleted;
      delete $scope.dojosToBeUpdated;
      changedDojos = [];
      if(err){
        alertService.showError('An error has occurred while updating Dojos:
' +
              (err.error || JSON.stringify(err)));
      }
      $scope.loadPage(+$scope.filterValue, false);
    });
  };

  cdCountriesService.listCountries(function(countries) {
    $scope.countries = _.map(countries, function(country) {
      return _.omit(country, 'entity$');
    });
  });

  $scope.pushChangedDojo = function(dojo){
    var exists = !!(_.find(changedDojos, function(changedDojo){
                      return dojo.id === changedDojo.id;
                    }));

    if((dojo.verified.value !== $scope.filterValue) || (dojo.toBeDeleted)){
      if(!exists){
        changedDojos.push(dojo);
      }

    } else if(dojo.verified.value === $scope.filterValue && !dojo.toBeDeleted) {
      changedDojos =  _.filter(changedDojos, function(filteredDojo){
                        return dojo.id !== filteredDojo.id;
                      });
    }

  };

  auth.get_loggedin_user(function(){
    $scope.loadPage(1, true);
  });
}

angular.module('cpZenPlatform')
  .controller('manage-dojo-controller',
  ['$scope', 'dojoManagementService', 'alertService', 'auth', 'tableUtils', 'cdDojoService', '$location', 'cdCountriesService',  manageDojosCtrl]);

<commit_msg>Fixes<commit_after>'use strict';

function manageDojosCtrl($scope, dojoManagementService, alertService, auth, tableUtils, cdDojoService, $location, cdCountriesService) {
  $scope.filter = {};
  $scope.filter.verified = 1;
  $scope.itemsPerPage = 10;

  $scope.pageChanged = function(){
    $scope.loadPage($scope.filter, false);
  };

  var verficationStates = [
    {label: 'Unverified', value: 0},
    {label: 'Verified', value: 1},
    {label: 'Previous', value: 2}
  ];

  var changedDojos = [];

  $scope.getVerificationStates = function(isSigned){
    return isSigned ? verficationStates : [verficationStates[0], verficationStates[2]];
  };

  $scope.editDojo = function(dojo) {
    cdDojoService.setDojo(dojo, function(response) {
      $location.path('/edit-dojo');
    }, function (err){
      if(err){
        alertService.showError(
          'An error has occurred while editing dojo:
 '+
          (err.error || JSON.stringify(err))
        );
      }
    });
  };

  $scope.resetFilter = function(){
    $scope.filter = {};
    $scope.filter.verified = 1;

    $scope.loadPage($scope.filter, true);
  };

  $scope.loadPage = function(query, resetFlag, cb){
    cb = cb || function(){};

    if(query.country){
      query.countryName = query.country.countryName;
      delete query.country;
    }

    var loadPageData = tableUtils.loadPage(resetFlag, $scope.itemsPerPage, $scope.pageNo, query);
    $scope.pageNo = loadPageData.pageNo;
    $scope.dojos = [];
    loadPageData.config = _.extend(loadPageData.config, query);

    dojoManagementService.loadDojos(loadPageData.config,  function(err, results){
      if(err){
        alertService.showError('An error has occurred while loading Dojos:
' +
          (err.error || JSON.stringify(err))
        );
        return;
      }

      $scope.dojos = _.map(results.dojos, function(dojo){
        dojo.verified = _.findWhere(verficationStates, {value: dojo.verified});
        return dojo;
      });

      $scope.totalItems = results.totalItems;

      return cb();
    });
  };

  $scope.filterDojos =  function(){
    $scope.loadPage($scope.filter, true);
    changedDojos = [];
  };

  $scope.processDojos = function(event){

    changedDojos = _.map(changedDojos, function(dojo){
      if(dojo.creatorEmail){
        delete dojo.creatorEmail;
      }

      if(dojo.agreements){
        delete dojo.agreements;
      }

      return dojo;
    });

    $scope.dojosToBeDeleted = _.filter(changedDojos, function(changedDojo){
      return changedDojo.toBeDeleted;
    });

    $scope.dojosToBeUpdated = _.filter(changedDojos, function(changedDojo){
      return !changedDojo.toBeDeleted;
    });

    function updateDojos(cb){
      if(!_.isEmpty($scope.dojosToBeUpdated)){
        var dojosToBeUpdated = _.map($scope.dojosToBeUpdated, function(dojo){
          dojo.verified = dojo.verified.value;

          return dojo;
        });

        dojoManagementService.bulkUpdate(dojosToBeUpdated, function(err, response){
          if(err){
            alertService.showError('An error has occurred while updating Dojos:
' +
              (err.error || JSON.stringify(err)));

            return cb(err);
          }

          cb(null, response);

        });
      } else {
        cb();
      }
    }

    function deleteDojos(cb){
      if(!_.isEmpty($scope.dojosToBeDeleted)){
        var dojos = _.map($scope.dojosToBeDeleted, function(dojo){
          return {id: dojo.id, creator: dojo.creator};
        });

        dojoManagementService.bulkDelete(dojos, function(err, response){
          if(err){
            alertService.showError('An error has occurred while deleting Dojos:
' +
                      (err.error || JSON.stringify(err)));

            return cb(err);
          }

          cb(null, response);
        });

      } else {
        cb();
      }
    }

    async.series([updateDojos, deleteDojos], function(err){
      delete $scope.dojosToBeDeleted;
      delete $scope.dojosToBeUpdated;
      changedDojos = [];
      if(err){
        alertService.showError('An error has occurred while updating Dojos:
' +
              (err.error || JSON.stringify(err)));
      }
      $scope.loadPage($scope.filter, false);
    });
  };

  cdCountriesService.listCountries(function(countries) {
    $scope.countries = _.map(countries, function(country) {
      return _.omit(country, 'entity$');
    });
  });

  $scope.pushChangedDojo = function(dojo){
    var filterVerified, exists = !!(_.find(changedDojos, function(changedDojo){
                      return dojo.id === changedDojo.id;
                    }));

    filterVerified = $scope.filter && $scope.filter.verified;

    if((dojo.verified.value !== filterVerified) || (dojo.toBeDeleted)){
      if(!exists){
        changedDojos.push(dojo);
      }

    } else if(dojo.verified.value === filterVerified && !dojo.toBeDeleted) {
      changedDojos =  _.filter(changedDojos, function(filteredDojo){
                        return dojo.id !== filteredDojo.id;
                      });
    }

  };

  auth.get_loggedin_user(function(){
    $scope.loadPage($scope.filter, true);
  });
}

angular.module('cpZenPlatform')
  .controller('manage-dojo-controller',
  ['$scope', 'dojoManagementService', 'alertService', 'auth', 'tableUtils', 'cdDojoService', '$location', 'cdCountriesService',  manageDojosCtrl]);

<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Input from '@material-ui/core/Input'
import IconButton from '@material-ui/core/IconButton'
import InputAdornment from '@material-ui/core/InputAdornment'
import SearchIcon from '@material-ui/icons/Search'
import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab'
import { isSearchPageEnabled } from 'js/utils/feature-flags'
import {
  goTo,
  dashboardURL,
  modifyURLParams
} from 'js/navigation/navigation'
import LogoWithText from 'js/components/Logo/LogoWithText'
import { parseUrlSearchString } from 'js/utils/utils'
import SearchResults from 'js/components/Search/SearchResults'

const searchBoxBorderColor = '#ced4da'
const searchBoxBorderColorFocused = '#bdbdbd'

const styles = theme => ({
  inputRootStyle: {
    padding: 0,
    borderRadius: 2,
    backgroundColor: theme.palette.common.white,
    border: `1px solid ${searchBoxBorderColor}`,
    fontSize: 16,
    boxShadow: '0rem 0rem 0.02rem 0.02rem rgba(0, 0, 0, 0.1)',
    transition: theme.transitions.create(['border-color', 'box-shadow'])
  },
  inputRootFocused: {
    borderColor: searchBoxBorderColorFocused,
    boxShadow: '0rem 0.05rem 0.2rem 0.05rem rgba(0, 0, 0, 0.1)'
  },
  inputStyle: {
    padding: '10px 12px'
  }
})

class SearchPage extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      searchFeatureEnabled: isSearchPageEnabled(),
      searchText: ''
    }
  }

  componentDidMount () {
    if (!this.state.searchFeatureEnabled) {
      goTo(dashboardURL)
    }
  }

  getSearchQueryDecoded () {
    const { location } = this.props
    return parseUrlSearchString(location.search).q
  }

  search () {
    const newQuery = this.state.searchText
    if (newQuery) {
      modifyURLParams({
        q: newQuery
      })
    }
  }

  onSearchTextChange (e) {
    this.setState({
      searchText: e.target.value
    })
  }

  render () {
    const { classes } = this.props
    const query = this.getSearchQueryDecoded()
    const queryEncoded = query ? encodeURI(query) : ''
    const searchResultsPaddingLeft = 170
    if (!this.state.searchFeatureEnabled) {
      return null
    }
    return (

            <LogoWithText
              style={{
                width: 100,
                height: 36
              }}
            />

              <Input
                id='search-input'
                type={'text'}
                defaultValue={query}
                onChange={this.onSearchTextChange.bind(this)}
                onKeyPress={e => {
                  if (e.key === 'Enter') {
                    this.search()
                  }
                }}
                placeholder='Search to raise money for charity...'
                disableUnderline
                fullWidth
                classes={{
                  root: classes.inputRootStyle,
                  input: classes.inputStyle,
                  focused: classes.inputRootFocused
                }}
                endAdornment={
                  <InputAdornment position='end'>
                    <IconButton
                      aria-label='Search button'
                      onClick={this.search.bind(this)}
                    >
                      <SearchIcon style={{ color: searchBoxBorderColorFocused }} />
                    </IconButton>
                  </InputAdornment>
                }
              />

          <Tabs
            value={0}
            indicatorColor={'secondary'}
            style={{
              marginLeft: searchResultsPaddingLeft + 7
            }}
          >
            <Tab label='Web' />
            <Tab
              label='Images'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/search?q=${queryEncoded}&tbm=isch`
                  : 'https://images.google.com'
              }
            />
            <Tab
              label='News'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/search?q=${queryEncoded}&tbm=nws`
                  : 'https://www.google.com'
              }
            />
            <Tab
              label='Video'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/search?q=${queryEncoded}&tbm=vid`
                  : 'https://www.google.com'
              }
            />
            <Tab
              label='Maps'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/maps/?q=${queryEncoded}`
                  : 'https://www.google.com/maps'
              }
            />
          </Tabs>

          <SearchResults
            query={query}
            style={{
              background: 'none',
              marginLeft: searchResultsPaddingLeft,
              maxWidth: 600,
              paddingTop: 20
            }}
          />

    )
  }
}

SearchPage.propTypes = {
  classes: PropTypes.object.isRequired,
  location: PropTypes.shape({
    search: PropTypes.string.isRequired
  }),
  user: PropTypes.shape({
    id: PropTypes.string.isRequired
  }),
  app: PropTypes.shape({
    // TODO: pass these to the MoneyRaised component
    // moneyRaised: PropTypes.number.isRequired,
    // dollarsPerDayRate: PropTypes.number.isRequired
  })
}

SearchPage.defaultProps = {}

export default withStyles(styles)(SearchPage)
<commit_msg>Style search category tabs<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Input from '@material-ui/core/Input'
import IconButton from '@material-ui/core/IconButton'
import InputAdornment from '@material-ui/core/InputAdornment'
import SearchIcon from '@material-ui/icons/Search'
import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab'
import { isSearchPageEnabled } from 'js/utils/feature-flags'
import {
  goTo,
  dashboardURL,
  modifyURLParams
} from 'js/navigation/navigation'
import LogoWithText from 'js/components/Logo/LogoWithText'
import { parseUrlSearchString } from 'js/utils/utils'
import SearchResults from 'js/components/Search/SearchResults'

const searchBoxBorderColor = '#ced4da'
const searchBoxBorderColorFocused = '#bdbdbd'

const styles = theme => ({
  inputRootStyle: {
    padding: 0,
    borderRadius: 2,
    backgroundColor: theme.palette.common.white,
    border: `1px solid ${searchBoxBorderColor}`,
    fontSize: 16,
    boxShadow: '0rem 0rem 0.02rem 0.02rem rgba(0, 0, 0, 0.1)',
    transition: theme.transitions.create(['border-color', 'box-shadow'])
  },
  inputRootFocused: {
    borderColor: searchBoxBorderColorFocused,
    boxShadow: '0rem 0.05rem 0.2rem 0.05rem rgba(0, 0, 0, 0.1)'
  },
  inputStyle: {
    padding: '10px 12px'
  },
  tabsContainerRootStyle: {
    minHeight: 0
  },
  tabRootStyle: {
    color: '#505050', // Same as search result description
    minHeight: 34,
    minWidth: 10
  }
})

class SearchPage extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      searchFeatureEnabled: isSearchPageEnabled(),
      searchText: ''
    }
  }

  componentDidMount () {
    if (!this.state.searchFeatureEnabled) {
      goTo(dashboardURL)
    }
  }

  getSearchQueryDecoded () {
    const { location } = this.props
    return parseUrlSearchString(location.search).q
  }

  search () {
    const newQuery = this.state.searchText
    if (newQuery) {
      modifyURLParams({
        q: newQuery
      })
    }
  }

  onSearchTextChange (e) {
    this.setState({
      searchText: e.target.value
    })
  }

  render () {
    const { classes } = this.props
    const query = this.getSearchQueryDecoded()
    const queryEncoded = query ? encodeURI(query) : ''
    const searchResultsPaddingLeft = 170
    if (!this.state.searchFeatureEnabled) {
      return null
    }
    return (

            <LogoWithText
              style={{
                width: 100,
                height: 36
              }}
            />

              <Input
                id='search-input'
                type={'text'}
                defaultValue={query}
                onChange={this.onSearchTextChange.bind(this)}
                onKeyPress={e => {
                  if (e.key === 'Enter') {
                    this.search()
                  }
                }}
                placeholder='Search to raise money for charity...'
                disableUnderline
                fullWidth
                classes={{
                  root: classes.inputRootStyle,
                  input: classes.inputStyle,
                  focused: classes.inputRootFocused
                }}
                endAdornment={
                  <InputAdornment position='end'>
                    <IconButton
                      aria-label='Search button'
                      onClick={this.search.bind(this)}
                    >
                      <SearchIcon style={{ color: searchBoxBorderColorFocused }} />
                    </IconButton>
                  </InputAdornment>
                }
              />

          <Tabs
            value={0}
            indicatorColor={'secondary'}
            style={{
              marginLeft: searchResultsPaddingLeft + 7
            }}
            classes={{
              root: classes.tabsContainerRootStyle
            }}
          >
            <Tab label='Web'
              classes={{
                root: classes.tabRootStyle
              }}
            />
            <Tab
              label='Images'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/search?q=${queryEncoded}&tbm=isch`
                  : 'https://images.google.com'
              }
              classes={{
                root: classes.tabRootStyle
              }}
            />
            <Tab
              label='News'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/search?q=${queryEncoded}&tbm=nws`
                  : 'https://www.google.com'
              }
              classes={{
                root: classes.tabRootStyle
              }}
            />
            <Tab
              label='Video'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/search?q=${queryEncoded}&tbm=vid`
                  : 'https://www.google.com'
              }
              classes={{
                root: classes.tabRootStyle
              }}
            />
            <Tab
              label='Maps'
              target='_top'
              href={
                queryEncoded
                  ? `https://www.google.com/maps/?q=${queryEncoded}`
                  : 'https://www.google.com/maps'
              }
              classes={{
                root: classes.tabRootStyle
              }}
            />
          </Tabs>

          <SearchResults
            query={query}
            style={{
              background: 'none',
              marginLeft: searchResultsPaddingLeft,
              maxWidth: 600,
              paddingTop: 20
            }}
          />

    )
  }
}

SearchPage.propTypes = {
  classes: PropTypes.object.isRequired,
  location: PropTypes.shape({
    search: PropTypes.string.isRequired
  }),
  user: PropTypes.shape({
    id: PropTypes.string.isRequired
  }),
  app: PropTypes.shape({
    // TODO: pass these to the MoneyRaised component
    // moneyRaised: PropTypes.number.isRequired,
    // dollarsPerDayRate: PropTypes.number.isRequired
  })
}

SearchPage.defaultProps = {}

export default withStyles(styles)(SearchPage)
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import * as styles from './ConnectButton.css';
import {has} from 'ramda';

const isLoading = (status) => status === 'loading';

export default class ConnectButton extends Component {

    render() {
        const {
            connectionsHaveBeenSaved,
            connect,
            connectRequest,
            saveConnectionsRequest,
            editMode
        } = this.props;

        let buttonText;
        let buttonClick = () => {};
        let error = null;

        if (connectRequest.status >= 400 || saveConnectionsRequest.status >= 400) {
            buttonText = 'Connect';
            buttonClick = connect;
            // TODO - Try out locking the home folder and verifying this.
            let errorMessage = 'Hm... had trouble connecting.';
            if (
               has('conent', connectRequest) &&
               has('error', connectRequest.content)
            ) {
               errorMessage = connectRequest.content.error;
            } else if (
               has('content', saveConnectionsRequest) &&
               has('error', saveConnectionsRequest.content) &&
               has('message', saveConnectionsRequest.content.error)
            ) {
               errorMessage = saveConnectionsRequest.content.error.message;
            }
            error =
{errorMessage}
;
        } else if (
            isLoading(connectRequest.status) ||
            isLoading(saveConnectionsRequest.status)
        ) {
            buttonText = 'Connecting...';
        } else if (
            connectRequest.status >= 200 &&
            connectRequest.status < 300
        ) {
            if (editMode) {
                buttonText = 'Save changes';
                buttonClick = connect;
            } else {
                buttonText = 'Connected';
            }
        } else if (!connectRequest.status) {
            buttonText = 'Connect';
            buttonClick = connect;
        }
       return (

                    {buttonText}

                    {error}

       );
    }
}
<commit_msg>PR comment about simplifying several R.has into pathOr<commit_after>import React, {Component, PropTypes} from 'react';
import * as styles from './ConnectButton.css';
import {has, pathOr} from 'ramda';

const isLoading = (status) => status === 'loading';

export default class ConnectButton extends Component {

    render() {
        const {
            connectionsHaveBeenSaved,
            connect,
            connectRequest,
            saveConnectionsRequest,
            editMode
        } = this.props;

        let buttonText;
        let buttonClick = () => {};
        let error = null;

        if (connectRequest.status >= 400 || saveConnectionsRequest.status >= 400) {
            buttonText = 'Connect';
            buttonClick = connect;
            // TODO - Try out locking the home folder and verifying this.

            // Possible errors.
            const connectErrorMessage = pathOr(
                null, ['content', 'error'], connectRequest
            );
            const saveErrorMessage = pathOr(
                null, ['content', 'error', 'message'], saveConnectionsRequest
            );
            const genericErrorMessage = 'Hm... had trouble connecting.';
            const errorMessage = connectErrorMessage || saveErrorMessage || genericErrorMessage;
            error =
{errorMessage}
;
        } else if (
            isLoading(connectRequest.status) ||
            isLoading(saveConnectionsRequest.status)
        ) {
            buttonText = 'Connecting...';
        } else if (
            connectRequest.status >= 200 &&
            connectRequest.status < 300
        ) {
            if (editMode) {
                buttonText = 'Save changes';
                buttonClick = connect;
            } else {
                buttonText = 'Connected';
            }
        } else if (!connectRequest.status) {
            buttonText = 'Connect';
            buttonClick = connect;
        }
       return (

                    {buttonText}

                    {error}

       );
    }
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl } from 'react-intl';
import { setupListEditor, clearListSuggestions, resetListEditor } from 'flavours/glitch/actions/lists';
import Account from './components/account';
import Search from './components/search';
import AccountContainer from './containers/account_container';
import SearchContainer from './containers/search_container';
import EditListForm from './components/edit_list_form';
import Motion from 'flavours/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';

const mapStateToProps = state => ({
  accountIds: state.getIn(['listEditor', 'accounts', 'items']),
  searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
});

const mapDispatchToProps = dispatch => ({
  onInitialize: listId => dispatch(setupListEditor(listId)),
  onClear: () => dispatch(clearListSuggestions()),
  onReset: () => dispatch(resetListEditor()),
});

@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class ListEditor extends ImmutablePureComponent {

  static propTypes = {
    listId: PropTypes.string.isRequired,
    onClose: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    onInitialize: PropTypes.func.isRequired,
    onClear: PropTypes.func.isRequired,
    onReset: PropTypes.func.isRequired,
    accountIds: ImmutablePropTypes.list.isRequired,
    searchAccountIds: ImmutablePropTypes.list.isRequired,
  };

  componentDidMount () {
    const { onInitialize, listId } = this.props;
    onInitialize(listId);
  }

  componentWillUnmount () {
    const { onReset } = this.props;
    onReset();
  }

  render () {
    const { accountIds, searchAccountIds, onClear } = this.props;
    const showSearch = searchAccountIds.size > 0;

    return (

        <EditListForm />

        <Search />

            {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}

          {showSearch &&
}

          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
            {({ x }) =>
              (<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
                {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}

)
            }
          </Motion>

    );
  }

}
<commit_msg>fix list editor<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl } from 'react-intl';
import { setupListEditor, clearListSuggestions, resetListEditor } from 'flavours/glitch/actions/lists';
import Account from './components/account';
import Search from './components/search';
import EditListForm from './components/edit_list_form';
import Motion from 'flavours/glitch/util/optional_motion';
import spring from 'react-motion/lib/spring';

const mapStateToProps = state => ({
  accountIds: state.getIn(['listEditor', 'accounts', 'items']),
  searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
});

const mapDispatchToProps = dispatch => ({
  onInitialize: listId => dispatch(setupListEditor(listId)),
  onClear: () => dispatch(clearListSuggestions()),
  onReset: () => dispatch(resetListEditor()),
});

@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
export default class ListEditor extends ImmutablePureComponent {

  static propTypes = {
    listId: PropTypes.string.isRequired,
    onClose: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    onInitialize: PropTypes.func.isRequired,
    onClear: PropTypes.func.isRequired,
    onReset: PropTypes.func.isRequired,
    accountIds: ImmutablePropTypes.list.isRequired,
    searchAccountIds: ImmutablePropTypes.list.isRequired,
  };

  componentDidMount () {
    const { onInitialize, listId } = this.props;
    onInitialize(listId);
  }

  componentWillUnmount () {
    const { onReset } = this.props;
    onReset();
  }

  render () {
    const { accountIds, searchAccountIds, onClear } = this.props;
    const showSearch = searchAccountIds.size > 0;

    return (

        <EditListForm />

        <Search />

            {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}

          {showSearch &&
}

          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
            {({ x }) =>
              (<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
                {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}

)
            }
          </Motion>

    );
  }

}
<|endoftext|>