@selfcommunity/react-ui 0.7.9-alpha.53 → 0.7.9-alpha.55

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.
@@ -73,7 +73,7 @@ function BottomNavigation(inProps) {
73
73
  preferences[react_core_1.SCPreferences.CONFIGURATIONS_EXPLORE_STREAM_ENABLED].value ? (react_1.default.createElement(material_1.BottomNavigationAction, { key: "explore", className: classes.action, component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.EXPLORE_ROUTE_NAME, {}), value: scRoutingContext.url(react_core_1.SCRoutes.EXPLORE_ROUTE_NAME, {}), icon: react_1.default.createElement(material_1.Icon, null, "explore") })) : null,
74
74
  react_1.default.createElement(material_1.BottomNavigationAction, { key: "composer", className: (0, classnames_1.default)(classes.composer, classes.action), component: ComposerIconButton_1.default, disableRipple: true }),
75
75
  groupsEnabled && scUserContext.user ? (react_1.default.createElement(material_1.BottomNavigationAction, { key: "groups", className: classes.action, component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.GROUPS_ROUTE_NAME, {}), value: scRoutingContext.url(react_core_1.SCRoutes.GROUPS_ROUTE_NAME, {}), icon: react_1.default.createElement(material_1.Icon, null, "groups") })) : null,
