浏览代码

add new members, all members, fixes

Viktoriia 4 月之前
父节点
当前提交
b833ad429e

+ 384 - 0
src/screens/InAppScreens/MessagesScreen/Components/AddNomadsModal.tsx

@@ -0,0 +1,384 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { View, StyleSheet, TouchableOpacity, Text, Image, ActivityIndicator } from 'react-native';
+import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
+import { useNavigation } from '@react-navigation/native';
+import { chatStyles } from './styles';
+import { Colors } from 'src/theme';
+import { AvatarWithInitials, Input } from 'src/components';
+import { API_HOST } from 'src/constants';
+import { NAVIGATION_PAGES } from 'src/types';
+import { FlashList } from '@shopify/flash-list';
+
+import SearchIcon from 'assets/icons/search.svg';
+import { getFontSize } from 'src/utils';
+import { useGetMyFriendsMutation } from '@api/friends';
+import { usePostAddToGroupMutation, usePostSearchUsers } from '@api/chat';
+import { storage, StoreType } from 'src/storage';
+
+import CheckSvg from 'assets/icons/travels-screens/circle-check.svg';
+import CloseIcon from 'assets/icons/close.svg';
+
+const AddNomadsModal = () => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const [searchQuery, setSearchQuery] = useState('');
+  const [data, setData] = useState<any>(null);
+  const [selectedUsers, setSelectedUsers] = useState<any[]>([]);
+
+  const [friends, setFriends] = useState<any[]>([]);
+  const [page, setPage] = useState(0);
+  const [isLoadingMore, setIsLoadingMore] = useState(false);
+  const [maxPages, setMaxPages] = useState(1);
+  const [isLoading, setIsLoading] = useState(false);
+
+  const { mutateAsync: getMyFriends } = useGetMyFriendsMutation();
+  const { mutate: addToGroup } = usePostAddToGroupMutation();
+  const { data: searchResult, isFetching } = usePostSearchUsers(
+    token,
+    searchQuery,
+    searchQuery.length > 1
+  );
+
+  const handleSheetOpen = (payload: any) => {
+    setData(payload);
+  };
+
+  useEffect(() => {
+    getMyFriends(
+      {
+        token,
+        type: 'friends',
+        page: 0
+      },
+      {
+        onSuccess: (data) => {
+          setFriends(data.friends.users);
+          setMaxPages(data.friends.max_pages);
+        }
+      }
+    );
+  }, []);
+
+  useEffect(() => {
+    const getNextPage = async () => {
+      await getMyFriends(
+        {
+          token,
+          type: 'friends',
+          page
+        },
+        {
+          onSuccess: (data) => {
+            setIsLoadingMore(false);
+            setFriends((prevState) => [...prevState, ...data['friends'].users]);
+          }
+        }
+      );
+    };
+    if (page !== 0) {
+      getNextPage();
+    }
+  }, [page]);
+
+  const handleEndReached = useCallback(() => {
+    if (friends && page < maxPages && !isLoadingMore && maxPages > 1) {
+      setIsLoadingMore(true);
+      setPage((prevPage) => prevPage + 1);
+    }
+  }, [friends, page]);
+
+  const toggleUserSelection = (user: any) => {
+    const isSelected = selectedUsers.some((selected) => selected.user_id === user.user_id);
+    if (isSelected) {
+      setSelectedUsers((prev) => prev.filter((selected) => selected.user_id !== user.user_id));
+    } else {
+      setSelectedUsers((prev) => [...prev, user]);
+    }
+  };
+
+  const handleAddToGroup = async () => {
+    if (!data) return;
+    setIsLoadingMore(true);
+
+    await Promise.all(
+      selectedUsers.map((user) =>
+        addToGroup(
+          {
+            token,
+            uid: user.user_id,
+            group_token: data?.groupToken
+          },
+          {
+            onSuccess: () => {
+              data && data?.refetch();
+              data && data?.refetchMembers();
+            },
+            onError: () => {
+              setIsLoading(false);
+            }
+          }
+        )
+      )
+    );
+
+    setIsLoading(false);
+    SheetManager.hide('add-nomads-modal');
+    setSelectedUsers([]);
+    setSearchQuery('');
+  };
+
+  const renderSelectedUser = ({ item }: { item: any }) => (
+    <View style={styles.selectedUserContainer}>
+      <TouchableOpacity
+        onPress={() =>
+          setSelectedUsers((prev) => prev.filter((selected) => selected.user_id !== item.user_id))
+        }
+        style={styles.removeIcon}
+      >
+        <CloseIcon width={10} height={10} fill={Colors.WHITE} />
+      </TouchableOpacity>
+      {item.avatar ? (
+        <Image source={{ uri: API_HOST + item.avatar }} style={styles.selectedAvatar} />
+      ) : (
+        <AvatarWithInitials
+          text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
+          flag={API_HOST + item.flag1}
+          size={60}
+          fontSize={21}
+          borderColor={Colors.LIGHT_GRAY}
+          borderWidth={1}
+        />
+      )}
+    </View>
+  );
+
+  const renderUserItem = ({ item }: { item: any }) => {
+    const isSelected = selectedUsers.some((selected) => selected.user_id === item.user_id);
+
+    return (
+      <TouchableOpacity style={styles.userItem} onPress={() => toggleUserSelection(item)}>
+        {item.avatar ? (
+          <Image source={{ uri: API_HOST + item.avatar }} style={chatStyles.avatar} />
+        ) : (
+          <AvatarWithInitials
+            text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
+            flag={API_HOST + item?.flag1}
+            size={36}
+            fontSize={12}
+            borderColor={Colors.LIGHT_GRAY}
+            borderWidth={1}
+          />
+        )}
+
+        <View style={chatStyles.textContainer}>
+          <Text style={chatStyles.name}>
+            {item.first_name} {item.last_name}
+          </Text>
+        </View>
+        {/* {item.admin === 1 && (
+          <Text
+            style={{
+              fontSize: getFontSize(10),
+              fontWeight: '600',
+              color: Colors.LIGHT_GRAY
+            }}
+          >
+            Admin
+          </Text>
+        )} */}
+        <View style={styles.unselectedCircle}>
+          {isSelected && <CheckSvg fill={Colors.DARK_BLUE} height={20} width={20} />}
+        </View>
+      </TouchableOpacity>
+    );
+  };
+
+  return (
+    <ActionSheet
+      id="add-nomads-modal"
+      containerStyle={styles.sheetContainer}
+      defaultOverlayOpacity={0.5}
+      closeOnTouchBackdrop={true}
+      keyboardHandlerEnabled={true}
+      onBeforeShow={(sheetRef) => {
+        const payload = sheetRef || null;
+        handleSheetOpen(payload);
+      }}
+    >
+      <View style={styles.container}>
+        <View style={chatStyles.header}>
+          <TouchableOpacity
+            style={{
+              paddingTop: 16,
+              paddingBottom: 6,
+              paddingHorizontal: 6
+            }}
+            onPress={() => {
+              SheetManager.hide('add-nomads-modal');
+              setSelectedUsers([]);
+              setSearchQuery('');
+            }}
+          >
+            <Text style={chatStyles.cancelText}>Cancel</Text>
+          </TouchableOpacity>
+
+          {isLoading ? (
+            <ActivityIndicator size="small" color={Colors.DARK_BLUE} style={{ padding: 10 }} />
+          ) : (
+            <TouchableOpacity
+              onPress={handleAddToGroup}
+              style={{
+                paddingTop: 16,
+                paddingBottom: 6,
+                paddingHorizontal: 6
+              }}
+            >
+              <Text style={chatStyles.headerText}>Save</Text>
+            </TouchableOpacity>
+          )}
+        </View>
+
+        <Input
+          inputMode={'search'}
+          placeholder={'Search nomads'}
+          onChange={(text) => {
+            setSearchQuery(text);
+          }}
+          value={searchQuery}
+          icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+        />
+
+        {selectedUsers.length > 0 && (
+          <View style={styles.usersRow}>
+            <FlashList
+              horizontal
+              data={selectedUsers}
+              renderItem={renderSelectedUser}
+              keyExtractor={(item) => item.user_id.toString()}
+              showsHorizontalScrollIndicator={false}
+              contentContainerStyle={styles.selectedUsersList}
+              estimatedItemSize={50}
+            />
+          </View>
+        )}
+
+        {isFetching ? (
+          <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
+        ) : searchQuery ? (
+          <FlashList
+            viewabilityConfig={{
+              waitForInteraction: true,
+              itemVisiblePercentThreshold: 50,
+              minimumViewTime: 1000
+            }}
+            data={searchResult?.data || []}
+            renderItem={renderUserItem}
+            keyExtractor={(item) => item.user_id.toString()}
+            estimatedItemSize={100}
+            extraData={selectedUsers}
+            showsVerticalScrollIndicator={false}
+            refreshing={isFetching}
+            contentContainerStyle={{ paddingBottom: 16 }}
+          />
+        ) : (
+          <FlashList
+            viewabilityConfig={{
+              waitForInteraction: true,
+              itemVisiblePercentThreshold: 50,
+              minimumViewTime: 1000
+            }}
+            data={friends || []}
+            renderItem={renderUserItem}
+            keyExtractor={(item) => item.user_id.toString()}
+            estimatedItemSize={100}
+            extraData={selectedUsers}
+            showsVerticalScrollIndicator={false}
+            refreshing={isFetching}
+            onEndReached={handleEndReached}
+            onEndReachedThreshold={0.2}
+            contentContainerStyle={{ paddingBottom: 16 }}
+          />
+        )}
+      </View>
+    </ActionSheet>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    backgroundColor: 'white',
+    gap: 16,
+    height: '100%'
+  },
+  sheetContainer: {
+    height: '95%',
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    paddingHorizontal: 16
+  },
+  avatar: {
+    width: 30,
+    height: 30,
+    borderRadius: 15,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  header: {
+    paddingTop: 16,
+    paddingHorizontal: 6,
+    width: 68
+  },
+  userItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    gap: 8,
+    borderRadius: 8,
+    marginBottom: 6
+  },
+  unselectedCircle: {
+    width: 20,
+    height: 20,
+    borderRadius: 10,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY,
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
+  usersRow: {
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8
+  },
+  selectedUsersList: {
+    paddingHorizontal: 12,
+    paddingVertical: 10
+  },
+  selectedUserContainer: {
+    position: 'relative',
+    marginRight: 12
+  },
+  selectedAvatar: {
+    width: 60,
+    height: 60,
+    borderRadius: 30,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  removeIcon: {
+    position: 'absolute',
+    top: -4,
+    right: -4,
+    width: 22,
+    height: 22,
+    borderRadius: 11,
+    borderWidth: 1,
+    borderColor: Colors.WHITE,
+    backgroundColor: Colors.RED,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 1
+  }
+});
+
+export default AddNomadsModal;

