diff --git a/api/src/controllers/communityUserController.js b/api/src/controllers/communityUserController.js
index 457a3c2a5..86f04513e 100644
--- a/api/src/controllers/communityUserController.js
+++ b/api/src/controllers/communityUserController.js
@@ -1,21 +1,6 @@
const { Op } = require('sequelize')
const db = require('../models')
-// @desc Get the community-users
-// @route GET /api/community-users
-// @access Public
-
-const getCommunityUsers = async (req, res) => {
- try {
- const data = await db.CommunityUser.findAll()
- res.json({
- data
- })
- } catch (error) {
- res.status(400).json({ error })
- }
-}
-
// @desc follow community
// @route POST /api/community-users/follow
// @access Public
@@ -82,10 +67,38 @@ const followCommunity = async (req, res) => {
const getAllMembers = async (req, res) => {
try {
- const data = await db.CommunityUser.findAll(
+ const communityId = req.params.id
+ const member = await db.CommunityUser.findOne({ where: { userId: req.user.id, communityId }, attributes: ['role'] })
+
+ if (member.dataValues.role === 'manager') {
+ const data = await db.CommunityUser.findAll(
+ {
+ where: { communityId: req.params.id, active: true },
+ attributes: ['id', 'userId', 'role'],
+ include: [{
+ model: db.User,
+ attributes: ['firstName', 'lastName', 'email', 'phone', 'dateOfBirth']
+ }],
+ required: true
+ }
+ )
+
+ // flattening the array to show only one object
+ const newArray = data.map(item => {
+ const { userId, role, id } = item.dataValues
+ return { id, userId, role, ...item.user.dataValues }
+ })
+
+ res.json({
+ results: newArray
+ })
+ return
+ }
+
+ const newData = await db.CommunityUser.findAll(
{
- where: { communityId: req.params.id, active: true },
- attributes: ['userId'],
+ where: { communityId, active: true },
+ attributes: ['userId', 'role'],
include: [{
model: db.User,
attributes: ['firstName']
@@ -93,20 +106,33 @@ const getAllMembers = async (req, res) => {
required: true
}
)
- res.json({
- data
+ return res.json({
+ results: newData
})
} catch (error) {
res.status(400).json({ error })
}
}
+// @desc Update the community users
+// @route PUT /api/community-users/:memberId/community/:id/
+// @access Public
+
+const updateMemberRole = async (req, res) => {
+ try {
+ const { role } = req.body
+ await db.CommunityUser.update({ role }, { where: { id: parseInt(req.params.memberId) } })
+ res.json({ message: 'Successfully role updated' })
+ } catch (error) {
+ res.status(400).json({ error })
+ }
+}
+
// @desc Search Name
// @route POST /api/news/community/:id/search
// @access Private
const searchMemberName = (req, res) => {
const { name } = req.query
- const order = req.query.order || 'ASC'
db.CommunityUser.findAll(
{
@@ -129,4 +155,4 @@ const searchMemberName = (req, res) => {
.catch(err => res.json({ error: err }).status(400))
}
-module.exports = { getCommunityUsers, followCommunity, getAllMembers, searchMemberName }
+module.exports = { followCommunity, getAllMembers, searchMemberName, updateMemberRole }
diff --git a/api/src/middleware/permission.js b/api/src/middleware/permission.js
index 5bdd4be4e..d693b3c6b 100644
--- a/api/src/middleware/permission.js
+++ b/api/src/middleware/permission.js
@@ -1,15 +1,48 @@
-const permit = (role) => {
- return (req, res, next) => {
- if (checkRole(req, role)) {
- next()
- } else {
- res.json({ error: 'Sorry, You don\'t have permission' })
+const db = require('../models')
+
+const permit = (category, roles) => {
+ return async (req, res, next) => {
+
+ switch(category) {
+ case 'community-member':
+ checkMemberRoles(roles, next, res, req)
+ break;
+ case 'user':
+ checkUserRoles(roles, next, res, req)
+ break;
+ default:
+ res.json({ error: 'Sorry, You don\'t have permission' })
}
+ // // getting member role
+ // const member = await db.CommunityUser.findOne({ where: { userId: req.user.id, communityId: req.params.id }, attributes: ['role'] })
+
+ // if (member.dataValues.role) {
+ // checkMemberRoles(roles, member, next, res)
+ // } else {
+ // checkUserRoles(roles, next, res, req)
+ // }
}
}
-function checkRole (req, role) {
- return role.some(el => el === req.user.role)
+const checkRole = (roles, dbRole) => {
+ return roles.includes(dbRole)
+}
+
+const checkMemberRoles = async (roles, next, res, req) => {
+ const member = await db.CommunityUser.findOne({ where: { userId: req.user.id, communityId: req.params.id }, attributes: ['role'] })
+ if (checkRole(roles, member.dataValues.role)) {
+ next()
+ } else {
+ res.json({ error: 'Sorry, You don\'t have permission' })
+ }
+}
+
+const checkUserRoles = (roles, next, res, req) => {
+ if (checkRole(roles, req.user.role)) {
+ next()
+ } else {
+ res.json({ error: 'Sorry, You don\'t have permission' })
+ }
}
module.exports = permit
diff --git a/api/src/migrations/20210728085949-alter_community_user.js b/api/src/migrations/20210728085949-alter_community_user.js
new file mode 100644
index 000000000..e05727f59
--- /dev/null
+++ b/api/src/migrations/20210728085949-alter_community_user.js
@@ -0,0 +1,14 @@
+'use strict'
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ queryInterface.addColumn('communities_users', 'role', {
+ type: Sequelize.STRING,
+ defaultValue: 'member'
+ })
+ },
+
+ down: async (queryInterface, Sequelize) => {
+ queryInterface.removeColumn('communities_users', 'role')
+ }
+}
diff --git a/api/src/models/communityUserModel.js b/api/src/models/communityUserModel.js
index 84b332fe5..e5c0a30f5 100644
--- a/api/src/models/communityUserModel.js
+++ b/api/src/models/communityUserModel.js
@@ -15,6 +15,10 @@ module.exports = (sequelize, DataTypes) => {
active: {
type: DataTypes.INTEGER,
defaultValue: true
+ },
+ role: {
+ type: DataTypes.STRING,
+ defaultValue: 'member'
}
},
{ timestamps: true }
diff --git a/api/src/routes/categoriesRouter.js b/api/src/routes/categoriesRouter.js
index 22482b388..83a91ba44 100644
--- a/api/src/routes/categoriesRouter.js
+++ b/api/src/routes/categoriesRouter.js
@@ -10,7 +10,7 @@ const {
} = require('../controllers/categoryController')
const permit = require('../middleware/permission')
-router.route('/').get(protect, getCategories).post(protect, permit(['sysadmin']), addCategory)
-router.route('/:id').get(getSingleCategory).put(protect, permit(['sysadmin']), updateCategory).delete(protect, permit(['sysadmin']), deleteCategory)
+router.route('/').get(protect, getCategories).post(protect, permit('user', ['sysadmin']), addCategory)
+router.route('/:id').get(getSingleCategory).put(protect, permit('user', ['sysadmin']), updateCategory).delete(protect, permit(['sysadmin']), deleteCategory)
module.exports = router
diff --git a/api/src/routes/communityUserRouter.js b/api/src/routes/communityUserRouter.js
index 0c448b74d..67638bb0d 100644
--- a/api/src/routes/communityUserRouter.js
+++ b/api/src/routes/communityUserRouter.js
@@ -1,11 +1,11 @@
-const { getCommunityUsers, followCommunity, getAllMembers, searchMemberName } = require('../controllers/communityUserController')
+const { followCommunity, getAllMembers, searchMemberName, updateMemberRole } = require('../controllers/communityUserController')
const protect = require('../middleware/authMiddleware')
+const permit = require('../middleware/permission')
const router = require('express').Router()
-router.get('/', getCommunityUsers)
router.post('/follow', protect, followCommunity)
-router.get('/community/:id', getAllMembers)
+router.get('/community/:id', protect, permit('community-member', ['manager','member']), getAllMembers)
router.get('/community/:id/search', searchMemberName)
-// router.put('/:id', updateCommunityUsers);
+router.put('/:memberId/community/:id/', protect, permit('community-member', ['manager']), updateMemberRole)
module.exports = router
diff --git a/src/App.jsx b/src/App.jsx
index 37cc2e1aa..b8ad1f88d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -52,6 +52,7 @@ import AddTest from './screens/addTest/AddTest'
import LogoutUser from './screens/logoutUser/LogoutUser'
import Category from './screens/category/Category'
import PageNotFound from './screens/pageNotFound/PageNotFound'
+import CommunityMemberAdmin from './screens/admin/CommunityMemberAdmin'
function App () {
return (
@@ -78,6 +79,7 @@ function App () {
+
diff --git a/src/actions/memberActions.js b/src/actions/memberActions.js
index 3e9b02af5..778d14164 100644
--- a/src/actions/memberActions.js
+++ b/src/actions/memberActions.js
@@ -1,5 +1,8 @@
-import { getApi } from '../utils/apiFunc'
+import { getApi, putApi } from '../utils/apiFunc'
import {
+ MEMBER_ACCESS_FAIL,
+ MEMBER_ACCESS_REQUEST,
+ MEMBER_ACCESS_SUCCESS,
MEMBER_LIST_FAIL, MEMBER_LIST_REQUEST, MEMBER_LIST_SUCCESS,
MEMBER_SEARCH_FAIL, MEMBER_SEARCH_REQUEST,
MEMBER_SEARCH_SUCCESS
@@ -39,13 +42,12 @@ export const searchMembers = (search) => async (
) => {
try {
dispatch({ type: MEMBER_SEARCH_REQUEST })
- const { data } = await getApi(
+ await getApi(
dispatch,
`${process.env.REACT_APP_API_BASE_URL}/api/communities-users/community/${currentCommunity.id}/search?name=${search}`
)
dispatch({
- type: MEMBER_SEARCH_SUCCESS,
- payload: data.member
+ type: MEMBER_SEARCH_SUCCESS
})
} catch (error) {
dispatch({
@@ -57,3 +59,28 @@ export const searchMembers = (search) => async (
})
}
}
+
+export const allowAccess = (id, role) => async (
+ dispatch
+) => {
+ try {
+ dispatch({ type: MEMBER_ACCESS_REQUEST })
+ const { data } = await putApi(
+ dispatch,
+ `${process.env.REACT_APP_API_BASE_URL}/api/communities-users/${id}/community/${currentCommunity.id}`,
+ { role }
+ )
+ dispatch({
+ type: MEMBER_ACCESS_SUCCESS,
+ payload: data
+ })
+ } catch (error) {
+ dispatch({
+ type: MEMBER_ACCESS_FAIL,
+ payload:
+ error.response && error.response.data.message
+ ? error.response.data.message
+ : error.message
+ })
+ }
+}
diff --git a/src/components/cardImage/CardImage.jsx b/src/components/cardImage/CardImage.jsx
index b60283d83..ff5880c45 100644
--- a/src/components/cardImage/CardImage.jsx
+++ b/src/components/cardImage/CardImage.jsx
@@ -43,7 +43,7 @@ function CardImage ({ data = [], className }) {
-
{profile?.user.firstName || 'anonymous'}
+
{profile.user !== undefined ? (profile.user.firstName || 'anonymous') : (profile.firstName || 'anonymous')}
{Follow()}
diff --git a/src/components/dropDown/DropDown.jsx b/src/components/dropDown/DropDown.jsx
new file mode 100644
index 000000000..b2687cab2
--- /dev/null
+++ b/src/components/dropDown/DropDown.jsx
@@ -0,0 +1,43 @@
+import React, { useState } from 'react'
+import { useEffect } from 'react'
+import { useRef } from 'react'
+import useHideOnClick from '../../utils/useHideOnClick'
+import './DropDown.scss'
+
+const DropDown = ({ data, title, getRole, id, role }) => {
+ const [active, setActive] = useState()
+ const domNode = useHideOnClick(() => {
+ setActive(false)
+ })
+
+ function clickHandler (item) {
+ setActive(false)
+ getRole(item, id)
+ }
+
+ function showDropdown () {
+ setActive(!active)
+ }
+
+ return (
+
+
+
{title}
+
![](/img/chevron-right-outline.svg)
+
+ {active && (
+
+ {data.length > 0 &&
+ data.filter(el => el !== role).map((item) => (
+ - clickHandler(item)}>{item}
+ ))}
+
+ )}
+
+ )
+}
+
+export default DropDown
diff --git a/src/components/dropDown/DropDown.scss b/src/components/dropDown/DropDown.scss
new file mode 100644
index 000000000..05b955cec
--- /dev/null
+++ b/src/components/dropDown/DropDown.scss
@@ -0,0 +1,45 @@
+.dropdown-wrapper {
+ width: 200px;
+ position: relative;
+
+ .dropdown-title {
+ color: var(--primary-white);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ top: 10px;
+ z-index: 999;
+ border: 1px solid var(--primary-white);
+ cursor: pointer;
+ padding: 11px;
+ width: 100%;
+ text-transform: capitalize;
+ border-radius: 4px;
+ }
+
+ .dropdown-lists {
+ color: var(--primary-white);
+ position: absolute;
+ top: 2.3em;
+ background: var(--background-color-light);
+ margin-top: 0.5em;
+ border: 1px solid var(--dropdowns);
+ border-radius: 4px;
+ z-index: 2000;
+ width: 100%;
+ list-style: none;
+
+ li {
+ padding: 1em 2em;
+ width: 100%;
+ cursor: pointer;
+ background: var(--background-color);
+ color: #ffff;
+ text-transform: capitalize;
+
+ &:hover {
+ background: var(--primary-color);
+ }
+ }
+ }
+ }
diff --git a/src/components/table/Table.jsx b/src/components/table/Table.jsx
index 033ecae4e..6db5c1901 100644
--- a/src/components/table/Table.jsx
+++ b/src/components/table/Table.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'
+import DropDown from '../dropDown/DropDown'
import './Table.scss'
// description:
@@ -18,7 +19,7 @@ import './Table.scss'
// img: the path of the image you want to show as button
// action: this holds function that is executed when the button is clicked (fn: returns id of the item)
-const Table = ({ addSymbolNumber, data = { tblData: [] }, options = [] }) => {
+const Table = ({ addSymbolNumber, data = { tblData: [] }, options = [], dropdown = { dropdownList: [] } }) => {
const [property, setProperty] = useState([])
const [header, setHeader] = useState([])
const [tblData, setTblData] = useState([])
@@ -29,6 +30,10 @@ const Table = ({ addSymbolNumber, data = { tblData: [] }, options = [] }) => {
setProperty(tblProperty || Object.keys(tblData[0]))
}, [])
+ const getRole = (role, id) => {
+ dropdown.action(id, role)
+ }
+
return (
@@ -40,7 +45,8 @@ const Table = ({ addSymbolNumber, data = { tblData: [] }, options = [] }) => {
return {propKey} |
})
}
- {options.length && Options | }
+ {options.length > 0 && Options | }
+ {dropdown.dropdownList.length > 0 && Options | }
@@ -54,7 +60,7 @@ const Table = ({ addSymbolNumber, data = { tblData: [] }, options = [] }) => {
return {item[propkey]} |
})
}
- {options.length &&
+ {options.length > 0 && |
{
options.map(el => {
@@ -63,6 +69,9 @@ const Table = ({ addSymbolNumber, data = { tblData: [] }, options = [] }) => {
}
| }
+ {
+ dropdown.dropdownList.length > 0 && |
+ }
)
})
diff --git a/src/constants/memberConstants.js b/src/constants/memberConstants.js
index 23cd06a25..1ab519271 100644
--- a/src/constants/memberConstants.js
+++ b/src/constants/memberConstants.js
@@ -5,3 +5,7 @@ export const MEMBER_LIST_FAIL = 'MEMBER_LIST_FAIL'
export const MEMBER_SEARCH_REQUEST = 'MEMBER_SEARCH_REQUEST'
export const MEMBER_SEARCH_SUCCESS = 'MEMBER_SEARCH_SUCCESS'
export const MEMBER_SEARCH_FAIL = 'MEMBER_SEARCH_FAIL'
+
+export const MEMBER_ACCESS_REQUEST = 'MEMBER_ACCESS_REQUEST'
+export const MEMBER_ACCESS_SUCCESS = 'MEMBER_ACCESS_SUCCESS'
+export const MEMBER_ACCESS_FAIL = 'MEMBER_ACCESS_FAIL'
diff --git a/src/reducers/memberReducers.js b/src/reducers/memberReducers.js
index 0d98695f2..24d8b9617 100644
--- a/src/reducers/memberReducers.js
+++ b/src/reducers/memberReducers.js
@@ -1,4 +1,7 @@
import {
+ MEMBER_ACCESS_FAIL,
+ MEMBER_ACCESS_REQUEST,
+ MEMBER_ACCESS_SUCCESS,
MEMBER_LIST_FAIL, MEMBER_LIST_REQUEST, MEMBER_LIST_SUCCESS,
MEMBER_SEARCH_FAIL,
MEMBER_SEARCH_REQUEST, MEMBER_SEARCH_SUCCESS
@@ -11,7 +14,7 @@ export const memberListReducer = (state = { members: [] }, action) => {
case MEMBER_LIST_SUCCESS:
return {
loading: false,
- members: action.payload,
+ members: action.payload.results,
pages: action.payload.pages,
page: action.payload.page
}
@@ -32,3 +35,19 @@ export const memberListReducer = (state = { members: [] }, action) => {
return state
}
}
+
+export const memberAccessReducer = (state = { success: false}, action) => {
+ switch (action.type) {
+ case MEMBER_ACCESS_REQUEST:
+ return { loading: true}
+ case MEMBER_ACCESS_SUCCESS:
+ return {
+ loading: false,
+ success: true
+ }
+ case MEMBER_ACCESS_FAIL:
+ return { loading: false, error: action.payload }
+ default:
+ return state
+ }
+}
diff --git a/src/screens/admin/CommunityMemberAdmin.jsx b/src/screens/admin/CommunityMemberAdmin.jsx
new file mode 100644
index 000000000..aa5174655
--- /dev/null
+++ b/src/screens/admin/CommunityMemberAdmin.jsx
@@ -0,0 +1,57 @@
+import React, { useEffect, useLayoutEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { allowAccess, listMembers } from '../../actions/memberActions'
+import Table from '../../components/table/Table'
+import DashboardLayout from '../../layout/dashboardLayout/DashboardLayout'
+import { getApi} from '../../utils/apiFunc'
+import { getCommunity } from '../../utils/getCommunity'
+import './CommunityMemberAdmin.scss'
+
+const ComMemberAdmin = () => {
+ const [data, setData] = useState([]);
+ const memberData = useSelector(state => state.accessMember)
+ const { members } = useSelector(state => state.listMember)
+ const {success} = memberData
+ const dispatch = useDispatch()
+
+ // fetching current community
+ const currentCommunity = getCommunity()
+
+ useEffect(() => {
+ dispatch(listMembers())
+ if(members) {
+ setData(members)
+ }
+ }, [dispatch, success])
+
+ const allowAccessFunc = async (id, role) => {
+ dispatch(allowAccess(id, role));
+ }
+
+ // const getMemberDetails = async () => {
+ // const { data } = await getApi(
+ // dispatch,
+ // `${process.env.REACT_APP_API_BASE_URL}/api/communities-users/community/${currentCommunity.id}`
+ // )
+ // setData(data.results)
+ // }
+
+ return (
+
+ {members.length && }
+
+ )
+}
+
+export default ComMemberAdmin
diff --git a/src/screens/admin/CommunityMemberAdmin.scss b/src/screens/admin/CommunityMemberAdmin.scss
new file mode 100644
index 000000000..a4e8391fb
--- /dev/null
+++ b/src/screens/admin/CommunityMemberAdmin.scss
@@ -0,0 +1,38 @@
+.com-member-container {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ color: #fff;
+}
+
+.com-member-header {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: var(--primary-color);
+ padding: 12px;
+ text-align: center;
+
+ p {
+ flex: 1;
+ }
+}
+
+.com-member-item {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px;
+ text-align: center;
+ background: var(--dropdowns);
+
+ p {
+ flex: 1;
+ }
+}
+
+.access-btn {
+ cursor: pointer;
+}
diff --git a/src/screens/communityMembers/CommunityMembers.jsx b/src/screens/communityMembers/CommunityMembers.jsx
index 8aff3dc18..26f266ccb 100644
--- a/src/screens/communityMembers/CommunityMembers.jsx
+++ b/src/screens/communityMembers/CommunityMembers.jsx
@@ -35,7 +35,7 @@ function CommunityMembers ({ history }) {
- {members && }
+ {members.length && }
diff --git a/src/store.js b/src/store.js
index a30506543..036b2958f 100644
--- a/src/store.js
+++ b/src/store.js
@@ -61,7 +61,7 @@ import {
communityDeleteReducer,
communityUpdateReducer
} from './reducers/communityReducers'
-import { memberListReducer } from './reducers/memberReducers'
+import { memberAccessReducer, memberListReducer } from './reducers/memberReducers'
import { addEnrollReducer } from './reducers/enrollReducer'
import { questionDeleteReducer, questionListReducer, questionUpdateReducer } from './reducers/questionReducers'
import {
@@ -110,6 +110,7 @@ const reducer = combineReducers({
newsDelete: newsDeleteReducer,
newsUpdate: newsUpdateReducer,
listMember: memberListReducer,
+ accessMember: memberAccessReducer,
userLogin: userLoginReducer,
userRegister: userRegisterReducer,
userConfirmCode: userConfirmCodeReducer,
diff --git a/src/utils/apiFunc.jsx b/src/utils/apiFunc.jsx
index 190c3b760..3febf2c4b 100644
--- a/src/utils/apiFunc.jsx
+++ b/src/utils/apiFunc.jsx
@@ -15,3 +15,7 @@ export const getApi = async (dispatch, url, config) => {
export const postApi = async (dispatch, url, data, config) => {
return await axios.post(url, data, configFunc(config))
}
+
+export const putApi = async (dispatch, url, data, config) => {
+ return await axios.put(url, data, configFunc(config))
+}
diff --git a/src/utils/getCommunity.js b/src/utils/getCommunity.js
new file mode 100644
index 000000000..dc7b67074
--- /dev/null
+++ b/src/utils/getCommunity.js
@@ -0,0 +1,5 @@
+export const getCommunity = () => {
+ return localStorage.getItem('currentCommunity')
+ ? JSON.parse(localStorage.getItem('currentCommunity'))
+ : null
+}