76
- scUserContext.user ? (react_1.default.createElement(material_1.BottomNavigationAction, { key: "notifications", className: classes.action, component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), value: scRoutingContext.url(react_core_1.SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), icon: react_1.default.createElement(material_1.Badge, { badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" },
76
+ scUserContext.user && !groupsEnabled ? (react_1.default.createElement(material_1.BottomNavigationAction, { key: "notifications", className: classes.action, component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), value: scRoutingContext.url(react_core_1.SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), icon: react_1.default.createElement(material_1.Badge, { badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" },
77
77
  react_1.default.createElement(material_1.Icon, null, "notifications_active")) })) : null,
78
78
  privateMessagingEnabled && scUserContext.user ? (react_1.default.createElement(material_1.BottomNavigationAction, { key: "messages", className: classes.action, component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, {}), value: scRoutingContext.url(react_core_1.SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, {}), icon: react_1.default.createElement(material_1.Badge, { badgeContent: 0, color: "secondary" },
79
79
  react_1.default.createElement(material_1.Icon, null, "email")) })) : null
@@ -92,6 +92,7 @@ const Root = (0, styles_1.styled)(BaseDialog_1.default, {
92
92
  * @param inProps
93
93
  */
94
94
  function GroupForm(inProps) {
95
+ var _a;
95
96
  //PROPS
96
97
  const props = (0, system_1.useThemeProps)({
97
98
  props: inProps,
@@ -227,7 +228,7 @@ function GroupForm(inProps) {
227
228
  endAdornment: react_1.default.createElement(material_1.Typography, { variant: "body2" }, Group_1.GROUP_TITLE_MAX_LENGTH - field.name.length)
228
229
  } }),
229
230
  react_1.default.createElement(material_1.TextField, { multiline: true, className: classes.description, placeholder: `${intl.formatMessage(messages.description)}`, margin: "normal", value: field.description, name: "description", onChange: handleChange, InputProps: {
230
- endAdornment: react_1.default.createElement(material_1.Typography, { variant: "body2" }, Group_1.GROUP_DESCRIPTION_MAX_LENGTH - field.description.length)
231
+ endAdornment: (react_1.default.createElement(material_1.Typography, { variant: "body2" }, ((_a = field.description) === null || _a === void 0 ? void 0 : _a.length) ? Group_1.GROUP_DESCRIPTION_MAX_LENGTH - field.description.length : Group_1.GROUP_DESCRIPTION_MAX_LENGTH))
231
232
  } }),
232
233
  react_1.default.createElement(material_1.Box, { className: classes.privacySection },
233
234
  react_1.default.createElement(material_1.Typography, { variant: "h4" },
@@ -88,6 +88,7 @@ function GroupInviteButton(inProps) {
88
88
  const [open, setOpen] = (0, react_1.useState)(false);
89
89
  const [isSending, setIsSending] = (0, react_1.useState)(false);
90
90
  const [value, setValue] = (0, react_1.useState)('');
91
+ const [suggested, setSuggested] = (0, react_1.useState)([]);
91
92
  const [list, setList] = (0, react_1.useState)([]);
92
93
  const [loading, setLoading] = (0, react_1.useState)(false);
93
94
  const [invited, setInvited] = (0, react_1.useState)([]);
@@ -126,17 +127,22 @@ function GroupInviteButton(inProps) {
126
127
  const intl = (0, react_intl_1.useIntl)();
127
128
  function fetchResults() {
128
129
  setLoading(true);
129
- let service;
130
- if (scGroup) {
131
- service = api_services_1.GroupService.getGroupSuggestedUsers(scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value);
132
- }
133
- else {
134
- service = api_services_1.GroupService.getGroupsSuggestedUsers(value);
135
- }
136
- service
130
+ api_services_1.GroupService.getGroupSuggestedUsers(scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value)
137
131
  .then((data) => {
138
132
  setLoading(false);
139
- setList(data.results);
133
+ setSuggested(data.results);
134
+ })
135
+ .catch((error) => {
136
+ setLoading(false);
137
+ utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
138
+ });
139
+ }
140
+ function fetchGeneralResults() {
141
+ setLoading(true);
142
+ api_services_1.GroupService.getGroupsSuggestedUsers(value)
143
+ .then((data) => {
144
+ setLoading(false);
145
+ setSuggested(data.results);
140
146
  })
141
147
  .catch((error) => {
142
148
  setLoading(false);
@@ -144,8 +150,30 @@ function GroupInviteButton(inProps) {
144
150
  });
145
151
  }
146
152
  (0, react_1.useEffect)(() => {
147
- fetchResults();
148
- }, [scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value]);
153
+ if (scGroup === null || scGroup === void 0 ? void 0 : scGroup.id) {
154
+ api_services_1.GroupService.getGroupSuggestedUsers(scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value).then((data) => {
155
+ setLoading(false);
156
+ setList(data.results);
157
+ });
158
+ }
159
+ else {
160
+ api_services_1.GroupService.getGroupsSuggestedUsers(value).then((data) => {
161
+ setLoading(false);
162
+ setList(data.results);
163
+ });
164
+ }
165
+ }, [scGroup === null || scGroup === void 0 ? void 0 : scGroup.id]);
166
+ /**
167
+ * If a value is entered in new message field, it fetches user suggested
168
+ */
169
+ (0, react_1.useEffect)(() => {
170
+ if (scGroup) {
171
+ fetchResults();
172
+ }
173
+ else {
174
+ fetchGeneralResults();
175
+ }
176
+ }, [value, scGroup]);
149
177
  /**
150
178
  * Handles dialog close
151
179
  */
@@ -182,7 +210,7 @@ function GroupInviteButton(inProps) {
182
210
  switch (reason) {
183
211
  case 'input':
184
212
  setValue(value);
185
- !value && setList([]);
213
+ !value && setSuggested([]);
186
214
  break;
187
215
  case 'reset':
188
216
  setValue(value);
@@ -212,6 +240,13 @@ function GroupInviteButton(inProps) {
212
240
  setInvited(invited.filter((v) => v !== option));
213
241
  setList((prev) => [...prev, option]);
214
242
  };
243
+ const filterOptions = (options, { inputValue }) => {
244
+ return options.filter((option) => {
245
+ const usernameMatch = option.username.toLowerCase().includes(inputValue.toLowerCase());
246
+ const nameMatch = option.real_name.toLowerCase().includes(inputValue.toLowerCase());
247
+ return usernameMatch || nameMatch;
248
+ });
249
+ };
215
250
  /**
216
251
  * If in group edit mode and logged-in user is not also the group manager, the component is hidden.
217
252
  // */
@@ -232,7 +267,7 @@ function GroupInviteButton(inProps) {
232
267
  react_1.default.createElement(lab_1.LoadingButton, { size: "small", color: "secondary", variant: "contained", onClick: handleSendInvitations, loading: isSending, disabled: !invited.length },
233
268
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupInviteButton.dialog.button.end", defaultMessage: "ui.groupInviteButton.dialog.button.end" }))) },
234
269
  react_1.default.createElement(material_1.Box, { className: classes.dialogContent },
235
- react_1.default.createElement(Autocomplete_1.default, { className: classes.autocomplete, loading: loading, size: "small", multiple: true, freeSolo: true, disableClearable: true, options: list, onChange: handleChange, onInputChange: handleInputChange, inputValue: value, value: invited, getOptionLabel: (option) => (option ? option.username : '...'), isOptionEqualToValue: (option, value) => (option ? value.id === option.id : false), renderTags: () => null, renderOption: (props, option) => (react_1.default.createElement(material_1.Box, Object.assign({ component: "li" }, props),
270
+ react_1.default.createElement(Autocomplete_1.default, { className: classes.autocomplete, loading: loading, size: "small", multiple: true, freeSolo: true, disableClearable: true, options: suggested, onChange: handleChange, onInputChange: handleInputChange, inputValue: value, filterOptions: filterOptions, value: invited, getOptionLabel: (option) => (option ? option.username : '...'), isOptionEqualToValue: (option, value) => (option ? value.id === option.id : false), renderTags: () => null, renderOption: (props, option) => (react_1.default.createElement(material_1.Box, Object.assign({ component: "li" }, props),
236
271
  react_1.default.createElement(material_1.Avatar, { alt: option.username, src: option.avatar }),
237
272
  react_1.default.createElement(material_1.Typography, { ml: 1 }, option.username))), renderInput: (params) => (react_1.default.createElement(material_1.TextField, Object.assign({}, params, { variant: "outlined", placeholder: `${intl.formatMessage(messages.placeholder)}`, InputProps: Object.assign(Object.assign({}, params.InputProps), { className: classes.input, startAdornment: (react_1.default.createElement(react_1.default.Fragment, null,
238
273
  react_1.default.createElement(material_1.InputAdornment, { position: "start" },
@@ -14,7 +14,7 @@ const Errors_1 = require("../../constants/Errors");
14
14
  const system_1 = require("@mui/system");
15
15
  const HiddenPlaceholder_1 = tslib_1.__importDefault(require("../../shared/HiddenPlaceholder"));
16
16
  const constants_1 = require("./constants");
17
- const Group_1 = tslib_1.__importDefault(require("../Group"));
17
+ const Group_1 = tslib_1.__importStar(require("../Group"));
18
18
  const Pagination_1 = require("../../constants/Pagination");
19
19
  const InfiniteScroll_1 = tslib_1.__importDefault(require("../../shared/InfiniteScroll"));
20
20
  const classes = {
@@ -73,6 +73,8 @@ function Groups(inProps) {
73
73
  const [loading, setLoading] = (0, react_1.useState)(true);
74
74
  const [next, setNext] = (0, react_1.useState)(null);
75
75
  const [search, setSearch] = (0, react_1.useState)('');
76
+ const theme = (0, material_1.useTheme)();
77
+ const isMobile = (0, material_1.useMediaQuery)(theme.breakpoints.down('md'));
76
78
  // CONTEXT
77
79
  const scUserContext = (0, react_core_1.useSCUser)();
78
80
  const scPreferencesContext = (0, react_core_1.useSCPreferences)();
@@ -167,7 +169,7 @@ function Groups(inProps) {
167
169
  react_1.default.createElement(material_1.Typography, { variant: "h4" },
168
170
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.title", defaultMessage: "ui.groups.noGroups.title" })),
169
171
  react_1.default.createElement(material_1.Typography, { variant: "body1" },
170
- react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (react_1.default.createElement(InfiniteScroll_1.default, { dataLength: groups.length, next: handleNext, hasMoreNext: Boolean(next), loaderNext: react_1.default.createElement(Skeleton_1.default, { groupsNumber: 2 }), endMessage: react_1.default.createElement(material_1.Typography, { component: "div", className: classes.endMessage },
172
+ react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (react_1.default.createElement(InfiniteScroll_1.default, { dataLength: groups.length, next: handleNext, hasMoreNext: Boolean(next), loaderNext: isMobile ? react_1.default.createElement(Group_1.GroupSkeleton, null) : react_1.default.createElement(Skeleton_1.default, { groupsNumber: 2 }), endMessage: react_1.default.createElement(material_1.Typography, { component: "div", className: classes.endMessage },
171
173
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.endMessage", defaultMessage: "ui.groups.endMessage", values: {
172
174
  button: (chunk) => (react_1.default.createElement(material_1.Button, { color: "secondary", variant: "text", onClick: handleScrollUp }, chunk))
173
175
  } })) },
@@ -10,7 +10,7 @@ export interface GroupsSkeletonProps {
10
10
  */
11
11
  GroupSkeletonProps?: any;
12
12
  /**
13
- * @default 10
13
+ * @default 20
14
14
  */
15
15
  groupsNumber?: number;
16
16
  }
@@ -37,7 +37,7 @@ const Root = (0, styles_1.styled)(material_1.Box, {
37
37
  *
38
38
  */
39
39
  function GroupsSkeleton(inProps) {
40
- const { className, GroupSkeletonProps = {}, groupsNumber = 10 } = inProps, rest = tslib_1.__rest(inProps, ["className", "GroupSkeletonProps", "groupsNumber"]);
40
+ const { className, GroupSkeletonProps = {}, groupsNumber = 20 } = inProps, rest = tslib_1.__rest(inProps, ["className", "GroupSkeletonProps", "groupsNumber"]);
41
41
  return (react_1.default.createElement(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest),
42
42
  react_1.default.createElement(material_1.Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, [...Array(groupsNumber)].map((category, index) => (react_1.default.createElement(material_1.Grid, { item: true, xs: 12, sm: 8, md: 6, key: index },
43
43
  react_1.default.createElement(Group_1.GroupSkeleton, Object.assign({ elevation: 0, variant: 'outlined' }, GroupSkeletonProps))))))));
@@ -59,6 +59,7 @@ export interface NavigationToolbarMobileProps extends ToolbarProps {
59
59
  |logo|.SCNavigationToolbarMobile-logo|Styles applied to the logo element.|
60
60
  |search|.SCNavigationToolbarMobile-search|Styles applied to the search button element|
61
61
  |searchDialog|.SCNavigationToolbarMobile-search-dialog|Styles applied to the search dialog element|
62
+ |notifications|.SCNavigationToolbarMobile-notifications|Styles applied to the notifications button element|
62
63
  |settings|.SCNavigationToolbarMobile-settings|Styles applied to the settings button element|
63
64
  |settingsDialog|.SCNavigationToolbarMobile-settingsDialog|Styles applied to the settings dialog elements|
64
65
  |login|.SCNavigationToolbarMobile-login|Styles applied to the login element.|
@@ -13,11 +13,13 @@ const SearchDialog_1 = tslib_1.__importDefault(require("../SearchDialog"));
13
13
  const NavigationSettingsIconButton_1 = tslib_1.__importDefault(require("../NavigationSettingsIconButton"));
14
14
  const NavigationMenuIconButton_1 = tslib_1.__importDefault(require("../NavigationMenuIconButton"));
15
15
  const constants_1 = require("./constants");
16
+ const types_1 = require("@selfcommunity/types");
16
17
  const classes = {
17
18
  root: `${constants_1.PREFIX}-root`,
18
19
  logo: `${constants_1.PREFIX}-logo`,
19
20
  search: `${constants_1.PREFIX}-search`,
20
21
  searchDialog: `${constants_1.PREFIX}-search-dialog`,
22
+ notifications: `${constants_1.PREFIX}-notifications`,
21
23
  settings: `${constants_1.PREFIX}-settings`,
22
24
  settingsDialog: `${constants_1.PREFIX}-settings-dialog`,
23
25
  login: `${constants_1.PREFIX}-login`
@@ -52,6 +54,7 @@ const Root = (0, material_1.styled)(material_1.Toolbar, {
52
54
  |logo|.SCNavigationToolbarMobile-logo|Styles applied to the logo element.|
53
55
  |search|.SCNavigationToolbarMobile-search|Styles applied to the search button element|
54
56
  |searchDialog|.SCNavigationToolbarMobile-search-dialog|Styles applied to the search dialog element|
57
+ |notifications|.SCNavigationToolbarMobile-notifications|Styles applied to the notifications button element|
55
58
  |settings|.SCNavigationToolbarMobile-settings|Styles applied to the settings button element|
56
59
  |settingsDialog|.SCNavigationToolbarMobile-settingsDialog|Styles applied to the settings dialog elements|
57
60
  |login|.SCNavigationToolbarMobile-login|Styles applied to the login element.|
@@ -69,9 +72,11 @@ function NavigationToolbarMobile(inProps) {
69
72
  const scUserContext = (0, react_core_1.useSCUser)();
70
73
  const scRoutingContext = (0, react_core_1.useSCRouting)();
71
74
  // PREFERENCES
72
- const { preferences } = (0, react_core_1.useSCPreferences)();
75
+ const { preferences, features } = (0, react_core_1.useSCPreferences)();
73
76
  // STATE
74
77
  const [searchOpen, setSearchOpen] = (0, react_1.useState)(false);
78
+ // MEMO
79
+ const groupsEnabled = (0, react_1.useMemo)(() => features.includes(types_1.SCFeatureName.GROUPING), [features]);
75
80
  // HANDLERS
76
81
  const handleOpenSearch = (0, react_1.useCallback)(() => {
77
82
  setSearchOpen(true);
@@ -94,6 +99,9 @@ function NavigationToolbarMobile(inProps) {
94
99
  react_1.default.createElement(Icon_1.default, null, "search")),
95
100
  react_1.default.createElement(SearchDialog_1.default, { className: classes.searchDialog, fullScreen: true, open: searchOpen, SearchAutocompleteProps: Object.assign(Object.assign({}, SearchAutocompleteProps), { onClear: handleCloseSearch }) }))),
96
101
  endActions,
102
+ scUserContext.user && groupsEnabled && (react_1.default.createElement(material_1.IconButton, { className: classes.notifications, component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}) },
103
+ react_1.default.createElement(material_1.Badge, { badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" },
104
+ react_1.default.createElement(Icon_1.default, null, "notifications_active")))),
97
105
  scUserContext.user ? (react_1.default.createElement(NavigationSettingsIconButtonComponent, { className: classes.settings })) : (react_1.default.createElement(material_1.Button, { className: classes.login, color: "inherit", component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.SIGNIN_ROUTE_NAME, {}) },
98
106
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.appBar.navigation.login", defaultMessage: "ui.appBar.navigation.login" })))));
99
107
  }
@@ -71,7 +71,7 @@ export default function BottomNavigation(inProps) {
71
71
  preferences[SCPreferences.CONFIGURATIONS_EXPLORE_STREAM_ENABLED].value ? (React.createElement(BottomNavigationAction, { key: "explore", className: classes.action, component: Link, to: scRoutingContext.url(SCRoutes.EXPLORE_ROUTE_NAME, {}), value: scRoutingContext.url(SCRoutes.EXPLORE_ROUTE_NAME, {}), icon: React.createElement(Icon, null, "explore") })) : null,
72
72
  React.createElement(BottomNavigationAction, { key: "composer", className: classNames(classes.composer, classes.action), component: ComposerIconButton, disableRipple: true }),
73
73
  groupsEnabled && scUserContext.user ? (React.createElement(BottomNavigationAction, { key: "groups", className: classes.action, component: Link, to: scRoutingContext.url(SCRoutes.GROUPS_ROUTE_NAME, {}), value: scRoutingContext.url(SCRoutes.GROUPS_ROUTE_NAME, {}), icon: React.createElement(Icon, null, "groups") })) : null,
74
- scUserContext.user ? (React.createElement(BottomNavigationAction, { key: "notifications", className: classes.action, component: Link, to: scRoutingContext.url(SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), value: scRoutingContext.url(SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), icon: React.createElement(Badge, { badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" },
74
+ scUserContext.user && !groupsEnabled ? (React.createElement(BottomNavigationAction, { key: "notifications", className: classes.action, component: Link, to: scRoutingContext.url(SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), value: scRoutingContext.url(SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}), icon: React.createElement(Badge, { badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" },
75
75
  React.createElement(Icon, null, "notifications_active")) })) : null,
76
76
  privateMessagingEnabled && scUserContext.user ? (React.createElement(BottomNavigationAction, { key: "messages", className: classes.action, component: Link, to: scRoutingContext.url(SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, {}), value: scRoutingContext.url(SCRoutes.USER_PRIVATE_MESSAGES_ROUTE_NAME, {}), icon: React.createElement(Badge, { badgeContent: 0, color: "secondary" },
77
77
  React.createElement(Icon, null, "email")) })) : null
@@ -90,6 +90,7 @@ const Root = styled(BaseDialog, {
90
90
  * @param inProps
91
91
  */
92
92
  export default function GroupForm(inProps) {
93
+ var _a;
93
94
  //PROPS
94
95
  const props = useThemeProps({
95
96
  props: inProps,
@@ -225,7 +226,7 @@ export default function GroupForm(inProps) {
225
226
  endAdornment: React.createElement(Typography, { variant: "body2" }, GROUP_TITLE_MAX_LENGTH - field.name.length)
226
227
  } }),
227
228
  React.createElement(TextField, { multiline: true, className: classes.description, placeholder: `${intl.formatMessage(messages.description)}`, margin: "normal", value: field.description, name: "description", onChange: handleChange, InputProps: {
228
- endAdornment: React.createElement(Typography, { variant: "body2" }, GROUP_DESCRIPTION_MAX_LENGTH - field.description.length)
229
+ endAdornment: (React.createElement(Typography, { variant: "body2" }, ((_a = field.description) === null || _a === void 0 ? void 0 : _a.length) ? GROUP_DESCRIPTION_MAX_LENGTH - field.description.length : GROUP_DESCRIPTION_MAX_LENGTH))
229
230
  } }),
230
231
  React.createElement(Box, { className: classes.privacySection },
231
232
  React.createElement(Typography, { variant: "h4" },
@@ -86,6 +86,7 @@ export default function GroupInviteButton(inProps) {
86
86
  const [open, setOpen] = useState(false);
87
87
  const [isSending, setIsSending] = useState(false);
88
88
  const [value, setValue] = useState('');
89
+ const [suggested, setSuggested] = useState([]);
89
90
  const [list, setList] = useState([]);
90
91
  const [loading, setLoading] = useState(false);
91
92
  const [invited, setInvited] = useState([]);
@@ -124,17 +125,22 @@ export default function GroupInviteButton(inProps) {
124
125
  const intl = useIntl();
125
126
  function fetchResults() {
126
127
  setLoading(true);
127
- let service;
128
- if (scGroup) {
129
- service = GroupService.getGroupSuggestedUsers(scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value);
130
- }
131
- else {
132
- service = GroupService.getGroupsSuggestedUsers(value);
133
- }
134
- service
128
+ GroupService.getGroupSuggestedUsers(scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value)
135
129
  .then((data) => {
136
130
  setLoading(false);
137
- setList(data.results);
131
+ setSuggested(data.results);
132
+ })
133
+ .catch((error) => {
134
+ setLoading(false);
135
+ Logger.error(SCOPE_SC_UI, error);
136
+ });
137
+ }
138
+ function fetchGeneralResults() {
139
+ setLoading(true);
140
+ GroupService.getGroupsSuggestedUsers(value)
141
+ .then((data) => {
142
+ setLoading(false);
143
+ setSuggested(data.results);
138
144
  })
139
145
  .catch((error) => {
140
146
  setLoading(false);
@@ -142,8 +148,30 @@ export default function GroupInviteButton(inProps) {
142
148
  });
143
149
  }
144
150
  useEffect(() => {
145
- fetchResults();
146
- }, [scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value]);
151
+ if (scGroup === null || scGroup === void 0 ? void 0 : scGroup.id) {
152
+ GroupService.getGroupSuggestedUsers(scGroup === null || scGroup === void 0 ? void 0 : scGroup.id, value).then((data) => {
153
+ setLoading(false);
154
+ setList(data.results);
155
+ });
156
+ }
157
+ else {
158
+ GroupService.getGroupsSuggestedUsers(value).then((data) => {
159
+ setLoading(false);
160
+ setList(data.results);
161
+ });
162
+ }
163
+ }, [scGroup === null || scGroup === void 0 ? void 0 : scGroup.id]);
164
+ /**
165
+ * If a value is entered in new message field, it fetches user suggested
166
+ */
167
+ useEffect(() => {
168
+ if (scGroup) {
169
+ fetchResults();
170
+ }
171
+ else {
172
+ fetchGeneralResults();
173
+ }
174
+ }, [value, scGroup]);
147
175
  /**
148
176
  * Handles dialog close
149
177
  */
@@ -180,7 +208,7 @@ export default function GroupInviteButton(inProps) {
180
208
  switch (reason) {
181
209
  case 'input':
182
210
  setValue(value);
183
- !value && setList([]);
211
+ !value && setSuggested([]);
184
212
  break;
185
213
  case 'reset':
186
214
  setValue(value);
@@ -210,6 +238,13 @@ export default function GroupInviteButton(inProps) {
210
238
  setInvited(invited.filter((v) => v !== option));
211
239
  setList((prev) => [...prev, option]);
212
240
  };
241
+ const filterOptions = (options, { inputValue }) => {
242
+ return options.filter((option) => {
243
+ const usernameMatch = option.username.toLowerCase().includes(inputValue.toLowerCase());
244
+ const nameMatch = option.real_name.toLowerCase().includes(inputValue.toLowerCase());
245
+ return usernameMatch || nameMatch;
246
+ });
247
+ };
213
248
  /**
214
249
  * If in group edit mode and logged-in user is not also the group manager, the component is hidden.
215
250
  // */
@@ -230,7 +265,7 @@ export default function GroupInviteButton(inProps) {
230
265
  React.createElement(LoadingButton, { size: "small", color: "secondary", variant: "contained", onClick: handleSendInvitations, loading: isSending, disabled: !invited.length },
231
266
  React.createElement(FormattedMessage, { id: "ui.groupInviteButton.dialog.button.end", defaultMessage: "ui.groupInviteButton.dialog.button.end" }))) },
232
267
  React.createElement(Box, { className: classes.dialogContent },
233
- React.createElement(Autocomplete, { className: classes.autocomplete, loading: loading, size: "small", multiple: true, freeSolo: true, disableClearable: true, options: list, onChange: handleChange, onInputChange: handleInputChange, inputValue: value, value: invited, getOptionLabel: (option) => (option ? option.username : '...'), isOptionEqualToValue: (option, value) => (option ? value.id === option.id : false), renderTags: () => null, renderOption: (props, option) => (React.createElement(Box, Object.assign({ component: "li" }, props),
268
+ React.createElement(Autocomplete, { className: classes.autocomplete, loading: loading, size: "small", multiple: true, freeSolo: true, disableClearable: true, options: suggested, onChange: handleChange, onInputChange: handleInputChange, inputValue: value, filterOptions: filterOptions, value: invited, getOptionLabel: (option) => (option ? option.username : '...'), isOptionEqualToValue: (option, value) => (option ? value.id === option.id : false), renderTags: () => null, renderOption: (props, option) => (React.createElement(Box, Object.assign({ component: "li" }, props),
234
269
  React.createElement(Avatar, { alt: option.username, src: option.avatar }),
235
270
  React.createElement(Typography, { ml: 1 }, option.username))), renderInput: (params) => (React.createElement(TextField, Object.assign({}, params, { variant: "outlined", placeholder: `${intl.formatMessage(messages.placeholder)}`, InputProps: Object.assign(Object.assign({}, params.InputProps), { className: classes.input, startAdornment: (React.createElement(React.Fragment, null,
236
271
  React.createElement(InputAdornment, { position: "start" },
@@ -1,7 +1,7 @@
1
1
  import { __rest } from "tslib";
2
2
  import React, { useEffect, useMemo, useState } from 'react';
3
3
  import { styled } from '@mui/material/styles';
4
- import { Box, Button, Grid, TextField, Typography } from '@mui/material';
4
+ import { Box, Button, Grid, TextField, Typography, useMediaQuery, useTheme } from '@mui/material';
5
5
  import { Endpoints, GroupService, http } from '@selfcommunity/api-services';
6
6
  import { Logger, sortByAttr } from '@selfcommunity/utils';
7
7
  import { SCPreferences, useSCPreferences, useSCUser } from '@selfcommunity/react-core';
@@ -12,7 +12,7 @@ import { SCOPE_SC_UI } from '../../constants/Errors';
12
12
  import { useThemeProps } from '@mui/system';
13
13
  import HiddenPlaceholder from '../../shared/HiddenPlaceholder';
14
14
  import { PREFIX } from './constants';
15
- import Group from '../Group';
15
+ import Group, { GroupSkeleton } from '../Group';
16
16
  import { DEFAULT_PAGINATION_OFFSET } from '../../constants/Pagination';
17
17
  import InfiniteScroll from '../../shared/InfiniteScroll';
18
18
  const classes = {
@@ -71,6 +71,8 @@ export default function Groups(inProps) {
71
71
  const [loading, setLoading] = useState(true);
72
72
  const [next, setNext] = useState(null);
73
73
  const [search, setSearch] = useState('');
74
+ const theme = useTheme();
75
+ const isMobile = useMediaQuery(theme.breakpoints.down('md'));
74
76
  // CONTEXT
75
77
  const scUserContext = useSCUser();
76
78
  const scPreferencesContext = useSCPreferences();
@@ -165,7 +167,7 @@ export default function Groups(inProps) {
165
167
  React.createElement(Typography, { variant: "h4" },
166
168
  React.createElement(FormattedMessage, { id: "ui.groups.noGroups.title", defaultMessage: "ui.groups.noGroups.title" })),
167
169
  React.createElement(Typography, { variant: "body1" },
168
- React.createElement(FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (React.createElement(InfiniteScroll, { dataLength: groups.length, next: handleNext, hasMoreNext: Boolean(next), loaderNext: React.createElement(Skeleton, { groupsNumber: 2 }), endMessage: React.createElement(Typography, { component: "div", className: classes.endMessage },
170
+ React.createElement(FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (React.createElement(InfiniteScroll, { dataLength: groups.length, next: handleNext, hasMoreNext: Boolean(next), loaderNext: isMobile ? React.createElement(GroupSkeleton, null) : React.createElement(Skeleton, { groupsNumber: 2 }), endMessage: React.createElement(Typography, { component: "div", className: classes.endMessage },
169
171
  React.createElement(FormattedMessage, { id: "ui.groups.endMessage", defaultMessage: "ui.groups.endMessage", values: {
170
172
  button: (chunk) => (React.createElement(Button, { color: "secondary", variant: "text", onClick: handleScrollUp }, chunk))
171
173
  } })) },
@@ -10,7 +10,7 @@ export interface GroupsSkeletonProps {
10
10
  */
11
11
  GroupSkeletonProps?: any;
12
12
  /**
13
- * @default 10
13
+ * @default 20
14
14
  */
15
15
  groupsNumber?: number;
16
16
  }
@@ -35,7 +35,7 @@ const Root = styled(Box, {
35
35
  *
36
36
  */
37
37
  export default function GroupsSkeleton(inProps) {
38
- const { className, GroupSkeletonProps = {}, groupsNumber = 10 } = inProps, rest = __rest(inProps, ["className", "GroupSkeletonProps", "groupsNumber"]);
38
+ const { className, GroupSkeletonProps = {}, groupsNumber = 20 } = inProps, rest = __rest(inProps, ["className", "GroupSkeletonProps", "groupsNumber"]);
39
39
  return (React.createElement(Root, Object.assign({ className: classNames(classes.root, className) }, rest),
40
40
  React.createElement(Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, [...Array(groupsNumber)].map((category, index) => (React.createElement(Grid, { item: true, xs: 12, sm: 8, md: 6, key: index },
41
41
  React.createElement(GroupSkeleton, Object.assign({ elevation: 0, variant: 'outlined' }, GroupSkeletonProps))))))));
@@ -59,6 +59,7 @@ export interface NavigationToolbarMobileProps extends ToolbarProps {
59
59
  |logo|.SCNavigationToolbarMobile-logo|Styles applied to the logo element.|
60
60
  |search|.SCNavigationToolbarMobile-search|Styles applied to the search button element|
61
61
  |searchDialog|.SCNavigationToolbarMobile-search-dialog|Styles applied to the search dialog element|
62
+ |notifications|.SCNavigationToolbarMobile-notifications|Styles applied to the notifications button element|
62
63
  |settings|.SCNavigationToolbarMobile-settings|Styles applied to the settings button element|
63
64
  |settingsDialog|.SCNavigationToolbarMobile-settingsDialog|Styles applied to the settings dialog elements|
64
65
  |login|.SCNavigationToolbarMobile-login|Styles applied to the login element.|
@@ -1,6 +1,6 @@
1
1
  import { __rest } from "tslib";
2
- import { Button, IconButton, styled, Toolbar } from '@mui/material';
3
- import React, { useCallback, useState } from 'react';
2
+ import { Badge, Button, IconButton, styled, Toolbar } from '@mui/material';
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
4
  import { Link, SCPreferences, SCRoutes, useSCPreferences, useSCRouting, useSCUser } from '@selfcommunity/react-core';
5
5
  import Icon from '@mui/material/Icon';
6
6
  import { useThemeProps } from '@mui/system';
@@ -11,11 +11,13 @@ import SearchDialog from '../SearchDialog';
11
11
  import NavigationSettingsIconButton from '../NavigationSettingsIconButton';
12
12
  import NavigationMenuIconButton from '../NavigationMenuIconButton';
13
13
  import { PREFIX } from './constants';
14
+ import { SCFeatureName } from '@selfcommunity/types';
14
15
  const classes = {
15
16
  root: `${PREFIX}-root`,
16
17
  logo: `${PREFIX}-logo`,
17
18
  search: `${PREFIX}-search`,
18
19
  searchDialog: `${PREFIX}-search-dialog`,
20
+ notifications: `${PREFIX}-notifications`,
19
21
  settings: `${PREFIX}-settings`,
20
22
  settingsDialog: `${PREFIX}-settings-dialog`,
21
23
  login: `${PREFIX}-login`
@@ -50,6 +52,7 @@ const Root = styled(Toolbar, {
50
52
  |logo|.SCNavigationToolbarMobile-logo|Styles applied to the logo element.|
51
53
  |search|.SCNavigationToolbarMobile-search|Styles applied to the search button element|
52
54
  |searchDialog|.SCNavigationToolbarMobile-search-dialog|Styles applied to the search dialog element|
55
+ |notifications|.SCNavigationToolbarMobile-notifications|Styles applied to the notifications button element|
53
56
  |settings|.SCNavigationToolbarMobile-settings|Styles applied to the settings button element|
54
57
  |settingsDialog|.SCNavigationToolbarMobile-settingsDialog|Styles applied to the settings dialog elements|
55
58
  |login|.SCNavigationToolbarMobile-login|Styles applied to the login element.|
@@ -67,9 +70,11 @@ export default function NavigationToolbarMobile(inProps) {
67
70
  const scUserContext = useSCUser();
68
71
  const scRoutingContext = useSCRouting();
69
72
  // PREFERENCES
70
- const { preferences } = useSCPreferences();
73
+ const { preferences, features } = useSCPreferences();
71
74
  // STATE
72
75
  const [searchOpen, setSearchOpen] = useState(false);
76
+ // MEMO
77
+ const groupsEnabled = useMemo(() => features.includes(SCFeatureName.GROUPING), [features]);
73
78
  // HANDLERS
74
79
  const handleOpenSearch = useCallback(() => {
75
80
  setSearchOpen(true);
@@ -92,6 +97,9 @@ export default function NavigationToolbarMobile(inProps) {
92
97
  React.createElement(Icon, null, "search")),
93
98
  React.createElement(SearchDialog, { className: classes.searchDialog, fullScreen: true, open: searchOpen, SearchAutocompleteProps: Object.assign(Object.assign({}, SearchAutocompleteProps), { onClear: handleCloseSearch }) }))),
94
99
  endActions,
100
+ scUserContext.user && groupsEnabled && (React.createElement(IconButton, { className: classes.notifications, component: Link, to: scRoutingContext.url(SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}) },
101
+ React.createElement(Badge, { badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" },
102
+ React.createElement(Icon, null, "notifications_active")))),
95
103
  scUserContext.user ? (React.createElement(NavigationSettingsIconButtonComponent, { className: classes.settings })) : (React.createElement(Button, { className: classes.login, color: "inherit", component: Link, to: scRoutingContext.url(SCRoutes.SIGNIN_ROUTE_NAME, {}) },
96
104
  React.createElement(FormattedMessage, { id: "ui.appBar.navigation.login", defaultMessage: "ui.appBar.navigation.login" })))));
97
105
  }