feature/IO-3182-Phone-Number-Consent - Checkpoint

This commit is contained in:
Dave Richer
2025-05-21 14:32:35 -04:00
parent 7bd5190bf2
commit 8ee52598e8
31 changed files with 128 additions and 991 deletions

View File

@@ -1,5 +1,5 @@
import { Badge, Card, List, Space, Tag } from "antd";
import { useEffect, useState, useMemo } from "react";
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { Virtuoso } from "react-virtuoso";
import { createStructuredSelector } from "reselect";
@@ -10,61 +10,35 @@ import PhoneFormatter from "../../utils/PhoneFormatter";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import _ from "lodash";
import "./chat-conversation-list.styles.scss";
import { useQuery } from "@apollo/client";
import { GET_PHONE_NUMBER_CONSENTS } from "../../graphql/consent.queries";
import { phone } from "phone";
import { useTranslation } from "react-i18next";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
bodyshop: selectBodyshop
selectedConversation: selectSelectedConversation
});
const mapDispatchToProps = (dispatch) => ({
setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId))
});
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
const { t } = useTranslation();
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) {
// That comma is there for a reason, do not remove it
const [, forceUpdate] = useState(false);
const enforceConsent = bodyshop?.enforce_sms_consent ?? false;
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
const { data: consentData, loading: consentLoading } = useQuery(GET_PHONE_NUMBER_CONSENTS, {
variables: {
bodyshopid: conversationList[0]?.bodyshopid,
phone_numbers: phoneNumbers
},
skip: !enforceConsent || !conversationList.length || !conversationList[0]?.bodyshopid,
fetchPolicy: "cache-and-network"
});
const consentMap = useMemo(() => {
const map = new Map();
consentData?.phone_number_consent?.forEach((consent) => {
map.set(consent.phone_number, consent.consent_status);
});
return map;
}, [consentData]);
// Re-render every minute
useEffect(() => {
const interval = setInterval(() => {
forceUpdate((prev) => !prev);
}, 60000);
return () => clearInterval(interval);
forceUpdate((prev) => !prev); // Toggle state to trigger re-render
}, 60000); // 1 minute in milliseconds
return () => clearInterval(interval); // Cleanup on unmount
}, []);
const sortedConversationList = useMemo(() => {
// Memoize the sorted conversation list
const sortedConversationList = React.useMemo(() => {
return _.orderBy(conversationList, ["updated_at"], ["desc"]);
}, [conversationList]);
const renderConversation = (index, t) => {
const renderConversation = (index) => {
const item = sortedConversationList[index];
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
const isConsented = enforceConsent ? (consentMap.get(normalizedPhone) ?? false) : true;
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
const cardContentLeft =
item.job_conversations.length > 0
@@ -86,12 +60,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
</>
);
const cardExtra = (
<>
<Badge count={item.messages_aggregate.aggregate.count} />
{enforceConsent && !isConsented && <Tag color="red">{t("messaging.labels.no_consent")}</Tag>}
</>
);
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count} />;
const getCardStyle = () =>
item.id === selectedConversation
@@ -104,25 +73,9 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`}
>
<Card style={getCardStyle()} variant={true} size="small" extra={cardExtra} title={cardTitle}>
<div
style={{
display: "inline-block",
width: "70%",
textAlign: "left"
}}
>
{cardContentLeft}
</div>
<div
style={{
display: "inline-block",
width: "30%",
textAlign: "right"
}}
>
{cardContentRight}
</div>
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div>
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div>
</Card>
</List.Item>
);
@@ -132,7 +85,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
<div className="chat-list-container">
<Virtuoso
data={sortedConversationList}
itemContent={(index) => renderConversation(index, t)}
itemContent={(index) => renderConversation(index)}
style={{ height: "100%", width: "100%" }}
/>
</div>