@selfcommunity/react-ui 0.7.9-alpha.6 → 0.7.9-alpha.7

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.
@@ -25,6 +25,10 @@ const messages = (0, react_intl_1.defineMessages)({
25
25
  errorLoadImage: {
26
26
  id: 'ui.changeGroupCover.button.change.alertErrorImage',
27
27
  defaultMessage: 'ui.changeGroupCover.button.change.alertErrorImage'
28
+ },
29
+ errorImageSize: {
30
+ id: 'ui.changeGroupCover.alert',
31
+ defaultMessage: 'ui.changeGroupCover.alert'
28
32
  }
29
33
  });
30
34
  /**
@@ -75,7 +79,26 @@ function ChangeGroupCover(inProps) {
75
79
  */
76
80
  const handleUpload = (event) => {
77
81
  fileInput = event.target.files[0];
78
- isCreationMode ? onChange && onChange(fileInput) : handleSave();
82
+ if (fileInput) {
83
+ const reader = new FileReader();
84
+ reader.onload = (e) => {
85
+ const img = new Image();
86
+ img.onload = () => {
87
+ if (img.width < 1920) {
88
+ setAlert(intl.formatMessage(messages.errorImageSize));
89
+ }
90
+ else {
91
+ isCreationMode ? onChange && onChange(fileInput) : handleSave();
92
+ }
93
+ };
94
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
95
+ // @ts-ignore
96
+ img.src = e.target.result;
97
+ };
98
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
99
+ // @ts-ignore
100
+ reader.readAsDataURL(fileInput);
101
+ }
79
102
  };
80
103
  /**
81
104
  * Handles cover saving after upload action
@@ -16,8 +16,8 @@ const react_intl_1 = require("react-intl");
16
16
  const lab_1 = require("@mui/lab");
17
17
  const messages = (0, react_intl_1.defineMessages)({
18
18
  errorLoadImage: {
19
- id: 'ui.changeGroupCover.button.change.alertErrorImage',
20
- defaultMessage: 'ui.changeGroupCover.button.change.alertErrorImage'
19
+ id: 'ui.changeGroupPicture.alert',
20
+ defaultMessage: 'ui.changeGroupPicture.alert'
21
21
  }
22
22
  });
23
23
  const classes = {
@@ -76,9 +76,27 @@ function ChangeGroupPicture(inProps) {
76
76
  * @param event
77
77
  */
78
78
  function handleUpload(event) {
79
- fileInput = event.target.files[0];
80
- isCreationMode ? onChange && onChange(fileInput) : handleSave();
79
+ const fileInput = event.target.files[0];
80
+ if (fileInput) {
81
+ const reader = new FileReader();
82
+ reader.onload = (e) => {
83
+ const img = new Image();
84
+ img.onload = () => {
85
+ if (img.width < 600 && img.height < 600) {
86
+ setAlert(intl.formatMessage(messages.errorLoadImage));
87
+ }
88
+ else {
89
+ isCreationMode ? onChange && onChange(fileInput) : handleSave();
90
+ }
91
+ };
92
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
93
+ // @ts-ignore
94
+ img.src = e.target.result;
95
+ };
96
+ reader.readAsDataURL(fileInput);
97
+ }
81
98
  }
99
+ // ui.changeGroupPicture.alert
82
100
  /**
83
101
  * Performs save avatar after upload
84
102
  */
@@ -226,9 +226,9 @@ function GroupInviteButton(inProps) {
226
226
  setList((prev) => [...prev, option]);
227
227
  };
228
228
  /**
229
- * If there's no authUserId, component is hidden.
229
+ * If in group edit mode and logged-in user is not also the group manager, the component is hidden.
230
230
  // */
