From c964432d9f1e20634540134e255bcfbe793f3d5d Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sun, 5 Jun 2022 22:57:40 +0100 Subject: [PATCH 01/21] feat(messages): Store user that created group/chat --- import.sql | 1 + messageUpdate.sql | 1 + .../apps/messages/components/form/NewMessageGroupForm.tsx | 1 + phone/src/apps/messages/hooks/useMessageAPI.ts | 2 ++ phone/src/apps/messages/hooks/useMessageService.ts | 1 + phone/src/apps/messages/utils/constants.ts | 2 ++ resources/server/messages/messages.db.ts | 6 ++++-- resources/server/messages/messages.service.ts | 3 +++ typings/messages.ts | 2 ++ 9 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 messageUpdate.sql diff --git a/import.sql b/import.sql index a9fc82348..36a0208ce 100644 --- a/import.sql +++ b/import.sql @@ -156,6 +156,7 @@ CREATE TABLE `npwd_messages_conversations` `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(), `updatedAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(), `last_message_id` INT(11) NULL DEFAULT NULL, + `createdBy` varchar(48) NOT NULL DEFAULT '' `is_group_chat` TINYINT(4) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ); diff --git a/messageUpdate.sql b/messageUpdate.sql new file mode 100644 index 000000000..b6bb9edf4 --- /dev/null +++ b/messageUpdate.sql @@ -0,0 +1 @@ +ALTER TABLE npwd_messages_conversations ADD COLUMN `createdBy` varchar(48) NOT NULL DEFAULT '' \ No newline at end of file diff --git a/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx b/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx index a9f71bd20..f3b6c14a8 100644 --- a/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx +++ b/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx @@ -31,6 +31,7 @@ const NewMessageGroupForm = ({ phoneNumber }: { phoneNumber?: string }) => { conversationLabel: isGroupChat ? conversationLabel : '', participants: [myPhoneNumber, ...selectedParticipants], isGroupChat, + createdBy: myPhoneNumber, }; addConversation(dto); diff --git a/phone/src/apps/messages/hooks/useMessageAPI.ts b/phone/src/apps/messages/hooks/useMessageAPI.ts index ed31e7462..b96f026fd 100644 --- a/phone/src/apps/messages/hooks/useMessageAPI.ts +++ b/phone/src/apps/messages/hooks/useMessageAPI.ts @@ -136,6 +136,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { conversationLabel: conversation.conversationLabel, participants: conversation.participants, isGroupChat: conversation.isGroupChat, + createdBy: conversation.createdBy, }, ).then((resp) => { if (resp.status !== 'ok') { @@ -178,6 +179,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { isGroupChat: resp.data.isGroupChat, unread: 0, unreadCount: 0, + createdBy: resp.data.createdBy, }); history.push(`/messages`); diff --git a/phone/src/apps/messages/hooks/useMessageService.ts b/phone/src/apps/messages/hooks/useMessageService.ts index 3eb49e4ec..1a0febc9d 100644 --- a/phone/src/apps/messages/hooks/useMessageService.ts +++ b/phone/src/apps/messages/hooks/useMessageService.ts @@ -42,6 +42,7 @@ export const useMessagesService = () => { conversationList: conversation.conversationList, label: conversation.label, unread: 0, + createdBy: conversation.createdBy, }); }, [updateLocalConversations], diff --git a/phone/src/apps/messages/utils/constants.ts b/phone/src/apps/messages/utils/constants.ts index fa746b30f..9c5059d4a 100644 --- a/phone/src/apps/messages/utils/constants.ts +++ b/phone/src/apps/messages/utils/constants.ts @@ -10,6 +10,7 @@ export const MockMessageConversations: MessageConversation[] = [ label: '', updatedAt: 5, isGroupChat: false, + createdBy: '111-1134', }, { id: 2, @@ -19,6 +20,7 @@ export const MockMessageConversations: MessageConversation[] = [ label: 'Secret Project Error chat', updatedAt: 5, isGroupChat: true, + createdBy: '111-1134', }, ]; diff --git a/resources/server/messages/messages.db.ts b/resources/server/messages/messages.db.ts index 507f87ad7..3ef25aa42 100644 --- a/resources/server/messages/messages.db.ts +++ b/resources/server/messages/messages.db.ts @@ -72,9 +72,10 @@ export class _MessagesDB { conversationList: string, conversationLabel: string, isGroupChat: boolean, + createdBy: string, ) { - const conversationQuery = `INSERT INTO npwd_messages_conversations (conversation_list, label, is_group_chat) - VALUES (?, ?, ?)`; + const conversationQuery = `INSERT INTO npwd_messages_conversations (conversation_list, label, is_group_chat, createdBy) + VALUES (?, ?, ?, ?)`; const participantQuery = `INSERT INTO npwd_messages_participants (conversation_id, participant) VALUES (?, ?)`; @@ -82,6 +83,7 @@ export class _MessagesDB { conversationList, isGroupChat ? conversationLabel : '', isGroupChat, + createdBy, ]); const result = results; diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 8e1cbf019..5052ddc0f 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -76,6 +76,7 @@ class _MessagesService { label: conversation.conversationLabel, conversationList, isGroupChat: conversation.isGroupChat, + createdBy: conversation.createdBy, }; return resp({ status: 'ok', data: { ...respData, participant: playerPhoneNumber } }); @@ -88,6 +89,7 @@ class _MessagesService { conversationList, conversation.conversationLabel, conversation.isGroupChat, + conversation.createdBy, ); // Return data @@ -96,6 +98,7 @@ class _MessagesService { label: conversation.conversationLabel, conversationList, isGroupChat: conversation.isGroupChat, + createdBy: conversation.createdBy, }; resp({ status: 'ok', data: { ...respData, participant: playerPhoneNumber } }); diff --git a/typings/messages.ts b/typings/messages.ts index e6de0d62e..0c1fcddef 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -35,12 +35,14 @@ export interface MessageConversation { unread?: number; unreadCount?: number; updatedAt?: number; + createdBy: string; } export interface PreDBConversation { participants: string[]; conversationLabel: string; isGroupChat: boolean; + createdBy: string; } export interface MessagesRequest { From 767d0e67ae0021852e59460cc40050359e069413 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sun, 5 Jun 2022 23:23:48 +0100 Subject: [PATCH 02/21] fix(messages): createdBy errors --- resources/server/messages/messages.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 5052ddc0f..94e419072 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -181,6 +181,7 @@ class _MessagesService { label: conversationDetails.label, conversationList: conversationDetails.conversationList, isGroupChat: conversationDetails.isGroupChat, + createdBy: conversationDetails.createdBy, }; // participantId is the participants phone number @@ -313,6 +314,7 @@ class _MessagesService { conversationList, '', false, + senderNumber, ); await this.messagesDB.addParticipantToConversation(conversationList, targetNumber); @@ -326,6 +328,7 @@ class _MessagesService { label: '', isGroupChat: false, participant: targetNumber, + createdBy: senderNumber, }, participantPlayer.source, ); From 7096ea554bfa72e2062114b44034d3317bd9ea15 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Mon, 6 Jun 2022 02:11:40 +0100 Subject: [PATCH 03/21] feat(messages): remove group member --- .../components/modal/GroupDetailsModal.tsx | 26 ++++++++-- .../components/modal/MessageModal.tsx | 11 +++++ .../src/apps/messages/hooks/useMessageAPI.ts | 26 ++++++++++ .../apps/messages/hooks/useMessageActions.ts | 24 ++++++++++ .../apps/messages/hooks/useMessageService.ts | 32 +++++++++++-- phone/src/locale/en.json | 3 +- resources/client/cl_messages.ts | 10 ++++ .../server/messages/messages.controller.ts | 13 +++++ resources/server/messages/messages.db.ts | 12 +++++ resources/server/messages/messages.service.ts | 48 +++++++++++++++++++ typings/messages.ts | 13 +++++ 11 files changed, 209 insertions(+), 9 deletions(-) diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index 3b6411607..001433cd4 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -3,6 +3,7 @@ import Modal from '@ui/components/Modal'; import { Box, Button, Stack, Typography } from '@mui/material'; import PersonIcon from '@mui/icons-material/Person'; import PersonAddIcon from '@mui/icons-material/PersonAdd'; +import PersonRemoveIcon from '@mui/icons-material/PersonRemove'; import { findParticipants } from '../../utils/helpers'; import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; import { useContactActions } from '../../../contacts/hooks/useContactActions'; @@ -11,14 +12,18 @@ interface GroupDetailsModalProps { open: boolean; onClose: () => void; conversationList: string; + createdBy: string; addContact: (number: any) => void; + removeMember: (number: any) => void; } const GroupDetailsModal: React.FC = ({ open, onClose, conversationList, + createdBy, addContact, + removeMember, }) => { const myPhoneNumber = useMyPhoneNumber(); const { getContactByNumber } = useContactActions(); @@ -33,6 +38,10 @@ const GroupDetailsModal: React.FC = ({ addContact(participant); }; + const handleGroupRemove = (participant: string) => { + removeMember(participant); + }; + return ( @@ -51,11 +60,18 @@ const GroupDetailsModal: React.FC = ({ {contact?.display ?? participant} - {!contact && ( - - )} + + {!contact && ( + + )} + {myPhoneNumber === createdBy && ( + + )} + ); diff --git a/phone/src/apps/messages/components/modal/MessageModal.tsx b/phone/src/apps/messages/components/modal/MessageModal.tsx index ed35c1505..6437cd7c3 100644 --- a/phone/src/apps/messages/components/modal/MessageModal.tsx +++ b/phone/src/apps/messages/components/modal/MessageModal.tsx @@ -67,6 +67,7 @@ export const MessageModal = () => { const { fetchMessages } = useMessageAPI(); const { getLabelOrContact, getConversationParticipant } = useMessageActions(); const { initializeCall } = useCall(); + const { removeGroupMember } = useMessageAPI(); const { getContactByNumber } = useContactActions(); const [messages, setMessages] = useMessagesState(); @@ -143,6 +144,14 @@ export const MessageModal = () => { return history.push(`/contacts/-1/?addNumber=${number}&referal=${referal}`); }; + const handleGroupRemove = (number: string) => { + removeGroupMember( + activeMessageConversation.conversationList, + activeMessageConversation.id, + number, + ); + }; + // This only gets used for 1 on 1 conversations let conversationList = activeMessageConversation.conversationList.split('+'); conversationList = conversationList.filter((targetNumber) => targetNumber !== myPhoneNumber); @@ -168,7 +177,9 @@ export const MessageModal = () => { open={isGroupModalOpen} onClose={closeGroupModal} conversationList={activeMessageConversation.conversationList} + createdBy={activeMessageConversation.createdBy} addContact={handleAddContact} + removeMember={handleGroupRemove} /> {isGroupModalOpen && } void; fetchMessages: (conversationId: string, page: number) => void; setMessageRead: (conversationId: number) => void; + removeGroupMember: ( + conversationList: string, + conversationId: number, + phoneNumber: string, + ) => void; }; export const useMessageAPI = (): UseMessageAPIProps => { @@ -35,6 +40,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { deleteLocalMessage, updateLocalConversations, removeLocalConversation, + removeLocalGroupMember, setMessageReadState, } = useMessageActions(); const history = useHistory(); @@ -238,6 +244,25 @@ export const useMessageAPI = (): UseMessageAPIProps => { [setMessages, addAlert, t, history], ); + const removeGroupMember = useCallback( + (conversationList: string, conversationId: number, phoneNumber: string) => { + fetchNui>(MessageEvents.REMOVE_GROUP_MEMBER, { + conversationList, + conversationId, + phoneNumber, + }).then((resp) => { + if (resp.status !== 'ok') { + return addAlert({ + message: t('MESSAGES.FEEDBACK.REMOVE_GROUP_MEMBER_FAILED'), + type: 'error', + }); + } + removeLocalGroupMember(conversationId, phoneNumber); + }); + }, + [addAlert, removeLocalGroupMember, t], + ); + return { sendMessage, deleteMessage, @@ -246,5 +271,6 @@ export const useMessageAPI = (): UseMessageAPIProps => { fetchMessages, sendEmbedMessage, setMessageRead, + removeGroupMember, }; }; diff --git a/phone/src/apps/messages/hooks/useMessageActions.ts b/phone/src/apps/messages/hooks/useMessageActions.ts index 1055ae6f4..e0cc13d24 100644 --- a/phone/src/apps/messages/hooks/useMessageActions.ts +++ b/phone/src/apps/messages/hooks/useMessageActions.ts @@ -3,6 +3,7 @@ import { useConversationId, useSetMessageConversations, useSetMessages, + useMessageConversationsValue, } from './state'; import { useCallback } from 'react'; import { Message, MessageConversation } from '@typings/messages'; @@ -19,6 +20,7 @@ interface MessageActionProps { setMessageReadState: (conversationId: number, unreadCount: number) => void; getLabelOrContact: (messageConversation: MessageConversation) => string; getConversationParticipant: (conversationList: string) => Contact | null; + removeLocalGroupMember: (conversationId: number, phoneNumber: string) => void; } export const useMessageActions = (): MessageActionProps => { @@ -91,6 +93,27 @@ export const useMessageActions = (): MessageActionProps => { [setMessageConversation, conversationLoading, conversations], ); + const removeLocalGroupMember = useCallback( + (conversationsId: number, phoneNumber: string) => { + setMessageConversation((curVal) => + curVal.map((conversation) => { + if (conversation.id === conversationsId) { + const conversationListRemove = conversation.conversationList + .split('+') + .filter((number) => number !== phoneNumber) + .join('+'); + return { + ...conversation, + conversationList: conversationListRemove, + }; + } + return conversation; + }), + ); + }, + [setMessageConversation], + ); + const updateLocalMessages = useCallback( (messageDto: Message) => { if (messageLoading !== 'hasValue') return; @@ -136,5 +159,6 @@ export const useMessageActions = (): MessageActionProps => { setMessageReadState, getLabelOrContact, getConversationParticipant, + removeLocalGroupMember, }; }; diff --git a/phone/src/apps/messages/hooks/useMessageService.ts b/phone/src/apps/messages/hooks/useMessageService.ts index 1a0febc9d..fa38aeaf5 100644 --- a/phone/src/apps/messages/hooks/useMessageService.ts +++ b/phone/src/apps/messages/hooks/useMessageService.ts @@ -1,5 +1,10 @@ import { useNuiEvent } from 'fivem-nui-react-lib'; -import { Message, MessageConversation, MessageEvents } from '@typings/messages'; +import { + Message, + MessageConversation, + MessageEvents, + RemoveGroupMemberResponse, +} from '@typings/messages'; import { useMessageActions } from './useMessageActions'; import { useCallback } from 'react'; import { useMessageNotifications } from './useMessageNotifications'; @@ -7,8 +12,13 @@ import { useLocation } from 'react-router'; import { useActiveMessageConversation } from './state'; export const useMessagesService = () => { - const { updateLocalMessages, updateLocalConversations, setMessageReadState } = - useMessageActions(); + const { + updateLocalMessages, + updateLocalConversations, + setMessageReadState, + removeLocalConversation, + removeLocalGroupMember, + } = useMessageActions(); const { setNotification } = useMessageNotifications(); const { pathname } = useLocation(); const activeConversation = useActiveMessageConversation(); @@ -48,7 +58,23 @@ export const useMessagesService = () => { [updateLocalConversations], ); + const handleDeleteConversation = useCallback( + (conversationId: number[]) => { + removeLocalConversation(conversationId); + }, + [removeLocalConversation], + ); + + const handleRemoveGroupMember = useCallback( + (conversation: RemoveGroupMemberResponse) => { + removeLocalGroupMember(conversation.conversationId, conversation.phoneNumber); + }, + [removeLocalGroupMember], + ); + useNuiEvent('MESSAGES', MessageEvents.CREATE_MESSAGE_BROADCAST, handleMessageBroadcast); useNuiEvent('MESSAGES', MessageEvents.SEND_MESSAGE_SUCCESS, handleUpdateMessages); useNuiEvent('MESSAGES', MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, handleAddConversation); + useNuiEvent('MESSAGES', MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, handleDeleteConversation); + useNuiEvent('MESSAGES', MessageEvents.REMOVE_GROUP_MEMBER_LIST, handleRemoveGroupMember); }; diff --git a/phone/src/locale/en.json b/phone/src/locale/en.json index 0ae74e220..a7b9bacdf 100644 --- a/phone/src/locale/en.json +++ b/phone/src/locale/en.json @@ -219,7 +219,8 @@ "FETCHED_MESSAGES_FAILED": "Failed to retrieve messages", "DELETE_MESSAGE": "Delete message", "DELETE_MESSAGE_FAILED": "Failed to delete message", - "DELETE_CONVERSATION_FAILED": "Failed to delete conversation" + "DELETE_CONVERSATION_FAILED": "Failed to delete conversation", + "REMOVE_GROUP_MEMBER_FAILED": "Failed to remove member" }, "SEARCH_PLACEHOLDER": "Search messages...", "DELETE_CONVERSATION": "Delete conversation", diff --git a/resources/client/cl_messages.ts b/resources/client/cl_messages.ts index c77f72eae..8423fa807 100644 --- a/resources/client/cl_messages.ts +++ b/resources/client/cl_messages.ts @@ -3,6 +3,7 @@ import { MessageConversationResponse, MessageEvents, PreDBMessage, + RemoveGroupMemberResponse, } from '../../typings/messages'; import { sendMessageEvent } from '../utils/messages'; import { RegisterNuiProxy, RegisterNuiCB } from './cl_utils'; @@ -12,6 +13,7 @@ RegisterNuiProxy(MessageEvents.DELETE_MESSAGE); RegisterNuiProxy(MessageEvents.FETCH_MESSAGES); RegisterNuiProxy(MessageEvents.CREATE_MESSAGE_CONVERSATION); RegisterNuiProxy(MessageEvents.DELETE_CONVERSATION); +RegisterNuiProxy(MessageEvents.REMOVE_GROUP_MEMBER); RegisterNuiProxy(MessageEvents.SEND_MESSAGE); RegisterNuiProxy(MessageEvents.SET_MESSAGE_READ); RegisterNuiProxy(MessageEvents.GET_MESSAGE_LOCATION); @@ -31,3 +33,11 @@ onNet(MessageEvents.CREATE_MESSAGE_BROADCAST, (result: CreateMessageBroadcast) = onNet(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, (result: MessageConversationResponse) => { sendMessageEvent(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, result); }); + +onNet(MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, (result: number[]) => { + sendMessageEvent(MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, result); +}); + +onNet(MessageEvents.REMOVE_GROUP_MEMBER_LIST, (result: RemoveGroupMemberResponse) => { + sendMessageEvent(MessageEvents.REMOVE_GROUP_MEMBER_LIST, result); +}); diff --git a/resources/server/messages/messages.controller.ts b/resources/server/messages/messages.controller.ts index ccf79f99c..f8e876a9b 100644 --- a/resources/server/messages/messages.controller.ts +++ b/resources/server/messages/messages.controller.ts @@ -6,6 +6,7 @@ import { MessageEvents, PreDBConversation, PreDBMessage, + RemoveGroupMemberRequest, } from '../../../typings/messages'; import MessagesService from './messages.service'; import { messagesLogger } from './messages.utils'; @@ -113,3 +114,15 @@ onNetPromise(MessageEvents.GET_MESSAGE_LOCATION, async (reqObj, resp) => { messagesLogger.error(`Error occurred in get location event (${src}), Error: ${e.message}`); }); }); + +onNetPromise( + MessageEvents.REMOVE_GROUP_MEMBER, + async (reqObj, resp) => { + MessagesService.handleRemoveGroupMember(reqObj, resp).catch((e) => { + messagesLogger.error( + `Error occurred while removing a group member (${reqObj.source}), Error: ${e.message}`, + ); + resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); + }); + }, +); diff --git a/resources/server/messages/messages.db.ts b/resources/server/messages/messages.db.ts index 3ef25aa42..28f1378de 100644 --- a/resources/server/messages/messages.db.ts +++ b/resources/server/messages/messages.db.ts @@ -34,6 +34,7 @@ export class _MessagesDB { npwd_messages_conversations.conversation_list as conversationList, npwd_messages_conversations.is_group_chat as isGroupChat, npwd_messages_conversations.label, + npwd_messages_conversations.createdBy, UNIX_TIMESTAMP(npwd_messages_conversations.createdAt) as createdAt, UNIX_TIMESTAMP(npwd_messages_conversations.updatedAt) as updatedAt FROM npwd_messages_conversations @@ -171,6 +172,17 @@ export class _MessagesDB { await DbInterface._rawExec(query, [conversationId, phoneNumber]); } + async removeGroupMember(conversationList: string, conversationId: number, phoneNumber: string) { + const updatedConversationList = conversationList + .split('+') + .filter((number) => number != phoneNumber) + .join('+'); + const conversationQuery = `UPDATE npwd_messages_conversations SET conversation_list = ? WHERE id = ?`; + const participantQuery = `DELETE FROM npwd_messages_participants WHERE conversation_id = ? AND participant = ?`; + await DbInterface._rawExec(conversationQuery, [updatedConversationList, conversationId]); + await DbInterface._rawExec(participantQuery, [conversationId, phoneNumber]); + } + async doesConversationExist(conversationList: string): Promise { const query = `SELECT COUNT(*) as count FROM npwd_messages_conversations diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 94e419072..125f62e8b 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -15,6 +15,8 @@ import { PreDBConversation, PreDBMessage, Location, + RemoveGroupMemberRequest, + RemoveGroupMemberResponse, } from '../../../typings/messages'; import PlayerService from '../players/player.service'; import { emitNetTyped } from '../utils/miscUtils'; @@ -289,6 +291,52 @@ class _MessagesService { } } + async handleRemoveGroupMember( + reqObj: PromiseRequest, + resp: PromiseEventResp, + ) { + try { + await this.messagesDB.removeGroupMember( + reqObj.data.conversationList, + reqObj.data.conversationId, + reqObj.data.phoneNumber, + ); + resp({ status: 'ok' }); + const playerPhoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); + const participants = reqObj.data.conversationList.split('+'); + for (const participant of participants) { + if (participant !== playerPhoneNumber) { + const participantIdentifier = await PlayerService.getIdentifierByPhoneNumber(participant); + const participantPlayer = PlayerService.getPlayerFromIdentifier(participantIdentifier); + if (participantPlayer) { + if (participant == reqObj.data.phoneNumber) { + //if the player is the one being removed + emitNetTyped( + MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, + { + conversationID: [reqObj.data.conversationId], + }, + participantPlayer.source, + ); + } else { + //if the player is not the one being removed + emitNetTyped( + MessageEvents.REMOVE_GROUP_MEMBER_LIST, + { + conversationId: reqObj.data.conversationId, + phoneNumber: reqObj.data.phoneNumber, + }, + participantPlayer.source, + ); + } + } + } + } + } catch (err) { + resp({ status: 'error', errorMsg: err.message }); + } + } + // Exports async handleEmitMessage(dto: EmitMessageExportCtx) { const { senderNumber, targetNumber, message } = dto; diff --git a/typings/messages.ts b/typings/messages.ts index 0c1fcddef..c2e347cf2 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -53,6 +53,11 @@ export interface MessagesRequest { export interface DeleteConversationRequest { conversationsId: number[]; } +export interface RemoveGroupMemberRequest { + conversationList: string; + conversationId: number; + phoneNumber: string; +} /** * Used for the raw npwd_messages_groups row responses @@ -114,6 +119,11 @@ export interface MessageConversationResponse { label: string; } +export interface RemoveGroupMemberResponse { + conversationId: number; + phoneNumber: string; +} + export interface OnMessageExportCtx { /** * The incoming message object @@ -152,6 +162,9 @@ export enum MessageEvents { DELETE_CONVERSATION = 'nwpd:deleteConversation', GET_MESSAGE_LOCATION = 'npwd:getMessageLocation', MESSAGES_SET_WAYPOINT = 'npwd:setWaypoint', + REMOVE_GROUP_MEMBER = 'nwpd:removeGroupMember', + REMOVE_GROUP_MEMBER_CONVERSATION = 'nwpd:removeGroupMemberChat', + REMOVE_GROUP_MEMBER_LIST = 'nwpd:removeGroupMemberList', } export interface Location { From dbae408adf681ecd21b1d9d41dcd3aa4698d39cb Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Mon, 6 Jun 2022 02:30:11 +0100 Subject: [PATCH 04/21] fix(messages): get createdBy from db --- resources/server/messages/messages.db.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/server/messages/messages.db.ts b/resources/server/messages/messages.db.ts index 28f1378de..48fca2020 100644 --- a/resources/server/messages/messages.db.ts +++ b/resources/server/messages/messages.db.ts @@ -17,6 +17,7 @@ export class _MessagesDB { npwd_messages_participants.unread_count as unreadCount, npwd_messages_conversations.is_group_chat as isGroupChat, npwd_messages_conversations.label, + npwd_messages_conversations.createdBy, UNIX_TIMESTAMP(npwd_messages_conversations.updatedAt) as updatedAt, npwd_messages_participants.participant FROM npwd_messages_conversations From c7b02e2ef2c2512eae4378c025bdb7864eb6a095 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Mon, 6 Jun 2022 02:35:49 +0100 Subject: [PATCH 05/21] fix(files): delete accidental upload --- messageUpdate.sql | 1 - 1 file changed, 1 deletion(-) delete mode 100644 messageUpdate.sql diff --git a/messageUpdate.sql b/messageUpdate.sql deleted file mode 100644 index b6bb9edf4..000000000 --- a/messageUpdate.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE npwd_messages_conversations ADD COLUMN `createdBy` varchar(48) NOT NULL DEFAULT '' \ No newline at end of file From 64dd1ce70f293f076a4c4def769df2a6ec1d9c3f Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Mon, 6 Jun 2022 15:17:11 +0100 Subject: [PATCH 06/21] feat(messages): leave group chats --- .../components/modal/GroupDetailsModal.tsx | 21 +++++++++++++--- .../components/modal/MessageModal.tsx | 11 +++++++- .../src/apps/messages/hooks/useMessageAPI.ts | 25 +++++++++++++++++++ phone/src/locale/en.json | 3 ++- resources/server/messages/messages.service.ts | 4 +-- typings/messages.ts | 1 + 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index 001433cd4..bff2f1ae0 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -15,6 +15,7 @@ interface GroupDetailsModalProps { createdBy: string; addContact: (number: any) => void; removeMember: (number: any) => void; + leaveGroup: () => void; } const GroupDetailsModal: React.FC = ({ @@ -24,6 +25,7 @@ const GroupDetailsModal: React.FC = ({ createdBy, addContact, removeMember, + leaveGroup, }) => { const myPhoneNumber = useMyPhoneNumber(); const { getContactByNumber } = useContactActions(); @@ -45,10 +47,7 @@ const GroupDetailsModal: React.FC = ({ return ( - - Details - {/**/} - + Details {participants.map((participant) => { const contact = findContact(participant); @@ -76,6 +75,20 @@ const GroupDetailsModal: React.FC = ({ ); })} + + + {/* {myPhoneNumber === createdBy && ( + + )} */} + ); }; diff --git a/phone/src/apps/messages/components/modal/MessageModal.tsx b/phone/src/apps/messages/components/modal/MessageModal.tsx index 6437cd7c3..6ee486fe7 100644 --- a/phone/src/apps/messages/components/modal/MessageModal.tsx +++ b/phone/src/apps/messages/components/modal/MessageModal.tsx @@ -67,7 +67,7 @@ export const MessageModal = () => { const { fetchMessages } = useMessageAPI(); const { getLabelOrContact, getConversationParticipant } = useMessageActions(); const { initializeCall } = useCall(); - const { removeGroupMember } = useMessageAPI(); + const { removeGroupMember, leaveGroup } = useMessageAPI(); const { getContactByNumber } = useContactActions(); const [messages, setMessages] = useMessagesState(); @@ -152,6 +152,14 @@ export const MessageModal = () => { ); }; + const handleLeaveGroup = () => { + leaveGroup( + activeMessageConversation.conversationList, + activeMessageConversation.id, + myPhoneNumber, + ); + }; + // This only gets used for 1 on 1 conversations let conversationList = activeMessageConversation.conversationList.split('+'); conversationList = conversationList.filter((targetNumber) => targetNumber !== myPhoneNumber); @@ -180,6 +188,7 @@ export const MessageModal = () => { createdBy={activeMessageConversation.createdBy} addContact={handleAddContact} removeMember={handleGroupRemove} + leaveGroup={handleLeaveGroup} /> {isGroupModalOpen && } void; @@ -30,6 +31,7 @@ type UseMessageAPIProps = { conversationId: number, phoneNumber: string, ) => void; + leaveGroup: (conversationList: string, conversationId: number, phoneNumber: string) => void; }; export const useMessageAPI = (): UseMessageAPIProps => { @@ -250,6 +252,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { conversationList, conversationId, phoneNumber, + leaveGroup: false, }).then((resp) => { if (resp.status !== 'ok') { return addAlert({ @@ -263,6 +266,27 @@ export const useMessageAPI = (): UseMessageAPIProps => { [addAlert, removeLocalGroupMember, t], ); + const leaveGroup = useCallback( + (conversationList: string, conversationId: number, phoneNumber: string) => { + fetchNui>(MessageEvents.REMOVE_GROUP_MEMBER, { + conversationList, + conversationId, + phoneNumber, + leaveGroup: true, + }).then((resp) => { + if (resp.status !== 'ok') { + return addAlert({ + message: t('MESSAGES.FEEDBACK.LEAVE_GROUP_FAILED'), + type: 'error', + }); + } + removeLocalConversation([conversationId]); + return history.push('/messages'); + }); + }, + [addAlert, history, removeLocalConversation, t], + ); + return { sendMessage, deleteMessage, @@ -272,5 +296,6 @@ export const useMessageAPI = (): UseMessageAPIProps => { sendEmbedMessage, setMessageRead, removeGroupMember, + leaveGroup, }; }; diff --git a/phone/src/locale/en.json b/phone/src/locale/en.json index a7b9bacdf..1b1047d26 100644 --- a/phone/src/locale/en.json +++ b/phone/src/locale/en.json @@ -220,7 +220,8 @@ "DELETE_MESSAGE": "Delete message", "DELETE_MESSAGE_FAILED": "Failed to delete message", "DELETE_CONVERSATION_FAILED": "Failed to delete conversation", - "REMOVE_GROUP_MEMBER_FAILED": "Failed to remove member" + "REMOVE_GROUP_MEMBER_FAILED": "Failed to remove member", + "LEAVE_GROUP_FAILED": "Failed to leave group" }, "SEARCH_PLACEHOLDER": "Search messages...", "DELETE_CONVERSATION": "Delete conversation", diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 125f62e8b..bb18c7163 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -305,11 +305,11 @@ class _MessagesService { const playerPhoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); const participants = reqObj.data.conversationList.split('+'); for (const participant of participants) { - if (participant !== playerPhoneNumber) { + if (reqObj.data.leaveGroup || participant !== playerPhoneNumber) { const participantIdentifier = await PlayerService.getIdentifierByPhoneNumber(participant); const participantPlayer = PlayerService.getPlayerFromIdentifier(participantIdentifier); if (participantPlayer) { - if (participant == reqObj.data.phoneNumber) { + if (!reqObj.data.leaveGroup && participant == reqObj.data.phoneNumber) { //if the player is the one being removed emitNetTyped( MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, diff --git a/typings/messages.ts b/typings/messages.ts index c2e347cf2..71ce410c2 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -57,6 +57,7 @@ export interface RemoveGroupMemberRequest { conversationList: string; conversationId: number; phoneNumber: string; + leaveGroup: boolean; } /** From 3168c0c341224eda082bc858574db23548e42e04 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:56:24 +0100 Subject: [PATCH 07/21] fix(messages): review changes --- import.sql | 2 +- phone/src/apps/messages/hooks/useMessageAPI.ts | 1 - phone/src/apps/messages/hooks/useMessageActions.ts | 1 - resources/server/messages/messages.service.ts | 2 +- typings/messages.ts | 6 +++--- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/import.sql b/import.sql index 36a0208ce..f0a8b2bd6 100644 --- a/import.sql +++ b/import.sql @@ -156,7 +156,7 @@ CREATE TABLE `npwd_messages_conversations` `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(), `updatedAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(), `last_message_id` INT(11) NULL DEFAULT NULL, - `createdBy` varchar(48) NOT NULL DEFAULT '' + `createdBy` varchar(48) NOT NULL DEFAULT '', `is_group_chat` TINYINT(4) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ); diff --git a/phone/src/apps/messages/hooks/useMessageAPI.ts b/phone/src/apps/messages/hooks/useMessageAPI.ts index 86429585a..3a8128021 100644 --- a/phone/src/apps/messages/hooks/useMessageAPI.ts +++ b/phone/src/apps/messages/hooks/useMessageAPI.ts @@ -16,7 +16,6 @@ import { messageState, useSetMessages } from './state'; import { useRecoilValueLoadable } from 'recoil'; import { MockConversationServerResp } from '../utils/constants'; import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; -import Conversation from '../components/modal/Conversation'; type UseMessageAPIProps = { sendMessage: ({ conversationId, message, tgtPhoneNumber }: PreDBMessage) => void; diff --git a/phone/src/apps/messages/hooks/useMessageActions.ts b/phone/src/apps/messages/hooks/useMessageActions.ts index e0cc13d24..0cf83df34 100644 --- a/phone/src/apps/messages/hooks/useMessageActions.ts +++ b/phone/src/apps/messages/hooks/useMessageActions.ts @@ -3,7 +3,6 @@ import { useConversationId, useSetMessageConversations, useSetMessages, - useMessageConversationsValue, } from './state'; import { useCallback } from 'react'; import { Message, MessageConversation } from '@typings/messages'; diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index bb18c7163..c42335caa 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -309,7 +309,7 @@ class _MessagesService { const participantIdentifier = await PlayerService.getIdentifierByPhoneNumber(participant); const participantPlayer = PlayerService.getPlayerFromIdentifier(participantIdentifier); if (participantPlayer) { - if (!reqObj.data.leaveGroup && participant == reqObj.data.phoneNumber) { + if (!reqObj.data.leaveGroup && participant === reqObj.data.phoneNumber) { //if the player is the one being removed emitNetTyped( MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, diff --git a/typings/messages.ts b/typings/messages.ts index 71ce410c2..a805418cf 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -163,9 +163,9 @@ export enum MessageEvents { DELETE_CONVERSATION = 'nwpd:deleteConversation', GET_MESSAGE_LOCATION = 'npwd:getMessageLocation', MESSAGES_SET_WAYPOINT = 'npwd:setWaypoint', - REMOVE_GROUP_MEMBER = 'nwpd:removeGroupMember', - REMOVE_GROUP_MEMBER_CONVERSATION = 'nwpd:removeGroupMemberChat', - REMOVE_GROUP_MEMBER_LIST = 'nwpd:removeGroupMemberList', + DELETE_GROUP_MEMBER = 'nwpd:deleteGroupMember', + DELETE_GROUP_MEMBER_CONVERSATION = 'nwpd:deleteGroupMemberChat', + DELETE_GROUP_MEMBER_LIST = 'nwpd:deleteGroupMemberList', } export interface Location { From 7bed1b73f99e4e19109b3024b5c6ab35f9b7334a Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 14:23:23 +0100 Subject: [PATCH 08/21] fix(messages): event typings --- phone/src/apps/messages/hooks/useMessageAPI.ts | 6 +++--- phone/src/apps/messages/hooks/useMessageService.ts | 4 ++-- phone/src/locale/en.json | 2 +- resources/client/cl_messages.ts | 10 +++++----- resources/server/messages/messages.controller.ts | 2 +- resources/server/messages/messages.service.ts | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/phone/src/apps/messages/hooks/useMessageAPI.ts b/phone/src/apps/messages/hooks/useMessageAPI.ts index 3a8128021..b2de78902 100644 --- a/phone/src/apps/messages/hooks/useMessageAPI.ts +++ b/phone/src/apps/messages/hooks/useMessageAPI.ts @@ -247,7 +247,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { const removeGroupMember = useCallback( (conversationList: string, conversationId: number, phoneNumber: string) => { - fetchNui>(MessageEvents.REMOVE_GROUP_MEMBER, { + fetchNui>(MessageEvents.DELETE_GROUP_MEMBER, { conversationList, conversationId, phoneNumber, @@ -255,7 +255,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { }).then((resp) => { if (resp.status !== 'ok') { return addAlert({ - message: t('MESSAGES.FEEDBACK.REMOVE_GROUP_MEMBER_FAILED'), + message: t('MESSAGES.FEEDBACK.DELETE_GROUP_MEMBER_FAILED'), type: 'error', }); } @@ -267,7 +267,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { const leaveGroup = useCallback( (conversationList: string, conversationId: number, phoneNumber: string) => { - fetchNui>(MessageEvents.REMOVE_GROUP_MEMBER, { + fetchNui>(MessageEvents.DELETE_GROUP_MEMBER, { conversationList, conversationId, phoneNumber, diff --git a/phone/src/apps/messages/hooks/useMessageService.ts b/phone/src/apps/messages/hooks/useMessageService.ts index fa38aeaf5..7dda958ad 100644 --- a/phone/src/apps/messages/hooks/useMessageService.ts +++ b/phone/src/apps/messages/hooks/useMessageService.ts @@ -75,6 +75,6 @@ export const useMessagesService = () => { useNuiEvent('MESSAGES', MessageEvents.CREATE_MESSAGE_BROADCAST, handleMessageBroadcast); useNuiEvent('MESSAGES', MessageEvents.SEND_MESSAGE_SUCCESS, handleUpdateMessages); useNuiEvent('MESSAGES', MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, handleAddConversation); - useNuiEvent('MESSAGES', MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, handleDeleteConversation); - useNuiEvent('MESSAGES', MessageEvents.REMOVE_GROUP_MEMBER_LIST, handleRemoveGroupMember); + useNuiEvent('MESSAGES', MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, handleDeleteConversation); + useNuiEvent('MESSAGES', MessageEvents.DELETE_GROUP_MEMBER_LIST, handleRemoveGroupMember); }; diff --git a/phone/src/locale/en.json b/phone/src/locale/en.json index 1b1047d26..279d4cbb7 100644 --- a/phone/src/locale/en.json +++ b/phone/src/locale/en.json @@ -220,7 +220,7 @@ "DELETE_MESSAGE": "Delete message", "DELETE_MESSAGE_FAILED": "Failed to delete message", "DELETE_CONVERSATION_FAILED": "Failed to delete conversation", - "REMOVE_GROUP_MEMBER_FAILED": "Failed to remove member", + "DELETE_GROUP_MEMBER_FAILED": "Failed to remove member", "LEAVE_GROUP_FAILED": "Failed to leave group" }, "SEARCH_PLACEHOLDER": "Search messages...", diff --git a/resources/client/cl_messages.ts b/resources/client/cl_messages.ts index 8423fa807..885bdfbf6 100644 --- a/resources/client/cl_messages.ts +++ b/resources/client/cl_messages.ts @@ -13,7 +13,7 @@ RegisterNuiProxy(MessageEvents.DELETE_MESSAGE); RegisterNuiProxy(MessageEvents.FETCH_MESSAGES); RegisterNuiProxy(MessageEvents.CREATE_MESSAGE_CONVERSATION); RegisterNuiProxy(MessageEvents.DELETE_CONVERSATION); -RegisterNuiProxy(MessageEvents.REMOVE_GROUP_MEMBER); +RegisterNuiProxy(MessageEvents.DELETE_GROUP_MEMBER); RegisterNuiProxy(MessageEvents.SEND_MESSAGE); RegisterNuiProxy(MessageEvents.SET_MESSAGE_READ); RegisterNuiProxy(MessageEvents.GET_MESSAGE_LOCATION); @@ -34,10 +34,10 @@ onNet(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, (result: MessageConvers sendMessageEvent(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, result); }); -onNet(MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, (result: number[]) => { - sendMessageEvent(MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, result); +onNet(MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, (result: number[]) => { + sendMessageEvent(MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, result); }); -onNet(MessageEvents.REMOVE_GROUP_MEMBER_LIST, (result: RemoveGroupMemberResponse) => { - sendMessageEvent(MessageEvents.REMOVE_GROUP_MEMBER_LIST, result); +onNet(MessageEvents.DELETE_GROUP_MEMBER_LIST, (result: RemoveGroupMemberResponse) => { + sendMessageEvent(MessageEvents.DELETE_GROUP_MEMBER_LIST, result); }); diff --git a/resources/server/messages/messages.controller.ts b/resources/server/messages/messages.controller.ts index f8e876a9b..a7da315be 100644 --- a/resources/server/messages/messages.controller.ts +++ b/resources/server/messages/messages.controller.ts @@ -116,7 +116,7 @@ onNetPromise(MessageEvents.GET_MESSAGE_LOCATION, async (reqObj, resp) => { }); onNetPromise( - MessageEvents.REMOVE_GROUP_MEMBER, + MessageEvents.DELETE_GROUP_MEMBER, async (reqObj, resp) => { MessagesService.handleRemoveGroupMember(reqObj, resp).catch((e) => { messagesLogger.error( diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index c42335caa..55b8c9d7d 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -312,7 +312,7 @@ class _MessagesService { if (!reqObj.data.leaveGroup && participant === reqObj.data.phoneNumber) { //if the player is the one being removed emitNetTyped( - MessageEvents.REMOVE_GROUP_MEMBER_CONVERSATION, + MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, { conversationID: [reqObj.data.conversationId], }, @@ -321,7 +321,7 @@ class _MessagesService { } else { //if the player is not the one being removed emitNetTyped( - MessageEvents.REMOVE_GROUP_MEMBER_LIST, + MessageEvents.DELETE_GROUP_MEMBER_LIST, { conversationId: reqObj.data.conversationId, phoneNumber: reqObj.data.phoneNumber, From ccf127453bc5d3b4c3b23601a5174c67216e63c9 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:53:43 +0100 Subject: [PATCH 09/21] fix(messages): nested if statement & error logging --- resources/server/messages/messages.service.ts | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 55b8c9d7d..c90118c22 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -305,35 +305,45 @@ class _MessagesService { const playerPhoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); const participants = reqObj.data.conversationList.split('+'); for (const participant of participants) { - if (reqObj.data.leaveGroup || participant !== playerPhoneNumber) { - const participantIdentifier = await PlayerService.getIdentifierByPhoneNumber(participant); - const participantPlayer = PlayerService.getPlayerFromIdentifier(participantIdentifier); - if (participantPlayer) { - if (!reqObj.data.leaveGroup && participant === reqObj.data.phoneNumber) { - //if the player is the one being removed - emitNetTyped( - MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, - { - conversationID: [reqObj.data.conversationId], - }, - participantPlayer.source, - ); - } else { - //if the player is not the one being removed - emitNetTyped( - MessageEvents.DELETE_GROUP_MEMBER_LIST, - { - conversationId: reqObj.data.conversationId, - phoneNumber: reqObj.data.phoneNumber, - }, - participantPlayer.source, - ); - } - } + if (!reqObj.data.leaveGroup || participant === playerPhoneNumber) { + //if not leave group (kick) and the participant is the person kicking then move to next player as he already has updated data + continue; + } + + const participantIdentifier = await PlayerService.getIdentifierByPhoneNumber(participant); + const participantPlayer = PlayerService.getPlayerFromIdentifier(participantIdentifier); + + if (!participantPlayer) { + //if not online, move to next player + continue; + } + + if (!reqObj.data.leaveGroup && participant === reqObj.data.phoneNumber) { + //if not leave group/leave themself and participant is equal to nubmer remove then remove the chat from their list + emitNetTyped( + MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, + { + conversationID: [reqObj.data.conversationId], + }, + participantPlayer.source, + ); + } + + if (participant !== reqObj.data.phoneNumber) { + //if they are not the one being removed then tell them who left + emitNetTyped( + MessageEvents.DELETE_GROUP_MEMBER_LIST, + { + conversationId: reqObj.data.conversationId, + phoneNumber: reqObj.data.phoneNumber, + }, + participantPlayer.source, + ); } } } catch (err) { - resp({ status: 'error', errorMsg: err.message }); + messagesLogger.error(`Failed to read message. Error: ${err.message}`); + resp({ status: 'error' }); } } From 1cf33e338e0351a5fa4c559bfa3448e3c93eb5d2 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:59:43 +0100 Subject: [PATCH 10/21] fix(messages): created by defined on server --- .../apps/messages/components/form/NewMessageGroupForm.tsx | 1 - phone/src/apps/messages/hooks/useMessageAPI.ts | 1 - resources/server/messages/messages.service.ts | 6 +++--- typings/messages.ts | 1 - 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx b/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx index f3b6c14a8..a9f71bd20 100644 --- a/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx +++ b/phone/src/apps/messages/components/form/NewMessageGroupForm.tsx @@ -31,7 +31,6 @@ const NewMessageGroupForm = ({ phoneNumber }: { phoneNumber?: string }) => { conversationLabel: isGroupChat ? conversationLabel : '', participants: [myPhoneNumber, ...selectedParticipants], isGroupChat, - createdBy: myPhoneNumber, }; addConversation(dto); diff --git a/phone/src/apps/messages/hooks/useMessageAPI.ts b/phone/src/apps/messages/hooks/useMessageAPI.ts index b2de78902..ed3b25179 100644 --- a/phone/src/apps/messages/hooks/useMessageAPI.ts +++ b/phone/src/apps/messages/hooks/useMessageAPI.ts @@ -143,7 +143,6 @@ export const useMessageAPI = (): UseMessageAPIProps => { conversationLabel: conversation.conversationLabel, participants: conversation.participants, isGroupChat: conversation.isGroupChat, - createdBy: conversation.createdBy, }, ).then((resp) => { if (resp.status !== 'ok') { diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index c90118c22..d00657ad8 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -78,7 +78,7 @@ class _MessagesService { label: conversation.conversationLabel, conversationList, isGroupChat: conversation.isGroupChat, - createdBy: conversation.createdBy, + createdBy: playerPhoneNumber, }; return resp({ status: 'ok', data: { ...respData, participant: playerPhoneNumber } }); @@ -91,7 +91,7 @@ class _MessagesService { conversationList, conversation.conversationLabel, conversation.isGroupChat, - conversation.createdBy, + playerPhoneNumber, ); // Return data @@ -100,7 +100,7 @@ class _MessagesService { label: conversation.conversationLabel, conversationList, isGroupChat: conversation.isGroupChat, - createdBy: conversation.createdBy, + createdBy: playerPhoneNumber, }; resp({ status: 'ok', data: { ...respData, participant: playerPhoneNumber } }); diff --git a/typings/messages.ts b/typings/messages.ts index a805418cf..f66695113 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -42,7 +42,6 @@ export interface PreDBConversation { participants: string[]; conversationLabel: string; isGroupChat: boolean; - createdBy: string; } export interface MessagesRequest { From a9748cf8b88562b80fd576ddd8320761ac289a2d Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 16:11:50 +0100 Subject: [PATCH 11/21] fix(messages): removegroupmember endpoint validation --- resources/server/messages/messages.db.ts | 9 +++++++++ resources/server/messages/messages.service.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/resources/server/messages/messages.db.ts b/resources/server/messages/messages.db.ts index 48fca2020..327d0fd8a 100644 --- a/resources/server/messages/messages.db.ts +++ b/resources/server/messages/messages.db.ts @@ -198,6 +198,15 @@ export class _MessagesDB { return count > 0; } + async getGroupOwner(conversationID: number): Promise { + const query = `SELECT createdBy FROM npwd_messages_conversations WHERE id = ?`; + + const [results] = await DbInterface._rawExec(query, [conversationID]); + const result = results; + + return result[0].createdBy; + } + async doesConversationExistForPlayer( conversationList: string, phoneNumber: string, diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index d00657ad8..6ca6d3fd8 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -295,6 +295,14 @@ class _MessagesService { reqObj: PromiseRequest, resp: PromiseEventResp, ) { + const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); + const groupOwner = await this.messagesDB.getGroupOwner(reqObj.data.conversationId); + + if (groupOwner !== phoneNumber) { + messagesLogger.error(`Use does not own group. Error`); + return resp({ status: 'error' }); + } + try { await this.messagesDB.removeGroupMember( reqObj.data.conversationList, @@ -342,7 +350,7 @@ class _MessagesService { } } } catch (err) { - messagesLogger.error(`Failed to read message. Error: ${err.message}`); + messagesLogger.error(`Failed to remove from group. Error: ${err.message}`); resp({ status: 'error' }); } } From 692fafaf5bc04fd391da95a03e71d356cd3fc0a0 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 20:20:20 +0100 Subject: [PATCH 12/21] chore(messages): cleanup --- phone/src/apps/messages/components/modal/GroupDetailsModal.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index bff2f1ae0..8c6bf5051 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -85,9 +85,6 @@ const GroupDetailsModal: React.FC = ({ - {/* {myPhoneNumber === createdBy && ( - - )} */} ); From abd01553cbf1db1438bd89511238c62d178c39ec Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Fri, 10 Jun 2022 20:23:32 +0100 Subject: [PATCH 13/21] chore(messages): cleanup --- .../messages/components/modal/GroupDetailsModal.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index 8c6bf5051..b5d6532d6 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -75,17 +75,15 @@ const GroupDetailsModal: React.FC = ({ ); })} - - - + Leave Group + ); }; From 876a3dd0a912dca79dfd871034be76851cfecae9 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sat, 11 Jun 2022 17:32:27 +0100 Subject: [PATCH 14/21] feat(messages): system messages --- import.sql | 11 +++ .../components/modal/MessageBubble.tsx | 67 +++++++++++++------ .../messages/components/ui/SystemMessage.tsx | 35 ++++++++++ .../src/apps/messages/hooks/useMessageAPI.ts | 60 ++++++++++++++++- .../apps/messages/hooks/useMessageActions.ts | 3 + phone/src/apps/messages/utils/constants.ts | 39 ++++++++--- resources/server/messages/messages.db.ts | 12 +++- resources/server/messages/messages.service.ts | 9 +++ typings/messages.ts | 9 +++ 9 files changed, 211 insertions(+), 34 deletions(-) create mode 100644 phone/src/apps/messages/components/ui/SystemMessage.tsx diff --git a/import.sql b/import.sql index f0a8b2bd6..1a6ce49f2 100644 --- a/import.sql +++ b/import.sql @@ -6,6 +6,14 @@ # ALTER TABLE npwd_messages ADD COLUMN `is_embed` tinyint(4) NOT NULL DEFAULT 0; # ALTER TABLE npwd_messages ADD COLUMN `embed` varchar(512) NOT NULL DEFAULT ''; + +# Group Chat Overhaul SQL Update +# ALTER TABLE npwd_messages ADD COLUMN `is_system` tinyint(4) NOT NULL DEFAULT 0; +# ALTER TABLE npwd_messages ADD COLUMN `system_type` varchar(48) NOT NULL DEFAULT ''; +# ALTER TABLE npwd_messages ADD COLUMN `system_number` varchar(48) NOT NULL DEFAULT ''; +# ALTER TABLE npwd_messages_conversations ADD COLUMN `createdBy` varchar(48) NOT NULL DEFAULT ''; + + CREATE TABLE IF NOT EXISTS `npwd_twitter_profiles` ( `id` int NOT NULL AUTO_INCREMENT, @@ -144,6 +152,9 @@ CREATE TABLE IF NOT EXISTS `npwd_messages` `author` varchar(255) NOT NULL, `is_embed` tinyint(4) NOT NULL default 0, `embed` varchar(512) NOT NULL DEFAULT '', + `is_system` tinyint(4) NOT NULL default 0, + `system_type` varchar(48) NOT NULL default '', + `system_number` varchar(48) NOT NULL default '', PRIMARY KEY (id), INDEX `user_identifier` (`user_identifier`) ); diff --git a/phone/src/apps/messages/components/modal/MessageBubble.tsx b/phone/src/apps/messages/components/modal/MessageBubble.tsx index e31478c0b..9c35fd4f1 100644 --- a/phone/src/apps/messages/components/modal/MessageBubble.tsx +++ b/phone/src/apps/messages/components/modal/MessageBubble.tsx @@ -11,6 +11,7 @@ import MessageBubbleMenu from './MessageBubbleMenu'; import { useSetSelectedMessage } from '../../hooks/state'; import MessageEmbed from '../ui/MessageEmbed'; import { useContactActions } from '../../../contacts/hooks/useContactActions'; +import SystemMessage from '../ui/SystemMessage'; const useStyles = makeStyles((theme) => ({ mySms: { @@ -39,6 +40,20 @@ const useStyles = makeStyles((theme) => ({ borderRadius: '15px', textOverflow: 'ellipsis', }, + system: { + float: 'left', + padding: '1px 12px', + width: 'auto', + marginLeft: 5, + maxWidth: '80%', + height: 'auto', + background: '#282828', //should by theme shit here for dark/light mode + color: '#ddd', //should by theme shit here for dark/light mode + border: '0px', + borderRadius: '8px', + display: 'flex', + justifyContent: 'center', + }, message: { wordBreak: 'break-word', display: 'flex', @@ -83,33 +98,43 @@ export const MessageBubble: React.FC = ({ message }) => { display="flex" ml={1} alignItems="stretch" - justifyContent={isMine ? 'flex-end' : 'flex-start'} + justifyContent={message.is_system ? 'center' : isMine ? 'flex-end' : 'flex-start'} mt={1} > - {!isMine ? : null} - - {message.is_embed ? ( - + {!message.is_system && <>{!isMine ? : null}} + + + {message.is_system ? ( + ) : ( - - {isImage(message.message) ? ( - - - + <> + {message.is_embed ? ( + ) : ( - <>{message.message} + + {isImage(message.message) ? ( + + + + ) : ( + <>{message.message} + )} + {isMine && ( + + + + )} + )} - {isMine && ( - - - + {!isMine && ( + + {getContact()?.display ?? message.author} + )} - - )} - {!isMine && ( - - {getContact()?.display ?? message.author} - + )} diff --git a/phone/src/apps/messages/components/ui/SystemMessage.tsx b/phone/src/apps/messages/components/ui/SystemMessage.tsx new file mode 100644 index 000000000..7d9c4ec5c --- /dev/null +++ b/phone/src/apps/messages/components/ui/SystemMessage.tsx @@ -0,0 +1,35 @@ +import { Message } from '@typings/messages'; +import { useContactActions } from '../../../contacts/hooks/useContactActions'; +import React from 'react'; +import { Typography } from '@mui/material'; + +const SystemMessage = ({ message, myNumber }: { message: Message; myNumber: string }) => { + const { getContactByNumber } = useContactActions(); + const getContact = (number: string) => { + if (number === myNumber) { + return 'You'; + } + const contact = getContactByNumber(number); + return contact ? contact.display : number; + }; + + return ( + <> + {message.system_type === 'add' ? ( + {`${getContact(message.author)} added ${getContact( + message.system_number, + )}`} + ) : message.system_type === 'remove' ? ( + {`${getContact(message.author)} removed ${getContact( + message.system_number, + )}`} + ) : ( + message.system_type === 'leave' && ( + {`${getContact(message.author)} left`} + ) + )} + + ); +}; + +export default SystemMessage; diff --git a/phone/src/apps/messages/hooks/useMessageAPI.ts b/phone/src/apps/messages/hooks/useMessageAPI.ts index ed3b25179..fdf345dd2 100644 --- a/phone/src/apps/messages/hooks/useMessageAPI.ts +++ b/phone/src/apps/messages/hooks/useMessageAPI.ts @@ -20,6 +20,12 @@ import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; type UseMessageAPIProps = { sendMessage: ({ conversationId, message, tgtPhoneNumber }: PreDBMessage) => void; sendEmbedMessage: ({ conversationId, embed }: PreDBMessage) => void; + sendSystemMessage: ({ + conversationId, + is_system, + system_type, + system_number, + }: PreDBMessage) => void; deleteMessage: (message: Message) => void; addConversation: (conversation: PreDBConversation) => void; deleteConversation: (conversationIds: number[]) => void; @@ -244,6 +250,38 @@ export const useMessageAPI = (): UseMessageAPIProps => { [setMessages, addAlert, t, history], ); + const sendSystemMessage = useCallback( + ({ + conversationId, + conversationList, + tgtPhoneNumber = '', + is_system, + system_number, + system_type, + }: PreDBMessage) => { + fetchNui, PreDBMessage>(MessageEvents.SEND_MESSAGE, { + conversationId, + conversationList, + message: '', + tgtPhoneNumber, + sourcePhoneNumber: myPhoneNumber, + is_system, + system_number, + system_type, + }).then((resp) => { + if (resp.status !== 'ok') { + return addAlert({ + message: t('MESSAGES.FEEDBACK.NEW_MESSAGE_FAILED'), + type: 'error', + }); + } + + updateLocalMessages(resp.data); + }); + }, + [t, updateLocalMessages, addAlert, myPhoneNumber], + ); + const removeGroupMember = useCallback( (conversationList: string, conversationId: number, phoneNumber: string) => { fetchNui>(MessageEvents.DELETE_GROUP_MEMBER, { @@ -259,9 +297,17 @@ export const useMessageAPI = (): UseMessageAPIProps => { }); } removeLocalGroupMember(conversationId, phoneNumber); + sendSystemMessage({ + conversationId: conversationId, + conversationList: conversationList, + tgtPhoneNumber: '', + is_system: true, + system_number: phoneNumber, + system_type: 'remove', + }); }); }, - [addAlert, removeLocalGroupMember, t], + [addAlert, removeLocalGroupMember, sendSystemMessage, t], ); const leaveGroup = useCallback( @@ -278,11 +324,20 @@ export const useMessageAPI = (): UseMessageAPIProps => { type: 'error', }); } + removeLocalConversation([conversationId]); + sendSystemMessage({ + conversationId: conversationId, + conversationList: conversationList, + tgtPhoneNumber: '', + is_system: true, + system_number: '', + system_type: 'leave', + }); return history.push('/messages'); }); }, - [addAlert, history, removeLocalConversation, t], + [addAlert, history, removeLocalConversation, sendSystemMessage, t], ); return { @@ -295,5 +350,6 @@ export const useMessageAPI = (): UseMessageAPIProps => { setMessageRead, removeGroupMember, leaveGroup, + sendSystemMessage, }; }; diff --git a/phone/src/apps/messages/hooks/useMessageActions.ts b/phone/src/apps/messages/hooks/useMessageActions.ts index 0cf83df34..2549a34fb 100644 --- a/phone/src/apps/messages/hooks/useMessageActions.ts +++ b/phone/src/apps/messages/hooks/useMessageActions.ts @@ -128,6 +128,9 @@ export const useMessageActions = (): MessageActionProps => { id: messageDto.id, is_embed: messageDto.is_embed, embed: messageDto.embed, + is_system: messageDto.is_system, + system_number: messageDto.system_number, + system_type: messageDto.system_type, }, ]); }, diff --git a/phone/src/apps/messages/utils/constants.ts b/phone/src/apps/messages/utils/constants.ts index 9c5059d4a..8a4727b5c 100644 --- a/phone/src/apps/messages/utils/constants.ts +++ b/phone/src/apps/messages/utils/constants.ts @@ -14,7 +14,7 @@ export const MockMessageConversations: MessageConversation[] = [ }, { id: 2, - conversationList: '111-1134+321+215-8139', + conversationList: '423-43214+111-1134+444-4444+215-8139', participant: '111-1134', unread: 0, label: 'Secret Project Error chat', @@ -26,19 +26,42 @@ export const MockMessageConversations: MessageConversation[] = [ const MockConversationMessages: Message[] = [ { - id: 2, + id: 4, author: '215-8139', - message: 'Dude, when is this rewrite done?????', + message: 'Someone add sam!', }, { - id: 3, - author: '111-1134', - message: 'Bro, finish notifications api?????', + id: 5, + author: '444-4444', + message: '', + is_system: true, + system_type: 'add', + system_number: '423-43214', }, { - id: 4, + id: 6, author: '444-4444', - message: "Couldn't be me!", + message: 'I just added sam', + }, + { + id: 7, + author: '423-43214', + message: 'Hi Guys', + }, + { + id: 9, + author: '111-1134', + message: '', + is_system: true, + system_type: 'remove', + system_number: '423-43214', + }, + { + id: 10, + author: '215-8139', + message: '', + is_system: true, + system_type: 'leave', }, ]; diff --git a/resources/server/messages/messages.db.ts b/resources/server/messages/messages.db.ts index 327d0fd8a..43ecb646a 100644 --- a/resources/server/messages/messages.db.ts +++ b/resources/server/messages/messages.db.ts @@ -55,7 +55,10 @@ export class _MessagesDB { npwd_messages.author, npwd_messages.message, npwd_messages.is_embed, - npwd_messages.embed + npwd_messages.embed, + npwd_messages.is_system, + npwd_messages.system_type, + npwd_messages.system_number FROM npwd_messages WHERE conversation_id = ? ORDER BY createdAt DESC @@ -110,8 +113,8 @@ export class _MessagesDB { } async createMessage(dto: CreateMessageDTO) { - const query = `INSERT INTO npwd_messages (message, user_identifier, conversation_id, author, is_embed, embed) - VALUES (?, ?, ?, ?, ?, ?)`; + const query = `INSERT INTO npwd_messages (message, user_identifier, conversation_id, author, is_embed, embed, is_system, system_type, system_number) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; const [results] = await DbInterface._rawExec(query, [ dto.message || '', @@ -120,6 +123,9 @@ export class _MessagesDB { dto.authorPhoneNumber, dto.is_embed || false, dto.embed || '', + dto.is_system || false, + dto.system_type || '', + dto.system_number || '', ]); const result = results; diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 6ca6d3fd8..8b24dee91 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -163,6 +163,9 @@ class _MessagesService { message: messageData.message, is_embed: messageData.is_embed, embed: messageData.embed, + is_system: messageData.is_system, + system_type: messageData.system_type, + system_number: messageData.system_number, }); resp({ @@ -175,6 +178,9 @@ class _MessagesService { message: messageData.message, embed: messageData.embed, is_embed: messageData.is_embed, + is_system: messageData.is_system, + system_type: messageData.system_type, + system_number: messageData.system_number, }, }); @@ -239,6 +245,9 @@ class _MessagesService { message: messageData.message, is_embed: messageData.is_embed, embed: messageData.embed, + is_system: messageData.is_system, + system_type: messageData.system_type, + system_number: messageData.system_number, }); } } catch (err) { diff --git a/typings/messages.ts b/typings/messages.ts index f66695113..40e4c39d6 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -5,6 +5,9 @@ export interface Message { author: string; is_embed?: boolean; embed?: any; + is_system?: boolean; + system_type?: 'leave' | 'remove' | 'add'; + system_number?: string; } export interface PreDBMessage { @@ -15,6 +18,9 @@ export interface PreDBMessage { message?: string; is_embed?: boolean; embed?: any; + is_system?: boolean; + system_type?: 'leave' | 'remove' | 'add'; + system_number?: string; } export interface CreateMessageDTO { @@ -24,6 +30,9 @@ export interface CreateMessageDTO { message: string; is_embed: boolean; embed: any; + is_system: boolean; + system_type: 'leave' | 'remove' | 'add'; + system_number: string; } export interface MessageConversation { From ac580dadd8133ed5deac2b26d723dc36023eb670 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sat, 11 Jun 2022 20:18:47 +0100 Subject: [PATCH 15/21] fix(messages): errors --- phone/src/apps/messages/components/modal/MessageBubble.tsx | 7 ++++++- phone/src/apps/messages/utils/constants.ts | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/phone/src/apps/messages/components/modal/MessageBubble.tsx b/phone/src/apps/messages/components/modal/MessageBubble.tsx index b213310ab..2bfdc263c 100644 --- a/phone/src/apps/messages/components/modal/MessageBubble.tsx +++ b/phone/src/apps/messages/components/modal/MessageBubble.tsx @@ -113,7 +113,12 @@ export const MessageBubble: React.FC = ({ message }) => { ) : ( <> {message.is_embed ? ( - + ) : ( {isImage(message.message) ? ( diff --git a/phone/src/apps/messages/utils/constants.ts b/phone/src/apps/messages/utils/constants.ts index e61906ab7..8f517f2b8 100644 --- a/phone/src/apps/messages/utils/constants.ts +++ b/phone/src/apps/messages/utils/constants.ts @@ -10,6 +10,7 @@ export const MockMessageConversations: MessageConversation[] = [ label: '', updatedAt: 5, isGroupChat: false, + createdBy: '111-1134', }, { id: 2, @@ -19,6 +20,7 @@ export const MockMessageConversations: MessageConversation[] = [ label: 'Secret Project Error chat', updatedAt: 5, isGroupChat: true, + createdBy: '111-1134', }, ]; From 1f6f048e92d29e992af0e7b22d4778fe0f58b2f2 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sat, 11 Jun 2022 20:30:50 +0100 Subject: [PATCH 16/21] feat(messages): translations --- .../src/apps/messages/components/modal/GroupDetailsModal.tsx | 5 ++++- phone/src/locale/en.json | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index b5d6532d6..21d6c244a 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -7,6 +7,7 @@ import PersonRemoveIcon from '@mui/icons-material/PersonRemove'; import { findParticipants } from '../../utils/helpers'; import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; import { useContactActions } from '../../../contacts/hooks/useContactActions'; +import { useTranslation } from 'react-i18next'; interface GroupDetailsModalProps { open: boolean; @@ -27,6 +28,8 @@ const GroupDetailsModal: React.FC = ({ removeMember, leaveGroup, }) => { + const [t] = useTranslation(); + const myPhoneNumber = useMyPhoneNumber(); const { getContactByNumber } = useContactActions(); @@ -82,7 +85,7 @@ const GroupDetailsModal: React.FC = ({ }} onClick={leaveGroup} > - Leave Group + {t('GENERIC.LEAVE')} ); diff --git a/phone/src/locale/en.json b/phone/src/locale/en.json index 279d4cbb7..66253e7c4 100644 --- a/phone/src/locale/en.json +++ b/phone/src/locale/en.json @@ -274,7 +274,8 @@ "SHARE": "Share", "ADD": "Add", "WRITE_TO_CLIPBOARD_TOOLTIP": "Copy {{ content }}", - "WRITE_TO_CLIPBOARD_MESSAGE": "Copied {{content}} to clipboard!" + "WRITE_TO_CLIPBOARD_MESSAGE": "Copied {{content}} to clipboard!", + "LEAVE": "Leave" }, "COMING_SOON": "Coming soon...", "INITIALIZING": "Initializing", From de9541e4602dfdd178027fc8e24f818219185f91 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sat, 11 Jun 2022 21:40:34 +0100 Subject: [PATCH 17/21] feat(messages): use name for avatar --- .../apps/messages/components/list/MessageGroupItem.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/phone/src/apps/messages/components/list/MessageGroupItem.tsx b/phone/src/apps/messages/components/list/MessageGroupItem.tsx index ad3f70fe0..25ffecc75 100644 --- a/phone/src/apps/messages/components/list/MessageGroupItem.tsx +++ b/phone/src/apps/messages/components/list/MessageGroupItem.tsx @@ -95,7 +95,13 @@ const MessageGroupItem = ({ {messageConversation.isGroupChat ? ( ) : ( - + <> + {getContact()?.avatar ? ( + + ) : ( + {getContact().display.charAt(0)} + )} + )} From 30833151f550fbc9b5b1da3c37eff30d92d9de3c Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Sat, 2 Jul 2022 18:00:45 +0100 Subject: [PATCH 18/21] refactor(messages): group chat rework --- import.sql | 4 +- .../components/list/MessageGroupItem.tsx | 10 +- .../components/modal/GroupDetailsModal.tsx | 255 +++++++++++++----- .../components/modal/GroupMemberInfo.tsx | 86 ++++++ .../components/modal/MessageBubble.tsx | 4 +- .../components/modal/MessageModal.tsx | 32 +-- .../src/apps/messages/hooks/useMessageAPI.ts | 40 ++- .../apps/messages/hooks/useMessageActions.ts | 19 ++ .../apps/messages/hooks/useMessageService.ts | 12 +- phone/src/apps/messages/utils/constants.ts | 19 +- phone/src/locale/en.json | 3 +- phone/src/ui/components/ContextMenu.tsx | 48 ++-- resources/client/cl_messages.ts | 6 + .../server/messages/messages.controller.ts | 13 +- resources/server/messages/messages.db.ts | 26 +- resources/server/messages/messages.service.ts | 69 ++++- typings/messages.ts | 21 +- 17 files changed, 529 insertions(+), 138 deletions(-) create mode 100644 phone/src/apps/messages/components/modal/GroupMemberInfo.tsx diff --git a/import.sql b/import.sql index 1a6ce49f2..92bbe6fbd 100644 --- a/import.sql +++ b/import.sql @@ -11,7 +11,7 @@ # ALTER TABLE npwd_messages ADD COLUMN `is_system` tinyint(4) NOT NULL DEFAULT 0; # ALTER TABLE npwd_messages ADD COLUMN `system_type` varchar(48) NOT NULL DEFAULT ''; # ALTER TABLE npwd_messages ADD COLUMN `system_number` varchar(48) NOT NULL DEFAULT ''; -# ALTER TABLE npwd_messages_conversations ADD COLUMN `createdBy` varchar(48) NOT NULL DEFAULT ''; +# ALTER TABLE npwd_messages_conversations ADD COLUMN `owner` varchar(48) NOT NULL DEFAULT ''; CREATE TABLE IF NOT EXISTS `npwd_twitter_profiles` @@ -167,7 +167,7 @@ CREATE TABLE `npwd_messages_conversations` `createdAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(), `updatedAt` TIMESTAMP NOT NULL DEFAULT current_timestamp(), `last_message_id` INT(11) NULL DEFAULT NULL, - `createdBy` varchar(48) NOT NULL DEFAULT '', + `owner` varchar(48) NOT NULL DEFAULT '', `is_group_chat` TINYINT(4) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ); diff --git a/phone/src/apps/messages/components/list/MessageGroupItem.tsx b/phone/src/apps/messages/components/list/MessageGroupItem.tsx index 25ffecc75..039aa782c 100644 --- a/phone/src/apps/messages/components/list/MessageGroupItem.tsx +++ b/phone/src/apps/messages/components/list/MessageGroupItem.tsx @@ -7,6 +7,8 @@ import { Avatar as MuiAvatar, Badge, ListItemIcon, + Box, + Grid, } from '@mui/material'; import { MessageConversation } from '@typings/messages'; @@ -93,7 +95,13 @@ const MessageGroupItem = ({ invisible={messageConversation.unreadCount <= 0} > {messageConversation.isGroupChat ? ( - + <> + {messageConversation.avatar ? ( + + ) : ( + + )} + ) : ( <> {getContact()?.avatar ? ( diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index 21d6c244a..cc12b70ca 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -1,93 +1,224 @@ -import React from 'react'; -import Modal from '@ui/components/Modal'; -import { Box, Button, Stack, Typography } from '@mui/material'; -import PersonIcon from '@mui/icons-material/Person'; -import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import PersonRemoveIcon from '@mui/icons-material/PersonRemove'; +import React, { useState } from 'react'; +import { + Avatar as MuiAvatar, + Button, + Paper, + Slide, + Typography, + List, + ListItem, + ListItemText, + ListItemAvatar, +} from '@mui/material'; import { findParticipants } from '../../utils/helpers'; import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; import { useContactActions } from '../../../contacts/hooks/useContactActions'; -import { useTranslation } from 'react-i18next'; +import makeStyles from '@mui/styles/makeStyles'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { MessageConversation } from '@typings/messages'; +import { SearchField } from '@ui/components/SearchField'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import GroupMemberInfo from './GroupMemberInfo'; + +const useStyles = makeStyles((theme) => ({ + root: { + zIndex: 20, + height: '100%', + width: '100%', + position: 'absolute', + background: theme.palette.background.default, + }, + groupDetails: { + width: '75%', + margin: '0 auto', + textAlign: 'center', + marginBottom: 15, + }, + participantList: { + margin: '0 auto', + textAlign: 'center', + }, + buttons: { + margin: '0 auto', + display: 'flex', + justifyContent: 'center', + gap: 5, + marginTop: 5, + }, + avatar: { + margin: 'auto', + height: '100px', + width: '100px', + marginBottom: 10, + }, +})); interface GroupDetailsModalProps { open: boolean; onClose: () => void; - conversationList: string; - createdBy: string; - addContact: (number: any) => void; - removeMember: (number: any) => void; + conversation: MessageConversation; + removeMember: (number: string) => void; leaveGroup: () => void; + addContact: (number: string) => void; + makeOwner: (number: string) => void; } const GroupDetailsModal: React.FC = ({ open, onClose, - conversationList, - createdBy, - addContact, - removeMember, + conversation, leaveGroup, + removeMember, + addContact, + makeOwner, }) => { - const [t] = useTranslation(); + const classes = useStyles(); + const groupAmount = conversation.conversationList.split('+').length; const myPhoneNumber = useMyPhoneNumber(); const { getContactByNumber } = useContactActions(); + const [inputVal, setInputVal] = useState(''); + + const [participants, setParticipants] = useState( + findParticipants(conversation.conversationList, myPhoneNumber), + ); + + const updateSearch = (e: string) => { + setInputVal(e); + setFilterVal(e); + }; - const participants = findParticipants(conversationList, myPhoneNumber); + const setFilterVal = (filterValue: string) => { + if (!filterValue) + return setParticipants(findParticipants(conversation.conversationList, myPhoneNumber)); + const searchRegex = new RegExp(filterValue, 'gi'); + const filteredParticipants = participants.filter((participant) => { + const contact = getContactByNumber(participant); + return participant.match(searchRegex) || contact?.display.match(searchRegex); + }); + setParticipants(filteredParticipants); + }; + + const removeGroupMember = (number: string) => { + removeMember(number); + setParticipants(participants.filter((participant) => participant !== number)); + }; - const findContact = (phoneNumber: string) => { - return getContactByNumber(phoneNumber); + const closeGroupSettings = () => { + setInputVal(null); + setSelectedMember(''); + setIsOptionsModalOpen(false); + onClose(); }; - const handleAddContact = (participant: string) => { - addContact(participant); + const [isOptionsModalOpen, setIsOptionsModalOpen] = useState(false); + + const [selectedMember, setSelectedMember] = useState(''); + + const selectMember = (member: string) => { + setSelectedMember(member); + setIsOptionsModalOpen(true); }; - const handleGroupRemove = (participant: string) => { - removeMember(participant); + const closeOptionsModal = () => { + setIsOptionsModalOpen(false); }; + const isGroupOwner = conversation.owner === myPhoneNumber; + return ( - - - Details - - {participants.map((participant) => { - const contact = findContact(participant); - - return ( - - - - - {contact?.display ?? participant} - - - {!contact && ( - - )} - {myPhoneNumber === createdBy && ( - +
+ + {conversation.label} + + Group · {groupAmount} Members + +
+ updateSearch(e.target.value)} + placeholder={'Search..'} + /> +
+ + {!inputVal && ( + + + + + + + )} + {participants.map((participant) => { + const contact = getContactByNumber(participant); + return ( + + + {contact?.avatar ? ( + + ) : ( + {contact?.display.slice(0, 1).toUpperCase()} + )} + + + + - )} - - - - ); - })} - - + + ); + })} + +
+
+ {isGroupOwner && ( + + )} + +
+
+ ); }; diff --git a/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx b/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx new file mode 100644 index 000000000..ecc2942a6 --- /dev/null +++ b/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx @@ -0,0 +1,86 @@ +import React, { useMemo, useCallback } from 'react'; +import Person from '@mui/icons-material/Person'; +import PersonRemoveIcon from '@mui/icons-material/PersonRemove'; +import { ContextMenu, IContextMenuOption } from '@ui/components/ContextMenu'; +import SupervisorAccountIcon from '@mui/icons-material/SupervisorAccount'; +import { useContactActions } from '../../../contacts/hooks/useContactActions'; +import { MessageConversation } from '@typings/messages'; +import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; +import { useHistory, useLocation } from 'react-router-dom'; +import { useCall } from '@os/call/hooks/useCall'; +import PhoneIcon from '@mui/icons-material/Phone'; +import ChatIcon from '@mui/icons-material/Chat'; + +interface GroupDetailsModalProps { + open: boolean; + onClose: () => void; + participant: string; + removeMember: (number: string) => void; + conversation: MessageConversation; + addContact: (number: string) => void; + makeOwner: (number: string) => void; +} + +const GroupMemberInfo: React.FC = ({ + open, + onClose, + participant, + removeMember, + conversation, + addContact, + makeOwner, +}) => { + const { getContactByNumber } = useContactActions(); + const { pathname } = useLocation(); + const history = useHistory(); + const { initializeCall } = useCall(); + + const myPhoneNumber = useMyPhoneNumber(); + const isGroupOwner = conversation.owner === myPhoneNumber; + const contact = getContactByNumber(participant); + + const messageContact = useCallback( + (number: string) => { + const referal = encodeURIComponent(pathname); + return history.push(`/messages/new?phoneNumber=${number}&referal=${referal}`); + }, + [history, pathname], + ); + + const menuOptions: IContextMenuOption[] = useMemo( + () => [ + { + label: 'Make Group Owner', + icon: , + onClick: () => makeOwner(participant), + hide: !isGroupOwner, + }, + { + label: contact ? 'Manage Contact' : 'Add Contact', + icon: , + onClick: () => addContact(participant), + }, + { + label: 'Message', + icon: , + onClick: () => messageContact(participant), + }, + { + label: 'Call', + icon: , + onClick: () => initializeCall(participant), + }, + { + label: 'Remove from Group', + icon: , + onClick: () => removeMember(participant), + hide: !isGroupOwner, + }, + ], + [isGroupOwner, contact, messageContact, participant, initializeCall, addContact, removeMember], + ); + + return ; +}; + +export default GroupMemberInfo; diff --git a/phone/src/apps/messages/components/modal/MessageBubble.tsx b/phone/src/apps/messages/components/modal/MessageBubble.tsx index 2bfdc263c..a04b4b4e5 100644 --- a/phone/src/apps/messages/components/modal/MessageBubble.tsx +++ b/phone/src/apps/messages/components/modal/MessageBubble.tsx @@ -136,11 +136,11 @@ export const MessageBubble: React.FC = ({ message }) => { )} {!isMine && ( - + {getContact()?.display ?? message.author} )} - + {dayjs.unix(message.createdAt).fromNow()} diff --git a/phone/src/apps/messages/components/modal/MessageModal.tsx b/phone/src/apps/messages/components/modal/MessageModal.tsx index 6ee486fe7..5c02a70b2 100644 --- a/phone/src/apps/messages/components/modal/MessageModal.tsx +++ b/phone/src/apps/messages/components/modal/MessageModal.tsx @@ -24,9 +24,8 @@ import { useMessageAPI } from '../../hooks/useMessageAPI'; import { useCall } from '@os/call/hooks/useCall'; import { Call } from '@mui/icons-material'; import { useMessageActions } from '../../hooks/useMessageActions'; -import GroupDetailsModal from './GroupDetailsModal'; -import Backdrop from '@ui/components/Backdrop'; import { useMyPhoneNumber } from '@os/simcard/hooks/useMyPhoneNumber'; +import GroupDetailsModal from './GroupDetailsModal'; const LARGE_HEADER_CHARS = 30; const MAX_HEADER_CHARS = 80; @@ -67,13 +66,13 @@ export const MessageModal = () => { const { fetchMessages } = useMessageAPI(); const { getLabelOrContact, getConversationParticipant } = useMessageActions(); const { initializeCall } = useCall(); - const { removeGroupMember, leaveGroup } = useMessageAPI(); + const { removeGroupMember, leaveGroup, makeGroupOwner } = useMessageAPI(); const { getContactByNumber } = useContactActions(); const [messages, setMessages] = useMessagesState(); const [isLoaded, setLoaded] = useState(false); - const [isGroupModalOpen, setIsGroupModalOpen] = useState(false); + const [isGroupSettingsModalOpen, setIsGroupSettingsModalOpen] = useState(false); const myPhoneNumber = useMyPhoneNumber(); @@ -96,12 +95,12 @@ export const MessageModal = () => { history.push('/messages'); }; - const openGroupModal = () => { - setIsGroupModalOpen(true); + const openGroupSettingsModal = () => { + setIsGroupSettingsModalOpen(true); }; - const closeGroupModal = () => { - setIsGroupModalOpen(false); + const closeGroupSettingsModal = () => { + setIsGroupSettingsModalOpen(false); }; useEffect(() => { @@ -152,6 +151,10 @@ export const MessageModal = () => { ); }; + const handleMakeGroupOwner = (number: string) => { + makeGroupOwner(activeMessageConversation.id, number); + }; + const handleLeaveGroup = () => { leaveGroup( activeMessageConversation.conversationList, @@ -182,15 +185,14 @@ export const MessageModal = () => { }} > - {isGroupModalOpen && } { )} {activeMessageConversation.isGroupChat ? ( ) : !activeMessageConversation.isGroupChat && !doesContactExist ? ( + + ); +}; + +export default AddParticipantModal; diff --git a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx index cc12b70ca..6ad6b8a08 100644 --- a/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx +++ b/phone/src/apps/messages/components/modal/GroupDetailsModal.tsx @@ -19,6 +19,9 @@ import { MessageConversation } from '@typings/messages'; import { SearchField } from '@ui/components/SearchField'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import GroupMemberInfo from './GroupMemberInfo'; +import AddParticipantModal from './AddParticipantModal'; +import Backdrop from '@ui/components/Backdrop'; +import { useMessageAPI } from '../../hooks/useMessageAPI'; const useStyles = makeStyles((theme) => ({ root: { @@ -77,6 +80,7 @@ const GroupDetailsModal: React.FC = ({ const groupAmount = conversation.conversationList.split('+').length; const myPhoneNumber = useMyPhoneNumber(); const { getContactByNumber } = useContactActions(); + const { addGroupMembers } = useMessageAPI(); const [inputVal, setInputVal] = useState(''); const [participants, setParticipants] = useState( @@ -104,6 +108,16 @@ const GroupDetailsModal: React.FC = ({ setParticipants(participants.filter((participant) => participant !== number)); }; + const handleAddGroupMembers = (selectedParticipants: string[]) => { + setParticipants([...participants, ...selectedParticipants]); + addGroupMembers( + conversation.id, + selectedParticipants, + myPhoneNumber, + conversation.conversationList, + ); + }; + const closeGroupSettings = () => { setInputVal(null); setSelectedMember(''); @@ -115,6 +129,8 @@ const GroupDetailsModal: React.FC = ({ const [selectedMember, setSelectedMember] = useState(''); + const [isAddModalOpen, setIsAddModalOpen] = useState(false); + const selectMember = (member: string) => { setSelectedMember(member); setIsOptionsModalOpen(true); @@ -129,6 +145,14 @@ const GroupDetailsModal: React.FC = ({ return ( + setIsAddModalOpen(!isAddModalOpen)} + participants={participants} + myPhoneNumber={myPhoneNumber} + handleAddGroupMembers={handleAddGroupMembers} + /> + {isAddModalOpen && } = ({
{isGroupOwner && ( - )} diff --git a/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx b/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx index ecc2942a6..bdc99d796 100644 --- a/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx +++ b/phone/src/apps/messages/components/modal/GroupMemberInfo.tsx @@ -77,7 +77,16 @@ const GroupMemberInfo: React.FC = ({ hide: !isGroupOwner, }, ], - [isGroupOwner, contact, messageContact, participant, initializeCall, addContact, removeMember], + [ + isGroupOwner, + contact, + makeOwner, + participant, + addContact, + messageContact, + initializeCall, + removeMember, + ], ); return ; diff --git a/phone/src/apps/messages/components/modal/MessageBubble.tsx b/phone/src/apps/messages/components/modal/MessageBubble.tsx index a04b4b4e5..06fc9b063 100644 --- a/phone/src/apps/messages/components/modal/MessageBubble.tsx +++ b/phone/src/apps/messages/components/modal/MessageBubble.tsx @@ -48,9 +48,8 @@ const useStyles = makeStyles((theme) => ({ marginLeft: 5, maxWidth: '80%', height: 'auto', - background: '#282828', //should by theme shit here for dark/light mode - color: '#ddd', //should by theme shit here for dark/light mode - border: '0px', + background: theme.palette.background.default, + color: theme.palette.text.secondary, borderRadius: '8px', display: 'flex', justifyContent: 'center', diff --git a/phone/src/apps/messages/components/ui/SystemMessage.tsx b/phone/src/apps/messages/components/ui/SystemMessage.tsx index 7d9c4ec5c..3a4931d14 100644 --- a/phone/src/apps/messages/components/ui/SystemMessage.tsx +++ b/phone/src/apps/messages/components/ui/SystemMessage.tsx @@ -2,8 +2,10 @@ import { Message } from '@typings/messages'; import { useContactActions } from '../../../contacts/hooks/useContactActions'; import React from 'react'; import { Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; const SystemMessage = ({ message, myNumber }: { message: Message; myNumber: string }) => { + const [t] = useTranslation(); const { getContactByNumber } = useContactActions(); const getContact = (number: string) => { if (number === myNumber) { @@ -16,16 +18,18 @@ const SystemMessage = ({ message, myNumber }: { message: Message; myNumber: stri return ( <> {message.system_type === 'add' ? ( - {`${getContact(message.author)} added ${getContact( - message.system_number, - )}`} + {`${getContact(message.author)} ${t( + 'MESSAGES.SYSTEM_MESSAGES.ADDED', + )} ${getContact(message.system_number)}`} ) : message.system_type === 'remove' ? ( - {`${getContact(message.author)} removed ${getContact( - message.system_number, - )}`} + {`${getContact(message.author)} ${t( + 'MESSAGES.SYSTEM_MESSAGES.REMOVED', + )} ${getContact(message.system_number)}`} ) : ( message.system_type === 'leave' && ( - {`${getContact(message.author)} left`} + {`${getContact(message.author)} ${t( + 'MESSAGES.SYSTEM_MESSAGES.LEFT', + )}`} ) )} diff --git a/phone/src/apps/messages/hooks/useMessageAPI.ts b/phone/src/apps/messages/hooks/useMessageAPI.ts index 86849f09a..edf14f589 100644 --- a/phone/src/apps/messages/hooks/useMessageAPI.ts +++ b/phone/src/apps/messages/hooks/useMessageAPI.ts @@ -7,6 +7,7 @@ import { PreDBConversation, PreDBMessage, RemoveGroupMemberRequest, + AddGroupMemberRequest, } from '@typings/messages'; import { ServerPromiseResp } from '@typings/common'; import { useSnackbar } from '@os/snackbar/hooks/useSnackbar'; @@ -40,6 +41,12 @@ type UseMessageAPIProps = { ) => void; leaveGroup: (conversationList: string, conversationId: number, phoneNumber: string) => void; makeGroupOwner: (conversationId: number, phoneNumber: string) => void; + addGroupMembers: ( + conversationId: number, + phoneNumbers: string[], + myNumber: string, + conversationList: string, + ) => void; }; export const useMessageAPI = (): UseMessageAPIProps => { @@ -53,6 +60,7 @@ export const useMessageAPI = (): UseMessageAPIProps => { removeLocalGroupMember, setMessageReadState, updateLocalGroupOwner, + addLocalConversationParticipants, } = useMessageActions(); const history = useHistory(); const { state: messageConversationsState, contents: messageConversationsContents } = @@ -335,6 +343,43 @@ export const useMessageAPI = (): UseMessageAPIProps => { [addAlert, removeLocalGroupMember, sendSystemMessage, t], ); + const addGroupMembers = useCallback( + ( + conversationId: number, + phoneNumbers: string[], + myNumber: string, + conversationList: string, + ) => { + fetchNui, AddGroupMemberRequest>( + MessageEvents.ADD_GROUP_MEMBER, + { + phoneNumbers, + conversationId, + conversationList, + }, + ).then((resp) => { + if (resp.status !== 'ok') { + return addAlert({ + message: t('MESSAGES.FEEDBACK.ADD_GROUP_MEMBER_FAILED'), + type: 'error', + }); + } + addLocalConversationParticipants(conversationId, resp.data.conversationList); + phoneNumbers.forEach((phoneNumber) => { + sendSystemMessage({ + conversationId: conversationId, + conversationList: resp.data.conversationList, + tgtPhoneNumber: '', + is_system: true, + system_number: phoneNumber, + system_type: 'add', + }); + }); + }); + }, + [addAlert, addLocalConversationParticipants, sendSystemMessage, t], + ); + const leaveGroup = useCallback( (conversationList: string, conversationId: number, phoneNumber: string) => { fetchNui>(MessageEvents.DELETE_GROUP_MEMBER, { @@ -377,5 +422,6 @@ export const useMessageAPI = (): UseMessageAPIProps => { leaveGroup, sendSystemMessage, makeGroupOwner, + addGroupMembers, }; }; diff --git a/phone/src/apps/messages/hooks/useMessageActions.ts b/phone/src/apps/messages/hooks/useMessageActions.ts index 5f1073940..fec91fa50 100644 --- a/phone/src/apps/messages/hooks/useMessageActions.ts +++ b/phone/src/apps/messages/hooks/useMessageActions.ts @@ -21,6 +21,7 @@ interface MessageActionProps { getConversationParticipant: (conversationList: string) => Contact | null; removeLocalGroupMember: (conversationId: number, phoneNumber: string) => void; updateLocalGroupOwner: (conversationId: number, phoneNumber: string) => void; + addLocalConversationParticipants: (conversationId: number, conversationList: string) => void; } export const useMessageActions = (): MessageActionProps => { @@ -83,9 +84,7 @@ export const useMessageActions = (): MessageActionProps => { const removeLocalConversation = useCallback( (conversationsId: number[]) => { if (conversationLoading !== 'hasValue') return; - if (!conversations.length) return; - setMessageConversation((curVal) => [...curVal].filter((conversation) => !conversationsId.includes(conversation.id)), ); @@ -172,6 +171,23 @@ export const useMessageActions = (): MessageActionProps => { [getContactByNumber, myPhoneNumber], ); + const addLocalConversationParticipants = useCallback( + (conversationId: number, conversationList: string) => { + setMessageConversation((curVal) => + curVal.map((conversation) => { + if (conversation.id === conversationId) { + return { + ...conversation, + conversationList: conversationList, + }; + } + return conversation; + }), + ); + }, + [setMessageConversation], + ); + return { updateLocalConversations, removeLocalConversation, @@ -182,5 +198,6 @@ export const useMessageActions = (): MessageActionProps => { getConversationParticipant, removeLocalGroupMember, updateLocalGroupOwner, + addLocalConversationParticipants, }; }; diff --git a/phone/src/apps/messages/hooks/useMessageService.ts b/phone/src/apps/messages/hooks/useMessageService.ts index 8e70a92d8..8a08f3142 100644 --- a/phone/src/apps/messages/hooks/useMessageService.ts +++ b/phone/src/apps/messages/hooks/useMessageService.ts @@ -5,6 +5,7 @@ import { MessageConversation, MessageEvents, RemoveGroupMemberResponse, + AddGroupMemberResponse, } from '@typings/messages'; import { useMessageActions } from './useMessageActions'; import { useCallback } from 'react'; @@ -20,6 +21,7 @@ export const useMessagesService = () => { removeLocalConversation, removeLocalGroupMember, updateLocalGroupOwner, + addLocalConversationParticipants, } = useMessageActions(); const { setNotification } = useMessageNotifications(); const { pathname } = useLocation(); @@ -61,8 +63,8 @@ export const useMessagesService = () => { ); const handleDeleteConversation = useCallback( - (conversationId: number[]) => { - removeLocalConversation(conversationId); + (conversationsId: number[]) => { + removeLocalConversation(conversationsId); }, [removeLocalConversation], ); @@ -81,10 +83,18 @@ export const useMessagesService = () => { [updateLocalGroupOwner], ); + const updateParticipantList = useCallback( + (conversation: AddGroupMemberResponse) => { + addLocalConversationParticipants(conversation.conversationId, conversation.conversationList); + }, + [addLocalConversationParticipants], + ); + useNuiEvent('MESSAGES', MessageEvents.CREATE_MESSAGE_BROADCAST, handleMessageBroadcast); useNuiEvent('MESSAGES', MessageEvents.SEND_MESSAGE_SUCCESS, handleUpdateMessages); useNuiEvent('MESSAGES', MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, handleAddConversation); useNuiEvent('MESSAGES', MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, handleDeleteConversation); useNuiEvent('MESSAGES', MessageEvents.DELETE_GROUP_MEMBER_LIST, handleRemoveGroupMember); useNuiEvent('MESSAGES', MessageEvents.UPDATE_GROUP_OWNER, updateGroupOwner); + useNuiEvent('MESSAGES', MessageEvents.UPDATE_PARTICIPANT_LIST, updateParticipantList); }; diff --git a/phone/src/locale/en.json b/phone/src/locale/en.json index dcbf0f9c2..12e662fca 100644 --- a/phone/src/locale/en.json +++ b/phone/src/locale/en.json @@ -221,9 +221,15 @@ "DELETE_MESSAGE_FAILED": "Failed to delete message", "DELETE_CONVERSATION_FAILED": "Failed to delete conversation", "DELETE_GROUP_MEMBER_FAILED": "Failed to remove member", + "ADD_GROUP_MEMBER_FAILED": "Failed to add member", "LEAVE_GROUP_FAILED": "Failed to leave group", "MAKE_GROUP_OWNER_FAILED": "Failed to make group owner" }, + "SYSTEM_MESSAGES": { + "ADDED": "added", + "REMOVED": "removed", + "LEFT": "left" + }, "SEARCH_PLACEHOLDER": "Search messages...", "DELETE_CONVERSATION": "Delete conversation", "NEW_MESSAGE": "Message...", diff --git a/phone/src/os/phone/hooks/state.ts b/phone/src/os/phone/hooks/state.ts index e0e50ff0e..4428a6c69 100644 --- a/phone/src/os/phone/hooks/state.ts +++ b/phone/src/os/phone/hooks/state.ts @@ -4,7 +4,7 @@ import { ResourceConfig } from '@typings/config'; export const phoneState = { visibility: atom({ key: 'phoneVisibility', - default: false, + default: true, }), resourceConfig: atom({ key: 'resourceConfig', diff --git a/resources/client/cl_messages.ts b/resources/client/cl_messages.ts index 91a98d4d1..f8e4184dd 100644 --- a/resources/client/cl_messages.ts +++ b/resources/client/cl_messages.ts @@ -5,6 +5,7 @@ import { MessageEvents, PreDBMessage, RemoveGroupMemberResponse, + AddGroupMemberResponse, } from '../../typings/messages'; import { sendMessageEvent } from '../utils/messages'; import { RegisterNuiProxy, RegisterNuiCB } from './cl_utils'; @@ -15,6 +16,7 @@ RegisterNuiProxy(MessageEvents.FETCH_MESSAGES); RegisterNuiProxy(MessageEvents.CREATE_MESSAGE_CONVERSATION); RegisterNuiProxy(MessageEvents.DELETE_CONVERSATION); RegisterNuiProxy(MessageEvents.DELETE_GROUP_MEMBER); +RegisterNuiProxy(MessageEvents.ADD_GROUP_MEMBER); RegisterNuiProxy(MessageEvents.MAKE_GROUP_OWNER); RegisterNuiProxy(MessageEvents.SEND_MESSAGE); RegisterNuiProxy(MessageEvents.SET_MESSAGE_READ); @@ -36,7 +38,7 @@ onNet(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, (result: MessageConvers sendMessageEvent(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, result); }); -onNet(MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, (result: number[]) => { +onNet(MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, (result: { conversationsId: number[] }) => { sendMessageEvent(MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, result); }); @@ -47,3 +49,7 @@ onNet(MessageEvents.DELETE_GROUP_MEMBER_LIST, (result: RemoveGroupMemberResponse onNet(MessageEvents.UPDATE_GROUP_OWNER, (result: MakeGroupOwner) => { sendMessageEvent(MessageEvents.UPDATE_GROUP_OWNER, result); }); + +onNet(MessageEvents.UPDATE_PARTICIPANT_LIST, (result: AddGroupMemberResponse) => { + sendMessageEvent(MessageEvents.UPDATE_PARTICIPANT_LIST, result); +}); diff --git a/resources/server/messages/messages.controller.ts b/resources/server/messages/messages.controller.ts index 8d23538d5..7a20b73cb 100644 --- a/resources/server/messages/messages.controller.ts +++ b/resources/server/messages/messages.controller.ts @@ -9,6 +9,7 @@ import { PreDBConversation, PreDBMessage, RemoveGroupMemberRequest, + AddGroupMemberRequest, } from '../../../typings/messages'; import MessagesService from './messages.service'; import { messagesLogger } from './messages.utils'; @@ -129,6 +130,18 @@ onNetPromise( }, ); +onNetPromise( + MessageEvents.ADD_GROUP_MEMBER, + async (reqObj, resp) => { + MessagesService.handleAddGroupMember(reqObj, resp).catch((e) => { + messagesLogger.error( + `Error occurred while adding group members (${reqObj.source}), Error: ${e.message}`, + ); + resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); + }); + }, +); + onNetPromise(MessageEvents.MAKE_GROUP_OWNER, async (reqObj, resp) => { MessagesService.handleMakeGroupOwner(reqObj, resp).catch((e) => { messagesLogger.error( diff --git a/resources/server/messages/messages.db.ts b/resources/server/messages/messages.db.ts index 0483a81ee..d109b7a79 100644 --- a/resources/server/messages/messages.db.ts +++ b/resources/server/messages/messages.db.ts @@ -115,6 +115,11 @@ export class _MessagesDB { return conversationId; } + async updateConversationList(conversationId: number, conversationList: string) { + const conversationQuery = `UPDATE npwd_messages_conversations SET conversation_list = ? WHERE id = ?`; + await DbInterface._rawExec(conversationQuery, [conversationList, conversationId]); + } + async createMessage(dto: CreateMessageDTO): Promise { const query = `INSERT INTO npwd_messages (message, user_identifier, conversation_id, author, is_embed, embed, is_system, system_type, system_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; diff --git a/resources/server/messages/messages.service.ts b/resources/server/messages/messages.service.ts index 8c57123a8..b9805f082 100644 --- a/resources/server/messages/messages.service.ts +++ b/resources/server/messages/messages.service.ts @@ -19,6 +19,8 @@ import { RemoveGroupMemberResponse, ConversationListResponse, MakeGroupOwner, + AddGroupMemberRequest, + AddGroupMemberResponse, } from '../../../typings/messages'; import PlayerService from '../players/player.service'; import { emitNetTyped } from '../utils/miscUtils'; @@ -298,6 +300,82 @@ class _MessagesService { } } + async handleAddGroupMember( + reqObj: PromiseRequest, + resp: PromiseEventResp, + ) { + const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); + const groupOwner = await this.messagesDB.getGroupOwner(reqObj.data.conversationId); + + if (groupOwner !== phoneNumber) { + messagesLogger.error(`User does not own group. Error`); + return resp({ status: 'error' }); + } + + try { + const updatedConversationList = reqObj.data.conversationList + .split('+') + .concat(reqObj.data.phoneNumbers) + .join('+'); + + await this.messagesDB.updateConversationList( + reqObj.data.conversationId, + updatedConversationList, + ); + reqObj.data.phoneNumbers.forEach(async (phoneNumber) => { + await this.messagesDB.addParticipantToConversation(updatedConversationList, phoneNumber); + }); + + resp({ status: 'ok', data: { conversationList: updatedConversationList } }); + + const playerPhoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); + const participants = reqObj.data.conversationList.split('+'); + + const conversationData = await this.messagesDB.getConversation(reqObj.data.conversationId); + + for (const participant of participants) { + if (participant === playerPhoneNumber) { + // Don't send to self + continue; + } + + const participantIdentifier = await PlayerService.getIdentifierByPhoneNumber(participant); + const participantPlayer = PlayerService.getPlayerFromIdentifier(participantIdentifier); + + if (!participantPlayer) { + //if not online, move to next player + continue; + } + + //if this is the member already in the conversation + if (!reqObj.data.phoneNumbers.includes(participant)) { + emitNetTyped( + MessageEvents.UPDATE_PARTICIPANT_LIST, + { + conversationId: reqObj.data.conversationId, + conversationList: updatedConversationList, + }, + participantPlayer.source, + ); + } + + //if this is the new member being added + if (reqObj.data.phoneNumbers.includes(participant)) { + emitNetTyped( + MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, + { + ...conversationData, + }, + participantPlayer.source, + ); + } + } + } catch (err) { + messagesLogger.error(`Failed to remove from group. Error: ${err.message}`); + resp({ status: 'error' }); + } + } + async handleRemoveGroupMember( reqObj: PromiseRequest, resp: PromiseEventResp, @@ -354,7 +432,7 @@ class _MessagesService { } for (const participant of participants) { - if (!reqObj.data.leaveGroup || participant === playerPhoneNumber) { + if (!reqObj.data.leaveGroup && participant === playerPhoneNumber) { //if not leave group (kick) and the participant is the person kicking then move to next player as he already has updated data continue; } @@ -369,10 +447,10 @@ class _MessagesService { if (!reqObj.data.leaveGroup && participant === reqObj.data.phoneNumber) { //if not leave group/leave themself and participant is equal to nubmer remove then remove the chat from their list - emitNetTyped( + emitNetTyped<{ conversationsId: number[] }>( MessageEvents.DELETE_GROUP_MEMBER_CONVERSATION, { - conversationID: [reqObj.data.conversationId], + conversationsId: [reqObj.data.conversationId], }, participantPlayer.source, ); diff --git a/typings/messages.ts b/typings/messages.ts index d99436dba..b74978130 100644 --- a/typings/messages.ts +++ b/typings/messages.ts @@ -70,6 +70,12 @@ export interface RemoveGroupMemberRequest { leaveGroup: boolean; } +export interface AddGroupMemberRequest { + phoneNumbers: string[]; + conversationId: number; + conversationList: string; +} + export interface MakeGroupOwner { conversationId: number; phoneNumber: string; @@ -79,6 +85,11 @@ export interface ConversationListResponse { conversationList: string; } +export interface AddGroupMemberResponse { + conversationId: number; + conversationList: string; +} + /** * Used for the raw npwd_messages_groups row responses */ @@ -187,8 +198,10 @@ export enum MessageEvents { DELETE_GROUP_MEMBER = 'nwpd:deleteGroupMember', DELETE_GROUP_MEMBER_CONVERSATION = 'nwpd:deleteGroupMemberChat', DELETE_GROUP_MEMBER_LIST = 'nwpd:deleteGroupMemberList', + ADD_GROUP_MEMBER = 'nwpd:addGroupMember', MAKE_GROUP_OWNER = 'npwd:makeGroupOwner', UPDATE_GROUP_OWNER = 'npwd:makeGroupOwnerSuccess', + UPDATE_PARTICIPANT_LIST = 'npwd:updateParticipantList', } export interface Location { From aea51e1e0d097efc4e6460861576a2c6f27eeb22 Mon Sep 17 00:00:00 2001 From: SamShanks1 <68856260+SamShanks1@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:23:48 +0100 Subject: [PATCH 21/21] fix(message): merge mistakes --- .../components/modal/MessageBubble.tsx | 48 ++++++++----------- phone/src/apps/messages/utils/constants.ts | 12 +++++ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/phone/src/apps/messages/components/modal/MessageBubble.tsx b/phone/src/apps/messages/components/modal/MessageBubble.tsx index a9f243c92..4f43b8e1f 100644 --- a/phone/src/apps/messages/components/modal/MessageBubble.tsx +++ b/phone/src/apps/messages/components/modal/MessageBubble.tsx @@ -53,6 +53,7 @@ const useStyles = makeStyles((theme) => ({ borderRadius: '8px', display: 'flex', justifyContent: 'center', + }, myAudioSms: { float: 'right', margin: theme.spacing(1), @@ -158,56 +159,47 @@ export const MessageBubble: React.FC = ({ message }) => { justifyContent={message.is_system ? 'center' : isMine ? 'flex-end' : 'flex-start'} mt={1} > - {!message.is_system && <>{!isMine ? : null}} - {message.is_system ? ( - - ) : ( - <> - {message.is_embed ? ( + {message.is_system && } + {!message.is_system && ( <> - - - ) : ( - - {isMessageImage ? ( - - - - + {message.is_embed ? ( + <> + + ) : ( - {isImage(message.message) ? ( + {isMessageImage ? ( ) : ( <>{message.message} )} - {isMine && ( + {showVertIcon && ( )} )} - {showVertIcon && ( - - - + {!isMine && ( + + {getContact()?.display ?? message.author} + )} - + {dayjs.unix(message.createdAt).fromNow()} diff --git a/phone/src/apps/messages/utils/constants.ts b/phone/src/apps/messages/utils/constants.ts index f1a0d539a..f79812a33 100644 --- a/phone/src/apps/messages/utils/constants.ts +++ b/phone/src/apps/messages/utils/constants.ts @@ -66,6 +66,18 @@ const MockConversationMessages: Message[] = [ phoneNumber: '111-1134', }), }, + { + id: 6, + author: '111-1134', + message: '', + createdAt: 16234242422, + is_system: true, + system_type: 'leave', + }, + + // is_system?: boolean; + // system_type?: 'leave' | 'remove' | 'add'; + // system_number?: string; ]; export const MockConversationServerResp: ServerPromiseResp = {