feature/IO-3499-React-19 - Phone Number Formatter / Chat Open Button / Chat Affix container
This commit is contained in:
@@ -12,11 +12,16 @@ export function ChatAffixContainer({ bodyshop, chatVisible, currentUser }) {
|
|||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
|
|
||||||
// 1) FCM subscription (independent of socket handler registration)
|
const messagingServicesId = bodyshop?.messagingservicesid;
|
||||||
useEffect(() => {
|
const bodyshopId = bodyshop?.id;
|
||||||
if (!bodyshop?.messagingservicesid) return;
|
const imexshopid = bodyshop?.imexshopid;
|
||||||
|
|
||||||
async function subscribeToTopicForFCMNotification() {
|
const messagingEnabled = Boolean(messagingServicesId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!messagingEnabled) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
try {
|
try {
|
||||||
await requestForToken();
|
await requestForToken();
|
||||||
await axios.post("/notifications/subscribe", {
|
await axios.post("/notifications/subscribe", {
|
||||||
@@ -24,23 +29,19 @@ export function ChatAffixContainer({ bodyshop, chatVisible, currentUser }) {
|
|||||||
vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY
|
vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY
|
||||||
}),
|
}),
|
||||||
type: "messaging",
|
type: "messaging",
|
||||||
imexshopid: bodyshop.imexshopid
|
imexshopid
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error attempting to subscribe to messaging topic: ", error);
|
console.log("Error attempting to subscribe to messaging topic: ", error);
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
|
}, [messagingEnabled, imexshopid]);
|
||||||
|
|
||||||
subscribeToTopicForFCMNotification();
|
|
||||||
}, [bodyshop?.messagingservicesid, bodyshop?.imexshopid]);
|
|
||||||
|
|
||||||
// 2) Register socket handlers as soon as socket is connected (regardless of chatVisible)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
if (!bodyshop?.messagingservicesid) return;
|
if (!messagingEnabled) return;
|
||||||
if (!bodyshop?.id) return;
|
if (!bodyshopId) return;
|
||||||
|
|
||||||
// If socket isn't connected yet, ensure no stale handlers remain.
|
|
||||||
if (!socket.connected) {
|
if (!socket.connected) {
|
||||||
unregisterMessagingHandlers({ socket });
|
unregisterMessagingHandlers({ socket });
|
||||||
return;
|
return;
|
||||||
@@ -56,16 +57,14 @@ export function ChatAffixContainer({ bodyshop, chatVisible, currentUser }) {
|
|||||||
bodyshop
|
bodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => unregisterMessagingHandlers({ socket });
|
||||||
unregisterMessagingHandlers({ socket });
|
}, [socket, messagingEnabled, bodyshopId, client, currentUser?.email, bodyshop]);
|
||||||
};
|
|
||||||
}, [socket, socket?.connected, bodyshop?.id, bodyshop?.messagingservicesid, client, currentUser?.email]);
|
|
||||||
|
|
||||||
if (!bodyshop?.messagingservicesid) return <></>;
|
if (!messagingEnabled) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||||
{bodyshop?.messagingservicesid ? <ChatPopupComponent /> : null}
|
{messagingEnabled ? <ChatPopupComponent /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
|
import { useCallback, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|
||||||
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
searchingForConversation: searchingForConversation
|
searchingForConversation
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone))
|
openChatByPhone: (payload) => dispatch(openChatByPhone(payload))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, type, jobid, openChatByPhone }) {
|
export function ChatOpenButton({ bodyshop, searchingForConversation, phone, type, jobid, openChatByPhone }) {
|
||||||
@@ -24,31 +25,59 @@ export function ChatOpenButton({ bodyshop, searchingForConversation, phone, type
|
|||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
if (!phone) return <></>;
|
if (!phone) return null;
|
||||||
|
|
||||||
if (!bodyshop.messagingservicesid) {
|
const messagingEnabled = Boolean(bodyshop?.messagingservicesid);
|
||||||
return <PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const parsed = useMemo(() => {
|
||||||
|
if (!messagingEnabled) return null;
|
||||||
|
try {
|
||||||
|
return parsePhoneNumber(phone, "CA") || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [messagingEnabled, phone]);
|
||||||
|
|
||||||
|
const isValid = Boolean(parsed?.isValid?.() && parsed.isValid());
|
||||||
|
const clickable = messagingEnabled && !searchingForConversation && isValid;
|
||||||
|
|
||||||
|
const onClick = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!messagingEnabled) return;
|
||||||
|
if (searchingForConversation) return;
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
notification.error({ title: t("messaging.error.invalidphone") });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openChatByPhone({
|
||||||
|
phone_num: parsed.formatInternational(),
|
||||||
|
jobid,
|
||||||
|
socket
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[messagingEnabled, searchingForConversation, isValid, parsed, jobid, socket, openChatByPhone, notification, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = <PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>;
|
||||||
|
|
||||||
|
// If not clickable, render plain formatted text (no link styling)
|
||||||
|
if (!clickable) return content;
|
||||||
|
|
||||||
|
// Clickable: render as a link-styled button (best for a “command”)
|
||||||
return (
|
return (
|
||||||
<a
|
<Button
|
||||||
href="# "
|
type="link"
|
||||||
onClick={(e) => {
|
onClick={onClick}
|
||||||
e.preventDefault();
|
className="chat-open-button-link"
|
||||||
e.stopPropagation();
|
aria-label={t("messaging.actions.openchat") || "Open chat"}
|
||||||
|
|
||||||
if (searchingForConversation) return; // Prevent finding the same thing twice.
|
|
||||||
|
|
||||||
const p = parsePhoneNumber(phone, "CA");
|
|
||||||
if (p && p.isValid()) {
|
|
||||||
openChatByPhone({ phone_num: p.formatInternational(), jobid, socket });
|
|
||||||
} else {
|
|
||||||
notification.error({ title: t("messaging.error.invalidphone") });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<PhoneNumberFormatter type={type}>{phone}</PhoneNumberFormatter>
|
{content}
|
||||||
</a>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2431,7 +2431,8 @@
|
|||||||
"messaging": {
|
"messaging": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"link": "Link to Job",
|
"link": "Link to Job",
|
||||||
"new": "New Conversation"
|
"new": "New Conversation",
|
||||||
|
"openchat": "Open Chat"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidphone": "The phone number is invalid. Unable to open conversation. ",
|
"invalidphone": "The phone number is invalid. Unable to open conversation. ",
|
||||||
|
|||||||
@@ -2428,7 +2428,8 @@
|
|||||||
"messaging": {
|
"messaging": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"link": "",
|
"link": "",
|
||||||
"new": ""
|
"new": "",
|
||||||
|
"openchat": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidphone": "",
|
"invalidphone": "",
|
||||||
|
|||||||
@@ -2428,7 +2428,9 @@
|
|||||||
"messaging": {
|
"messaging": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"link": "",
|
"link": "",
|
||||||
"new": ""
|
"new": "",
|
||||||
|
|
||||||
|
"openchat": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidphone": "",
|
"invalidphone": "",
|
||||||
|
|||||||
@@ -11,13 +11,8 @@ export default function PhoneNumberFormatter({ children, type }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<Text>{phone}</Text>
|
<span>{phone}</span>
|
||||||
{type ? (
|
{type ? <Text type="secondary"> ({type})</Text> : null}
|
||||||
<>
|
|
||||||
{" "}
|
|
||||||
<Text type="secondary">({type})</Text>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user