231
- if (!canEdit) {
231
+ if (group && !canEdit) {
232
232
  return null;
233
233
  }
234
234
  /**
@@ -242,7 +242,7 @@ function GroupInviteButton(inProps) {
242
242
  react_1.default.createElement(material_1.Icon, { fontSize: "medium" }, "arrow_back")),
243
243
  react_1.default.createElement(material_1.Typography, { className: classes.dialogTitle },
244
244
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupInviteButton.dialog.title", defaultMessage: "ui.groupInviteButton.dialog.title" })),
245
- react_1.default.createElement(lab_1.LoadingButton, { size: "small", color: "secondary", variant: "contained", onClick: handleSendInvitations, loading: isSending },
245
+ react_1.default.createElement(lab_1.LoadingButton, { size: "small", color: "secondary", variant: "contained", onClick: handleSendInvitations, loading: isSending, disabled: !invited.length },
246
246
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupInviteButton.dialog.button.end", defaultMessage: "ui.groupInviteButton.dialog.button.end" }))) },
247
247
  react_1.default.createElement(material_1.Box, { className: classes.dialogContent },
248
248
  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, 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),
@@ -1,31 +1,27 @@
1
+ import { SCGroupType } from '@selfcommunity/types';
1
2
  import { EndpointType } from '@selfcommunity/api-services';
2
- import { CacheStrategies } from '@selfcommunity/utils';
3
3
  import { GroupProps } from '../Group';
4
4
  export interface GroupsProps {
5
5
  /**
6
- * Endpoint to call
7
- */
8
- endpoint: EndpointType;
9
- /**
10
- * Hides this component
11
- * @default false
6
+ * Overrides or extends the styles applied to the component.
7
+ * @default null
12
8
  */
13
- autoHide?: boolean;
9
+ className?: string;
14
10
  /**
15
- * Limit the number of users to show
16
- * @default false
11
+ * Endpoint to call
17
12
  */
18
- limit?: number;
13
+ endpoint: EndpointType;
19
14
  /**
20
- * Caching strategies
21
- * @default CacheStrategies.CACHE_FIRST
15
+ * Props to spread to single group object
16
+ * @default {variant: 'outlined', ButtonBaseProps: {disableRipple: 'true'}}
22
17
  */
23
- cacheStrategy?: CacheStrategies;
18
+ GroupComponentProps?: GroupProps;
24
19
  /**
25
- * Props to spread to single group object
26
- * @default empty object
20
+ * Prefetch groups. Useful for SSR.
21
+ * Use this to init the component with groups
22
+ * @default null
27
23
  */
28
- GroupProps?: GroupProps;
24
+ prefetchedGroups?: SCGroupType[];
29
25
  /**
30
26
  * Other props
31
27
  */
@@ -7,7 +7,6 @@ const material_1 = require("@mui/material");
7
7
  const api_services_1 = require("@selfcommunity/api-services");
8
8
  const utils_1 = require("@selfcommunity/utils");
9
9
  const react_core_1 = require("@selfcommunity/react-core");
10
- const widget_1 = require("../../utils/widget");
11
10
  const Skeleton_1 = tslib_1.__importDefault(require("./Skeleton"));
12
11
  const react_intl_1 = require("react-intl");
13
12
  const classnames_1 = tslib_1.__importDefault(require("classnames"));
@@ -65,117 +64,65 @@ function Groups(inProps) {
65
64
  props: inProps,
66
65
  name: constants_1.PREFIX
67
66
  });
68
- const { endpoint, autoHide = false, limit = 6, className, cacheStrategy = utils_1.CacheStrategies.NETWORK_ONLY, onHeightChange, onStateChange, GroupProps = { variant: 'outlined', ButtonBaseProps: { disableRipple: true, component: material_1.Box } } } = props, rest = tslib_1.__rest(props, ["endpoint", "autoHide", "limit", "className", "cacheStrategy", "onHeightChange", "onStateChange", "GroupProps"]);
67
+ const { endpoint, className, GroupComponentProps = { variant: 'outlined', ButtonBaseProps: { disableRipple: true, component: material_1.Box } }, prefetchedGroups = [] } = props, rest = tslib_1.__rest(props, ["endpoint", "className", "GroupComponentProps", "prefetchedGroups"]);
69
68
  // STATE
70
- const [state, dispatch] = (0, react_1.useReducer)(widget_1.dataWidgetReducer, {
71
- isLoadingNext: false,
72
- next: null,
73
- cacheKey: react_core_1.SCCache.getWidgetStateCacheKey(react_core_1.SCCache.GROUPS_LIST_TOOLS_STATE_CACHE_PREFIX_KEY),
74
- cacheStrategy,
75
- visibleItems: limit
76
- }, widget_1.stateWidgetInitializer);
69
+ const [groups, setGroups] = (0, react_1.useState)([]);
70
+ const [loading, setLoading] = (0, react_1.useState)(true);
77
71
  // CONTEXT
78
72
  const scUserContext = (0, react_core_1.useSCUser)();
79
73
  const scPreferencesContext = (0, react_core_1.useSCPreferences)();
80
74
  // MEMO
81
75
  const contentAvailability = (0, react_1.useMemo)(() => react_core_1.SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY in scPreferencesContext.preferences &&
82
76
  scPreferencesContext.preferences[react_core_1.SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY].value, [scPreferencesContext.preferences]);
83
- // HOOKS
84
- // const theme = useTheme<SCThemeType>();
85
- // const isMobile = useMediaQuery(theme.breakpoints.down('md'));
77
+ // CONST
78
+ const authUserId = scUserContext.user ? scUserContext.user.id : null;
79
+ // REFS
80
+ const isMountedRef = (0, react_core_1.useIsComponentMountedRef)();
86
81
  /**
87
- * Initialize component
88
- * Fetch data only if the component is not initialized, and it is not loading data
82
+ * Fetches groups list
89
83
  */
90
- const _initComponent = (0, react_1.useMemo)(() => () => {
91
- if (!state.initialized && !state.isLoadingNext) {
92
- dispatch({ type: widget_1.actionWidgetTypes.LOADING_NEXT });
93
- api_services_1.http
94
- .request({
95
- url: endpoint.url({ limit }),
96
- method: endpoint.method
97
- })
98
- .then((payload) => {
99
- dispatch({ type: widget_1.actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: Object.assign(Object.assign({}, payload.data), { initialized: true }) });
100
- })
101
- .catch((error) => {
102
- dispatch({ type: widget_1.actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } });
103
- utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
104
- });
105
- }
106
- }, [state.isLoadingNext, state.initialized, endpoint, limit, dispatch]);
107
- // EFFECTS
108
- (0, react_1.useEffect)(() => {
109
- var _a;
110
- let _t;
111
- if ((contentAvailability || (!contentAvailability && ((_a = scUserContext.user) === null || _a === void 0 ? void 0 : _a.id))) && scUserContext.user !== undefined) {
112
- _t = setTimeout(_initComponent);
113
- return () => {
114
- _t && clearTimeout(_t);
115
- };
116
- }
117
- }, [scUserContext.user, contentAvailability]);
118
- (0, react_1.useEffect)(() => {
119
- if (state.next && state.results.length === limit && state.initialized) {
120
- dispatch({ type: widget_1.actionWidgetTypes.LOADING_NEXT });
121
- api_services_1.http
122
- .request({
123
- url: endpoint.url({ offset: limit, limit: 10 }),
124
- method: endpoint.method
125
- })
126
- .then((payload) => {
127
- dispatch({ type: widget_1.actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: payload.data });
128
- })
129
- .catch((error) => {
130
- dispatch({ type: widget_1.actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } });
131
- utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
132
- });
133
- }
134
- }, [state.next, state.results.length, state.initialized, limit]);
84
+ const fetchGroups = (next = endpoint.url({})) => tslib_1.__awaiter(this, void 0, void 0, function* () {
85
+ const response = yield api_services_1.http.request({
86
+ url: next,
87
+ method: endpoint.method
88
+ });
89
+ const data = response.data;
90
+ return data.next ? data.results.concat(yield fetchGroups(data.next)) : data.results;
91
+ });
135
92
  /**
136
- * Virtual feed update
93
+ * On mount, fetches groups list
137
94
  */
