Source

externalCode/membersList.js

import * as React from "react";

/**
 * @typedef {Object} Member
 */

/**
 * @typedef {Object} FeatureValues
 */

/**
 * @typedef {Function} TransformMemberViewModelCallback
 * @param {MemberViewModel} viewModel
 * @param {Object} item Member data from API
 * @param {ToUserBasedOnSettings} toUserBasedOnSettings
 * @param {Boolean} isOwnAccount Returns `true` if item is logged in user's own account
 * @param {Boolean} isProfile
 * @return {Object}
 */

/**
 * @typedef {Function} TransformMemberActionsCallback
 * @param {Array<ButtonConfig>}
 * @return {Array<ButtonConfig>}
 */

/**
 * @typedef {Object} ButtonConfig
 * @property {?String} permissionField
 * @property {String} statusField
 * @property {Array<ButtonFlow>} flow
 */

/**
 * @typedef {Function} CheckProperty
 * @param {Object} object View model
 * @return {Boolean}
 */

/**
 * Redux action to dispatch or simple function to execute
 * @typedef {Function} DoFunction 
 * @param {Object} item - View model
 * @param {?String} value - Value from prompt popup input
 * @return {Function}
 * @example 
 * 	doFunction: (item, value) => () => { navigateToReport(item.id) } // `navigateToReport` is function that we dont want to dispatch on redux
 * 	doFunction: (item, value) =>
						documentUpdateRequest(item, {
							title: value,
							group_id: item.groupId,
							folder_id: item.folderId
						}) // `documentUpdateRequest` is redux action to dispatch
 */

/**
 * Get error from redux state
 * @typedef {Function} GetError
 * @param {Object} store - Whole redux store
 * @return {any}
 * @example
 * state => state.forums.unsubscribe.error
 */

/**
 * @typedef {Object} ActionButton
 * @property {?Number} icon - Image loaded via "require"
 * @property {String} label - Translatable string
 * @property {DoFunction} doFunction - Redux action to dispatch or simple function to execute
 * @property {?GetError} getError - Redux action to dispatch or simple function to execute
 * @property {?Boolean} isNavigation - If true button will not show spinner when changing state
 * @property {?String} promptMessage - If set prompt popup with input will show on action executed
 * @property {Function} promptDefaultValue
 */

/**
 * @typedef {Object} ButtonFlow
 * @property {CheckProperty} check Checks for a view model property and checks if it should show the button
 * @property {Array} buttons Buttons to show if check passes
 */

/**
 * @typedef {Function} ToUserBasedOnSettings
 * @param {?String} selectedTabId - Profile subscreen idenitifier
 * @param {User} User - User to navigate to
 * @param {Object} params - Params to send through route
 */

/**
 * @typedef {Object} MemberItemComponentProps
 * @property { ?Number } groupId Group Id
 * @property { ?Boolean } groupMember Returns `true` if user is a member of the group
 * @property { Object } colors App colors
 * @property { Object } global App global style
 * @property { MemberViewModel } item
 * @property { Number } index Index of the item in the list
 * @property { TranslationFunction } t
 * @property { Array } actions Member actions
 * @property { Object } settings App settings
 * @property { Boolean } lastItem Returns `true` if it is the last element in the list
 * @property { String } locale
 * @property { Object } rawData Member data from API
 */

/**
 * @typedef {Function} TransformMembersParams
 * @param {FetchMembersParams}
 * @return {Object}
 */

/**
 * @typedef {Object} FetchMembersParams
 * @see {@link https://www.buddyboss.com/resources/api/#api-Members-GetBBMembers}
 * @property {Number} per_page Maximum number of items to be returned in result set.
 * @property {Number} page Current page of the collection.
 * @property {String} scope Limit result set to items with a specific scope.
 * @property {String} search Limit results to those matching a string.
 * @property {String} type Shorthand for certain orderby/order combinations.
 */

/**
 * @typedef {Function} TransformMembersSubFiltersFilterCallback
 * @param {Object}
 * @return {Object}
 */

/**
 * @class
 * Members Screen Hooks.
 * Instance name: membersListHooksApi
 
   You can customize how the members list screen such as replacing item components, modifying call to action buttons and more.
 * @example
 * externalCodeSetup.membersListHooksApi.METHOD_NAME
 */
