Source

externalCode/groupsList.js

import * as React from "react";

/**
 * @typedef {Object} MemberViewModel
 * @property {Number} id Id of member
 * @property {Boolean} someoneelse Returns true if member is not the user logged in
 * @property {String} fullname Full name of user
 * @property {String} avatarUrl Url to user's avatar
 * @property {String} avatarFull Url to user's full avatar image
 * @property {String} friendStatus Returns friend status
 * @property {String} nicename Returns a nice name format of user's name
 * @property {?Number} friendshipId Returns the friendship id between logged in user and user loaded in the model
 * @property {String} lastActivity Returns last activity of user
 * @property {Function} onClick Can be used to redirect to user's profile
 * @property {String} coverSrc Url to user's cover image
 * @property {Number} points Points associated to user
 * @property {Array} userPoints User points information
 * @property {String} type User type
 * @property {String} registeredDate Date when the user registered
 * @property {String} groupJoiningDate Date when the user joined the group
 * @property {Number} followers Number of followers
 * @property {Number} following Number of persons that the user is following
 * @property {Boolean} can_report Returns `true` if user can be reported
 * @property {Boolean} reported Returns `true` if user has already been reported
 * @property {MemberViewModel} inviter Inviter member details
 */

/**
 * @typedef {"-1" | "new_member" | "new_avatar" | "updated_profile" | "activity_update" | "activity_comment" | "friendship_accepted" | "friendship_created" | "created_group" | "joined_group" | "group_details_updated" | "bbp_topic_create" | "bbp_reply_create"} ActivitySubFilter
 */

/**
 * @typedef {Function} TransformGroupActivityCallback
 * @param {Array<ActivitySubFilter>} list
 * @return {Array<string>}
 */

/**
 * @ignore
 * @typedef {Function} TransformGroupsParamsCallback
 * @param {Object} filterProps - params
 * @return {Object}
 */

/**
 * @typedef {Object} GroupViewModel
 * @property {Number} parentId
 * @property {Number} id Group id
 * @property {String} avatar Group avatar
 * @property {Boolean} coverImage Returns `true` if group has cover image
 * @property {Boolean} isMember Returns `true` if logged in user is a member
 * @property {Boolean} isAdmin Returns `true` if logged in user is an admin
 * @property {Boolean} isAuthor Returns `true` if logged in user is the creator of the group
 * @property {Boolean} isMod Returns `true` if logged in user is assigned as a moderator
 * @property {Boolean} isSoloOrganizer Returns `true` if logged in user is assigned as an organizer
 * @property {Boolean} notAdmin Returns `true` if logged in user is not an admin
 * @property {Boolean|Number} inviteId Returns the invite id if logged in user has been invited to join. Otherwise, will return `false`
 * @property {Boolean} can_report Returns `true` if group can be reported
 * @property {Boolean} reported Returns `true` if group has already been reported
 * @property {Boolean} requestId Returns request id if logged in user is trying to join the group. Otherwise, will return `false`
 * @property {String} title Group title
 * @property {String} content Group description
 * @property {String} contentRendered Group description rendered
 * @property {String} shortContent Group short description
 * @property {Boolean} hasForum Returns `true` if group has associated forum
 * @property {Number} membersCount Number of members in the group
 * @property {String} status Group visibility. Ex: `public`, `hidden`
 * @property {Array} admins
 * @property {String} role Role of logged in user
 * @property {String} roleLabel Role label of logged in user
 * @property {String} type Group type
 * @property {Array} subgroupIds
 * @property {Function} navigateToWeb Function to navigate to group link in web
 * @property {Function} getGroup Function to get all fields given by api. Ex: `const group = getGroup(g => g)`.
 * @property {Boolean} canPost Returns `true` if logged in user can post
 * @property {Boolean} canJoin Returns `true` if logged in user can join
 * @property {Boolean} canCreateMedia Returns `true` if logged in user can create media
 * @property {Boolean} canCreateDocument Returns `true` if logged in user can create document
 * @property {Function} onClick Function to navigate to group item
 */