138
95
  (0, react_1.useEffect)(() => {
139
- onHeightChange && onHeightChange();
140
- }, [state.results]);
141
- (0, react_1.useEffect)(() => {
142
- if (!endpoint || (!contentAvailability && !scUserContext.user)) {
96
+ if (!contentAvailability && !authUserId) {
143
97
  return;
144
98
  }
145
- else if (cacheStrategy === utils_1.CacheStrategies.NETWORK_ONLY) {
146
- onStateChange && onStateChange({ cacheStrategy: utils_1.CacheStrategies.CACHE_FIRST });
99
+ else if (prefetchedGroups.length) {
100
+ setGroups(prefetchedGroups);
101
+ setLoading(false);
147
102
  }
148
- }, [scUserContext.user, endpoint, contentAvailability]);
149
- (0, react_1.useEffect)(() => {
150
- if (!endpoint || !scUserContext.user || !state.initialized) {
151
- return;
103
+ else {
104
+ fetchGroups()
105
+ .then((data) => {
106
+ if (isMountedRef.current) {
107
+ setGroups(data);
108
+ setLoading(false);
109
+ }
110
+ })
111
+ .catch((error) => {
112
+ utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
113
+ });
152
114
  }
153
- }, []);
154
- // HANDLERS
155
- const handleNext = (0, react_1.useMemo)(() => () => {
156
- dispatch({ type: widget_1.actionWidgetTypes.LOADING_NEXT });
157
- api_services_1.http
158
- .request({
159
- url: state.next,
160
- method: endpoint.method
161
- })
162
- .then((res) => {
163
- dispatch({ type: widget_1.actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: res.data });
164
- });
165
- }, [dispatch, state.next, state.isLoadingNext, state.initialized, endpoint.method]);
115
+ }, [contentAvailability, authUserId, prefetchedGroups.length]);
166
116
  // RENDER
167
- if ((autoHide && !state.count && state.initialized) || (!contentAvailability && !scUserContext.user) || !endpoint) {
117
+ if (!contentAvailability && !scUserContext.user) {
168
118
  return react_1.default.createElement(HiddenPlaceholder_1.default, null);
169
119
  }
170
- if (!state.initialized) {
171
- return react_1.default.createElement(Skeleton_1.default, null);
172
- }
173
- const content = (react_1.default.createElement(react_1.default.Fragment, null, !state.count ? (react_1.default.createElement(material_1.Typography, { className: classes.noResults, variant: "body2" },
174
- react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupRequestsWidget.subtitle.noResults", defaultMessage: "" }))) : (react_1.default.createElement(react_1.default.Fragment, null,
175
- react_1.default.createElement(material_1.Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, state.results.slice(0, state.visibleItems).map((group) => (react_1.default.createElement(material_1.Grid, { item: true, xs: 12, sm: 8, md: 6, key: group.id, className: classes.item },
176
- react_1.default.createElement(Group_1.default, Object.assign({ group: group, groupId: group.id }, GroupProps)))))),
177
- state.count > state.visibleItems && (react_1.default.createElement(material_1.Button, { className: classes.showMore, onClick: handleNext },
178
- react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groupRequestsWidget.button.showMore", defaultMessage: "ui.groupRequestsWidget.button.showMore" })))))));
120
+ 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 },
121
+ react_1.default.createElement(material_1.Typography, { variant: "h4" },
122
+ react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.groups.noGroups.title", defaultMessage: "ui.groups.noGroups.title" })),
123
+ react_1.default.createElement(material_1.Typography, { variant: "body1" },
124
+ 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 },
125
+ react_1.default.createElement(Group_1.default, Object.assign({ group: group, groupId: group.id }, GroupComponentProps)))))))))));
179
126
  return (react_1.default.createElement(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest), content));
180
127
  }
181
128
  exports.default = Groups;
@@ -23,6 +23,10 @@ const messages = defineMessages({
23
23
  errorLoadImage: {
24
24
  id: 'ui.changeGroupCover.button.change.alertErrorImage',
25
25
  defaultMessage: 'ui.changeGroupCover.button.change.alertErrorImage'
26
+ },
27
+ errorImageSize: {
28
+ id: 'ui.changeGroupCover.alert',
29
+ defaultMessage: 'ui.changeGroupCover.alert'
26
30
  }
27
31
  });