export class MembersListHooksApi {
	// filters and changes screen navigationOptions
	memberViewModelFilter = (
		viewModel,
		item,
		toUserBasedOnSettings,
		currentUserId,
		isProfile
	) => viewModel;

	/**
	 * Sets the callback function that can change an existing member view model object.
	 * @method
	 * @param {TransformMemberViewModelCallback} memberViewModelFilter
	 * @example
	 *   externalCodeSetup.membersListHooksApi.setMemberViewModelFilter((props) => {
	 *     return {...props, date: new Date()}
	 *   })
	 */
	setMemberViewModelFilter = memberViewModelFilter => {
		this.memberViewModelFilter = memberViewModelFilter;
	};

	// filters and changes action buttons list
	actionsFilter = membersActions => membersActions;

	/**
	 * It sets the filter function to modify the member action buttons array such as removing an action button from the list.
	 * @method
	 * @param {TransformMemberActionsCallback} actionsFilter
	 * @example <caption>The following example shows how to remove a button from the members list.</caption>
	 *   externalCodeSetup.membersListHooksApi.setActionsFilter((buttonConfig) => {
	 *     return buttonConfig.splice(0, 1);
	 *   })
	 */
	setActionsFilter = actionsFilter => {
		this.actionsFilter = actionsFilter;
	};

	/**
	 * @deprecated
	 * hides the BuddyPressListFilters bellow header in MembersList
	 */
	shouldHideFilterComponent = false;

	hideFilterComponent = () => {
		this.shouldHideFilterComponent = true;
	};

