From 3f4b7164a05f1339108c2b53e2c2fea7b6ba87ce Mon Sep 17 00:00:00 2001 From: Usame Algan <5880855+usame-algan@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:55:26 +0200 Subject: [PATCH 01/34] feat: Add Safe route for dashboard (#3759) * feat: Add safe specific dashboard route * fix: Redirect user to Dashboard when adding/loading/removing a safe * fix: Remove old dashboard route * fix: Move Dashboard component inside SafeContainer to access transactions * fix: Remove null fallback for wrapInSuspense, remove unnecessary React reference --- src/routes/safe/container/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/safe/container/index.tsx b/src/routes/safe/container/index.tsx index f4140a1ea8..2efcd41cbd 100644 --- a/src/routes/safe/container/index.tsx +++ b/src/routes/safe/container/index.tsx @@ -90,6 +90,8 @@ const Container = (): React.ReactElement => { return ( <> + wrapInSuspense()} /> + {/* Legacy redirect */} { )} /> - wrapInSuspense()} /> Date: Tue, 29 Mar 2022 14:11:00 +0200 Subject: [PATCH 02/34] feat: add harcoded WC app --- src/components/Dashboard/SafeApps/Card.tsx | 72 ++++++++++++++++++++++ src/routes/Home/index.tsx | 12 ++++ 2 files changed, 84 insertions(+) create mode 100644 src/components/Dashboard/SafeApps/Card.tsx diff --git a/src/components/Dashboard/SafeApps/Card.tsx b/src/components/Dashboard/SafeApps/Card.tsx new file mode 100644 index 0000000000..d822e41139 --- /dev/null +++ b/src/components/Dashboard/SafeApps/Card.tsx @@ -0,0 +1,72 @@ +import { ReactElement } from 'react' +import styled from 'styled-components' +import { Text, Title } from '@gnosis.pm/safe-react-components' +import { Bookmark, BookmarkBorder } from '@material-ui/icons' +import { IconButton } from '@material-ui/core' +import { Link } from 'react-router-dom' + +const StyledLink = styled(Link)` + text-decoration: none; + color: black; +` + +const StyledCard = styled.div` + position: relative; + width: 260px; + height: 200px; + background-color: white; + border-radius: 8px; + padding: 24px; +` + +const StyledLogo = styled.img` + display: block; + width: 60px; + height: auto; +` + +const IconBtn = styled(IconButton)` + &.MuiButtonBase-root { + position: absolute; + top: 10px; + right: 10px; + z-index: 10; + padding: 5px; + } + + svg { + width: 16px; + height: 16px; + } +` + +const StyledFooter = styled.div` + position: absolute; + bottom: 24px; +` + +type CardProps = { + name: string + description: string + logoUri: string + appUri: string + isPinned?: boolean +} + +const Card = (props: CardProps): ReactElement => { + return ( + + + {props.isPinned ? : } + + {props.name} + + {props.description} + + Last used at: + + + ) +} + +export default Card diff --git a/src/routes/Home/index.tsx b/src/routes/Home/index.tsx index 4e376d6a18..af403e79ef 100644 --- a/src/routes/Home/index.tsx +++ b/src/routes/Home/index.tsx @@ -10,6 +10,8 @@ import PendingTxsList from 'src/components/Dashboard/PendingTxs/PendingTxsList' import AddSafeWidget from 'src/components/Dashboard/AddSafe' import CreateSafeWidget from 'src/components/Dashboard/CreateSafe' +import SafeAppCard from 'src/components/Dashboard/SafeApps/Card' + const Card = styled.div` background: #fff; padding: 40px; @@ -61,6 +63,16 @@ function Home(): ReactElement {

Gas Fees

+ +

Safe dApps

+ + + ) } From 85c3f82b5dd3eb60313362abf6087d6d92f2b368 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Tue, 29 Mar 2022 15:50:32 +0200 Subject: [PATCH 03/34] Add a redirect for Safe Apps + Bookmark handler --- src/components/Dashboard/SafeApps/Card.tsx | 45 ++++++++++++++++------ src/components/Dashboard/SafeApps/Grid.tsx | 36 +++++++++++++++++ src/routes/Home/index.tsx | 14 ++----- src/routes/index.tsx | 23 +++++------ src/routes/routes.ts | 1 + 5 files changed, 84 insertions(+), 35 deletions(-) create mode 100644 src/components/Dashboard/SafeApps/Grid.tsx diff --git a/src/components/Dashboard/SafeApps/Card.tsx b/src/components/Dashboard/SafeApps/Card.tsx index d822e41139..5e78ac3c9d 100644 --- a/src/components/Dashboard/SafeApps/Card.tsx +++ b/src/components/Dashboard/SafeApps/Card.tsx @@ -1,9 +1,10 @@ -import { ReactElement } from 'react' +import { ReactElement, useCallback, useEffect, useState } from 'react' import styled from 'styled-components' import { Text, Title } from '@gnosis.pm/safe-react-components' import { Bookmark, BookmarkBorder } from '@material-ui/icons' import { IconButton } from '@material-ui/core' -import { Link } from 'react-router-dom' +import { Link, generatePath } from 'react-router-dom' +import { GENERIC_APPS_ROUTE } from 'src/routes/routes' const StyledLink = styled(Link)` text-decoration: none; @@ -40,30 +41,52 @@ const IconBtn = styled(IconButton)` } ` -const StyledFooter = styled.div` - position: absolute; - bottom: 24px; -` - type CardProps = { name: string description: string logoUri: string appUri: string - isPinned?: boolean + isPinned: boolean + onPin: () => void } const Card = (props: CardProps): ReactElement => { + const path = generatePath(GENERIC_APPS_ROUTE) + const { isPinned } = props + const [localPinned, setLocalPinned] = useState(isPinned) + const { onPin } = props + + const handlePinClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault() + setLocalPinned((prev) => !prev) + }, + [setLocalPinned], + ) + + useEffect(() => { + if (localPinned === isPinned) return + + // Add a small delay when pinning/unpinning for visual feedback + const delay = setTimeout(onPin, 500) + return () => clearTimeout(delay) + }, [localPinned, isPinned, onPin]) + return ( - + - {props.isPinned ? : } + {props.name} + {props.description} - Last used at: + + {/* Bookmark button */} + {localPinned ? : } + + {/* TODO: Share button */} ) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx new file mode 100644 index 0000000000..568bc00a53 --- /dev/null +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -0,0 +1,36 @@ +import { ReactElement } from 'react' +import styled from 'styled-components' +import { useAppList } from 'src/routes/safe/components/Apps/hooks/appList/useAppList' +import Card from './Card' + +const StyledGrid = styled.div` + display: flex; + align-items: center; + gap: 20px; +` + +const Grid = (): ReactElement => { + const { pinnedSafeApps, togglePin } = useAppList() + + return ( +
+

Safe Apps

+ + + {pinnedSafeApps.map((safeApp) => ( + app.id === safeApp.id)} + onPin={() => togglePin(safeApp)} + /> + ))} + +
+ ) +} + +export default Grid diff --git a/src/routes/Home/index.tsx b/src/routes/Home/index.tsx index af403e79ef..092610d70f 100644 --- a/src/routes/Home/index.tsx +++ b/src/routes/Home/index.tsx @@ -3,14 +3,14 @@ import styled from 'styled-components' import { Breadcrumb, BreadcrumbElement, Menu } from '@gnosis.pm/safe-react-components' import Page from 'src/components/layout/Page' -import Row from 'src/components/layout/Row' import Col from 'src/components/layout/Col' import PendingTxsList from 'src/components/Dashboard/PendingTxs/PendingTxsList' import AddSafeWidget from 'src/components/Dashboard/AddSafe' import CreateSafeWidget from 'src/components/Dashboard/CreateSafe' -import SafeAppCard from 'src/components/Dashboard/SafeApps/Card' +import SafeApps from 'src/components/Dashboard/SafeApps/Grid' +import Row from 'src/components/layout/Row' const Card = styled.div` background: #fff; @@ -64,15 +64,7 @@ function Home(): ReactElement { -

Safe dApps

- - - + ) } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f3969ca34d..938e4a3066 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -6,6 +6,7 @@ import { Redirect, Route, Switch, useLocation } from 'react-router-dom' import { LoadingContainer } from 'src/components/LoaderContainer' import { lastViewedSafe } from 'src/logic/currentSession/store/selectors' import { + generateSafeRoute, LOAD_SPECIFIC_SAFE_ROUTE, OPEN_SAFE_ROUTE, ADDRESSED_ROUTE, @@ -15,12 +16,11 @@ import { getNetworkRootRoutes, extractSafeAddress, SAFE_ROUTES, - generateSafeRoute, } from './routes' +import { getShortName } from 'src/config' import { setChainId } from 'src/logic/config/utils' import { setChainIdFromUrl } from 'src/utils/history' import { usePageTracking } from 'src/utils/googleTagManager' -import { getShortName } from 'src/config' const Welcome = React.lazy(() => import('./welcome/Welcome')) const CreateSafePage = React.lazy(() => import('./CreateSafePage/CreateSafePage')) @@ -30,7 +30,7 @@ const SafeContainer = React.lazy(() => import('./safe/container')) const Routes = (): React.ReactElement => { const location = useLocation() const { pathname } = location - const defaultSafe = useSelector(lastViewedSafe) + const lastSafe = useSelector(lastViewedSafe) // Google Tag Manager page tracking usePageTracking() @@ -73,7 +73,7 @@ const Routes = (): React.ReactElement => { exact path={ROOT_ROUTE} render={() => { - if (defaultSafe === null) { + if (lastSafe === null) { return ( @@ -81,15 +81,12 @@ const Routes = (): React.ReactElement => { ) } - if (defaultSafe) { - return ( - - ) + if (lastSafe) { + const redirectPath = generateSafeRoute(SAFE_ROUTES.DASHBOARD, { + shortName: getShortName(), + safeAddress: lastSafe, + }) + return } return diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 1af65acf7f..71e53c971d 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -43,6 +43,7 @@ export const LOAD_SPECIFIC_SAFE_ROUTE = `/load/:${SAFE_ADDRESS_SLUG}?` // ? = op export const ROOT_ROUTE = '/' export const WELCOME_ROUTE = '/welcome' export const OPEN_SAFE_ROUTE = '/open' +export const GENERIC_APPS_ROUTE = '/apps' export const LOAD_SAFE_ROUTE = generatePath(LOAD_SPECIFIC_SAFE_ROUTE) // By providing no slug, we get '/load' // [SAFE_SECTION_SLUG], [SAFE_SUBSECTION_SLUG] populated safe routes From 6d023da575a9f97e7546ae146862d801880af321 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 29 Mar 2022 17:21:12 +0200 Subject: [PATCH 04/34] feat: display "official" apps after pinned apps --- src/components/Dashboard/SafeApps/Grid.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 568bc00a53..b21b068e56 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -9,15 +9,22 @@ const StyledGrid = styled.div` gap: 20px; ` +const MAX_APPS = 3 + const Grid = (): ReactElement => { - const { pinnedSafeApps, togglePin } = useAppList() + const { allApps, pinnedSafeApps, togglePin } = useAppList() + // Transactions Builder && Wallet connect + const officialAppIds = ['29', '11'] + const officialApps = allApps.filter((app) => officialAppIds.includes(app.id)) + + const displayedApps = pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) return (

Safe Apps

- {pinnedSafeApps.map((safeApp) => ( + {displayedApps.map((safeApp) => ( Date: Wed, 30 Mar 2022 08:52:02 +0200 Subject: [PATCH 05/34] Add "Explore" Card --- public/resources/explore.svg | 5 +++ src/components/Dashboard/SafeApps/Grid.tsx | 45 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 public/resources/explore.svg diff --git a/public/resources/explore.svg b/public/resources/explore.svg new file mode 100644 index 0000000000..3a85614508 --- /dev/null +++ b/public/resources/explore.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index b21b068e56..22f3abc1ea 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -1,7 +1,11 @@ import { ReactElement } from 'react' import styled from 'styled-components' +import { Button } from '@gnosis.pm/safe-react-components' +import { generatePath, Link } from 'react-router-dom' + import { useAppList } from 'src/routes/safe/components/Apps/hooks/appList/useAppList' -import Card from './Card' +import { GENERIC_APPS_ROUTE } from 'src/routes/routes' +import Card from 'src/components/Dashboard/SafeApps/Card' const StyledGrid = styled.div` display: flex; @@ -11,6 +15,33 @@ const StyledGrid = styled.div` const MAX_APPS = 3 +const StyledExplorerButton = styled.div` + width: 260px; + height: 200px; + background-color: white; + border-radius: 8px; + padding: 24px; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; +` + +const StyledLink = styled(Link)` + text-decoration: none; + + > button { + width: 200px; + } +` + +const StyledIcon = styled.img` + width: 160px; + height: auto; +` + const Grid = (): ReactElement => { const { allApps, pinnedSafeApps, togglePin } = useAppList() // Transactions Builder && Wallet connect @@ -19,6 +50,8 @@ const Grid = (): ReactElement => { const displayedApps = pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) + const path = generatePath(GENERIC_APPS_ROUTE) + return (

Safe Apps

@@ -35,6 +68,16 @@ const Grid = (): ReactElement => { onPin={() => togglePin(safeApp)} /> ))} + {displayedApps.length < MAX_APPS && ( + + + + + + + )}
) From 043affe235c79aa6f7f0d2fdb28e8f8baebf78b3 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 09:16:16 +0200 Subject: [PATCH 06/34] refactor: extract official app idss to enum --- src/components/Dashboard/SafeApps/Card.tsx | 7 +++---- src/components/Dashboard/SafeApps/Grid.tsx | 13 ++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Card.tsx b/src/components/Dashboard/SafeApps/Card.tsx index 5e78ac3c9d..e528457806 100644 --- a/src/components/Dashboard/SafeApps/Card.tsx +++ b/src/components/Dashboard/SafeApps/Card.tsx @@ -51,10 +51,9 @@ type CardProps = { } const Card = (props: CardProps): ReactElement => { - const path = generatePath(GENERIC_APPS_ROUTE) - const { isPinned } = props + const appRoute = generatePath(GENERIC_APPS_ROUTE) + `?appUrl=${props.appUri}` + const { isPinned, onPin } = props const [localPinned, setLocalPinned] = useState(isPinned) - const { onPin } = props const handlePinClick = useCallback( (e: React.MouseEvent) => { @@ -73,7 +72,7 @@ const Card = (props: CardProps): ReactElement => { }, [localPinned, isPinned, onPin]) return ( - + diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 22f3abc1ea..a75149b01b 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -13,8 +13,6 @@ const StyledGrid = styled.div` gap: 20px; ` -const MAX_APPS = 3 - const StyledExplorerButton = styled.div` width: 260px; height: 200px; @@ -42,11 +40,16 @@ const StyledIcon = styled.img` height: auto; ` +const MAX_APPS = 3 + +const OFFICIAL_APPS: Record = { + TRANSACTION_BUILDER: '29', + WALLET_CONNECT: '11', +} + const Grid = (): ReactElement => { const { allApps, pinnedSafeApps, togglePin } = useAppList() - // Transactions Builder && Wallet connect - const officialAppIds = ['29', '11'] - const officialApps = allApps.filter((app) => officialAppIds.includes(app.id)) + const officialApps = allApps.filter((app) => Object.values(OFFICIAL_APPS).includes(app.id)) const displayedApps = pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) From 2ad47700e55f6d2f55d6afb7b2e8847f3e4984e8 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 09:35:01 +0200 Subject: [PATCH 07/34] fix: render after safe apps info response --- src/components/Dashboard/SafeApps/Grid.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index a75149b01b..17a1ff03fb 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -55,6 +55,8 @@ const Grid = (): ReactElement => { const path = generatePath(GENERIC_APPS_ROUTE) + if (allApps.length === 0) return <> + return (

Safe Apps

From e0715a2f8d52ef4d87627a3763371e9c94229a07 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 10:49:29 +0200 Subject: [PATCH 08/34] fix: import explore icon as module --- .../assets/icons}/explore.svg | 0 src/components/Dashboard/SafeApps/Grid.tsx | 19 ++++++------------- 2 files changed, 6 insertions(+), 13 deletions(-) rename {public/resources => src/assets/icons}/explore.svg (100%) diff --git a/public/resources/explore.svg b/src/assets/icons/explore.svg similarity index 100% rename from public/resources/explore.svg rename to src/assets/icons/explore.svg diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 17a1ff03fb..c3f09d4e2d 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -6,6 +6,7 @@ import { generatePath, Link } from 'react-router-dom' import { useAppList } from 'src/routes/safe/components/Apps/hooks/appList/useAppList' import { GENERIC_APPS_ROUTE } from 'src/routes/routes' import Card from 'src/components/Dashboard/SafeApps/Card' +import ExploreIcon from 'src/assets/icons/explore.svg' const StyledGrid = styled.div` display: flex; @@ -35,21 +36,13 @@ const StyledLink = styled(Link)` } ` -const StyledIcon = styled.img` - width: 160px; - height: auto; -` - const MAX_APPS = 3 -const OFFICIAL_APPS: Record = { - TRANSACTION_BUILDER: '29', - WALLET_CONNECT: '11', -} - const Grid = (): ReactElement => { const { allApps, pinnedSafeApps, togglePin } = useAppList() - const officialApps = allApps.filter((app) => Object.values(OFFICIAL_APPS).includes(app.id)) + // Transactions Builder && Wallet connect + const officialAppIds = ['29', '11'] + const officialApps = allApps.filter((app) => officialAppIds.includes(app.id)) const displayedApps = pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) @@ -75,10 +68,10 @@ const Grid = (): ReactElement => { ))} {displayedApps.length < MAX_APPS && ( - + Explore Safe Apps From 79d5e6f964f9122ea4c413434a70750ade58eeab Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 11:00:56 +0200 Subject: [PATCH 09/34] fix: remove duplicated safe apps --- src/components/Dashboard/SafeApps/Grid.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index c3f09d4e2d..9040c36f87 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -42,7 +42,8 @@ const Grid = (): ReactElement => { const { allApps, pinnedSafeApps, togglePin } = useAppList() // Transactions Builder && Wallet connect const officialAppIds = ['29', '11'] - const officialApps = allApps.filter((app) => officialAppIds.includes(app.id)) + const pinnedSafeAppsIds = pinnedSafeApps.map((app) => app.id) + const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppsIds.includes(app.id)) const displayedApps = pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) From 2b1f275816874a297d6dff739b185e97cb824d2a Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 12:42:05 +0200 Subject: [PATCH 10/34] fix: memoize safeApps data --- src/components/Dashboard/SafeApps/Grid.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 9040c36f87..1a8b5e1538 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -1,4 +1,4 @@ -import { ReactElement } from 'react' +import { ReactElement, useMemo } from 'react' import styled from 'styled-components' import { Button } from '@gnosis.pm/safe-react-components' import { generatePath, Link } from 'react-router-dom' @@ -36,16 +36,22 @@ const StyledLink = styled(Link)` } ` +// Transactions Builder && Wallet connect +const officialAppIds = ['29', '11'] const MAX_APPS = 3 const Grid = (): ReactElement => { const { allApps, pinnedSafeApps, togglePin } = useAppList() - // Transactions Builder && Wallet connect - const officialAppIds = ['29', '11'] - const pinnedSafeAppsIds = pinnedSafeApps.map((app) => app.id) - const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppsIds.includes(app.id)) - const displayedApps = pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) + const pinnedSafeAppsIds = useMemo(() => pinnedSafeApps.map((app) => app.id), [pinnedSafeApps]) + const officialApps = useMemo( + () => allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppsIds.includes(app.id)), + [allApps, pinnedSafeAppsIds], + ) + const displayedApps = useMemo( + () => pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS), + [pinnedSafeAppsIds, officialApps], + ) const path = generatePath(GENERIC_APPS_ROUTE) From ff36879e417a404ecdb5d0a36d0767d1cc8b0f8a Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 14:36:06 +0200 Subject: [PATCH 11/34] fix: change useMemo dependency --- src/components/Dashboard/SafeApps/Grid.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 1a8b5e1538..9e138c3aa6 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -43,14 +43,14 @@ const MAX_APPS = 3 const Grid = (): ReactElement => { const { allApps, pinnedSafeApps, togglePin } = useAppList() - const pinnedSafeAppsIds = useMemo(() => pinnedSafeApps.map((app) => app.id), [pinnedSafeApps]) + const pinnedSafeAppIds = useMemo(() => pinnedSafeApps.map((app) => app.id), [pinnedSafeApps]) const officialApps = useMemo( - () => allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppsIds.includes(app.id)), - [allApps, pinnedSafeAppsIds], + () => allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppIds.includes(app.id)), + [allApps, pinnedSafeAppIds], ) const displayedApps = useMemo( () => pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS), - [pinnedSafeAppsIds, officialApps], + [pinnedSafeApps, officialApps], ) const path = generatePath(GENERIC_APPS_ROUTE) From 9d15334fabb42f745b2b12ab60da83adaa6c69b1 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 30 Mar 2022 16:59:24 +0200 Subject: [PATCH 12/34] fix: move related data inside the same function. Use hook isLoading. --- src/components/Dashboard/SafeApps/Grid.tsx | 69 +++++++++++----------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 9e138c3aa6..57405f71b8 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -41,49 +41,46 @@ const officialAppIds = ['29', '11'] const MAX_APPS = 3 const Grid = (): ReactElement => { - const { allApps, pinnedSafeApps, togglePin } = useAppList() + const { allApps, pinnedSafeApps, togglePin, isLoading } = useAppList() - const pinnedSafeAppIds = useMemo(() => pinnedSafeApps.map((app) => app.id), [pinnedSafeApps]) - const officialApps = useMemo( - () => allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppIds.includes(app.id)), - [allApps, pinnedSafeAppIds], - ) - const displayedApps = useMemo( - () => pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS), - [pinnedSafeApps, officialApps], - ) + const displayedApps = useMemo(() => { + const pinnedSafeAppIds = pinnedSafeApps.map((app) => app.id) + const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppIds.includes(app.id)) + return pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) + }, [allApps, pinnedSafeApps]) const path = generatePath(GENERIC_APPS_ROUTE) - if (allApps.length === 0) return <> - return (

Safe Apps

- - - {displayedApps.map((safeApp) => ( - app.id === safeApp.id)} - onPin={() => togglePin(safeApp)} - /> - ))} - {displayedApps.length < MAX_APPS && ( - - Explore Safe Apps - - - - - )} - + {isLoading ? ( +

Loading...

+ ) : ( + + {displayedApps.map((safeApp) => ( + app.id === safeApp.id)} + onPin={() => togglePin(safeApp)} + /> + ))} + {displayedApps.length < MAX_APPS && ( + + Explore Safe Apps + + + + + )} + + )}
) } From 7c6cd51f96719c55d14aa8bd1c4752bcf35c36b0 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Thu, 7 Apr 2022 11:02:39 +0200 Subject: [PATCH 13/34] fix: GENERIC_APPS_ROUTE route --- src/routes/Home/index.tsx | 6 +----- src/routes/index.tsx | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/routes/Home/index.tsx b/src/routes/Home/index.tsx index 092610d70f..6a9ed975bc 100644 --- a/src/routes/Home/index.tsx +++ b/src/routes/Home/index.tsx @@ -59,12 +59,8 @@ function Home(): ReactElement { - -

Gas Fees

-
+
- - ) } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 938e4a3066..b94d06c35a 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -16,6 +16,7 @@ import { getNetworkRootRoutes, extractSafeAddress, SAFE_ROUTES, + GENERIC_APPS_ROUTE, } from './routes' import { getShortName } from 'src/config' import { setChainId } from 'src/logic/config/utils' @@ -82,17 +83,36 @@ const Routes = (): React.ReactElement => { } if (lastSafe) { - const redirectPath = generateSafeRoute(SAFE_ROUTES.DASHBOARD, { - shortName: getShortName(), - safeAddress: lastSafe, - }) - return + return ( + + ) } return }} /> + {/* Redirect /app/apps?appUrl=https://... to that app within the current Safe */} + { + if (!lastSafe) { + return + } + const redirectPath = generateSafeRoute(SAFE_ROUTES.APPS, { + shortName: getShortName(), + safeAddress: lastSafe, + }) + return + }} + /> + From 78c05d446f8f2d0ab905e6d1d84d647db54b77bb Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 00:04:06 +0200 Subject: [PATCH 14/34] feat: track timestamp when opening safeApp --- .../components/Apps/components/AppFrame.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 32d8593da0..36a16a1093 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -37,6 +37,8 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { SAFE_APPS_EVENTS } from 'src/utils/events/safeApps' import { trackEvent } from 'src/utils/googleTagManager' import { checksumAddress } from 'src/utils/checksumAddress' +import { loadFromStorage, saveToStorage } from 'src/utils/storage' +import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' const AppWrapper = styled.div` display: flex; @@ -83,9 +85,19 @@ const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = { params: undefined, } +type AppTrackData = { + string: { + timestamp: Date + openCount: number + txCount: number + } +} + const URL_NOT_PROVIDED_ERROR = 'App url No provided or it is invalid.' const APP_LOAD_ERROR = 'There was an error loading the Safe App. There might be a problem with the App provider.' +const APPS_DASHBOARD = 'APPS_DASHBOARD' + const AppFrame = ({ appUrl }: Props): ReactElement => { const { address: safeAddress, ethBalance, owners, threshold } = useSelector(currentSafe) const { nativeCurrency, chainId, chainName, shortName } = getChainInfo() @@ -102,6 +114,8 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { const errorTimer = useRef() const [, setAppLoadError] = useState(false) const { thirdPartyCookiesDisabled, setThirdPartyCookiesDisabled } = useThirdPartyCookies() + const { remoteSafeApps } = useRemoteSafeApps() + const currentApp = remoteSafeApps.filter((app) => app.url === appUrl)[0] const safeAppsRpc = getSafeAppsRpcServiceUrl() const safeAppWeb3Provider = useMemo( @@ -115,7 +129,9 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { clearTimeout(errorTimer.current) } - if (appIsLoading) { + if (appIsLoading && currentApp) { + const trackData = loadFromStorage(APPS_DASHBOARD) || {} + saveToStorage(APPS_DASHBOARD, { ...trackData, [currentApp.id]: { timestamp: Date.now() } }) timer.current = window.setTimeout(() => { setIsLoadingSlow(true) }, SAFE_POLLING_INTERVAL) @@ -132,7 +148,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { return () => { clearTimeouts() } - }, [appIsLoading]) + }, [appIsLoading, currentApp]) const openConfirmationModal = useCallback( (txs: Transaction[], params: TransactionParams | undefined, requestId: RequestId) => From 9a1729eefe0087fe098f808743ca243854231a7e Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 00:13:36 +0200 Subject: [PATCH 15/34] feat: track openingCount when opening safeApp --- src/routes/safe/components/Apps/components/AppFrame.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 36a16a1093..0827fd59a7 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -131,7 +131,11 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { if (appIsLoading && currentApp) { const trackData = loadFromStorage(APPS_DASHBOARD) || {} - saveToStorage(APPS_DASHBOARD, { ...trackData, [currentApp.id]: { timestamp: Date.now() } }) + let currentOpenCount = trackData[currentApp.id]?.openCount + saveToStorage(APPS_DASHBOARD, { + ...trackData, + [currentApp.id]: { timestamp: Date.now(), openCount: currentOpenCount ? ++currentOpenCount : 1 }, + }) timer.current = window.setTimeout(() => { setIsLoadingSlow(true) }, SAFE_POLLING_INTERVAL) From 8ce38f30b4b3a1085ab0be08b3a8be363f846f2a Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 01:55:08 +0200 Subject: [PATCH 16/34] feat: track txCount when creating a transaction from ReviewConfirm --- .../safe/components/Apps/components/AppFrame.tsx | 14 ++------------ .../components/ConfirmTxModal/ReviewConfirm.tsx | 12 ++++++++++++ src/routes/safe/components/Apps/types.ts | 8 ++++++++ src/routes/safe/components/Apps/utils.ts | 1 + 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 0827fd59a7..5084612d22 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -21,9 +21,9 @@ import { LoadingContainer } from 'src/components/LoaderContainer/index' import { SAFE_POLLING_INTERVAL } from 'src/utils/constants' import { ConfirmTxModal } from './ConfirmTxModal' import { useIframeMessageHandler } from '../hooks/useIframeMessageHandler' -import { EMPTY_SAFE_APP, getAppInfoFromUrl, getEmptySafeApp, getLegacyChainName } from '../utils' -import { SafeApp } from '../types' import { LegacyMethods, useAppCommunicator } from '../communicator' +import { APPS_DASHBOARD, EMPTY_SAFE_APP, getAppInfoFromUrl, getEmptySafeApp, getLegacyChainName } from '../utils' +import { AppTrackData, SafeApp } from '../types' import { fetchTokenCurrenciesBalances } from 'src/logic/safe/api/fetchTokenCurrenciesBalances' import { fetchSafeTransaction } from 'src/logic/safe/transactions/api/fetchSafeTransaction' import { logError, Errors } from 'src/logic/exceptions/CodedException' @@ -85,19 +85,9 @@ const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = { params: undefined, } -type AppTrackData = { - string: { - timestamp: Date - openCount: number - txCount: number - } -} - const URL_NOT_PROVIDED_ERROR = 'App url No provided or it is invalid.' const APP_LOAD_ERROR = 'There was an error loading the Safe App. There might be a problem with the App provider.' -const APPS_DASHBOARD = 'APPS_DASHBOARD' - const AppFrame = ({ appUrl }: Props): ReactElement => { const { address: safeAddress, ethBalance, owners, threshold } = useSelector(currentSafe) const { nativeCurrency, chainId, chainName, shortName } = getChainInfo() diff --git a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx index 83a8689d51..2adb9ca358 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx @@ -26,6 +26,10 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { TxModalWrapper } from 'src/routes/safe/components/Transactions/helpers/TxModalWrapper' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' +import { loadFromStorage, saveToStorage } from 'src/utils/storage' +import { AppTrackData } from 'src/routes/safe/components/Apps/types' +import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' +import { APPS_DASHBOARD } from 'src/routes/safe/components/Apps/utils' const Container = styled.div` max-width: 480px; @@ -75,6 +79,8 @@ export const ReviewConfirm = ({ const nativeCurrency = getNativeCurrency() const explorerUrl = getExplorerInfo(safeAddress) const isOwner = useSelector(grantedSelector) + const { remoteSafeApps } = useRemoteSafeApps() + const currentApp = remoteSafeApps.filter((remoteApp) => remoteApp.url === app.url)[0] const txRecipient: string | undefined = useMemo( () => (isMultiSend ? getMultisendContractAddress() : txs[0]?.to), @@ -112,6 +118,12 @@ export const ReviewConfirm = ({ } const confirmTransactions = (txParameters: TxParameters, delayExecution: boolean) => { + const trackData = loadFromStorage(APPS_DASHBOARD) || {} + let currentTxCount = trackData[currentApp.id]?.txCount + saveToStorage(APPS_DASHBOARD, { + ...trackData, + [currentApp.id]: { ...trackData[currentApp.id], txCount: currentTxCount ? ++currentTxCount : 1 }, + }) dispatch( createTransaction( { diff --git a/src/routes/safe/components/Apps/types.ts b/src/routes/safe/components/Apps/types.ts index 16c7de1fd6..f08c196dc8 100644 --- a/src/routes/safe/components/Apps/types.ts +++ b/src/routes/safe/components/Apps/types.ts @@ -11,3 +11,11 @@ export type SafeApp = Omit & { export type StoredSafeApp = { url: string } + +export type AppTrackData = { + string: { + timestamp: Date + openCount: number + txCount: number + } +} diff --git a/src/routes/safe/components/Apps/utils.ts b/src/routes/safe/components/Apps/utils.ts index e0618d2844..140eaf4144 100644 --- a/src/routes/safe/components/Apps/utils.ts +++ b/src/routes/safe/components/Apps/utils.ts @@ -23,6 +23,7 @@ export interface AppManifest { providedBy: string } +export const APPS_DASHBOARD = 'APPS_DASHBOARD' export const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' export const PINNED_SAFE_APP_IDS = 'PINNED_SAFE_APP_IDS' export const EMPTY_SAFE_APP = 'unknown' From 171d354fb6bf4e269b2e7b74878ed36c8bdddfb3 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 02:03:24 +0200 Subject: [PATCH 17/34] fix: keep previous data when tracking on opening --- src/routes/safe/components/Apps/components/AppFrame.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 5084612d22..f3072ba27d 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -124,7 +124,11 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { let currentOpenCount = trackData[currentApp.id]?.openCount saveToStorage(APPS_DASHBOARD, { ...trackData, - [currentApp.id]: { timestamp: Date.now(), openCount: currentOpenCount ? ++currentOpenCount : 1 }, + [currentApp.id]: { + ...trackData[currentApp.id], + timestamp: Date.now(), + openCount: currentOpenCount ? ++currentOpenCount : 1, + }, }) timer.current = window.setTimeout(() => { setIsLoadingSlow(true) From 36273d3dd2844e50565107a69f52b086fe243b55 Mon Sep 17 00:00:00 2001 From: Usame Algan Date: Fri, 8 Apr 2022 12:32:17 +0200 Subject: [PATCH 18/34] fix: Adds ranking function for tracked safe apps --- .../SafeApps/__tests__/utils.test.ts | 57 +++++++++++++++++++ src/components/Dashboard/SafeApps/utils.ts | 28 +++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/components/Dashboard/SafeApps/__tests__/utils.test.ts create mode 100644 src/components/Dashboard/SafeApps/utils.ts diff --git a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts new file mode 100644 index 0000000000..4a7839f2ee --- /dev/null +++ b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts @@ -0,0 +1,57 @@ +import { rankTrackedSafeApps, TrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' + +describe('rankTrackedSafeApps', () => { + it('ranks more recent apps higher', () => { + const trackedSafeApps: TrackedSafeApps = { + '1': { + timestamp: 1, + txCount: 1, + openCount: 1, + }, + '2': { + timestamp: 3, + txCount: 1, + openCount: 1, + }, + '3': { + timestamp: 5, + txCount: 1, + openCount: 1, + }, + '4': { + timestamp: 2, + txCount: 1, + openCount: 1, + }, + } + const result = rankTrackedSafeApps(trackedSafeApps) + expect(result).toEqual(['3', '2', '4', '1']) + }) + + it('ranks apps by relevancy', () => { + const trackedSafeApps: TrackedSafeApps = { + '1': { + timestamp: 1, + txCount: 1, + openCount: 1, + }, + '2': { + timestamp: 4, + txCount: 4, + openCount: 6, + }, + '3': { + timestamp: 8, + txCount: 3, + openCount: 4, + }, + '4': { + timestamp: 5, + txCount: 2, + openCount: 2, + }, + } + const result = rankTrackedSafeApps(trackedSafeApps) + expect(result).toEqual(['3', '2', '4', '1']) + }) +}) diff --git a/src/components/Dashboard/SafeApps/utils.ts b/src/components/Dashboard/SafeApps/utils.ts new file mode 100644 index 0000000000..85133e3974 --- /dev/null +++ b/src/components/Dashboard/SafeApps/utils.ts @@ -0,0 +1,28 @@ +export type TrackedSafeApps = Record + +export type TrackedSafeApp = { + timestamp: number + txCount: number + openCount: number +} + +const TXCOUNT_WEIGHT = 2 +const OPENCOUNT_WEIGHT = 1 +const MORE_RECENT_MULTIPLIER = 2 +const LESS_RECENT_MULTIPLIER = 1 + +export const rankTrackedSafeApps = (apps: TrackedSafeApps): string[] => { + const appsMap = Object.entries(apps) + + return appsMap + .sort((a, b) => { + const aTimeMultiplier = a[1].timestamp - b[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + const bTimeMultiplier = b[1].timestamp - a[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + + const aScore = (TXCOUNT_WEIGHT * a[1].txCount + OPENCOUNT_WEIGHT * a[1].openCount) * aTimeMultiplier + const bScore = (TXCOUNT_WEIGHT * b[1].txCount + OPENCOUNT_WEIGHT * b[1].openCount) * bTimeMultiplier + + return bScore - aScore + }) + .map((values) => values[0]) +} From 96b13b19d6d3ddfc55d6d4718ebb5d49e3860a13 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 14:34:11 +0200 Subject: [PATCH 19/34] fix: unify rankTrackedSafeApps input types --- .../SafeApps/__tests__/utils.test.ts | 23 ++++++++++--------- src/components/Dashboard/SafeApps/utils.ts | 16 +++++-------- src/routes/safe/components/Apps/types.ts | 2 +- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts index 4a7839f2ee..370cb004f2 100644 --- a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts +++ b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts @@ -1,25 +1,26 @@ -import { rankTrackedSafeApps, TrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' +import { rankTrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' +import { AppTrackData } from 'src/routes/safe/components/Apps/types' describe('rankTrackedSafeApps', () => { it('ranks more recent apps higher', () => { - const trackedSafeApps: TrackedSafeApps = { + const trackedSafeApps: AppTrackData = { '1': { - timestamp: 1, + timestamp: new Date(1), txCount: 1, openCount: 1, }, '2': { - timestamp: 3, + timestamp: new Date(3), txCount: 1, openCount: 1, }, '3': { - timestamp: 5, + timestamp: new Date(5), txCount: 1, openCount: 1, }, '4': { - timestamp: 2, + timestamp: new Date(2), txCount: 1, openCount: 1, }, @@ -29,24 +30,24 @@ describe('rankTrackedSafeApps', () => { }) it('ranks apps by relevancy', () => { - const trackedSafeApps: TrackedSafeApps = { + const trackedSafeApps: AppTrackData = { '1': { - timestamp: 1, + timestamp: new Date(1), txCount: 1, openCount: 1, }, '2': { - timestamp: 4, + timestamp: new Date(4), txCount: 4, openCount: 6, }, '3': { - timestamp: 8, + timestamp: new Date(8), txCount: 3, openCount: 4, }, '4': { - timestamp: 5, + timestamp: new Date(5), txCount: 2, openCount: 2, }, diff --git a/src/components/Dashboard/SafeApps/utils.ts b/src/components/Dashboard/SafeApps/utils.ts index 85133e3974..0da22586e1 100644 --- a/src/components/Dashboard/SafeApps/utils.ts +++ b/src/components/Dashboard/SafeApps/utils.ts @@ -1,23 +1,19 @@ -export type TrackedSafeApps = Record - -export type TrackedSafeApp = { - timestamp: number - txCount: number - openCount: number -} +import { AppTrackData } from 'src/routes/safe/components/Apps/types' const TXCOUNT_WEIGHT = 2 const OPENCOUNT_WEIGHT = 1 const MORE_RECENT_MULTIPLIER = 2 const LESS_RECENT_MULTIPLIER = 1 -export const rankTrackedSafeApps = (apps: TrackedSafeApps): string[] => { +export const rankTrackedSafeApps = (apps: AppTrackData): string[] => { const appsMap = Object.entries(apps) return appsMap .sort((a, b) => { - const aTimeMultiplier = a[1].timestamp - b[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER - const bTimeMultiplier = b[1].timestamp - a[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + const aTimeMultiplier = + a[1].timestamp.valueOf() - b[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + const bTimeMultiplier = + b[1].timestamp.valueOf() - a[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER const aScore = (TXCOUNT_WEIGHT * a[1].txCount + OPENCOUNT_WEIGHT * a[1].openCount) * aTimeMultiplier const bScore = (TXCOUNT_WEIGHT * b[1].txCount + OPENCOUNT_WEIGHT * b[1].openCount) * bTimeMultiplier diff --git a/src/routes/safe/components/Apps/types.ts b/src/routes/safe/components/Apps/types.ts index f08c196dc8..2526a44df3 100644 --- a/src/routes/safe/components/Apps/types.ts +++ b/src/routes/safe/components/Apps/types.ts @@ -13,7 +13,7 @@ export type StoredSafeApp = { } export type AppTrackData = { - string: { + [safeAppId: string]: { timestamp: Date openCount: number txCount: number From 8dfd294ef45a8b84cccd773e5c045daa59179da4 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 14:37:26 +0200 Subject: [PATCH 20/34] fix: change localstorage prefered module --- src/routes/safe/components/Apps/components/AppFrame.tsx | 6 +++--- .../Apps/components/ConfirmTxModal/ReviewConfirm.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index f3072ba27d..3608b097a8 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -37,7 +37,7 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { SAFE_APPS_EVENTS } from 'src/utils/events/safeApps' import { trackEvent } from 'src/utils/googleTagManager' import { checksumAddress } from 'src/utils/checksumAddress' -import { loadFromStorage, saveToStorage } from 'src/utils/storage' +import local from 'src/utils/storage/local' import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' const AppWrapper = styled.div` @@ -120,9 +120,9 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { } if (appIsLoading && currentApp) { - const trackData = loadFromStorage(APPS_DASHBOARD) || {} + const trackData = local.getItem(APPS_DASHBOARD) || {} let currentOpenCount = trackData[currentApp.id]?.openCount - saveToStorage(APPS_DASHBOARD, { + local.setItem(APPS_DASHBOARD, { ...trackData, [currentApp.id]: { ...trackData[currentApp.id], diff --git a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx index 2adb9ca358..348b0beda3 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx @@ -26,7 +26,7 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { TxModalWrapper } from 'src/routes/safe/components/Transactions/helpers/TxModalWrapper' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { loadFromStorage, saveToStorage } from 'src/utils/storage' +import local from 'src/utils/storage/local' import { AppTrackData } from 'src/routes/safe/components/Apps/types' import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' import { APPS_DASHBOARD } from 'src/routes/safe/components/Apps/utils' @@ -118,9 +118,9 @@ export const ReviewConfirm = ({ } const confirmTransactions = (txParameters: TxParameters, delayExecution: boolean) => { - const trackData = loadFromStorage(APPS_DASHBOARD) || {} + const trackData = local.getItem(APPS_DASHBOARD) || {} let currentTxCount = trackData[currentApp.id]?.txCount - saveToStorage(APPS_DASHBOARD, { + local.setItem(APPS_DASHBOARD, { ...trackData, [currentApp.id]: { ...trackData[currentApp.id], txCount: currentTxCount ? ++currentTxCount : 1 }, }) From 962a5be3348bd77df08939ba7a81727a1dc83ce7 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 8 Apr 2022 17:49:10 +0200 Subject: [PATCH 21/34] feat: display top ranked apps --- src/components/Dashboard/SafeApps/Grid.tsx | 26 ++++++++++++++----- .../SafeApps/__tests__/utils.test.ts | 16 ++++++------ .../components/Apps/components/AppFrame.tsx | 1 + src/routes/safe/components/Apps/types.ts | 2 +- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 57405f71b8..f8df4f158e 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -7,6 +7,10 @@ import { useAppList } from 'src/routes/safe/components/Apps/hooks/appList/useApp import { GENERIC_APPS_ROUTE } from 'src/routes/routes' import Card from 'src/components/Dashboard/SafeApps/Card' import ExploreIcon from 'src/assets/icons/explore.svg' +import local from 'src/utils/storage/local' +import { AppTrackData, SafeApp } from 'src/routes/safe/components/Apps/types' +import { APPS_DASHBOARD } from 'src/routes/safe/components/Apps/utils' +import { rankTrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' const StyledGrid = styled.div` display: flex; @@ -38,16 +42,24 @@ const StyledLink = styled(Link)` // Transactions Builder && Wallet connect const officialAppIds = ['29', '11'] -const MAX_APPS = 3 -const Grid = (): ReactElement => { +const Grid = ({ size = 3 }: { size?: number }): ReactElement => { const { allApps, pinnedSafeApps, togglePin, isLoading } = useAppList() const displayedApps = useMemo(() => { - const pinnedSafeAppIds = pinnedSafeApps.map((app) => app.id) - const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !pinnedSafeAppIds.includes(app.id)) - return pinnedSafeApps.concat(officialApps).slice(0, MAX_APPS) - }, [allApps, pinnedSafeApps]) + const trackData = local.getItem(APPS_DASHBOARD) || {} + const rankedSafeAppIds = rankTrackedSafeApps(trackData) + + const topRankedSafeApps: SafeApp[] = [] + rankedSafeAppIds.forEach((id) => { + const sortedApp = allApps.find((app) => app.id === id) + if (sortedApp) topRankedSafeApps.push(sortedApp) + }) + + // Do not repeat top ranked apps + const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !rankedSafeAppIds.includes(app.id)) + return topRankedSafeApps.concat(officialApps).slice(0, size) + }, [allApps, size]) const path = generatePath(GENERIC_APPS_ROUTE) @@ -69,7 +81,7 @@ const Grid = (): ReactElement => { onPin={() => togglePin(safeApp)} /> ))} - {displayedApps.length < MAX_APPS && ( + {displayedApps.length < size && ( Explore Safe Apps diff --git a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts index 370cb004f2..1493568b80 100644 --- a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts +++ b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts @@ -5,22 +5,22 @@ describe('rankTrackedSafeApps', () => { it('ranks more recent apps higher', () => { const trackedSafeApps: AppTrackData = { '1': { - timestamp: new Date(1), + timestamp: 1, txCount: 1, openCount: 1, }, '2': { - timestamp: new Date(3), + timestamp: 3, txCount: 1, openCount: 1, }, '3': { - timestamp: new Date(5), + timestamp: 5, txCount: 1, openCount: 1, }, '4': { - timestamp: new Date(2), + timestamp: 2, txCount: 1, openCount: 1, }, @@ -32,22 +32,22 @@ describe('rankTrackedSafeApps', () => { it('ranks apps by relevancy', () => { const trackedSafeApps: AppTrackData = { '1': { - timestamp: new Date(1), + timestamp: 1, txCount: 1, openCount: 1, }, '2': { - timestamp: new Date(4), + timestamp: 4, txCount: 4, openCount: 6, }, '3': { - timestamp: new Date(8), + timestamp: 8, txCount: 3, openCount: 4, }, '4': { - timestamp: new Date(5), + timestamp: 5, txCount: 2, openCount: 2, }, diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 3608b097a8..31065764b7 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -128,6 +128,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { ...trackData[currentApp.id], timestamp: Date.now(), openCount: currentOpenCount ? ++currentOpenCount : 1, + txCount: trackData[currentApp.id]?.txCount || 0, }, }) timer.current = window.setTimeout(() => { diff --git a/src/routes/safe/components/Apps/types.ts b/src/routes/safe/components/Apps/types.ts index 2526a44df3..4e59f2fa3b 100644 --- a/src/routes/safe/components/Apps/types.ts +++ b/src/routes/safe/components/Apps/types.ts @@ -14,7 +14,7 @@ export type StoredSafeApp = { export type AppTrackData = { [safeAppId: string]: { - timestamp: Date + timestamp: number openCount: number txCount: number } From da3ec11ce28bc14a7e9d59ab2e16745bf0189872 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Mon, 11 Apr 2022 17:38:50 +0200 Subject: [PATCH 22/34] fix: track opening SafeApp in a separate hook --- .../components/Apps/components/AppFrame.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 31065764b7..3eeb6cb30c 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -119,18 +119,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { clearTimeout(errorTimer.current) } - if (appIsLoading && currentApp) { - const trackData = local.getItem(APPS_DASHBOARD) || {} - let currentOpenCount = trackData[currentApp.id]?.openCount - local.setItem(APPS_DASHBOARD, { - ...trackData, - [currentApp.id]: { - ...trackData[currentApp.id], - timestamp: Date.now(), - openCount: currentOpenCount ? ++currentOpenCount : 1, - txCount: trackData[currentApp.id]?.txCount || 0, - }, - }) + if (appIsLoading) { timer.current = window.setTimeout(() => { setIsLoadingSlow(true) }, SAFE_POLLING_INTERVAL) @@ -147,7 +136,23 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { return () => { clearTimeouts() } - }, [appIsLoading, currentApp]) + }, [appIsLoading]) + + useEffect(() => { + if (!currentApp) return + + const trackData = local.getItem(APPS_DASHBOARD) || {} + let currentOpenCount = trackData[currentApp.id]?.openCount + local.setItem(APPS_DASHBOARD, { + ...trackData, + [currentApp.id]: { + ...trackData[currentApp.id], + timestamp: Date.now(), + openCount: currentOpenCount ? ++currentOpenCount : 1, + txCount: trackData[currentApp.id]?.txCount || 0, + }, + }) + }, [currentApp]) const openConfirmationModal = useCallback( (txs: Transaction[], params: TransactionParams | undefined, requestId: RequestId) => From d0d5860e8bd8b9a81d9e3b7187c7357c5899e37a Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Mon, 11 Apr 2022 17:54:21 +0200 Subject: [PATCH 23/34] fix: move app count tracking to a separate module --- src/components/Dashboard/SafeApps/Grid.tsx | 4 +-- .../SafeApps/__tests__/utils.test.ts | 2 +- src/components/Dashboard/SafeApps/utils.ts | 2 +- .../components/Apps/components/AppFrame.tsx | 18 +++------- .../ConfirmTxModal/ReviewConfirm.tsx | 11 ++---- .../components/Apps/trackAppUsageCount.ts | 35 +++++++++++++++++++ src/routes/safe/components/Apps/types.ts | 8 ----- src/routes/safe/components/Apps/utils.ts | 1 - 8 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 src/routes/safe/components/Apps/trackAppUsageCount.ts diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index f8df4f158e..4857bec936 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -8,9 +8,9 @@ import { GENERIC_APPS_ROUTE } from 'src/routes/routes' import Card from 'src/components/Dashboard/SafeApps/Card' import ExploreIcon from 'src/assets/icons/explore.svg' import local from 'src/utils/storage/local' -import { AppTrackData, SafeApp } from 'src/routes/safe/components/Apps/types' -import { APPS_DASHBOARD } from 'src/routes/safe/components/Apps/utils' +import { SafeApp } from 'src/routes/safe/components/Apps/types' import { rankTrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' +import { APPS_DASHBOARD, AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' const StyledGrid = styled.div` display: flex; diff --git a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts index 1493568b80..0d2b0d0e14 100644 --- a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts +++ b/src/components/Dashboard/SafeApps/__tests__/utils.test.ts @@ -1,5 +1,5 @@ import { rankTrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' -import { AppTrackData } from 'src/routes/safe/components/Apps/types' +import { AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' describe('rankTrackedSafeApps', () => { it('ranks more recent apps higher', () => { diff --git a/src/components/Dashboard/SafeApps/utils.ts b/src/components/Dashboard/SafeApps/utils.ts index 0da22586e1..f29e08688b 100644 --- a/src/components/Dashboard/SafeApps/utils.ts +++ b/src/components/Dashboard/SafeApps/utils.ts @@ -1,4 +1,4 @@ -import { AppTrackData } from 'src/routes/safe/components/Apps/types' +import { AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' const TXCOUNT_WEIGHT = 2 const OPENCOUNT_WEIGHT = 1 diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 3eeb6cb30c..a5ddc2049c 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -22,8 +22,8 @@ import { SAFE_POLLING_INTERVAL } from 'src/utils/constants' import { ConfirmTxModal } from './ConfirmTxModal' import { useIframeMessageHandler } from '../hooks/useIframeMessageHandler' import { LegacyMethods, useAppCommunicator } from '../communicator' -import { APPS_DASHBOARD, EMPTY_SAFE_APP, getAppInfoFromUrl, getEmptySafeApp, getLegacyChainName } from '../utils' -import { AppTrackData, SafeApp } from '../types' +import { SafeApp } from '../types' +import { EMPTY_SAFE_APP, getAppInfoFromUrl, getEmptySafeApp, getLegacyChainName } from '../utils' import { fetchTokenCurrenciesBalances } from 'src/logic/safe/api/fetchTokenCurrenciesBalances' import { fetchSafeTransaction } from 'src/logic/safe/transactions/api/fetchSafeTransaction' import { logError, Errors } from 'src/logic/exceptions/CodedException' @@ -37,8 +37,8 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { SAFE_APPS_EVENTS } from 'src/utils/events/safeApps' import { trackEvent } from 'src/utils/googleTagManager' import { checksumAddress } from 'src/utils/checksumAddress' -import local from 'src/utils/storage/local' import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' +import { countOpen } from 'src/routes/safe/components/Apps/trackAppUsageCount' const AppWrapper = styled.div` display: flex; @@ -141,17 +141,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { useEffect(() => { if (!currentApp) return - const trackData = local.getItem(APPS_DASHBOARD) || {} - let currentOpenCount = trackData[currentApp.id]?.openCount - local.setItem(APPS_DASHBOARD, { - ...trackData, - [currentApp.id]: { - ...trackData[currentApp.id], - timestamp: Date.now(), - openCount: currentOpenCount ? ++currentOpenCount : 1, - txCount: trackData[currentApp.id]?.txCount || 0, - }, - }) + countOpen(currentApp) }, [currentApp]) const openConfirmationModal = useCallback( diff --git a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx index 348b0beda3..98a7db1449 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx @@ -26,10 +26,8 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { TxModalWrapper } from 'src/routes/safe/components/Transactions/helpers/TxModalWrapper' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import local from 'src/utils/storage/local' -import { AppTrackData } from 'src/routes/safe/components/Apps/types' import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' -import { APPS_DASHBOARD } from 'src/routes/safe/components/Apps/utils' +import { countTxs } from 'src/routes/safe/components/Apps/trackAppUsageCount' const Container = styled.div` max-width: 480px; @@ -118,12 +116,7 @@ export const ReviewConfirm = ({ } const confirmTransactions = (txParameters: TxParameters, delayExecution: boolean) => { - const trackData = local.getItem(APPS_DASHBOARD) || {} - let currentTxCount = trackData[currentApp.id]?.txCount - local.setItem(APPS_DASHBOARD, { - ...trackData, - [currentApp.id]: { ...trackData[currentApp.id], txCount: currentTxCount ? ++currentTxCount : 1 }, - }) + countTxs(currentApp) dispatch( createTransaction( { diff --git a/src/routes/safe/components/Apps/trackAppUsageCount.ts b/src/routes/safe/components/Apps/trackAppUsageCount.ts new file mode 100644 index 0000000000..714b6f91d3 --- /dev/null +++ b/src/routes/safe/components/Apps/trackAppUsageCount.ts @@ -0,0 +1,35 @@ +import { SafeApp } from 'src/routes/safe/components/Apps/types' +import local from 'src/utils/storage/local' + +export const APPS_DASHBOARD = 'APPS_DASHBOARD' + +export type AppTrackData = { + [safeAppId: string]: { + timestamp: number + openCount: number + txCount: number + } +} + +export const countOpen = (app: SafeApp): void => { + const trackData = local.getItem(APPS_DASHBOARD) || {} + let currentOpenCount = trackData[app.id]?.openCount + local.setItem(APPS_DASHBOARD, { + ...trackData, + [app.id]: { + ...trackData[app.id], + timestamp: Date.now(), + openCount: currentOpenCount ? ++currentOpenCount : 1, + txCount: trackData[app.id]?.txCount || 0, + }, + }) +} + +export const countTxs = (app: SafeApp): void => { + const trackData = local.getItem(APPS_DASHBOARD) || {} + let currentTxCount = trackData[app.id]?.txCount + local.setItem(APPS_DASHBOARD, { + ...trackData, + [app.id]: { ...trackData[app.id], txCount: currentTxCount ? ++currentTxCount : 1 }, + }) +} diff --git a/src/routes/safe/components/Apps/types.ts b/src/routes/safe/components/Apps/types.ts index 4e59f2fa3b..16c7de1fd6 100644 --- a/src/routes/safe/components/Apps/types.ts +++ b/src/routes/safe/components/Apps/types.ts @@ -11,11 +11,3 @@ export type SafeApp = Omit & { export type StoredSafeApp = { url: string } - -export type AppTrackData = { - [safeAppId: string]: { - timestamp: number - openCount: number - txCount: number - } -} diff --git a/src/routes/safe/components/Apps/utils.ts b/src/routes/safe/components/Apps/utils.ts index 140eaf4144..e0618d2844 100644 --- a/src/routes/safe/components/Apps/utils.ts +++ b/src/routes/safe/components/Apps/utils.ts @@ -23,7 +23,6 @@ export interface AppManifest { providedBy: string } -export const APPS_DASHBOARD = 'APPS_DASHBOARD' export const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' export const PINNED_SAFE_APP_IDS = 'PINNED_SAFE_APP_IDS' export const EMPTY_SAFE_APP = 'unknown' From 47d2cef59fab171f82f7f0ae41b6f0e8ed5e5e7c Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Mon, 11 Apr 2022 18:08:55 +0200 Subject: [PATCH 24/34] chore: Add comments to the sorting formula --- src/components/Dashboard/SafeApps/utils.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Dashboard/SafeApps/utils.ts b/src/components/Dashboard/SafeApps/utils.ts index f29e08688b..e0dc9a85fe 100644 --- a/src/components/Dashboard/SafeApps/utils.ts +++ b/src/components/Dashboard/SafeApps/utils.ts @@ -1,7 +1,7 @@ import { AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' -const TXCOUNT_WEIGHT = 2 -const OPENCOUNT_WEIGHT = 1 +const TX_COUNT_WEIGHT = 2 +const OPEN_COUNT_WEIGHT = 1 const MORE_RECENT_MULTIPLIER = 2 const LESS_RECENT_MULTIPLIER = 1 @@ -10,13 +10,15 @@ export const rankTrackedSafeApps = (apps: AppTrackData): string[] => { return appsMap .sort((a, b) => { + // The more recently used app gets a bigger score/relevancy multiplier const aTimeMultiplier = a[1].timestamp.valueOf() - b[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER const bTimeMultiplier = b[1].timestamp.valueOf() - a[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER - const aScore = (TXCOUNT_WEIGHT * a[1].txCount + OPENCOUNT_WEIGHT * a[1].openCount) * aTimeMultiplier - const bScore = (TXCOUNT_WEIGHT * b[1].txCount + OPENCOUNT_WEIGHT * b[1].openCount) * bTimeMultiplier + // The sorting score is a weighted function where the OPEN_C0UNT weights differently than the TX_COUNT + const aScore = (TX_COUNT_WEIGHT * a[1].txCount + OPEN_COUNT_WEIGHT * a[1].openCount) * aTimeMultiplier + const bScore = (TX_COUNT_WEIGHT * b[1].txCount + OPEN_COUNT_WEIGHT * b[1].openCount) * bTimeMultiplier return bScore - aScore }) From 93d499973fb66207c7f87a2556337fc31c7e40b5 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Mon, 11 Apr 2022 19:28:25 +0200 Subject: [PATCH 25/34] chore: move app usage related functions to the same file --- src/components/Dashboard/SafeApps/Grid.tsx | 3 +-- src/components/Dashboard/SafeApps/utils.ts | 26 ------------------- .../__tests__/trackAppUsageCount.test.ts} | 3 +-- .../components/Apps/trackAppUsageCount.ts | 25 ++++++++++++++++++ 4 files changed, 27 insertions(+), 30 deletions(-) delete mode 100644 src/components/Dashboard/SafeApps/utils.ts rename src/{components/Dashboard/SafeApps/__tests__/utils.test.ts => routes/safe/components/Apps/__tests__/trackAppUsageCount.test.ts} (87%) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 4857bec936..8dd8476e49 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -9,8 +9,7 @@ import Card from 'src/components/Dashboard/SafeApps/Card' import ExploreIcon from 'src/assets/icons/explore.svg' import local from 'src/utils/storage/local' import { SafeApp } from 'src/routes/safe/components/Apps/types' -import { rankTrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' -import { APPS_DASHBOARD, AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' +import { APPS_DASHBOARD, AppTrackData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount' const StyledGrid = styled.div` display: flex; diff --git a/src/components/Dashboard/SafeApps/utils.ts b/src/components/Dashboard/SafeApps/utils.ts deleted file mode 100644 index e0dc9a85fe..0000000000 --- a/src/components/Dashboard/SafeApps/utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' - -const TX_COUNT_WEIGHT = 2 -const OPEN_COUNT_WEIGHT = 1 -const MORE_RECENT_MULTIPLIER = 2 -const LESS_RECENT_MULTIPLIER = 1 - -export const rankTrackedSafeApps = (apps: AppTrackData): string[] => { - const appsMap = Object.entries(apps) - - return appsMap - .sort((a, b) => { - // The more recently used app gets a bigger score/relevancy multiplier - const aTimeMultiplier = - a[1].timestamp.valueOf() - b[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER - const bTimeMultiplier = - b[1].timestamp.valueOf() - a[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER - - // The sorting score is a weighted function where the OPEN_C0UNT weights differently than the TX_COUNT - const aScore = (TX_COUNT_WEIGHT * a[1].txCount + OPEN_COUNT_WEIGHT * a[1].openCount) * aTimeMultiplier - const bScore = (TX_COUNT_WEIGHT * b[1].txCount + OPEN_COUNT_WEIGHT * b[1].openCount) * bTimeMultiplier - - return bScore - aScore - }) - .map((values) => values[0]) -} diff --git a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts b/src/routes/safe/components/Apps/__tests__/trackAppUsageCount.test.ts similarity index 87% rename from src/components/Dashboard/SafeApps/__tests__/utils.test.ts rename to src/routes/safe/components/Apps/__tests__/trackAppUsageCount.test.ts index 0d2b0d0e14..dedcbc6f7f 100644 --- a/src/components/Dashboard/SafeApps/__tests__/utils.test.ts +++ b/src/routes/safe/components/Apps/__tests__/trackAppUsageCount.test.ts @@ -1,5 +1,4 @@ -import { rankTrackedSafeApps } from 'src/components/Dashboard/SafeApps/utils' -import { AppTrackData } from 'src/routes/safe/components/Apps/trackAppUsageCount' +import { AppTrackData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount' describe('rankTrackedSafeApps', () => { it('ranks more recent apps higher', () => { diff --git a/src/routes/safe/components/Apps/trackAppUsageCount.ts b/src/routes/safe/components/Apps/trackAppUsageCount.ts index 714b6f91d3..d877bf5958 100644 --- a/src/routes/safe/components/Apps/trackAppUsageCount.ts +++ b/src/routes/safe/components/Apps/trackAppUsageCount.ts @@ -3,6 +3,11 @@ import local from 'src/utils/storage/local' export const APPS_DASHBOARD = 'APPS_DASHBOARD' +const TX_COUNT_WEIGHT = 2 +const OPEN_COUNT_WEIGHT = 1 +const MORE_RECENT_MULTIPLIER = 2 +const LESS_RECENT_MULTIPLIER = 1 + export type AppTrackData = { [safeAppId: string]: { timestamp: number @@ -33,3 +38,23 @@ export const countTxs = (app: SafeApp): void => { [app.id]: { ...trackData[app.id], txCount: currentTxCount ? ++currentTxCount : 1 }, }) } + +export const rankTrackedSafeApps = (apps: AppTrackData): string[] => { + const appsMap = Object.entries(apps) + + return appsMap + .sort((a, b) => { + // The more recently used app gets a bigger score/relevancy multiplier + const aTimeMultiplier = + a[1].timestamp.valueOf() - b[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + const bTimeMultiplier = + b[1].timestamp.valueOf() - a[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + + // The sorting score is a weighted function where the OPEN_COUNT weights differently than the TX_COUNT + const aScore = (TX_COUNT_WEIGHT * a[1].txCount + OPEN_COUNT_WEIGHT * a[1].openCount) * aTimeMultiplier + const bScore = (TX_COUNT_WEIGHT * b[1].txCount + OPEN_COUNT_WEIGHT * b[1].openCount) * bTimeMultiplier + + return bScore - aScore + }) + .map((values) => values[0]) +} From aba59ac34bafb78714feefab6716f7426033aba7 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Mon, 11 Apr 2022 22:39:43 +0200 Subject: [PATCH 26/34] fix: improve the setItem in the LS logic --- src/routes/safe/components/Apps/trackAppUsageCount.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/routes/safe/components/Apps/trackAppUsageCount.ts b/src/routes/safe/components/Apps/trackAppUsageCount.ts index d877bf5958..1948c86aa6 100644 --- a/src/routes/safe/components/Apps/trackAppUsageCount.ts +++ b/src/routes/safe/components/Apps/trackAppUsageCount.ts @@ -18,24 +18,25 @@ export type AppTrackData = { export const countOpen = (app: SafeApp): void => { const trackData = local.getItem(APPS_DASHBOARD) || {} - let currentOpenCount = trackData[app.id]?.openCount + const currentOpenCount = trackData[app.id]?.openCount || 0 + const currentTxCount = trackData[app.id]?.txCount || 0 local.setItem(APPS_DASHBOARD, { ...trackData, [app.id]: { ...trackData[app.id], timestamp: Date.now(), - openCount: currentOpenCount ? ++currentOpenCount : 1, - txCount: trackData[app.id]?.txCount || 0, + openCount: currentOpenCount + 1, + txCount: currentTxCount, }, }) } export const countTxs = (app: SafeApp): void => { const trackData = local.getItem(APPS_DASHBOARD) || {} - let currentTxCount = trackData[app.id]?.txCount + const currentTxCount = trackData[app.id]?.txCount || 0 local.setItem(APPS_DASHBOARD, { ...trackData, - [app.id]: { ...trackData[app.id], txCount: currentTxCount ? ++currentTxCount : 1 }, + [app.id]: { ...trackData[app.id], txCount: currentTxCount + 1 }, }) } From 3491252dbb26a4c7dbf467e04301d044890e6161 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 00:00:12 +0200 Subject: [PATCH 27/34] feat: add Skeleton Cards --- src/components/Dashboard/SafeApps/Grid.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 8dd8476e49..9c0adf85d3 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -10,6 +10,7 @@ import ExploreIcon from 'src/assets/icons/explore.svg' import local from 'src/utils/storage/local' import { SafeApp } from 'src/routes/safe/components/Apps/types' import { APPS_DASHBOARD, AppTrackData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount' +import Skeleton from '@material-ui/lab/Skeleton/Skeleton' const StyledGrid = styled.div` display: flex; @@ -17,6 +18,11 @@ const StyledGrid = styled.div` gap: 20px; ` +const SkeletonWrapper = styled.div` + border-radius: 8px; + overflow: hidden; +` + const StyledExplorerButton = styled.div` width: 260px; height: 200px; @@ -66,7 +72,13 @@ const Grid = ({ size = 3 }: { size?: number }): ReactElement => {

Safe Apps

{isLoading ? ( -

Loading...

+ + {Array.from(Array(size).keys()).map((key) => ( + + + + ))} + ) : ( {displayedApps.map((safeApp) => ( From 12b81db533734de2a44f48e4fc602efddbdbf714 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 00:10:08 +0200 Subject: [PATCH 28/34] fix: remove breadcrumbs in Dashboard --- src/components/Dashboard/SafeApps/Grid.tsx | 1 + src/routes/Home/index.tsx | 14 -------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 9c0adf85d3..74b4968fea 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -16,6 +16,7 @@ const StyledGrid = styled.div` display: flex; align-items: center; gap: 20px; + flex-wrap: wrap; ` const SkeletonWrapper = styled.div` diff --git a/src/routes/Home/index.tsx b/src/routes/Home/index.tsx index 6a9ed975bc..b953751491 100644 --- a/src/routes/Home/index.tsx +++ b/src/routes/Home/index.tsx @@ -1,14 +1,10 @@ import { ReactElement } from 'react' import styled from 'styled-components' -import { Breadcrumb, BreadcrumbElement, Menu } from '@gnosis.pm/safe-react-components' import Page from 'src/components/layout/Page' -import Col from 'src/components/layout/Col' import PendingTxsList from 'src/components/Dashboard/PendingTxs/PendingTxsList' - import AddSafeWidget from 'src/components/Dashboard/AddSafe' import CreateSafeWidget from 'src/components/Dashboard/CreateSafe' - import SafeApps from 'src/components/Dashboard/SafeApps/Grid' import Row from 'src/components/layout/Row' @@ -27,16 +23,6 @@ const Card = styled.div` function Home(): ReactElement { return ( - - - - - - - - - - From 7fac871e8d67fb7fa2bdd10f69207a00ccba1ee7 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 00:15:08 +0200 Subject: [PATCH 29/34] feat: always display the "Explore" card --- src/components/Dashboard/SafeApps/Grid.tsx | 22 +++++++++++----------- src/routes/Home/index.tsx | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 74b4968fea..986941ffb2 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -64,7 +64,9 @@ const Grid = ({ size = 3 }: { size?: number }): ReactElement => { // Do not repeat top ranked apps const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !rankedSafeAppIds.includes(app.id)) - return topRankedSafeApps.concat(officialApps).slice(0, size) + + // Display size - 1 in order to always display the "Explore Safe Apps" card + return topRankedSafeApps.concat(officialApps).slice(0, size - 1) }, [allApps, size]) const path = generatePath(GENERIC_APPS_ROUTE) @@ -93,16 +95,14 @@ const Grid = ({ size = 3 }: { size?: number }): ReactElement => { onPin={() => togglePin(safeApp)} /> ))} - {displayedApps.length < size && ( - - Explore Safe Apps - - - - - )} + + Explore Safe Apps + + + + )}
diff --git a/src/routes/Home/index.tsx b/src/routes/Home/index.tsx index b953751491..abc43f908a 100644 --- a/src/routes/Home/index.tsx +++ b/src/routes/Home/index.tsx @@ -45,7 +45,7 @@ function Home(): ReactElement { - + ) From 75feb5807190e979f37ff497503c673246beb470 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 13:15:05 +0200 Subject: [PATCH 30/34] rename safe app tracking methods. extract card dimensions to constants --- src/components/Dashboard/SafeApps/Card.tsx | 10 ++++++--- src/components/Dashboard/SafeApps/Grid.tsx | 11 +++++----- .../components/Apps/components/AppFrame.tsx | 4 ++-- .../ConfirmTxModal/ReviewConfirm.tsx | 4 ++-- .../components/Apps/trackAppUsageCount.ts | 21 +++++++++++-------- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Card.tsx b/src/components/Dashboard/SafeApps/Card.tsx index e528457806..0181bec306 100644 --- a/src/components/Dashboard/SafeApps/Card.tsx +++ b/src/components/Dashboard/SafeApps/Card.tsx @@ -11,13 +11,17 @@ const StyledLink = styled(Link)` color: black; ` +export const CARD_WIDTH = 260 +export const CARD_HEIGHT = 200 +export const CARD_PADDING = 24 + const StyledCard = styled.div` position: relative; - width: 260px; - height: 200px; + width: ${CARD_WIDTH}px; + height: ${CARD_HEIGHT}px; background-color: white; border-radius: 8px; - padding: 24px; + padding: ${CARD_PADDING}px; ` const StyledLogo = styled.img` diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 986941ffb2..64c9aae730 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -2,15 +2,14 @@ import { ReactElement, useMemo } from 'react' import styled from 'styled-components' import { Button } from '@gnosis.pm/safe-react-components' import { generatePath, Link } from 'react-router-dom' +import Skeleton from '@material-ui/lab/Skeleton/Skeleton' import { useAppList } from 'src/routes/safe/components/Apps/hooks/appList/useAppList' import { GENERIC_APPS_ROUTE } from 'src/routes/routes' -import Card from 'src/components/Dashboard/SafeApps/Card' +import Card, { CARD_HEIGHT, CARD_PADDING, CARD_WIDTH } from 'src/components/Dashboard/SafeApps/Card' import ExploreIcon from 'src/assets/icons/explore.svg' -import local from 'src/utils/storage/local' import { SafeApp } from 'src/routes/safe/components/Apps/types' -import { APPS_DASHBOARD, AppTrackData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount' -import Skeleton from '@material-ui/lab/Skeleton/Skeleton' +import { getAppsUsageData, rankTrackedSafeApps } from 'src/routes/safe/components/Apps/trackAppUsageCount' const StyledGrid = styled.div` display: flex; @@ -53,7 +52,7 @@ const Grid = ({ size = 3 }: { size?: number }): ReactElement => { const { allApps, pinnedSafeApps, togglePin, isLoading } = useAppList() const displayedApps = useMemo(() => { - const trackData = local.getItem(APPS_DASHBOARD) || {} + const trackData = getAppsUsageData() const rankedSafeAppIds = rankTrackedSafeApps(trackData) const topRankedSafeApps: SafeApp[] = [] @@ -78,7 +77,7 @@ const Grid = ({ size = 3 }: { size?: number }): ReactElement => { {Array.from(Array(size).keys()).map((key) => ( - + ))} diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index a5ddc2049c..6ecdc0705a 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -38,7 +38,7 @@ import { SAFE_APPS_EVENTS } from 'src/utils/events/safeApps' import { trackEvent } from 'src/utils/googleTagManager' import { checksumAddress } from 'src/utils/checksumAddress' import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' -import { countOpen } from 'src/routes/safe/components/Apps/trackAppUsageCount' +import { trackSafeAppOpenCount } from 'src/routes/safe/components/Apps/trackAppUsageCount' const AppWrapper = styled.div` display: flex; @@ -141,7 +141,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { useEffect(() => { if (!currentApp) return - countOpen(currentApp) + trackSafeAppOpenCount(currentApp) }, [currentApp]) const openConfirmationModal = useCallback( diff --git a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx index 98a7db1449..841943ccc8 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx @@ -27,7 +27,7 @@ import { TxModalWrapper } from 'src/routes/safe/components/Transactions/helpers/ import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' -import { countTxs } from 'src/routes/safe/components/Apps/trackAppUsageCount' +import { trackSafeAppTxCount } from 'src/routes/safe/components/Apps/trackAppUsageCount' const Container = styled.div` max-width: 480px; @@ -116,7 +116,7 @@ export const ReviewConfirm = ({ } const confirmTransactions = (txParameters: TxParameters, delayExecution: boolean) => { - countTxs(currentApp) + trackSafeAppTxCount(currentApp) dispatch( createTransaction( { diff --git a/src/routes/safe/components/Apps/trackAppUsageCount.ts b/src/routes/safe/components/Apps/trackAppUsageCount.ts index 1948c86aa6..c9ed6a332d 100644 --- a/src/routes/safe/components/Apps/trackAppUsageCount.ts +++ b/src/routes/safe/components/Apps/trackAppUsageCount.ts @@ -16,14 +16,18 @@ export type AppTrackData = { } } -export const countOpen = (app: SafeApp): void => { - const trackData = local.getItem(APPS_DASHBOARD) || {} +export const getAppsUsageData = (): AppTrackData => { + return local.getItem(APPS_DASHBOARD) || {} +} + +export const trackSafeAppOpenCount = (app: SafeApp): void => { + const trackData = getAppsUsageData() const currentOpenCount = trackData[app.id]?.openCount || 0 const currentTxCount = trackData[app.id]?.txCount || 0 + local.setItem(APPS_DASHBOARD, { ...trackData, [app.id]: { - ...trackData[app.id], timestamp: Date.now(), openCount: currentOpenCount + 1, txCount: currentTxCount, @@ -31,9 +35,10 @@ export const countOpen = (app: SafeApp): void => { }) } -export const countTxs = (app: SafeApp): void => { - const trackData = local.getItem(APPS_DASHBOARD) || {} +export const trackSafeAppTxCount = (app: SafeApp): void => { + const trackData = getAppsUsageData() const currentTxCount = trackData[app.id]?.txCount || 0 + local.setItem(APPS_DASHBOARD, { ...trackData, [app.id]: { ...trackData[app.id], txCount: currentTxCount + 1 }, @@ -46,10 +51,8 @@ export const rankTrackedSafeApps = (apps: AppTrackData): string[] => { return appsMap .sort((a, b) => { // The more recently used app gets a bigger score/relevancy multiplier - const aTimeMultiplier = - a[1].timestamp.valueOf() - b[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER - const bTimeMultiplier = - b[1].timestamp.valueOf() - a[1].timestamp.valueOf() > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + const aTimeMultiplier = a[1].timestamp - b[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER + const bTimeMultiplier = b[1].timestamp - a[1].timestamp > 0 ? MORE_RECENT_MULTIPLIER : LESS_RECENT_MULTIPLIER // The sorting score is a weighted function where the OPEN_COUNT weights differently than the TX_COUNT const aScore = (TX_COUNT_WEIGHT * a[1].txCount + OPEN_COUNT_WEIGHT * a[1].openCount) * aTimeMultiplier From c76c2a89baa2b99ae088f33c8929d8df851b3786 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 14:34:54 +0200 Subject: [PATCH 31/34] include random apps to fill the gaps in the widget --- src/components/Dashboard/SafeApps/Grid.tsx | 31 ++++++++++++++++++---- src/routes/Home/index.tsx | 4 +-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 64c9aae730..502be11bd4 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -46,12 +46,33 @@ const StyledLink = styled(Link)` ` // Transactions Builder && Wallet connect -const officialAppIds = ['29', '11'] +const featuredAppsId = ['29', '11'] + +const getRandomApps = (allApps: SafeApp[], size: number) => { + const randomIndexes: string[] = [] + for (let i = 1; randomIndexes.length < size; i++) { + const randomAppIndex = Math.floor(Math.random() * allApps.length).toString() + const randomAppId = allApps[randomAppIndex].id + + // Do not repeat random apps or featured apps + if (!randomIndexes.includes(randomAppIndex) && !featuredAppsId.includes(randomAppId)) { + randomIndexes.push(randomAppIndex) + } + } + + const randomSafeApps: SafeApp[] = [] + randomIndexes.forEach((index) => { + randomSafeApps.push(allApps[index]) + }) + + return randomSafeApps +} -const Grid = ({ size = 3 }: { size?: number }): ReactElement => { +const Grid = ({ size = 6 }: { size?: number }): ReactElement => { const { allApps, pinnedSafeApps, togglePin, isLoading } = useAppList() const displayedApps = useMemo(() => { + if (!allApps.length) return [] const trackData = getAppsUsageData() const rankedSafeAppIds = rankTrackedSafeApps(trackData) @@ -61,11 +82,11 @@ const Grid = ({ size = 3 }: { size?: number }): ReactElement => { if (sortedApp) topRankedSafeApps.push(sortedApp) }) - // Do not repeat top ranked apps - const officialApps = allApps.filter((app) => officialAppIds.includes(app.id) && !rankedSafeAppIds.includes(app.id)) + // Get random apps to fill the empty slots + const randomApps = getRandomApps(allApps, size - 1 - rankedSafeAppIds.length) // Display size - 1 in order to always display the "Explore Safe Apps" card - return topRankedSafeApps.concat(officialApps).slice(0, size - 1) + return topRankedSafeApps.concat(randomApps).slice(0, size - 1) }, [allApps, size]) const path = generatePath(GENERIC_APPS_ROUTE) diff --git a/src/routes/Home/index.tsx b/src/routes/Home/index.tsx index abc43f908a..a2b6cfb5f2 100644 --- a/src/routes/Home/index.tsx +++ b/src/routes/Home/index.tsx @@ -5,7 +5,7 @@ import Page from 'src/components/layout/Page' import PendingTxsList from 'src/components/Dashboard/PendingTxs/PendingTxsList' import AddSafeWidget from 'src/components/Dashboard/AddSafe' import CreateSafeWidget from 'src/components/Dashboard/CreateSafe' -import SafeApps from 'src/components/Dashboard/SafeApps/Grid' +import SafeAppsGrid from 'src/components/Dashboard/SafeApps/Grid' import Row from 'src/components/layout/Row' const Card = styled.div` @@ -45,7 +45,7 @@ function Home(): ReactElement { - + ) From b94a01af35852fe113a77b4e171e84f97dd3b281 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 16:21:17 +0200 Subject: [PATCH 32/34] prop drill the safeApp number id --- src/components/Dashboard/SafeApps/Grid.tsx | 13 +++++++------ .../safe/components/Apps/components/AppFrame.tsx | 3 ++- .../ConfirmTxModal/ConfirmTxModal.test.tsx | 9 +++++++++ .../components/ConfirmTxModal/ReviewConfirm.tsx | 6 ++---- .../Apps/components/ConfirmTxModal/index.tsx | 1 + .../safe/components/Apps/trackAppUsageCount.ts | 14 +++++++------- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Grid.tsx b/src/components/Dashboard/SafeApps/Grid.tsx index 502be11bd4..8d1f0c1bc9 100644 --- a/src/components/Dashboard/SafeApps/Grid.tsx +++ b/src/components/Dashboard/SafeApps/Grid.tsx @@ -48,11 +48,11 @@ const StyledLink = styled(Link)` // Transactions Builder && Wallet connect const featuredAppsId = ['29', '11'] -const getRandomApps = (allApps: SafeApp[], size: number) => { +const getRandomApps = (nonRankedApps: SafeApp[], size: number) => { const randomIndexes: string[] = [] for (let i = 1; randomIndexes.length < size; i++) { - const randomAppIndex = Math.floor(Math.random() * allApps.length).toString() - const randomAppId = allApps[randomAppIndex].id + const randomAppIndex = Math.floor(Math.random() * nonRankedApps.length).toString() + const randomAppId = nonRankedApps[randomAppIndex].id // Do not repeat random apps or featured apps if (!randomIndexes.includes(randomAppIndex) && !featuredAppsId.includes(randomAppId)) { @@ -62,7 +62,7 @@ const getRandomApps = (allApps: SafeApp[], size: number) => { const randomSafeApps: SafeApp[] = [] randomIndexes.forEach((index) => { - randomSafeApps.push(allApps[index]) + randomSafeApps.push(nonRankedApps[index]) }) return randomSafeApps @@ -82,8 +82,9 @@ const Grid = ({ size = 6 }: { size?: number }): ReactElement => { if (sortedApp) topRankedSafeApps.push(sortedApp) }) - // Get random apps to fill the empty slots - const randomApps = getRandomApps(allApps, size - 1 - rankedSafeAppIds.length) + const nonRankedApps = allApps.filter((app) => !rankedSafeAppIds.includes(app.id)) + // Get random apps that are not ranked + const randomApps = getRandomApps(nonRankedApps, size - 1 - rankedSafeAppIds.length) // Display size - 1 in order to always display the "Explore Safe Apps" card return topRankedSafeApps.concat(randomApps).slice(0, size - 1) diff --git a/src/routes/safe/components/Apps/components/AppFrame.tsx b/src/routes/safe/components/Apps/components/AppFrame.tsx index 6ecdc0705a..49a01cfec6 100644 --- a/src/routes/safe/components/Apps/components/AppFrame.tsx +++ b/src/routes/safe/components/Apps/components/AppFrame.tsx @@ -141,7 +141,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { useEffect(() => { if (!currentApp) return - trackSafeAppOpenCount(currentApp) + trackSafeAppOpenCount(currentApp.id) }, [currentApp]) const openConfirmationModal = useCallback( @@ -376,6 +376,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => { onUserConfirm={onUserTxConfirm} params={confirmTransactionModal.params} onTxReject={onTxReject} + appId={currentApp?.id} /> { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -88,6 +89,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -117,6 +119,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -148,6 +151,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -179,6 +183,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -204,6 +209,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -236,6 +242,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -264,6 +271,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) @@ -297,6 +305,7 @@ describe('ConfirmTxModal Component', () => { onTxReject={jest.fn()} requestId="1" app={getEmptySafeApp()} + appId="1" />, ) diff --git a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx index 841943ccc8..d82248ea3f 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTxModal/ReviewConfirm.tsx @@ -26,7 +26,6 @@ import { grantedSelector } from 'src/routes/safe/container/selector' import { TxModalWrapper } from 'src/routes/safe/components/Transactions/helpers/TxModalWrapper' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { useRemoteSafeApps } from 'src/routes/safe/components/Apps/hooks/appList/useRemoteSafeApps' import { trackSafeAppTxCount } from 'src/routes/safe/components/Apps/trackAppUsageCount' const Container = styled.div` @@ -70,6 +69,7 @@ export const ReviewConfirm = ({ onClose, onReject, requestId, + appId, }: Props): ReactElement => { const isMultiSend = txs.length > 1 const [decodedData, setDecodedData] = useState() @@ -77,8 +77,6 @@ export const ReviewConfirm = ({ const nativeCurrency = getNativeCurrency() const explorerUrl = getExplorerInfo(safeAddress) const isOwner = useSelector(grantedSelector) - const { remoteSafeApps } = useRemoteSafeApps() - const currentApp = remoteSafeApps.filter((remoteApp) => remoteApp.url === app.url)[0] const txRecipient: string | undefined = useMemo( () => (isMultiSend ? getMultisendContractAddress() : txs[0]?.to), @@ -116,7 +114,7 @@ export const ReviewConfirm = ({ } const confirmTransactions = (txParameters: TxParameters, delayExecution: boolean) => { - trackSafeAppTxCount(currentApp) + trackSafeAppTxCount(appId) dispatch( createTransaction( { diff --git a/src/routes/safe/components/Apps/components/ConfirmTxModal/index.tsx b/src/routes/safe/components/Apps/components/ConfirmTxModal/index.tsx index 79c5ac9650..882b551a1d 100644 --- a/src/routes/safe/components/Apps/components/ConfirmTxModal/index.tsx +++ b/src/routes/safe/components/Apps/components/ConfirmTxModal/index.tsx @@ -22,6 +22,7 @@ export type ConfirmTxModalProps = { onUserConfirm: (safeTxHash: string, requestId: RequestId) => void onTxReject: (requestId: RequestId) => void onClose: () => void + appId: string } const isTxValid = (t: Transaction): boolean => { diff --git a/src/routes/safe/components/Apps/trackAppUsageCount.ts b/src/routes/safe/components/Apps/trackAppUsageCount.ts index c9ed6a332d..960c5e9911 100644 --- a/src/routes/safe/components/Apps/trackAppUsageCount.ts +++ b/src/routes/safe/components/Apps/trackAppUsageCount.ts @@ -20,14 +20,14 @@ export const getAppsUsageData = (): AppTrackData => { return local.getItem(APPS_DASHBOARD) || {} } -export const trackSafeAppOpenCount = (app: SafeApp): void => { +export const trackSafeAppOpenCount = (id: SafeApp['id']): void => { const trackData = getAppsUsageData() - const currentOpenCount = trackData[app.id]?.openCount || 0 - const currentTxCount = trackData[app.id]?.txCount || 0 + const currentOpenCount = trackData[id]?.openCount || 0 + const currentTxCount = trackData[id]?.txCount || 0 local.setItem(APPS_DASHBOARD, { ...trackData, - [app.id]: { + [id]: { timestamp: Date.now(), openCount: currentOpenCount + 1, txCount: currentTxCount, @@ -35,13 +35,13 @@ export const trackSafeAppOpenCount = (app: SafeApp): void => { }) } -export const trackSafeAppTxCount = (app: SafeApp): void => { +export const trackSafeAppTxCount = (id: SafeApp['id']): void => { const trackData = getAppsUsageData() - const currentTxCount = trackData[app.id]?.txCount || 0 + const currentTxCount = trackData[id]?.txCount || 0 local.setItem(APPS_DASHBOARD, { ...trackData, - [app.id]: { ...trackData[app.id], txCount: currentTxCount + 1 }, + [id]: { ...trackData[id], txCount: currentTxCount + 1 }, }) } From b71f360d10b011af5644bcaedde89243c15c1bee Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 12 Apr 2022 16:51:28 +0200 Subject: [PATCH 33/34] code comments clean up --- src/components/Dashboard/SafeApps/Card.tsx | 2 -- src/routes/safe/components/Apps/trackAppUsageCount.ts | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Card.tsx b/src/components/Dashboard/SafeApps/Card.tsx index 0181bec306..32cd0fe2bd 100644 --- a/src/components/Dashboard/SafeApps/Card.tsx +++ b/src/components/Dashboard/SafeApps/Card.tsx @@ -88,8 +88,6 @@ const Card = (props: CardProps): ReactElement => { {/* Bookmark button */} {localPinned ? : } - - {/* TODO: Share button */}
) diff --git a/src/routes/safe/components/Apps/trackAppUsageCount.ts b/src/routes/safe/components/Apps/trackAppUsageCount.ts index 960c5e9911..372d90de24 100644 --- a/src/routes/safe/components/Apps/trackAppUsageCount.ts +++ b/src/routes/safe/components/Apps/trackAppUsageCount.ts @@ -41,6 +41,7 @@ export const trackSafeAppTxCount = (id: SafeApp['id']): void => { local.setItem(APPS_DASHBOARD, { ...trackData, + // The object contains the openCount when we are creating a transaction [id]: { ...trackData[id], txCount: currentTxCount + 1 }, }) } From b80f4a7b773d087fa10a385c5a94c5c0991036b9 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 13 Apr 2022 11:21:01 +0200 Subject: [PATCH 34/34] fix: make logo height fix to align SafeApp name --- src/components/Dashboard/SafeApps/Card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Dashboard/SafeApps/Card.tsx b/src/components/Dashboard/SafeApps/Card.tsx index 32cd0fe2bd..425b82fb5c 100644 --- a/src/components/Dashboard/SafeApps/Card.tsx +++ b/src/components/Dashboard/SafeApps/Card.tsx @@ -26,8 +26,8 @@ const StyledCard = styled.div` const StyledLogo = styled.img` display: block; - width: 60px; - height: auto; + width: auto; + height: 60px; ` const IconBtn = styled(IconButton)`