/**
 * @typedef {Object} GroupItemComponentProps
 * @property {Object} global App global style
 * @property {Object} colors App colors
 * @property {GroupViewModel} item
 * @property {Number} index Item index in a list
 * @property {TranslationFunction} t
 * @property {Array} actions Group actions such as functions to join or leave a group
 * @property {Number} currentUserId Logged in user ID
 * @return {?React.ComponentType}
 */

/**
 * @typedef {Object} GroupPendingMemberItemComponentProps
 * @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 { Object } groupInvite Group inviter details
 */

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

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

/**
 * @typedef {Object} ButtonFlow
 * @property {CheckProperty} check - Checks for a view model property, and based on the check it decides if it should show button
 * @property {Array<Button>} buttons - Buttons to show if check passes
 */

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

/**
 * @typedef {Object} TransformGroupSubFiltersFilterCallback
 * @property {Object} type Filter types such as `active`, `popular`, `newest`, `alphabetical`
 * @property {Object} group_type Group types
 */

/**
 * @typedef {Function} TransformGroupsParams
 * @param {FetchGroupsParams}
 * @return {Object}
 */

/**
 * @typedef {Object} FetchGroupsParams
 * @see {@link https://www.buddyboss.com/resources/api/#api-Groups-GetBBGroups}
 * @property {Number} per_page Maximum number of items to be returned in result set.
 * @property {Number} page Current page of the collection.
 * @property {String} search Limit results to those matching a string.
 * @property {String} order Order sort attribute ascending or descending. Default value: `desc`. Allowed values: `asc`, `desc`
 * @property {String} orderby Order Groups by which attribute. Default value: `date_created`. Allowed values: `date_created`, `last_activity`, `total_member_count`, `name`, `random`
 * @property {String} type Shorthand for certain orderby/order combinations. Default value: `active`. Allowed values: `active`, `newest`, `alphabetical`, `random`, `popular`
 * @property {String} scope Limit result set to items with a specific scope. Default value: `all`. Allowed values: `all`, `personal`
 *
 */

/**
 * @class
 * Groups Index Hooks.
 * Instance name: groupsListHooksApi
 
   You can customize Group lists such as replacing list components, adding sub filters to the groups list and more with this hook.
 * @example
 * externalCodeSetup.groupsListHooksApi.METHOD_NAME
 */
export class GroupsListHooksApi {
	groupActivitySubFilter = list => list;
	/**
	 * @ignore Not being used in app
	 * Sets the filter function capable of modifying array for groups Activity
	 * @method
	 * @param {TransformGroupActivityCallback} groupActivitySubFilter
	 */
	setGroupActivitySubFilter = groupActivitySubFilter => {
		this.groupActivitySubFilter = groupActivitySubFilter;
	};

	filterProps = filterProps => filterProps;

	/**
	 * @ignore Not being used in app
	 * Sets the filter function capable of modifying fetch params that are used in the request
	 * @method
	 * @param {TransformGroupsParamsCallback} filterProps
	 */
	setFilterProps = filterProps => {
		this.filterProps = filterProps;
	};

	subFilterProps = subFilterProps => subFilterProps;

	/**
	 *
	 * Sets the available sub filter function.
	 * As shown in the example below, it's one of the ways you can modify the existing subfilter and customize it according to your preferences.
	 * @method
	 * @param {TransformGroupSubFiltersFilterCallback} subFiltersFilter
	 * @example <caption>You can add a "Recently Active" subfilter; remove the "All Group Types" subfilter; and change the labels of the group types sub filters.</caption>
	 *  externalCodeSetup.groupsListHooksApi.setSubFiltersFilter((filters) => {
	 *    return {
	 *      type: [{ value: "active", label: "Recently Active Groups" }],
	 *      group_type: [
	 *        { value: "beagle", label: "Beagle Dogs" },
	 *        { value: "japanese-spitz", label: "Japanese Spitz Dogs" }
	 *      ]
	 *    }
	 *
	 *  });
	 */
	setSubFiltersFilter = subFilterProps => {
		this.subFilterProps = subFilterProps;
	};

