Skip to content

Commit

Permalink
feat: Create custom role (#473)
Browse files Browse the repository at this point in the history
Co-authored-by: Jeeva Ramachandran <[email protected]>
Co-authored-by: Pritish Budhiraja <[email protected]>
Co-authored-by: Jeeva Ramachandran <[email protected]>
  • Loading branch information
4 people authored Mar 5, 2024
1 parent 0cdf96d commit d5739ad
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 28 deletions.
4 changes: 4 additions & 0 deletions src/entryPoints/HyperSwitchApp.res
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ let make = () => {
<AccessControl permission=userPermissionJson.usersManage>
<InviteUsers />
</AccessControl>
| list{"users", "create-custom-role"} =>
<AccessControl permission=userPermissionJson.usersManage>
<CreateCustomRole />
</AccessControl>
| list{"users", ...remainingPath} =>
<AccessControl permission=userPermissionJson.usersView>
<EntityScaffold
Expand Down
2 changes: 1 addition & 1 deletion src/screens/APIUtils/APIUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ let getURL = (
| Get => `${userUrl}/switch/list`
| _ => `${userUrl}/${(userType :> string)->String.toLowerCase}`
}
| #GET_PERMISSIONS => `${userUrl}/role`
| #GET_PERMISSIONS | #CREATE_CUSTOM_ROLE => `${userUrl}/role`
| #SIGNINV2 => `${userUrl}/v2/signin`
| #VERIFY_EMAILV2 => `${userUrl}/v2/verify_email`
| #ACCEPT_INVITE => `${userUrl}/user/invite/accept`
Expand Down
1 change: 1 addition & 0 deletions src/screens/APIUtils/APIUtilsTypes.res
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,6 @@ type userType = [
| #CREATE_MERCHANT
| #ACCEPT_INVITE
| #GET_PERMISSIONS
| #CREATE_CUSTOM_ROLE
| #NONE
]
180 changes: 180 additions & 0 deletions src/screens/UserManagement/CreateCustomRole.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
module RenderCustomRoles = {
@react.component
let make = (~heading, ~description, ~groupName) => {
let groupsInput = ReactFinalForm.useField(`groups`).input
let groupsAdded = groupsInput.value->LogicUtils.getStrArryFromJson
let (checkboxSelected, setCheckboxSelected) = React.useState(_ =>
groupsAdded->Array.includes(groupName)
)
let onClickGroup = groupName => {
if !(groupsAdded->Array.includes(groupName)) {
let _ = groupsAdded->Array.push(groupName)
groupsInput.onChange(groupsAdded->Identity.arrayOfGenericTypeToFormReactEvent)
} else {
let arr = groupsInput.value->LogicUtils.getStrArryFromJson

let filteredValue = arr->Array.filter(value => {value !== groupName})
groupsInput.onChange(filteredValue->Identity.arrayOfGenericTypeToFormReactEvent)
}
setCheckboxSelected(prev => !prev)
}

<UIUtils.RenderIf
condition={groupName->PermissionUtils.mapStringToPermissionType !== OrganizationManage}>
<div className="flex gap-6 items-start">
<div className="mt-1">
<CheckBoxIcon
isSelected={checkboxSelected}
setIsSelected={_ => {
onClickGroup(groupName)
}}
size={Large}
/>
</div>
<div className="flex flex-col gap-3 items-start">
<div className="font-semibold"> {heading->React.string} </div>
<div className="text-base text-hyperswitch_black opacity-50 flex-1">
{description->React.string}
</div>
</div>
</div>
</UIUtils.RenderIf>
}
}

module NewCustomRoleInputFields = {
open UserManagementUtils
@react.component
let make = () => {
let userRole = HSLocalStorage.getFromUserDetails("user_role")
<div className="flex justify-between">
<div className="flex flex-col gap-4 w-full">
<FormRenderer.FieldRenderer
field={userRole->roleScope}
fieldWrapperClass="w-4/5"
labelClass="!text-black !text-base !-ml-[0.5px]"
/>
<FormRenderer.FieldRenderer
field=createCustomRole
fieldWrapperClass="w-4/5"
labelClass="!text-black !text-base !-ml-[0.5px]"
/>
</div>
<div className="absolute top-10 right-5">
<FormRenderer.SubmitButton text="Create role" loadingText="Loading..." />
</div>
</div>
}
}

@react.component
let make = (~isInviteUserFlow=true, ~setNewRoleSelected=_ => ()) => {
open APIUtils
open LogicUtils
open UIUtils
let fetchDetails = useGetMethod()
let updateDetails = useUpdateMethod()

let initialValuesForForm =
[
("role_scope", "merchant"->JSON.Encode.string),
("groups", []->JSON.Encode.array),
]->Dict.fromArray

let {permissionInfo, setPermissionInfo} = React.useContext(GlobalProvider.defaultContext)
let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Loading)
let (initalValue, setInitialValues) = React.useState(_ => initialValuesForForm)

let paddingClass = isInviteUserFlow ? "p-10" : ""
let marginClass = isInviteUserFlow ? "mt-5" : ""
let showToast = ToastState.useShowToast()
let onSubmit = async (values, _) => {
try {
// TODO - Seperate RoleName & RoleId in Backend. role_name as free text and role_id as snake_text
setScreenState(_ => PageLoaderWrapper.Loading)
let copiedJson = Js.Json.parseExn(Js.Json.stringify(values))
let url = getURL(~entityName=USERS, ~userType=#CREATE_CUSTOM_ROLE, ~methodType=Post, ())

let body = copiedJson->getDictFromJsonObject->JSON.Encode.object
let roleNameValue =
body->getDictFromJsonObject->getString("role_name", "")->String.trim->titleToSnake
body->getDictFromJsonObject->Dict.set("role_name", roleNameValue->JSON.Encode.string)
let _ = await updateDetails(url, body, Post, ())
setScreenState(_ => PageLoaderWrapper.Success)
RescriptReactRouter.replace("/users")
} catch {
| Exn.Error(e) => {
let err = Exn.message(e)->Option.getOr("Something went wrong")
let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "")
let errorMessage = err->safeParse->getDictFromJsonObject->getString("message", "")
if errorCode === "UR_35" {
setInitialValues(_ => values->LogicUtils.getDictFromJsonObject)
setScreenState(_ => PageLoaderWrapper.Success)
} else {
showToast(~message=errorMessage, ~toastType=ToastError, ())
setScreenState(_ => PageLoaderWrapper.Error(err))
}
}
}
Nullable.null
}

let getPermissionInfo = async () => {
try {
setScreenState(_ => PageLoaderWrapper.Loading)
let url = getURL(~entityName=USERS, ~userType=#PERMISSION_INFO, ~methodType=Get, ())
let res = await fetchDetails(`${url}?groups=true`)
let permissionInfoValue = res->getArrayDataFromJson(ProviderHelper.itemToObjMapperForGetInfo)
setPermissionInfo(_ => permissionInfoValue)
setScreenState(_ => PageLoaderWrapper.Success)
} catch {
| _ => setScreenState(_ => PageLoaderWrapper.Error("Something went wrong!"))
}
}

React.useEffect0(() => {
if permissionInfo->Array.length === 0 {
getPermissionInfo()->ignore
} else {
setScreenState(_ => PageLoaderWrapper.Success)
}
None
})

<div className="flex flex-col overflow-y-scroll h-full">
<RenderIf condition={isInviteUserFlow}>
<BreadCrumbNavigation
path=[{title: "Users", link: "/users"}] currentPageTitle="Create custom roles"
/>
<PageUtils.PageHeading
title="Create custom roles" subTitle="A new custom role will be created"
/>
</RenderIf>
<div
className={`h-4/5 bg-white relative overflow-y-scroll flex flex-col gap-10 ${paddingClass} ${marginClass}`}>
<PageLoaderWrapper screenState>
<Form
key="invite-user-management"
initialValues={initalValue->JSON.Encode.object}
validate={values => values->UserManagementUtils.validateFormForRoles}
onSubmit
formClass="flex flex-col gap-8">
<NewCustomRoleInputFields />
<div className="flex flex-col justify-between gap-12 show-scrollbar overflow-scroll">
{permissionInfo
->Array.mapWithIndex((ele, index) => {
<RenderCustomRoles
key={index->Int.toString}
heading={`${ele.module_->snakeToTitle}`}
description={ele.description}
groupName={ele.module_}
/>
})
->React.array}
</div>
<FormValuesSpy />
</Form>
</PageLoaderWrapper>
</div>
</div>
}
57 changes: 57 additions & 0 deletions src/screens/UserManagement/RoleListTableView.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@react.component
let make = () => {
open APIUtils
open RolesEntity

let fetchDetails = useGetMethod()
let (screenStateRoles, setScreenStateRoles) = React.useState(_ => PageLoaderWrapper.Loading)
let (rolesAvailableData, setRolesAvailableData) = React.useState(_ => [])
let (rolesOffset, setRolesOffset) = React.useState(_ => 0)

let getRolesAvailable = async () => {
setScreenStateRoles(_ => PageLoaderWrapper.Loading)
try {
let userDataURL = getURL(
~entityName=USER_MANAGEMENT,
~methodType=Get,
~userRoleTypes=ROLE_LIST,
(),
)
let res = await fetchDetails(`${userDataURL}?groups=true`)
let rolesData = res->LogicUtils.getArrayDataFromJson(itemToObjMapperForRoles)
setRolesAvailableData(_ => rolesData->Array.map(Nullable.make))
setScreenStateRoles(_ => PageLoaderWrapper.Success)
} catch {
| _ => setScreenStateRoles(_ => PageLoaderWrapper.Error(""))
}
}

React.useEffect0(() => {
if rolesAvailableData->Array.length == 0 {
getRolesAvailable()->ignore
} else {
setScreenStateRoles(_ => PageLoaderWrapper.Success)
}

None
})

<div className="mt-5">
<PageLoaderWrapper screenState={screenStateRoles}>
<LoadedTable
title="Roles"
hideTitle=true
actualData=rolesAvailableData
totalResults={rolesAvailableData->Array.length}
resultsPerPage=10
offset=rolesOffset
setOffset=setRolesOffset
entity={rolesEntity}
currrentFetchCount={rolesAvailableData->Array.length}
showSerialNumber=true
collapseTableRow=false
tableheadingClass="h-12"
/>
</PageLoaderWrapper>
</div>
}
65 changes: 65 additions & 0 deletions src/screens/UserManagement/RolesEntity.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
open LogicUtils

type rolesTableTypes = {
role_name: string,
role_scope: string,
groups: array<Js.Json.t>,
}

type rolesColTypes =
| RoleName
| RoleScope
| ModulePermissions

let defaultColumnsForRoles = [RoleName, RoleScope, ModulePermissions]

let allColumnsForUser = [RoleName, RoleScope, ModulePermissions]

let itemToObjMapperForRoles = dict => {
{
role_name: getString(dict, "role_name", ""),
role_scope: getString(dict, "role_scope", ""),
groups: getArrayFromDict(dict, "groups", []),
}
}

let getHeadingForRoles = (colType: rolesColTypes) => {
switch colType {
| RoleName => Table.makeHeaderInfo(~key="role_name", ~title="Role name", ~showSort=true, ())
| RoleScope => Table.makeHeaderInfo(~key="role_scope", ~title="Role scope", ~showSort=true, ())
| ModulePermissions => Table.makeHeaderInfo(~key="groups", ~title="Module permissions", ())
}
}

let getCellForRoles = (data: rolesTableTypes, colType: rolesColTypes): Table.cell => {
switch colType {
| RoleName => Text(data.role_name->LogicUtils.snakeToTitle)
| RoleScope => Text(data.role_scope->LogicUtils.capitalizeString)
| ModulePermissions =>
Table.CustomCell(
<div>
{data.groups
->LogicUtils.getStrArrayFromJsonArray
->Array.map(item => `${item->LogicUtils.snakeToTitle}`)
->Array.joinWith(", ")
->React.string}
</div>,
"",
)
}
}

let getrolesData: JSON.t => array<rolesTableTypes> = json => {
getArrayDataFromJson(json, itemToObjMapperForRoles)
}

let rolesEntity = EntityType.makeEntity(
~uri="",
~getObjects=getrolesData,
~defaultColumns=defaultColumnsForRoles,
~allColumns=allColumnsForUser,
~getHeading=getHeadingForRoles,
~getCell=getCellForRoles,
~dataKey="",
(),
)
2 changes: 2 additions & 0 deletions src/screens/UserManagement/UserManagementTypes.res
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
type userManagementTypes = Users | Roles

type permissionType =
| OperationsView
| OperationsManage
Expand Down
Loading

0 comments on commit d5739ad

Please sign in to comment.