- {this.addToggleFormField(
- "textingHoursEnforced",
- "Texting hours enforced?"
- )}
-
{this.props.formValues.textingHoursEnforced ? (
{this.addAutocompleteFormField(
diff --git a/src/components/IncomingMessageActions.jsx b/src/components/IncomingMessageActions.jsx
index 7ef85ba17..d8c7e211f 100644
--- a/src/components/IncomingMessageActions.jsx
+++ b/src/components/IncomingMessageActions.jsx
@@ -101,6 +101,7 @@ class IncomingMessageActions extends Component {
texterNodes.sort((left, right) => {
return left.text.localeCompare(right.text, "en", { sensitivity: "base" });
});
+ texterNodes.splice(0, 0, dataSourceItem("Unassign", -2));
const hasCampaignsFilter =
this.props.campaignsFilter &&
diff --git a/src/components/IncomingMessageList/MessageResponse.jsx b/src/components/IncomingMessageList/MessageResponse.jsx
index 2129b0fda..f1a1d74c3 100644
--- a/src/components/IncomingMessageList/MessageResponse.jsx
+++ b/src/components/IncomingMessageList/MessageResponse.jsx
@@ -24,7 +24,8 @@ class MessageResponse extends Component {
this.state = {
messageText: "",
isSending: false,
- sendError: ""
+ sendError: "",
+ doneFirstClick: false
};
this.handleCloseErrorDialog = this.handleCloseErrorDialog.bind(this);
@@ -43,14 +44,21 @@ class MessageResponse extends Component {
handleMessageFormChange = ({ messageText }) => this.setState({ messageText });
handleMessageFormSubmit = async ({ messageText }) => {
- const { campaignContactId } = this.props.conversation;
- const message = this.createMessageToContact(messageText);
if (this.state.isSending) {
return; // stops from multi-send
}
+
+ if (window.TEXTER_TWOCLICK && !this.state.doneFirstClick) {
+ this.setState({ doneFirstClick: true }); // Enforce TEXTER_TWOCLICK
+ return;
+ }
+
+ const { campaignContactId } = this.props.conversation;
+ const message = this.createMessageToContact(messageText);
+
this.setState({ isSending: true });
- const finalState = { isSending: false };
+ const finalState = { isSending: false, doneFirstClick: false };
try {
const response = await this.props.mutations.sendMessage(
message,
@@ -82,7 +90,7 @@ class MessageResponse extends Component {
.max(window.MAX_MESSAGE_LENGTH)
});
- const { messageText, isSending } = this.state;
+ const { messageText, isSending, doneFirstClick } = this.state;
const isSendDisabled = isSending || messageText.trim() === "";
const errorActions = [
@@ -114,6 +122,7 @@ class MessageResponse extends Component {
diff --git a/src/components/SendButton.jsx b/src/components/SendButton.jsx
index 39a7e0a92..47d6afc45 100644
--- a/src/components/SendButton.jsx
+++ b/src/components/SendButton.jsx
@@ -1,8 +1,10 @@
import PropTypes from "prop-types";
import React, { Component } from "react";
-import RaisedButton from "material-ui/RaisedButton";
+import FlatButton from "material-ui/FlatButton";
import { StyleSheet, css } from "aphrodite";
import { dataTest } from "../lib/attributes";
+import theme from "../styles/theme";
+import { inlineStyles, flexStyles } from "./AssignmentTexter/StyleControls";
// This is because the Toolbar from material-ui seems to only apply the correct margins if the
// immediate child is a Button or other type it recognizes. Can get rid of this if we remove material-ui
@@ -16,11 +18,27 @@ class SendButton extends Component {
render() {
return (
-
@@ -30,7 +48,8 @@ class SendButton extends Component {
SendButton.propTypes = {
onFinalTouchTap: PropTypes.func,
- disabled: PropTypes.bool
+ disabled: PropTypes.bool,
+ doneFirstClick: PropTypes.bool
};
export default SendButton;
diff --git a/src/components/TexterFrequentlyAskedQuestions.jsx b/src/components/TexterFrequentlyAskedQuestions.jsx
index bc8c1cc85..59908f87c 100644
--- a/src/components/TexterFrequentlyAskedQuestions.jsx
+++ b/src/components/TexterFrequentlyAskedQuestions.jsx
@@ -1,3 +1,24 @@
+// Spoke: A mass-contact text/SMS peer-to-peer messaging tool
+// Copyright (c) 2016-2021 MoveOn Civic Action
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3 as
+// published by the Free Software Foundation,
+// with the Additional Term under Section 7(b) to include preserving
+// the following author attribution statement in the Spoke application:
+//
+// Spoke is developed and maintained by people committed to fighting
+// oppressive systems and structures, including economic injustice,
+// racism, patriarchy, and militarism
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program (see ./LICENSE). If not, see
.
+
import React from "react";
import PropTypes from "prop-types";
import { Card, CardTitle, CardText } from "material-ui/Card";
@@ -7,13 +28,23 @@ const TexterFaqs = ({ faqs }) => {
Frequently Asked Questions
{faqs.map((faq, idx) => (
-
+
{faq.answer}
))}
+
+
+
+
+ Spoke is developed and maintained by people committed to fighting
+ oppressive systems and structures, including economic injustice,
+ racism, patriarchy, and militarism.
+
+
+
);
};
diff --git a/src/containers/AdminCampaignEdit.jsx b/src/containers/AdminCampaignEdit.jsx
index 82de2f18a..5bdd612a0 100644
--- a/src/containers/AdminCampaignEdit.jsx
+++ b/src/containers/AdminCampaignEdit.jsx
@@ -759,7 +759,9 @@ export class AdminCampaignEdit extends React.Component {
.fullyConfigured;
const { isArchived } = this.props.campaignData.campaign;
const settingsLink = `/admin/${this.props.organizationData.organization.id}/settings`;
- let isCompleted = this.props.campaignData.campaign.pendingJobs.length === 0;
+ let isCompleted = !this.props.campaignData.campaign.pendingJobs.filter(
+ j => j.status >= 0
+ ).length;
this.sections().forEach(section => {
if (
(section.blocksStarting && !this.checkSectionCompleted(section)) ||
diff --git a/src/containers/AdminCampaignList.jsx b/src/containers/AdminCampaignList.jsx
index 79be990ac..bc7774945 100644
--- a/src/containers/AdminCampaignList.jsx
+++ b/src/containers/AdminCampaignList.jsx
@@ -157,7 +157,6 @@ export class AdminCampaignList extends React.Component {
onSearchRequested={this.handleSearchRequested}
searchString={this.state.campaignsFilter.searchString}
onCancelSearch={this.handleCancelSearch}
- hintText="Search for campaign title. Hit enter to search."
/>
)
@@ -373,6 +372,9 @@ const campaignInfoFragment = `
description
timezone
dueBy
+ organization {
+ id
+ }
creator {
displayName
}
diff --git a/src/containers/AdminCampaignStats.jsx b/src/containers/AdminCampaignStats.jsx
index e468e8c40..56ddac21c 100644
--- a/src/containers/AdminCampaignStats.jsx
+++ b/src/containers/AdminCampaignStats.jsx
@@ -177,7 +177,7 @@ class AdminCampaignStats extends React.Component {
}
render() {
- const { data, params } = this.props;
+ const { data, params, organizationData } = this.props;
const { adminPerms, organizationId, campaignId } = params;
const campaign = data.campaign;
const currentExportJob = this.props.data.campaign.pendingJobs.filter(
@@ -327,6 +327,36 @@ class AdminCampaignStats extends React.Component {