@plusscommunities/pluss-circles-web-groups 1.6.2-beta.0 → 1.6.2-beta.2

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.
@@ -1,570 +0,0 @@
1
- import React, { Component } from "react";
2
- import { connect } from "react-redux";
3
- import _ from "lodash";
4
- import moment from "moment";
5
- import { Link } from "react-router-dom";
6
- import { PlussCore } from "../feature.config";
7
- import { circlesLoaded, circleRemoved } from "../actions";
8
- import { circleActions } from "../apis";
9
- import { Table } from "react-bootstrap";
10
- import { Icon } from "@components"
11
- import { values } from "../values.config";
12
- import badgeStyles from "./Circles.module.css";
13
-
14
- const { Components, Session, Helper } = PlussCore;
15
-
16
- class Circles extends Component {
17
- constructor(props) {
18
- super(props);
19
- this.state = {
20
- userSearch: "",
21
- sortBy: "newest",
22
- showUnreadOnly: false,
23
- pollingInterval: null,
24
- };
25
- }
26
-
27
- componentDidMount() {
28
- this.getData();
29
- // Poll for updates every 10 seconds
30
- this.pollingInterval = setInterval(() => {
31
- this.getData();
32
- }, 10000);
33
- }
34
-
35
- getData = () => {
36
- const { auth } = this.props;
37
- this.setState({ loadingAll: true }, async () => {
38
- try {
39
- const res = await circleActions.getAll(auth.site);
40
- console.log("getData", res.data);
41
- this.props.circlesLoaded(res.data);
42
- this.setState({ loadingAll: false });
43
- } catch (error) {
44
- console.error("getData", error);
45
- this.setState({ loadingAll: false });
46
- }
47
- });
48
- };
49
-
50
- componentWillUnmount() {
51
- if (this.pollingInterval) {
52
- clearInterval(this.pollingInterval);
53
- }
54
- }
55
-
56
- isCircleUnread(circle) {
57
- const userId = this.props.user.Id;
58
- if (!circle.Unread || !circle.Unread[userId]) {
59
- return false;
60
- }
61
- return circle.Unread[userId] > 0;
62
- }
63
-
64
- getUnreadCount(circle) {
65
- const userId = this.props.user.Id;
66
- if (!circle.Unread || !circle.Unread[userId]) {
67
- return 0;
68
- }
69
- return circle.Unread[userId];
70
- }
71
-
72
- selectUnreadFilter = (showUnreadOnly) => {
73
- this.setState({ showUnreadOnly });
74
- };
75
- getCircles() {
76
- let result = this.props.circles;
77
- if (this.state.selectedTypeFilter) {
78
- result = _.filter(result, (circle) => {
79
- return this.state.selectedTypeFilter === "circle"
80
- ? !circle.IsPrivate
81
- : circle.IsPrivate;
82
- });
83
- }
84
- if (this.state.selectedUserFilter) {
85
- result = _.filter(result, (circle) => {
86
- return _.some(circle.Audience, (user) => {
87
- return user.userId === this.state.selectedUserFilter.userId;
88
- });
89
- });
90
- }
91
- // Apply sorting
92
- result = _.sortBy(result, (circle) => {
93
- return moment(circle.Changed).valueOf();
94
- });
95
- if (this.state.sortBy === "newest") {
96
- result.reverse();
97
- if (this.state.showUnreadOnly) {
98
- result = _.filter(result, (circle) => {
99
- return this.isCircleUnread(circle);
100
- });
101
- }
102
- }
103
- return result;
104
- }
105
-
106
- getUsers() {
107
- let users = [];
108
- this.props.circles.forEach((circle) => {
109
- circle.Audience.forEach((user) => {
110
- users.push(user);
111
- });
112
- });
113
- users = _.sortBy(
114
- _.uniqBy(users, (user) => {
115
- return user.userId;
116
- }),
117
- "displayName",
118
- );
119
- return _.filter(users, (u) => {
120
- if (!_.isEmpty(this.state.userSearch)) {
121
- return (
122
- (u.displayName || "")
123
- .toLowerCase()
124
- .indexOf(this.state.userSearch.toLowerCase()) > -1
125
- );
126
- }
127
- return true;
128
- });
129
- }
130
-
131
- validateCircleAdmin(circle, onlyCreator) {
132
- if (
133
- Session.validateAccess(
134
- this.props.auth.site,
135
- values.permission,
136
- this.props.auth,
137
- )
138
- ) {
139
- return true;
140
- }
141
- if (onlyCreator) {
142
- return (
143
- (circle.Creator && circle.Creator.userId === this.props.user.Id) ||
144
- circle.CreatorId === this.props.user.Id
145
- );
146
- }
147
- return _.some(circle.Audience, (user) => {
148
- return user.userId === this.props.user.Id && user.isAdmin;
149
- });
150
- }
151
-
152
- canAddNew = () => {
153
- const { auth } = this.props;
154
- return Session.validateAccess(auth.site, values.permission, auth);
155
- };
156
-
157
- onAddNew = () => {
158
- const { auth } = this.props;
159
- if (Session.validateAccess(auth.site, values.permission, auth)) {
160
- this.props.history.push(`/${values.featureKey}/add`);
161
- }
162
- };
163
-
164
- openUserFilter = () => {
165
- this.setState({
166
- userFilterOpen: true,
167
- });
168
- };
169
-
170
- closeUserFilter = () => {
171
- this.setState({
172
- userFilterOpen: false,
173
- });
174
- };
175
-
176
- selectUserFilter = (user) => {
177
- this.setState({
178
- selectedUserFilter: user,
179
- userFilterOpen: false,
180
- });
181
- };
182
-
183
- removeUserFilter = (event) => {
184
- event.stopPropagation();
185
- this.setState({
186
- selectedUserFilter: null,
187
- });
188
- };
189
-
190
- openTypeFilter = () => {
191
- this.setState({
192
- typeFilterOpen: true,
193
- });
194
- };
195
-
196
- closeTypeFilter = () => {
197
- this.setState({
198
- typeFilterOpen: false,
199
- });
200
- };
201
-
202
- selectTypeFilter = (type) => {
203
- this.setState({
204
- selectedTypeFilter: type,
205
- typeFilterOpen: false,
206
- });
207
- };
208
-
209
- removeTypeFilter = (event) => {
210
- event.stopPropagation();
211
- this.setState({
212
- selectedTypeFilter: null,
213
- });
214
- };
215
-
216
- selectSort = (sortType) => {
217
- this.setState({
218
- sortBy: sortType,
219
- });
220
- };
221
-
222
- onHandleChange = (event) => {
223
- var stateChange = {};
224
- stateChange[event.target.getAttribute("id")] = event.target.value;
225
- this.setState(stateChange);
226
- };
227
-
228
- removeCircle = (circle) => {
229
- if (
230
- window.confirm(
231
- `Are you sure you want to delete that ${values.entityName}? You will no longer be able to access the messages in the ${values.entityName}.`,
232
- )
233
- ) {
234
- this.props.circleRemoved(circle.Id);
235
- circleActions
236
- .delete(circle.Id)
237
- .then((res) => {
238
- this.getData();
239
- })
240
- .catch((res) => {
241
- alert("Something went wrong with the request. Please try again.");
242
- });
243
- }
244
- };
245
-
246
- getTypeFilterText = (type) => {
247
- if (type === "private") {
248
- return "Private Message";
249
- }
250
- return _.capitalize(values.entityName);
251
- };
252
-
253
- getTitle = (circle) => {
254
- if (circle.IsPrivate) {
255
- return `PM: ${circle.Audience.map((user) => {
256
- return user.displayName;
257
- }).join(", ")}`;
258
- }
259
- return circle.Title;
260
- };
261
-
262
- renderRow(circle) {
263
- const unreadCount = this.getUnreadCount(circle);
264
- const badge =
265
- unreadCount > 0 ? (
266
- <Components.Tag
267
- className={badgeStyles.badgeTag}
268
- text={`${unreadCount} new`}
269
- />
270
- ) : null;
271
-
272
- return (
273
- <tr key={circle.Id}>
274
- <td className="table-TitleColumn">
275
- <div className="flex flex-center" style={{ gap: "8px" }}>
276
- <Link to={`/${values.featureKey}/${values.entityKey}/${circle.Id}`}>
277
- {this.getTitle(circle)}
278
- </Link>
279
- {badge}
280
- </div>
281
- </td>
282
- <td>{moment(circle.Changed).local().format("D MMM YYYY")}</td>
283
- <td>
284
- {circle.Audience.map((user) => {
285
- return (
286
- <Components.ProfilePic
287
- size={30}
288
- image={user.profilePic}
289
- hoverText={user.displayName}
290
- containerClass="circleTableProfilePic"
291
- />
292
- );
293
- })}
294
- </td>
295
- <td className="table-options">
296
- <div style={{ display: "flex", alignItems: "center" }}>
297
- {this.validateCircleAdmin(circle) && !circle.IsPrivate && (
298
- <Link to={`/${values.featureKey}/edit/${circle.Id}`}>
299
- <Icon
300
- style={{
301
- fontSize: 20,
302
- padding: 5,
303
- marginLeft: 12,
304
- cursor: "pointer",
305
- }}
306
- icon="fa-pencil"
307
- />
308
- </Link>
309
- )}
310
- {this.validateCircleAdmin(circle, true) && !circle.IsPrivate && (
311
- <a
312
- onClick={() => {
313
- this.removeCircle(circle);
314
- }}
315
- >
316
- <Icon
317
- style={{
318
- fontSize: 20,
319
- padding: 5,
320
- marginLeft: 12,
321
- cursor: "pointer",
322
- }}
323
- icon="fa-circle-minus"
324
- />
325
- </a>
326
- )}
327
- </div>
328
- </td>
329
- </tr>
330
- );
331
- }
332
-
333
- renderUnreadFilterInfo() {
334
- const { showUnreadOnly } = this.state;
335
-
336
- if (!showUnreadOnly) {
337
- return null;
338
- }
339
-
340
- return (
341
- <div className={badgeStyles.unreadFilterBanner}>
342
- <div className={badgeStyles.unreadFilterContent}>
343
- <Icon
344
- icon="fa-circle-info"
345
- className={badgeStyles.unreadFilterIcon}
346
- />
347
- <span className={badgeStyles.unreadFilterText}>
348
- Showing only items with new messages.{" "}
349
- <button
350
- className={badgeStyles.unreadFilterButton}
351
- onClick={() => this.selectUnreadFilter(false)}
352
- aria-label="Turn off unread filter"
353
- >
354
- Turn off filter
355
- </button>
356
- </span>
357
- </div>
358
- </div>
359
- );
360
- }
361
-
362
- renderFilters() {
363
- let userFilter = (
364
- <Components.Tag
365
- className="marginRight-10"
366
- onClick={this.openUserFilter}
367
- text="User"
368
- />
369
- );
370
-
371
- let unreadFilter = (
372
- <Components.Tag
373
- className="marginRight-10"
374
- onClick={() => this.selectUnreadFilter(!this.state.showUnreadOnly)}
375
- leftIcon={this.state.showUnreadOnly ? "check" : null}
376
- text="Unread Only"
377
- />
378
- );
379
-
380
- let typeFilter = (
381
- <Components.Tag
382
- className="marginRight-10"
383
- onClick={this.openTypeFilter}
384
- text="Type"
385
- />
386
- );
387
-
388
- if (this.state.selectedUserFilter) {
389
- userFilter = (
390
- <Components.Tag
391
- className="marginRight-10"
392
- onClick={this.openUserFilter}
393
- rightIcon="close"
394
- rightClick={this.removeUserFilter}
395
- >
396
- <Components.UserListing
397
- size={15}
398
- user={this.state.selectedUserFilter}
399
- textClass="tag_text"
400
- />
401
- </Components.Tag>
402
- );
403
- }
404
- if (this.state.selectedTypeFilter) {
405
- typeFilter = (
406
- <Components.Tag
407
- className="marginRight-10"
408
- onClick={this.openTypeFilter}
409
- rightIcon="close"
410
- rightClick={this.removeTypeFilter}
411
- text={this.getTypeFilterText(this.state.selectedTypeFilter)}
412
- />
413
- );
414
- }
415
- return (
416
- <div className="flex flex-center marginTop-20">
417
- <Components.Text type="h5" className="marginRight-20">
418
- Filter by
419
- </Components.Text>
420
- {userFilter}
421
- {typeFilter}
422
- {unreadFilter}
423
- <Components.Text type="h5" className="marginRight-20 marginLeft-20">
424
- Sort by:
425
- </Components.Text>
426
- <Components.Tag
427
- className="marginRight-10"
428
- onClick={() => this.selectSort("newest")}
429
- leftIcon={this.state.sortBy === "newest" ? "check" : null}
430
- text="Newest first"
431
- />
432
- <Components.Tag
433
- onClick={() => this.selectSort("oldest")}
434
- leftIcon={this.state.sortBy === "oldest" ? "check" : null}
435
- text="Oldest first"
436
- />
437
- </div>
438
- );
439
- }
440
-
441
- renderUserFilterPopup() {
442
- if (!this.state.userFilterOpen) {
443
- return null;
444
- }
445
- return (
446
- <Components.Popup
447
- title="Select User"
448
- maxWidth={600}
449
- minWidth={400}
450
- minHeight={400}
451
- hasPadding
452
- onClose={this.closeUserFilter}
453
- >
454
- <Components.GenericInput
455
- id="userSearch"
456
- type="text"
457
- label="Search"
458
- placeholder="Enter name"
459
- value={this.state.userSearch}
460
- onChange={(e) => this.onHandleChange(e)}
461
- alwaysShowLabel
462
- />
463
- {this.getUsers().map((user) => {
464
- return (
465
- <Components.UserListing
466
- key={user.userId}
467
- user={user}
468
- onClick={() => {
469
- this.selectUserFilter(user);
470
- }}
471
- />
472
- );
473
- })}
474
- </Components.Popup>
475
- );
476
- }
477
-
478
- renderTypeFilterPopup() {
479
- if (!this.state.typeFilterOpen) {
480
- return null;
481
- }
482
- return (
483
- <Components.Popup
484
- title="Select Type"
485
- maxWidth={600}
486
- minWidth={400}
487
- hasPadding
488
- onClose={this.closeTypeFilter}
489
- >
490
- <Components.Tag
491
- onClick={() => {
492
- this.selectTypeFilter("circle");
493
- }}
494
- text={_.capitalize(values.entityName)}
495
- className="marginRight-10"
496
- />
497
- <Components.Tag
498
- onClick={() => {
499
- this.selectTypeFilter("private");
500
- }}
501
- text="Private Message"
502
- />
503
- </Components.Popup>
504
- );
505
- }
506
-
507
- render() {
508
- return (
509
- <div>
510
- {this.renderTypeFilterPopup()}
511
- {this.renderUserFilterPopup()}
512
- <Components.Header>
513
- {this.canAddNew() && (
514
- <Components.AddButton
515
- onClick={this.onAddNew}
516
- text={`NEW ${_.upperCase(values.entityName)}`}
517
- />
518
- )}
519
- </Components.Header>
520
- <div className="pageContainer paddingVertical-20 paddingHorizontal-40">
521
- <Components.Text type="h1" className="">
522
- {values.textFeatureTitle}
523
- </Components.Text>
524
- {this.renderFilters()}
525
- {this.renderUnreadFilterInfo()}
526
-
527
- <Table
528
- className="plussTable"
529
- striped
530
- bordered
531
- condensed
532
- hover
533
- style={{ minWidth: "100%" }}
534
- >
535
- <thead>
536
- <tr>
537
- <th>Title</th>
538
- <th>Last updated</th>
539
- <th>Members</th>
540
- <th style={{ width: 50 }} />
541
- </tr>
542
- </thead>
543
- <tbody>
544
- {this.getCircles().map((circle) => {
545
- return this.renderRow(circle);
546
- })}
547
- </tbody>
548
- </Table>
549
- </div>
550
- </div>
551
- );
552
- }
553
- }
554
-
555
- const styles = {};
556
-
557
- const mapStateToProps = (state) => {
558
- const { circles } = state[values.reducerKey];
559
- const { auth } = state;
560
-
561
- return {
562
- circles,
563
- auth,
564
- user: Helper.getUserFromState(state),
565
- };
566
- };
567
-
568
- export default connect(mapStateToProps, { circlesLoaded, circleRemoved })(
569
- Circles,
570
- );
@@ -1,95 +0,0 @@
1
- /* Badge styling for unread count */
2
- :root {
3
- --badge-bg-color: #597db4; /* COLOUR_BRANDING_OFF */
4
- }
5
-
6
- .badgeTag {
7
- font-size: 11px;
8
- font-weight: 400;
9
- padding: 3px 10px;
10
- background-color: var(--badge-bg-color) !important;
11
- color: #fff !important;
12
- border-radius: 999px !important;
13
- border: none !important;
14
- display: inline-flex;
15
- align-items: center;
16
- justify-content: center;
17
- line-height: 1;
18
- letter-spacing: 0.02em;
19
- }
20
-
21
- /* Unread filter info banner */
22
- .unreadFilterBanner {
23
- background-color: #f0f4f8;
24
- border-left: 3px solid var(--badge-bg-color);
25
- border-radius: 4px;
26
- padding: 12px 16px;
27
- margin-top: 16px;
28
- margin-bottom: 16px;
29
- display: flex;
30
- align-items: center;
31
- }
32
-
33
- .unreadFilterContent {
34
- display: flex;
35
- align-items: center;
36
- gap: 10px;
37
- width: 100%;
38
- }
39
-
40
- .unreadFilterIcon {
41
- font-size: 16px;
42
- color: var(--badge-bg-color);
43
- flex-shrink: 0;
44
- }
45
-
46
- .unreadFilterText {
47
- font-size: 14px;
48
- line-height: 1.5;
49
- color: #333333;
50
- }
51
-
52
- .unreadFilterButton {
53
- background: none;
54
- border: none;
55
- color: var(--badge-bg-color);
56
- font-size: 14px;
57
- font-weight: 500;
58
- text-decoration: underline;
59
- cursor: pointer;
60
- padding: 4px 8px;
61
- margin-left: 4px;
62
- min-height: 32px;
63
- min-width: 32px;
64
- transition: all 0.2s ease;
65
- }
66
-
67
- .unreadFilterButton:hover {
68
- background-color: rgba(89, 125, 180, 0.1);
69
- text-decoration: none;
70
- }
71
-
72
- .unreadFilterButton:focus {
73
- outline: 2px solid var(--badge-bg-color);
74
- outline-offset: 2px;
75
- border-radius: 2px;
76
- }
77
-
78
- /* Mobile responsiveness */
79
- @media (max-width: 768px) {
80
- .unreadFilterBanner {
81
- flex-direction: column;
82
- align-items: flex-start;
83
- padding: 12px;
84
- }
85
-
86
- .unreadFilterContent {
87
- flex-direction: column;
88
- align-items: flex-start;
89
- gap: 8px;
90
- }
91
-
92
- .unreadFilterText {
93
- font-size: 13px;
94
- }
95
- }
@@ -1,34 +0,0 @@
1
- const values = {
2
- featureKey: "circles",
3
- entityKey: "circle",
4
- entityName: "circle",
5
- serviceKey: "circles",
6
- permission: "circles",
7
- menuIcon: "circle-o",
8
-
9
- reducerKey: "circles",
10
-
11
- actionCircleKey: "CIRCLE",
12
-
13
- textFeatureTitle: "Circles",
14
- textAddMenuTitle: "Circle",
15
- textEmptyGroups: "You aren't in any Circles",
16
- textEmptyPeople: "Contacts will show here",
17
-
18
- componentCircles: "Circles",
19
- componentAddCircle: "AddCircle",
20
- componentCircle: "Circle",
21
-
22
- inviteKey: "circleInvite",
23
- messageKey: "circleMessage",
24
- chatRoute: "circleChat",
25
- updateKeyUserCircles: "userCircles",
26
- allowPublicKey: "circleAllowPublicCircles",
27
- allowPublicKeyDefault: false,
28
-
29
- settings: {
30
- allowAnyCreate: false,
31
- },
32
- };
33
-
34
- export { values };