From 4e4fb630b46cf18984501dd426eebb688a356bb1 Mon Sep 17 00:00:00 2001
From: Valentino K <valentino@init.hr>
Date: Fri, 15 Apr 2022 14:30:27 +0200
Subject: [PATCH] Added user roles functionality

---
 src/components/Form/Select/Select.tsx         | 10 ++---
 .../CurrentUserModal/CurrentUserModal.tsx     | 43 ++++++++-----------
 .../components/CurrentUserModal/types.ts      |  4 +-
 .../users/components/UserModal/UserModal.tsx  | 39 ++++++++---------
 src/services/auth/redux/actions.ts            |  1 +
 src/services/auth/redux/reducers.ts           | 25 ++++++++---
 src/services/auth/transformations.ts          | 18 ++++++++
 src/services/auth/types.ts                    | 11 ++---
 src/services/users/redux/actions.ts           | 16 ++++---
 src/services/users/transformations.ts         | 27 ++++++------
 src/services/users/types.ts                   |  9 ++--
 11 files changed, 114 insertions(+), 89 deletions(-)
 create mode 100644 src/services/auth/transformations.ts

diff --git a/src/components/Form/Select/Select.tsx b/src/components/Form/Select/Select.tsx
index ab9b2382..b5416d2c 100644
--- a/src/components/Form/Select/Select.tsx
+++ b/src/components/Form/Select/Select.tsx
@@ -10,8 +10,6 @@ export const Select = ({ control, name, label, options }: SelectProps) => {
   } = useController({
     name,
     control,
-    rules: { required: true },
-    defaultValue: '',
   });
 
   return (
@@ -25,14 +23,14 @@ export const Select = ({ control, name, label, options }: SelectProps) => {
         id={name}
         onChange={field.onChange} // send value to hook form
         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
         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"
       >
-        {options?.map((value) => (
-          <option key={value} value={value}>
-            {value}
+        {options?.map((option) => (
+          <option key={option.value} value={option.value}>
+            {option.name}
           </option>
         ))}
       </select>
diff --git a/src/components/Header/components/CurrentUserModal/CurrentUserModal.tsx b/src/components/Header/components/CurrentUserModal/CurrentUserModal.tsx
index be7f18f0..cf578cb0 100644
--- a/src/components/Header/components/CurrentUserModal/CurrentUserModal.tsx
+++ b/src/components/Header/components/CurrentUserModal/CurrentUserModal.tsx
@@ -1,21 +1,21 @@
 import React, { useEffect } from 'react';
 import { useForm } from 'react-hook-form';
 import { Modal, Banner } from 'src/components';
-import { Input } from 'src/components/Form';
-import { useUsers } from 'src/services/users';
-import { CurrentUser } from 'src/services/auth';
+import { Input, Select } from 'src/components/Form';
+import { User, UserRole, useUsers } from 'src/services/users';
 import { appAccessList } from './consts';
 import { UserModalProps } from './types';
 
 export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
   const { editUserById, userModalLoading } = useUsers();
 
-  const { control, reset, handleSubmit } = useForm<CurrentUser>({
+  const { control, reset, handleSubmit } = useForm<User>({
     defaultValues: {
       name: null,
       email: null,
       id: null,
-      preferredUsername: null,
+      role_id: null,
+      status: null,
     },
   });
 
@@ -70,11 +70,23 @@ export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
               </div>
 
               <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 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">
                   Status
                 </label>
@@ -90,23 +102,6 @@ export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
                   </select>
                 </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>
 
diff --git a/src/components/Header/components/CurrentUserModal/types.ts b/src/components/Header/components/CurrentUserModal/types.ts
index 31814e0a..00bc7d6e 100644
--- a/src/components/Header/components/CurrentUserModal/types.ts
+++ b/src/components/Header/components/CurrentUserModal/types.ts
@@ -1,7 +1,7 @@
-import { CurrentUser } from 'src/services/auth';
+import { User } from 'src/services/users';
 
 export type UserModalProps = {
   open: boolean;
   onClose: () => void;
-  user: CurrentUser;
+  user: User;
 };
diff --git a/src/modules/users/components/UserModal/UserModal.tsx b/src/modules/users/components/UserModal/UserModal.tsx
index f00b0983..5b3814bf 100644
--- a/src/modules/users/components/UserModal/UserModal.tsx
+++ b/src/modules/users/components/UserModal/UserModal.tsx
@@ -2,9 +2,8 @@ import React, { useEffect, useState } from 'react';
 import { TrashIcon } from '@heroicons/react/outline';
 import { useForm } from 'react-hook-form';
 import { Modal, Banner, ConfirmationModal } from 'src/components';
-import { Input } from 'src/components/Form';
-import { useUsers } from 'src/services/users';
-import { CurrentUserState } from 'src/services/users/redux';
+import { Input, Select } from 'src/components/Form';
+import { User, UserRole, useUsers } from 'src/services/users';
 import { useAuth } from 'src/services/auth';
 import { appAccessList } from './consts';
 import { UserModalProps } from './types';
@@ -14,11 +13,12 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
   const { user, loadUser, editUserById, createNewUser, userModalLoading, deleteUserById } = useUsers();
   const { currentUser } = useAuth();
 
-  const { control, reset, handleSubmit } = useForm<CurrentUserState>({
+  const { control, reset, handleSubmit } = useForm<User>({
     defaultValues: {
       name: null,
       email: null,
       id: null,
+      role_id: null,
       status: null,
     },
   });
@@ -117,7 +117,19 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
                 </div>
 
                 <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 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>
                   </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>
 
diff --git a/src/services/auth/redux/actions.ts b/src/services/auth/redux/actions.ts
index 9fb3bcab..cde2b8eb 100644
--- a/src/services/auth/redux/actions.ts
+++ b/src/services/auth/redux/actions.ts
@@ -6,6 +6,7 @@ export enum AuthActionTypes {
   SIGN_IN_SUCCESS = 'auth/sign_in_success',
   SIGN_IN_FAILURE = 'auth/sign_in_failure',
   SIGN_OUT = 'auth/SIGN_OUT',
+  UPDATE_AUTH_USER = 'auth/update_auth_user',
   REGISTRATION_START = 'auth/registration_start',
   REGISTRATION_FAILURE = 'auth/registration_failure',
 }
diff --git a/src/services/auth/redux/reducers.ts b/src/services/auth/redux/reducers.ts
index d2d05f2f..8deb265e 100644
--- a/src/services/auth/redux/reducers.ts
+++ b/src/services/auth/redux/reducers.ts
@@ -1,14 +1,17 @@
 import { createApiReducer, chainReducers, INITIAL_API_STATUS } from 'src/services/api';
 
+import { User } from 'src/services/users';
 import { AuthState } from './types';
 import { AuthActionTypes } from './actions';
-import { CurrentUser } from '../types';
+import { transformAuthUser } from '../transformations';
 
-const initialCurrentUserState: CurrentUser = {
+const initialCurrentUserState: User = {
   email: null,
   name: null,
-  preferredUsername: null,
   id: null,
+  role_id: null,
+  status: null,
+  preferredUsername: null,
 };
 
 const initialState: AuthState = {
@@ -17,9 +20,21 @@ const initialState: AuthState = {
   _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(
   [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,
 );
 
@@ -29,4 +44,4 @@ const signOut = createApiReducer(
   () => initialState,
 );
 
-export default chainReducers(initialState, auth, signOut);
+export default chainReducers(initialState, auth, signOut, authLocalReducer);
diff --git a/src/services/auth/transformations.ts b/src/services/auth/transformations.ts
new file mode 100644
index 00000000..2cfebaf6
--- /dev/null
+++ b/src/services/auth/transformations.ts
@@ -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,
+    },
+  };
+};
diff --git a/src/services/auth/types.ts b/src/services/auth/types.ts
index c7ac644d..f1963855 100644
--- a/src/services/auth/types.ts
+++ b/src/services/auth/types.ts
@@ -1,11 +1,6 @@
+import { User } from '../users';
+
 export interface Auth {
   token: string | null;
-  userInfo: CurrentUser;
-}
-
-export interface CurrentUser {
-  email: string | null;
-  name: string | null;
-  preferredUsername: string | null;
-  id: string | null;
+  userInfo: User;
 }
diff --git a/src/services/users/redux/actions.ts b/src/services/users/redux/actions.ts
index dbcabe8a..67be7eee 100644
--- a/src/services/users/redux/actions.ts
+++ b/src/services/users/redux/actions.ts
@@ -1,7 +1,8 @@
 import { Dispatch } from 'redux';
 import { showToast, ToastType } from 'src/common/util/show-toast';
 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 {
   FETCH_USERS = 'users/fetch_users',
@@ -38,7 +39,7 @@ export const fetchUsers = () => async (dispatch: Dispatch<any>) => {
 
     dispatch({
       type: UserActionTypes.FETCH_USERS,
-      payload: data.map(transformResponseUser),
+      payload: data.map(transformUser),
     });
   } catch (err) {
     console.error(err);
@@ -58,7 +59,7 @@ export const fetchUserById = (id: string) => async (dispatch: Dispatch<any>) =>
 
     dispatch({
       type: UserActionTypes.FETCH_USER,
-      payload: transformResponseUser(data),
+      payload: transformUser(data),
     });
   } catch (err) {
     console.error(err);
@@ -79,7 +80,12 @@ export const updateUserById = (user: any) => async (dispatch: Dispatch<any>) =>
 
     dispatch({
       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);
@@ -104,7 +110,7 @@ export const createUser = (user: any) => async (dispatch: Dispatch<any>) => {
 
     dispatch({
       type: UserActionTypes.CREATE_USER,
-      payload: transformResponseUser(data),
+      payload: transformUser(data),
     });
 
     showToast('User created successfully.', ToastType.Success);
diff --git a/src/services/users/transformations.ts b/src/services/users/transformations.ts
index db77f9a4..5bef470c 100644
--- a/src/services/users/transformations.ts
+++ b/src/services/users/transformations.ts
@@ -1,31 +1,32 @@
 import _ from 'lodash';
 
-import { User } from './types';
+import { User, UserRole } from './types';
 
-export const transformResponseUser = (response: any): User => {
+export const transformUser = (response: any): User => {
   const userResponse = _.get(response, 'user', response);
 
+  const resolvedUserRole = !userResponse.traits.role_id ? UserRole.User : userResponse.traits.role_id;
+
   return {
     id: userResponse.id,
+    role_id: resolvedUserRole,
     email: userResponse.traits.email,
     name: userResponse.traits.name ?? null,
+    preferredUsername: userResponse.preferredUsername,
     status: userResponse.state,
   };
 };
 
-export const transformUser = (response: any): User => {
-  const userResponse = _.get(response, 'user', response);
-
-  return {
-    id: userResponse.id,
-    email: userResponse.email,
-    name: userResponse.name,
-    status: userResponse.status,
-  };
-};
+export const transformRequestUser = (data: Pick<User, 'role_id' | 'name' | 'email'>) => {
+  if (data.role_id === UserRole.User) {
+    return {
+      email: data.email,
+      name: data.name,
+    };
+  }
 
-export const transformRequestUser = (data: any) => {
   return {
+    role_id: Number(data.role_id),
     email: data.email,
     name: data.name,
   };
diff --git a/src/services/users/types.ts b/src/services/users/types.ts
index 1e509922..2ab4e1b7 100644
--- a/src/services/users/types.ts
+++ b/src/services/users/types.ts
@@ -1,7 +1,9 @@
 export interface User {
   id: number | null;
+  role_id: UserRole | null;
   email: string | null;
   name: string | null;
+  preferredUsername: string | null;
   status: string | null;
 }
 
@@ -10,10 +12,9 @@ export interface FormUser extends User {
   confirmPassword?: string;
 }
 
-export interface UserRole {
-  id?: number;
-  name: string;
-  isAdministrator: boolean;
+export enum UserRole {
+  Admin = '1',
+  User = '2',
 }
 
 export interface UserApiRequest {
-- 
GitLab