mattermost-mobile-upstream/app/components/server_user_list/index.tsx
Daniel Espino García 84ea188366
Mattermost Blocks (#9764)
* Mattermost Blocks

* Move block renderer to its own components folder

* Sync translation layer

* UX fixes

* Refactor button to use the button component

* Code review

* Fix CI

* Add animations to collapse blocks

* Add unit tests

* Add e2e tests and related fixes

* Address coderabbit comments

* Address UX comments

* Address feedback

* Address coderabbit comments

* Fix snapshots

* Sync with webapp translation

* Add node limit to button markdown node

* Address feedback

* i18n-extract

* Fix test

* Move image utils to the utils folder

* Remove unneeded style

* Revert build script change

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-06-29 18:42:16 +02:00

136 lines
4.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import UserList from '@components/user_list';
import {General} from '@constants';
import useDidMount from '@hooks/did_mount';
import {useDebounce} from '@hooks/utils';
import {filterProfilesMatchingTerm} from '@utils/user';
import type {AvailableScreens} from '@typings/screens/navigation';
import type {SectionListData} from 'react-native';
type Props = {
tutorialWatched: boolean;
handleSelectProfile: (user: UserProfile) => void;
term: string;
selectedIds: Set<string>;
fetchFunction: (page: number) => Promise<UserProfile[]>;
searchFunction: (term: string) => Promise<UserProfile[]>;
createFilter: (exactMatches: UserProfile[], term: string) => ((p: UserProfile) => boolean);
testID: string;
location: AvailableScreens;
customSection?: (profiles: UserProfile[]) => Array<SectionListData<UserProfile>>;
}
export default function ServerUserList({
tutorialWatched,
handleSelectProfile,
term,
selectedIds,
fetchFunction,
searchFunction,
createFilter,
testID,
location,
customSection,
}: Props) {
const searchTimeoutId = useRef<NodeJS.Timeout | null>(null);
const next = useRef(true);
const page = useRef(-1);
const mounted = useRef(false);
const [profiles, setProfiles] = useState<UserProfile[]>([]);
const [searchResults, setSearchResults] = useState<UserProfile[]>([]);
const [loading, setLoading] = useState(false);
const isSearch = Boolean(term);
const loadedProfiles = (users: UserProfile[]) => {
if (mounted.current) {
if (!users.length) {
next.current = false;
}
page.current += 1;
setLoading(false);
setProfiles((current) => {
if (users?.length) {
return [...current, ...users];
}
return current;
});
}
};
const getProfiles = useDebounce(useCallback(() => {
if (next.current && !loading && !term && mounted.current) {
setLoading(true);
fetchFunction(page.current + 1).then(loadedProfiles);
}
}, [loading, term, fetchFunction]), 100);
const searchUsers = useCallback(async (searchTerm: string) => {
setLoading(true);
const data = await searchFunction(searchTerm);
setSearchResults(data);
setLoading(false);
}, [searchFunction]);
useEffect(() => {
if (term) {
if (searchTimeoutId.current) {
clearTimeout(searchTimeoutId.current);
}
searchTimeoutId.current = setTimeout(() => {
searchUsers(term);
}, General.SEARCH_TIMEOUT_MILLISECONDS);
} else {
setSearchResults([]);
}
// We only want to run the search when the term changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [term]);
useDidMount(() => {
mounted.current = true;
getProfiles();
return () => {
mounted.current = false;
};
});
const data = useMemo(() => {
if (isSearch) {
const exactMatches: UserProfile[] = [];
const filterByTerm = createFilter(exactMatches, term);
const profilesToFilter = searchResults.length ? searchResults : profiles;
const results = filterProfilesMatchingTerm(profilesToFilter, term).filter(filterByTerm);
return [...exactMatches, ...results];
}
return profiles;
}, [isSearch, profiles, createFilter, term, searchResults]);
return (
<UserList
handleSelectProfile={handleSelectProfile}
loading={loading}
profiles={data}
selectedIds={selectedIds}
showNoResults={!loading && page.current !== -1}
fetchMore={getProfiles}
term={term}
testID={testID}
tutorialWatched={tutorialWatched}
location={location}
customSection={customSection}
/>
);
}