Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cd15a8d

Browse files
committedFeb 13, 2021
working suggestions and reactselect element
1 parent 2c06f11 commit cd15a8d

File tree

8 files changed

+135
-13
lines changed

8 files changed

+135
-13
lines changed
 

‎config/dev.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ module.exports = {
2121

2222
API: {
2323
V5: "https://api.topcoder-dev.com/v5",
24+
V3: "https://api.topcoder-dev.com/v3"
2425
},
2526
};

‎config/prod.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ module.exports = {
2121

2222
API: {
2323
V5: "https://api.topcoder.com/v5",
24+
V3: "https://api.topcoder.com/v3"
2425
},
2526
};

‎src/components/ReactSelect/index.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ const ReactSelect = (props) => {
6262
textAlign: "left",
6363
borderRadius: "5px",
6464
}),
65+
dropdownIndicator: () => ({
66+
display: "none"
67+
}),
6568
};
6669

6770
return (
@@ -76,6 +79,7 @@ const ReactSelect = (props) => {
7679
onBlur={props.onBlur}
7780
onFocus={props.onFocus}
7881
placeholder={props.placeholder}
82+
onInputChange={props.onInputChange}
7983
/>
8084
</div>
8185
);
@@ -89,6 +93,7 @@ ReactSelect.propTypes = {
8993
isMulti: PT.bool,
9094
onBlur: PT.func,
9195
onFocus: PT.func,
96+
onInputChange: PT.func,
9297
options: PT.arrayOf(
9398
PT.shape({
9499
value: PT.string.isRequired,

‎src/routes/TeamAccess/actions/index.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getTeamInvitees,
88
deleteTeamMember,
99
deleteInvite,
10+
getMemberSuggestions,
1011
} from "services/teams";
1112

1213
export const ACTION_TYPE = {
@@ -18,6 +19,10 @@ export const ACTION_TYPE = {
1819
LOAD_INVITES_PENDING: "LOAD_INVITES_PENDING",
1920
LOAD_INVITES_SUCCESS: "LOAD_INVITES_SUCCESS",
2021
LOAD_INVITES_ERROR: "LOAD_INVITES_ERROR",
22+
LOAD_SUGGESTIONS: "LOAD_SUGGESTIONS",
23+
LOAD_SUGGESTIONS_PENDING: "LOAD_SUGGESTIONS_PENDING",
24+
LOAD_SUGGESTIONS_SUCCESS: "LOAD_SUGGESTIONS_SUCCESS",
25+
LOAD_SUGGESTIONS_ERROR: "LOAD_SUGGESTIONS_ERROR",
2126
REMOVE_MEMBER: "REMOVE_MEMBER",
2227
REMOVE_MEMBER_PENDING: "REMOVE_MEMBER_PENDING",
2328
REMOVE_MEMBER_SUCCESS: "REMOVE_MEMBER_SUCCESS",
@@ -27,6 +32,7 @@ export const ACTION_TYPE = {
2732
REMOVE_INVITE_SUCCESS: "REMOVE_INVITE_SUCCESS",
2833
REMOVE_INVITE_ERROR: "REMOVE_INVITE_ERROR",
2934
CLEAR_ALL: "CLEAR_ALL",
35+
CLEAR_SUGGESTIONS: "CLEAR_SUGGESTIONS"
3036
};
3137

3238
/**
@@ -95,8 +101,10 @@ export const removeTeamMember = (teamId, memberId) => ({
95101
/**
96102
* Removes an invite
97103
*
98-
* @para {string|number} teamId
104+
* @param {string|number} teamId
99105
* @param {string|number} REMOVE_INVITE_PENDING
106+
*
107+
* @returns {Promise} deleted invite id or error
100108
*/
101109
export const removeInvite = (teamId, inviteId) => ({
102110
type: ACTION_TYPE.REMOVE_INVITE,
@@ -109,3 +117,28 @@ export const removeInvite = (teamId, inviteId) => ({
109117
inviteId,
110118
},
111119
});
120+
121+
/**
122+
* Loads suggestions for invites
123+
*
124+
* @param {string} fragment
125+
*
126+
* @returns {Promise<object[]>} list of suggestions or error
127+
*/
128+
export const loadSuggestions = fragment => ({
129+
type: ACTION_TYPE.LOAD_SUGGESTIONS,
130+
payload: async () => {
131+
const res = await getMemberSuggestions(fragment);
132+
return res.data.result.content;
133+
},
134+
meta: {
135+
fragment
136+
}
137+
})
138+
139+
/**
140+
* Clears invite suggestions
141+
*/
142+
export const clearSuggestions = () => ({
143+
type: ACTION_TYPE.CLEAR_SUGGESTIONS
144+
})

‎src/routes/TeamAccess/components/AddModal/index.jsx

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,61 @@
1-
import React from "react";
1+
import React, { useCallback, useState } from "react";
2+
import _ from 'lodash';
3+
import { useDispatch, useSelector } from "react-redux";
4+
import { loadSuggestions, clearSuggestions } from "../../actions";
25
import Button from "components/Button";
36
import BaseModal from "components/BaseModal";
47
import ReactSelect from "components/ReactSelect";
58

9+
const SUGGESTION_TRIGGER_LENGTH = 3;
10+
611
function AddModal({ open, onClose }) {
7-
const options = [
8-
{ value: "chocolate", label: "Chocolate" },
9-
{ value: "strawberry", label: "Strawberry" },
10-
{ value: "vanilla", label: "Vanilla" },
11-
];
12+
13+
const [selectedMembers, setSelectedMembers] = useState([]);
14+
const options = useSelector(state => state.teamMembers.suggestions.map(sugg => ({ label: sugg.handle, value: sugg.handle })));
15+
const dispatch = useDispatch();
16+
17+
const debouncedLoadSuggestions = _.debounce(arg => {dispatch(loadSuggestions(arg))}, 500, {leading: true});
18+
19+
const handleClose = useCallback(() => {
20+
setSelectedMembers([]);
21+
onClose()
22+
},[onClose])
23+
24+
const onInputChange = useCallback((val) => {
25+
const spaceIndex = val.indexOf(" ");
26+
const semiColonIndex = val.indexOf(";");
27+
28+
// use space or semi-colon to add a member
29+
if (spaceIndex === 0 || semiColonIndex === 0) return "";
30+
if (spaceIndex >= 1 || semiColonIndex >= 1) {
31+
val = val.slice(0, -1);
32+
onUpdate([...selectedMembers, {label: val, value: val}])
33+
return "";
34+
}
35+
36+
// load suggestions
37+
if (val.length >= SUGGESTION_TRIGGER_LENGTH) {
38+
debouncedLoadSuggestions(val);
39+
} else {
40+
dispatch(clearSuggestions());
41+
}
42+
}, [dispatch])
43+
44+
const onUpdate = useCallback((arr) => {
45+
const normalizedArr = arr.map(member => ({
46+
...member,
47+
isEmail: (/(.+)@(.+){2,}\.(.+){2,}/).test(member.label)
48+
}));
49+
50+
setSelectedMembers(normalizedArr);
51+
dispatch(clearSuggestions());
52+
}, [dispatch])
1253

1354
const inviteButton = (
1455
<Button
1556
type="primary"
1657
size="medium"
17-
onClick={() => console.log("You clicked invite")}
58+
onClick={() => console.log('yay')}
1859
>
1960
Invite
2061
</Button>
@@ -23,15 +64,16 @@ function AddModal({ open, onClose }) {
2364
return (
2465
<BaseModal
2566
open={open}
26-
onClose={onClose}
67+
onClose={handleClose}
2768
button={inviteButton}
2869
title="Invite more people"
2970
>
3071
<ReactSelect
31-
value=""
32-
onChange={(e) => console.log(e)}
72+
value={selectedMembers}
73+
onChange={onUpdate}
3374
options={options}
34-
isMulti={true}
75+
onInputChange={onInputChange}
76+
isMulti
3577
placeholder="Enter one or more user handles"
3678
/>
3779
</BaseModal>

‎src/routes/TeamAccess/hooks/useTeamMembers.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
loadMembers,
55
loadInvites,
66
clearAll,
7-
removeTeamMember,
87
} from "../actions";
98

109
/**

‎src/routes/TeamAccess/reducers/index.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ACTION_TYPE } from "../actions";
66
const initialState = {
77
members: undefined,
88
invites: undefined,
9+
suggestions: [],
910
loading: false,
1011
error: undefined,
1112
updating: false,
@@ -104,6 +105,34 @@ const reducer = (state = initialState, action) => {
104105
error: action.payload,
105106
};
106107

108+
case ACTION_TYPE.LOAD_SUGGESTIONS_PENDING:
109+
return {
110+
...state,
111+
loading: true,
112+
error: undefined,
113+
}
114+
115+
case ACTION_TYPE.LOAD_SUGGESTIONS_SUCCESS:
116+
return {
117+
...state,
118+
suggestions: action.payload,
119+
loading: false,
120+
error: undefined,
121+
}
122+
123+
case ACTION_TYPE.LOAD_SUGGESTIONS_ERROR:
124+
return {
125+
...state,
126+
loading: false,
127+
error: action.payload
128+
}
129+
130+
case ACTION_TYPE.CLEAR_SUGGESTIONS:
131+
return {
132+
...state,
133+
suggestions: []
134+
}
135+
107136
default:
108137
return state;
109138
}

‎src/services/teams.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,15 @@ export const deleteInvite = (teamId, inviteId) => {
123123
.catch((ex) => reject(ex));
124124
});
125125
};
126+
127+
/**
128+
* Get member suggestions
129+
*
130+
* @param {string} fragment text for suggestions
131+
*
132+
* @returns {Promise}
133+
*/
134+
export const getMemberSuggestions = (fragment) => {
135+
const url = `${config.API.V3}/members/_suggest/${fragment}`;
136+
return axios.get(url);
137+
}

0 commit comments

Comments
 (0)
This repository has been archived.