+ 171 - 0
src/screens/InAppScreens/MessagesScreen/Components/AllNomadsModal.tsx

@@ -0,0 +1,171 @@
+import React, { useState } from 'react';
+import { View, StyleSheet, TouchableOpacity, Text, Image } from 'react-native';
+import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
+import { useNavigation } from '@react-navigation/native';
+import { chatStyles } from './styles';
+import { Colors } from 'src/theme';
+import { AvatarWithInitials, Input } from 'src/components';
+import { API_HOST } from 'src/constants';
+import { NAVIGATION_PAGES } from 'src/types';
+import { FlashList } from '@shopify/flash-list';
+
+import SearchIcon from 'assets/icons/search.svg';
+import { getFontSize } from 'src/utils';
+
+const AllNomadsModal = () => {
+  const navigation = useNavigation();
+  const [searchQuery, setSearchQuery] = useState('');
+  const [data, setData] = useState<any[]>([]);
+  const [filteredData, setFilteredData] = useState<any[]>([]);
+
+  const handleSheetOpen = (payload: any) => {
+    setData(payload?.members ?? []);
+    setFilteredData(payload?.members ?? []);
+  };
+
+  const handleSearch = (text: string) => {
+    if (text) {
+      const searchData =
+        data?.filter((item: any) => {
+          const itemData = item.name ? item.name.toLowerCase() : ''.toLowerCase();
+          const textData = text.toLowerCase();
+          return itemData.indexOf(textData) > -1;
+        }) ?? [];
+      setFilteredData(searchData);
+      setSearchQuery(text);
+    } else {
+      setFilteredData(data);
+      setSearchQuery(text);
+    }
+  };
+
+  const renderItem = ({ item }: { item: any }) => (
+    <TouchableOpacity
+      style={styles.userItem}
+      onPress={() => {
+        SheetManager.hide('all-nomads-modal').then(() => {
+          navigation.navigate(
+            ...([
+              NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
+              {
+                userId: item.uid
+              }
+            ] as never)
+          );
+        });
+      }}
+    >
+      {item.avatar ? (
+        <Image source={{ uri: API_HOST + item.avatar }} style={chatStyles.avatar} />
+      ) : (
+        <AvatarWithInitials
+          text={item.name?.split(' ')[0][0] + item.name?.split(' ')[1][0]}
+          flag={API_HOST + item?.flag1}
+          size={36}
+          fontSize={12}
+          borderColor={Colors.LIGHT_GRAY}
+          borderWidth={1}
+        />
+      )}
+
+      <View style={chatStyles.textContainer}>
+        <Text style={chatStyles.name}>{item.name}</Text>
+      </View>
+      {item.admin === 1 && (
+        <Text
+          style={{
+            fontSize: getFontSize(10),
+            fontWeight: '600',
+            color: Colors.LIGHT_GRAY
+          }}
+        >
+          Admin
+        </Text>
+      )}
+    </TouchableOpacity>
+  );
+
+  return (
+    <ActionSheet
+      id="all-nomads-modal"
+      containerStyle={styles.sheetContainer}
+      defaultOverlayOpacity={0.5}
+      closeOnTouchBackdrop={true}
+      keyboardHandlerEnabled={true}
+      onBeforeShow={(sheetRef) => {
+        const payload = sheetRef || null;
+        handleSheetOpen(payload);
+      }}
+    >
+      <View style={styles.container}>
+        <TouchableOpacity
+          style={styles.header}
+          onPress={() => SheetManager.hide('all-nomads-modal')}
+        >
+          <Text style={chatStyles.cancelText}>Cancel</Text>
+        </TouchableOpacity>
+
+        <Input
+          inputMode={'search'}
+          placeholder={'Search nomads'}
+          onChange={handleSearch}
+          value={searchQuery}
+          icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
+        />
+
+        <FlashList
+          viewabilityConfig={{
+            waitForInteraction: true,
+            itemVisiblePercentThreshold: 50,
+            minimumViewTime: 1000
+          }}
+          data={filteredData || []}
+          renderItem={renderItem}
+          keyExtractor={(item) => item.uid.toString()}
+          estimatedItemSize={100}
+          extraData={filteredData}
+          showsVerticalScrollIndicator={false}
+          contentContainerStyle={{ paddingBottom: 16 }}
+        />
+      </View>
+    </ActionSheet>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    backgroundColor: 'white',
+    gap: 16,
+    height: '100%'
+  },
+  sheetContainer: {
+    height: '95%',
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    paddingHorizontal: 16
+  },
+  avatar: {
+    width: 30,
+    height: 30,
+    borderRadius: 15,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY
+  },
+  header: {
+    paddingTop: 16,
+    paddingHorizontal: 6,
+    width: 68
+  },
+  userItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    gap: 8,
+    borderRadius: 8,
+    marginBottom: 6
+  }
+});
+
+export default AllNomadsModal;

