Source

externalCode/groupsList.ts

import * as React from "react";
import {TGroupViewModel, TTranslationFunction, TMemberViewModel} from "./types";

/**
 * GroupItemComponentProps
 */
type GroupItemComponentProps = {
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,

	item: TGroupViewModel,
	index: number,
	t: TTranslationFunction,
	actions: Record<any, any>[],
	currentUserId: number
};

/**
 * GroupPendingMemberItemComponentProps
 */
type GroupPendingMemberItemComponentProps = {
	/** App colors */
	colors: Record<any, any>,
	/** App global style */
	global: Record<any, any>,
	/** */
	item: TMemberViewModel,
	/** Index of the item in the list */
	index: number,
	/** */
	t: TTranslationFunction,
	/** Group inviter details */
	groupInvite: Record<any, any>
};

/**
 * TransformGroupActionsCallback
 */
type TransformGroupActionsCallback = {
	TransformGroupActionsCallback: ButtonConfig[]
};

/**
 * ButtonConfig
 */
type ButtonConfig = {
	permissionField: string,
	statusField: string,
	flow: ButtonFlow[]
};

/**
 * ButtonFlow
 */
type ButtonFlow = {
	/**
	 * Checks for a view model property, and based on the check it decides if it should show button
	 */
	check: CheckProperty,
	/**
	 * Buttons to show if check passes
	 */
	buttons: Record<any, any>[]
};

/**
 * CheckProperty
 */
type CheckProperty = {
	object: Record<any, any>
};

/**
 * TransformGroupSubFiltersFilterCallback
 */
type TransformGroupSubFiltersFilterCallback = {
	/**
	 * Filter types such as `active`, `popular`, `newest`, `alphabetical`
	 */
	type: Record<any, any>,

	/**
	 * Group types
	 */
	group_type: Record<any, any>
};

/**
 * TransformGroupsParams
 */
type TransformGroupsParams = {
	group: FetchGroupsParams
};

/**
 * FetchGroupsParams
 * @see {@link https://www.buddyboss.com/resources/api/#api-Groups-GetBBGroups}
 */
type FetchGroupsParams = {
	/** Maximum number of items to be returned in result set. */
	per_page: number,
	/** Current page of the collection. */
	page: number,
	/** Limit results to those matching a string. */
	search: string,
	/** Order sort attribute ascending or descending. Default value: `desc`. Allowed values: `asc`, `desc` */
	order: "asc" | "desc",
	/** Order Groups by which attribute. Default value: `date_created`. Allowed values: `date_created`, `last_activity`, `total_member_count`, `name`, `random` */
	orderby:
		| "date_created"
		| "last_activity"
		| "total_member_count"
		| "name"
		| "random",
	/** Shorthand for certain orderby/order combinations. Default value: `active`. Allowed values: `active`, `newest`, `alphabetical`, `random`, `popular` */
	type: "active" | "newest" | "alphabetical" | "random" | "popular",
	/** Limit result set to items with a specific scope. Default value: `all`. Allowed values: `all`, `personal` */
	scope: "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: Record<any, any>) => 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} subFilterProps
	 * @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: (
			viewModel: TransformGroupSubFiltersFilterCallback | Record<any, any>
		) => TransformGroupSubFiltersFilterCallback | Record<any, any>
	) => {
		this.subFilterProps = subFilterProps;
	};

	GroupItemComponent: React.ComponentType<
		GroupItemComponentProps
	> | null = 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={{fontIconName: "check", weight: 400}}
	 *                                            tintColor={colors.labelTextColor}
	 *                                            styles={{
	 *                                                width: 16,
	 *                                                height: 16,
	 *                                                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: React.ComponentType<GroupItemComponentProps> | null
	) => {
		this.GroupItemComponent = GroupItemComponent;
	};

	GroupPendingMemberItemComponent: React.ComponentType<
		GroupPendingMemberItemComponentProps
	> | null = 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={{fontIconName: "times", weight: 300}}
	 *                        tintColor="#C6C6C8"
	 *                    />
	 *                )}
	 *            </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: React.ComponentType<
			GroupPendingMemberItemComponentProps
		> | null
	) => {
		this.GroupPendingMemberItemComponent = GroupPendingMemberItemComponent;
	};

	actionsFilter = (
		groupActions: TransformGroupActionsCallback | Record<any, any>
	) => groupActions;

	/**
	 * Sets the filter function for modifing group action buttons array
	 * @method
	 * @param {TransformGroupActionsCallback} actionsFilter
	 * @example <caption> Remove the "Leave" button </caption>
	 *
	 *  externalCodeSetup.groupsListHooksApi.setActionsFilter(props => {
	 *
	 *    let newButtons = props[1];
	 *    delete newButtons.flow[1];
	 *
	 *    return [
	 *        ...props,
	 *        newButtons
	 *    ];
	 *
	 *  })
	 */
	setActionsFilter = (
		actionsFilter: (
			viewModel: TransformGroupActionsCallback | Record<any, any>
		) => TransformGroupActionsCallback | Record<any, any>
	) => {
		this.actionsFilter = actionsFilter;
	};

	fetchParamsFilter = (params: TransformGroupsParams | Record<any, any>) =>
		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: (
			fetchParamsFilter: TransformGroupsParams | Record<any, any>
		) => TransformGroupsParams | Record<any, any>
	) => {
		this.fetchParamsFilter = fetchParamsFilter;
	};
}