28
32
  /**
@@ -73,7 +77,26 @@ export default function ChangeGroupCover(inProps) {
73
77
  */
74
78
  const handleUpload = (event) => {
75
79
  fileInput = event.target.files[0];
76
- isCreationMode ? onChange && onChange(fileInput) : handleSave();
80
+ if (fileInput) {
81
+ const reader = new FileReader();
82
+ reader.onload = (e) => {
83
+ const img = new Image();
84
+ img.onload = () => {
85
+ if (img.width < 1920) {
86
+ setAlert(intl.formatMessage(messages.errorImageSize));
87
+ }
88
+ else {
89
+ isCreationMode ? onChange && onChange(fileInput) : handleSave();
90
+ }
91
+ };
92
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
93
+ // @ts-ignore
94
+ img.src = e.target.result;
95
+ };
96
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
97
+ // @ts-ignore
98
+ reader.readAsDataURL(fileInput);
99
+ }
77
100
  };
78
101
  /**
79
102
  * Handles cover saving after upload action
@@ -14,8 +14,8 @@ import { defineMessages, useIntl } from 'react-intl';
14
14
  import { LoadingButton } from '@mui/lab';
15
15
  const messages = defineMessages({
16
16
  errorLoadImage: {
17
- id: 'ui.changeGroupCover.button.change.alertErrorImage',
18
- defaultMessage: 'ui.changeGroupCover.button.change.alertErrorImage'
17
+ id: 'ui.changeGroupPicture.alert',
18
+ defaultMessage: 'ui.changeGroupPicture.alert'
19
19
  }
20
20
  });
21
21
  const classes = {
@@ -74,9 +74,27 @@ export default function ChangeGroupPicture(inProps) {
74
74
  * @param event
75
75
  */
76
76
  function handleUpload(event) {
77
- fileInput = event.target.files[0];
78
- isCreationMode ? onChange && onChange(fileInput) : handleSave();
77
+ const fileInput = event.target.files[0];
78
+ if (fileInput) {
79
+ const reader = new FileReader();
80
+ reader.onload = (e) => {
81
+ const img = new Image();
82
+ img.onload = () => {
83
+ if (img.width < 600 && img.height < 600) {
84
+ setAlert(intl.formatMessage(messages.errorLoadImage));
85
+ }
86
+ else {
87
+ isCreationMode ? onChange && onChange(fileInput) : handleSave();
88
+ }
89
+ };
90
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
91
+ // @ts-ignore
92
+ img.src = e.target.result;
93
+ };
94
+ reader.readAsDataURL(fileInput);
95
+ }
79
96
  }
97
+ // ui.changeGroupPicture.alert
80
98
  /**
81
99
  * Performs save avatar after upload
82
100
  */
@@ -224,9 +224,9 @@ export default function GroupInviteButton(inProps) {
224
224
  setList((prev) => [...prev, option]);
225
225
  };
226
226
  /**
227
- * If there's no authUserId, component is hidden.
227
+ * If in group edit mode and logged-in user is not also the group manager, the component is hidden.
228
228
  // */
