@selfcommunity/react-ui 0.7.9-alpha.37 → 0.7.9-alpha.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,7 +57,7 @@ function FeedUpdatesWidget(inProps) {
57
57
  const [updates, setUpdates] = (0, react_1.useState)(false);
58
58
  // REFS
59
59
  const updatesSubscription = (0, react_1.useRef)(null);
60
- // Subscripber for pubsub callback
60
+ // Subscriber for pubsub callback
61
61
  const subscriber = (msg, data) => {
62
62
  if (subscriptionChannelUpdatesCallback(msg, data)) {
63
63
  setUpdates(true);
@@ -15,10 +15,12 @@ const ChangeGroupPicture_1 = tslib_1.__importDefault(require("../ChangeGroupPict
15
15
  const ChangeGroupCover_1 = tslib_1.__importDefault(require("../ChangeGroupCover"));
16
16
  const Group_1 = require("../../constants/Group");
17
17
  const GroupInviteButton_1 = tslib_1.__importDefault(require("../GroupInviteButton"));
18
+ const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
18
19
  const types_1 = require("@selfcommunity/types");
19
20
  const Errors_1 = require("../../constants/Errors");
20
21
  const api_services_1 = require("@selfcommunity/api-services");
21
22
  const utils_1 = require("@selfcommunity/utils");
23
+ const PubSub_1 = require("../../constants/PubSub");
22
24
  const messages = (0, react_intl_1.defineMessages)({
23
25
  name: {
24
26
  id: 'ui.groupForm.name.placeholder',
@@ -142,6 +144,22 @@ function GroupForm(inProps) {
142
144
  setError(error);
143
145
  }
144
146
  }
147
+ /**
148
+ * Notify when a group info changed
149
+ * @param data
150
+ */
151
+ function notifyChanges(data) {
152
+ if (data) {
153
+ if (group) {
154
+ // Edit group
155
+ pubsub_js_1.default.publish(`${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.EDIT}`, data);
156
+ }
157
+ else {
158
+ // Create group
159
+ pubsub_js_1.default.publish(`${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.CREATE}`, data);
160
+ }
161
+ }
162
+ }
145
163
  const handleSubmit = () => {
146
164
  setField((prev) => (Object.assign(Object.assign({}, prev), { ['isSubmitting']: true })));
147
165
  const formData = new FormData();
@@ -168,6 +186,7 @@ function GroupForm(inProps) {
168
186
  groupService
169
187
  .then((data) => {
170
188
  onSuccess && onSuccess(data);
189
+ notifyChanges(data);
171
190
  onClose && onClose();
172
191
  setField((prev) => (Object.assign(Object.assign({}, prev), { ['isSubmitting']: false })));
173
192
  })
@@ -18,6 +18,8 @@ const GroupMembersButton_1 = tslib_1.__importDefault(require("../GroupMembersBut
18
18
  const EditGroupButton_1 = tslib_1.__importDefault(require("../EditGroupButton"));
19
19
  const GroupSubscribeButton_1 = tslib_1.__importDefault(require("../GroupSubscribeButton"));
20
20
  const GroupInviteButton_1 = tslib_1.__importDefault(require("../GroupInviteButton"));
21
+ const PubSub_1 = require("../../constants/PubSub");
22
+ const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
21
23
  const classes = {
22
24
  root: `${constants_1.PREFIX}-root`,
23
25
  cover: `${constants_1.PREFIX}-cover`,
@@ -81,6 +83,8 @@ function GroupHeader(inProps) {
81
83
  const scUserContext = (0, react_core_1.useSCUser)();
82
84
  // HOOKS
83
85
  const { scGroup, setSCGroup } = (0, react_core_1.useSCFetchGroup)({ id: groupId, group });
86
+ // REFS
87
+ const updatesSubscription = (0, react_1.useRef)(null);
84
88
  // CONST
85
89
  const isGroupAdmin = (0, react_1.useMemo)(() => { var _a; return scUserContext.user && ((_a = scGroup === null || scGroup === void 0 ? void 0 : scGroup.managed_by) === null || _a === void 0 ? void 0 : _a.id) === scUserContext.user.id; }, [scUserContext.user, (_a = scGroup === null || scGroup === void 0 ? void 0 : scGroup.managed_by) === null || _a === void 0 ? void 0 : _a.id]);
86
90
  /**
@@ -107,6 +111,33 @@ function GroupHeader(inProps) {
107
111
  const handleSubscribe = (group, status) => {
108
112
  setSCGroup(Object.assign(Object.assign({}, group), { subscribers_counter: group.subscribers_counter + (status ? 1 : -1) }));
109
113
  };
114
+ /**
115
+ * Subscriber for pubsub callback
116
+ */
117
+ const onChangeGroupMembersHandler = (0, react_1.useCallback)((msg, data) => {
118
+ if (data && data.group.id === scGroup.id) {
119
+ let _group = Object.assign({}, scGroup);
120
+ if (msg === `${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.ADD_MEMBER}`) {
121
+ _group.subscribers_counter = _group.subscribers_counter + 1;
122
+ }
123
+ else if (msg === `${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.REMOVE_MEMBER}`) {
124
+ _group.subscribers_counter = Math.max(_group.subscribers_counter - 1, 0);
125
+ }
126
+ console.log(_group);
127
+ setSCGroup(_group);
128
+ }
129
+ }, [scGroup, setSCGroup]);
130
+ /**
131
+ * On mount, subscribe to receive groups updates (only edit)
132
+ */
133
+ (0, react_1.useEffect)(() => {
134
+ if (scGroup) {
135
+ updatesSubscription.current = pubsub_js_1.default.subscribe(`${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.MEMBERS}`, onChangeGroupMembersHandler);
136
+ }
137
+ return () => {
138
+ updatesSubscription.current && pubsub_js_1.default.unsubscribe(updatesSubscription.current);
139
+ };
140
+ }, [scGroup]);
110
141
  // RENDER
111
142
  if (!scGroup) {
112
143
  return react_1.default.createElement(Skeleton_1.default, null);
@@ -143,7 +174,7 @@ function GroupHeader(inProps) {
143
174
  isGroupAdmin) && (react_1.default.createElement(material_1.Box, { className: classes.members },
144
175
  react_1.default.createElement(material_1.Typography, { className: classes.membersCounter, component: "div" },
145
176
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupHeader.members", defaultMessage: "ui.groupHeader.members", values: { total: scGroup.subscribers_counter } })),
146
- react_1.default.createElement(GroupMembersButton_1.default, Object.assign({ groupId: scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, group: scGroup, autoHide: !isGroupAdmin }, GroupMembersButtonProps))))),
177
+ react_1.default.createElement(GroupMembersButton_1.default, Object.assign({ key: scGroup.subscribers_counter, groupId: scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, group: scGroup, autoHide: !isGroupAdmin }, GroupMembersButtonProps))))),
147
178
  isGroupAdmin ? (react_1.default.createElement(GroupInviteButton_1.default, { group: scGroup, groupId: scGroup.id })) : (react_1.default.createElement(GroupSubscribeButton_1.default, Object.assign({ group: scGroup, onSubscribe: handleSubscribe }, GroupSubscribeButtonProps))))));
148
179
  }
149
180
  exports.default = GroupHeader;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- const react_1 = tslib_1.__importDefault(require("react"));
4
+ const react_1 = tslib_1.__importStar(require("react"));
5
5
  const styles_1 = require("@mui/material/styles");
6
6
  const material_1 = require("@mui/material");
7
7
  const classnames_1 = tslib_1.__importDefault(require("classnames"));
@@ -10,8 +10,10 @@ const system_1 = require("@mui/system");
10
10
  const constants_1 = require("./constants");
11
11
  const react_intl_1 = require("react-intl");
12
12
  const types_1 = require("@selfcommunity/types");
13
+ const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
13
14
  const react_core_1 = require("@selfcommunity/react-core");
14
15
  const Skeleton_1 = tslib_1.__importDefault(require("./Skeleton"));
16
+ const PubSub_1 = require("../../constants/PubSub");
15
17
  const classes = {
16
18
  root: `${constants_1.PREFIX}-root`,
17
19
  title: `${constants_1.PREFIX}-title`,
@@ -62,9 +64,33 @@ function GroupInfoWidget(inProps) {
62
64
  });
63
65
  const { className, group, groupId, onHeightChange, onStateChange } = props, rest = tslib_1.__rest(props, ["className", "group", "groupId", "onHeightChange", "onStateChange"]);
64
66
  // HOOKS
65
- const { scGroup } = (0, react_core_1.useSCFetchGroup)({ id: groupId, group });
67
+ const { scGroup, setSCGroup } = (0, react_core_1.useSCFetchGroup)({ id: groupId, group });
66
68
  // INTL
67
69
  const intl = (0, react_intl_1.useIntl)();
70
+ // REFS
71
+ const updatesSubscription = (0, react_1.useRef)(null);
72
+ /**
73
+ * Subscriber for pubsub callback
74
+ */
75
+ const onChangeGroupHandler = (0, react_1.useCallback)((_msg, data) => {
76
+ if (data && scGroup.id === data.id) {
77
+ setSCGroup(data);
78
+ }
79
+ }, [scGroup, setSCGroup]);
80
+ /**
81
+ * On mount, subscribe to receive groups updates (only edit)
82
+ */
83
+ (0, react_1.useEffect)(() => {
84
+ if (scGroup) {
85
+ updatesSubscription.current = pubsub_js_1.default.subscribe(`${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.EDIT}`, onChangeGroupHandler);
86
+ }
87
+ return () => {
88
+ updatesSubscription.current && pubsub_js_1.default.unsubscribe(updatesSubscription.current);
89
+ };
90
+ }, [scGroup]);
91
+ /**
92
+ * Loading group
93
+ */
68
94
  if (!scGroup) {
69
95
  return react_1.default.createElement(Skeleton_1.default, null);
70
96
  }
@@ -207,9 +207,12 @@ function GroupMembersWidget(inProps) {
207
207
  openDialog && (react_1.default.createElement(DialogRoot, Object.assign({ className: classes.dialogRoot, title: react_1.default.createElement(react_intl_1.FormattedMessage, { defaultMessage: "ui.groupMembersWidget.dialogTitle", id: "ui.groupMembersWidget.dialogTitle", values: { total: scGroup.subscribers_counter } }), onClose: handleToggleDialogOpen, open: openDialog }, DialogProps),
208
208
  react_1.default.createElement(InfiniteScroll_1.default, { dataLength: state.results.length, next: handleNext, hasMoreNext: Boolean(state.next), loaderNext: react_1.default.createElement(User_1.UserSkeleton, Object.assign({ elevation: 0 }, UserProps)), height: isMobile ? '100%' : 400, endMessage: react_1.default.createElement(material_1.Typography, { className: classes.endMessage },
209
209
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupMembersWidget.noMoreResults", defaultMessage: "ui.groupMembersWidget.noMoreResults" })) },
210
- react_1.default.createElement(List_1.default, null, state.results.map((user) => (react_1.default.createElement(material_1.ListItem, { key: user.id },
211
- react_1.default.createElement(User_1.default, { elevation: 0, actions: isGroupAdmin ? (react_1.default.createElement(GroupSettingsIconButton_1.default, { group: scGroup, user: user, onRemoveSuccess: () => handleRefresh(user.id) })) : (react_1.default.createElement(material_1.Button, null,
212
- react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupSettingsIconButton.item.message", defaultMessage: "ui.groupSettingsIconButton.item.message" }))), user: user, userId: user.id }))))))))),
210
+ react_1.default.createElement(List_1.default, null, state.results.map((user) => {
211
+ var _a;
212
+ return (react_1.default.createElement(material_1.ListItem, { key: user.id },
213
+ react_1.default.createElement(User_1.default, { elevation: 0, actions: isGroupAdmin ? (react_1.default.createElement(GroupSettingsIconButton_1.default, { group: scGroup, user: user, onRemoveSuccess: () => handleRefresh(user.id) })) : ((_a = scUserContext === null || scUserContext === void 0 ? void 0 : scUserContext.user) === null || _a === void 0 ? void 0 : _a.id) !== user.id ? (react_1.default.createElement(material_1.Button, { size: "small", variant: "outlined", component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, user) },
214
+ react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupSettingsIconButton.item.message", defaultMessage: "ui.groupSettingsIconButton.item.message" }))) : null, user: user, userId: user.id })));
215
+ })))))),
213
216
  react_1.default.createElement(material_1.CardActions, { className: classes.actions },
214
217
  react_1.default.createElement(GroupInviteButton_1.default, { groupId: scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, group: scGroup }))));
215
218
  return (react_1.default.createElement(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest), content));
@@ -11,6 +11,8 @@ const system_1 = require("@mui/system");
11
11
  const react_core_1 = require("@selfcommunity/react-core");
12
12
  const ConfirmDialog_1 = tslib_1.__importDefault(require("../../shared/ConfirmDialog/ConfirmDialog"));
13
13
  const api_services_1 = require("@selfcommunity/api-services");
14
+ const PubSub_1 = require("../../constants/PubSub");
15
+ const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
14
16
  const PREFIX = 'SCGroupSettingsIconButton';
15
17
  const classes = {
16
18
  root: `${PREFIX}-root`,
@@ -84,12 +86,23 @@ function GroupSettingsIconButton(inProps) {
84
86
  setOpenConfirmDialog(false);
85
87
  setAnchorEl(null);
86
88
  };
89
+ /**
90
+ * Notify UI when a user is removed from a group
91
+ * @param group
92
+ * @param user
93
+ */
94
+ function notifyChanges(group, user) {
95
+ if (group && user) {
96
+ pubsub_js_1.default.publish(`${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.REMOVE_MEMBER}`, { group, user });
97
+ }
98
+ }
87
99
  /**
88
100
  * Handles thread deletion
89
101
  */
90
102
  function handleRemoveUser() {
91
103
  api_services_1.GroupService.removeUserFromGroup(group.id, user.id)
92
104
  .then(() => {
105
+ notifyChanges(group, user);
93
106
  onRemoveSuccess && onRemoveSuccess();
94
107
  handleCloseDialog();
95
108
  })
@@ -11,6 +11,8 @@ const react_intl_1 = require("react-intl");
11
11
  const classnames_1 = tslib_1.__importDefault(require("classnames"));
12
12
  const system_1 = require("@mui/system");
13
13
  const Errors_1 = require("../../constants/Errors");
14
+ const PubSub_1 = require("../../constants/PubSub");
15
+ const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
14
16
  const PREFIX = 'SCGroupSubscribeButton';
15
17
  const classes = {
16
18
  root: `${PREFIX}-root`
@@ -73,14 +75,25 @@ function GroupSubscribeButton(inProps) {
73
75
  setStatus(scGroupsManager.subscriptionStatus(scGroup));
74
76
  }
75
77
  }, [authUserId, scGroupsManager.subscriptionStatus]);
76
- const subscribe = (userId) => {
78
+ /**
79
+ * Notify UI when a member is added to a group
80
+ * @param group
81
+ * @param user
82
+ */
83
+ function notifyChanges(group, user) {
84
+ if (group && user) {
85
+ pubsub_js_1.default.publish(`${PubSub_1.SCTopicType.GROUP}.${PubSub_1.SCEventType.ADD_MEMBER}`, { group, user });
86
+ }
87
+ }
88
+ const subscribe = (user) => {
77
89
  scGroupsManager
78
- .subscribe(scGroup, userId)
90
+ .subscribe(scGroup, user === null || user === void 0 ? void 0 : user.id)
79
91
  .then(() => {
80
- onSubscribe &&
81
- onSubscribe(scGroup, scGroup.privacy === types_1.SCGroupPrivacyType.PRIVATE && scGroup.subscription_status !== types_1.SCGroupSubscriptionStatusType.INVITED
82
- ? types_1.SCGroupSubscriptionStatusType.REQUESTED
83
- : types_1.SCGroupSubscriptionStatusType.SUBSCRIBED);
92
+ const _status = scGroup.privacy === types_1.SCGroupPrivacyType.PRIVATE && scGroup.subscription_status !== types_1.SCGroupSubscriptionStatusType.INVITED
93
+ ? types_1.SCGroupSubscriptionStatusType.REQUESTED
94
+ : types_1.SCGroupSubscriptionStatusType.SUBSCRIBED;
95
+ notifyChanges(scGroup, user);
96
+ onSubscribe && onSubscribe(scGroup, _status);
84
97
  })
85
98
  .catch((e) => {
86
99
  utils_1.Logger.error(Errors_1.SCOPE_SC_UI, e);
@@ -101,7 +114,7 @@ function GroupSubscribeButton(inProps) {
101
114
  scContext.settings.handleAnonymousAction();
102
115
  }
103
116
  else {
104
- status === types_1.SCGroupSubscriptionStatusType.SUBSCRIBED && !(user === null || user === void 0 ? void 0 : user.id) ? unsubscribe() : (user === null || user === void 0 ? void 0 : user.id) ? subscribe(user === null || user === void 0 ? void 0 : user.id) : subscribe();
117
+ status === types_1.SCGroupSubscriptionStatusType.SUBSCRIBED && !(user === null || user === void 0 ? void 0 : user.id) ? unsubscribe() : (user === null || user === void 0 ? void 0 : user.id) ? subscribe(user) : subscribe();
105
118
  }
106
119
  };
107
120
  /**
@@ -10,7 +10,7 @@ export interface GroupsProps {
10
10
  /**
11
11
  * Endpoint to call
12
12
  */
13
- endpoint: EndpointType;
13
+ endpoint?: EndpointType;
14
14
  /**
15
15
  * Props to spread to single group object
16
16
  * @default {variant: 'outlined', ButtonBaseProps: {disableRipple: 'true'}}
@@ -124,11 +124,11 @@ function Groups(inProps) {
124
124
  if (!contentAvailability && !scUserContext.user) {
125
125
  return react_1.default.createElement(HiddenPlaceholder_1.default, null);
126
126
  }
127
- const content = (react_1.default.createElement(react_1.default.Fragment, null, loading ? (react_1.default.createElement(Skeleton_1.default, null)) : (react_1.default.createElement(material_1.Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, !groups.length ? (react_1.default.createElement(material_1.Box, { className: classes.noResults },
127
+ const content = (react_1.default.createElement(react_1.default.Fragment, null, loading ? (react_1.default.createElement(Skeleton_1.default, null)) : (react_1.default.createElement(react_1.default.Fragment, null, !groups.length ? (react_1.default.createElement(material_1.Box, { className: classes.noResults },
128
128
  react_1.default.createElement(material_1.Typography, { variant: "h4" },
129
129
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.title", defaultMessage: "ui.groups.noGroups.title" })),
130
130
  react_1.default.createElement(material_1.Typography, { variant: "body1" },
131
- react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (react_1.default.createElement(react_1.default.Fragment, null, groups.map((group) => (react_1.default.createElement(material_1.Grid, { item: true, xs: 12, sm: 8, md: 6, key: group.id, className: classes.item },
131
+ react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (react_1.default.createElement(material_1.Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, groups.map((group) => (react_1.default.createElement(material_1.Grid, { item: true, xs: 12, sm: 8, md: 6, key: group.id, className: classes.item },
132
132
  react_1.default.createElement(Group_1.default, Object.assign({ group: group, groupId: group.id, groupSubscribeButtonProps: { onSubscribe: handleSubscribe } }, GroupComponentProps)))))))))));
133
133
  return (react_1.default.createElement(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest), content));
134
134
  }
@@ -0,0 +1,27 @@
1
+ import { SCUserType, SCGroupType } from '@selfcommunity/types';
2
+ /**
3
+ * Define topics for pubsub
4
+ */
5
+ export declare enum SCTopicType {
6
+ GROUP = "group"
7
+ }
8
+ /**
9
+ * Event types
10
+ */
11
+ export declare enum SCEventType {
12
+ CREATE = "create",
13
+ EDIT = "edit",
14
+ MEMBERS = "members",
15
+ ADD_MEMBER = "members.add_member",
16
+ REMOVE_MEMBER = "members.remove_member"
17
+ }
18
+ /**
19
+ * Event structure
20
+ */
21
+ export interface SCGroupChangeEventType {
22
+ group: SCGroupType;
23
+ }
24
+ export interface SCGroupMembersEventType {
25
+ group: SCGroupType;
26
+ user?: SCUserType;
27
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SCEventType = exports.SCTopicType = void 0;
4
+ /**
5
+ * Define topics for pubsub
6
+ */
7
+ var SCTopicType;
8
+ (function (SCTopicType) {
9
+ SCTopicType["GROUP"] = "group";
10
+ })(SCTopicType = exports.SCTopicType || (exports.SCTopicType = {}));
11
+ /**
12
+ * Event types
13
+ */
14
+ var SCEventType;
15
+ (function (SCEventType) {
16
+ SCEventType["CREATE"] = "create";
17
+ SCEventType["EDIT"] = "edit";
18
+ SCEventType["MEMBERS"] = "members";
19
+ SCEventType["ADD_MEMBER"] = "members.add_member";
20
+ SCEventType["REMOVE_MEMBER"] = "members.remove_member";
21
+ })(SCEventType = exports.SCEventType || (exports.SCEventType = {}));
@@ -55,7 +55,7 @@ export default function FeedUpdatesWidget(inProps) {
55
55
  const [updates, setUpdates] = useState(false);
56
56
  // REFS
57
57
  const updatesSubscription = useRef(null);
58
- // Subscripber for pubsub callback
58
+ // Subscriber for pubsub callback
59
59
  const subscriber = (msg, data) => {
60
60
  if (subscriptionChannelUpdatesCallback(msg, data)) {
61
61
  setUpdates(true);
@@ -13,10 +13,12 @@ import ChangeGroupPicture from '../ChangeGroupPicture';
13
13
  import ChangeGroupCover from '../ChangeGroupCover';
14
14
  import { GROUP_DESCRIPTION_MAX_LENGTH, GROUP_TITLE_MAX_LENGTH } from '../../constants/Group';
15
15
  import GroupInviteButton from '../GroupInviteButton';
16
+ import PubSub from 'pubsub-js';
16
17
  import { SCGroupPrivacyType } from '@selfcommunity/types';
17
18
  import { SCOPE_SC_UI } from '../../constants/Errors';
18
19
  import { formatHttpErrorCode, GroupService } from '@selfcommunity/api-services';
19
20
  import { Logger } from '@selfcommunity/utils';
21
+ import { SCEventType, SCTopicType } from '../../constants/PubSub';
20
22
  const messages = defineMessages({
21
23
  name: {
22
24
  id: 'ui.groupForm.name.placeholder',
@@ -140,6 +142,22 @@ export default function GroupForm(inProps) {
140
142
  setError(error);
141
143
  }
142
144
  }
145
+ /**
146
+ * Notify when a group info changed
147
+ * @param data
148
+ */
149
+ function notifyChanges(data) {
150
+ if (data) {
151
+ if (group) {
152
+ // Edit group
153
+ PubSub.publish(`${SCTopicType.GROUP}.${SCEventType.EDIT}`, data);
154
+ }
155
+ else {
156
+ // Create group
157
+ PubSub.publish(`${SCTopicType.GROUP}.${SCEventType.CREATE}`, data);
158
+ }
159
+ }
160
+ }
143
161
  const handleSubmit = () => {
144
162
  setField((prev) => (Object.assign(Object.assign({}, prev), { ['isSubmitting']: true })));
145
163
  const formData = new FormData();
@@ -166,6 +184,7 @@ export default function GroupForm(inProps) {
166
184
  groupService
167
185
  .then((data) => {
168
186
  onSuccess && onSuccess(data);
187
+ notifyChanges(data);
169
188
  onClose && onClose();
170
189
  setField((prev) => (Object.assign(Object.assign({}, prev), { ['isSubmitting']: false })));
171
190
  })
@@ -1,5 +1,5 @@
1
1
  import { __rest } from "tslib";
2
- import React, { useMemo } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
3
3
  import { styled } from '@mui/material/styles';
4
4
  import { Avatar, Box, Icon, Paper, Typography } from '@mui/material';
5
5
  import { SCGroupPrivacyType, SCGroupSubscriptionStatusType } from '@selfcommunity/types';
@@ -16,6 +16,8 @@ import GroupMembersButton from '../GroupMembersButton';
16
16
  import EditGroupButton from '../EditGroupButton';
17
17
  import GroupSubscribeButton from '../GroupSubscribeButton';
18
18
  import GroupInviteButton from '../GroupInviteButton';
19
+ import { SCEventType, SCTopicType } from '../../constants/PubSub';
20
+ import PubSub from 'pubsub-js';
19
21
  const classes = {
20
22
  root: `${PREFIX}-root`,
21
23
  cover: `${PREFIX}-cover`,
@@ -79,6 +81,8 @@ export default function GroupHeader(inProps) {
79
81
  const scUserContext = useSCUser();
80
82
  // HOOKS
81
83
  const { scGroup, setSCGroup } = useSCFetchGroup({ id: groupId, group });
84
+ // REFS
85
+ const updatesSubscription = useRef(null);
82
86
  // CONST
83
87
  const isGroupAdmin = useMemo(() => { var _a; return scUserContext.user && ((_a = scGroup === null || scGroup === void 0 ? void 0 : scGroup.managed_by) === null || _a === void 0 ? void 0 : _a.id) === scUserContext.user.id; }, [scUserContext.user, (_a = scGroup === null || scGroup === void 0 ? void 0 : scGroup.managed_by) === null || _a === void 0 ? void 0 : _a.id]);
84
88
  /**
@@ -105,6 +109,33 @@ export default function GroupHeader(inProps) {
105
109
  const handleSubscribe = (group, status) => {
106
110
  setSCGroup(Object.assign(Object.assign({}, group), { subscribers_counter: group.subscribers_counter + (status ? 1 : -1) }));
107
111
  };
112
+ /**
113
+ * Subscriber for pubsub callback
114
+ */
115
+ const onChangeGroupMembersHandler = useCallback((msg, data) => {
116
+ if (data && data.group.id === scGroup.id) {
117
+ let _group = Object.assign({}, scGroup);
118
+ if (msg === `${SCTopicType.GROUP}.${SCEventType.ADD_MEMBER}`) {
119
+ _group.subscribers_counter = _group.subscribers_counter + 1;
120
+ }
121
+ else if (msg === `${SCTopicType.GROUP}.${SCEventType.REMOVE_MEMBER}`) {
122
+ _group.subscribers_counter = Math.max(_group.subscribers_counter - 1, 0);
123
+ }
124
+ console.log(_group);
125
+ setSCGroup(_group);
126
+ }
127
+ }, [scGroup, setSCGroup]);
128
+ /**
129
+ * On mount, subscribe to receive groups updates (only edit)
130
+ */
131
+ useEffect(() => {
132
+ if (scGroup) {
133
+ updatesSubscription.current = PubSub.subscribe(`${SCTopicType.GROUP}.${SCEventType.MEMBERS}`, onChangeGroupMembersHandler);
134
+ }
135
+ return () => {
136
+ updatesSubscription.current && PubSub.unsubscribe(updatesSubscription.current);
137
+ };
138
+ }, [scGroup]);
108
139
  // RENDER
109
140
  if (!scGroup) {
110
141
  return React.createElement(GroupHeaderSkeleton, null);
@@ -141,6 +172,6 @@ export default function GroupHeader(inProps) {
141
172
  isGroupAdmin) && (React.createElement(Box, { className: classes.members },
142
173
  React.createElement(Typography, { className: classes.membersCounter, component: "div" },
143
174
  React.createElement(FormattedMessage, { id: "ui.groupHeader.members", defaultMessage: "ui.groupHeader.members", values: { total: scGroup.subscribers_counter } })),
144
- React.createElement(GroupMembersButton, Object.assign({ groupId: scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, group: scGroup, autoHide: !isGroupAdmin }, GroupMembersButtonProps))))),
175
+ React.createElement(GroupMembersButton, Object.assign({ key: scGroup.subscribers_counter, groupId: scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, group: scGroup, autoHide: !isGroupAdmin }, GroupMembersButtonProps))))),
145
176
  isGroupAdmin ? (React.createElement(GroupInviteButton, { group: scGroup, groupId: scGroup.id })) : (React.createElement(GroupSubscribeButton, Object.assign({ group: scGroup, onSubscribe: handleSubscribe }, GroupSubscribeButtonProps))))));
146
177
  }
@@ -1,5 +1,5 @@
1
1
  import { __rest } from "tslib";
2
- import React from 'react';
2
+ import React, { useCallback, useEffect, useRef } from 'react';
3
3
  import { styled } from '@mui/material/styles';
4
4
  import { CardContent, Icon, Typography } from '@mui/material';
5
5
  import classNames from 'classnames';
@@ -8,8 +8,10 @@ import { useThemeProps } from '@mui/system';
8
8
  import { PREFIX } from './constants';
9
9
  import { FormattedMessage, useIntl } from 'react-intl';
10
10
  import { SCGroupPrivacyType } from '@selfcommunity/types';
11
+ import PubSub from 'pubsub-js';
11
12
  import { useSCFetchGroup } from '@selfcommunity/react-core';
12
13
  import GroupInfoWidgetSkeleton from './Skeleton';
14
+ import { SCEventType, SCTopicType } from '../../constants/PubSub';
13
15
  const classes = {
14
16
  root: `${PREFIX}-root`,
15
17
  title: `${PREFIX}-title`,
@@ -60,9 +62,33 @@ export default function GroupInfoWidget(inProps) {
60
62
  });
61
63
  const { className, group, groupId, onHeightChange, onStateChange } = props, rest = __rest(props, ["className", "group", "groupId", "onHeightChange", "onStateChange"]);
62
64
  // HOOKS
63
- const { scGroup } = useSCFetchGroup({ id: groupId, group });
65
+ const { scGroup, setSCGroup } = useSCFetchGroup({ id: groupId, group });
64
66
  // INTL
65
67
  const intl = useIntl();
68
+ // REFS
69
+ const updatesSubscription = useRef(null);
70
+ /**
71
+ * Subscriber for pubsub callback
72
+ */
73
+ const onChangeGroupHandler = useCallback((_msg, data) => {
74
+ if (data && scGroup.id === data.id) {
75
+ setSCGroup(data);
76
+ }
77
+ }, [scGroup, setSCGroup]);
78
+ /**
79
+ * On mount, subscribe to receive groups updates (only edit)
80
+ */
81
+ useEffect(() => {
82
+ if (scGroup) {
83
+ updatesSubscription.current = PubSub.subscribe(`${SCTopicType.GROUP}.${SCEventType.EDIT}`, onChangeGroupHandler);
84
+ }
85
+ return () => {
86
+ updatesSubscription.current && PubSub.unsubscribe(updatesSubscription.current);
87
+ };
88
+ }, [scGroup]);
89
+ /**
90
+ * Loading group
91
+ */
66
92
  if (!scGroup) {
67
93
  return React.createElement(GroupInfoWidgetSkeleton, null);
68
94
  }
@@ -205,9 +205,12 @@ export default function GroupMembersWidget(inProps) {
205
205
  openDialog && (React.createElement(DialogRoot, Object.assign({ className: classes.dialogRoot, title: React.createElement(FormattedMessage, { defaultMessage: "ui.groupMembersWidget.dialogTitle", id: "ui.groupMembersWidget.dialogTitle", values: { total: scGroup.subscribers_counter } }), onClose: handleToggleDialogOpen, open: openDialog }, DialogProps),
206
206
  React.createElement(InfiniteScroll, { dataLength: state.results.length, next: handleNext, hasMoreNext: Boolean(state.next), loaderNext: React.createElement(UserSkeleton, Object.assign({ elevation: 0 }, UserProps)), height: isMobile ? '100%' : 400, endMessage: React.createElement(Typography, { className: classes.endMessage },
207
207
  React.createElement(FormattedMessage, { id: "ui.groupMembersWidget.noMoreResults", defaultMessage: "ui.groupMembersWidget.noMoreResults" })) },
208
- React.createElement(List, null, state.results.map((user) => (React.createElement(ListItem, { key: user.id },
209
- React.createElement(User, { elevation: 0, actions: isGroupAdmin ? (React.createElement(GroupSettingsIconButton, { group: scGroup, user: user, onRemoveSuccess: () => handleRefresh(user.id) })) : (React.createElement(Button, null,
210
- React.createElement(FormattedMessage, { id: "ui.groupSettingsIconButton.item.message", defaultMessage: "ui.groupSettingsIconButton.item.message" }))), user: user, userId: user.id }))))))))),
208
+ React.createElement(List, null, state.results.map((user) => {
209
+ var _a;
210
+ return (React.createElement(ListItem, { key: user.id },
211
+ React.createElement(User, { elevation: 0, actions: isGroupAdmin ? (React.createElement(GroupSettingsIconButton, { group: scGroup, user: user, onRemoveSuccess: () => handleRefresh(user.id) })) : ((_a = scUserContext === null || scUserContext === void 0 ? void 0 : scUserContext.user) === null || _a === void 0 ? void 0 : _a.id) !== user.id ? (React.createElement(Button, { size: "small", variant: "outlined", component: Link, to: scRoutingContext.url(SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, user) },
212
+ React.createElement(FormattedMessage, { id: "ui.groupSettingsIconButton.item.message", defaultMessage: "ui.groupSettingsIconButton.item.message" }))) : null, user: user, userId: user.id })));
213
+ })))))),
211
214
  React.createElement(CardActions, { className: classes.actions },
212
215
  React.createElement(GroupInviteButton, { groupId: scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, group: scGroup }))));
213
216
  return (React.createElement(Root, Object.assign({ className: classNames(classes.root, className) }, rest), content));
@@ -9,6 +9,8 @@ import { useThemeProps } from '@mui/system';
9
9
  import { Link, SCRoutes, useSCRouting, useSCUser } from '@selfcommunity/react-core';
10
10
  import ConfirmDialog from '../../shared/ConfirmDialog/ConfirmDialog';
11
11
  import { GroupService } from '@selfcommunity/api-services';
12
+ import { SCEventType, SCTopicType } from '../../constants/PubSub';
13
+ import PubSub from 'pubsub-js';
12
14
  const PREFIX = 'SCGroupSettingsIconButton';
13
15
  const classes = {
14
16
  root: `${PREFIX}-root`,
@@ -82,12 +84,23 @@ export default function GroupSettingsIconButton(inProps) {
82
84
  setOpenConfirmDialog(false);
83
85
  setAnchorEl(null);
84
86
  };
87
+ /**
88
+ * Notify UI when a user is removed from a group
89
+ * @param group
90
+ * @param user
91
+ */
92
+ function notifyChanges(group, user) {
93
+ if (group && user) {
94
+ PubSub.publish(`${SCTopicType.GROUP}.${SCEventType.REMOVE_MEMBER}`, { group, user });
95
+ }
96
+ }
85
97
  /**
86
98
  * Handles thread deletion
87
99
  */
88
100
  function handleRemoveUser() {
89
101
  GroupService.removeUserFromGroup(group.id, user.id)
90
102
  .then(() => {
103
+ notifyChanges(group, user);
91
104
  onRemoveSuccess && onRemoveSuccess();
92
105
  handleCloseDialog();
93
106
  })
@@ -9,6 +9,8 @@ import { FormattedMessage } from 'react-intl';
9
9
  import classNames from 'classnames';
10
10
  import { useThemeProps } from '@mui/system';
11
11
  import { SCOPE_SC_UI } from '../../constants/Errors';
12
+ import { SCEventType, SCTopicType } from '../../constants/PubSub';
13
+ import PubSub from 'pubsub-js';
12
14
  const PREFIX = 'SCGroupSubscribeButton';
13
15
  const classes = {
14
16
  root: `${PREFIX}-root`
@@ -71,14 +73,25 @@ export default function GroupSubscribeButton(inProps) {
71
73
  setStatus(scGroupsManager.subscriptionStatus(scGroup));
72
74
  }
73
75
  }, [authUserId, scGroupsManager.subscriptionStatus]);
74
- const subscribe = (userId) => {
76
+ /**
77
+ * Notify UI when a member is added to a group
78
+ * @param group
79
+ * @param user
80
+ */
81
+ function notifyChanges(group, user) {
82
+ if (group && user) {
83
+ PubSub.publish(`${SCTopicType.GROUP}.${SCEventType.ADD_MEMBER}`, { group, user });
84
+ }
85
+ }
86
+ const subscribe = (user) => {
75
87
  scGroupsManager
76
- .subscribe(scGroup, userId)
88
+ .subscribe(scGroup, user === null || user === void 0 ? void 0 : user.id)
77
89
  .then(() => {
78
- onSubscribe &&
79
- onSubscribe(scGroup, scGroup.privacy === SCGroupPrivacyType.PRIVATE && scGroup.subscription_status !== SCGroupSubscriptionStatusType.INVITED
80
- ? SCGroupSubscriptionStatusType.REQUESTED
81
- : SCGroupSubscriptionStatusType.SUBSCRIBED);
90
+ const _status = scGroup.privacy === SCGroupPrivacyType.PRIVATE && scGroup.subscription_status !== SCGroupSubscriptionStatusType.INVITED
91
+ ? SCGroupSubscriptionStatusType.REQUESTED
92
+ : SCGroupSubscriptionStatusType.SUBSCRIBED;
93
+ notifyChanges(scGroup, user);
94
+ onSubscribe && onSubscribe(scGroup, _status);
82
95
  })
83
96
  .catch((e) => {
84
97
  Logger.error(SCOPE_SC_UI, e);
@@ -99,7 +112,7 @@ export default function GroupSubscribeButton(inProps) {
99
112
  scContext.settings.handleAnonymousAction();
100
113
  }
101
114
  else {
102
- status === SCGroupSubscriptionStatusType.SUBSCRIBED && !(user === null || user === void 0 ? void 0 : user.id) ? unsubscribe() : (user === null || user === void 0 ? void 0 : user.id) ? subscribe(user === null || user === void 0 ? void 0 : user.id) : subscribe();
115
+ status === SCGroupSubscriptionStatusType.SUBSCRIBED && !(user === null || user === void 0 ? void 0 : user.id) ? unsubscribe() : (user === null || user === void 0 ? void 0 : user.id) ? subscribe(user) : subscribe();
103
116
  }
104
117
  };
105
118
  /**
@@ -10,7 +10,7 @@ export interface GroupsProps {
10
10
  /**
11
11
  * Endpoint to call
12
12
  */
13
- endpoint: EndpointType;
13
+ endpoint?: EndpointType;
14
14
  /**
15
15
  * Props to spread to single group object
16
16
  * @default {variant: 'outlined', ButtonBaseProps: {disableRipple: 'true'}}