Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create custom role #473

Merged
merged 12 commits into from
Mar 5, 2024
Merged
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
JeevaRamu0104 marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading