Allow to remove a Safe-app added manually (#1260)

* partial imp

* Delete safe apps added manually

* Fix and review changes

* remove unnecesary exports

* Fix deleteApp and sort in modal list
This commit is contained in:
nicolas 2020-08-25 17:19:01 -03:00 committed by GitHub
parent 50995c3f0c
commit 4dc28942c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 882 additions and 41 deletions

View File

@ -164,7 +164,7 @@
"dependencies": { "dependencies": {
"@gnosis.pm/safe-apps-sdk": "0.3.1", "@gnosis.pm/safe-apps-sdk": "0.3.1",
"@gnosis.pm/safe-contracts": "1.1.1-dev.2", "@gnosis.pm/safe-contracts": "1.1.1-dev.2",
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#45c746a12661b9c38e839e76022b6a0a92285db7", "@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#fd4498f",
"@gnosis.pm/util-contracts": "2.0.6", "@gnosis.pm/util-contracts": "2.0.6",
"@ledgerhq/hw-transport-node-hid": "5.19.1", "@ledgerhq/hw-transport-node-hid": "5.19.1",
"@material-ui/core": "4.11.0", "@material-ui/core": "4.11.0",

View File

@ -71,6 +71,7 @@ const AddApp = ({ appList, closeModal, formId, onAppAdded, setIsSubmitDisabled }
<StyledText size="xl">Add custom app</StyledText> <StyledText size="xl">Add custom app</StyledText>
<AppUrl appList={appList} /> <AppUrl appList={appList} />
{/* Fetch app from url and return a SafeApp */}
<AppInfoUpdater onAppInfo={setAppInfo} /> <AppInfoUpdater onAppInfo={setAppInfo} />
<AppInfo> <AppInfo>

View File

@ -11,9 +11,10 @@ type Props = {
appList: Array<SafeApp> appList: Array<SafeApp>
onAppAdded: (app: SafeApp) => void onAppAdded: (app: SafeApp) => void
onAppToggle: (appId: string, enabled: boolean) => void onAppToggle: (appId: string, enabled: boolean) => void
onAppRemoved: (appId: string) => void
} }
const ManageApps = ({ appList, onAppAdded, onAppToggle }: Props): React.ReactElement => { const ManageApps = ({ appList, onAppAdded, onAppToggle, onAppRemoved }: Props): React.ReactElement => {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true) const [isSubmitDisabled, setIsSubmitDisabled] = useState(true)
@ -54,12 +55,14 @@ const ManageApps = ({ appList, onAppAdded, onAppToggle }: Props): React.ReactEle
{isOpen && ( {isOpen && (
<ManageListModal <ManageListModal
addButtonLabel="Add custom app" addButtonLabel="Add custom app"
showDeleteButton
defaultIconUrl={appsIconSvg} defaultIconUrl={appsIconSvg}
formBody={Form} formBody={Form}
isSubmitFormDisabled={isSubmitDisabled} isSubmitFormDisabled={isSubmitDisabled}
itemList={getItemList()} itemList={getItemList()}
onClose={closeModal} onClose={closeModal}
onItemToggle={onItemToggle} onItemToggle={onItemToggle}
onItemDeleted={onAppRemoved}
onSubmitForm={onSubmitForm} onSubmitForm={onSubmitForm}
/> />
)} )}

View File

@ -7,12 +7,14 @@ const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY'
type onAppToggleHandler = (appId: string, enabled: boolean) => Promise<void> type onAppToggleHandler = (appId: string, enabled: boolean) => Promise<void>
type onAppAddedHandler = (app: SafeApp) => void type onAppAddedHandler = (app: SafeApp) => void
type onAppRemovedHandler = (appId: string) => void
type UseAppListReturnType = { type UseAppListReturnType = {
appList: SafeApp[] appList: SafeApp[]
loadingAppList: boolean loadingAppList: boolean
onAppToggle: onAppToggleHandler onAppToggle: onAppToggleHandler
onAppAdded: onAppAddedHandler onAppAdded: onAppAddedHandler
onAppRemoved: onAppRemovedHandler
} }
const useAppList = (): UseAppListReturnType => { const useAppList = (): UseAppListReturnType => {
@ -26,32 +28,40 @@ const useAppList = (): UseAppListReturnType => {
// * third-party apps added by the user // * third-party apps added by the user
// * disabled status for both static and third-party apps // * disabled status for both static and third-party apps
const persistedAppList = (await loadFromStorage<StoredSafeApp[]>(APPS_STORAGE_KEY)) || [] const persistedAppList = (await loadFromStorage<StoredSafeApp[]>(APPS_STORAGE_KEY)) || []
const list = [...persistedAppList] const list: (StoredSafeApp & { isDeletable?: boolean })[] = persistedAppList.map((a) => ({
...a,
isDeletable: true,
}))
staticAppsList.forEach((staticApp) => { staticAppsList.forEach((staticApp) => {
if (!list.some((persistedApp) => persistedApp.url === staticApp.url)) { const app = list.find((persistedApp) => persistedApp.url === staticApp.url)
list.push(staticApp) if (!app) {
list.push({ ...staticApp, isDeletable: false })
} else {
app.isDeletable = false
} }
}) })
const apps = [] let apps = []
// using the appURL to recover app info // using the appURL to recover app info
for (let index = 0; index < list.length; index++) { for (let index = 0; index < list.length; index++) {
try { try {
const currentApp = list[index] const currentApp = list[index]
const appInfo: any = await getAppInfoFromUrl(currentApp.url) const appInfo: SafeApp = await getAppInfoFromUrl(currentApp.url)
if (appInfo.error) { if (appInfo.error) {
throw Error(`There was a problem trying to load app ${currentApp.url}`) throw Error(`There was a problem trying to load app ${currentApp.url}`)
} }
appInfo.disabled = currentApp.disabled === undefined ? false : currentApp.disabled appInfo.disabled = Boolean(currentApp.disabled)
appInfo.isDeletable = Boolean(currentApp.isDeletable) === undefined ? true : currentApp.isDeletable
apps.push(appInfo) apps.push(appInfo)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
} }
apps = apps.sort((a, b) => a.name.localeCompare(b.name))
setAppList(apps) setAppList(apps)
setLoadingAppList(false) setLoadingAppList(false)
@ -69,8 +79,8 @@ const useAppList = (): UseAppListReturnType => {
if (!app) { if (!app) {
return return
} }
app.disabled = !enabled app.disabled = !enabled
setAppList(appListCopy) setAppList(appListCopy)
// update storage list // update storage list
@ -91,7 +101,19 @@ const useAppList = (): UseAppListReturnType => {
] ]
saveToStorage(APPS_STORAGE_KEY, newAppList) saveToStorage(APPS_STORAGE_KEY, newAppList)
setAppList([...appList, { ...app, disabled: false }]) setAppList([...appList, { ...app, isDeletable: true }])
},
[appList],
)
const onAppRemoved: onAppRemovedHandler = useCallback(
(appId) => {
const appListCopy = appList.filter((a) => a.id !== appId)
setAppList(appListCopy)
const listToPersist: StoredSafeApp[] = appListCopy.map(({ url, disabled }) => ({ url, disabled }))
saveToStorage(APPS_STORAGE_KEY, listToPersist)
}, },
[appList], [appList],
) )
@ -101,6 +123,7 @@ const useAppList = (): UseAppListReturnType => {
loadingAppList, loadingAppList,
onAppToggle, onAppToggle,
onAppAdded, onAppAdded,
onAppRemoved,
} }
} }

View File

@ -44,7 +44,7 @@ type AppsProps = {
} }
const Apps = ({ closeModal, openModal }: AppsProps): React.ReactElement => { const Apps = ({ closeModal, openModal }: AppsProps): React.ReactElement => {
const { appList, loadingAppList, onAppToggle, onAppAdded } = useAppList() const { appList, loadingAppList, onAppToggle, onAppAdded, onAppRemoved } = useAppList()
const [appIsLoading, setAppIsLoading] = useState<boolean>(true) const [appIsLoading, setAppIsLoading] = useState<boolean>(true)
const [selectedAppId, setSelectedAppId] = useState<string>() const [selectedAppId, setSelectedAppId] = useState<string>()
@ -111,7 +111,7 @@ const Apps = ({ closeModal, openModal }: AppsProps): React.ReactElement => {
return ( return (
<> <>
<Menu> <Menu>
<ManageApps appList={appList} onAppAdded={onAppAdded} onAppToggle={onAppToggle} /> <ManageApps appList={appList} onAppAdded={onAppAdded} onAppToggle={onAppToggle} onAppRemoved={onAppRemoved} />
</Menu> </Menu>
{enabledApps.length ? ( {enabledApps.length ? (
<LCL.Wrapper> <LCL.Wrapper>

View File

@ -4,6 +4,7 @@ export type SafeApp = {
name: string name: string
iconUrl: string iconUrl: string
disabled?: boolean disabled?: boolean
isDeletable?: boolean
error: boolean error: boolean
description: string description: string
} }

871
yarn.lock

File diff suppressed because it is too large Load Diff