	MemberItemComponent = null;
	/**
	 * Replaces a member item component in the members list.
	 * For example, you can add more information about the members as shown in the example below.
	 * @method
	 * @param {?React.ComponentType<MemberItemComponentProps>} MemberItemComponent
	 * @example <caption> Use default member item structure and add more information about the member </caption>
	 *
	 * //In custom_code/components/MemberItem.js
	 *
	 * import React, { useMemo } from "react";
	 * import { View, StyleSheet, Text, ActivityIndicator } from "react-native";
	 *
	 * //Load BuddyBoss components and helper functions
	 * import AppAvatar from "@src/components/AppAvatar";
	 * import { ItemTitle } from "@src/components//TextComponents";
	 * import Icon from "@src/components/Icon";
	 * import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
	 * import { Settings } from "@src/reducers/config";
	 * import AuthWrapper from "@src/components/AuthWrapper";
	 * import { withSettings } from "@src/components/hocs/withSettings";
	 * import ActionSheetButton from "@src/components/ActionButtons/ActionSheetButton";
	 * import { formatDate, displayUserName } from "@src/utils";
	 * import { GUTTER } from "@src/styles/global";
	 *
	 * const MemberItem = props => {
	 *    const {
	 *        item,
	 *        global,
	 *        actions,
	 *        index,
	 *        colors,
	 *        settings,
	 *        lastItem,
	 *        groupId,
	 *        locale,
	 *        t,
	 *        showLoader = false,
	 *        rawData
	 *    } = props;
	 *
	 *    const userMeta = useMemo(() => {
	 *        if (!!groupId && item.groupJoiningDate) {
	 *            return `${t("members:joined")} ${formatDate(locale)(
	 *                item.groupJoiningDate
	 *            )}`;
	 *        } else if (settings[Settings.ENABLE_MEMBER_TYPE_DISPLAY] && item.type) {
	 *            return item.type;
	 *        } else if (item.nicename) {
	 *            return displayUserName(item.nicename);
	 *        }
	 *    });
	 *
	 *    return (
	 *        <AppTouchableOpacity
	 *            onPress={item.onClick}
	 *            style={[styles.item, index === 0 ? { paddingTop: 0 } : {}]}
	 *       >
	 *            <View style={[global.row, styles.itemInner]}>
	 *                <AppAvatar
	 *                    size={64}
	 *                    name={item.fullname}
	 *                    source={{
	 *                        uri: item.avatarUrl
	 *                    }}
	 *                />
	 *                <View
	 *                    style={[global.row, !lastItem && global.bottomBorder, styles.text]}
	 *                >
	 *                    <View style={{ flex: 1 }}>
	 *                        <ItemTitle global={global} style={{ marginBottom: 3 }}>
	 *                            {item.fullname}
	 *                        </ItemTitle>
	 *                        {!!userMeta && <Text style={global.itemMeta}>{userMeta}</Text>}
	 *
	 *                        //Add more data for member...
	 *                        <Text>Link to user profile: {rawData?.link}</Text>
	 *                        <Text>Last Activity: {rawData?.last_activity}</Text>
	 *                        <Text>Friendship status: {rawData?.friendship_status}</Text>
	 *                    </View>
	 *                    {showLoader ? (
	 *                        <ActivityIndicator size={"small"} color={colors.highlightColor} />
	 *                    ) : (
	 *                        <ActionSheetButton
	 *                            headerProps={{
	 *                                id: item.id,
	 *                                title: item.fullname,
	 *                                avatarSource: { uri: item.avatarUrl },
	 *                                onClick: item.onClick
	 *                            }}
	 *                            object={item}
	 *                            actionButtons={actions}
	 *                            global={global}
	 *                            colors={colors}
	 *                            t={t}
	 *                            renderButton={() => (
	 *                                <AuthWrapper actionOnGuestLogin={"hide"}>
	 *                                    <Icon
	 *                                        icon={{fontIconName: "ellipsis-h", weight: 300}}
	 *                                        tintColor={colors.textIconColor}
	 *                                        styles={{
	 *                                            margin: 5,
	 *                                             height: 16
	 *                                        }}
	 *                                    />
	 *                                </AuthWrapper>
	 *                            )}
	 *                         />
	 *                    )}
	 *                </View>
	 *            </View>
	 *        </AppTouchableOpacity>
	 *    );
	 * };
	 *
	 * const styles = StyleSheet.create({
	 *    item: {
	 *        flex: 1,
	 *        paddingHorizontal: GUTTER
	 *    },
	 *    itemInner: {
	 *        flex: 1,
	 *        justifyContent: "space-between"
	 *    },
	 *    text: {
	 *        paddingVertical: 24,
	 *        marginLeft: 14,
	 *        justifyContent: "space-between",
	 *        flex: 1
	 *    },
	 *    buttonsWrap: {}
	 * });
	 *
	 * export default withSettings(MemberItem);
	 *
	 * //In custom_code/index.js
	 *
	 * ...
	 *
	 * import MemberItem from "./components/MemberItem";
	 *  export const applyCustomCode = externalCodeSetup => {
	 *   externalCodeSetup.membersListHooksApi.setMemberItemComponent(props => {
	 *     return <MemberItem {...props} />
	 *   })
	 * }
	 */
	setMemberItemComponent = MemberItemComponent => {
		this.MemberItemComponent = MemberItemComponent;
	};

	fetchParamsFilter = params => params;

