diff --git a/src/components/Garden/Preferences/GlobalPreferences.js b/src/components/Garden/Preferences/GlobalPreferences.js
index 989a1424e..9bca38313 100644
--- a/src/components/Garden/Preferences/GlobalPreferences.js
+++ b/src/components/Garden/Preferences/GlobalPreferences.js
@@ -11,6 +11,7 @@ import {
useTheme,
useViewport,
} from '@1hive/1hive-ui'
+import styled from 'styled-components'
import { Transition, animated } from 'react-spring/renderprops'
import { useConnectedGarden } from '@providers/ConnectedGarden'
import { useWallet } from '@/providers/Wallet'
@@ -18,16 +19,40 @@ import { useEsc } from '../../../hooks/useKeyboardArrows'
import AppsAddresses from './AppsAddresses'
import EVMExecutor from './EVMExecutor'
+import Permissions from './Permissions'
+
+
+const LayoutWrapper = styled(Layout)`
+ z-index: 2;
+ height: 100%;
+`
+const ParentDiv = styled.div`
+ display: flex;
+ height: 100%;
+ outline: 0;
+`
+
+const RootWrapper = styled(Root.Provider)`
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+`
+
+const HeaderWrapper = styled(Header)`
+ padding-top: ${(props) => !props.compact ? 10 * GU : 0}px;
+`
const SECTIONS = new Map([
['generalInfo', 'General Info'],
['evmExecutor', 'EVM Executor'],
+ ['permissions', 'Permissions']
])
const PATHS = Array.from(SECTIONS.keys())
const VALUES = Array.from(SECTIONS.values())
const GENERAL_INFO_INDEX = 0
const EVM_EXECUTOR_INDEX = 1
+const PERMISSIONS_INDEX = 2
const AnimatedDiv = animated.div
@@ -65,17 +90,18 @@ function GlobalPreferences({ compact, onClose, onNavigation, sectionIndex }) {
getEvmCrispr()
}, [account, connectedGarden, ethers, isSafari])
+ console.log('sectionIndex ', sectionIndex)
+
return (
-
-
+
+
-
-
+
+
+
+
)}
+ {sectionIndex === PERMISSIONS_INDEX && }
-
-
-
+
+
+
)
}
diff --git a/src/components/Garden/Preferences/Permissions.tsx b/src/components/Garden/Preferences/Permissions.tsx
new file mode 100644
index 000000000..7450a4d0a
--- /dev/null
+++ b/src/components/Garden/Preferences/Permissions.tsx
@@ -0,0 +1,118 @@
+/* eslint-disable react/jsx-key */
+import React from 'react'
+import { AppBadge, DataView, IdentityBadge, textStyle } from '@1hive/1hive-ui'
+import { useGardenState } from '@providers/GardenState'
+import { useRoles } from '@/hooks/useRoles'
+import Loader from '@components/Loader'
+
+const ENTRIES_PER_PAGE = 10
+
+interface EntryProps {
+ appAddress: string,
+ appIcon: string,
+ appName: string,
+ description: string,
+ manager: any,
+ permissions: any
+}
+
+
+function Permissions() {
+ const [appRoles, loading] = useRoles()
+ const { installedApps } = useGardenState()
+
+ const fields = [
+ 'Action',
+ 'On app',
+ { label: 'Assigned to entity', childStart: true },
+ 'Managed by',
+ ]
+
+ return (
+
+ {loading ?
+ (
+
+
+ ) :
+ renderEntry(entry, installedApps)}
+ renderEntryExpansion={(entry: EntryProps) => renderEntryExpansion(entry, installedApps)}
+ />
+ }
+
+ )
+}
+
+interface EntryProps {
+ appAddress: string,
+ appIcon: string,
+ appName: string,
+ description: string,
+ manager: any,
+ permissions: any
+}
+
+function EntryEntities({ permissions, installedApps }: { permissions: any, installedApps: any }) {
+ const allowedEntities = permissions.filter((permission: any) => permission.allowed === true)
+
+ if (allowedEntities.length === 1) {
+ return
+ }
+
+ return
+ {allowedEntities.length} entities
+
+}
+
+function EntityBadge({ address, name, icon, installedApps }: { address: string, name: string, icon: string, installedApps: any }) {
+ if (installedApps.find((app: any) => app.address === address)) {
+
+ return
+ }
+
+ return
+}
+
+
+function renderEntry(entry: EntryProps, installedApps: any) {
+ const { appAddress, appIcon, appName, description, manager, permissions } = entry
+
+ const cells = [
+
+ {description}
+ ,
+ ,
+ ,
+
+
+ ]
+ return cells
+}
+
+function renderEntryExpansion(entry: EntryProps, installedApps: any) {
+ const allowedEntities = entry.permissions.filter((permission: any) => permission.allowed === true)
+
+ return allowedEntities.length < 2
+ ? null
+ : allowedEntities.map((entity: any) => (
+
+ ))
+}
+
+
+
+
+
+export default Permissions
\ No newline at end of file
diff --git a/src/environment.js b/src/environment.js
index fc30c25e4..b86f6a050 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -1,3 +1,4 @@
+const DEFAULT_AGENT_APP_NAME = 'agent'
const DEFAULT_AGREEMENT_APP_NAME = 'agreement'
const DEFAULT_CONVICTION_APP_NAME = 'disputable-conviction-voting'
const DEFAULT_HOOKED_TOKEN_MANAGER = 'wrappable-hooked-token-manager'
diff --git a/src/hooks/useGardenHooks.ts b/src/hooks/useGardenHooks.ts
index a21806e8c..d47a964e1 100644
--- a/src/hooks/useGardenHooks.ts
+++ b/src/hooks/useGardenHooks.ts
@@ -226,4 +226,4 @@ export function useTokenBalances(
}, [account, balances, timeout, tokenContract, token])
return balances
-}
+}
\ No newline at end of file
diff --git a/src/hooks/usePromise.ts b/src/hooks/usePromise.ts
index 56cb278d4..da75e741d 100644
--- a/src/hooks/usePromise.ts
+++ b/src/hooks/usePromise.ts
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'
export default function usePromise(
fn: () => Promise,
memoParams: Array,
- defaultValue: boolean
+ defaultValue: any
) {
const [result, setResult] = useState(defaultValue)
diff --git a/src/hooks/useRoles.ts b/src/hooks/useRoles.ts
new file mode 100644
index 000000000..20c727b50
--- /dev/null
+++ b/src/hooks/useRoles.ts
@@ -0,0 +1,90 @@
+import { useEffect, useMemo, useState } from 'react'
+import { useGardenState } from '@providers/GardenState'
+import usePromise from '@hooks/usePromise'
+import { getAppPresentationByAddress } from '@utils/app-utils'
+
+const ANY_ADDRESS = "0xffffffffffffffffffffffffffffffffffffffff"
+
+interface ManagerProps {
+ address: string,
+ shortenedName: string,
+}
+
+interface PermissionProps {
+ allowed: boolean,
+ appAddress: string,
+ granteeAddress: string,
+ granteeName: string | undefined,
+ params: Array,
+ roleHash: string
+}
+
+interface RoleProps {
+ appAddress: string,
+ appName: string,
+ appIcon: string,
+ description: string | undefined, // TODO- check why this is happening proabably old ABI
+ hash: string,
+ manager: Array,
+ name: string,
+ params: Array,
+ permissions: Array
+}
+
+export function useRoles() {
+ const [loading, setLoading] = useState(true)
+ const gardenState = useGardenState()
+
+ const appsWithPermissions = useMemo(() => {
+ if (!gardenState?.installedApps) {
+ return async () => { null }
+ }
+ return () => Promise.all(gardenState?.installedApps.map(async (app: any) => {
+ return {
+ ...app,
+ roles: await app.roles()
+ }
+ }))
+ }, [gardenState])
+
+ const appsWithRolesResolved = usePromise(appsWithPermissions, [], [])
+
+ function isAnyAddress(address: string) {
+ return address === ANY_ADDRESS
+ }
+
+ const rolesWithEntitiesResolved: Array = appsWithRolesResolved ? appsWithRolesResolved.map((app: any) => {
+ return app.roles.map((role: any) => {
+ const appPresentation = getAppPresentationByAddress(gardenState?.installedApps, app.address)
+
+ return {
+ ...role,
+ appName: appPresentation?.shortenedName || appPresentation?.humanName,
+ appIcon: appPresentation?.iconSrc,
+ manager: {
+ address: role.manager,
+ shortenedName: getAppPresentationByAddress(gardenState?.installedApps, role.manager)?.shortenedName,
+ managerIcon: getAppPresentationByAddress(gardenState?.installedApps, role.manager)?.iconSrc
+ },
+ permissions: role.permissions.map((permission: any) => {
+ return {
+ ...permission,
+ granteeName: isAnyAddress(permission.granteeAddress) ? 'Any account' : getAppPresentationByAddress(gardenState?.installedApps, permission.granteeAddress)?.shortenedName,
+ granteeIcon: getAppPresentationByAddress(gardenState?.installedApps, permission.granteeAddress)?.iconSrc
+ }
+ })
+ }
+ })
+ }) : null
+
+ useEffect(() => {
+ if (rolesWithEntitiesResolved) {
+ setLoading(false)
+ }
+ }, [rolesWithEntitiesResolved])
+
+
+
+ return [rolesWithEntitiesResolved ? rolesWithEntitiesResolved.flat() : [], loading]
+
+}
diff --git a/src/hooks/useStakes.ts b/src/hooks/useStakes.ts
index 446b15f3c..e4f89c546 100644
--- a/src/hooks/useStakes.ts
+++ b/src/hooks/useStakes.ts
@@ -96,5 +96,5 @@ export function useInactiveProposalsWithStake(account: string) {
)
})
- return inactiveStakes
+ return inactiveStakes.flat()
}
diff --git a/src/utils/app-utils.ts b/src/utils/app-utils.ts
index 3db61b5d6..904918995 100644
--- a/src/utils/app-utils.ts
+++ b/src/utils/app-utils.ts
@@ -55,6 +55,7 @@ export function getAppPresentation(app: AppType): {
humanName: string
iconSrc: string
name?: string
+ shortenedName?: string
} | null {
const { contentUri, name, manifest, appId } = app
// Get human readable name and icon from manifest if available
@@ -66,6 +67,7 @@ export function getAppPresentation(app: AppType): {
humanName,
iconSrc: iconPath ? getIpfsUrlFromUri(contentUri) + iconPath : '',
name,
+ shortenedName: SHORTENED_APPS_NAMES.get(name) || name,
}
}