feature/IO-3182-Phone-Number-Consent - Checkpoint
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Input, Table } from "antd";
|
import { Input, Space, Table, Typography } from "antd";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { GET_PHONE_NUMBER_OPT_OUTS, SEARCH_OWNERS_BY_PHONE_NUMBERS } from "../../graphql/phone-number-opt-out.queries";
|
import { GET_PHONE_NUMBER_OPT_OUTS, SEARCH_OWNERS_BY_PHONE_NUMBERS } from "../../graphql/phone-number-opt-out.queries";
|
||||||
|
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||||
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
|
const { Paragraph } = Typography; // Destructure Paragraph from Typography
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -44,18 +47,6 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
|||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Format owner names for display
|
|
||||||
const formatOwnerName = (owner) => {
|
|
||||||
const parts = [];
|
|
||||||
if (owner.ownr_fn || owner.ownr_ln) {
|
|
||||||
parts.push([owner.ownr_fn, owner.ownr_ln].filter(Boolean).join(" "));
|
|
||||||
}
|
|
||||||
if (owner.ownr_co_nm) {
|
|
||||||
parts.push(owner.ownr_co_nm);
|
|
||||||
}
|
|
||||||
return parts.join(", ") || "-";
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map phone numbers to their associated owners and identify phone field
|
// Map phone numbers to their associated owners and identify phone field
|
||||||
const getAssociatedOwners = (phoneNumber) => {
|
const getAssociatedOwners = (phoneNumber) => {
|
||||||
if (!ownersData?.owners) return [];
|
if (!ownersData?.owners) return [];
|
||||||
@@ -102,15 +93,20 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
|||||||
}
|
}
|
||||||
return owners.map((owner) => (
|
return owners.map((owner) => (
|
||||||
<div key={owner.id}>
|
<div key={owner.id}>
|
||||||
{formatOwnerName(owner)} ({owner.phoneField})
|
<Space direction="horizontal">
|
||||||
|
<Link to={"/manage/owners/" + owner.id}>
|
||||||
|
<OwnerNameDisplay ownerObject={owner} />
|
||||||
|
</Link>
|
||||||
|
({owner.phoneField})
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
sorter: (a, b) => {
|
sorter: (a, b) => {
|
||||||
const aOwners = getAssociatedOwners(a.phone_number);
|
const aOwners = getAssociatedOwners(a.phone_number);
|
||||||
const bOwners = getAssociatedOwners(b.phone_number);
|
const bOwners = getAssociatedOwners(b.phone_number);
|
||||||
const aName = aOwners[0] ? formatOwnerName(aOwners[0]) : "";
|
const aName = aOwners[0] ? `${aOwners[0].ownr_fn} ${aOwners[0].ownr_ln}` : "";
|
||||||
const bName = bOwners[0] ? formatOwnerName(bOwners[0]) : "";
|
const bName = bOwners[0] ? `${bOwners[0].ownr_fn} ${bOwners[0].ownr_ln}` : "";
|
||||||
return aName.localeCompare(bName);
|
return aName.localeCompare(bName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -124,6 +120,7 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Paragraph>{t("consent.text_body")}</Paragraph>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
onSearch={(value) => setSearch(value)}
|
onSearch={(value) => setSearch(value)}
|
||||||
|
|||||||
@@ -92,13 +92,15 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Consent Settings tab
|
if (bodyshop.messagingservicesid) {
|
||||||
items.push({
|
// Add Consent Settings tab
|
||||||
key: "consent",
|
items.push({
|
||||||
label: t("bodyshop.labels.consent_settings"),
|
key: "consent",
|
||||||
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
|
label: t("bodyshop.labels.consent_settings"),
|
||||||
});
|
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RbacWrapper action="shop:config">
|
<RbacWrapper action="shop:config">
|
||||||
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />
|
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />
|
||||||
|
|||||||
@@ -3875,7 +3875,8 @@
|
|||||||
"created_at": "Opt-Out Date",
|
"created_at": "Opt-Out Date",
|
||||||
"no_owners": "No Associated Owners",
|
"no_owners": "No Associated Owners",
|
||||||
"phone_1": "Phone 1",
|
"phone_1": "Phone 1",
|
||||||
"phone_2": "Phone 2"
|
"phone_2": "Phone 2",
|
||||||
|
"text_body": "Users can opt out of receiving SMS messages by replying with keywords such as STOP, UNSUBSCRIBE, CANCEL, END, QUIT, STOPALL, REVOKE and OPTOUT. To opt back in, users can reply with START, YES, or UNSTOP. Even after opting out, users can still send messages to us, which will be received and processed as needed. Ensure customers are informed to reply with these keywords to manage their messaging preferences. After opting out, users receive a confirmation message and will not receive further messages until they opt back in."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Phone Number Opt-Out List"
|
"title": "Phone Number Opt-Out List"
|
||||||
|
|||||||
@@ -3877,7 +3877,8 @@
|
|||||||
"created_at": "",
|
"created_at": "",
|
||||||
"no_owners": "",
|
"no_owners": "",
|
||||||
"phone_1": "",
|
"phone_1": "",
|
||||||
"phone_2": ""
|
"phone_2": "",
|
||||||
|
"text_body": ""
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": ""
|
"title": ""
|
||||||
|
|||||||
@@ -3877,7 +3877,8 @@
|
|||||||
"created_at": "Opt-Out Date",
|
"created_at": "Opt-Out Date",
|
||||||
"no_owners": "No Associated Owners",
|
"no_owners": "No Associated Owners",
|
||||||
"phone_1": "Phone 1",
|
"phone_1": "Phone 1",
|
||||||
"phone_2": "Phone 2"
|
"phone_2": "Phone 2",
|
||||||
|
"text_body": ""
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": ""
|
"title": ""
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ const { phone } = require("phone");
|
|||||||
const { admin } = require("../firebase/firebase-handler");
|
const { admin } = require("../firebase/firebase-handler");
|
||||||
const InstanceManager = require("../utils/instanceMgr").default;
|
const InstanceManager = require("../utils/instanceMgr").default;
|
||||||
|
|
||||||
|
/ TWILLIO KEYWORDS;
|
||||||
|
// Note: When we handle different languages, we might need to adjust these keywords accordingly.
|
||||||
|
const optInKeywords = ["START", "YES", "UNSTOP"];
|
||||||
|
const optOutKeywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT", "REVOKE", "OPTOUT"];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receive SMS messages from Twilio and process them
|
* Receive SMS messages from Twilio and process them
|
||||||
* @param req
|
* @param req
|
||||||
@@ -58,9 +63,6 @@ const receive = async (req, res) => {
|
|||||||
const messageText = (req.body.Body || "").trim().toUpperCase();
|
const messageText = (req.body.Body || "").trim().toUpperCase();
|
||||||
|
|
||||||
// Step 2: Check for opt-in or opt-out keywords
|
// Step 2: Check for opt-in or opt-out keywords
|
||||||
const optInKeywords = ["START", "YES", "UNSTOP"];
|
|
||||||
const optOutKeywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"];
|
|
||||||
|
|
||||||
if (optInKeywords.includes(messageText) || optOutKeywords.includes(messageText)) {
|
if (optInKeywords.includes(messageText) || optOutKeywords.includes(messageText)) {
|
||||||
// Check if the phone number is in phone_number_opt_out
|
// Check if the phone number is in phone_number_opt_out
|
||||||
const optOutCheck = await client.request(CHECK_PHONE_NUMBER_OPT_OUT, {
|
const optOutCheck = await client.request(CHECK_PHONE_NUMBER_OPT_OUT, {
|
||||||
|
|||||||
Reference in New Issue
Block a user