	GroupItemComponent = null;
	/**
	 * It used to replace the default group item component in the groups list.
	 * For example, you can add a short description of the group or disable avatar to display in the list.
	 * @method
	 * @param {?React.ComponentType<GroupItemComponentProps>} GroupItemComponent
	 * @example <caption>Add a short group description and remove avatar display.</caption>
	 * //In custom_code/components/GroupItem.js...
	 *
	 * import React from "react";
	 * import {
	 *    View,
	 *    StyleSheet,
	 *    Text,
	 *    Image,
	 *    TouchableOpacity,
	 * } from "react-native";
	 *
	 * //Import BuddyBoss components and helper functions...
	 * import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
	 * import { ItemTitle } from "@src/components/TextComponents";
	 * import Icon from "@src/components/Icon";
	 * import AuthWrapper from "@src/components/AuthWrapper";
	 * import {
	 *    groupInviteDescription,
	 *    groupMembersCountTranslation,
	 *    groupStatusTranslation,
	 * } from "@src/utils";
	 * import ActionButtonList from "@src/components/ActionButtons/ActionButtonList";
	 * import GroupActionSheetWrapper from "@src/components/Group/GroupActionSheetWrapper";
	 * import { GUTTER } from "@src/styles/global";
	 * import FontManager from "@src/FontManager";
	 * import { Typographies } from "@src/services/enums/branding";
	 *
	 * const GroupItem = props => {
	 *    const { item, global, colors, actions, userId, index, t, isInvite } = props;
	 *
	 *    const styles = getStyles(colors);
	 *
	 *    return (
	 *        <AppTouchableOpacity
	 *            onPress={item.onClick}
	 *            style={[styles.item, index === 0 ? { paddingTop: 0 } : {}]}
	 *        >
	 *            <View
	 *                style={[
	 *                    global.row,
	 *                    { justifyContent: "space-between", flex: 1, alignItems: "flex-start" }
	 *                ]}
	 *            >
	 *
	 *                <View style={[global.bottomBorder, {
	 *                    paddingBottom: 15, paddingBottom: 14,
	 *                    marginLeft: 14,
	 *                    flex: 1
	 *                }]}>
	 *                    //Group title
	 *                    <ItemTitle global={global} style={{ marginBottom: 4 }}>
	 *                        {item.title}
	 *                    </ItemTitle>
	 *
	 *                    //Show component depending on if user received invitation to join the group
	 *                    {isInvite ? (
	 *                        <View>
	 *                            {groupInviteDescription(item, t, global, { marginBottom: 9 })}
	 *                            {!!item.inviteMessage && <Text>{item.inviteMessage}</Text>}
	 *                        </View>
	 *                    ) : (
	 *                        <View style={[global.row, {
	 *                            flex: 1,
	 *                            flexWrap: "wrap"
	 *                        }]}>
	 *                            <Text style={global.itemMeta}>
	 *                                {groupStatusTranslation(t, item)}
	 *                            </Text>
	 *                            <View style={global.dotSep} />
	 *                            <Text style={global.itemMeta}>
	 *                                {groupMembersCountTranslation(t, item)}
	 *                            </Text>
	 *                        </View>
	 *                    )}
	 *
	 *                    <View style={{
	 *                        marginTop: 16,
	 *                        position: "relative"
	 *                    }}>
	 *                        <View style={{ alignItems: "flex-start" }}>
	 *
	 *                            // Add short content
	 *                            {item.shortContent != "" && <Text style={{ color: colors.labelTextColor, marginBottom: 10 }}> {item.shortContent} </Text> }
	 *
	 *                            //Show a component depending on if user has already joined the group
	 *                            {item.isMember && !isInvite ? (
	 *                                <GroupActionSheetWrapper
	 *                                    group={item}
	 *                                    actionButtons={actions}
	 *                                    {...{
	 *                                        global,
	 *                                        colors,
	 *                                        t
	 *                                    }}
	 *                                >
	 *                                    <View
	 *                                        style={[
	 *                                            global.wrappedButton,
	 *                                            global.wrappedTextButton,
	 *                                            global.row,
	 *                                            { backgroundColor: colors.labelBgColor }
	 *                                        ]}
	 *                                    >
	 *                                        <Icon
	 *                                            icon={require("@src/assets/img/checkmark.png")}
	 *                                            tintColor={colors.labelTextColor}
	 *                                            styles={{
	 *                                                width: 11,
	 *                                                height: 11,
	 *                                                marginLeft: -2,
	 *                                                marginRight: 4
	 *                                            }}
	 *                                            rtlStyleFix={"handled"}
	 *                                        />
	 *                                        <Text
	 *                                            style={[
	 *                                                global.wrappedTextButtonLabel,
	 *                                                { color: colors.labelTextColor }
	 *                                            ]}
	 *                                        >
	 *                                            {item.role}
	 *                                        </Text>
	 *                                    </View>
	 *                                </GroupActionSheetWrapper>
	 *                            ) : (
	 *                                <AuthWrapper>
	 *                                    <ActionButtonList
	 *                                        hideIcons={true}
	 *                                        actionButtons={actions}
	 *                                        object={item}
	 *                                        t={t}
	 *                                        color={colors.labelTextColor}
	 *                                        buttonStyle={({ label }) => ({
	 *                                            ...(label.match(/acceptInvite/)
	 *                                                ? styles.inviteAcceptButton
	 *                                                : label.match(/cancelInvite/)
	 *                                                    ? styles.inviteRejectButton
	 *                                                    : { marginRight: 10 }),
	 *                                            backgroundColor: colors.labelBgColor
	 *                                        })}
	 *                                        textStyle={({ label }, color) =>
	 *                                            label.match(/acceptInvite/)
	 *                                                ? {
	 *                                                    ...global.boldText,
	 *                                                    color
	 *                                                }
	 *                                                : label.match(/cancelInvite/)
	 *                                                    ? { ...global.boldText, color: colors.warningColor }
	 *                                                    : { ...global.boldText, color }
	 *                                        }
	 *                                    />
	 *                                </AuthWrapper>
	 *                            )}
	 *                        </View>
	 *                    </View>
	 *                </View>
	 *            </View>
	 *        </AppTouchableOpacity>
	 *    );
	 * };
	 *
	 * const getStyles = colors =>
	 *    StyleSheet.create({
	 *        item: {
	 *            flex: 1,
	 *            marginTop: 15,
	 *            paddingHorizontal: GUTTER
	 *        },
	 *        inviteAcceptButton: {
	 *            flex: 0.65,
	 *            marginRight: 4,
	 *            backgroundColor: colors.highlightColor,
	 *            height: FontManager.applyFontHeightAdjustment(36, Typographies.bodyText),
	 *            justifyContent: "center",
	 *            borderRadius: 8
	 *        },
	 *        inviteRejectButton: {
	 *            flex: 1,
	 *            paddingVertical: 5,
	 *            marginLeft: 4,
	 *            height: FontManager.applyFontHeightAdjustment(36, Typographies.bodyText),
	 *            justifyContent: "center",
	 *            borderRadius: 8,
	 *            borderWidth: StyleSheet.hairlineWidth,
	 *            borderColor: colors.borderColor
	 *        }
	 *    });
	 *
	 * export default GroupItem;
	 *
	 * //In custom_code/index.js...
	 *
	 * ...
	 *
	 * import GroupItem from "./components/GroupItem";
	 * export const applyCustomCode = externalCodeSetup => {
	 *  externalCodeSetup.groupsListHooksApi.setGroupItemComponent(props => <GroupItem {...props} />)
	 * }
	 */
	setGroupItemComponent = GroupItemComponent => {
		this.GroupItemComponent = GroupItemComponent;
	};

