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

Fix: reload widget on safe change + catch errors #1319

Merged
merged 1 commit into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 58 additions & 45 deletions src/components/dashboard/GovernanceSection/GovernanceSection.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useRef } from 'react'
import { Typography, Card, Box, Alert, IconButton, Link, SvgIcon } from '@mui/material'
import { WidgetBody } from '@/components/dashboard/styled'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import Accordion from '@mui/material/Accordion'
import AccordionSummary from '@mui/material/AccordionSummary'
import AccordionDetails from '@mui/material/AccordionDetails'
import css from './styles.module.css'
import SafeAppsErrorBoundary from '@/components/safe-apps/SafeAppsErrorBoundary'
import { useBrowserPermissions } from '@/hooks/safe-apps/permissions'
import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
import { SafeAppsTag, SAFE_APPS_SUPPORT_CHAT_URL } from '@/config/constants'
Expand All @@ -18,35 +18,15 @@ import SafeAppIframe from '@/components/safe-apps/AppFrame/SafeAppIframe'
import type { UseAppCommunicatorHandlers } from '@/components/safe-apps/AppFrame/useAppCommunicator'
import useAppCommunicator from '@/components/safe-apps/AppFrame/useAppCommunicator'
import { useCurrentChain } from '@/hooks/useChains'
import useAppIsLoading from '@/components/safe-apps/AppFrame/useAppIsLoading'
import useGetSafeInfo from '@/components/safe-apps/AppFrame/useGetSafeInfo'
import type { SafeAppData } from '@gnosis.pm/safe-react-gateway-sdk'
import useSafeInfo from '@/hooks/useSafeInfo'
import { fetchSafeAppFromManifest } from '@/services/safe-apps/manifest'
import useAsync from '@/hooks/useAsync'

// Prevent `GovernanceSection` hooks from needlessly being called
const GovernanceSectionWrapper = () => {
const chainId = useChainId()
if (!getSafeTokenAddress(chainId)) {
return null
}

return <GovernanceSection />
}

const GovernanceSection = () => {
const isDarkMode = useDarkMode()
const theme = isDarkMode ? 'dark' : 'light'
const { getAllowedFeaturesList } = useBrowserPermissions()
const chain = useCurrentChain()
const [matchingApps, errorFetchingClaimingSafeApp] = useRemoteSafeApps(SafeAppsTag.SAFE_CLAIMING_APP)
const claimingApp = matchingApps?.[0]
const { iframeRef, setAppIsLoading } = useAppIsLoading()
const fetchingSafeClaimingApp = !claimingApp && !errorFetchingClaimingSafeApp

// Initialize the app communicator
useAppCommunicator(iframeRef, claimingApp, chain, {
onGetSafeInfo: useGetSafeInfo(),
} as Partial<UseAppCommunicatorHandlers> as UseAppCommunicatorHandlers)

const WidgetLoadError = () => (
// A fallback component when the Safe App fails to load
const WidgetLoadErrorFallback = () => (
<Box display="flex" flexDirection="column" alignItems="center" height="100%">
<Card className={css.loadErrorCard}>
<Box className={css.loadErrorMsgContainer}>
<Typography variant="h4" color="text.primary" fontWeight="bold">
Expand All @@ -62,14 +42,46 @@ const GovernanceSection = () => {
</Typography>
</Box>
</Card>
)
</Box>
)

// A mini Safe App frame with a minimal set of communication handlers
const MiniAppFrame = ({ app, title }: { app: SafeAppData; title: string }) => {
const chain = useCurrentChain()
const isDarkMode = useDarkMode()
const theme = isDarkMode ? 'dark' : 'light'
const { getAllowedFeaturesList } = useBrowserPermissions()
const iframeRef = useRef<HTMLIFrameElement>(null)

const WidgetLoadErrorFallback = () => (
<Box className={css.loadErrorWrapper} display="flex" gap={3} width={1} sx={{ backgroundColor: 'background.main' }}>
<WidgetLoadError />
<WidgetLoadError />
</Box>
const [, error] = useAsync(() => {
if (!chain?.chainId) return
return fetchSafeAppFromManifest(app.url, chain.chainId)
}, [app.url, chain?.chainId])

// Initialize the app communicator
useAppCommunicator(iframeRef, app, chain, {
onGetSafeInfo: useGetSafeInfo(),
} as Partial<UseAppCommunicatorHandlers> as UseAppCommunicatorHandlers)

return error ? (
<WidgetLoadErrorFallback />
) : (
<SafeAppIframe
key={theme}
appUrl={`${app.url}#widget+${theme}`}
allowedFeaturesList={getAllowedFeaturesList(app.url)}
title={title}
iframeRef={iframeRef}
/>
)
}

// Entire section for the governance widgets
const GovernanceSection = () => {
const [matchingApps, errorFetchingClaimingSafeApp] = useRemoteSafeApps(SafeAppsTag.SAFE_CLAIMING_APP)
const claimingApp = matchingApps?.[0]
const fetchingSafeClaimingApp = !claimingApp && !errorFetchingClaimingSafeApp
const { safeLoading } = useSafeInfo()

return (
<Accordion className={css.accordion} defaultExpanded>
Expand All @@ -94,17 +106,8 @@ const GovernanceSection = () => {
{claimingApp || fetchingSafeClaimingApp ? (
<WidgetBody>
<Card className={css.widgetWrapper}>
{claimingApp ? (
<SafeAppsErrorBoundary render={() => <WidgetLoadErrorFallback />}>
<SafeAppIframe
key={theme}
appUrl={`${claimingApp.url}#widget+${theme}`}
allowedFeaturesList={getAllowedFeaturesList(claimingApp.url)}
title="Sage Governance"
iframeRef={iframeRef}
onLoad={() => setAppIsLoading(false)}
/>
</SafeAppsErrorBoundary>
{claimingApp && !safeLoading ? (
<MiniAppFrame app={claimingApp} title="Safe Governance" />
) : (
<Box
className={css.widgetWrapper}
Expand All @@ -130,4 +133,14 @@ const GovernanceSection = () => {
)
}

// Prevent `GovernanceSection` hooks from needlessly being called
const GovernanceSectionWrapper = () => {
const chainId = useChainId()
if (!getSafeTokenAddress(chainId)) {
return null
}

return <GovernanceSection />
}

export default GovernanceSectionWrapper
14 changes: 2 additions & 12 deletions src/components/dashboard/GovernanceSection/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,16 @@
pointer-events: auto;
}

.loadErrorWrapper {
display: flex;
}

.widgetWrapper,
.loadErrorWrapper {
.widgetWrapper {
border: none;
height: 300px;
}

/* iframe sm breakpoint + paddings */
@media (max-width: 662px) {
.widgetWrapper,
.loadErrorWrapper {
.widgetWrapper {
height: 624px;
}

.loadErrorWrapper {
flex-direction: column;
}
}

.loadErrorCard {
Expand Down
4 changes: 2 additions & 2 deletions src/components/safe-apps/AppFrame/SafeAppIframe.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ReactElement } from 'react'
import type { MutableRefObject, ReactElement } from 'react'
import css from './styles.module.css'

type SafeAppIFrameProps = {
appUrl: string
allowedFeaturesList: string
title?: string
iframeRef?: React.MutableRefObject<HTMLIFrameElement | null>
iframeRef?: MutableRefObject<HTMLIFrameElement | null>
onLoad?: () => void
}

Expand Down