Added user roles functionality
This commit is contained in:
parent
24bdcd14e0
commit
4e4fb630b4
11 changed files with 117 additions and 92 deletions
|
@ -10,8 +10,6 @@ export const Select = ({ control, name, label, options }: SelectProps) => {
|
||||||
} = useController({
|
} = useController({
|
||||||
name,
|
name,
|
||||||
control,
|
control,
|
||||||
rules: { required: true },
|
|
||||||
defaultValue: '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -25,14 +23,14 @@ export const Select = ({ control, name, label, options }: SelectProps) => {
|
||||||
id={name}
|
id={name}
|
||||||
onChange={field.onChange} // send value to hook form
|
onChange={field.onChange} // send value to hook form
|
||||||
onBlur={field.onBlur} // notify when input is touched/blur
|
onBlur={field.onBlur} // notify when input is touched/blur
|
||||||
value={field.value ? field.value.toString() : ''} // input value
|
value={field.value ? field.value : ''} // input value
|
||||||
name={name} // send down the input name
|
name={name} // send down the input name
|
||||||
ref={field.ref} // send input ref, so we can focus on input when error appear
|
ref={field.ref} // send input ref, so we can focus on input when error appear
|
||||||
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||||
>
|
>
|
||||||
{options?.map((value) => (
|
{options?.map((option) => (
|
||||||
<option key={value} value={value}>
|
<option key={option.value} value={option.value}>
|
||||||
{value}
|
{option.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { Modal, Banner } from 'src/components';
|
import { Modal, Banner } from 'src/components';
|
||||||
import { Input } from 'src/components/Form';
|
import { Input, Select } from 'src/components/Form';
|
||||||
import { useUsers } from 'src/services/users';
|
import { User, UserRole, useUsers } from 'src/services/users';
|
||||||
import { CurrentUser } from 'src/services/auth';
|
|
||||||
import { appAccessList } from './consts';
|
import { appAccessList } from './consts';
|
||||||
import { UserModalProps } from './types';
|
import { UserModalProps } from './types';
|
||||||
|
|
||||||
export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
|
export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
|
||||||
const { editUserById, userModalLoading } = useUsers();
|
const { editUserById, userModalLoading } = useUsers();
|
||||||
|
|
||||||
const { control, reset, handleSubmit } = useForm<CurrentUser>({
|
const { control, reset, handleSubmit } = useForm<User>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: null,
|
name: null,
|
||||||
email: null,
|
email: null,
|
||||||
id: null,
|
id: null,
|
||||||
preferredUsername: null,
|
role_id: null,
|
||||||
|
status: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,11 +70,23 @@ export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-6">
|
<div className="sm:col-span-6">
|
||||||
<Banner title="Editing user status, roles and app access coming soon." titleSm="Comming soon!" />
|
<Select
|
||||||
|
control={control}
|
||||||
|
name="role_id"
|
||||||
|
label="Role"
|
||||||
|
options={[
|
||||||
|
{ value: UserRole.Admin, name: 'Admin' },
|
||||||
|
{ value: UserRole.User, name: 'User' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sm:col-span-6">
|
||||||
|
<Banner title="Editing user status and app access coming soon." titleSm="Comming soon!" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
||||||
{/* <Select control={control} name="status" label="Status" options={['Active', 'Inactive']} /> */}
|
{/* <Select control={control} name="status" label="Status" options={['Admin', 'Inactive']} /> */}
|
||||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
||||||
Status
|
Status
|
||||||
</label>
|
</label>
|
||||||
|
@ -90,23 +102,6 @@ export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
|
||||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
|
||||||
Role
|
|
||||||
</label>
|
|
||||||
<div className="mt-1">
|
|
||||||
<select
|
|
||||||
id="status"
|
|
||||||
name="status"
|
|
||||||
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
|
||||||
>
|
|
||||||
<option>User</option>
|
|
||||||
<option>Admin</option>
|
|
||||||
<option>Super Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { CurrentUser } from 'src/services/auth';
|
import { User } from 'src/services/users';
|
||||||
|
|
||||||
export type UserModalProps = {
|
export type UserModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
user: CurrentUser;
|
user: User;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,8 @@ import React, { useEffect, useState } from 'react';
|
||||||
import { TrashIcon } from '@heroicons/react/outline';
|
import { TrashIcon } from '@heroicons/react/outline';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { Modal, Banner, ConfirmationModal } from 'src/components';
|
import { Modal, Banner, ConfirmationModal } from 'src/components';
|
||||||
import { Input } from 'src/components/Form';
|
import { Input, Select } from 'src/components/Form';
|
||||||
import { useUsers } from 'src/services/users';
|
import { User, UserRole, useUsers } from 'src/services/users';
|
||||||
import { CurrentUserState } from 'src/services/users/redux';
|
|
||||||
import { useAuth } from 'src/services/auth';
|
import { useAuth } from 'src/services/auth';
|
||||||
import { appAccessList } from './consts';
|
import { appAccessList } from './consts';
|
||||||
import { UserModalProps } from './types';
|
import { UserModalProps } from './types';
|
||||||
|
@ -14,11 +13,12 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
||||||
const { user, loadUser, editUserById, createNewUser, userModalLoading, deleteUserById } = useUsers();
|
const { user, loadUser, editUserById, createNewUser, userModalLoading, deleteUserById } = useUsers();
|
||||||
const { currentUser } = useAuth();
|
const { currentUser } = useAuth();
|
||||||
|
|
||||||
const { control, reset, handleSubmit } = useForm<CurrentUserState>({
|
const { control, reset, handleSubmit } = useForm<User>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: null,
|
name: null,
|
||||||
email: null,
|
email: null,
|
||||||
id: null,
|
id: null,
|
||||||
|
role_id: null,
|
||||||
status: null,
|
status: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -117,7 +117,19 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-6">
|
<div className="sm:col-span-6">
|
||||||
<Banner title="Editing user status, roles and app access coming soon." titleSm="Comming soon!" />
|
<Select
|
||||||
|
control={control}
|
||||||
|
name="role_id"
|
||||||
|
label="Role"
|
||||||
|
options={[
|
||||||
|
{ value: UserRole.Admin, name: 'Admin' },
|
||||||
|
{ value: UserRole.User, name: 'User' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sm:col-span-6">
|
||||||
|
<Banner title="Editing user status and app access coming soon." titleSm="Comming soon!" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
||||||
|
@ -137,23 +149,6 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
|
||||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
|
||||||
Role
|
|
||||||
</label>
|
|
||||||
<div className="mt-1">
|
|
||||||
<select
|
|
||||||
id="status"
|
|
||||||
name="status"
|
|
||||||
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
|
||||||
>
|
|
||||||
<option>User</option>
|
|
||||||
<option>Admin</option>
|
|
||||||
<option>Super Admin</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ export enum AuthActionTypes {
|
||||||
SIGN_IN_SUCCESS = 'auth/sign_in_success',
|
SIGN_IN_SUCCESS = 'auth/sign_in_success',
|
||||||
SIGN_IN_FAILURE = 'auth/sign_in_failure',
|
SIGN_IN_FAILURE = 'auth/sign_in_failure',
|
||||||
SIGN_OUT = 'auth/SIGN_OUT',
|
SIGN_OUT = 'auth/SIGN_OUT',
|
||||||
|
UPDATE_AUTH_USER = 'auth/update_auth_user',
|
||||||
REGISTRATION_START = 'auth/registration_start',
|
REGISTRATION_START = 'auth/registration_start',
|
||||||
REGISTRATION_FAILURE = 'auth/registration_failure',
|
REGISTRATION_FAILURE = 'auth/registration_failure',
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import { createApiReducer, chainReducers, INITIAL_API_STATUS } from 'src/services/api';
|
import { createApiReducer, chainReducers, INITIAL_API_STATUS } from 'src/services/api';
|
||||||
|
|
||||||
|
import { User } from 'src/services/users';
|
||||||
import { AuthState } from './types';
|
import { AuthState } from './types';
|
||||||
import { AuthActionTypes } from './actions';
|
import { AuthActionTypes } from './actions';
|
||||||
import { CurrentUser } from '../types';
|
import { transformAuthUser } from '../transformations';
|
||||||
|
|
||||||
const initialCurrentUserState: CurrentUser = {
|
const initialCurrentUserState: User = {
|
||||||
email: null,
|
email: null,
|
||||||
name: null,
|
name: null,
|
||||||
preferredUsername: null,
|
|
||||||
id: null,
|
id: null,
|
||||||
|
role_id: null,
|
||||||
|
status: null,
|
||||||
|
preferredUsername: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: AuthState = {
|
const initialState: AuthState = {
|
||||||
|
@ -17,9 +20,21 @@ const initialState: AuthState = {
|
||||||
_status: INITIAL_API_STATUS,
|
_status: INITIAL_API_STATUS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const authLocalReducer = (state: any = initialState, action: any) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case AuthActionTypes.UPDATE_AUTH_USER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userInfo: action.payload,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const auth = createApiReducer(
|
const auth = createApiReducer(
|
||||||
[AuthActionTypes.SIGN_IN_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.SIGN_IN_FAILURE],
|
[AuthActionTypes.SIGN_IN_START, AuthActionTypes.SIGN_IN_SUCCESS, AuthActionTypes.SIGN_IN_FAILURE],
|
||||||
(data) => ({ token: data.accessToken, userInfo: data.userInfo }),
|
(data) => transformAuthUser(data),
|
||||||
(data) => data.error.message,
|
(data) => data.error.message,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -29,4 +44,4 @@ const signOut = createApiReducer(
|
||||||
() => initialState,
|
() => initialState,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default chainReducers(initialState, auth, signOut);
|
export default chainReducers(initialState, auth, signOut, authLocalReducer);
|
||||||
|
|
18
src/services/auth/transformations.ts
Normal file
18
src/services/auth/transformations.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { UserRole } from '../users';
|
||||||
|
import { Auth } from './types';
|
||||||
|
|
||||||
|
export const transformAuthUser = (response: any): Auth => {
|
||||||
|
const resolvedUserRole = !response.userInfo.role_id ? UserRole.User : response.userInfo.role_id;
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: response.accessToken,
|
||||||
|
userInfo: {
|
||||||
|
id: response.userInfo.id,
|
||||||
|
role_id: resolvedUserRole,
|
||||||
|
email: response.userInfo.email ?? null,
|
||||||
|
name: response.userInfo.name ?? null,
|
||||||
|
preferredUsername: response.userInfo.preferredUsername,
|
||||||
|
status: response.userInfo.state ?? null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,11 +1,6 @@
|
||||||
|
import { User } from '../users';
|
||||||
|
|
||||||
export interface Auth {
|
export interface Auth {
|
||||||
token: string | null;
|
token: string | null;
|
||||||
userInfo: CurrentUser;
|
userInfo: User;
|
||||||
}
|
|
||||||
|
|
||||||
export interface CurrentUser {
|
|
||||||
email: string | null;
|
|
||||||
name: string | null;
|
|
||||||
preferredUsername: string | null;
|
|
||||||
id: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { showToast, ToastType } from 'src/common/util/show-toast';
|
import { showToast, ToastType } from 'src/common/util/show-toast';
|
||||||
import { performApiCall } from 'src/services/api';
|
import { performApiCall } from 'src/services/api';
|
||||||
import { transformRequestUser, transformResponseUser } from '../transformations';
|
import { AuthActionTypes } from 'src/services/auth';
|
||||||
|
import { transformRequestUser, transformUser } from '../transformations';
|
||||||
|
|
||||||
export enum UserActionTypes {
|
export enum UserActionTypes {
|
||||||
FETCH_USERS = 'users/fetch_users',
|
FETCH_USERS = 'users/fetch_users',
|
||||||
|
@ -38,7 +39,7 @@ export const fetchUsers = () => async (dispatch: Dispatch<any>) => {
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UserActionTypes.FETCH_USERS,
|
type: UserActionTypes.FETCH_USERS,
|
||||||
payload: data.map(transformResponseUser),
|
payload: data.map(transformUser),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -58,7 +59,7 @@ export const fetchUserById = (id: string) => async (dispatch: Dispatch<any>) =>
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UserActionTypes.FETCH_USER,
|
type: UserActionTypes.FETCH_USER,
|
||||||
payload: transformResponseUser(data),
|
payload: transformUser(data),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -79,7 +80,12 @@ export const updateUserById = (user: any) => async (dispatch: Dispatch<any>) =>
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UserActionTypes.UPDATE_USER,
|
type: UserActionTypes.UPDATE_USER,
|
||||||
payload: transformResponseUser(data),
|
payload: transformUser(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: AuthActionTypes.UPDATE_AUTH_USER,
|
||||||
|
payload: transformUser(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
showToast('User updated successfully.', ToastType.Success);
|
showToast('User updated successfully.', ToastType.Success);
|
||||||
|
@ -104,7 +110,7 @@ export const createUser = (user: any) => async (dispatch: Dispatch<any>) => {
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UserActionTypes.CREATE_USER,
|
type: UserActionTypes.CREATE_USER,
|
||||||
payload: transformResponseUser(data),
|
payload: transformUser(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
showToast('User created successfully.', ToastType.Success);
|
showToast('User created successfully.', ToastType.Success);
|
||||||
|
|
|
@ -1,31 +1,32 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { User } from './types';
|
import { User, UserRole } from './types';
|
||||||
|
|
||||||
export const transformResponseUser = (response: any): User => {
|
|
||||||
const userResponse = _.get(response, 'user', response);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: userResponse.id,
|
|
||||||
email: userResponse.traits.email,
|
|
||||||
name: userResponse.traits.name ?? null,
|
|
||||||
status: userResponse.state,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const transformUser = (response: any): User => {
|
export const transformUser = (response: any): User => {
|
||||||
const userResponse = _.get(response, 'user', response);
|
const userResponse = _.get(response, 'user', response);
|
||||||
|
|
||||||
|
const resolvedUserRole = !userResponse.traits.role_id ? UserRole.User : userResponse.traits.role_id;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: userResponse.id,
|
id: userResponse.id,
|
||||||
email: userResponse.email,
|
role_id: resolvedUserRole,
|
||||||
name: userResponse.name,
|
email: userResponse.traits.email,
|
||||||
status: userResponse.status,
|
name: userResponse.traits.name ?? null,
|
||||||
|
preferredUsername: userResponse.preferredUsername,
|
||||||
|
status: userResponse.state,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformRequestUser = (data: any) => {
|
export const transformRequestUser = (data: Pick<User, 'role_id' | 'name' | 'email'>) => {
|
||||||
|
if (data.role_id === UserRole.User) {
|
||||||
|
return {
|
||||||
|
email: data.email,
|
||||||
|
name: data.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
role_id: Number(data.role_id),
|
||||||
email: data.email,
|
email: data.email,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number | null;
|
id: number | null;
|
||||||
|
role_id: UserRole | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
|
preferredUsername: string | null;
|
||||||
status: string | null;
|
status: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +12,9 @@ export interface FormUser extends User {
|
||||||
confirmPassword?: string;
|
confirmPassword?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRole {
|
export enum UserRole {
|
||||||
id?: number;
|
Admin = '1',
|
||||||
name: string;
|
User = '2',
|
||||||
isAdministrator: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserApiRequest {
|
export interface UserApiRequest {
|
||||||
|
|
Loading…
Reference in a new issue