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:
parent
50995c3f0c
commit
4dc28942c0
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue