From 0c4fae5cb24b4b706db4aec6b3fdf4286f433363 Mon Sep 17 00:00:00 2001
From: JRB958 <141108231+JRB958@users.noreply.github.com>
Date: Fri, 28 Feb 2025 02:13:08 -0500
Subject: [PATCH 01/28] GH-365: [feat] UI template for Gifted Chat library,
might have to change later due to incompatibility with react native expo
---
ClientApp/app/(tabs)/chats/index.tsx | 16 ++---
ClientApp/app/_layout.tsx | 31 +++++-----
ClientApp/app/messaging/[id].tsx | 52 ++++++++++++++++
ClientApp/components/chat/ChatCard.tsx | 79 ++++++++++++++++++++++++
ClientApp/components/chat/ChatList.tsx | 83 ++++++++++++++++++++++++++
ClientApp/package.json | 1 +
6 files changed, 237 insertions(+), 25 deletions(-)
create mode 100644 ClientApp/app/messaging/[id].tsx
create mode 100644 ClientApp/components/chat/ChatCard.tsx
create mode 100644 ClientApp/components/chat/ChatList.tsx
diff --git a/ClientApp/app/(tabs)/chats/index.tsx b/ClientApp/app/(tabs)/chats/index.tsx
index 75e92fb25..9f5b50cf9 100644
--- a/ClientApp/app/(tabs)/chats/index.tsx
+++ b/ClientApp/app/(tabs)/chats/index.tsx
@@ -1,15 +1,9 @@
import React from 'react';
-import { SafeAreaView, StyleSheet, View, Text } from 'react-native';
-
-
-const Chats = () => {
-
-
- return (
-
- Chats
-
- );
+import ChatList from '@/components/chat/ChatList';
+const Chats =() => {
+ return (
+
+ )
}
export default Chats
\ No newline at end of file
diff --git a/ClientApp/app/_layout.tsx b/ClientApp/app/_layout.tsx
index 4f229db69..305e83ec6 100644
--- a/ClientApp/app/_layout.tsx
+++ b/ClientApp/app/_layout.tsx
@@ -1,6 +1,7 @@
import { Stack } from 'expo-router';
import { Provider } from 'react-redux';
import { store } from '@/state/store';
+import "../global.css";
import { setupAxiosInstance } from '@/services/axiosInstance';
import { NotificationProvider } from "@/context/NotificationContext";
import * as Notifications from "expo-notifications"
@@ -23,20 +24,22 @@ export default function RootLayout() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/ClientApp/app/messaging/[id].tsx b/ClientApp/app/messaging/[id].tsx
new file mode 100644
index 000000000..f16b89aae
--- /dev/null
+++ b/ClientApp/app/messaging/[id].tsx
@@ -0,0 +1,52 @@
+import { useLocalSearchParams, useRouter } from 'expo-router';
+import React, { useCallback, useEffect, useState } from 'react';
+import { StyleSheet } from 'react-native';
+import { GiftedChat, IMessage } from 'react-native-gifted-chat';
+
+const ChatScreen = () => {
+ const { id } = useLocalSearchParams();
+ const router = useRouter();
+ const [messages, setMessages] = useState([]);
+
+ useEffect(() => {
+ setMessages([
+ {
+ _id: 1,
+ text: 'Hello developer',
+ createdAt: new Date(),
+ user: {
+ _id: 2,
+ name: 'React Native',
+ },
+ },
+ ]);
+ }, []);
+
+ const onSend = useCallback((newMessages: IMessage[] = []) => {
+ if (!Array.isArray(newMessages)) return;
+ setMessages(previousMessages => GiftedChat.append(previousMessages, newMessages));
+ }, []);
+
+ return (
+
+ onSend(messages)}
+ user={{ _id: 1 }}
+ />
+ );
+};
+
+export default ChatScreen;
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+});
diff --git a/ClientApp/components/chat/ChatCard.tsx b/ClientApp/components/chat/ChatCard.tsx
new file mode 100644
index 000000000..7d112c9ea
--- /dev/null
+++ b/ClientApp/components/chat/ChatCard.tsx
@@ -0,0 +1,79 @@
+import { mvs } from '@/utils/helpers/uiScaler';
+import { useRouter } from 'expo-router';
+import React from 'react';
+import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
+
+interface CardProps {
+ chatId: string;
+ userName: string;
+ userImg: any;
+ messageTime: string;
+ messageText: string;
+}
+
+const ChatCard: React.FC = ({ chatId, userName, userImg, messageTime, messageText }) => {
+ const router = useRouter();
+
+ return (
+ router.push(`/messaging/${chatId}`)}
+>
+
+
+
+
+
+
+ {userName}
+ {messageTime}
+
+ {messageText}
+
+
+
+ );
+};
+
+export default ChatCard;
+
+const styles = StyleSheet.create({
+ card: {
+ width: '100%',
+ padding: 10,
+ borderBottomWidth: 1,
+ borderBottomColor: '#ccc',
+ },
+ userInfo: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ userImgWrapper: {
+ paddingVertical: 10,
+ },
+ userImg: {
+ width: mvs(60),
+ height: mvs(60),
+ borderRadius: 25,
+ },
+ textSection: {
+ flex: 1,
+ marginLeft: 10,
+ },
+ userInfoText: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ userName: {
+ fontSize: 14,
+ fontWeight: 'bold',
+ },
+ postTime: {
+ fontSize: 12,
+ color: '#666',
+ },
+ messageText: {
+ fontSize: 14,
+ color: '#333',
+ },
+});
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
new file mode 100644
index 000000000..48270930a
--- /dev/null
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import ChatCard from '@/components/chat/ChatCard';
+import { FlatList } from 'react-native';
+const Chats =() => {
+
+ interface CardProps {
+ chatId: string;
+ userName: string;
+ userImg: any;
+ messageTime: string;
+ messageText: string;
+ }
+
+ const chatData: CardProps[] = [
+ {
+ chatId: '1',
+ userName: 'Alice Smith',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: '2 mins ago',
+ messageText: 'Hey Alice! How have you been?',
+ },
+ {
+ chatId: '2',
+ userName: 'Bob Johnson',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: '10 mins ago',
+ messageText: 'Are we still on for tonight?',
+ },
+ {
+ chatId: '3',
+ userName: 'Charlie Davis',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: '30 mins ago',
+ messageText: 'That was a great game yesterday!',
+ },
+ {
+ chatId: '4',
+ userName: 'Diana Prince',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: '1 hour ago',
+ messageText: 'Let’s catch up soon!',
+ },
+ {
+ chatId: '5',
+ userName: 'Edward Norton',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: '3 hours ago',
+ messageText: 'Do you have the notes from the meeting?',
+ },
+ {
+ chatId: '6',
+ userName: 'Fiona Gallagher',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: 'Yesterday',
+ messageText: 'Hope you had a great weekend!',
+ },
+ {
+ chatId: '7',
+ userName: 'George Michael',
+ userImg: require('@/assets/images/avatar-placeholder.png'),
+ messageTime: '2 days ago',
+ messageText: 'Thanks for the recommendation!',
+ },
+ ];
+
+ return (
+ item.chatId}
+ renderItem={({ item }) => (
+
+ )}
+ />
+ )
+}
+
+export default Chats
\ No newline at end of file
diff --git a/ClientApp/package.json b/ClientApp/package.json
index fdcdb1e32..95a3ce197 100644
--- a/ClientApp/package.json
+++ b/ClientApp/package.json
@@ -103,6 +103,7 @@
"react-native-elements": "^3.4.3",
"react-native-gesture-handler": "~2.20.2",
"react-native-get-random-values": "^1.11.0",
+ "react-native-gifted-chat": "^2.6.5",
"react-native-google-places-autocomplete": "^2.5.7",
"react-native-image-viewing": "^0.2.2",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
From 0d27e17b35b5de131ff656301ff1a3a5e460edc6 Mon Sep 17 00:00:00 2001
From: JRB958 <141108231+JRB958@users.noreply.github.com>
Date: Fri, 28 Feb 2025 19:06:59 -0500
Subject: [PATCH 02/28] add the library back to package.json
---
ClientApp/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ClientApp/package.json b/ClientApp/package.json
index 95a3ce197..4acf0d297 100644
--- a/ClientApp/package.json
+++ b/ClientApp/package.json
@@ -107,7 +107,7 @@
"react-native-google-places-autocomplete": "^2.5.7",
"react-native-image-viewing": "^0.2.2",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
- "react-native-maps": "1.18.0",
+ "react-native-maps": "1.20.1",
"react-native-mime-types": "^2.5.0",
"react-native-modal-datetime-picker": "^18.0.0",
"react-native-pager-view": "6.5.1",
From e2011b43c7117633d84c4a66ef81f16abc0fbbb1 Mon Sep 17 00:00:00 2001
From: DanDuguay
Date: Wed, 5 Mar 2025 20:42:33 -0500
Subject: [PATCH 03/28] GH-365: [Feat] Initial commit
---
ClientApp/components/chat/ChatList.tsx | 104 +++++++++++++++----------
ClientApp/services/chatService.ts | 15 ++++
ClientApp/utils/api/endpoints.tsx | 3 +
3 files changed, 81 insertions(+), 41 deletions(-)
create mode 100644 ClientApp/services/chatService.ts
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index 48270930a..c861239ae 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -1,79 +1,101 @@
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import ChatCard from '@/components/chat/ChatCard';
-import { FlatList } from 'react-native';
+import {Alert, FlatList} from 'react-native';
+import {useSelector} from "react-redux";
+import {getChatrooms} from "@/services/chatService";
+import {User} from "react-native-gifted-chat";
+
const Chats =() => {
+ const [chatrooms, setChatrooms] = useState([]);
+ const [user, setUser] = useState(null);
interface CardProps {
- chatId: string;
- userName: string;
+ chatroomId: string;
+ userId: string;
userImg: any;
- messageTime: string;
- messageText: string;
+ createdAt: string;
+ content: string;
+ }
+
+ const fetchChatrooms = async () => {
+ try {
+ const user = useSelector((state: {user: any }) => state.user);
+ setUser(user);
+ const chatroomData = await getChatrooms(user.id);
+ setChatrooms(chatroomData);
+ } catch (error) {
+ Alert.alert("Error", "Failed to fetch chatrooms.");
+ }
}
+
+ useEffect(() => {
+ fetchChatrooms();
+ })
+
const chatData: CardProps[] = [
{
- chatId: '1',
- userName: 'Alice Smith',
+ chatroomId: '1',
+ userId: 'Alice Smith',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: '2 mins ago',
- messageText: 'Hey Alice! How have you been?',
+ createdAt: '2 mins ago',
+ content: 'Hey Alice! How have you been?',
},
{
- chatId: '2',
- userName: 'Bob Johnson',
+ chatroomId: '2',
+ userId: 'Bob Johnson',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: '10 mins ago',
- messageText: 'Are we still on for tonight?',
+ createdAt: '10 mins ago',
+ content: 'Are we still on for tonight?',
},
{
- chatId: '3',
- userName: 'Charlie Davis',
+ chatroomId: '3',
+ userId: 'Charlie Davis',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: '30 mins ago',
- messageText: 'That was a great game yesterday!',
+ createdAt: '30 mins ago',
+ content: 'That was a great game yesterday!',
},
{
- chatId: '4',
- userName: 'Diana Prince',
+ chatroomId: '4',
+ userId: 'Diana Prince',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: '1 hour ago',
- messageText: 'Let’s catch up soon!',
+ createdAt: '1 hour ago',
+ content: 'Let’s catch up soon!',
},
{
- chatId: '5',
- userName: 'Edward Norton',
+ chatroomId: '5',
+ userId: 'Edward Norton',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: '3 hours ago',
- messageText: 'Do you have the notes from the meeting?',
+ createdAt: '3 hours ago',
+ content: 'Do you have the notes from the meeting?',
},
{
- chatId: '6',
- userName: 'Fiona Gallagher',
+ chatroomId: '6',
+ userId: 'Fiona Gallagher',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: 'Yesterday',
- messageText: 'Hope you had a great weekend!',
+ createdAt: 'Yesterday',
+ content: 'Hope you had a great weekend!',
},
{
- chatId: '7',
- userName: 'George Michael',
+ chatroomId: '7',
+ userId: 'George Michael',
userImg: require('@/assets/images/avatar-placeholder.png'),
- messageTime: '2 days ago',
- messageText: 'Thanks for the recommendation!',
+ createdAt: '2 days ago',
+ content: 'Thanks for the recommendation!',
},
];
return (
item.chatId}
+ data={chatrooms}
+ keyExtractor={(item) => item.chatroomId}
renderItem={({ item }) => (
)}
/>
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
new file mode 100644
index 000000000..4ca5931b2
--- /dev/null
+++ b/ClientApp/services/chatService.ts
@@ -0,0 +1,15 @@
+import { getAxiosInstance } from '@/services/axiosInstance';
+import { API_ENDPOINTS } from '@/utils/api/endpoints';
+// API_ENDPOINTS.GET_CHATROOMS
+
+export const getChatrooms = async (userId: string) => {
+ try {
+ const axiosInstance = await getAxiosInstance();
+ const response = await axiosInstance.get(
+ API_ENDPOINTS.GET_CHATROOMS.replace("{userId}", userId));
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching chatrooms:', error);
+ throw error;
+ }
+};
diff --git a/ClientApp/utils/api/endpoints.tsx b/ClientApp/utils/api/endpoints.tsx
index d104a0f91..45a191cc0 100644
--- a/ClientApp/utils/api/endpoints.tsx
+++ b/ClientApp/utils/api/endpoints.tsx
@@ -31,6 +31,7 @@ export const API_ENDPOINTS = {
GET_ALL_FRIENDS: 'user-service/user/{userId}/friends',
RESPOND_TO_FRIEND_REQUEST: 'user-service/user/{userId}/friend-requests/{requestId}',
GET_PROFILE_BY_ID: 'user-service/user/{userId}/profile',
+
GET_PUBLIC_PROFILE: 'user-service/user/{userId}/profile',
GET_ALL_POSTS: "event-service/event/{eventId}/social/post",
@@ -38,4 +39,6 @@ export const API_ENDPOINTS = {
UPLOAD_FILE: "storage-service/objects/upload",
GET_FILE: "storage-service/objects/file{objectPath}",
+
+ GET_CHATROOMS: 'messaging-service/messaging/chatrooms/{userId}',
};
From 083528759528533162300bb61e1c02fc69c6f286 Mon Sep 17 00:00:00 2001
From: DanDuguay
Date: Mon, 24 Mar 2025 11:23:38 -0400
Subject: [PATCH 04/28] GH-365: [Feat] Connecting to the websocket with
authorization and subscribing to a specific topic working
---
ClientApp/app/_layout.tsx | 2 +
ClientApp/app/messaging/[id].tsx | 128 +++++++++++++++++--
ClientApp/components/chat/ChatList.tsx | 79 ++++++------
ClientApp/package.json | 3 +
ClientApp/services/axiosLocalInstance.ts | 155 +++++++++++++++++++++++
ClientApp/services/chatService.ts | 33 ++++-
ClientApp/services/tokenService.ts | 4 +-
ClientApp/utils/api/endpoints.tsx | 1 +
8 files changed, 349 insertions(+), 56 deletions(-)
create mode 100644 ClientApp/services/axiosLocalInstance.ts
diff --git a/ClientApp/app/_layout.tsx b/ClientApp/app/_layout.tsx
index 305e83ec6..6ad88feb7 100644
--- a/ClientApp/app/_layout.tsx
+++ b/ClientApp/app/_layout.tsx
@@ -3,6 +3,7 @@ import { Provider } from 'react-redux';
import { store } from '@/state/store';
import "../global.css";
import { setupAxiosInstance } from '@/services/axiosInstance';
+import { setupLocalAxiosInstance } from "@/services/axiosLocalInstance";
import { NotificationProvider } from "@/context/NotificationContext";
import * as Notifications from "expo-notifications"
import 'react-native-get-random-values';
@@ -21,6 +22,7 @@ export default function RootLayout() {
// Inject Redux dispatch into Axios
const { dispatch } = store;
setupAxiosInstance(dispatch);
+ setupLocalAxiosInstance(dispatch);
return (
diff --git a/ClientApp/app/messaging/[id].tsx b/ClientApp/app/messaging/[id].tsx
index f16b89aae..5b07b0fb5 100644
--- a/ClientApp/app/messaging/[id].tsx
+++ b/ClientApp/app/messaging/[id].tsx
@@ -1,31 +1,131 @@
import { useLocalSearchParams, useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, {useCallback, useEffect, useRef, useState} from 'react';
import { StyleSheet } from 'react-native';
import { GiftedChat, IMessage } from 'react-native-gifted-chat';
+import {useSelector} from "react-redux";
+import {getMessages, getChatroom} from "@/services/chatService";
+import { Client } from "@stomp/stompjs";
+import { getAccessToken } from "@/services/tokenService"
+
+interface message {
+ messageId: string;
+ chatroomId: string;
+ senderId: string;
+ receiverIds: string[];
+ content: string;
+ createdAt: string;
+ attachments: string[];
+}
+
+interface chatroomProps {
+ chatroomId: string;
+ createdAt: string,
+ createdBy: string,
+ members: string[],
+ messages: message[],
+ isEvent: boolean,
+ unread: boolean,
+}
const ChatScreen = () => {
const { id } = useLocalSearchParams();
const router = useRouter();
const [messages, setMessages] = useState([]);
+ const [finalToken, setFinalToken] = useState("");
+ const user = useSelector((state: {user: any}) => state.user);
+ const [connected, setConnected] = useState(false);
+ const clientRef = useRef(null);
+ const [chatroom, setChatroom] = useState([]);
useEffect(() => {
- setMessages([
- {
- _id: 1,
- text: 'Hello developer',
- createdAt: new Date(),
- user: {
- _id: 2,
- name: 'React Native',
- },
- },
- ]);
+ const response = async () => {
+ try {
+ const token = await getAccessToken();
+ if (token) {
+ setFinalToken(token);
+ }
+ } catch (error) {
+ console.error('Failed to get access token:', error);
+ }
+ };
+
+ response();
}, []);
+
+ useEffect(() => {
+ if (!finalToken) return;
+
+ const client = new Client({
+ brokerURL: "ws://localhost:8080/api/messaging-service/ws",
+ heartbeatIncoming: 0,
+ heartbeatOutgoing: 0,
+ connectHeaders: {
+ Authorization: "Bearer " + finalToken,
+ },
+ //forceBinaryWSFrames: true,
+ //appendMissingNULLonIncoming: true,
+
+ onWebSocketError: (error: any) => console.error("Websocket error:", error),
+ onConnect: () => {
+ setConnected(true);
+
+ client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
+ console.log(JSON.parse(message.body));
+ setMessages((prev: any) => [...prev, message.body]);
+ },
+ {
+ "Authorization": "Bearer " + finalToken,
+ });
+ },
+ onDisconnect: () => setConnected(false),
+ debug: (msg: any) => console.log(msg),
+ onStompError: (frame: any) => console.error("Stomp error:", frame),
+
+ });
+
+ client.activate();
+ clientRef.current = client;
+
+ const fetchChatroom = async () => {
+ try {
+ const messagesData = await getMessages(id.toString())
+ const chatroomData = await getChatroom(id.toString())
+ setChatroom(chatroomData);
+ setMessages(messagesData);
+ } catch (error) {
+ console.error("Failed to fetch messages", error);
+ throw error;
+ }
+ }
+ fetchChatroom();
+
+ return () => {
+ client.deactivate();
+ }
+ }, [finalToken]);
+
const onSend = useCallback((newMessages: IMessage[] = []) => {
+
+ console.log(finalToken);
if (!Array.isArray(newMessages)) return;
- setMessages(previousMessages => GiftedChat.append(previousMessages, newMessages));
- }, []);
+ try {
+ const message = newMessages[0];
+
+
+
+ // @ts-ignore
+ clientRef.current.publish({
+ destination: "/app/message",
+ headers: {Authorization: "Bearer " + finalToken},
+ body: JSON.stringify(message),
+ })
+ } catch (error) {
+ console.error("Failed to publish messages", error);
+ }
+
+ //setMessages(previousMessages => GiftedChat.append(previousMessages, newMessages));
+ }, [finalToken]);
return (
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index c861239ae..cda060e75 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -5,85 +5,88 @@ import {useSelector} from "react-redux";
import {getChatrooms} from "@/services/chatService";
import {User} from "react-native-gifted-chat";
-const Chats =() => {
- const [chatrooms, setChatrooms] = useState([]);
- const [user, setUser] = useState(null);
+interface CardProps {
+ chatroomId: string;
+ createdBy: string;
+ //userImg: any;
+ createdAt: string;
+ //content: string;
+}
- interface CardProps {
- chatroomId: string;
- userId: string;
- userImg: any;
- createdAt: string;
- content: string;
- }
- const fetchChatrooms = async () => {
- try {
- const user = useSelector((state: {user: any }) => state.user);
- setUser(user);
- const chatroomData = await getChatrooms(user.id);
- setChatrooms(chatroomData);
- } catch (error) {
- Alert.alert("Error", "Failed to fetch chatrooms.");
- }
- }
+const Chats: React.FC = () => {
+ const [chatrooms, setChatrooms] = useState([]);
+ const user = useSelector((state: {user: any}) => state.user);
useEffect(() => {
- fetchChatrooms();
- })
+ const fetchChatrooms = async (user: any) => {
+ try {
+ const chatroomData = await getChatrooms(user.id);
+ setChatrooms(chatroomData);
+
+ } catch (error) {
+ console.error("Failed to fetch chatrooms:", error);
+ throw error;
+ }
+ }
+ fetchChatrooms(user);
+ }, []);
+
+ /*
const chatData: CardProps[] = [
{
chatroomId: '1',
- userId: 'Alice Smith',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'Alice Smith',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: '2 mins ago',
content: 'Hey Alice! How have you been?',
},
{
chatroomId: '2',
- userId: 'Bob Johnson',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'Bob Johnson',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: '10 mins ago',
content: 'Are we still on for tonight?',
},
{
chatroomId: '3',
- userId: 'Charlie Davis',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'Charlie Davis',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: '30 mins ago',
content: 'That was a great game yesterday!',
},
{
chatroomId: '4',
- userId: 'Diana Prince',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'Diana Prince',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: '1 hour ago',
content: 'Let’s catch up soon!',
},
{
chatroomId: '5',
- userId: 'Edward Norton',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'Edward Norton',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: '3 hours ago',
content: 'Do you have the notes from the meeting?',
},
{
chatroomId: '6',
- userId: 'Fiona Gallagher',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'Fiona Gallagher',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: 'Yesterday',
content: 'Hope you had a great weekend!',
},
{
chatroomId: '7',
- userId: 'George Michael',
- userImg: require('@/assets/images/avatar-placeholder.png'),
+ createdBy: 'George Michael',
+ //userImg: require('@/assets/images/avatar-placeholder.png'),
createdAt: '2 days ago',
content: 'Thanks for the recommendation!',
},
];
+ */
return (
{
renderItem={({ item }) => (
)}
diff --git a/ClientApp/package.json b/ClientApp/package.json
index 4acf0d297..d0bfd1f91 100644
--- a/ClientApp/package.json
+++ b/ClientApp/package.json
@@ -62,6 +62,7 @@
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
"@reduxjs/toolkit": "^2.4.0",
+ "@stomp/stompjs": "^7.0.1",
"@testing-library/react": "^16.0.1",
"date-fns": "^2.30.0",
"eas-cli": "^15.0.12",
@@ -122,6 +123,8 @@
"react-native-vector-icons": "^10.2.0",
"react-native-web": "~0.19.10",
"react-redux": "^9.1.2",
+ "react-stomp": "^5.1.0",
+ "sockjs-client": "^1.6.1",
"tailwindcss": "^3.4.14",
"use-debounce": "^10.0.4",
"expo-file-system": "~18.0.12"
diff --git a/ClientApp/services/axiosLocalInstance.ts b/ClientApp/services/axiosLocalInstance.ts
new file mode 100644
index 000000000..096b58060
--- /dev/null
+++ b/ClientApp/services/axiosLocalInstance.ts
@@ -0,0 +1,155 @@
+import axios, { AxiosInstance, AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios';
+import { refreshAccessToken, getAuthHeaders } from './tokenService';
+import { API_ENDPOINTS } from '@/utils/api/endpoints';
+import { logoutUser } from './authService';
+import { ALERT_MESSAGES, consoleError } from '@/utils/api/errorHandlers';
+
+interface ErrorResponseData {
+ error?: string;
+ [key: string]: any; // To account for additional properties in the response
+}
+
+let axiosLocalInstance: AxiosInstance | null = null;
+
+export const getAxiosLocalInstance = (): AxiosInstance => {
+ if (!axiosLocalInstance) {
+ throw new Error('Axios instance not configured. Call setupAxiosInstance(dispatch) before using it.');
+ }
+ return axiosLocalInstance;
+};
+
+export const setupLocalAxiosInstance = (dispatch: any): AxiosInstance => {
+ if ((global as any).axiosLocalInstance) {
+ return (global as any).axiosLocalInstance;
+ }
+ const config: AxiosRequestConfig = {
+ baseURL: 'http://localhost:8080/api/', // Fallback for baseURL
+ timeout: 5000,
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ };
+
+ axiosLocalInstance = axios.create(config); // Assign to global variable
+
+ // Request interceptor
+ axiosLocalInstance.interceptors.request.use(
+ async (config) => {
+ try {
+ const AUTH_ENDPOINTS = [
+ API_ENDPOINTS.LOGIN,
+ API_ENDPOINTS.REFRESH_TOKEN,
+ API_ENDPOINTS.REGISTER,
+ API_ENDPOINTS.RESET_PASSWORD,
+ ];
+
+ // Skip adding Authorization header for auth-related endpoints
+ if (AUTH_ENDPOINTS.some((endpoint) => config.url?.includes(endpoint))) {
+ return config;
+ }
+
+ const headers = await getAuthHeaders();
+ config.headers.Authorization = headers.Authorization;
+ return config;
+ } catch (error) {
+ console.error('Error attaching headers:', error);
+ throw error;
+ }
+ },
+ (error) => Promise.reject(error)
+ );
+
+ // Response interceptor
+ axiosLocalInstance.interceptors.response.use(
+ (response: AxiosResponse) => response, // Pass through successful responses
+ async (error: AxiosError) => {
+ const status = error.response?.status;
+ const errorData = error.response?.data;
+ console.log(axiosLocalInstance);
+
+ switch (status) {
+ case 400:
+ consoleError(ALERT_MESSAGES.badRequest.title, ALERT_MESSAGES.badRequest.message, status);
+ break;
+
+ case 401: {
+ const retryCount = (error.config as any)?._retryCount || 0;
+ const MAX_RETRY_LIMIT = 1;
+
+ // Handle invalid credentials
+ if (errorData?.error && errorData.error.includes('invalid_grant')) {
+ consoleError(
+ ALERT_MESSAGES.invalidCredentials.title,
+ ALERT_MESSAGES.invalidCredentials.message,
+ status
+ );
+ return Promise.reject(new Error(ALERT_MESSAGES.invalidCredentials.message));
+ }
+
+ // Retry logic for token expiration
+ if (retryCount >= MAX_RETRY_LIMIT) {
+ console.error('Retry limit exceeded.');
+ await logoutUser(dispatch);
+ return Promise.reject(error);
+ }
+
+ (error.config as any)._retryCount = retryCount + 1;
+
+ try {
+ await refreshAccessToken();
+ const headers = await getAuthHeaders();
+ if (!axiosLocalInstance) {
+ throw new Error('Axios instance is not configured.');
+ }
+ const retryConfig = {
+ ...error.config,
+ headers: { ...error.config?.headers, ...headers },
+ };
+ return axiosLocalInstance.request(retryConfig);
+ } catch (refreshError) {
+ await logoutUser(dispatch);
+ console.error('Token refresh failed:', refreshError);
+ return Promise.reject(refreshError);
+ }
+ }
+
+ case 403:
+ consoleError(ALERT_MESSAGES.forbidden.title, ALERT_MESSAGES.forbidden.message, status);
+ break;
+
+ case 404:
+ consoleError(ALERT_MESSAGES.notFound.title, ALERT_MESSAGES.notFound.message, status);
+ break;
+
+ case 409:
+ consoleError(ALERT_MESSAGES.conflict.title, ALERT_MESSAGES.conflict.message, status);
+ break;
+
+ case 500:
+ consoleError(ALERT_MESSAGES.serverError.title, ALERT_MESSAGES.serverError.message, status);
+ break;
+
+ case 503:
+ consoleError(ALERT_MESSAGES.serviceUnavailable.title, ALERT_MESSAGES.serviceUnavailable.message, status);
+ break;
+
+ default:
+ consoleError(ALERT_MESSAGES.defaultError.title, ALERT_MESSAGES.defaultError.message);
+ break;
+ }
+
+ // Handle network errors or cases without a response
+ if (!error.response) {
+ console.error('Network error or no response:', error.message);
+ return Promise.reject(new Error(ALERT_MESSAGES.networkError.message));
+ }
+
+ return Promise.reject(error);
+ }
+ );
+ (global as any).axiosInstance = axiosLocalInstance;
+ return axiosLocalInstance;
+};
+
+
\ No newline at end of file
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index 4ca5931b2..d6748ec24 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -1,11 +1,25 @@
import { getAxiosInstance } from '@/services/axiosInstance';
+import { getAxiosLocalInstance } from '@/services/axiosLocalInstance';
import { API_ENDPOINTS } from '@/utils/api/endpoints';
+import {store} from "@/state/store";
// API_ENDPOINTS.GET_CHATROOMS
-export const getChatrooms = async (userId: string) => {
+export const getChatroom = async (chatroomId: string) => {
try {
const axiosInstance = await getAxiosInstance();
- const response = await axiosInstance.get(
+ const response = await axiosInstance.get(`/chatroom/${chatroomId}`);
+ return response.data;
+ } catch (error) {
+ console.error("Error fetching chatroom:", error);
+ throw error;
+ }
+};
+
+export const getChatrooms = async (userId: string) => {
+
+ try {
+ const axiosLocalInstance = await getAxiosLocalInstance();
+ const response = await axiosLocalInstance.get(
API_ENDPOINTS.GET_CHATROOMS.replace("{userId}", userId));
return response.data;
} catch (error) {
@@ -13,3 +27,18 @@ export const getChatrooms = async (userId: string) => {
throw error;
}
};
+
+// API_ENDPOINTS.GET_MESSAGES
+export const getMessages = async (chatroomId: string) => {
+
+ try {
+ const axiosLocalInstance = await getAxiosLocalInstance();
+ const response = await axiosLocalInstance.get(
+ API_ENDPOINTS.GET_MESSAGES.replace("{chatroomId}", chatroomId));
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching messages:', error);
+ throw error;
+ }
+};
+
diff --git a/ClientApp/services/tokenService.ts b/ClientApp/services/tokenService.ts
index dea48c7ce..9dd4c27a7 100644
--- a/ClientApp/services/tokenService.ts
+++ b/ClientApp/services/tokenService.ts
@@ -82,7 +82,7 @@ export function stopTokenRefresh() {
//#endregion
//#region Helpers
-function decodeJWT(jwt: string): { exp: number } {
+export function decodeJWT(jwt: string): { exp: number } {
try {
const payload = JSON.parse(
Buffer.from(
@@ -112,7 +112,7 @@ function joinToken(part1: string, part2: string): string {
return part1 + part2;
}
-async function getAccessToken() {
+export async function getAccessToken() {
try {
const part1 = await getFromSecureStore('accessTokenPart1');
const part2 = await getFromSecureStore('accessTokenPart2');
diff --git a/ClientApp/utils/api/endpoints.tsx b/ClientApp/utils/api/endpoints.tsx
index 45a191cc0..b50b8cd1a 100644
--- a/ClientApp/utils/api/endpoints.tsx
+++ b/ClientApp/utils/api/endpoints.tsx
@@ -41,4 +41,5 @@ export const API_ENDPOINTS = {
GET_FILE: "storage-service/objects/file{objectPath}",
GET_CHATROOMS: 'messaging-service/messaging/chatrooms/{userId}',
+ GET_MESSAGES: 'messaging/chatrooms/messages/{chatroomId}',
};
From 89509713d6bf02b2c94a3e16d6a7c1e97afc5400 Mon Sep 17 00:00:00 2001
From: DanDuguay
Date: Mon, 24 Mar 2025 13:39:23 -0400
Subject: [PATCH 05/28] GH-365: [Feat] Sending messages works
---
ClientApp/app/messaging/[id].tsx | 116 +++++++++++++++++--------
ClientApp/components/chat/ChatList.tsx | 56 ------------
ClientApp/services/chatService.ts | 11 +--
ClientApp/utils/api/endpoints.tsx | 3 +-
4 files changed, 90 insertions(+), 96 deletions(-)
diff --git a/ClientApp/app/messaging/[id].tsx b/ClientApp/app/messaging/[id].tsx
index 5b07b0fb5..e3de8e0d3 100644
--- a/ClientApp/app/messaging/[id].tsx
+++ b/ClientApp/app/messaging/[id].tsx
@@ -13,18 +13,26 @@ interface message {
senderId: string;
receiverIds: string[];
content: string;
- createdAt: string;
+ createdAt: Number | Date;
attachments: string[];
}
interface chatroomProps {
chatroomId: string;
- createdAt: string,
- createdBy: string,
- members: string[],
- messages: message[],
- isEvent: boolean,
- unread: boolean,
+ createdAt: Number | Date;
+ createdBy: string;
+ members: string[];
+ messages: message[];
+ isEvent: boolean;
+ unread: boolean;
+}
+
+interface messageRequest {
+ chatroomId: string;
+ senderId: string;
+ receiverIds: string[];
+ content: string;
+ attachments: string[];
}
const ChatScreen = () => {
@@ -35,7 +43,7 @@ const ChatScreen = () => {
const user = useSelector((state: {user: any}) => state.user);
const [connected, setConnected] = useState(false);
const clientRef = useRef(null);
- const [chatroom, setChatroom] = useState([]);
+ const [chatroom, setChatroom] = useState();
useEffect(() => {
const response = async () => {
@@ -63,8 +71,6 @@ const ChatScreen = () => {
connectHeaders: {
Authorization: "Bearer " + finalToken,
},
- //forceBinaryWSFrames: true,
- //appendMissingNULLonIncoming: true,
onWebSocketError: (error: any) => console.error("Websocket error:", error),
onConnect: () => {
@@ -72,7 +78,19 @@ const ChatScreen = () => {
client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
console.log(JSON.parse(message.body));
- setMessages((prev: any) => [...prev, message.body]);
+ setMessages((prev: IMessage[]) => [
+ ...prev,
+ {
+ _id: JSON.parse(message.body).messageId,
+ text: JSON.parse(message.body).content,
+ createdAt: new Date(JSON.parse(message.body).createdAt),
+ user: {
+ _id: JSON.parse(message.body).senderId,
+ name: 'Sender Name', // Replace with actual sender name if available
+ avatar: 'https://example.com/sender-avatar.png', // Replace with actual avatar URL if available
+ },
+ },
+ ]);
},
{
"Authorization": "Bearer " + finalToken,
@@ -87,17 +105,33 @@ const ChatScreen = () => {
client.activate();
clientRef.current = client;
- const fetchChatroom = async () => {
- try {
- const messagesData = await getMessages(id.toString())
- const chatroomData = await getChatroom(id.toString())
- setChatroom(chatroomData);
- setMessages(messagesData);
- } catch (error) {
- console.error("Failed to fetch messages", error);
- throw error;
- }
+ const fetchChatroom = async () => {
+ try {
+ const messagesData = await getMessages(id.toString());
+ console.log("messageData: ", messagesData);
+
+ // Ensure messagesData is mapped to IMessage structure
+ const formattedMessages = messagesData.map((message: any) => ({
+ _id: message.messageId,
+ text: message.content,
+ createdAt: new Date(message.createdAt),
+ user: {
+ _id: message.senderId,
+ name: message.senderName || "Unknown", // Replace with sender's name if available
+ avatar: message.senderAvatar || "https://example.com/default-avatar.png", // Replace with avatar URL if available
+ },
+ }));
+
+ const chatroomData = await getChatroom(id.toString());
+ console.log("chatroomData: ", chatroomData);
+
+ setChatroom(chatroomData);
+ setMessages(formattedMessages);
+ } catch (error) {
+ console.error("Failed to fetch messages", error);
+ throw error;
}
+ };
fetchChatroom();
return () => {
@@ -106,33 +140,47 @@ const ChatScreen = () => {
}, [finalToken]);
const onSend = useCallback((newMessages: IMessage[] = []) => {
+ let attachments: string[] = [];
+ console.log("newMessages: ", newMessages);
+ console.log("chatroom: ", chatroom);
- console.log(finalToken);
if (!Array.isArray(newMessages)) return;
try {
- const message = newMessages[0];
-
-
-
+ const newMessage = newMessages[0];
+ if (newMessage.audio != undefined) {
+ attachments.push(newMessage.audio)
+ }
+ if (newMessage.video != undefined) {
+ attachments.push(newMessage.video)
+ }
+ if (chatroom != undefined) {
+ const newMessageRequest: messageRequest = {
+ chatroomId: chatroom.chatroomId,
+ attachments: attachments,
+ content: newMessage.text,
+ receiverIds: chatroom.members,
+ senderId: user.id,
+ }
+ console.log("newMessageRequest: ",newMessageRequest);
// @ts-ignore
- clientRef.current.publish({
- destination: "/app/message",
+ clientRef.current.publish({
+ destination: "/app/message",
headers: {Authorization: "Bearer " + finalToken},
- body: JSON.stringify(message),
- })
- } catch (error) {
+ body: JSON.stringify(newMessageRequest),
+ })
+ }
+ } catch (error) {
console.error("Failed to publish messages", error);
}
- //setMessages(previousMessages => GiftedChat.append(previousMessages, newMessages));
- }, [finalToken]);
+ }, [finalToken, chatroom]);
return (
onSend(messages)}
- user={{ _id: 1 }}
+ user={{ _id: user.id }}
/>
);
};
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index cda060e75..000523e54 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -13,8 +13,6 @@ interface CardProps {
//content: string;
}
-
-
const Chats: React.FC = () => {
const [chatrooms, setChatrooms] = useState([]);
const user = useSelector((state: {user: any}) => state.user);
@@ -34,60 +32,6 @@ const Chats: React.FC = () => {
fetchChatrooms(user);
}, []);
- /*
- const chatData: CardProps[] = [
- {
- chatroomId: '1',
- createdBy: 'Alice Smith',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: '2 mins ago',
- content: 'Hey Alice! How have you been?',
- },
- {
- chatroomId: '2',
- createdBy: 'Bob Johnson',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: '10 mins ago',
- content: 'Are we still on for tonight?',
- },
- {
- chatroomId: '3',
- createdBy: 'Charlie Davis',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: '30 mins ago',
- content: 'That was a great game yesterday!',
- },
- {
- chatroomId: '4',
- createdBy: 'Diana Prince',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: '1 hour ago',
- content: 'Let’s catch up soon!',
- },
- {
- chatroomId: '5',
- createdBy: 'Edward Norton',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: '3 hours ago',
- content: 'Do you have the notes from the meeting?',
- },
- {
- chatroomId: '6',
- createdBy: 'Fiona Gallagher',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: 'Yesterday',
- content: 'Hope you had a great weekend!',
- },
- {
- chatroomId: '7',
- createdBy: 'George Michael',
- //userImg: require('@/assets/images/avatar-placeholder.png'),
- createdAt: '2 days ago',
- content: 'Thanks for the recommendation!',
- },
- ];
- */
-
return (
{
try {
- const axiosInstance = await getAxiosInstance();
- const response = await axiosInstance.get(`/chatroom/${chatroomId}`);
+ const axiosLocalInstance = getAxiosLocalInstance();
+ const response = await axiosLocalInstance.get(API_ENDPOINTS.GET_CHATROOM.replace("{chatroomId}", chatroomId));
return response.data;
} catch (error) {
console.error("Error fetching chatroom:", error);
@@ -15,10 +15,11 @@ export const getChatroom = async (chatroomId: string) => {
}
};
+// API_ENDPOINTS.GET_CHATROOMS
export const getChatrooms = async (userId: string) => {
try {
- const axiosLocalInstance = await getAxiosLocalInstance();
+ const axiosLocalInstance = getAxiosLocalInstance();
const response = await axiosLocalInstance.get(
API_ENDPOINTS.GET_CHATROOMS.replace("{userId}", userId));
return response.data;
@@ -32,7 +33,7 @@ export const getChatrooms = async (userId: string) => {
export const getMessages = async (chatroomId: string) => {
try {
- const axiosLocalInstance = await getAxiosLocalInstance();
+ const axiosLocalInstance = getAxiosLocalInstance();
const response = await axiosLocalInstance.get(
API_ENDPOINTS.GET_MESSAGES.replace("{chatroomId}", chatroomId));
return response.data;
diff --git a/ClientApp/utils/api/endpoints.tsx b/ClientApp/utils/api/endpoints.tsx
index b50b8cd1a..b3f3270e2 100644
--- a/ClientApp/utils/api/endpoints.tsx
+++ b/ClientApp/utils/api/endpoints.tsx
@@ -41,5 +41,6 @@ export const API_ENDPOINTS = {
GET_FILE: "storage-service/objects/file{objectPath}",
GET_CHATROOMS: 'messaging-service/messaging/chatrooms/{userId}',
- GET_MESSAGES: 'messaging/chatrooms/messages/{chatroomId}',
+ GET_MESSAGES: 'messaging-service/messaging/chatrooms/messages/{chatroomId}',
+ GET_CHATROOM: 'messaging-service/messaging/chatroom/{chatroomId}',
};
From 3a5593cc595fd8fb27a3a955acf3d7405847a092 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Fri, 28 Mar 2025 11:11:55 -0400
Subject: [PATCH 06/28] GH-365: [feat] Getting started on frontend
---
ClientApp/components/chat/ChatList.tsx | 4 ++--
ClientApp/services/chatService.ts | 13 ++++++-------
ClientApp/utils/api/endpoints.tsx | 2 +-
3 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index 000523e54..2b565e8ab 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react';
import ChatCard from '@/components/chat/ChatCard';
import {Alert, FlatList} from 'react-native';
import {useSelector} from "react-redux";
-import {getChatrooms} from "@/services/chatService";
+import {getAllChatrooms} from "@/services/chatService";
import {User} from "react-native-gifted-chat";
interface CardProps {
@@ -21,7 +21,7 @@ const Chats: React.FC = () => {
const fetchChatrooms = async (user: any) => {
try {
- const chatroomData = await getChatrooms(user.id);
+ const chatroomData = await getAllChatrooms(user.id);
setChatrooms(chatroomData);
} catch (error) {
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index a3f81c5ea..c7434e103 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -1,12 +1,11 @@
import { getAxiosInstance } from '@/services/axiosInstance';
-import { getAxiosLocalInstance } from '@/services/axiosLocalInstance';
import { API_ENDPOINTS } from '@/utils/api/endpoints';
import {store} from "@/state/store";
// API_ENDPOINTS.GET_CHATROOM
export const getChatroom = async (chatroomId: string) => {
try {
- const axiosLocalInstance = getAxiosLocalInstance();
+ const axiosLocalInstance = getAxiosInstance();
const response = await axiosLocalInstance.get(API_ENDPOINTS.GET_CHATROOM.replace("{chatroomId}", chatroomId));
return response.data;
} catch (error) {
@@ -16,12 +15,12 @@ export const getChatroom = async (chatroomId: string) => {
};
// API_ENDPOINTS.GET_CHATROOMS
-export const getChatrooms = async (userId: string) => {
+export const getAllChatrooms = async (userId: string) => {
try {
- const axiosLocalInstance = getAxiosLocalInstance();
- const response = await axiosLocalInstance.get(
- API_ENDPOINTS.GET_CHATROOMS.replace("{userId}", userId));
+ const axiosLocalInstance = getAxiosInstance();
+ const response = await axiosLocalInstance.get(API_ENDPOINTS.GET_All_CHATROOMS.replace("{userId}", userId));
+ console.log("Chatrooms response:", response.data);
return response.data;
} catch (error) {
console.error('Error fetching chatrooms:', error);
@@ -33,7 +32,7 @@ export const getChatrooms = async (userId: string) => {
export const getMessages = async (chatroomId: string) => {
try {
- const axiosLocalInstance = getAxiosLocalInstance();
+ const axiosLocalInstance = getAxiosInstance();
const response = await axiosLocalInstance.get(
API_ENDPOINTS.GET_MESSAGES.replace("{chatroomId}", chatroomId));
return response.data;
diff --git a/ClientApp/utils/api/endpoints.tsx b/ClientApp/utils/api/endpoints.tsx
index b3f3270e2..3086d6200 100644
--- a/ClientApp/utils/api/endpoints.tsx
+++ b/ClientApp/utils/api/endpoints.tsx
@@ -40,7 +40,7 @@ export const API_ENDPOINTS = {
UPLOAD_FILE: "storage-service/objects/upload",
GET_FILE: "storage-service/objects/file{objectPath}",
- GET_CHATROOMS: 'messaging-service/messaging/chatrooms/{userId}',
+ GET_All_CHATROOMS: 'messaging-service/messaging/chatrooms/{userId}',
GET_MESSAGES: 'messaging-service/messaging/chatrooms/messages/{chatroomId}',
GET_CHATROOM: 'messaging-service/messaging/chatroom/{chatroomId}',
};
From b7399604723a2d5d9a2026a48e9e6c3cb4f153c4 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Fri, 28 Mar 2025 13:35:52 -0400
Subject: [PATCH 07/28] GH-365: [refactor] Add messaging types to message.ts
---
ClientApp/app/messaging/[id].tsx | 30 ++--------------------
ClientApp/components/chat/ChatCard.tsx | 14 +++++++++--
ClientApp/components/chat/ChatList.tsx | 26 +++++++++++++++++--
ClientApp/package.json | 1 +
ClientApp/services/chatService.ts | 35 ++++++++++++++++++++++++++
ClientApp/types/messaging.ts | 28 +++++++++++++++++++++
ClientApp/utils/api/endpoints.tsx | 2 ++
7 files changed, 104 insertions(+), 32 deletions(-)
create mode 100644 ClientApp/types/messaging.ts
diff --git a/ClientApp/app/messaging/[id].tsx b/ClientApp/app/messaging/[id].tsx
index e3de8e0d3..bad200121 100644
--- a/ClientApp/app/messaging/[id].tsx
+++ b/ClientApp/app/messaging/[id].tsx
@@ -6,34 +6,8 @@ import {useSelector} from "react-redux";
import {getMessages, getChatroom} from "@/services/chatService";
import { Client } from "@stomp/stompjs";
import { getAccessToken } from "@/services/tokenService"
+import {message, chatroomProps, messageRequest} from "@/types/messaging";
-interface message {
- messageId: string;
- chatroomId: string;
- senderId: string;
- receiverIds: string[];
- content: string;
- createdAt: Number | Date;
- attachments: string[];
-}
-
-interface chatroomProps {
- chatroomId: string;
- createdAt: Number | Date;
- createdBy: string;
- members: string[];
- messages: message[];
- isEvent: boolean;
- unread: boolean;
-}
-
-interface messageRequest {
- chatroomId: string;
- senderId: string;
- receiverIds: string[];
- content: string;
- attachments: string[];
-}
const ChatScreen = () => {
const { id } = useLocalSearchParams();
@@ -65,7 +39,7 @@ const ChatScreen = () => {
if (!finalToken) return;
const client = new Client({
- brokerURL: "ws://localhost:8080/api/messaging-service/ws",
+ brokerURL: "wss://api.sportahub.app/api/messaging-service/ws",
heartbeatIncoming: 0,
heartbeatOutgoing: 0,
connectHeaders: {
diff --git a/ClientApp/components/chat/ChatCard.tsx b/ClientApp/components/chat/ChatCard.tsx
index 7d112c9ea..7625467da 100644
--- a/ClientApp/components/chat/ChatCard.tsx
+++ b/ClientApp/components/chat/ChatCard.tsx
@@ -9,16 +9,26 @@ interface CardProps {
userImg: any;
messageTime: string;
messageText: string;
+ onLongPress?: () => void;
+
}
-const ChatCard: React.FC = ({ chatId, userName, userImg, messageTime, messageText }) => {
+const ChatCard: React.FC = ({
+ chatId,
+ userName,
+ userImg,
+ messageTime,
+ messageText,
+ onLongPress
+}) => {
const router = useRouter();
return (
router.push(`/messaging/${chatId}`)}
->
+ onLongPress={onLongPress}
+ >
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index 2b565e8ab..335dc0de7 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react';
import ChatCard from '@/components/chat/ChatCard';
import {Alert, FlatList} from 'react-native';
import {useSelector} from "react-redux";
-import {getAllChatrooms} from "@/services/chatService";
+import {deleteChatroom, getAllChatrooms} from "@/services/chatService";
import {User} from "react-native-gifted-chat";
interface CardProps {
@@ -17,8 +17,29 @@ const Chats: React.FC = () => {
const [chatrooms, setChatrooms] = useState([]);
const user = useSelector((state: {user: any}) => state.user);
+ const handleDelete = (chatroomId: string) => {
+ Alert.alert(
+ "Delete Chat",
+ "Do you want to delete this chat?",
+ [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => {
+ try {
+ await deleteChatroom(chatroomId);
+ setChatrooms(prev => prev.filter(c => c.chatroomId !== chatroomId));
+ } catch (e) {
+ console.error("Failed to delete chatroom:", e);
+ Alert.alert("Error", "Failed to delete chat. Please try again.");
+ }
+ }
+ }
+ ]
+ );
+ };
useEffect(() => {
-
const fetchChatrooms = async (user: any) => {
try {
const chatroomData = await getAllChatrooms(user.id);
@@ -43,6 +64,7 @@ const Chats: React.FC = () => {
messageTime={item.createdAt}
userName={user.username}
chatId={item.chatroomId}
+ onLongPress={() => handleDelete(item.chatroomId)}
/>
)}
/>
diff --git a/ClientApp/package.json b/ClientApp/package.json
index d0bfd1f91..05e9f937f 100644
--- a/ClientApp/package.json
+++ b/ClientApp/package.json
@@ -118,6 +118,7 @@
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",
"react-native-svg": "15.8.0",
+ "react-native-swipe-list-view": "^3.2.9",
"react-native-tab-view": "^4.0.1",
"react-native-toast-message": "^2.2.1",
"react-native-vector-icons": "^10.2.0",
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index c7434e103..61bb7a326 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -1,6 +1,7 @@
import { getAxiosInstance } from '@/services/axiosInstance';
import { API_ENDPOINTS } from '@/utils/api/endpoints';
import {store} from "@/state/store";
+import { message } from '@/types/messaging';
// API_ENDPOINTS.GET_CHATROOM
export const getChatroom = async (chatroomId: string) => {
@@ -42,3 +43,37 @@ export const getMessages = async (chatroomId: string) => {
}
};
+// API_ENDPOINTS.CREATE_CHATROOM
+export const createUserChatroom = async (createrId: string, nameOfChatRoom: string, members: message [], messages: string[], isEvent: boolean, unread: boolean) => {
+ try {
+ const axiosLocalInstance = getAxiosInstance();
+ const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM,
+ {
+ createdBy: createrId,
+ chatroomName: nameOfChatRoom,
+ members,
+ messages,
+ isEvent,
+ unread
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error creating chatroom:', error);
+ throw error;
+ }
+}
+
+
+
+// API_ENDPOINTS.DELETE_CHATROOM
+export const deleteChatroom = async (chatroomId: string) => {
+ try {
+ const axiosLocalInstance = getAxiosInstance();
+ const response = await axiosLocalInstance.delete(API_ENDPOINTS.DELETE_CHATROOM.replace("{chatroomId}", chatroomId));
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting chatroom:', error);
+ throw error;
+ }
+};
diff --git a/ClientApp/types/messaging.ts b/ClientApp/types/messaging.ts
new file mode 100644
index 000000000..9d593286d
--- /dev/null
+++ b/ClientApp/types/messaging.ts
@@ -0,0 +1,28 @@
+export interface message {
+ messageId: string;
+ chatroomId: string; //required
+ senderId: string; //required
+ receiverIds: string[]; //required
+ senderName: string;
+ content: string; //required
+ createdAt: Number | Date;
+ attachments: string[];
+ }
+
+export interface chatroomProps {
+ chatroomId: string;
+ createdAt: Number | Date;
+ createdBy: string;
+ members: string[];
+ messages: message[];
+ isEvent: boolean;
+ unread: boolean;
+ }
+
+export interface messageRequest {
+ chatroomId: string;
+ senderId: string;
+ receiverIds: string[];
+ content: string;
+ attachments: string[];
+ }
\ No newline at end of file
diff --git a/ClientApp/utils/api/endpoints.tsx b/ClientApp/utils/api/endpoints.tsx
index 3086d6200..aa64fa02e 100644
--- a/ClientApp/utils/api/endpoints.tsx
+++ b/ClientApp/utils/api/endpoints.tsx
@@ -43,4 +43,6 @@ export const API_ENDPOINTS = {
GET_All_CHATROOMS: 'messaging-service/messaging/chatrooms/{userId}',
GET_MESSAGES: 'messaging-service/messaging/chatrooms/messages/{chatroomId}',
GET_CHATROOM: 'messaging-service/messaging/chatroom/{chatroomId}',
+ CREATE_CHATROOM: 'messaging-service/messaging/chatroom',
+ DELETE_CHATROOM: 'messaging-service/messaging/chatroom/{chatroomId}',
};
From 86a58ddd55a644a00094301b097605130247cf9b Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Fri, 28 Mar 2025 19:12:00 -0400
Subject: [PATCH 08/28] GH-365: [feat] Create a chat from a friend's profile
page, doesn't route to chatroom yet
---
ClientApp/app/userProfiles/[id].tsx | 1 +
.../components/Profile/ProfileSection.tsx | 28 +++++++++----
ClientApp/components/chat/ChatCard.tsx | 6 +--
ClientApp/components/chat/ChatList.tsx | 42 +++++++++++--------
ClientApp/services/chatService.ts | 5 ++-
5 files changed, 51 insertions(+), 31 deletions(-)
diff --git a/ClientApp/app/userProfiles/[id].tsx b/ClientApp/app/userProfiles/[id].tsx
index 16a599ba7..a852ba4e8 100644
--- a/ClientApp/app/userProfiles/[id].tsx
+++ b/ClientApp/app/userProfiles/[id].tsx
@@ -273,6 +273,7 @@ const ProfilePage: React.FC = () => {
return (
void | Promise | null;
@@ -22,13 +26,16 @@ interface ProfileRequest {
}
const ProfileSection: React.FC = ({
+ visitedId,
user,
friendStatus,
handleFriendRequest,
handleRemoveFriend,
isUserProfile,
}) => {
+ const router = useRouter();
const [loading, setLoading] = useState(false);
+ const loggedInUser = useSelector((state: { user: any }) => state.user);
const { t } = useTranslation();
if (loading) {
@@ -47,6 +54,16 @@ const ProfileSection: React.FC = ({
);
}
+ const handlePressMessage = async () => {
+ // Handle press event
+ console.log("Message button pressed");
+ try{
+ const response = await createUserChatroom(loggedInUser.id, visitedId, user.username, [], false, false);
+ }catch(e){
+ console.log("Error in handlePress", e);
+ }
+ };
+
return (
<>
= ({
)}
{/* Message button */}
-
+
= ({
>
{t('profile_section.message')}
+
= ({
chatId,
- userName,
+ cardTitle,
userImg,
messageTime,
messageText,
@@ -35,7 +35,7 @@ const ChatCard: React.FC = ({
- {userName}
+ {cardTitle}
{messageTime}
{messageText}
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index 335dc0de7..dfdc2afea 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -1,19 +1,21 @@
-import React, {useEffect, useState} from 'react';
+import React, {useCallback, useEffect, useState} from 'react';
import ChatCard from '@/components/chat/ChatCard';
import {Alert, FlatList} from 'react-native';
import {useSelector} from "react-redux";
import {deleteChatroom, getAllChatrooms} from "@/services/chatService";
import {User} from "react-native-gifted-chat";
+import { useFocusEffect } from '@react-navigation/native';
interface CardProps {
chatroomId: string;
createdBy: string;
- //userImg: any;
+ userImg: any;
createdAt: string;
- //content: string;
+ content: string;
+ chatroomName: string
}
-const Chats: React.FC = () => {
+const Chats = () => {
const [chatrooms, setChatrooms] = useState([]);
const user = useSelector((state: {user: any}) => state.user);
@@ -39,19 +41,25 @@ const Chats: React.FC = () => {
]
);
};
- useEffect(() => {
- const fetchChatrooms = async (user: any) => {
- try {
- const chatroomData = await getAllChatrooms(user.id);
- setChatrooms(chatroomData);
- } catch (error) {
- console.error("Failed to fetch chatrooms:", error);
- throw error;
- }
- }
- fetchChatrooms(user);
- }, []);
+ useFocusEffect(
+ useCallback(() => {
+ const fetchChatrooms = async (user: any) => {
+ try {
+ const chatroomData = await getAllChatrooms(user.id);
+ setChatrooms(chatroomData);
+ } catch (error) {
+ console.error("Failed to fetch chatrooms:", error);
+ throw error;
+ }
+ };
+ fetchChatrooms(user);
+
+ return () => {
+ setChatrooms([]); // Cleanup if needed
+ };
+ }, [user])
+ );
return (
= () => {
userImg={require('@/assets/images/avatar-placeholder.png')}
messageText={"hello"}
messageTime={item.createdAt}
- userName={user.username}
+ cardTitle={item.chatroomName}
chatId={item.chatroomId}
onLongPress={() => handleDelete(item.chatroomId)}
/>
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index 61bb7a326..1ab28fcff 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -44,19 +44,20 @@ export const getMessages = async (chatroomId: string) => {
};
// API_ENDPOINTS.CREATE_CHATROOM
-export const createUserChatroom = async (createrId: string, nameOfChatRoom: string, members: message [], messages: string[], isEvent: boolean, unread: boolean) => {
+export const createUserChatroom = async (createrId: string, chatWithUserId: string, nameOfChatRoom: string, messages: string[], isEvent: boolean, unread: boolean) => {
try {
const axiosLocalInstance = getAxiosInstance();
const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM,
{
createdBy: createrId,
chatroomName: nameOfChatRoom,
- members,
+ members : [createrId, chatWithUserId],
messages,
isEvent,
unread
}
);
+ console.log("createUserChatroom response:", response.data);
return response.data;
} catch (error) {
console.error('Error creating chatroom:', error);
From 5ad11fc7418e11fc18afc86a07e6fcc0d2cbad4b Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Fri, 28 Mar 2025 19:25:04 -0400
Subject: [PATCH 09/28] GH-365: [refactor] Some temporary cleanups
---
ClientApp/app/messaging/[id].tsx | 218 +++++++++++++++---------------
ClientApp/services/chatService.ts | 1 -
2 files changed, 109 insertions(+), 110 deletions(-)
diff --git a/ClientApp/app/messaging/[id].tsx b/ClientApp/app/messaging/[id].tsx
index bad200121..6d7d5c9ba 100644
--- a/ClientApp/app/messaging/[id].tsx
+++ b/ClientApp/app/messaging/[id].tsx
@@ -35,117 +35,117 @@ const ChatScreen = () => {
}, []);
- useEffect(() => {
- if (!finalToken) return;
-
- const client = new Client({
- brokerURL: "wss://api.sportahub.app/api/messaging-service/ws",
- heartbeatIncoming: 0,
- heartbeatOutgoing: 0,
- connectHeaders: {
- Authorization: "Bearer " + finalToken,
- },
-
- onWebSocketError: (error: any) => console.error("Websocket error:", error),
- onConnect: () => {
- setConnected(true);
-
- client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
- console.log(JSON.parse(message.body));
- setMessages((prev: IMessage[]) => [
- ...prev,
- {
- _id: JSON.parse(message.body).messageId,
- text: JSON.parse(message.body).content,
- createdAt: new Date(JSON.parse(message.body).createdAt),
- user: {
- _id: JSON.parse(message.body).senderId,
- name: 'Sender Name', // Replace with actual sender name if available
- avatar: 'https://example.com/sender-avatar.png', // Replace with actual avatar URL if available
- },
- },
- ]);
- },
- {
- "Authorization": "Bearer " + finalToken,
- });
- },
- onDisconnect: () => setConnected(false),
- debug: (msg: any) => console.log(msg),
- onStompError: (frame: any) => console.error("Stomp error:", frame),
-
- });
-
- client.activate();
- clientRef.current = client;
-
- const fetchChatroom = async () => {
- try {
- const messagesData = await getMessages(id.toString());
- console.log("messageData: ", messagesData);
-
- // Ensure messagesData is mapped to IMessage structure
- const formattedMessages = messagesData.map((message: any) => ({
- _id: message.messageId,
- text: message.content,
- createdAt: new Date(message.createdAt),
- user: {
- _id: message.senderId,
- name: message.senderName || "Unknown", // Replace with sender's name if available
- avatar: message.senderAvatar || "https://example.com/default-avatar.png", // Replace with avatar URL if available
- },
- }));
-
- const chatroomData = await getChatroom(id.toString());
- console.log("chatroomData: ", chatroomData);
-
- setChatroom(chatroomData);
- setMessages(formattedMessages);
- } catch (error) {
- console.error("Failed to fetch messages", error);
- throw error;
- }
- };
- fetchChatroom();
-
- return () => {
- client.deactivate();
- }
- }, [finalToken]);
+ // useEffect(() => {
+ // if (!finalToken) return;
+
+ // const client = new Client({
+ // brokerURL: "wss://api.sportahub.app/api/messaging-service/ws",
+ // heartbeatIncoming: 0,
+ // heartbeatOutgoing: 0,
+ // connectHeaders: {
+ // Authorization: "Bearer " + finalToken,
+ // },
+
+ // onWebSocketError: (error: any) => console.error("Websocket error:", error),
+ // onConnect: () => {
+ // setConnected(true);
+
+ // client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
+ // console.log(JSON.parse(message.body));
+ // setMessages((prev: IMessage[]) => [
+ // ...prev,
+ // {
+ // _id: JSON.parse(message.body).messageId,
+ // text: JSON.parse(message.body).content,
+ // createdAt: new Date(JSON.parse(message.body).createdAt),
+ // user: {
+ // _id: JSON.parse(message.body).senderId,
+ // name: 'Sender Name', // Replace with actual sender name if available
+ // avatar: 'https://example.com/sender-avatar.png', // Replace with actual avatar URL if available
+ // },
+ // },
+ // ]);
+ // },
+ // {
+ // "Authorization": "Bearer " + finalToken,
+ // });
+ // },
+ // onDisconnect: () => setConnected(false),
+ // debug: (msg: any) => console.log(msg),
+ // onStompError: (frame: any) => console.error("Stomp error:", frame),
+
+ // });
+
+ // client.activate();
+ // clientRef.current = client;
+
+ // const fetchChatroom = async () => {
+ // try {
+ // const messagesData = await getMessages(id.toString());
+ // console.log("messageData: ", messagesData);
+
+ // // Ensure messagesData is mapped to IMessage structure
+ // const formattedMessages = messagesData.map((message: any) => ({
+ // _id: message.messageId,
+ // text: message.content,
+ // createdAt: new Date(message.createdAt),
+ // user: {
+ // _id: message.senderId,
+ // name: message.senderName || "Unknown", // Replace with sender's name if available
+ // avatar: message.senderAvatar || "https://example.com/default-avatar.png", // Replace with avatar URL if available
+ // },
+ // }));
+
+ // const chatroomData = await getChatroom(id.toString());
+ // console.log("chatroomData: ", chatroomData);
+
+ // setChatroom(chatroomData);
+ // setMessages(formattedMessages);
+ // } catch (error) {
+ // console.error("Failed to fetch messages", error);
+ // throw error;
+ // }
+ // };
+ // fetchChatroom();
+
+ // return () => {
+ // client.deactivate();
+ // }
+ // }, [finalToken]);
const onSend = useCallback((newMessages: IMessage[] = []) => {
- let attachments: string[] = [];
- console.log("newMessages: ", newMessages);
- console.log("chatroom: ", chatroom);
-
- if (!Array.isArray(newMessages)) return;
- try {
- const newMessage = newMessages[0];
- if (newMessage.audio != undefined) {
- attachments.push(newMessage.audio)
- }
- if (newMessage.video != undefined) {
- attachments.push(newMessage.video)
- }
- if (chatroom != undefined) {
- const newMessageRequest: messageRequest = {
- chatroomId: chatroom.chatroomId,
- attachments: attachments,
- content: newMessage.text,
- receiverIds: chatroom.members,
- senderId: user.id,
- }
- console.log("newMessageRequest: ",newMessageRequest);
- // @ts-ignore
- clientRef.current.publish({
- destination: "/app/message",
- headers: {Authorization: "Bearer " + finalToken},
- body: JSON.stringify(newMessageRequest),
- })
- }
- } catch (error) {
- console.error("Failed to publish messages", error);
- }
+ // let attachments: string[] = [];
+ // console.log("newMessages: ", newMessages);
+ // console.log("chatroom: ", chatroom);
+
+ // if (!Array.isArray(newMessages)) return;
+ // try {
+ // const newMessage = newMessages[0];
+ // if (newMessage.audio != undefined) {
+ // attachments.push(newMessage.audio)
+ // }
+ // if (newMessage.video != undefined) {
+ // attachments.push(newMessage.video)
+ // }
+ // if (chatroom != undefined) {
+ // const newMessageRequest: messageRequest = {
+ // chatroomId: chatroom.chatroomId,
+ // attachments: attachments,
+ // content: newMessage.text,
+ // receiverIds: chatroom.members,
+ // senderId: user.id,
+ // }
+ // console.log("newMessageRequest: ",newMessageRequest);
+ // // @ts-ignore
+ // clientRef.current.publish({
+ // destination: "/app/message",
+ // headers: {Authorization: "Bearer " + finalToken},
+ // body: JSON.stringify(newMessageRequest),
+ // })
+ // }
+ // } catch (error) {
+ // console.error("Failed to publish messages", error);
+ // }
}, [finalToken, chatroom]);
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index 1ab28fcff..59dca6914 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -21,7 +21,6 @@ export const getAllChatrooms = async (userId: string) => {
try {
const axiosLocalInstance = getAxiosInstance();
const response = await axiosLocalInstance.get(API_ENDPOINTS.GET_All_CHATROOMS.replace("{userId}", userId));
- console.log("Chatrooms response:", response.data);
return response.data;
} catch (error) {
console.error('Error fetching chatrooms:', error);
From 71f48c7885c9ebf799d0b95f445ba21a2bf8a9a2 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Fri, 28 Mar 2025 21:08:39 -0400
Subject: [PATCH 10/28] GH-365: [feat] Route to chatroom from friend user
profile and fix tabs and navigation
---
ClientApp/app/(tabs)/_layout.tsx | 8 ++---
.../app/{messaging => (tabs)/chats}/[id].tsx | 29 +++++++++++++++++--
ClientApp/app/(tabs)/chats/_layout.tsx | 14 +++++++++
ClientApp/app/_layout.tsx | 1 -
.../components/Profile/ProfileSection.tsx | 7 +++++
ClientApp/components/chat/ChatCard.tsx | 10 ++++++-
6 files changed, 61 insertions(+), 8 deletions(-)
rename ClientApp/app/{messaging => (tabs)/chats}/[id].tsx (89%)
create mode 100644 ClientApp/app/(tabs)/chats/_layout.tsx
diff --git a/ClientApp/app/(tabs)/_layout.tsx b/ClientApp/app/(tabs)/_layout.tsx
index 2650d4e25..677d85576 100644
--- a/ClientApp/app/(tabs)/_layout.tsx
+++ b/ClientApp/app/(tabs)/_layout.tsx
@@ -56,7 +56,7 @@ export default function TabLayout() {
}}
/>
(
@@ -64,9 +64,9 @@ export default function TabLayout() {
),
}}
listeners={{
- tabPress: (e) => {
- console.log('Chats tab pressed');
- },
+ // tabPress: (e) => {
+ // console.log('Chats tab pressed');
+ // },
}}
/>
{
- const { id } = useLocalSearchParams();
+ const { id, title } = useLocalSearchParams();
const router = useRouter();
const [messages, setMessages] = useState([]);
const [finalToken, setFinalToken] = useState("");
@@ -18,7 +18,32 @@ const ChatScreen = () => {
const [connected, setConnected] = useState(false);
const clientRef = useRef(null);
const [chatroom, setChatroom] = useState();
+ const navigation = useNavigation();
+ useEffect(() => {
+ // Hide tab bar when this screen is focused
+ const parent = navigation.getParent();
+ parent?.setOptions({
+ tabBarStyle: {
+ display: 'none'
+ }
+ });
+
+
+ // Restore it when unmounted
+ return () => {
+ parent?.setOptions({
+ tabBarStyle: undefined
+ });
+ };
+ }, []);
+
+ useEffect(() => {
+ if (title && typeof title === 'string') {
+ navigation.setOptions({ headerTitle: title });
+ }
+ }, [title]);
+
useEffect(() => {
const response = async () => {
try {
diff --git a/ClientApp/app/(tabs)/chats/_layout.tsx b/ClientApp/app/(tabs)/chats/_layout.tsx
new file mode 100644
index 000000000..55e97015c
--- /dev/null
+++ b/ClientApp/app/(tabs)/chats/_layout.tsx
@@ -0,0 +1,14 @@
+import { Stack } from 'expo-router';
+
+export default function ChatsLayout() {
+ return (
+
+
+
+
+ );
+}
diff --git a/ClientApp/app/_layout.tsx b/ClientApp/app/_layout.tsx
index 6ad88feb7..deb806971 100644
--- a/ClientApp/app/_layout.tsx
+++ b/ClientApp/app/_layout.tsx
@@ -36,7 +36,6 @@ export default function RootLayout() {
-
diff --git a/ClientApp/components/Profile/ProfileSection.tsx b/ClientApp/components/Profile/ProfileSection.tsx
index 8de563b1f..712e2869d 100644
--- a/ClientApp/components/Profile/ProfileSection.tsx
+++ b/ClientApp/components/Profile/ProfileSection.tsx
@@ -59,6 +59,13 @@ const ProfileSection: React.FC = ({
console.log("Message button pressed");
try{
const response = await createUserChatroom(loggedInUser.id, visitedId, user.username, [], false, false);
+ router.push({
+ pathname: '/(tabs)/chats/[id]',
+ params: {
+ id: response.chatroomId,
+ title: user.username },
+ });
+
}catch(e){
console.log("Error in handlePress", e);
}
diff --git a/ClientApp/components/chat/ChatCard.tsx b/ClientApp/components/chat/ChatCard.tsx
index db4bd697d..a00f06993 100644
--- a/ClientApp/components/chat/ChatCard.tsx
+++ b/ClientApp/components/chat/ChatCard.tsx
@@ -26,7 +26,15 @@ const ChatCard: React.FC = ({
return (
router.push(`/messaging/${chatId}`)}
+ onPress={() =>
+ router.push({
+ pathname: '/(tabs)/chats/[id]',
+ params: {
+ id: chatId,
+ title: cardTitle,
+ },
+ })
+ }
onLongPress={onLongPress}
>
From 367255594c14f92ef6ab006d1bcda04ca3f25771 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Sat, 29 Mar 2025 02:06:43 -0400
Subject: [PATCH 11/28] GH-365: [feat] Starter code for creating a group chat -
changes to createUserChatroom are now caushing 400 (but over postamn too)
---
ClientApp/app/(tabs)/_layout.tsx | 1 +
ClientApp/app/(tabs)/chats/index.tsx | 8 +-
ClientApp/app/(tabs)/profile/index.tsx | 1 +
ClientApp/components/chat/ChatListHeader.tsx | 83 +++++++++++
ClientApp/components/chat/NewChatModal.tsx | 138 +++++++++++++++++++
ClientApp/services/chatService.ts | 21 ++-
6 files changed, 246 insertions(+), 6 deletions(-)
create mode 100644 ClientApp/components/chat/ChatListHeader.tsx
create mode 100644 ClientApp/components/chat/NewChatModal.tsx
diff --git a/ClientApp/app/(tabs)/_layout.tsx b/ClientApp/app/(tabs)/_layout.tsx
index 677d85576..5dc34a4c3 100644
--- a/ClientApp/app/(tabs)/_layout.tsx
+++ b/ClientApp/app/(tabs)/_layout.tsx
@@ -59,6 +59,7 @@ export default function TabLayout() {
name="chats"
options={{
title: t('tab_layout.chats'),
+ headerShown: false,
tabBarIcon: ({ color }) => (
),
diff --git a/ClientApp/app/(tabs)/chats/index.tsx b/ClientApp/app/(tabs)/chats/index.tsx
index 9f5b50cf9..172118eb3 100644
--- a/ClientApp/app/(tabs)/chats/index.tsx
+++ b/ClientApp/app/(tabs)/chats/index.tsx
@@ -1,8 +1,14 @@
import React from 'react';
import ChatList from '@/components/chat/ChatList';
+import ChatListHeader from '@/components/chat/ChatListHeader';
+import { SafeAreaView } from 'react-native-safe-area-context';
const Chats =() => {
+
return (
-
+
+
+
+
)
}
diff --git a/ClientApp/app/(tabs)/profile/index.tsx b/ClientApp/app/(tabs)/profile/index.tsx
index c7509cf3f..f044e216e 100644
--- a/ClientApp/app/(tabs)/profile/index.tsx
+++ b/ClientApp/app/(tabs)/profile/index.tsx
@@ -116,6 +116,7 @@ const ProfilePage: React.FC = () => {
{
+
+ const [modalVisible, setModalVisible] = useState(false);
+ const [friends, setFriends] = useState([]);
+ const [selectedFriends, setSelectedFriends] = useState([]);
+
+ const user = useSelector((state: { user: any }) => state.user);
+
+ const fetchFriends = async () => {
+ setLoading(true);
+ try {
+ const friendList = await getFriendsOfUser(user.id);
+ const friendsWithProfiles = await Promise.all(
+ friendList.map(async (friend: any) => {
+ const profile = await getUserProfile(friend.friendUserId);
+ return { ...friend, profile };
+ })
+ );
+ setFriends(friendsWithProfiles);
+ } catch (error) {
+ console.error("Failed to fetch friends:", error);
+ setFriends([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleNewChatPress = () => {
+ setModalVisible(true);
+ fetchFriends();
+ };
+ const handleCreateGroup = async() => {
+ // Call your API or navigate to group chat screen
+
+ };
+
+
+ return (
+
+
+
+
+
+ setModalVisible(false)}
+ onCreateGroup={handleCreateGroup}
+ />
+
+ );
+};
+
+const styles = StyleSheet.create({
+ headerContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 16,
+ backgroundColor: ThemeColors.background.lightGrey,
+ borderBottomWidth: 1,
+ borderBottomColor: '#ddd',
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#333',
+ },
+ spacer: {
+ flex: 1,
+ },
+});
+
+export default ChatListHeader;
diff --git a/ClientApp/components/chat/NewChatModal.tsx b/ClientApp/components/chat/NewChatModal.tsx
new file mode 100644
index 000000000..8226db5ed
--- /dev/null
+++ b/ClientApp/components/chat/NewChatModal.tsx
@@ -0,0 +1,138 @@
+import React, { useState } from 'react';
+import {Modal,View,Text,FlatList,TouchableOpacity,StyleSheet,Pressable,} from 'react-native';
+import FontAwesome from '@expo/vector-icons/FontAwesome';
+
+const sampleFriends = [
+ { id: '1', name: 'User1' },
+ { id: '2', name: 'User2' },
+ { id: '3', name: 'User3' },
+ { id: '4', name: 'User4' },
+ ];
+
+interface NewChatModalProps {
+ visible: boolean;
+ onClose: () => void;
+ onCreateGroup: (selected: typeof sampleFriends) => void;
+}
+
+const NewChatModal: React.FC = ({
+ visible,
+ onClose,
+ onCreateGroup,
+}) => {
+ const [selected, setSelected] = useState([]);
+
+ const toggleSelect = (id: string) => {
+ setSelected((prev) =>
+ prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
+ );
+ };
+
+ const handleCreate = () => {
+ const selectedFriends = sampleFriends.filter((f) =>
+ selected.includes(f.id)
+ );
+ onCreateGroup(selectedFriends);
+ onClose();
+ setSelected([]);
+ };
+
+ return (
+
+
+
+ Select Friends
+ item.id}
+ renderItem={({ item }) => {
+ const isSelected = selected.includes(item.id);
+ return (
+ toggleSelect(item.id)}
+ >
+ {item.name}
+ {isSelected && (
+
+ )}
+
+ );
+ }}
+ />
+
+ Start Group Chat
+
+
+
+ Cancel
+
+
+
+
+ );
+};
+
+export default NewChatModal;
+
+const styles = StyleSheet.create({
+ overlay: {
+ flex: 1,
+ backgroundColor: 'rgba(0,0,0,0.4)',
+ justifyContent: 'center',
+ paddingHorizontal: 20,
+ },
+ modalContent: {
+ backgroundColor: 'white',
+ borderRadius: 16,
+ padding: 20,
+ maxHeight: '80%',
+ },
+ title: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ marginBottom: 12,
+ },
+ friendItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ padding: 12,
+ borderRadius: 10,
+ borderWidth: 1,
+ borderColor: '#ddd',
+ marginBottom: 10,
+ backgroundColor: '#f9f9f9',
+ },
+ selectedFriend: {
+ backgroundColor: '#e0f0ff',
+ borderColor: '#007AFF',
+ },
+ friendName: {
+ fontSize: 16,
+ },
+ createBtn: {
+ backgroundColor: '#007AFF',
+ padding: 12,
+ borderRadius: 10,
+ alignItems: 'center',
+ marginTop: 10,
+ },
+ createBtnText: {
+ color: 'white',
+ fontWeight: 'bold',
+ },
+ closeBtn: {
+ alignItems: 'center',
+ marginTop: 10,
+ },
+ closeBtnText: {
+ color: '#666',
+ },
+});
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index 59dca6914..96804086a 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -43,17 +43,28 @@ export const getMessages = async (chatroomId: string) => {
};
// API_ENDPOINTS.CREATE_CHATROOM
-export const createUserChatroom = async (createrId: string, chatWithUserId: string, nameOfChatRoom: string, messages: string[], isEvent: boolean, unread: boolean) => {
+export const createUserChatroom = async (createrId: string, chatWithUserId: string, nameOfChatRoom: string, messages: string[], isEvent: boolean, unread: boolean, creatorUsername: string, participantUsername: string) => {
try {
const axiosLocalInstance = getAxiosInstance();
const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM,
{
createdBy: createrId,
chatroomName: nameOfChatRoom,
- members : [createrId, chatWithUserId],
- messages,
- isEvent,
- unread
+ members : [
+ {
+ userId: createrId,
+ username: creatorUsername,
+ userImage: null
+ },
+ {
+ userId: chatWithUserId,
+ username: participantUsername,
+ userImage: null
+ }
+ ],
+ messages: messages,
+ isEvent: isEvent,
+ unread: unread
}
);
console.log("createUserChatroom response:", response.data);
From a358aa3c7425df852cf8c376f44d913b6da92440 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Sat, 29 Mar 2025 11:01:13 -0400
Subject: [PATCH 12/28] GH-365: [feat] Customize the list of friends inside the
modal to match the api call
---
ClientApp/components/chat/ChatListHeader.tsx | 5 +-
ClientApp/components/chat/NewChatModal.tsx | 105 ++++++++++++++-----
ClientApp/services/chatService.ts | 57 +++++++---
3 files changed, 129 insertions(+), 38 deletions(-)
diff --git a/ClientApp/components/chat/ChatListHeader.tsx b/ClientApp/components/chat/ChatListHeader.tsx
index 2862da7db..3c202ca11 100644
--- a/ClientApp/components/chat/ChatListHeader.tsx
+++ b/ClientApp/components/chat/ChatListHeader.tsx
@@ -40,7 +40,7 @@ const ChatListHeader: React.FC = () => {
setModalVisible(true);
fetchFriends();
};
- const handleCreateGroup = async() => {
+ const handleCreateGroupButton = async() => {
// Call your API or navigate to group chat screen
};
@@ -53,9 +53,10 @@ const ChatListHeader: React.FC = () => {
setModalVisible(false)}
- onCreateGroup={handleCreateGroup}
+ onCreateGroup={handleCreateGroupButton}
/>
);
diff --git a/ClientApp/components/chat/NewChatModal.tsx b/ClientApp/components/chat/NewChatModal.tsx
index 8226db5ed..6983ea7d8 100644
--- a/ClientApp/components/chat/NewChatModal.tsx
+++ b/ClientApp/components/chat/NewChatModal.tsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
-import {Modal,View,Text,FlatList,TouchableOpacity,StyleSheet,Pressable,} from 'react-native';
+import {Modal,View,Text,FlatList,TouchableOpacity,StyleSheet,Pressable,Image} from 'react-native';
import FontAwesome from '@expo/vector-icons/FontAwesome';
+import { hs, mhs, mvs, vs } from '@/utils/helpers/uiScaler';
const sampleFriends = [
{ id: '1', name: 'User1' },
@@ -10,32 +11,44 @@ const sampleFriends = [
];
interface NewChatModalProps {
+ friends: any [];
visible: boolean;
onClose: () => void;
onCreateGroup: (selected: typeof sampleFriends) => void;
}
const NewChatModal: React.FC = ({
+ friends,
visible,
onClose,
onCreateGroup,
}) => {
- const [selected, setSelected] = useState([]);
+ const [selected, setSelected] = useState<{ userId: string; username: string; userImage: string }[]>([]);
+ console.log("Selected friends:", selected);
+ const toggleSelect = (item: any) => {
+ const exists = selected.find((u) => u.userId === item.friendUserId);
+
+ if (exists) {
+ setSelected((prev) => prev.filter((u) => u.userId !== item.friendUserId));
+ } else {
+ const newUser = {
+ userId: item.friendUserId,
+ username: item.friendUsername,
+ userImage: item.profile.profileImage || '', // use fallback if needed
+ };
+ setSelected((prev) => [...prev, newUser]);
+ }
+ };
- const toggleSelect = (id: string) => {
- setSelected((prev) =>
- prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
- );
- };
-
- const handleCreate = () => {
- const selectedFriends = sampleFriends.filter((f) =>
- selected.includes(f.id)
- );
- onCreateGroup(selectedFriends);
- onClose();
- setSelected([]);
- };
+ const handleCreate = () => {
+ const selectedFriends = friends.filter((f) =>
+ selected.includes(f.id)
+ );
+ console.log("Selected friends:", selectedFriends);
+ onCreateGroup(selectedFriends);
+ onClose();
+ setSelected([]);
+ };
return (
@@ -43,19 +56,27 @@ const NewChatModal: React.FC = ({
Select Friends
item.id}
+ data={friends}
+ keyExtractor={(item) => item.friendUserId} // fix: use actual unique friend ID
renderItem={({ item }) => {
- const isSelected = selected.includes(item.id);
- return (
+ const isSelected = selected.some((u) => u.userId === item.friendUserId);
+ return (
toggleSelect(item.id)}
- >
- {item.name}
+ onPress={() => toggleSelect(item)} >
+
+
+
+ {item.friendUsername }
+
+
{isSelected && (
)}
@@ -110,13 +131,19 @@ const styles = StyleSheet.create({
marginBottom: 10,
backgroundColor: '#f9f9f9',
},
+ pictureSection: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "flex-start",
+ },
selectedFriend: {
backgroundColor: '#e0f0ff',
borderColor: '#007AFF',
},
friendName: {
fontSize: 16,
- },
+ color: '#333',
+},
createBtn: {
backgroundColor: '#007AFF',
padding: 12,
@@ -135,4 +162,34 @@ const styles = StyleSheet.create({
closeBtnText: {
color: '#666',
},
+ participantAvatar: {
+ width: mhs(45),
+ height: mvs(45),
+ borderRadius: mhs(30),
+},
+infoSection: {
+ display: "flex",
+ flex: 3,
+ flexDirection: "column",
+ justifyContent: "center",
+ alignItems: "flex-start",
+ marginLeft: vs(16),
+ },
+ userInfo: {
+ fontSize: 14,
+ fontWeight: "bold",
+ marginBottom: hs(5),
+ color: "#333",
+ },
+ friendInfo: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 12,
+ },
+ avatar: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ marginRight: 12,
+ }
});
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index 96804086a..4782a83c4 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -50,18 +50,7 @@ export const createUserChatroom = async (createrId: string, chatWithUserId: stri
{
createdBy: createrId,
chatroomName: nameOfChatRoom,
- members : [
- {
- userId: createrId,
- username: creatorUsername,
- userImage: null
- },
- {
- userId: chatWithUserId,
- username: participantUsername,
- userImage: null
- }
- ],
+ members : [ createrId, chatWithUserId],
messages: messages,
isEvent: isEvent,
unread: unread
@@ -75,6 +64,50 @@ export const createUserChatroom = async (createrId: string, chatWithUserId: stri
}
}
+export const createUserChatroomV2 = async (
+ creatorId: string,
+ participantIds: string[],
+ nameOfChatRoom: string,
+ messages: string[],
+ isEvent: boolean,
+ unread: boolean,
+ usernames: string[],
+ userImages: string[],
+ creatorUsername: string,
+ creatorImage: string
+) => {
+ try {
+ const axiosLocalInstance = getAxiosInstance();
+
+ const members = [
+ {
+ userId: creatorId,
+ username: creatorUsername,
+ userImage: creatorImage,
+ },
+ ...participantIds.map((id, index) => ({
+ userId: id,
+ username: usernames[index],
+ userImage: userImages[index],
+ })),
+ ];
+
+ const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM, {
+ createdBy: creatorId,
+ chatroomName: nameOfChatRoom,
+ members: members,
+ messages: messages,
+ isEvent: isEvent,
+ unread: unread,
+ });
+
+ console.log("createUserChatroom response:", response.data);
+ return response.data;
+ } catch (error) {
+ console.error('Error creating chatroom:', error);
+ throw error;
+ }
+};
// API_ENDPOINTS.DELETE_CHATROOM
From 859ad1ec9095d03d29ecb921c1b0dabb2670c1b9 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Sun, 30 Mar 2025 19:56:13 -0400
Subject: [PATCH 13/28] GH-365: [feat] starter code for group/user chat
creation
---
ClientApp/app/(tabs)/chats/[id].tsx | 137 ++++++++++++++++--
.../components/Profile/ProfileSection.tsx | 5 +-
ClientApp/components/chat/ChatList.tsx | 7 +-
ClientApp/components/chat/ChatListHeader.tsx | 5 -
ClientApp/components/chat/NewChatModal.tsx | 36 +++--
ClientApp/services/chatService.ts | 83 +++++++----
6 files changed, 206 insertions(+), 67 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index cf2e5ec61..2d7ef8bfc 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -1,12 +1,13 @@
import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
import React, {useCallback, useEffect, useRef, useState} from 'react';
-import { StyleSheet } from 'react-native';
+import { Button, Modal, StyleSheet, View, Text } from 'react-native';
import { GiftedChat, IMessage } from 'react-native-gifted-chat';
import {useSelector} from "react-redux";
import {getMessages, getChatroom} from "@/services/chatService";
import { Client } from "@stomp/stompjs";
import { getAccessToken } from "@/services/tokenService"
import {message, chatroomProps, messageRequest} from "@/types/messaging";
+import { mvs } from '@/utils/helpers/uiScaler';
const ChatScreen = () => {
@@ -17,9 +18,15 @@ const ChatScreen = () => {
const user = useSelector((state: {user: any}) => state.user);
const [connected, setConnected] = useState(false);
const clientRef = useRef(null);
+
const [chatroom, setChatroom] = useState();
+ // TODO This will be a duplicate of the chatroom state
+ const [chatroomInformation, setChatroomInformation] = useState();
+
const navigation = useNavigation();
+ const [infoVisible, setInfoVisible] = useState(false);
+ // use effect to control the bottom tab bar visibility of the part "chat"
useEffect(() => {
// Hide tab bar when this screen is focused
const parent = navigation.getParent();
@@ -29,18 +36,27 @@ const ChatScreen = () => {
}
});
-
// Restore it when unmounted
return () => {
parent?.setOptions({
- tabBarStyle: undefined
+ tabBarStyle: {
+ paddingHorizontal: 10,
+ height: mvs(65),
+ },
});
};
}, []);
+
+
useEffect(() => {
if (title && typeof title === 'string') {
- navigation.setOptions({ headerTitle: title });
+ navigation.setOptions({
+ headerTitle: title,
+ headerRight: () => (
+
);
diff --git a/ClientApp/components/chat/NewChatModal.tsx b/ClientApp/components/chat/NewChatModal.tsx
index 6983ea7d8..18b3504f9 100644
--- a/ClientApp/components/chat/NewChatModal.tsx
+++ b/ClientApp/components/chat/NewChatModal.tsx
@@ -2,29 +2,22 @@ import React, { useState } from 'react';
import {Modal,View,Text,FlatList,TouchableOpacity,StyleSheet,Pressable,Image} from 'react-native';
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { hs, mhs, mvs, vs } from '@/utils/helpers/uiScaler';
-
-const sampleFriends = [
- { id: '1', name: 'User1' },
- { id: '2', name: 'User2' },
- { id: '3', name: 'User3' },
- { id: '4', name: 'User4' },
- ];
+import { createUserChatroom } from '@/services/chatService';
+import { useSelector } from 'react-redux';
interface NewChatModalProps {
friends: any [];
visible: boolean;
onClose: () => void;
- onCreateGroup: (selected: typeof sampleFriends) => void;
}
const NewChatModal: React.FC = ({
friends,
visible,
onClose,
- onCreateGroup,
}) => {
const [selected, setSelected] = useState<{ userId: string; username: string; userImage: string }[]>([]);
- console.log("Selected friends:", selected);
+ const LoggedUser = useSelector((state: { user: any }) => state.user);
const toggleSelect = (item: any) => {
const exists = selected.find((u) => u.userId === item.friendUserId);
@@ -40,12 +33,22 @@ const NewChatModal: React.FC = ({
}
};
+ const onCreateGroup = (selectedFriends: any[]) => {
+
+ console.log("Creating group with friends from onCreateGroup:", selectedFriends);
+
+ //TODO Joud check if the selected Friends are a group and ask for a group name
+ try {
+ createUserChatroom(LoggedUser.id, LoggedUser.username, '', "name", selectedFriends, [], false, false)
+ }catch (error) {
+ console.error("Error creating group:", error);
+ }
+
+ //TODO Joud route to the channel just created
+ };
+
const handleCreate = () => {
- const selectedFriends = friends.filter((f) =>
- selected.includes(f.id)
- );
- console.log("Selected friends:", selectedFriends);
- onCreateGroup(selectedFriends);
+ onCreateGroup(selected);
onClose();
setSelected([]);
};
@@ -89,7 +92,8 @@ const NewChatModal: React.FC = ({
onPress={handleCreate}
disabled={selected.length === 0}
>
- Start Group Chat
+ {/*TODO JOUD udpate the langague and check for name if the selected ones are more than 2 */}
+ Start Chat
diff --git a/ClientApp/services/chatService.ts b/ClientApp/services/chatService.ts
index 4782a83c4..505348ea5 100644
--- a/ClientApp/services/chatService.ts
+++ b/ClientApp/services/chatService.ts
@@ -43,38 +43,46 @@ export const getMessages = async (chatroomId: string) => {
};
// API_ENDPOINTS.CREATE_CHATROOM
-export const createUserChatroom = async (createrId: string, chatWithUserId: string, nameOfChatRoom: string, messages: string[], isEvent: boolean, unread: boolean, creatorUsername: string, participantUsername: string) => {
- try {
- const axiosLocalInstance = getAxiosInstance();
- const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM,
- {
- createdBy: createrId,
- chatroomName: nameOfChatRoom,
- members : [ createrId, chatWithUserId],
- messages: messages,
- isEvent: isEvent,
- unread: unread
- }
- );
- console.log("createUserChatroom response:", response.data);
- return response.data;
- } catch (error) {
- console.error('Error creating chatroom:', error);
- throw error;
- }
-}
+// export const createUserChatroom = async (createrId: string, chatWithUserId: string, nameOfChatRoom: string, messages: string[], isEvent: boolean, unread: boolean, creatorUsername: string, participantUsername: string) => {
+// try {
+// const axiosLocalInstance = getAxiosInstance();
+// const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM,
+// {
+// createdBy: createrId,
+// chatroomName: nameOfChatRoom,
+// members : [
+// {
+// userId: createrId,
+// username: creatorUsername,
+// userImage: "",
+// },
+// {
+// userId: chatWithUserId,
+// username: participantUsername,
+// userImage: "",
+// },],
+// messages: messages,
+// isEvent: isEvent,
+// unread: unread
+// }
+// );
+// console.log("createUserChatroom response:", response.data);
+// return response.data;
+// } catch (error) {
+// console.error('Error creating chatroom:', error);
+// throw error;
+// }
+// }
-export const createUserChatroomV2 = async (
+export const createUserChatroom = async (
creatorId: string,
- participantIds: string[],
+ creatorUsername: string,
+ creatorImage: string,
nameOfChatRoom: string,
+ participants: any [],
messages: string[],
isEvent: boolean,
- unread: boolean,
- usernames: string[],
- userImages: string[],
- creatorUsername: string,
- creatorImage: string
+ unread: boolean,
) => {
try {
const axiosLocalInstance = getAxiosInstance();
@@ -85,13 +93,24 @@ export const createUserChatroomV2 = async (
username: creatorUsername,
userImage: creatorImage,
},
- ...participantIds.map((id, index) => ({
- userId: id,
- username: usernames[index],
- userImage: userImages[index],
- })),
+ ...participants.map((participant) => ({
+ userId: participant.userId,
+ username: participant.username,
+ userImage: "",
+ }))
];
+ console.log("members: ", members);
+ console.log(
+ {
+ createdBy: creatorId,
+ chatroomName: nameOfChatRoom,
+ members: members,
+ messages: messages,
+ isEvent: isEvent,
+ unread: unread,
+ }
+ )
const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM, {
createdBy: creatorId,
chatroomName: nameOfChatRoom,
From 0b2679d63cd9c84019c8ba2a3d212a74d33c3d0a Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Mon, 31 Mar 2025 00:46:03 -0400
Subject: [PATCH 14/28] GH-365: [feat] leave/delete chatroom implemented, final
touch ups
---
ClientApp/app/(tabs)/chats/[id].tsx | 20 ++++++-
ClientApp/components/chat/ChatCard.tsx | 20 ++++++-
ClientApp/components/chat/NewChatModal.tsx | 68 +++++++++++++++++-----
ClientApp/services/chatService.ts | 47 +++++----------
ClientApp/utils/api/endpoints.tsx | 1 +
5 files changed, 108 insertions(+), 48 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index 2d7ef8bfc..7a66d0b83 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -3,11 +3,12 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
import { Button, Modal, StyleSheet, View, Text } from 'react-native';
import { GiftedChat, IMessage } from 'react-native-gifted-chat';
import {useSelector} from "react-redux";
-import {getMessages, getChatroom} from "@/services/chatService";
+import {getMessages, getChatroom, deleteChatroom, leaveChatroom} from "@/services/chatService";
import { Client } from "@stomp/stompjs";
import { getAccessToken } from "@/services/tokenService"
import {message, chatroomProps, messageRequest} from "@/types/messaging";
import { mvs } from '@/utils/helpers/uiScaler';
+import { set } from 'date-fns';
const ChatScreen = () => {
@@ -93,9 +94,24 @@ const ChatScreen = () => {
const handleDeleteChat = async () => {
console.log("Delete chatroom");
+ try {
+ deleteChatroom(id.toString());
+ setInfoVisible(false);
+ router.back();
+ }catch (error) {
+ console.error("Error deleting chatroom: ", error);
+ }
}
+
const handleLeaveChat = async () => {
console.log("Leave chatroom");
+ try {
+ leaveChatroom(id.toString(), user.id);
+ setInfoVisible(false);
+ router.back();
+ }catch (error) {
+ console.error("Error deleting chatroom: ", error);
+ }
}
// useEffect(() => {
// if (!finalToken) return;
@@ -242,7 +258,7 @@ const ChatScreen = () => {
handleDeleteChat()}
+ onPress={handleDeleteChat}
/>
) : (
= ({
}) => {
const router = useRouter();
+
+ const formatChatTimestamp = (isoTimestamp: string): string => {
+ const date = parseISO(isoTimestamp);
+
+ if (isToday(date)) {
+ return format(date, 'hh:mm a'); // e.g. 04:26 AM
+ }
+
+ const daysDiff = differenceInCalendarDays(new Date(), date);
+
+ if (daysDiff < 7) {
+ return format(date, 'EEEE'); // e.g. Monday
+ }
+
+ return format(date, 'dd/MM/yyyy'); // e.g. 25/03/2025
+ };
+
return (
= ({
{cardTitle}
- {messageTime}
+ {formatChatTimestamp(messageTime)}
{messageText}
diff --git a/ClientApp/components/chat/NewChatModal.tsx b/ClientApp/components/chat/NewChatModal.tsx
index 18b3504f9..607f343f9 100644
--- a/ClientApp/components/chat/NewChatModal.tsx
+++ b/ClientApp/components/chat/NewChatModal.tsx
@@ -1,9 +1,10 @@
import React, { useState } from 'react';
-import {Modal,View,Text,FlatList,TouchableOpacity,StyleSheet,Pressable,Image} from 'react-native';
+import {Modal,View,Text,FlatList,TouchableOpacity,StyleSheet,Pressable,Image, TextInput} from 'react-native';
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { hs, mhs, mvs, vs } from '@/utils/helpers/uiScaler';
import { createUserChatroom } from '@/services/chatService';
import { useSelector } from 'react-redux';
+import { router } from 'expo-router';
interface NewChatModalProps {
friends: any [];
@@ -18,6 +19,9 @@ const NewChatModal: React.FC = ({
}) => {
const [selected, setSelected] = useState<{ userId: string; username: string; userImage: string }[]>([]);
const LoggedUser = useSelector((state: { user: any }) => state.user);
+ const [chatTitle, setChatTitle] = useState('');
+ const user = useSelector((state: { user: any }) => state.user);
+
const toggleSelect = (item: any) => {
const exists = selected.find((u) => u.userId === item.friendUserId);
@@ -33,24 +37,41 @@ const NewChatModal: React.FC = ({
}
};
- const onCreateGroup = (selectedFriends: any[]) => {
-
+ const onCreateGroup = async (selectedFriends: any[], title: string) => {
+ let chatroomId = '';
console.log("Creating group with friends from onCreateGroup:", selectedFriends);
-
- //TODO Joud check if the selected Friends are a group and ask for a group name
try {
- createUserChatroom(LoggedUser.id, LoggedUser.username, '', "name", selectedFriends, [], false, false)
- }catch (error) {
- console.error("Error creating group:", error);
- }
-
- //TODO Joud route to the channel just created
+ const response = await createUserChatroom(LoggedUser.id, LoggedUser.username, '', title, selectedFriends, [], false, false)
+ chatroomId = response.chatroomId;
+
+ if (!chatroomId) {
+ console.error("No chatroomId returned from server.");
+ return;
+ }
+
+ router.push({
+ pathname: '/(tabs)/chats/[id]',
+ params: {
+ id: chatroomId,
+ title: title || user.username },
+ });
+ }catch (error) {
+ console.error("Error creating group:", error);
+ }
};
const handleCreate = () => {
- onCreateGroup(selected);
- onClose();
- setSelected([]);
+ if (selected.length > 1 && chatTitle.trim() === '') {
+ alert('Please enter a group name.');
+ return;
+ }
+
+ const titleToUse = selected.length > 1 ? chatTitle.trim() : 'Direct Chat';
+
+ onCreateGroup(selected, titleToUse);
+ setChatTitle('');
+ setSelected([]);
+ onClose();
};
return (
@@ -87,6 +108,25 @@ const NewChatModal: React.FC = ({
);
}}
/>
+ {selected.length > 1 && (
+
+ Group Name
+
+
+
+
+ )}
{
}
};
-// API_ENDPOINTS.CREATE_CHATROOM
-// export const createUserChatroom = async (createrId: string, chatWithUserId: string, nameOfChatRoom: string, messages: string[], isEvent: boolean, unread: boolean, creatorUsername: string, participantUsername: string) => {
-// try {
-// const axiosLocalInstance = getAxiosInstance();
-// const response = await axiosLocalInstance.post(API_ENDPOINTS.CREATE_CHATROOM,
-// {
-// createdBy: createrId,
-// chatroomName: nameOfChatRoom,
-// members : [
-// {
-// userId: createrId,
-// username: creatorUsername,
-// userImage: "",
-// },
-// {
-// userId: chatWithUserId,
-// username: participantUsername,
-// userImage: "",
-// },],
-// messages: messages,
-// isEvent: isEvent,
-// unread: unread
-// }
-// );
-// console.log("createUserChatroom response:", response.data);
-// return response.data;
-// } catch (error) {
-// console.error('Error creating chatroom:', error);
-// throw error;
-// }
-// }
export const createUserChatroom = async (
creatorId: string,
@@ -133,6 +102,7 @@ export const createUserChatroom = async (
export const deleteChatroom = async (chatroomId: string) => {
try {
const axiosLocalInstance = getAxiosInstance();
+ console.log("deleteChatroom: ", chatroomId);
const response = await axiosLocalInstance.delete(API_ENDPOINTS.DELETE_CHATROOM.replace("{chatroomId}", chatroomId));
return response.data;
} catch (error) {
@@ -140,3 +110,18 @@ export const deleteChatroom = async (chatroomId: string) => {
throw error;
}
};
+
+export const leaveChatroom = async (chatroomId: string, userId: string) => {
+ try {
+ const axiosLocalInstance = getAxiosInstance();
+ const response = await axiosLocalInstance.post(
+ API_ENDPOINTS.LEAVE_CHATROOM
+ .replace("{chatroomId}", chatroomId)
+ .replace("{userId}", userId)
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting chatroom:', error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/ClientApp/utils/api/endpoints.tsx b/ClientApp/utils/api/endpoints.tsx
index aa64fa02e..02caebe49 100644
--- a/ClientApp/utils/api/endpoints.tsx
+++ b/ClientApp/utils/api/endpoints.tsx
@@ -45,4 +45,5 @@ export const API_ENDPOINTS = {
GET_CHATROOM: 'messaging-service/messaging/chatroom/{chatroomId}',
CREATE_CHATROOM: 'messaging-service/messaging/chatroom',
DELETE_CHATROOM: 'messaging-service/messaging/chatroom/{chatroomId}',
+ LEAVE_CHATROOM: 'messaging-service/messaging//chatroom/leave-chatroom/{chatroomId}/{userId}',
};
From 99bf6ee63f42d142e78dc147610d39d35dab6644 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Mon, 31 Mar 2025 00:56:46 -0400
Subject: [PATCH 15/28] GH-365: [test] Test commit for git configuration
---
ClientApp/app/(tabs)/chats/[id].tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index 7a66d0b83..4ff907567 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -20,6 +20,7 @@ const ChatScreen = () => {
const [connected, setConnected] = useState(false);
const clientRef = useRef(null);
+
const [chatroom, setChatroom] = useState();
// TODO This will be a duplicate of the chatroom state
const [chatroomInformation, setChatroomInformation] = useState();
From bdbec3e0898117522e7494c8528c7f9a4e66f35c Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Mon, 31 Mar 2025 15:33:18 -0400
Subject: [PATCH 16/28] GH-365: [feat] Add the french language to chat
---
ClientApp/app/(tabs)/chats/[id].tsx | 9 +++--
ClientApp/components/chat/ChatCard.tsx | 5 ++-
ClientApp/components/chat/ChatList.tsx | 1 +
ClientApp/components/chat/ChatListHeader.tsx | 1 -
ClientApp/components/chat/NewChatModal.tsx | 40 ++++++++++++--------
ClientApp/utils/localization/en.json | 19 ++++++++++
ClientApp/utils/localization/fr.json | 19 ++++++++++
7 files changed, 73 insertions(+), 21 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index 4ff907567..bcc11a48c 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -9,6 +9,7 @@ import { getAccessToken } from "@/services/tokenService"
import {message, chatroomProps, messageRequest} from "@/types/messaging";
import { mvs } from '@/utils/helpers/uiScaler';
import { set } from 'date-fns';
+import { useTranslation } from 'react-i18next';
const ChatScreen = () => {
@@ -28,6 +29,8 @@ const ChatScreen = () => {
const navigation = useNavigation();
const [infoVisible, setInfoVisible] = useState(false);
+ const { t } = useTranslation();
+
// use effect to control the bottom tab bar visibility of the part "chat"
useEffect(() => {
// Hide tab bar when this screen is focused
@@ -257,19 +260,19 @@ const ChatScreen = () => {
{/*delete or leave group*/}
{chatroomInformation?.createdBy === user.id ? (
) : (
handleLeaveChat()}
/>
)}
- setInfoVisible(false)} />
+ setInfoVisible(false)} />
diff --git a/ClientApp/components/chat/ChatCard.tsx b/ClientApp/components/chat/ChatCard.tsx
index d4d7b2cce..0256127b9 100644
--- a/ClientApp/components/chat/ChatCard.tsx
+++ b/ClientApp/components/chat/ChatCard.tsx
@@ -3,6 +3,7 @@ import { useRouter } from 'expo-router';
import React from 'react';
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
import { format, isToday, isThisWeek, parseISO, differenceInCalendarDays } from 'date-fns';
+import { useTranslation } from 'react-i18next';
interface CardProps {
chatId: string;
@@ -23,6 +24,7 @@ const ChatCard: React.FC = ({
onLongPress
}) => {
const router = useRouter();
+ const { t } = useTranslation();
const formatChatTimestamp = (isoTimestamp: string): string => {
@@ -35,7 +37,8 @@ const ChatCard: React.FC = ({
const daysDiff = differenceInCalendarDays(new Date(), date);
if (daysDiff < 7) {
- return format(date, 'EEEE'); // e.g. Monday
+ const weekday = format(date, 'EEEE'); // e.g. "Monday"
+ return t(`days.${weekday}`);
}
return format(date, 'dd/MM/yyyy'); // e.g. 25/03/2025
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index c56c6c8cb..687593ccb 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -43,6 +43,7 @@ const Chats = () => {
);
};
+ //TODO Joud find a balance between automatic updates and performance
useFocusEffect(
useCallback(() => {
const fetchChatrooms = async (user: any) => {
diff --git a/ClientApp/components/chat/ChatListHeader.tsx b/ClientApp/components/chat/ChatListHeader.tsx
index 00d10c8f6..1924a0e90 100644
--- a/ClientApp/components/chat/ChatListHeader.tsx
+++ b/ClientApp/components/chat/ChatListHeader.tsx
@@ -13,7 +13,6 @@ const ChatListHeader: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
const [friends, setFriends] = useState([]);
- const [selectedFriends, setSelectedFriends] = useState([]);
const user = useSelector((state: { user: any }) => state.user);
diff --git a/ClientApp/components/chat/NewChatModal.tsx b/ClientApp/components/chat/NewChatModal.tsx
index 607f343f9..fb1a57d17 100644
--- a/ClientApp/components/chat/NewChatModal.tsx
+++ b/ClientApp/components/chat/NewChatModal.tsx
@@ -5,6 +5,9 @@ import { hs, mhs, mvs, vs } from '@/utils/helpers/uiScaler';
import { createUserChatroom } from '@/services/chatService';
import { useSelector } from 'react-redux';
import { router } from 'expo-router';
+import themeColors from '@/utils/constants/colors';
+import { useTranslation } from 'react-i18next';
+import { enUS, fr, ar } from 'date-fns/locale';
interface NewChatModalProps {
friends: any [];
@@ -21,6 +24,7 @@ const NewChatModal: React.FC = ({
const LoggedUser = useSelector((state: { user: any }) => state.user);
const [chatTitle, setChatTitle] = useState('');
const user = useSelector((state: { user: any }) => state.user);
+ const { t } = useTranslation();
const toggleSelect = (item: any) => {
const exists = selected.find((u) => u.userId === item.friendUserId);
@@ -67,7 +71,8 @@ const NewChatModal: React.FC = ({
}
const titleToUse = selected.length > 1 ? chatTitle.trim() : 'Direct Chat';
-
+
+ //TODO when creating this, it's taking the "Direct Chat" name only on the first routing
onCreateGroup(selected, titleToUse);
setChatTitle('');
setSelected([]);
@@ -78,10 +83,10 @@ const NewChatModal: React.FC = ({
- Select Friends
+ {t('chat.select_Friends')}
item.friendUserId} // fix: use actual unique friend ID
+ keyExtractor={(item) => item.friendUserId}
renderItem={({ item }) => {
const isSelected = selected.some((u) => u.userId === item.friendUserId);
return (
@@ -101,16 +106,13 @@ const NewChatModal: React.FC = ({
{item.friendUsername }
- {isSelected && (
-
- )}
);
}}
/>
{selected.length > 1 && (
- Group Name
+ {t('chat.Group_name')}
= ({
paddingVertical: 6,
}}>
= ({
disabled={selected.length === 0}
>
{/*TODO JOUD udpate the langague and check for name if the selected ones are more than 2 */}
- Start Chat
+ {t('chat.start_chat')}
- Cancel
+ {t('chat.cancel')}
@@ -181,19 +183,25 @@ const styles = StyleSheet.create({
alignItems: "flex-start",
},
selectedFriend: {
- backgroundColor: '#e0f0ff',
- borderColor: '#007AFF',
+ backgroundColor: '#dff2de',
+ borderColor: '#00b803',
},
friendName: {
fontSize: 16,
color: '#333',
},
createBtn: {
- backgroundColor: '#007AFF',
- padding: 12,
- borderRadius: 10,
+ backgroundColor: themeColors.button.primaryBackground,
+ height: vs(50),
+ borderRadius: mhs(25),
alignItems: 'center',
- marginTop: 10,
+ justifyContent: "center",
+ marginBottom: vs(16),
+ shadowColor: '#475569',
+ shadowOffset: { width: 0, height: vs(2) },
+ shadowOpacity: 0.25,
+ shadowRadius: hs(4),
+ minHeight: 40
},
createBtnText: {
color: 'white',
diff --git a/ClientApp/utils/localization/en.json b/ClientApp/utils/localization/en.json
index 52e49cbdf..266e75e08 100644
--- a/ClientApp/utils/localization/en.json
+++ b/ClientApp/utils/localization/en.json
@@ -467,5 +467,24 @@
"failed_to_load_user_data": "Failed to load user data.",
"unfriend": "Unfriend",
"message": "Message"
+ },
+ "chat" : {
+ "select_Friends" : "Select Friends",
+ "start_chat" : "Start Chat",
+ "cancel": "Cancel",
+ "Group_name" : "Group name",
+ "enter_group_name_placeholder" : "Enter group name",
+ "delete_chat" : "Delete Chat",
+ "leave_chat": "Leave Chat",
+ "close": "Close"
+ },
+ "days": {
+ "Monday": "Monday",
+ "Tuesday": "Tuesday",
+ "Wednesday": "Wednesday",
+ "Thursday": "Thursday",
+ "Friday": "Friday",
+ "Saturday": "Saturday",
+ "Sunday": "Sunday"
}
}
diff --git a/ClientApp/utils/localization/fr.json b/ClientApp/utils/localization/fr.json
index 50afb4ca9..a7fb03a31 100644
--- a/ClientApp/utils/localization/fr.json
+++ b/ClientApp/utils/localization/fr.json
@@ -406,6 +406,25 @@
"select_cutoff_time": "Sélectionnez l'heure limite d'inscription",
"enter_event_description": "Entrez la description de l'événement",
"failed_to_load_details": "Échec du chargement des détails de l'événement"
+ },
+ "chat" : {
+ "select_Friends" : "Sélectionner des amis",
+ "start_chat" : "Démarrer la discussion",
+ "cancel": "Annuler",
+ "Group_name" : "Nom du groupe",
+ "enter_group_name_placeholder" : "Entrez le nom du groupe",
+ "delete_chat" : "Supprimer la discussion",
+ "leave_chat": "Quitter la discussion",
+ "close": "Fermer"
+ },
+ "days": {
+ "Monday": "Lundi",
+ "Tuesday": "Mardi",
+ "Wednesday": "Mercredi",
+ "Thursday": "Jeudi",
+ "Friday": "Vendredi",
+ "Saturday": "Samedi",
+ "Sunday": "Dimanche"
},
"event_list": {
"loading_events": "Chargement des événements ...",
From 90a4a6920052a8bf4d6e96e1af7fd7cebb0342a7 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Mon, 31 Mar 2025 21:01:54 -0400
Subject: [PATCH 17/28] GH-365: [feat] Push for Nico
---
ClientApp/app/(tabs)/chats/[id].tsx | 74 +++++++++++++++++++++++-
ClientApp/package.json | 2 +
ClientApp/services/axiosLocalInstance.ts | 2 +-
3 files changed, 75 insertions(+), 3 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index bcc11a48c..75d0bfe68 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -1,7 +1,9 @@
import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import { Button, Modal, StyleSheet, View, Text } from 'react-native';
-import { GiftedChat, IMessage } from 'react-native-gifted-chat';
+import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
+import { faPaperPlane } from '@fortawesome/free-solid-svg-icons';
+import { Composer, GiftedChat, IMessage, InputToolbar, InputToolbarProps, Send, SendProps } from 'react-native-gifted-chat';
import {useSelector} from "react-redux";
import {getMessages, getChatroom, deleteChatroom, leaveChatroom} from "@/services/chatService";
import { Client } from "@stomp/stompjs";
@@ -10,6 +12,8 @@ import {message, chatroomProps, messageRequest} from "@/types/messaging";
import { mvs } from '@/utils/helpers/uiScaler';
import { set } from 'date-fns';
import { useTranslation } from 'react-i18next';
+import { IconButton } from 'react-native-paper';
+import themeColors from '@/utils/constants/colors';
const ChatScreen = () => {
@@ -231,12 +235,71 @@ const ChatScreen = () => {
}, [finalToken, chatroom]);
+ // const renderSend = (props: any) => (
+ //
+ //
+ //
+ //
+ //
+ // );
+
+ const renderInputToolbar = ( props: InputToolbarProps ) => (
+
+ );
+
+ const renderComposer = (props: any) => (
+
+ );
+
+ const renderSend = (props:any) => (
+
+
+
+ );
+
return (
<>
onSend(messages)}
user={{ _id: user.id }}
+ alwaysShowSend
+ renderInputToolbar={renderInputToolbar}
+ renderComposer={renderComposer}
+ renderSend={renderSend}
/>
@@ -332,5 +395,12 @@ const styles = StyleSheet.create({
color: '#333',
justifyContent: 'center',
alignItems: 'center',
- },
+ },
+ sendButton: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingHorizontal: 10,
+ paddingVertical: 8,
+ marginRight: 4,
+ },
});
diff --git a/ClientApp/package.json b/ClientApp/package.json
index 05e9f937f..845442fab 100644
--- a/ClientApp/package.json
+++ b/ClientApp/package.json
@@ -52,6 +52,8 @@
"dependencies": {
"@expo/config-plugins": "~9.0.0",
"@expo/vector-icons": "^14.0.3",
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
+ "@fortawesome/react-native-fontawesome": "^0.3.2",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-community/datetimepicker": "8.2.0",
"@react-native-community/template": "^0.76.6",
diff --git a/ClientApp/services/axiosLocalInstance.ts b/ClientApp/services/axiosLocalInstance.ts
index 096b58060..f0924a39e 100644
--- a/ClientApp/services/axiosLocalInstance.ts
+++ b/ClientApp/services/axiosLocalInstance.ts
@@ -23,7 +23,7 @@ export const setupLocalAxiosInstance = (dispatch: any): AxiosInstance => {
return (global as any).axiosLocalInstance;
}
const config: AxiosRequestConfig = {
- baseURL: 'http://localhost:8080/api/', // Fallback for baseURL
+ baseURL: 'https://api-dev.sportahub.com/', // Fallback for baseURL
timeout: 5000,
headers: {
'Content-Type': 'application/json',
From 596dc646984ab78a442c7c4a7e70a88b6855a123 Mon Sep 17 00:00:00 2001
From: DanDuguay
Date: Mon, 31 Mar 2025 22:19:37 -0400
Subject: [PATCH 18/28] GH-358: [feature] updated interfaces to implement new
Member object
---
ClientApp/app/(tabs)/chats/[id].tsx | 292 ++++++++++++++--------------
ClientApp/types/messaging.ts | 14 +-
2 files changed, 156 insertions(+), 150 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index 75d0bfe68..fc015dddc 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -25,7 +25,7 @@ const ChatScreen = () => {
const [connected, setConnected] = useState(false);
const clientRef = useRef(null);
-
+
const [chatroom, setChatroom] = useState();
// TODO This will be a duplicate of the chatroom state
const [chatroomInformation, setChatroomInformation] = useState();
@@ -39,19 +39,19 @@ const ChatScreen = () => {
useEffect(() => {
// Hide tab bar when this screen is focused
const parent = navigation.getParent();
- parent?.setOptions({
- tabBarStyle: {
- display: 'none'
- }
+ parent?.setOptions({
+ tabBarStyle: {
+ display: 'none'
+ }
});
-
+
// Restore it when unmounted
return () => {
- parent?.setOptions({
+ parent?.setOptions({
tabBarStyle: {
paddingHorizontal: 10,
height: mvs(65),
- },
+ },
});
};
}, []);
@@ -60,15 +60,15 @@ const ChatScreen = () => {
useEffect(() => {
if (title && typeof title === 'string') {
- navigation.setOptions({
- headerTitle: title,
+ navigation.setOptions({
+ headerTitle: title,
headerRight: () => (
setInfoVisible(true)} title="Info" />
),
});
}
}, [title]);
-
+
useEffect(() => {
const response = async () => {
try {
@@ -121,127 +121,127 @@ const ChatScreen = () => {
console.error("Error deleting chatroom: ", error);
}
}
- // useEffect(() => {
- // if (!finalToken) return;
-
- // const client = new Client({
- // brokerURL: "wss://api.sportahub.app/api/messaging-service/ws",
- // heartbeatIncoming: 0,
- // heartbeatOutgoing: 0,
- // connectHeaders: {
- // Authorization: "Bearer " + finalToken,
- // },
-
- // onWebSocketError: (error: any) => console.error("Websocket error:", error),
- // onConnect: () => {
- // setConnected(true);
-
- // client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
- // console.log(JSON.parse(message.body));
- // setMessages((prev: IMessage[]) => [
- // ...prev,
- // {
- // _id: JSON.parse(message.body).messageId,
- // text: JSON.parse(message.body).content,
- // createdAt: new Date(JSON.parse(message.body).createdAt),
- // user: {
- // _id: JSON.parse(message.body).senderId,
- // name: 'Sender Name', // Replace with actual sender name if available
- // avatar: 'https://example.com/sender-avatar.png', // Replace with actual avatar URL if available
- // },
- // },
- // ]);
- // },
- // {
- // "Authorization": "Bearer " + finalToken,
- // });
- // },
- // onDisconnect: () => setConnected(false),
- // debug: (msg: any) => console.log(msg),
- // onStompError: (frame: any) => console.error("Stomp error:", frame),
-
- // });
-
- // client.activate();
- // clientRef.current = client;
-
- // const fetchChatroom = async () => {
- // try {
- // const messagesData = await getMessages(id.toString());
- // console.log("messageData: ", messagesData);
-
- // // Ensure messagesData is mapped to IMessage structure
- // const formattedMessages = messagesData.map((message: any) => ({
- // _id: message.messageId,
- // text: message.content,
- // createdAt: new Date(message.createdAt),
- // user: {
- // _id: message.senderId,
- // name: message.senderName || "Unknown", // Replace with sender's name if available
- // avatar: message.senderAvatar || "https://example.com/default-avatar.png", // Replace with avatar URL if available
- // },
- // }));
-
- // const chatroomData = await getChatroom(id.toString());
- // console.log("chatroomData: ", chatroomData);
-
- // setChatroom(chatroomData);
- // setMessages(formattedMessages);
- // } catch (error) {
- // console.error("Failed to fetch messages", error);
- // throw error;
- // }
- // };
- // fetchChatroom();
-
- // return () => {
- // client.deactivate();
- // }
- // }, [finalToken]);
+ useEffect(() => {
+ if (!finalToken) return;
+
+ const client = new Client({
+ brokerURL: "ws://localhost:8080/api/messaging-service/ws",
+ heartbeatIncoming: 0,
+ heartbeatOutgoing: 0,
+ connectHeaders: {
+ Authorization: "Bearer " + finalToken,
+ },
+
+ onWebSocketError: (error: any) => console.error("Websocket error:", error),
+ onConnect: () => {
+ setConnected(true);
+
+ client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
+ console.log(JSON.parse(message.body));
+ setMessages((prev: IMessage[]) => [
+ ...prev,
+ {
+ _id: JSON.parse(message.body).messageId,
+ text: JSON.parse(message.body).content,
+ createdAt: new Date(JSON.parse(message.body).createdAt),
+ user: {
+ _id: JSON.parse(message.body).senderId,
+ name: 'Sender Name', // Replace with actual sender name if available
+ avatar: 'https://example.com/sender-avatar.png', // Replace with actual avatar URL if available
+ },
+ },
+ ]);
+ },
+ {
+ "Authorization": "Bearer " + finalToken,
+ });
+ },
+ onDisconnect: () => setConnected(false),
+ debug: (msg: any) => console.log(msg),
+ onStompError: (frame: any) => console.error("Stomp error:", frame),
+
+ });
+
+ client.activate();
+ clientRef.current = client;
+
+ const fetchChatroom = async () => {
+ try {
+ const messagesData = await getMessages(id.toString());
+ console.log("messageData: ", messagesData);
+
+ // Ensure messagesData is mapped to IMessage structure
+ const formattedMessages = messagesData.map((message: any) => ({
+ _id: message.messageId,
+ text: message.content,
+ createdAt: new Date(message.createdAt),
+ user: {
+ _id: message.senderId,
+ name: message.senderName || "Unknown", // Replace with sender's name if available
+ avatar: message.senderAvatar || "https://example.com/default-avatar.png", // Replace with avatar URL if available
+ },
+ }));
+
+ const chatroomData = await getChatroom(id.toString());
+ console.log("chatroomData: ", chatroomData);
+
+ setChatroom(chatroomData);
+ setMessages(formattedMessages);
+ } catch (error) {
+ console.error("Failed to fetch messages", error);
+ throw error;
+ }
+ };
+ fetchChatroom();
+
+ return () => {
+ client.deactivate();
+ }
+ }, [finalToken]);
const onSend = useCallback((newMessages: IMessage[] = []) => {
- // let attachments: string[] = [];
- // console.log("newMessages: ", newMessages);
- // console.log("chatroom: ", chatroom);
-
- // if (!Array.isArray(newMessages)) return;
- // try {
- // const newMessage = newMessages[0];
- // if (newMessage.audio != undefined) {
- // attachments.push(newMessage.audio)
- // }
- // if (newMessage.video != undefined) {
- // attachments.push(newMessage.video)
- // }
- // if (chatroom != undefined) {
- // const newMessageRequest: messageRequest = {
- // chatroomId: chatroom.chatroomId,
- // attachments: attachments,
- // content: newMessage.text,
- // receiverIds: chatroom.members,
- // senderId: user.id,
- // }
- // console.log("newMessageRequest: ",newMessageRequest);
- // // @ts-ignore
- // clientRef.current.publish({
- // destination: "/app/message",
- // headers: {Authorization: "Bearer " + finalToken},
- // body: JSON.stringify(newMessageRequest),
- // })
- // }
- // } catch (error) {
- // console.error("Failed to publish messages", error);
- // }
+ let attachments: string[] = [];
+ console.log("newMessages: ", newMessages);
+ console.log("chatroom: ", chatroom);
+
+ if (!Array.isArray(newMessages)) return;
+ try {
+ const newMessage = newMessages[0];
+ if (newMessage.audio != undefined) {
+ attachments.push(newMessage.audio)
+ }
+ if (newMessage.video != undefined) {
+ attachments.push(newMessage.video)
+ }
+ if (chatroom != undefined) {
+ const newMessageRequest: messageRequest = {
+ chatroomId: chatroom.chatroomId,
+ attachments: attachments,
+ content: newMessage.text,
+ receivers: chatroom.members,
+ senderId: user.id,
+ }
+ console.log("newMessageRequest: ",newMessageRequest);
+ // @ts-ignore
+ clientRef.current.publish({
+ destination: "/app/message",
+ headers: {Authorization: "Bearer " + finalToken},
+ body: JSON.stringify(newMessageRequest),
+ })
+ }
+ } catch (error) {
+ console.error("Failed to publish messages", error);
+ }
}, [finalToken, chatroom]);
- // const renderSend = (props: any) => (
- //
- //
- //
- //
- //
- // );
+ const renderSend = (props: any) => (
+
+
+
+
+
+ );
const renderInputToolbar = ( props: InputToolbarProps ) => (
{
/>
);
- const renderSend = (props:any) => (
-
-
-
- );
-
+ // const renderSend = (props:any) => (
+ //
+ //
+ //
+ // );
+
return (
<>
{
alwaysShowSend
renderInputToolbar={renderInputToolbar}
renderComposer={renderComposer}
- renderSend={renderSend}
+ // renderSend={renderSend}
/>
@@ -319,7 +319,7 @@ const ChatScreen = () => {
)}
-
+
{/*delete or leave group*/}
{chatroomInformation?.createdBy === user.id ? (
Date: Tue, 1 Apr 2025 03:12:57 -0400
Subject: [PATCH 19/28] GH-365: [feat] Work on messages UI
---
ClientApp/app/(tabs)/chats/[id].tsx | 108 ++++++++++++++++++----------
ClientApp/types/messaging.ts | 3 +
2 files changed, 74 insertions(+), 37 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index fc015dddc..4c9322d74 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -2,8 +2,8 @@ import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import { Button, Modal, StyleSheet, View, Text } from 'react-native';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
-import { faPaperPlane } from '@fortawesome/free-solid-svg-icons';
-import { Composer, GiftedChat, IMessage, InputToolbar, InputToolbarProps, Send, SendProps } from 'react-native-gifted-chat';
+import { faChevronDown, faPaperPlane } from '@fortawesome/free-solid-svg-icons';
+import { Bubble, Composer, GiftedChat, IMessage, InputToolbar, InputToolbarProps, Send, SendProps, Time } from 'react-native-gifted-chat';
import {useSelector} from "react-redux";
import {getMessages, getChatroom, deleteChatroom, leaveChatroom} from "@/services/chatService";
import { Client } from "@stomp/stompjs";
@@ -15,7 +15,6 @@ import { useTranslation } from 'react-i18next';
import { IconButton } from 'react-native-paper';
import themeColors from '@/utils/constants/colors';
-
const ChatScreen = () => {
const { id, title } = useLocalSearchParams();
const router = useRouter();
@@ -24,6 +23,7 @@ const ChatScreen = () => {
const user = useSelector((state: {user: any}) => state.user);
const [connected, setConnected] = useState(false);
const clientRef = useRef(null);
+ const avatarPlaceholder = require('@/assets/images/avatar-placeholder.png');
const [chatroom, setChatroom] = useState();
@@ -56,8 +56,6 @@ const ChatScreen = () => {
};
}, []);
-
-
useEffect(() => {
if (title && typeof title === 'string') {
navigation.setOptions({
@@ -121,11 +119,12 @@ const ChatScreen = () => {
console.error("Error deleting chatroom: ", error);
}
}
+
useEffect(() => {
if (!finalToken) return;
const client = new Client({
- brokerURL: "ws://localhost:8080/api/messaging-service/ws",
+ brokerURL: "ws://api-dev.sportahub.app/api/messagin-service/ws",
heartbeatIncoming: 0,
heartbeatOutgoing: 0,
connectHeaders: {
@@ -139,7 +138,6 @@ const ChatScreen = () => {
client.subscribe(`/topic/chatroom/${id}`, (message: any) => {
console.log(JSON.parse(message.body));
setMessages((prev: IMessage[]) => [
- ...prev,
{
_id: JSON.parse(message.body).messageId,
text: JSON.parse(message.body).content,
@@ -147,9 +145,10 @@ const ChatScreen = () => {
user: {
_id: JSON.parse(message.body).senderId,
name: 'Sender Name', // Replace with actual sender name if available
- avatar: 'https://example.com/sender-avatar.png', // Replace with actual avatar URL if available
+ avatar: avatarPlaceholder, // Replace with actual avatar URL if available
},
},
+ ...prev,
]);
},
{
@@ -166,6 +165,7 @@ const ChatScreen = () => {
clientRef.current = client;
const fetchChatroom = async () => {
+
try {
const messagesData = await getMessages(id.toString());
console.log("messageData: ", messagesData);
@@ -178,7 +178,7 @@ const ChatScreen = () => {
user: {
_id: message.senderId,
name: message.senderName || "Unknown", // Replace with sender's name if available
- avatar: message.senderAvatar || "https://example.com/default-avatar.png", // Replace with avatar URL if available
+ avatar: message.senderAvatar || avatarPlaceholder, // Replace with avatar URL if available
},
}));
@@ -220,6 +220,9 @@ const ChatScreen = () => {
content: newMessage.text,
receivers: chatroom.members,
senderId: user.id,
+ // TODO joud use when supported
+ // senderName: user.username,
+ // senderImage: user.avatar,
}
console.log("newMessageRequest: ",newMessageRequest);
// @ts-ignore
@@ -235,28 +238,52 @@ const ChatScreen = () => {
}, [finalToken, chatroom]);
- const renderSend = (props: any) => (
-
-
-
-
-
- );
const renderInputToolbar = ( props: InputToolbarProps ) => (
);
+ const renderBubble = (props: any) => (
+
+ );
+
+ const renderTime = (props: any) => (
+
+ );
+
const renderComposer = (props: any) => (
{
/>
);
- // const renderSend = (props:any) => (
- //
- //
- //
- // );
+ const renderSend = (props:any) => (
+
+
+
+ );
return (
<>
@@ -299,8 +326,15 @@ const ChatScreen = () => {
alwaysShowSend
renderInputToolbar={renderInputToolbar}
renderComposer={renderComposer}
- // renderSend={renderSend}
- />
+ renderSend={renderSend}
+ isScrollToBottomEnabled={true}
+ scrollToBottomComponent={() => (
+
+ )}
+ renderBubble={renderBubble}
+ renderTime={renderTime}
+ renderUsernameOnMessage={true}
+ />
diff --git a/ClientApp/types/messaging.ts b/ClientApp/types/messaging.ts
index b1ed76678..5d01ef892 100644
--- a/ClientApp/types/messaging.ts
+++ b/ClientApp/types/messaging.ts
@@ -7,6 +7,7 @@ export interface message {
content: string; //required
createdAt: Number | Date;
attachments: string[];
+ // avatar: string
}
export interface chatroomProps {
@@ -25,6 +26,8 @@ export interface messageRequest {
receivers: member[];
content: string;
attachments: string[];
+ // senderName: string;
+ // senderImage: string;
}
export interface member {
From 4b73b0737a91268c7d77451f869f026f83c3fbf3 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Tue, 1 Apr 2025 23:45:24 -0400
Subject: [PATCH 20/28] GH-365: [feat] Emergency commit to save work
---
ClientApp/app/(tabs)/chats/[id].tsx | 4 ++--
ClientApp/components/chat/ChatList.tsx | 9 +++++----
ClientApp/types/messaging.ts | 2 +-
3 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index 4c9322d74..28cdc1c1d 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -124,7 +124,7 @@ const ChatScreen = () => {
if (!finalToken) return;
const client = new Client({
- brokerURL: "ws://api-dev.sportahub.app/api/messagin-service/ws",
+ brokerURL: "ws://api.sportahub.app/api/messaging-service/ws",
heartbeatIncoming: 0,
heartbeatOutgoing: 0,
connectHeaders: {
@@ -221,7 +221,7 @@ const ChatScreen = () => {
receivers: chatroom.members,
senderId: user.id,
// TODO joud use when supported
- // senderName: user.username,
+ senderName: user.username,
// senderImage: user.avatar,
}
console.log("newMessageRequest: ",newMessageRequest);
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index 687593ccb..ca64f4762 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -5,14 +5,15 @@ import {useSelector} from "react-redux";
import {deleteChatroom, getAllChatrooms} from "@/services/chatService";
import {User} from "react-native-gifted-chat";
import { useFocusEffect } from '@react-navigation/native';
+import { message } from '@/types/messaging';
interface CardProps {
chatroomId: string;
createdBy: string;
userImg: any;
createdAt: string;
- content: string;
chatroomName: string
+ messages: message[]
}
const Chats = () => {
@@ -62,7 +63,7 @@ const Chats = () => {
};
}, [user])
);
-
+ console.log("chatrooms", chatrooms);
return (
{
renderItem={({ item }) => (
handleDelete(item.chatroomId)}
diff --git a/ClientApp/types/messaging.ts b/ClientApp/types/messaging.ts
index 5d01ef892..9ef459eeb 100644
--- a/ClientApp/types/messaging.ts
+++ b/ClientApp/types/messaging.ts
@@ -26,7 +26,7 @@ export interface messageRequest {
receivers: member[];
content: string;
attachments: string[];
- // senderName: string;
+ senderName: string;
// senderImage: string;
}
From 6b79b2a2a6de1fa18a159b47581587752fa9eed2 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Wed, 2 Apr 2025 01:48:37 -0400
Subject: [PATCH 21/28] GH-365: [feat] chats work real time and chatlist
updates every 3 seconds, no unread ui updates yet
---
ClientApp/app/(tabs)/chats/[id].tsx | 19 +++++++++++--------
ClientApp/components/chat/ChatList.tsx | 9 +++++++--
ClientApp/components/chat/NewChatModal.tsx | 3 ++-
ClientApp/types/messaging.ts | 2 +-
4 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/ClientApp/app/(tabs)/chats/[id].tsx b/ClientApp/app/(tabs)/chats/[id].tsx
index 28cdc1c1d..accfd4b3f 100644
--- a/ClientApp/app/(tabs)/chats/[id].tsx
+++ b/ClientApp/app/(tabs)/chats/[id].tsx
@@ -7,7 +7,7 @@ import { Bubble, Composer, GiftedChat, IMessage, InputToolbar, InputToolbarProps
import {useSelector} from "react-redux";
import {getMessages, getChatroom, deleteChatroom, leaveChatroom} from "@/services/chatService";
import { Client } from "@stomp/stompjs";
-import { getAccessToken } from "@/services/tokenService"
+import { getAccessToken, refreshAccessToken, startTokenRefresh } from "@/services/tokenService"
import {message, chatroomProps, messageRequest} from "@/types/messaging";
import { mvs } from '@/utils/helpers/uiScaler';
import { set } from 'date-fns';
@@ -78,7 +78,12 @@ const ChatScreen = () => {
console.error('Failed to get access token:', error);
}
};
+ const interval = setInterval(() => {
+ refreshAccessToken()
+ response();
+ }, 180000);
+ refreshAccessToken();
response();
}, []);
@@ -144,8 +149,8 @@ const ChatScreen = () => {
createdAt: new Date(JSON.parse(message.body).createdAt),
user: {
_id: JSON.parse(message.body).senderId,
- name: 'Sender Name', // Replace with actual sender name if available
- avatar: avatarPlaceholder, // Replace with actual avatar URL if available
+ name: JSON.parse(message.body).sendName, // Replace with actual sender name if available
+ avatar: JSON.parse(message.body).senderAvatar, // Replace with actual avatar URL if available
},
},
...prev,
@@ -168,7 +173,6 @@ const ChatScreen = () => {
try {
const messagesData = await getMessages(id.toString());
- console.log("messageData: ", messagesData);
// Ensure messagesData is mapped to IMessage structure
const formattedMessages = messagesData.map((message: any) => ({
@@ -183,7 +187,8 @@ const ChatScreen = () => {
}));
const chatroomData = await getChatroom(id.toString());
- console.log("chatroomData: ", chatroomData);
+
+ // console.log("chatroomData: ", chatroomData);
setChatroom(chatroomData);
setMessages(formattedMessages);
@@ -201,8 +206,6 @@ const ChatScreen = () => {
const onSend = useCallback((newMessages: IMessage[] = []) => {
let attachments: string[] = [];
- console.log("newMessages: ", newMessages);
- console.log("chatroom: ", chatroom);
if (!Array.isArray(newMessages)) return;
try {
@@ -224,7 +227,7 @@ const ChatScreen = () => {
senderName: user.username,
// senderImage: user.avatar,
}
- console.log("newMessageRequest: ",newMessageRequest);
+ // TODO remove log
// @ts-ignore
clientRef.current.publish({
destination: "/app/message",
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index ca64f4762..a6d2b4832 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -56,14 +56,19 @@ const Chats = () => {
throw error;
}
};
- fetchChatrooms(user);
+
+ const intervalId = setInterval(() => {
+ fetchChatrooms(user);
+ }, 3000);
+
+ fetchChatrooms(user); // Initial fetch
return () => {
+ clearInterval(intervalId); // Cleanup interval
setChatrooms([]); // Cleanup if needed
};
}, [user])
);
- console.log("chatrooms", chatrooms);
return (
= ({
let chatroomId = '';
console.log("Creating group with friends from onCreateGroup:", selectedFriends);
try {
- const response = await createUserChatroom(LoggedUser.id, LoggedUser.username, '', title, selectedFriends, [], false, false)
+ // TODO the last "true" is done to help the read unread status
+ const response = await createUserChatroom(LoggedUser.id, LoggedUser.username, '', title, selectedFriends, [], false, true)
chatroomId = response.chatroomId;
if (!chatroomId) {
diff --git a/ClientApp/types/messaging.ts b/ClientApp/types/messaging.ts
index 9ef459eeb..e2788ad83 100644
--- a/ClientApp/types/messaging.ts
+++ b/ClientApp/types/messaging.ts
@@ -7,7 +7,7 @@ export interface message {
content: string; //required
createdAt: Number | Date;
attachments: string[];
- // avatar: string
+ senderImage: string
}
export interface chatroomProps {
From 0e1cd14c7b3f3fb0e870926b641880ddcc8e3f51 Mon Sep 17 00:00:00 2001
From: Joud Babik
Date: Wed, 2 Apr 2025 02:45:36 -0400
Subject: [PATCH 22/28] add package-lock.json
---
ClientApp/.gitignore | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ClientApp/.gitignore b/ClientApp/.gitignore
index b3e4a1294..f5f129f40 100644
--- a/ClientApp/.gitignore
+++ b/ClientApp/.gitignore
@@ -13,7 +13,7 @@ web-build/
# macOS
.DS_Store
-package-lock.json
+# package-lock.json
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli
From 051f97d590c5ced9e8ecadb0eba6dac203084411 Mon Sep 17 00:00:00 2001
From: Walid
Date: Thu, 3 Apr 2025 01:58:33 -0400
Subject: [PATCH 23/28] GH-365: [fix] Update googleServicesFile paths in
app.json for iOS and Android
---
ClientApp/app.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ClientApp/app.json b/ClientApp/app.json
index f3ede3e15..e4f4d2597 100644
--- a/ClientApp/app.json
+++ b/ClientApp/app.json
@@ -15,7 +15,7 @@
},
"ios": {
"supportsTablet": true,
- "googleServicesFile": "process.env.GOOGLE_SERVICE_INFO_PLIST ?? ./GoogleService-Info.plist",
+ "googleServicesFile": "./GoogleService-Info.plist",
"bundleIdentifier": "com.nchelico.ClientApp",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false,
@@ -45,7 +45,7 @@
"android.permission.ACCESS_COARSE_LOCATION",
"android.permission.ACCESS_FINE_LOCATION"
],
- "googleServicesFile": "process.env.GOOGLE_SERVICES_JSON ?? ./google-services.json",
+ "googleServicesFile": "./google-services.json",
"package": "com.sporta.clientApp",
"config" : {
"googleMaps": {
From 2187eb4348fdccfee09ff768ba295c4249cbe890 Mon Sep 17 00:00:00 2001
From: Walid
Date: Thu, 3 Apr 2025 01:58:54 -0400
Subject: [PATCH 24/28] GH-365: [feat] Add new dependencies for FontAwesome and
Expo File System
---
ClientApp/package.json | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/ClientApp/package.json b/ClientApp/package.json
index 845442fab..c329ef7df 100644
--- a/ClientApp/package.json
+++ b/ClientApp/package.json
@@ -52,6 +52,7 @@
"dependencies": {
"@expo/config-plugins": "~9.0.0",
"@expo/vector-icons": "^14.0.3",
+ "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-native-fontawesome": "^0.3.2",
"@react-native-async-storage/async-storage": "1.23.1",
@@ -76,6 +77,7 @@
"expo-constants": "~17.0.8",
"expo-dev-client": "~5.0.15",
"expo-device": "~7.0.2",
+ "expo-file-system": "~18.0.12",
"expo-font": "~13.0.2",
"expo-image-picker": "~16.0.3",
"expo-linking": "~7.0.5",
@@ -110,6 +112,7 @@
"react-native-google-places-autocomplete": "^2.5.7",
"react-native-image-viewing": "^0.2.2",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
+ "react-native-keyboard-controller": "^1.16.8",
"react-native-maps": "1.20.1",
"react-native-mime-types": "^2.5.0",
"react-native-modal-datetime-picker": "^18.0.0",
@@ -129,8 +132,7 @@
"react-stomp": "^5.1.0",
"sockjs-client": "^1.6.1",
"tailwindcss": "^3.4.14",
- "use-debounce": "^10.0.4",
- "expo-file-system": "~18.0.12"
+ "use-debounce": "^10.0.4"
},
"devDependencies": {
"@babel/core": "^7.20.0",
From c795d527a2a1d8fc383859622ebe14398fb4fd00 Mon Sep 17 00:00:00 2001
From: Walid
Date: Thu, 3 Apr 2025 01:59:13 -0400
Subject: [PATCH 25/28] GH-365: [fix] Update Firebase and Google Maps
dependencies in Podfile.lock and project.pbxproj
---
.../ios/ClientApp.xcodeproj/project.pbxproj | 5 +-
ClientApp/ios/Podfile.lock | 49 ++++++-------------
2 files changed, 18 insertions(+), 36 deletions(-)
diff --git a/ClientApp/ios/ClientApp.xcodeproj/project.pbxproj b/ClientApp/ios/ClientApp.xcodeproj/project.pbxproj
index f3314865f..4e2a24ec7 100644
--- a/ClientApp/ios/ClientApp.xcodeproj/project.pbxproj
+++ b/ClientApp/ios/ClientApp.xcodeproj/project.pbxproj
@@ -307,8 +307,7 @@
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle",
- "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.xcframework/ios-arm64/GoogleMaps.framework/Resources/GoogleMaps.bundle",
- "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.xcframework/ios-arm64_x86_64-simulator/GoogleMaps.framework/Resources/GoogleMaps.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
@@ -361,7 +360,7 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
diff --git a/ClientApp/ios/Podfile.lock b/ClientApp/ios/Podfile.lock
index 4bfa4a205..da575906c 100644
--- a/ClientApp/ios/Podfile.lock
+++ b/ClientApp/ios/Podfile.lock
@@ -350,7 +350,7 @@ PODS:
- GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- - FirebaseRemoteConfigInterop (11.10.0)
+ - FirebaseRemoteConfigInterop (11.11.0)
- FirebaseSessions (11.10.0):
- FirebaseCore (~> 11.10.0)
- FirebaseCoreExtension (~> 11.10.0)
@@ -362,32 +362,15 @@ PODS:
- PromisesSwift (~> 2.1)
- fmt (9.1.0)
- glog (0.3.5)
- - Google-Maps-iOS-Utils (4.2.2):
- - Google-Maps-iOS-Utils/Clustering (= 4.2.2)
- - Google-Maps-iOS-Utils/Geometry (= 4.2.2)
- - Google-Maps-iOS-Utils/GeometryUtils (= 4.2.2)
- - Google-Maps-iOS-Utils/Heatmap (= 4.2.2)
- - Google-Maps-iOS-Utils/QuadTree (= 4.2.2)
- - GoogleMaps (~> 7.3)
- - Google-Maps-iOS-Utils/Clustering (4.2.2):
- - Google-Maps-iOS-Utils/QuadTree
- - GoogleMaps (~> 7.3)
- - Google-Maps-iOS-Utils/Geometry (4.2.2):
- - GoogleMaps (~> 7.3)
- - Google-Maps-iOS-Utils/GeometryUtils (4.2.2):
- - GoogleMaps (~> 7.3)
- - Google-Maps-iOS-Utils/Heatmap (4.2.2):
- - Google-Maps-iOS-Utils/QuadTree
- - GoogleMaps (~> 7.3)
- - Google-Maps-iOS-Utils/QuadTree (4.2.2):
- - GoogleMaps (~> 7.3)
+ - Google-Maps-iOS-Utils (5.0.0):
+ - GoogleMaps (~> 8.0)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- - GoogleMaps (7.4.0):
- - GoogleMaps/Maps (= 7.4.0)
- - GoogleMaps/Base (7.4.0)
- - GoogleMaps/Maps (7.4.0):
+ - GoogleMaps (8.4.0):
+ - GoogleMaps/Maps (= 8.4.0)
+ - GoogleMaps/Base (8.4.0)
+ - GoogleMaps/Maps (8.4.0):
- GoogleMaps/Base
- GoogleUtilities/AppDelegateSwizzler (8.0.2):
- GoogleUtilities/Environment
@@ -1664,11 +1647,11 @@ PODS:
- React-Core
- react-native-get-random-values (1.11.0):
- React-Core
- - react-native-google-maps (1.18.0):
- - Google-Maps-iOS-Utils (= 4.2.2)
- - GoogleMaps (= 7.4.0)
+ - react-native-google-maps (1.20.1):
+ - Google-Maps-iOS-Utils (= 5.0.0)
+ - GoogleMaps (= 8.4.0)
- React-Core
- - react-native-maps (1.18.0):
+ - react-native-maps (1.20.1):
- React-Core
- react-native-pager-view (6.5.1):
- DoubleConversion
@@ -2544,13 +2527,13 @@ SPEC CHECKSUMS:
FirebaseCrashlytics: 84b073c997235740e6a951b7ee49608932877e5c
FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3
FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9
- FirebaseRemoteConfigInterop: 7c9a9c65eff32cbb0f7bf8d18140612ad57dfcc6
+ FirebaseRemoteConfigInterop: 85bdce8babed7814816496bb6f082bc05b0a45e1
FirebaseSessions: 9b3b30947b97a15370e0902ee7a90f50ef60ead6
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
- Google-Maps-iOS-Utils: f77eab4c4326d7e6a277f8e23a0232402731913a
+ Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
- GoogleMaps: 032f676450ba0779bd8ce16840690915f84e57ac
+ GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
hermes-engine: 1949ca944b195a8bde7cbf6316b9068e19cf53c6
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
@@ -2588,8 +2571,8 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: b8ec254319f3ab74325caddc8f0c1483bcef59ae
react-native-date-picker: 4cb5304937dc370817a84f120271c565964a763a
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
- react-native-google-maps: a3a01acdc5e15d1b74d600ba270c454bac6239b4
- react-native-maps: 3b0c8378ff774053bdd8954c820e987fdd7b4d72
+ react-native-google-maps: c4f5c5b2dda17e7cb2cb2b37a81f140b039b3e7e
+ react-native-maps: 9febd31278b35cd21e4fad2cf6fa708993be5dab
react-native-pager-view: 37b5ff5daefc40c2e756457b0d0b2483a8ed3b94
react-native-safe-area-context: 8b8404e70b0cbf2a56428a17017c14c1dcc16448
React-nativeconfig: 014403841159c621de6d2f888ad829aac530f93e
From 1fd979a18d06fc7056b4bc12ebf65dec347efd7a Mon Sep 17 00:00:00 2001
From: Walid
Date: Thu, 10 Apr 2025 12:24:46 -0400
Subject: [PATCH 26/28] GH-365: [fix] Clean up formatting and update Google API
keys in app.json
---
ClientApp/app.json | 29 ++++++++++++++++-------------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/ClientApp/app.json b/ClientApp/app.json
index e4f4d2597..ecaeaff8e 100644
--- a/ClientApp/app.json
+++ b/ClientApp/app.json
@@ -1,4 +1,3 @@
-
{
"expo": {
"name": "ClientApp",
@@ -17,16 +16,18 @@
"supportsTablet": true,
"googleServicesFile": "./GoogleService-Info.plist",
"bundleIdentifier": "com.nchelico.ClientApp",
- "infoPlist": {
- "ITSAppUsesNonExemptEncryption": false,
- "NSCameraUsageDescription": "This app needs access to your camera to take photos.",
- "NSMicrophoneUsageDescription": "This app needs access to your microphone for video recording.",
- "NSPhotoLibraryUsageDescription": "This app needs access to your photo library to upload images.",
- "NSPhotoLibraryAddUsageDescription": "This app needs permission to save photos to your gallery.",
- "LSApplicationQueriesSchemes": [
- "myapp"
- ],
- "UIBackgroundModes": ["remote-notification"]
+ "infoPlist": {
+ "ITSAppUsesNonExemptEncryption": false,
+ "NSCameraUsageDescription": "This app needs access to your camera to take photos.",
+ "NSMicrophoneUsageDescription": "This app needs access to your microphone for video recording.",
+ "NSPhotoLibraryUsageDescription": "This app needs access to your photo library to upload images.",
+ "NSPhotoLibraryAddUsageDescription": "This app needs permission to save photos to your gallery.",
+ "LSApplicationQueriesSchemes": [
+ "myapp"
+ ],
+ "UIBackgroundModes": [
+ "remote-notification"
+ ]
},
"config": {
"googleMapsApiKey": "process.env.GOOGLE_MAPS_API_KEY"
@@ -47,7 +48,7 @@
],
"googleServicesFile": "./google-services.json",
"package": "com.sporta.clientApp",
- "config" : {
+ "config": {
"googleMaps": {
"apiKey": "process.env.GOOGLE_MAPS_API_KEY"
}
@@ -90,6 +91,8 @@
"typedRoutes": true
},
"extra": {
+ "apiBaseUrl": "https://api.sportahub.app/api/",
+ "googlePlacesApiKey": "process.env.GOOGLE_MAPS_API_KEY",
"router": {
"origin": false
},
@@ -98,7 +101,7 @@
}
},
"owner": "sportaapp",
- "runtimeVersion": "1.0.0",
+ "runtimeVersion": "1.0.0",
"updates": {
"url": "https://u.expo.dev/0a493fac-f7b6-4eee-a154-9cc7280a3dad"
}
From 0e100f55211f62ad400d84f8bd0f50437fbd5aae Mon Sep 17 00:00:00 2001
From: Walid
Date: Thu, 10 Apr 2025 12:30:19 -0400
Subject: [PATCH 27/28] GH-365: [refactor] Improve code formatting and
structure in ProfileSection.tsx
---
.../components/Profile/ProfileSection.tsx | 97 +++++++++++++------
1 file changed, 67 insertions(+), 30 deletions(-)
diff --git a/ClientApp/components/Profile/ProfileSection.tsx b/ClientApp/components/Profile/ProfileSection.tsx
index 7a21c3f9d..1fba78f48 100644
--- a/ClientApp/components/Profile/ProfileSection.tsx
+++ b/ClientApp/components/Profile/ProfileSection.tsx
@@ -35,7 +35,7 @@ const ProfileSection: React.FC = ({
}) => {
const router = useRouter();
const [loading, setLoading] = useState(false);
- const loggedInUser = useSelector((state: { user: any }) => state.user);
+ const loggedInUser = useSelector((state: { user: any }) => state.user);
const { t } = useTranslation();
if (loading) {
@@ -59,16 +59,16 @@ const ProfileSection: React.FC = ({
console.log("Message button pressed");
try{
const participants = [{ userId: visitedId, username: user.username, userImage: ''}];
- const response = await createUserChatroom(loggedInUser.id , loggedInUser.username, '','',participants, [],false, false);
+ const response = await createUserChatroom(loggedInUser.id , loggedInUser.username, '','',participants, [],false, false);
- // const response = await createUserChatroom(loggedInUser.id, visitedId, user.username, [], false, false, loggedInUser.username, user.username);
+ // const response = await createUserChatroom(loggedInUser.id, visitedId, user.username, [], false, false, loggedInUser.username, user.username);
router.push({
pathname: '/(tabs)/chats/[id]',
- params: {
- id: response.chatroomId,
+ params: {
+ id: response.chatroomId,
title: user.username },
});
-
+
}catch(e){
console.log("Error in handlePress", e);
}
@@ -76,20 +76,32 @@ const ProfileSection: React.FC = ({
return (
<>
-
+
+
+
= ({
{user?.profile.firstName} {user?.profile.lastName}
- @{user?.username}
+
+ @{user?.username}
+
{!isUserProfile ? (
<>
{friendStatus === "ACCEPTED" ? (
- {t('profile_section.unfriend')}
-
+
+ {t("profile_section.unfriend")}
+
+
) : (
= ({
{friendStatus === "PENDING" ? "Pending" : "Add"}
@@ -153,20 +176,32 @@ const ProfileSection: React.FC = ({
: "account-plus"
}
size={22}
- color={friendStatus === "UNKNOWN" ? "#fff" : "#0C9E04"}
+ color={
+ friendStatus === "UNKNOWN" ? "#fff" : "#0C9E04"
+ }
/>
)}
{/* Message button */}
-
+
- {t('profile_section.message')}
+ {t("profile_section.message")}
= ({
: "chat"
}
size={22}
- color={friendStatus === "UNKNOWN" ? "#0C9E04" : "#fff"}
+ color={
+ friendStatus === "UNKNOWN" ? "#0C9E04" : "#fff"
+ }
/>
>
From 3984c85bc02c55af72247386fb71ffb16bf805e8 Mon Sep 17 00:00:00 2001
From: Walid
Date: Thu, 10 Apr 2025 12:32:09 -0400
Subject: [PATCH 28/28] GH-365: [refactor] Enhance chatroom fetching and
rendering logic in ChatList.tsx
---
ClientApp/components/chat/ChatList.tsx | 144 +++++++++++++------------
1 file changed, 74 insertions(+), 70 deletions(-)
diff --git a/ClientApp/components/chat/ChatList.tsx b/ClientApp/components/chat/ChatList.tsx
index a6d2b4832..04a23db0d 100644
--- a/ClientApp/components/chat/ChatList.tsx
+++ b/ClientApp/components/chat/ChatList.tsx
@@ -1,90 +1,94 @@
-import React, {useCallback, useEffect, useState} from 'react';
+import React, {useCallback, useState} from 'react';
import ChatCard from '@/components/chat/ChatCard';
import {Alert, FlatList} from 'react-native';
import {useSelector} from "react-redux";
import {deleteChatroom, getAllChatrooms} from "@/services/chatService";
-import {User} from "react-native-gifted-chat";
import { useFocusEffect } from '@react-navigation/native';
import { message } from '@/types/messaging';
interface CardProps {
- chatroomId: string;
- createdBy: string;
- userImg: any;
- createdAt: string;
- chatroomName: string
- messages: message[]
+ chatroomId: string;
+ createdBy: string;
+ userImg: any;
+ createdAt: string;
+ chatroomName: string
+ messages: message[]
}
const Chats = () => {
- const [chatrooms, setChatrooms] = useState([]);
- const user = useSelector((state: {user: any}) => state.user);
+ const [chatrooms, setChatrooms] = useState([]);
+ const user = useSelector((state: {user: any}) => state.user);
- const handleDelete = (chatroomId: string) => {
- Alert.alert(
- "Leave Chat",
- "Do you want to leave this chat?",
- [
- { text: "Cancel", style: "cancel" },
- {
- text: "Leave",
- style: "destructive",
- onPress: async () => {
- // TODO JOUD handle selection between delete and leave endpoint calls
- try {
- await deleteChatroom(chatroomId);
- setChatrooms(prev => prev.filter(c => c.chatroomId !== chatroomId));
- } catch (e) {
- console.error("Failed to delete chatroom:", e);
- Alert.alert("Error", "Failed to delete chat. Please try again.");
- }
- }
+ const handleDelete = (chatroomId: string) => {
+ Alert.alert(
+ "Leave Chat",
+ "Do you want to leave this chat?",
+ [
+ { text: "Cancel", style: "cancel" },
+ {
+ text: "Leave",
+ style: "destructive",
+ onPress: async () => {
+ // TODO JOUD handle selection between delete and leave endpoint calls
+ try {
+ await deleteChatroom(chatroomId);
+ setChatrooms(prev => prev.filter(c => c.chatroomId !== chatroomId));
+ } catch (e) {
+ console.error("Failed to delete chatroom:", e);
+ Alert.alert("Error", "Failed to delete chat. Please try again.");
}
- ]
- );
- };
+ }
+ }
+ ]
+ );
+ };
- //TODO Joud find a balance between automatic updates and performance
- useFocusEffect(
- useCallback(() => {
- const fetchChatrooms = async (user: any) => {
- try {
- const chatroomData = await getAllChatrooms(user.id);
- setChatrooms(chatroomData);
- } catch (error) {
- console.error("Failed to fetch chatrooms:", error);
- throw error;
- }
- };
+ //TODO Joud find a balance between automatic updates and performance
+ useFocusEffect(
+ useCallback(() => {
+ const fetchChatrooms = async (user: any) => {
+ try {
+ const chatroomData = await getAllChatrooms(user.id);
+ const sortedChatrooms = chatroomData.sort((a: CardProps, b: CardProps) => {
+ const dateA = new Date(a.messages[0]?.createdAt.toString()).getTime();
+ const dateB = new Date(b.messages[0]?.createdAt.toString()).getTime();
+ return dateB - dateA; // most recent first
+ });
+ setChatrooms(sortedChatrooms);
+ } catch (error) {
+ console.error("Failed to fetch chatrooms:", error);
+ throw error;
+ }
+ };
- const intervalId = setInterval(() => {
- fetchChatrooms(user);
- }, 3000);
+ const intervalId = setInterval(() => {
+ fetchChatrooms(user);
+ }, 3000);
- fetchChatrooms(user); // Initial fetch
+ fetchChatrooms(user); // Initial fetch
- return () => {
- clearInterval(intervalId); // Cleanup interval
- setChatrooms([]); // Cleanup if needed
- };
- }, [user])
- );
- return (
- item.chatroomId}
- renderItem={({ item }) => (
- handleDelete(item.chatroomId)}
- />
- )}
+ return () => {
+ clearInterval(intervalId); // Cleanup interval
+ setChatrooms([]); // Cleanup if needed
+ };
+ }, [user])
+ );
+ return (
+ item.chatroomId}
+ renderItem={({ item }) => (
+ handleDelete(item.chatroomId)}
/>
- )
+ )}
+ />
+ )
}
-export default Chats
\ No newline at end of file
+export default Chats