229
- if (!canEdit) {
229
+ if (group && !canEdit) {
230
230
  return null;
231
231
  }
232
232
  /**
@@ -240,7 +240,7 @@ export default function GroupInviteButton(inProps) {
240
240
  React.createElement(Icon, { fontSize: "medium" }, "arrow_back")),
241
241
  React.createElement(Typography, { className: classes.dialogTitle },
242
242
  React.createElement(FormattedMessage, { id: "ui.groupInviteButton.dialog.title", defaultMessage: "ui.groupInviteButton.dialog.title" })),
243
- React.createElement(LoadingButton, { size: "small", color: "secondary", variant: "contained", onClick: handleSendInvitations, loading: isSending },
243
+ React.createElement(LoadingButton, { size: "small", color: "secondary", variant: "contained", onClick: handleSendInvitations, loading: isSending, disabled: !invited.length },
244
244
  React.createElement(FormattedMessage, { id: "ui.groupInviteButton.dialog.button.end", defaultMessage: "ui.groupInviteButton.dialog.button.end" }))) },
245
245
  React.createElement(Box, { className: classes.dialogContent },
246
246
  React.createElement(Autocomplete, { className: classes.autocomplete, loading: loading, size: "small", multiple: true, freeSolo: true, disableClearable: true, options: suggested, 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),
@@ -1,31 +1,27 @@
1
+ import { SCGroupType } from '@selfcommunity/types';
1
2
  import { EndpointType } from '@selfcommunity/api-services';
2
- import { CacheStrategies } from '@selfcommunity/utils';
3
3
  import { GroupProps } from '../Group';
4
4
  export interface GroupsProps {
5
5
  /**
6
- * Endpoint to call
7
- */
8
- endpoint: EndpointType;
9
- /**
10
- * Hides this component
11
- * @default false
6
+ * Overrides or extends the styles applied to the component.
7
+ * @default null
12
8
  */
13
- autoHide?: boolean;
9
+ className?: string;
14
10
  /**
15
- * Limit the number of users to show
16
- * @default false
11
+ * Endpoint to call
17
12
  */
18
- limit?: number;
13
+ endpoint: EndpointType;
19
14
  /**
20
- * Caching strategies
21
- * @default CacheStrategies.CACHE_FIRST
15
+ * Props to spread to single group object
16
+ * @default {variant: 'outlined', ButtonBaseProps: {disableRipple: 'true'}}
22
17
  */
23
- cacheStrategy?: CacheStrategies;
18
+ GroupComponentProps?: GroupProps;
24
19
  /**
25
- * Props to spread to single group object
26
- * @default empty object
20
+ * Prefetch groups. Useful for SSR.
21
+ * Use this to init the component with groups
22
+ * @default null
27
23
  */
28
- GroupProps?: GroupProps;
24
+ prefetchedGroups?: SCGroupType[];
29
25
  /**
30
26
  * Other props
31
27
  */
@@ -1,11 +1,10 @@
1
- import { __rest } from "tslib";
2
- import React, { useEffect, useMemo, useReducer } from 'react';
1
+ import { __awaiter, __rest } from "tslib";
2
+ import React, { useEffect, useMemo, useState } from 'react';
3
3
  import { styled } from '@mui/material/styles';
4
- import { Box, Button, Grid, Typography } from '@mui/material';
4
+ import { Box, Grid, Typography } from '@mui/material';
5
5
  import { http } from '@selfcommunity/api-services';
6
- import { CacheStrategies, Logger } from '@selfcommunity/utils';
7
- import { SCCache, SCPreferences, useSCPreferences, useSCUser } from '@selfcommunity/react-core';
8
- import { actionWidgetTypes, dataWidgetReducer, stateWidgetInitializer } from '../../utils/widget';
6
+ import { Logger } from '@selfcommunity/utils';
7
+ import { SCPreferences, useIsComponentMountedRef, useSCPreferences, useSCUser } from '@selfcommunity/react-core';
9
8
  import Skeleton from './Skeleton';
10
9
  import { FormattedMessage } from 'react-intl';
11
10
  import classNames from 'classnames';
@@ -63,116 +62,64 @@ export default function Groups(inProps) {
63
62
  props: inProps,
64
63
  name: PREFIX
65
64
  });
66
- const { endpoint, autoHide = false, limit = 6, className, cacheStrategy = CacheStrategies.NETWORK_ONLY, onHeightChange, onStateChange, GroupProps = { variant: 'outlined', ButtonBaseProps: { disableRipple: true, component: Box } } } = props, rest = __rest(props, ["endpoint", "autoHide", "limit", "className", "cacheStrategy", "onHeightChange", "onStateChange", "GroupProps"]);
65
+ const { endpoint, className, GroupComponentProps = { variant: 'outlined', ButtonBaseProps: { disableRipple: true, component: Box } }, prefetchedGroups = [] } = props, rest = __rest(props, ["endpoint", "className", "GroupComponentProps", "prefetchedGroups"]);
67
66
  // STATE
