@chayns-components/person-finder
Version:
A set of beautiful React components for developing your own applications with chayns.
321 lines (320 loc) • 10.3 kB
JavaScript
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import throttle from 'lodash.throttle';
import { LoadingState, PersonFinderFilterTypes, Priority } from '../types/personFinder';
import { getFriends } from '../api/friends/get';
import { postFriends } from '../api/friends/post';
import { deleteFriends } from '../api/friends/delete';
import { filterDataByKeys, loadData } from '../utils/personFinder';
import { getUACGroups, getUsersByGroups } from '../utils/uac';
const THROTTLE_INTERVAL = 500;
export const PersonFinderContext = /*#__PURE__*/createContext({
data: undefined,
updateData: undefined,
friends: undefined,
addFriend: undefined,
removeFriend: undefined,
activeFilter: undefined,
updateActiveFilter: undefined,
search: undefined,
updateSearch: undefined,
loadMore: undefined,
loadingState: undefined,
updateLoadingState: undefined,
tags: undefined,
setTags: undefined
});
PersonFinderContext.displayName = 'PersonFinderContext';
export const usePersonFinder = () => useContext(PersonFinderContext);
const PersonFinderProvider = ({
children,
friendsPriority,
filterTypes,
defaultEntries,
excludedEntryIds,
shouldShowOwnUser = false,
uacFilter
}) => {
const [data, setData] = useState();
const [friends, setFriends] = useState();
const [uacUsers, setUacUsers] = useState();
const [activeFilter, setActiveFilter] = useState();
const [search, setSearch] = useState('');
const [tags, setTags] = useState(defaultEntries?.map(({
id,
name
}) => ({
id,
text: name
})) ?? []);
const [loadingState, setLoadingState] = useState({
[PersonFinderFilterTypes.PERSON]: LoadingState.None,
[PersonFinderFilterTypes.SITE]: LoadingState.None
});
const dataRef = useRef();
const updateActiveFilter = useCallback(filter => {
setActiveFilter(filter);
}, []);
const updateData = useCallback((key, newData) => {
setData(prevState => ({
...prevState,
[key]: newData
}));
}, []);
const appendData = useCallback((key, newData) => {
setData(prevState => {
const oldEntries = prevState && prevState[key]?.entries ? prevState[key]?.entries : [];
return {
...prevState,
[key]: {
...newData,
entries: [...oldEntries, ...newData.entries]
}
};
});
}, []);
const updateLoadingState = useCallback((key, state) => {
setLoadingState(prev => ({
...prev,
[key]: state
}));
}, []);
const updateSearch = useCallback(value => {
setSearch(value);
}, []);
const loadMore = useCallback(key => {
updateLoadingState(key, LoadingState.Pending);
const current = data?.[key];
if (!current) {
updateLoadingState(key, LoadingState.Error);
return;
}
void loadData({
searchString: search ?? '',
filter: [key],
skipMap: {
[key]: current.skip
}
}).then(result => {
const newData = result?.[key];
if (newData) {
appendData(key, newData);
}
}).finally(() => {
updateLoadingState(key, LoadingState.Success);
});
}, [updateLoadingState, data, search, appendData]);
const addFriend = useCallback(personId => {
void postFriends(personId).then(result => {
if (result) {
const {
firstName,
lastName,
verificationState
} = result;
setFriends(prev => [...(prev ?? []), {
id: personId,
isVerified: verificationState === 1,
commonSites: 0,
firstName,
lastName,
type: PersonFinderFilterTypes.PERSON
}]);
}
});
}, []);
const removeFriend = useCallback(personId => {
void deleteFriends(personId).then(wasSuccessful => {
if (wasSuccessful) {
setFriends(prev => prev?.filter(({
id
}) => id !== personId));
}
});
}, []);
useEffect(() => {
if (!filterTypes.includes(PersonFinderFilterTypes.PERSON)) {
return;
}
void getFriends().then(result => {
if (result) {
setFriends(result.map(({
personId,
firstName,
lastName,
verificationState
}) => ({
lastName,
firstName,
id: personId,
commonSites: 0,
isVerified: verificationState === 1,
type: PersonFinderFilterTypes.PERSON
})));
}
});
}, [filterTypes]);
const latestArgsRef = useRef(null);
const latestHandledRequestRef = useRef(0);
const throttledRequest = useRef(throttle(async () => {
const args = latestArgsRef.current;
if (!args) return;
const {
search: searchString,
filter
} = args;
const requestTimestamp = Date.now();
filter.forEach(key => {
updateLoadingState(key, LoadingState.Pending);
});
const result = await loadData({
searchString,
filter,
skipMap: {}
});
if (requestTimestamp < latestHandledRequestRef.current) {
return;
}
latestHandledRequestRef.current = requestTimestamp;
if (!result) return;
Object.entries(result).forEach(([keyString, value]) => {
const key = keyString;
if (key === PersonFinderFilterTypes.PERSON && friendsPriority === Priority.HIGH && friends) {
const friendIds = new Set(friends.map(f => f.id));
const serverFriendEntries = value.entries.filter(entry => friendIds.has(entry.id));
const serverFriendIds = new Set(serverFriendEntries.map(f => f.id));
const missingFriends = friends.filter(f => !serverFriendIds.has(f.id)).filter(f => f.firstName?.toLowerCase().includes(searchString.toLowerCase()) || f.lastName?.toLowerCase().includes(searchString.toLowerCase()));
const otherEntries = value.entries.filter(entry => !friendIds.has(entry.id));
updateData(key, {
...value,
entries: [...serverFriendEntries, ...missingFriends, ...otherEntries]
});
} else {
updateData(key, value);
}
updateLoadingState(key, value.entries.length === 0 ? LoadingState.Error : LoadingState.Success);
});
}, THROTTLE_INTERVAL, {
leading: false,
trailing: true
})).current;
useEffect(() => {
dataRef.current = data;
}, [data]);
const searchData = useCallback(({
filter
}) => {
const tmpData = dataRef.current;
filter.forEach(key => {
updateLoadingState(key, LoadingState.Pending);
if (tmpData && tmpData[key]) {
// Add all Types that are not searched by a request
const entries = tmpData[key].entries;
const filteredEntries = entries.filter(({
name
}) => name.toLowerCase().includes(search.toLowerCase()));
updateData(key, {
entries: filteredEntries,
searchString: search,
count: filteredEntries.length,
skip: filteredEntries.length
});
updateLoadingState(key, filteredEntries.length === 0 ? LoadingState.Error : LoadingState.Success);
}
});
}, [search, updateData, updateLoadingState]);
const searchLocal = useCallback(() => {
if (search.length < 3) {
return;
}
updateLoadingState(PersonFinderFilterTypes.PERSON, LoadingState.Pending);
const searchedUsers = [];
uacUsers?.forEach(entry => {
if (entry.firstName.toLowerCase().includes(search.toLowerCase()) || entry.lastName.toLowerCase().includes(search.toLowerCase()) || entry.id.toLowerCase().includes(search.toLowerCase())) {
searchedUsers.push(entry);
}
});
updateData(PersonFinderFilterTypes.PERSON, {
entries: searchedUsers,
searchString: search,
count: searchedUsers.length,
skip: searchedUsers.length
});
updateLoadingState(PersonFinderFilterTypes.PERSON, searchedUsers.length === 0 ? LoadingState.Error : LoadingState.Success);
}, [search, uacUsers, updateData, updateLoadingState]);
useEffect(() => {
if (!search) return;
const active = activeFilter ?? filterTypes;
if (uacFilter) {
searchLocal();
} else if (active?.includes(PersonFinderFilterTypes.UAC)) {
searchData({
filter: [PersonFinderFilterTypes.UAC]
});
} else {
latestArgsRef.current = {
search,
filter: active
};
throttledRequest();
}
}, [filterTypes, search, activeFilter, friends, friendsPriority, updateData, updateLoadingState, throttledRequest, searchData, uacFilter, searchLocal]);
useEffect(() => () => {
throttledRequest.cancel();
}, [throttledRequest]);
// load initial data
useEffect(() => {
if (uacFilter) {
void getUsersByGroups(uacFilter).then(users => {
setUacUsers(users);
});
return;
}
if (filterTypes.includes(PersonFinderFilterTypes.UAC) && search === '') {
void getUACGroups().then(result => {
setData({
uac: {
entries: result,
searchString: '',
skip: result.length,
count: result.length
}
});
});
}
if (friendsPriority === Priority.HIGH && filterTypes.includes(PersonFinderFilterTypes.PERSON) && friends && search === '') {
setData({
person: {
entries: friends,
searchString: '',
skip: friends.length,
count: friends.length
}
});
}
}, [filterTypes, friends, friendsPriority, search, uacFilter]);
const providerValue = useMemo(() => ({
data: filterDataByKeys(data, activeFilter, {
excludedEntryIds,
shouldShowOwnUser
}),
updateData,
activeFilter,
updateActiveFilter,
friends,
addFriend,
removeFriend,
search,
updateSearch,
loadMore,
loadingState,
updateLoadingState,
setTags,
tags
}), [activeFilter, addFriend, data, excludedEntryIds, friends, loadMore, loadingState, removeFriend, search, shouldShowOwnUser, tags, updateActiveFilter, updateData, updateLoadingState, updateSearch]);
return /*#__PURE__*/React.createElement(PersonFinderContext.Provider, {
value: providerValue
}, children);
};
PersonFinderProvider.displayName = 'PersonFinderProvider';
export default PersonFinderProvider;
//# sourceMappingURL=PersonFinderProvider.js.map