	/**
	 * It overrides the parameters that are used to fetch members in the Members screen so that you can make it as customizable as possible when calling its API.
	 * @method
	 * @param {TransformMembersParams} fetchParamsFilter
	 *
	 * @example <caption> Create a custom filter in members screen </caption>
	 *
	 * //In components/MembersBeforeList.js...
	 *
	 * import React, { useState } from "react";
	 * import { TextInput, View, Button } from 'react-native'
	 * import { useDispatch } from "react-redux";
	 * import { membersRequested } from "@src/actions/members";
	 * import { getExternalCodeSetup } from "@src/externalCode/externalRepo";
	 * import withGlobalStyles from "@src/components/hocs/withGlobalStyles";
	 *
	 * const hook = getExternalCodeSetup().membersListHooksApi;
	 *
	 * const screenName = "book";
	 *
	 * const filter = "all"; //"all", "friends", "following", "followers", "requests"
	 * const subfilters = {
	 *    type: "active" // "active", "newest", "alphabetical", "random", "online", "popular"
	 * };
	 *
	 * const refresh = true; //Set to true to refresh list
	 * const searchTerm = ""
	 *
	 * const MembersBeforeList = (props) => {
	 *
	 *    const { navigation, route, colors } = props;
	 *
	 *    const dispatch = useDispatch();
	 *
	 *    //If showing the matched screen, show custom filter before displaying list component
	 *    if (route?.params?.item?.object === screenName) {
	 *
	 *        const [experience, setExperience] = useState('');
	 *        const [coursesCompleted, setCoursesCompleted] = useState('')
	 *
	 *        const handleSubmit = () => {
	 *
	 *            //Set custom parameters before fetching documents
	 *            hook.setFetchParamsFilter((props) => {
	 *
	 *                //You can add more parameters such as "subject", "keyword" etc...
	 *                return {
	 *                    ...props,
	 *                    experience,
	 *                    coursesCompleted
	 *                }
	 *            })
	 *
	 *            //Dispatch redux action to call api using customized filters
	 *            dispatch(membersRequested(filter, subfilters, refresh, searchTerm));
	 *
	 *        }
	 *
	 *        return <View style={{ backgroundColor: colors.whiteColor}}>
	 *
	 *            <TextInput
	 *                style={{paddingHorizontal: 20, marginTop: 10, fontSize: 20}}
	 *                autoFocus
	 *                keyboardType="number-pad"
	 *                value={experience}
	 *                onChangeText={experience => setExperience(experience)}
	 *                placeholder="Experience (Cumulative Years)"
	 *            />
	 *
	 *            <TextInput
	 *                style={{paddingHorizontal: 20, marginTop: 10, fontSize: 20}}
	 *                value={coursesCompleted}
	 *                onChangeText={coursesCompleted => setCoursesCompleted(coursesCompleted)}
	 *                placeholder="Courses completed (Enter keyword...)"
	 *            />
	 *
	 *            <Button
	 *                onPress={() => handleSubmit()}
	 *                title="Filter"
	 *            />
	 *        </View>
	 *    }
	 *
	 *    return null;
	 *
	 * }
	 *
	 * export default withGlobalStyles(MembersBeforeList);
	 *
	 * //In components/MyCustomScreen.js...
	 * import React from 'react';
	 * import MembersScreen from "@src/containers/Custom/MembersScreen";
	 *
	 * const MyCustomScreen = props => (<MembersScreen {...props} showSearch={false} hideFilters={true} headerHeight={250} />)
	 *
	 *
	 * export default MyCustomScreen;
	 *
	 * //In custom_code/index.js...
	 *
	 * ...
	 *
	 * import MembersBeforeList from "./components/MembersBeforeList";
	 * export const applyCustomCode = externalCodeSetup => {
	 *
	 *    externalCodeSetup.filterScreenApiHooks.setAfterFilterComponent(MembersBeforeList);
	 *
	 *    externalCodeSetup.navigationApi.addNavigationRoute(
	 *       "book",
	 *       "BookScreen",
	 *       MyCustomScreen,
	 *       "All"
	 *    );
	 *    externalCodeSetup.navigationApi.addNavigationRoute(
	 *       "book",
	 *       "BookScreen",
	 *       MyCustomScreen,
	 *       "Main"
	 *    );
	 * }
	 */
	setFetchParamsFilter = fetchParamsFilter => {
		this.fetchParamsFilter = fetchParamsFilter;
	};

	subFiltersFilter = filters => filters;

	/**
	 * You can use this to set the sub filter function to rearrange the order of the filters in the Members screen.
	 * For example, you can rearrange the labels to set "Alphabetical" as the default filter on the members list screen under the search bar.
	 * @method
	 * @param {TransformMembersSubFiltersFilterCallback} subFiltersFilter
	 * @example <caption>User would like to set "Alphabetical" as the first filter</caption>
	 * externalCodeSetup.membersListHooksApi.setSubFiltersFilter(filter => {
	 *     return {
	 *         type: [
	 *             {
	 *                 value: "alphabetical",
	 *                 label: "Alphabetical"
	 *             },
	 *             {
	 *                 value: "active",
	 *                 label: "Recently Active"
	 *             },
	 *             {
	 *                 value: "newest",
	 *                 label: "Newest Members"
	 *             }
	 *         ],
	 *         member_type: filter.member_type
	 *     };
	 * });
	 */
	setSubFiltersFilter = subFiltersFilter => {
		this.subFiltersFilter = subFiltersFilter;
	};
}