+ 13 - 7
src/screens/InAppScreens/MessagesScreen/Components/RouteSearch.tsx

@@ -53,8 +53,10 @@ const RouteSearch = () => {
         <AvatarWithInitials
           text={`${item.first_name[0] ?? ''}${item.last_name[0] ?? ''}`}
           flag={API_HOST + item.homebase_flag}
-          size={30}
+          size={36}
           fontSize={12}
+          borderColor={Colors.LIGHT_GRAY}
+          borderWidth={1}
         />
       )}
 
@@ -131,15 +133,19 @@ const styles = StyleSheet.create({
   itemContainer: {
     flexDirection: 'row',
     alignItems: 'center',
-    paddingBottom: 24,
-    gap: 8
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    gap: 8,
+    borderRadius: 8,
+    marginBottom: 6
   },
   avatar: {
-    width: 30,
-    height: 30,
-    borderRadius: 15,
+    width: 36,
+    height: 36,
+    borderRadius: 18,
     borderWidth: 1,
-    borderColor: Colors.FILL_LIGHT
+    borderColor: Colors.LIGHT_GRAY
   },
   textContainer: {
     flex: 1

+ 0 - 1
src/screens/InAppScreens/MessagesScreen/Components/SearchUsersModal.tsx

@@ -33,7 +33,6 @@ const SearchModal = () => {
       closeOnTouchBackdrop={false}
       keyboardHandlerEnabled={false}
       onClose={() => clearStore()}
-      isModal={false}
     />
   );
 };

+ 33 - 0
src/screens/InAppScreens/MessagesScreen/Components/styles.tsx

@@ -145,5 +145,38 @@ export const chatStyles = StyleSheet.create({
     fontSize: getFontSize(14),
     fontFamily: 'redhat-700',
     marginBottom: 5
+  },
+  cancelText: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontWeight: '700'
+  },
+  itemContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingBottom: 24,
+    gap: 8
+  },
+  textContainer: {
+    flex: 1
+  },
+  name: {
+    fontSize: getFontSize(14),
+    color: Colors.DARK_BLUE,
+    fontFamily: 'montserrat-700'
+  },
+  newGroup: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    padding: 12,
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 8,
+    height: 44
+  },
+  text: {
+    fontSize: getFontSize(12),
+    color: Colors.DARK_BLUE,
+    fontWeight: '700'
   }
 });