	GroupPendingMemberItemComponent = null;
	/**
	 * Replaces group item component in the pending invites group list.
	 * @method
	 * @param {?React.ComponentType<GroupPendingMemberItemComponentProps>} GroupPendingMemberItemComponent
	 * @example <caption>You can add a component to redirect users in the pending invites list.</caption>
	 * //In custom_code/components/PendingMemberItem.js
	 *
	 * import React, {useEffect, useState} from "react";
	 * import {
	 *	View,
	 *	StyleSheet,
	 *	Text,
	 *	ActivityIndicator,
	 *	TouchableOpacity
	 * } from "react-native";
	 *
	 * //Load BuddyBoss components...
	 * import AppAvatar from "@src/components/AppAvatar";
	 * import IconButton from "@src/components/IconButton";
	 * import {GUTTER, FontWeights} from "@src/styles/global";
	 * import {groupInviteModify} from "@src/actions/groupInvites";
	 * import {connect} from "react-redux"; // Will be used to connect to redux for dispatching an action
	 * import {usePrevious} from "@src/components/hooks";
	 *
	 * const MemberItem = props => {
	 *	const {
	 *		item,
	 *		global,
	 *		groupInvite,
	 *		loading,
	 *		groupInviteModify,
	 *		index,
	 *		colors,
	 *		settings,
	 *		t
	 *	} = props;
	 *
	 *	const [active, setActive] = useState(false);
	 *	const prevloading = usePrevious(loading);
	 *
	 *	useEffect(
	 *		() => {
	 *			if (prevloading === true && loading === false) {
	 *				setActive(false);
	 *			}
	 *		},
	 *		[loading]
	 *	);
	 *
	 *	return (
	 *		<View
	 *			style={[
	 *				global.row,
	 *				styles.itemInner,
	 *				styles.item,
	 *				index === 0 ? {paddingTop: 0} : {}
	 *			]}
	 *		>
	 *			<AppAvatar
	 *				size={50}
	 *				name={item.fullname}
	 *				source={{
	 *					uri: item.avatarUrl
	 *				}}
	 *			/>
	 *			<View style={[global.row, global.bottomBorder, styles.text]}>
	 *				<View>
	 *					<Text
	 *							style={[
	 *							global.text,
	 *							{marginBottom: 5, fontWeight: FontWeights.medium}
	 *						]}
	 *					>
	 *						{item.fullname}
	 *					</Text>
	 *				</View>
	 *
	 *				//Add component to redirect to user profile
	 *				<TouchableOpacity onPress={() => item.onClick()}>
	 *					<Text>Go to user profile</Text>
	 *				</TouchableOpacity>
	 *
	 *				{loading && active ? (
	 *					<ActivityIndicator />
	 *				) : (
	 *					<IconButton
	 *						pressHandler={() => {
	 *							setActive(true);
	 *							groupInviteModify(groupInvite);
	 *						}}
	 *						icon={require("@src/assets/img/x.png")}
	 *						tintColor="#C6C6C8"
	 *						style={{
	 *							width: 11,
	 *							height: 11
	 *						}}
	 *					/>
	 *				)}
	 *			</View>
	 *		</View>
	 *	);
	 * };
	 *
	 * const styles = StyleSheet.create({
	 *	item: {
	 *		paddingHorizontal: GUTTER
	 *	},
	 *	itemInner: {
	 *		flex: 1,
	 *		justifyContent: "space-between"
	 *	},
	 *	text: {
	 *		paddingVertical: 20,
	 *		marginLeft: 14,
	 *		justifyContent: "space-between",
	 *		flex: 1
	 *	}
	 * });
	 *
	 * const mapStateToProps = (state, ownProps) => {
	 *	const groupId = ownProps.groupId;
	 *	return {
	 *		loading: state.groupInvites.modify.cancelLoading
	 *	};
	 * };
	 *
	 * const mapDispatchToProps = (dispatch, ownProps) => {
	 *	return {
	 *        //Dispatch `cancel` action to reject the invitation
	 *		groupInviteModify: groupInvite =>
	 *			dispatch(
	 *				groupInviteModify("cancel", groupInvite.group_id, groupInvite.id)
	 *			)
	 *	};
	 * };
	 *
	 * export default connect(
	 *	mapStateToProps,
	 *	mapDispatchToProps
	 * )(MemberItem);
	 *
	 * //In custom_code/index.js
	 *
	 * ...
	 *
	 * import PendingMemberItem from "./components/PendingMemberItem";
	 * export const applyCustomCode = externalCodeSetup => {
	 *
	 *  externalCodeSetup.groupsListHooksApi.setGroupPendingMemberItemComponent(props => <PendingMemberItem {...props} />)
	 * }
	 *
	 */
	setGroupPendingMemberItemComponent = GroupPendingMemberItemComponent => {
		this.GroupPendingMemberItemComponent = GroupPendingMemberItemComponent;
	};

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

