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, } }