68
- const [state, dispatch] = useReducer(dataWidgetReducer, {
69
- isLoadingNext: false,
70
- next: null,
71
- cacheKey: SCCache.getWidgetStateCacheKey(SCCache.GROUPS_LIST_TOOLS_STATE_CACHE_PREFIX_KEY),
72
- cacheStrategy,
73
- visibleItems: limit
74
- }, stateWidgetInitializer);
67
+ const [groups, setGroups] = useState([]);
68
+ const [loading, setLoading] = useState(true);
75
69
  // CONTEXT
76
70
  const scUserContext = useSCUser();
77
71
  const scPreferencesContext = useSCPreferences();
78
72
  // MEMO
79
73
  const contentAvailability = useMemo(() => SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY in scPreferencesContext.preferences &&
80
74
  scPreferencesContext.preferences[SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY].value, [scPreferencesContext.preferences]);
81
- // HOOKS
82
- // const theme = useTheme<SCThemeType>();
83
- // const isMobile = useMediaQuery(theme.breakpoints.down('md'));
75
+ // CONST
76
+ const authUserId = scUserContext.user ? scUserContext.user.id : null;
77
+ // REFS
78
+ const isMountedRef = useIsComponentMountedRef();
84
79
  /**
85
- * Initialize component
86
- * Fetch data only if the component is not initialized, and it is not loading data
80
+ * Fetches groups list
87
81
  */
88
- const _initComponent = useMemo(() => () => {
89
- if (!state.initialized && !state.isLoadingNext) {
90
- dispatch({ type: actionWidgetTypes.LOADING_NEXT });
91
- http
92
- .request({
93
- url: endpoint.url({ limit }),
94
- method: endpoint.method
95
- })
96
- .then((payload) => {
97
- dispatch({ type: actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: Object.assign(Object.assign({}, payload.data), { initialized: true }) });
98
- })
99
- .catch((error) => {
100
- dispatch({ type: actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } });
101
- Logger.error(SCOPE_SC_UI, error);
102
- });
103
- }
104
- }, [state.isLoadingNext, state.initialized, endpoint, limit, dispatch]);
105
- // EFFECTS
106
- useEffect(() => {
107
- var _a;
108
- let _t;
109
- if ((contentAvailability || (!contentAvailability && ((_a = scUserContext.user) === null || _a === void 0 ? void 0 : _a.id))) && scUserContext.user !== undefined) {
110
- _t = setTimeout(_initComponent);
111
- return () => {
112
- _t && clearTimeout(_t);
113
- };
114
- }
115
- }, [scUserContext.user, contentAvailability]);
116
- useEffect(() => {
117
- if (state.next && state.results.length === limit && state.initialized) {
118
- dispatch({ type: actionWidgetTypes.LOADING_NEXT });
119
- http
120
- .request({
121
- url: endpoint.url({ offset: limit, limit: 10 }),
122
- method: endpoint.method
123
- })
124
- .then((payload) => {
125
- dispatch({ type: actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: payload.data });
126
- })
127
- .catch((error) => {
128
- dispatch({ type: actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } });
129
- Logger.error(SCOPE_SC_UI, error);
130
- });
131
- }
132
- }, [state.next, state.results.length, state.initialized, limit]);
82
+ const fetchGroups = (next = endpoint.url({})) => __awaiter(this, void 0, void 0, function* () {
83
+ const response = yield http.request({
84
+ url: next,
85
+ method: endpoint.method
86
+ });
87
+ const data = response.data;
88
+ return data.next ? data.results.concat(yield fetchGroups(data.next)) : data.results;
89
+ });
133
90
  /**
134
- * Virtual feed update
91
+ * On mount, fetches groups list
135
92
  */
136
93
  useEffect(() => {
137
- onHeightChange && onHeightChange();
138
- }, [state.results]);
139
- useEffect(() => {
140
- if (!endpoint || (!contentAvailability && !scUserContext.user)) {
94
+ if (!contentAvailability && !authUserId) {
141
95
  return;
142
96
  }
143
- else if (cacheStrategy === CacheStrategies.NETWORK_ONLY) {
144
- onStateChange && onStateChange({ cacheStrategy: CacheStrategies.CACHE_FIRST });
97
+ else if (prefetchedGroups.length) {
98
+ setGroups(prefetchedGroups);
99
+ setLoading(false);
145
100
  }
146
- }, [scUserContext.user, endpoint, contentAvailability]);
147
- useEffect(() => {
148
- if (!endpoint || !scUserContext.user || !state.initialized) {
149
- return;
101
+ else {
102
+ fetchGroups()
103
+ .then((data) => {
104
+ if (isMountedRef.current) {
105
+ setGroups(data);
106
+ setLoading(false);
107
+ }
108
+ })
109
+ .catch((error) => {
110
+ Logger.error(SCOPE_SC_UI, error);
111
+ });
150
112
  }
151
- }, []);
152
- // HANDLERS
153
- const handleNext = useMemo(() => () => {
154
- dispatch({ type: actionWidgetTypes.LOADING_NEXT });
155
- http
156
- .request({
157
- url: state.next,
158
- method: endpoint.method
159
- })
160
- .then((res) => {
161
- dispatch({ type: actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: res.data });
162
- });
163
- }, [dispatch, state.next, state.isLoadingNext, state.initialized, endpoint.method]);
113
+ }, [contentAvailability, authUserId, prefetchedGroups.length]);
164
114
  // RENDER
165
- if ((autoHide && !state.count && state.initialized) || (!contentAvailability && !scUserContext.user) || !endpoint) {
115
+ if (!contentAvailability && !scUserContext.user) {
166
116
  return React.createElement(HiddenPlaceholder, null);
167
117
  }
168
- if (!state.initialized) {
169
- return React.createElement(Skeleton, null);
170
- }
171
- const content = (React.createElement(React.Fragment, null, !state.count ? (React.createElement(Typography, { className: classes.noResults, variant: "body2" },
172
- React.createElement(FormattedMessage, { id: "ui.groupRequestsWidget.subtitle.noResults", defaultMessage: "" }))) : (React.createElement(React.Fragment, null,
173
- React.createElement(Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, state.results.slice(0, state.visibleItems).map((group) => (React.createElement(Grid, { item: true, xs: 12, sm: 8, md: 6, key: group.id, className: classes.item },
174
- React.createElement(Group, Object.assign({ group: group, groupId: group.id }, GroupProps)))))),
175
- state.count > state.visibleItems && (React.createElement(Button, { className: classes.showMore, onClick: handleNext },
176
- React.createElement(FormattedMessage, { id: "ui.groupRequestsWidget.button.showMore", defaultMessage: "ui.groupRequestsWidget.button.showMore" })))))));
118
+ const content = (React.createElement(React.Fragment, null, loading ? (React.createElement(Skeleton, null)) : (React.createElement(Grid, { container: true, spacing: { xs: 3 }, className: classes.groups }, !groups.length ? (React.createElement(Box, { className: classes.noResults },
119
+ React.createElement(Typography, { variant: "h4" },
120
+ React.createElement(FormattedMessage, { id: "ui.groups.noGroups.title", defaultMessage: "ui.groups.noGroups.title" })),
121
+ React.createElement(Typography, { variant: "body1" },
122
+ React.createElement(FormattedMessage, { id: "ui.groups.noGroups.subtitle", defaultMessage: "ui.groups.noGroups.subtitle" })))) : (React.createElement(React.Fragment, null, groups.map((group) => (React.createElement(Grid, { item: true, xs: 12, sm: 8, md: 6, key: group.id, className: classes.item },
123
+ React.createElement(Group, Object.assign({ group: group, groupId: group.id }, GroupComponentProps)))))))))));
177
124
  return (React.createElement(Root, Object.assign({ className: classNames(classes.root, className) }, rest), content));
178
125
  }