	/**
	 * Sets the filter function for modifing group action buttons array
	 * @param {TransformGroupActionsCallback} actionsFilter
	 */
	setActionsFilter = actionsFilter => {
		this.actionsFilter = actionsFilter;
	};

	fetchParamsFilter = params => params;

	/**
	 * It overrides the parameters that are used to fetch groups in the Group screen so that you can make it as customizable as possible when calling its API.
	 * @method
	 * @param {TransformGroupsParams} fetchParamsFilter
	 *
	 * @example <caption> Create a custom filter in groups screen </caption>
	 *
	 * //In components/GroupFiltersCustom.js...
	 *
	 * import React, { useState } from "react";
	 * import { TextInput, View, Button, Text, Switch } from 'react-native'
	 * import { useDispatch } from "react-redux";
	 * import { groupsRequested } from "@src/actions/socialGroups";
	 * import { getExternalCodeSetup } from "@src/externalCode/externalRepo";
	 * import withGlobalStyles from "@src/components/hocs/withGlobalStyles";
	 *
	 * const hook = getExternalCodeSetup().groupsListHooksApi;
	 *
	 * getExternalCodeSetup().indexScreenApiHooks.setHeaderHeight((defaultHeaderHeight, filterType, navigation) => {
	 *
	 *    if (filterType === "groups")
	 *      return 300;
	 *
	 *    return defaultHeaderHeight;
	 *  });
	 *
	 * const screenName = "HomeGroupsScreen";
	 *
	 * const filter = "all"; //"all", "personal", "my-groups", "invites"
	 * const subfilters = {type: "active"}; // "active", "newest", "alphabetical", "popular";
	 *
	 * const refresh = true; //Set to true to refresh list
	 * const searchTerm = ""
	 *
	 *
	 * const GroupFiltersCustom = (props) => {
	 *
	 *    const { navigation, route, colors } = props;
	 *
	 *    const dispatch = useDispatch();
	 *
	 *    //If showing the matched screen, show custom filter before displaying list component
	 *    if (navigation?.state?.routeName === screenName) {
	 *
	 *        const [isEnabled, setIsEnabled] = useState(false);
	 *
	 *        const toggleSwitch = () => setIsEnabled(previousState => !previousState)
	 *
	 *        const handleSubmit = () => {
	 *
	 *            //Set custom parameters before fetching
	 *            hook.setFetchParamsFilter((props) => {
	 *
	 *                //You can add more parameters such as "subject", "keyword" etc...
	 *                return {
	 *                    ...props,
	 *                    show_hidden: isEnabled
	 *                }
	 *            })
	 *
	 *            //Dispatch redux action to call api using customized filters
	 *            dispatch(groupsRequested(filter, subfilters, refresh, searchTerm));
	 *
	 *        }
	 *
	 *        return <View style={{ backgroundColor: colors.whiteColor, flexDirection: "row", alignItems: "center", justifyContent: "center" }}>
	 *
	 *            <Text>Show hidden groups?</Text>
	 *            <Switch
	 *                trackColor={{ false: "#767577", true: "#81b0ff" }}
	 *                thumbColor={isEnabled ? "#f5dd4b" : "#f4f3f4"}
	 *                ios_backgroundColor="#3e3e3e"
	 *                onValueChange={toggleSwitch}
	 *                value={isEnabled}
	 *            />
	 *            <Button
	 *                onPress={() => handleSubmit()}
	 *                title="Filter"
	 *            />
	 *        </View>
	 *    }
	 *
	 *    return null;
	 *
	 * }
	 *
	 * export default withGlobalStyles(GroupFiltersCustom);
	 *
	 * //In custom_code/index.js...
	 *
	 * ...
	 *
	 * import GroupFiltersCustom from "./components/GroupFiltersCustom";
	 * export const applyCustomCode = externalCodeSetup => {
	 *    externalCodeSetup.filterScreenApiHooks.setAfterFilterComponent(GroupFiltersCustom);
	 * }
	 */
	setFetchParamsFilter = fetchParamsFilter => {
		this.fetchParamsFilter = fetchParamsFilter;
	};
}