Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 074e0fb

Browse files
committed
feat: loading team members for permissinos
ref issue #83
1 parent b4d6661 commit 074e0fb

File tree

6 files changed

+178
-31
lines changed

6 files changed

+178
-31
lines changed

src/constants/index.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ export const ACTION_TYPE = {
169169
*/
170170
AUTH_USER_SUCCESS: "AUTH_USER_SUCCESS",
171171
AUTH_USER_ERROR: "AUTH_USER_ERROR",
172+
// load team members for authentication/permission purposes
173+
AUTH_LOAD_TEAM_MEMBERS: "AUTH_LOAD_TEAM_MEMBERS",
174+
AUTH_LOAD_TEAM_MEMBERS_PENDING: "AUTH_LOAD_TEAM_MEMBERS_PENDING",
175+
AUTH_LOAD_TEAM_MEMBERS_SUCCESS: "AUTH_LOAD_TEAM_MEMBERS_SUCCESS",
176+
AUTH_LOAD_TEAM_MEMBERS_ERROR: "AUTH_LOAD_TEAM_MEMBERS_ERROR",
177+
AUTH_CLEAR_TEAM_MEMBERS: "AUTH_CLEAR_TEAM_MEMBERS",
172178

173179
/*
174180
Report Popup
@@ -204,7 +210,7 @@ export const ACTION_TYPE = {
204210
};
205211

206212
/**
207-
* All fonr field types
213+
* All form field types
208214
*/
209215
export const FORM_FIELD_TYPE = {
210216
TEXT: "text",

src/hoc/withAuthentication/actions/index.js

+24-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Auth User actions
33
*/
44
import { ACTION_TYPE } from "constants";
5+
import { getTeamMembers } from "services/teams";
56

67
/**
78
* Action to set auth user data
@@ -22,9 +23,28 @@ export const authUserError = (error) => ({
2223
});
2324

2425
/**
25-
* Action to load project/team members
26+
* Loads team members for authentication/permission purposes
27+
*
28+
* @param {string|number} teamId
29+
*
30+
* @returns {Promise} loaded members or error
2631
*/
27-
export const loadTeamMembers = (error) => ({
28-
type: ACTION_TYPE.AUTH_USER_ERROR,
29-
payload: error,
32+
export const authLoadTeamMembers = (teamId) => ({
33+
type: ACTION_TYPE.AUTH_LOAD_TEAM_MEMBERS,
34+
payload: async () => {
35+
const res = await getTeamMembers(teamId);
36+
return res.data;
37+
},
38+
meta: {
39+
teamId,
40+
},
41+
});
42+
43+
/**
44+
* Clear team members for authentication/permission purposes
45+
*
46+
* We need this if we are going to some route which doesn't have `teamId`
47+
*/
48+
export const authClearTeamMembers = () => ({
49+
type: ACTION_TYPE.AUTH_CLEAR_TEAM_MEMBERS,
3050
});

src/hoc/withAuthentication/index.js

+61-16
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,85 @@
22
* Authentication
33
*
44
* wrap component for authentication
5+
*
6+
* - checks if user is logged-in, and if not, then redirects to the login page
7+
*
8+
* Also, this component load important data for `hasPermission` method:
9+
* - decodes user token and set in Redux Store `authUser.userId, handle, roles`
10+
* - we need to know user `roles` to check if user user has Topcoder Roles
11+
* - load team (project) members if current route has `:teamId` param
12+
* - we need to know members of the team to check user users Project Roles
513
*/
6-
import React, { useState, useEffect } from "react";
14+
import React, { useEffect } from "react";
715
import _ from "lodash";
816
import { getAuthUserTokens, login } from "@topcoder/micro-frontends-navbar-app";
917
import LoadingIndicator from "../../components/LoadingIndicator";
10-
import { authUserSuccess, authUserError } from "./actions";
18+
import {
19+
authUserSuccess,
20+
authUserError,
21+
authLoadTeamMembers,
22+
authClearTeamMembers,
23+
} from "./actions";
1124
import { decodeToken } from "tc-auth-lib";
1225
import { useDispatch, useSelector } from "react-redux";
26+
import { useParams } from "@reach/router";
1327

1428
export default function withAuthentication(Component) {
1529
const AuthenticatedComponent = (props) => {
1630
const dispatch = useDispatch();
17-
const { isLoggedIn, authError } = useSelector((state) => state.authUser);
31+
const { isLoggedIn, authError, teamId } = useSelector(
32+
(state) => state.authUser
33+
);
34+
const params = useParams();
1835

36+
/*
37+
Check if user is logged-in or redirect ot the login page
38+
*/
1939
useEffect(() => {
2040
// prevent page redirecting to login page when unmount
2141
let isUnmount = false;
22-
getAuthUserTokens()
23-
.then(({ tokenV3 }) => {
24-
if (!!tokenV3) {
25-
const tokenData = decodeToken(tokenV3);
26-
dispatch(
27-
authUserSuccess(_.pick(tokenData, ["userId", "handle", "roles"]))
28-
);
29-
} else if (!isUnmount) {
30-
login();
31-
}
32-
})
33-
.catch((error) => dispatch(authUserError(error)));
42+
43+
if (!isLoggedIn) {
44+
getAuthUserTokens()
45+
.then(({ tokenV3 }) => {
46+
if (!!tokenV3) {
47+
const tokenData = decodeToken(tokenV3);
48+
dispatch(
49+
authUserSuccess(
50+
_.pick(tokenData, ["userId", "handle", "roles"])
51+
)
52+
);
53+
} else if (!isUnmount) {
54+
login();
55+
}
56+
})
57+
.catch((error) => dispatch(authUserError(error)));
58+
}
3459

3560
return () => {
3661
isUnmount = true;
3762
};
38-
}, [dispatch]);
63+
}, [dispatch, isLoggedIn]);
64+
65+
/*
66+
Load team (project) members if current URL has `:teamId` param
67+
*/
68+
useEffect(() => {
69+
// if we haven't loaded team members yet, or we if we've moved to a page for another team
70+
// we have to load team members which we would use for checking permissions
71+
if (
72+
isLoggedIn &&
73+
params.teamId &&
74+
(!teamId || params.teamId !== teamId)
75+
) {
76+
dispatch(authLoadTeamMembers(params.teamId));
77+
78+
// if we are going to some page without `teamId` then we have to clear team members
79+
// if we had some
80+
} else if (teamId && !params.teamId) {
81+
dispatch(authClearTeamMembers());
82+
}
83+
}, [params.teamId, teamId, dispatch, isLoggedIn]);
3984

4085
return (
4186
<>

src/hoc/withAuthentication/reducers/index.js

+71-7
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,96 @@
11
/**
22
* Reducer for `authUser`
33
*/
4-
4+
import _ from "lodash";
55
import { ACTION_TYPE } from "constants";
66

77
const initialState = {
8-
isLoggedIn: null,
9-
userId: null,
10-
handle: null,
8+
isLoggedIn: undefined,
9+
userId: undefined,
10+
handle: undefined,
1111
roles: [],
12-
authError: null,
12+
authError: undefined,
13+
// for permissions check purpose we need to know team `members'
14+
teamId: undefined,
15+
teamMembers: undefined,
16+
teamMembersLoading: undefined,
17+
teamMembersLoadingError: undefined,
1318
};
1419

20+
const authInitialState = _.pick(initialState, [
21+
"isLoggedIn",
22+
"userId",
23+
"handle",
24+
"roles",
25+
"authError",
26+
]);
27+
28+
const teamMembersInitialState = _.pick(initialState, [
29+
"teamId",
30+
"teamMembers",
31+
"teamMembersLoading",
32+
"teamMembersLoadingError",
33+
]);
34+
1535
const reducer = (state = initialState, action) => {
1636
switch (action.type) {
1737
case ACTION_TYPE.AUTH_USER_SUCCESS:
1838
return {
19-
...initialState,
39+
...state,
40+
...authInitialState,
2041
...action.payload,
2142
isLoggedIn: true,
2243
};
2344

2445
case ACTION_TYPE.AUTH_USER_ERROR:
2546
return {
26-
...initialState,
47+
...state,
48+
...authInitialState,
2749
authError: action.payload,
2850
};
2951

52+
case ACTION_TYPE.AUTH_LOAD_TEAM_MEMBERS_PENDING:
53+
return {
54+
...state,
55+
teamId: action.meta.teamId,
56+
teamMembers: initialState.teamMembersLoadingError,
57+
teamMembersLoading: true,
58+
teamMembersLoadingError: initialState.teamMembersLoadingError,
59+
};
60+
61+
case ACTION_TYPE.AUTH_LOAD_TEAM_MEMBERS_SUCCESS: {
62+
// only set loaded team members if we haven't changed the team yet
63+
if (state.teamId === action.meta.teamId) {
64+
return {
65+
...state,
66+
teamMembersLoading: false,
67+
teamMembers: action.payload,
68+
};
69+
}
70+
71+
return state;
72+
}
73+
74+
case ACTION_TYPE.AUTH_LOAD_TEAM_MEMBERS_ERROR: {
75+
// only set error for loading team members if we haven't changed the team yet
76+
if (state.teamId === action.meta.teamId) {
77+
return {
78+
...state,
79+
teamMembersLoading: false,
80+
teamMembersLoadingError: action.payload,
81+
};
82+
}
83+
84+
return state;
85+
}
86+
87+
case ACTION_TYPE.AUTH_CLEAR_TEAM_MEMBERS: {
88+
return {
89+
...state,
90+
...teamMembersInitialState,
91+
};
92+
}
93+
3094
default:
3195
return state;
3296
}

src/routes/TeamAccess/actions/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
getMemberSuggestions,
1010
postMembers,
1111
} from "services/teams";
12-
import { ACTION_TYPE } from "constants"
12+
import { ACTION_TYPE } from "constants";
1313

1414
/**
1515
* Loads team members

src/utils/permissions.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,20 @@ import store from "../store";
5353
*/
5454
export const hasPermission = (permission, entities = {}) => {
5555
const user = entities.user || _.get(store.getState(), "authUser", {});
56-
// TODO: at the moment there is no place in Redux Store where we store project, so we have to always pass it manually
57-
const project = entities.project || null;
56+
let project = entities.project;
57+
58+
// if project was not provided directly, then try to build it
59+
// based on the team members which might be loaded to the Redux Store
60+
// into `authUser.teamMembers` (this only happens for pages which have URL param `:teamId`)
61+
if (!project) {
62+
const teamMembers = _.get(store.getState(), "authUser.teamMembers");
63+
64+
if (teamMembers) {
65+
project = {
66+
members: teamMembers,
67+
};
68+
}
69+
}
5870

5971
const allowRule = permission.allowRule ? permission.allowRule : permission;
6072
const denyRule = permission.denyRule ? permission.denyRule : null;

0 commit comments

Comments
 (0)