+ 37 - 19
src/screens/InAppScreens/MessagesScreen/GroupSettingsScreen/index.tsx

@@ -32,6 +32,8 @@ import { NAVIGATION_PAGES } from 'src/types';
 import { SheetManager } from 'react-native-actions-sheet';
 import EditGroupModal from '../Components/EditGroupModal';
 import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
+import AllNomadsModal from '../Components/AllNomadsModal';
+import AddNomadsModal from '../Components/AddNomadsModal';
 
 type Props = {
   navigation: NavigationProp<any>;
@@ -218,23 +220,30 @@ const GroupSettingScreen: FC<Props> = ({ navigation, route }) => {
             />
           )}
 
-          {canSeeMembers && members ? (
-            <View style={{ gap: 8, marginBottom: 16 }}>
-              <View
-                style={{
-                  flexDirection: 'row',
-                  justifyContent: 'space-between',
-                  alignItems: 'center'
-                }}
-              >
-                <Text style={styles.title}>{members.settings?.length} nomads</Text>
-                {data.settings.members_can_add_new_members === 1 || data.settings.admin === 1 ? (
-                  <TouchableOpacity style={{ padding: 6, paddingRight: 0 }}>
-                    <UserPlusIcon fill={Colors.ORANGE} height={18} width={23} />
-                  </TouchableOpacity>
-                ) : null}
-              </View>
+          <View style={{ gap: 8, marginBottom: 16 }}>
+            <View
+              style={{
+                flexDirection: 'row',
+                justifyContent: 'space-between',
+                alignItems: 'center'
+              }}
+            >
+              <Text style={styles.title}>{data.settings?.member_count} nomads</Text>
+              {data.settings.members_can_add_new_members === 1 || data.settings.admin === 1 ? (
+                <TouchableOpacity
+                  style={{ padding: 6, paddingRight: 0 }}
+                  onPress={() =>
+                    SheetManager.show('add-nomads-modal', {
+                      payload: { refetchMembers, groupToken, refetch }
+                    })
+                  }
+                >
+                  <UserPlusIcon fill={Colors.ORANGE} height={18} width={23} />
+                </TouchableOpacity>
+              ) : null}
+            </View>
 
+            {canSeeMembers && members ? (
               <View style={{ gap: 6 }}>
                 {(data.settings.member_count > 4
                   ? members.settings.slice(0, 4)
@@ -280,7 +289,14 @@ const GroupSettingScreen: FC<Props> = ({ navigation, route }) => {
                   </TouchableOpacity>
                 ))}
                 {data.settings.member_count > 4 ? (
-                  <TouchableOpacity style={{ padding: 8, alignItems: 'center' }}>
+                  <TouchableOpacity
+                    style={{ padding: 8, alignItems: 'center' }}
+                    onPress={() =>
+                      SheetManager.show('all-nomads-modal', {
+                        payload: { members: members.settings }
+                      })
+                    }
+                  >
                     <Text
                       style={{
                         color: Colors.DARK_BLUE,
@@ -293,8 +309,8 @@ const GroupSettingScreen: FC<Props> = ({ navigation, route }) => {
                   </TouchableOpacity>
                 ) : null}
               </View>
-            </View>
-          ) : null}
+            ) : null}
+          </View>
         </View>
 
         <View style={{ gap: 16 }}>
@@ -345,6 +361,8 @@ const GroupSettingScreen: FC<Props> = ({ navigation, route }) => {
         doubleTapToZoomEnabled={true}
       />
       <EditGroupModal />
+      <AllNomadsModal />
+      <AddNomadsModal />
     </PageWrapper>
   );
 };