From 49dab7d8115a2f3091b8ebdeb21e1476d48c757a Mon Sep 17 00:00:00 2001 From: James Mealy Date: Thu, 30 May 2024 15:36:54 +0100 Subject: [PATCH 01/21] feat: add native swap card to storybook --- .../NativeFeatureCard/index.stories.tsx | 32 ++++++++++++ .../safe-apps/NativeFeatureCard/index.tsx | 47 +++++++++++++++++ .../NativeFeatureCard/styles.module.css | 50 +++++++++++++++++++ src/pages/apps/index.tsx | 17 +++++++ 4 files changed, 146 insertions(+) create mode 100644 src/components/safe-apps/NativeFeatureCard/index.stories.tsx create mode 100644 src/components/safe-apps/NativeFeatureCard/index.tsx create mode 100644 src/components/safe-apps/NativeFeatureCard/styles.module.css diff --git a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx new file mode 100644 index 0000000000..f9a88f290d --- /dev/null +++ b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from '@storybook/react' +import NativeFeatureCard from './index' +import { Box } from '@mui/material' + +const meta = { + component: NativeFeatureCard, + parameters: { + componentSubtitle: 'Renders an order id with an external link and a copy button', + }, + + decorators: [ + (Story) => { + return ( + + + + ) + }, + ], + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + // orderId: + // '0x1282e32a3a69aeb5a65fcdec0ae40fe16b398d54609bc9a3c8be3eb57d1a0fd07a9af6ef9197041a5841e84cb27873bebd3486e2661510ab', + // href: 'https://explorer.cow.fi/orders/0x1282e32a3a69aeb5a65fcdec0ae40fe16b398d54609bc9a3c8be3eb57d1a0fd07a9af6ef9197041a5841e84cb27873bebd3486e2661510ab', + }, +} diff --git a/src/components/safe-apps/NativeFeatureCard/index.tsx b/src/components/safe-apps/NativeFeatureCard/index.tsx new file mode 100644 index 0000000000..107148e236 --- /dev/null +++ b/src/components/safe-apps/NativeFeatureCard/index.tsx @@ -0,0 +1,47 @@ +import CardHeader from '@mui/material/CardHeader' +import CardContent from '@mui/material/CardContent' +import Typography from '@mui/material/Typography' +import SwapIcon from '@/public/images/common/swap.svg' + +import css from './styles.module.css' +import { Button, Paper, Stack, SvgIcon } from '@mui/material' + +const SafeAppCardGridView = () => ( + + {/* Safe App Header */} + + {/* Safe App Icon */} + + + } + /> + + + {/* Safe App Title */} + + Native swaps are here! + + + {/* Safe App Description */} + + Experience seamless trading with better decoding and security in native swaps. + + + + + + + + {/* */} + + +) + +export default SafeAppCardGridView diff --git a/src/components/safe-apps/NativeFeatureCard/styles.module.css b/src/components/safe-apps/NativeFeatureCard/styles.module.css new file mode 100644 index 0000000000..6164687651 --- /dev/null +++ b/src/components/safe-apps/NativeFeatureCard/styles.module.css @@ -0,0 +1,50 @@ +.safeAppContainer { + transition: background-color 0.3s ease-in-out, border 0.3s ease-in-out; + border: 1px solid transparent; +} + +.safeAppContainer:hover { + background-color: var(--color-background-light); + border: 1px solid var(--color-secondary-light); +} + +.safeAppHeader { + padding: var(--space-2) var(--space-2) 0 var(--space-2) !important; +} + +.safeAppContent { + padding: var(--space-2); +} + +.safeAppIconContainer { + position: relative; + background: var(--color-secondary-light); + border-radius: 50%; + display: flex; + padding: var(--space-1); +} + +.safeAppTitle { + line-height: 175%; + margin: 0; + + flex-grow: 1; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.safeAppDescription { + /* Truncate Safe App Description (3 lines) */ + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.buttons { + padding-top: var(--space-2); + /* display: flex; + justify-content: space-between; */ +} diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 4839e57974..355fe1b5b5 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -14,6 +14,23 @@ import SafeAppsFilters from '@/components/safe-apps/SafeAppsFilters' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' +// const getSwapAppBanner = () => { +// return { +// id: 100_000, +// url: normalizedAppUrl, +// name: appManifest.name, +// description: appManifest.description, +// accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, +// tags: [], +// features: [], +// socialProfiles: [], +// developerWebsite: '', +// chainIds: [currentChainId], +// iconUrl, +// safeAppsPermissions: '', +// } +// } + const SafeApps: NextPage = () => { const router = useRouter() const { remoteSafeApps, remoteSafeAppsLoading, pinnedSafeApps, pinnedSafeAppIds, togglePin } = useSafeApps() From c423b0995bf6f3ac36f92f9b5d4a3bbb9b6fd230 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 31 May 2024 12:10:35 +0100 Subject: [PATCH 02/21] make card clickable and handle dismiss --- .../NativeFeatureCard/index.stories.tsx | 17 +++- .../safe-apps/NativeFeatureCard/index.tsx | 77 ++++++++++--------- .../NativeFeatureCard/styles.module.css | 3 +- .../useNativeSwapsAppCard.ts | 31 ++++++++ .../safe-apps/SafeAppList/index.tsx | 49 +++++++++--- src/pages/apps/index.tsx | 38 +++++---- 6 files changed, 145 insertions(+), 70 deletions(-) create mode 100644 src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts diff --git a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx index f9a88f290d..4768a6b56f 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx +++ b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react' import NativeFeatureCard from './index' import { Box } from '@mui/material' +import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' const meta = { component: NativeFeatureCard, @@ -25,8 +26,18 @@ type Story = StoryObj export const Default: Story = { args: { - // orderId: - // '0x1282e32a3a69aeb5a65fcdec0ae40fe16b398d54609bc9a3c8be3eb57d1a0fd07a9af6ef9197041a5841e84cb27873bebd3486e2661510ab', - // href: 'https://explorer.cow.fi/orders/0x1282e32a3a69aeb5a65fcdec0ae40fe16b398d54609bc9a3c8be3eb57d1a0fd07a9af6ef9197041a5841e84cb27873bebd3486e2661510ab', + details: { + id: 100_000, + url: '', + name: 'Native swaps are here!', + description: 'Experience seamless trading with better decoding and security in native swaps.', + accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, + tags: ['DeFi'], + features: [], + socialProfiles: [], + developerWebsite: '', + chainIds: ['11155111'], + iconUrl: '@/public/images/common/swap.svg', + }, }, } diff --git a/src/components/safe-apps/NativeFeatureCard/index.tsx b/src/components/safe-apps/NativeFeatureCard/index.tsx index 107148e236..7a267bfa80 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.tsx +++ b/src/components/safe-apps/NativeFeatureCard/index.tsx @@ -1,47 +1,52 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import Typography from '@mui/material/Typography' -import SwapIcon from '@/public/images/common/swap.svg' import css from './styles.module.css' -import { Button, Paper, Stack, SvgIcon } from '@mui/material' +import { Button, Paper, Stack } from '@mui/material' +import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' +import SafeAppIconCard from '../SafeAppIconCard' -const SafeAppCardGridView = () => ( - - {/* Safe App Header */} - - {/* Safe App Icon */} - - - } - /> +type NativeFeatureCardProps = { + details: SafeAppData + onClick: () => void + onDismiss: () => void +} - - {/* Safe App Title */} - - Native swaps are here! - +const NativeFeatureCard = ({ details, onClick, onDismiss }: NativeFeatureCardProps) => { + return ( + + {/* Safe App Header */} + + {/* */} + + + } + /> - {/* Safe App Description */} - - Experience seamless trading with better decoding and security in native swaps. - + + + {details.name} + - - - - + + {details.description} + - {/* */} - - -) + + + + + + + ) +} -export default SafeAppCardGridView +export default NativeFeatureCard diff --git a/src/components/safe-apps/NativeFeatureCard/styles.module.css b/src/components/safe-apps/NativeFeatureCard/styles.module.css index 6164687651..7fea639fef 100644 --- a/src/components/safe-apps/NativeFeatureCard/styles.module.css +++ b/src/components/safe-apps/NativeFeatureCard/styles.module.css @@ -1,6 +1,7 @@ .safeAppContainer { transition: background-color 0.3s ease-in-out, border 0.3s ease-in-out; border: 1px solid transparent; + height: 100%; } .safeAppContainer:hover { @@ -45,6 +46,4 @@ .buttons { padding-top: var(--space-2); - /* display: flex; - justify-content: space-between; */ } diff --git a/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts b/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts new file mode 100644 index 0000000000..7db8014f8b --- /dev/null +++ b/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts @@ -0,0 +1,31 @@ +import useChainId from '@/hooks/useChainId' +import useLocalStorage from '@/services/local-storage/useLocalStorage' +import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' +import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' + +const SWAPS_APP_CARD_STORAGE_KEY = 'showSwapsAppCard' + +export function useNativeSwapsAppCard() { + const chainId = useChainId() + const [isVisible = true, setIsVisible] = useLocalStorage(SWAPS_APP_CARD_STORAGE_KEY) + + const swapsCard: SafeAppData = { + id: 100_000, + url: '', + name: 'Native swaps are here!', + description: 'Experience seamless trading with better decoding and security in native swaps.', + accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, + tags: ['DeFi'], + features: [], + socialProfiles: [], + developerWebsite: '', + chainIds: [chainId], + iconUrl: '/images/common/swap.svg', + } + + return { + swapsCardDetails: swapsCard, + isVisible, + setIsVisible, + } +} diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index c9a7daf84e..79fa246117 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -10,6 +10,10 @@ import useSafeAppPreviewDrawer from '@/hooks/safe-apps/useSafeAppPreviewDrawer' import css from './styles.module.css' import { Skeleton } from '@mui/material' import { useOpenedSafeApps } from '@/hooks/safe-apps/useOpenedSafeApps' +import NativeFeatureCard from '../NativeFeatureCard' +import { useNativeSwapsAppCard } from '../NativeFeatureCard/useNativeSwapsAppCard' +import { AppRoutes } from '@/config/routes' +import { useRouter } from 'next/router' type SafeAppListProps = { safeAppsList: SafeAppData[] @@ -34,6 +38,8 @@ const SafeAppList = ({ }: SafeAppListProps) => { const { isPreviewDrawerOpen, previewDrawerApp, openPreviewDrawer, closePreviewDrawer } = useSafeAppPreviewDrawer() const { openedSafeAppIds } = useOpenedSafeApps() + const { isVisible, setIsVisible } = useNativeSwapsAppCard() + const router = useRouter() const showZeroResultsPlaceholder = query && safeAppsList.length === 0 @@ -48,6 +54,13 @@ const SafeAppList = ({ [openPreviewDrawer, openedSafeAppIds], ) + const handleNativeAppClick = useCallback(() => { + router.push({ + pathname: AppRoutes.swap, + query: router.query, + }) + }, [router]) + return ( <> {/* Safe Apps List Header */} @@ -70,18 +83,30 @@ const SafeAppList = ({ ))} {/* Flat list filtered by search query */} - {safeAppsList.map((safeApp) => ( -
  • - -
  • - ))} + {safeAppsList.map((safeApp) => { + const isNativeFeatureCard = !safeApp.url + if (isNativeFeatureCard && !isVisible) return null + return !isNativeFeatureCard ? ( +
  • + +
  • + ) : isVisible ? ( +
  • + handleNativeAppClick()} + onDismiss={() => setIsVisible(false)} + /> +
  • + ) : null + })} {/* Zero results placeholder */} diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 355fe1b5b5..7a03428fb3 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -13,29 +13,33 @@ import useSafeAppsFilters from '@/hooks/safe-apps/useSafeAppsFilters' import SafeAppsFilters from '@/components/safe-apps/SafeAppsFilters' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' +import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' +import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' +import { useNativeSwapsAppCard } from '@/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard' -// const getSwapAppBanner = () => { -// return { -// id: 100_000, -// url: normalizedAppUrl, -// name: appManifest.name, -// description: appManifest.description, -// accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, -// tags: [], -// features: [], -// socialProfiles: [], -// developerWebsite: '', -// chainIds: [currentChainId], -// iconUrl, -// safeAppsPermissions: '', -// } -// } +const getSwapAppBanner = (): SafeAppData => { + return { + id: 100_000, + url: '', + name: 'Native swaps are here!', + description: 'Experience seamless trading with better decoding and security in native swaps.', + accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, + tags: ['DeFi'], + features: [], + socialProfiles: [], + developerWebsite: '', + chainIds: ['11155111'], + iconUrl: '@/public/images/common/swap.svg', + } +} const SafeApps: NextPage = () => { const router = useRouter() + const { swapsCardDetails } = useNativeSwapsAppCard() const { remoteSafeApps, remoteSafeAppsLoading, pinnedSafeApps, pinnedSafeAppIds, togglePin } = useSafeApps() + const allApps = [swapsCardDetails, ...remoteSafeApps] const { filteredApps, query, setQuery, setSelectedCategories, setOptimizedWithBatchFilter, selectedCategories } = - useSafeAppsFilters(remoteSafeApps) + useSafeAppsFilters(allApps) const isFiltered = filteredApps.length !== remoteSafeApps.length const isSafeAppsEnabled = useHasFeature(FEATURES.SAFE_APPS) From 3e1445ce420c075320b12b3f1313b4b704c54c68 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 31 May 2024 13:41:16 +0100 Subject: [PATCH 03/21] add flag to safe apps card type for native features --- .../NativeFeatureCard/index.stories.tsx | 4 +++- .../safe-apps/NativeFeatureCard/index.tsx | 4 +--- .../useNativeSwapsAppCard.ts | 4 +++- .../safe-apps/SafeAppList/index.tsx | 22 +++++++++---------- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx index 4768a6b56f..c196fe669a 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx +++ b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx @@ -37,7 +37,9 @@ export const Default: Story = { socialProfiles: [], developerWebsite: '', chainIds: ['11155111'], - iconUrl: '@/public/images/common/swap.svg', + iconUrl: '/images/common/swap.svg', }, + onClick: () => {}, + onDismiss: () => {}, }, } diff --git a/src/components/safe-apps/NativeFeatureCard/index.tsx b/src/components/safe-apps/NativeFeatureCard/index.tsx index 7a267bfa80..923cf9c4e5 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.tsx +++ b/src/components/safe-apps/NativeFeatureCard/index.tsx @@ -1,11 +1,10 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import Typography from '@mui/material/Typography' - -import css from './styles.module.css' import { Button, Paper, Stack } from '@mui/material' import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' import SafeAppIconCard from '../SafeAppIconCard' +import css from './styles.module.css' type NativeFeatureCardProps = { details: SafeAppData @@ -21,7 +20,6 @@ const NativeFeatureCard = ({ details, onClick, onDismiss }: NativeFeatureCardPro className={css.safeAppHeader} avatar={
    - {/* */}
    } diff --git a/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts b/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts index 7db8014f8b..e8a207fc10 100644 --- a/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts +++ b/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts @@ -1,3 +1,4 @@ +import { AppRoutes } from '@/config/routes' import useChainId from '@/hooks/useChainId' import useLocalStorage from '@/services/local-storage/useLocalStorage' import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' @@ -11,7 +12,7 @@ export function useNativeSwapsAppCard() { const swapsCard: SafeAppData = { id: 100_000, - url: '', + url: AppRoutes.swap, name: 'Native swaps are here!', description: 'Experience seamless trading with better decoding and security in native swaps.', accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, @@ -21,6 +22,7 @@ export function useNativeSwapsAppCard() { developerWebsite: '', chainIds: [chainId], iconUrl: '/images/common/swap.svg', + isNativeFeature: true, } return { diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index 79fa246117..a59e49e398 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -12,7 +12,6 @@ import { Skeleton } from '@mui/material' import { useOpenedSafeApps } from '@/hooks/safe-apps/useOpenedSafeApps' import NativeFeatureCard from '../NativeFeatureCard' import { useNativeSwapsAppCard } from '../NativeFeatureCard/useNativeSwapsAppCard' -import { AppRoutes } from '@/config/routes' import { useRouter } from 'next/router' type SafeAppListProps = { @@ -54,12 +53,15 @@ const SafeAppList = ({ [openPreviewDrawer, openedSafeAppIds], ) - const handleNativeAppClick = useCallback(() => { - router.push({ - pathname: AppRoutes.swap, - query: router.query, - }) - }, [router]) + const handleNativeAppClick = useCallback( + (route: string) => { + router.push({ + pathname: route, + query: router.query, + }) + }, + [router], + ) return ( <> @@ -84,9 +86,7 @@ const SafeAppList = ({ {/* Flat list filtered by search query */} {safeAppsList.map((safeApp) => { - const isNativeFeatureCard = !safeApp.url - if (isNativeFeatureCard && !isVisible) return null - return !isNativeFeatureCard ? ( + return !safeApp.isNativeFeature ? (
  • handleNativeAppClick()} + onClick={() => handleNativeAppClick(safeApp.url)} onDismiss={() => setIsVisible(false)} />
  • From 77dd4c5fb9bcf4476727db72d00a7e3bda113de8 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 31 May 2024 13:47:14 +0100 Subject: [PATCH 04/21] move hook to hooks folder --- src/components/safe-apps/NativeFeatureCard/index.stories.tsx | 3 ++- src/components/safe-apps/SafeAppList/index.tsx | 2 +- .../{NativeFeatureCard => hooks}/useNativeSwapsAppCard.ts | 0 src/pages/apps/index.tsx | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) rename src/components/safe-apps/{NativeFeatureCard => hooks}/useNativeSwapsAppCard.ts (100%) diff --git a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx index c196fe669a..f53a88e226 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx +++ b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react' import NativeFeatureCard from './index' import { Box } from '@mui/material' import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' +import { AppRoutes } from '@/config/routes' const meta = { component: NativeFeatureCard, @@ -28,7 +29,7 @@ export const Default: Story = { args: { details: { id: 100_000, - url: '', + url: AppRoutes.swap, name: 'Native swaps are here!', description: 'Experience seamless trading with better decoding and security in native swaps.', accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index a59e49e398..b999bf181a 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -11,7 +11,7 @@ import css from './styles.module.css' import { Skeleton } from '@mui/material' import { useOpenedSafeApps } from '@/hooks/safe-apps/useOpenedSafeApps' import NativeFeatureCard from '../NativeFeatureCard' -import { useNativeSwapsAppCard } from '../NativeFeatureCard/useNativeSwapsAppCard' +import { useNativeSwapsAppCard } from '../hooks/useNativeSwapsAppCard' import { useRouter } from 'next/router' type SafeAppListProps = { diff --git a/src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts b/src/components/safe-apps/hooks/useNativeSwapsAppCard.ts similarity index 100% rename from src/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard.ts rename to src/components/safe-apps/hooks/useNativeSwapsAppCard.ts diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 7a03428fb3..2dd9889b83 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -15,7 +15,7 @@ import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' -import { useNativeSwapsAppCard } from '@/components/safe-apps/NativeFeatureCard/useNativeSwapsAppCard' +import { useNativeSwapsAppCard } from '@/components/safe-apps/hooks/useNativeSwapsAppCard' const getSwapAppBanner = (): SafeAppData => { return { From aac00da4c427f7055df494f8814b3721dd2459d4 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 31 May 2024 13:56:14 +0100 Subject: [PATCH 05/21] adjust padding --- .../safe-apps/NativeFeatureCard/index.tsx | 13 ++++++------- .../NativeFeatureCard/styles.module.css | 16 ++++++++-------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/components/safe-apps/NativeFeatureCard/index.tsx b/src/components/safe-apps/NativeFeatureCard/index.tsx index 923cf9c4e5..2b3a4749c4 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.tsx +++ b/src/components/safe-apps/NativeFeatureCard/index.tsx @@ -14,23 +14,22 @@ type NativeFeatureCardProps = { const NativeFeatureCard = ({ details, onClick, onDismiss }: NativeFeatureCardProps) => { return ( - - {/* Safe App Header */} + +
    } /> - - + + {details.name} - + {details.description} diff --git a/src/components/safe-apps/NativeFeatureCard/styles.module.css b/src/components/safe-apps/NativeFeatureCard/styles.module.css index 7fea639fef..f7490cc955 100644 --- a/src/components/safe-apps/NativeFeatureCard/styles.module.css +++ b/src/components/safe-apps/NativeFeatureCard/styles.module.css @@ -1,23 +1,23 @@ -.safeAppContainer { +.container { transition: background-color 0.3s ease-in-out, border 0.3s ease-in-out; border: 1px solid transparent; height: 100%; } -.safeAppContainer:hover { +.container:hover { background-color: var(--color-background-light); border: 1px solid var(--color-secondary-light); } -.safeAppHeader { - padding: var(--space-2) var(--space-2) 0 var(--space-2) !important; +.header { + padding: var(--space-3) var(--space-2) var(--space-1) var(--space-2); } -.safeAppContent { +.content { padding: var(--space-2); } -.safeAppIconContainer { +.iconContainer { position: relative; background: var(--color-secondary-light); border-radius: 50%; @@ -25,7 +25,7 @@ padding: var(--space-1); } -.safeAppTitle { +.title { line-height: 175%; margin: 0; @@ -36,7 +36,7 @@ text-overflow: ellipsis; } -.safeAppDescription { +.description { /* Truncate Safe App Description (3 lines) */ display: -webkit-box; -webkit-line-clamp: 3; From ae9d480c9188b3f233420652e62af8f9e651ae51 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Fri, 31 May 2024 13:59:22 +0100 Subject: [PATCH 06/21] remove test code --- src/pages/apps/index.tsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 2dd9889b83..127d2a2f3c 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -13,26 +13,8 @@ import useSafeAppsFilters from '@/hooks/safe-apps/useSafeAppsFilters' import SafeAppsFilters from '@/components/safe-apps/SafeAppsFilters' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' -import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' -import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' import { useNativeSwapsAppCard } from '@/components/safe-apps/hooks/useNativeSwapsAppCard' -const getSwapAppBanner = (): SafeAppData => { - return { - id: 100_000, - url: '', - name: 'Native swaps are here!', - description: 'Experience seamless trading with better decoding and security in native swaps.', - accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, - tags: ['DeFi'], - features: [], - socialProfiles: [], - developerWebsite: '', - chainIds: ['11155111'], - iconUrl: '@/public/images/common/swap.svg', - } -} - const SafeApps: NextPage = () => { const router = useRouter() const { swapsCardDetails } = useNativeSwapsAppCard() From 11a347c4d922fd53ee31a99f2bffdfc93e7e054b Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Mon, 3 Jun 2024 16:25:51 +0200 Subject: [PATCH 07/21] fix: enable swap app card only on supported networks --- src/components/safe-apps/SafeAppList/index.tsx | 3 ++- .../safe-apps/hooks/useNativeSwapsAppCard.ts | 13 ++++++++++--- src/features/swap/config/constants.ts | 1 + src/features/swap/index.tsx | 3 ++- 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/features/swap/config/constants.ts diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index b999bf181a..c8b4872aa7 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -13,6 +13,7 @@ import { useOpenedSafeApps } from '@/hooks/safe-apps/useOpenedSafeApps' import NativeFeatureCard from '../NativeFeatureCard' import { useNativeSwapsAppCard } from '../hooks/useNativeSwapsAppCard' import { useRouter } from 'next/router' +import { NATIVE_SWAPS_APP_ID } from '@/features/swap/config/constants' type SafeAppListProps = { safeAppsList: SafeAppData[] @@ -86,7 +87,7 @@ const SafeAppList = ({ {/* Flat list filtered by search query */} {safeAppsList.map((safeApp) => { - return !safeApp.isNativeFeature ? ( + return safeApp.id !== NATIVE_SWAPS_APP_ID ? (
  • (SWAPS_APP_CARD_STORAGE_KEY) + let [isVisible = true, setIsVisible] = useLocalStorage(SWAPS_APP_CARD_STORAGE_KEY) + const isSwapFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS) + + if (isVisible && !isSwapFeatureEnabled) { + isVisible = false + } const swapsCard: SafeAppData = { - id: 100_000, + id: NATIVE_SWAPS_APP_ID, url: AppRoutes.swap, name: 'Native swaps are here!', description: 'Experience seamless trading with better decoding and security in native swaps.', @@ -22,7 +30,6 @@ export function useNativeSwapsAppCard() { developerWebsite: '', chainIds: [chainId], iconUrl: '/images/common/swap.svg', - isNativeFeature: true, } return { diff --git a/src/features/swap/config/constants.ts b/src/features/swap/config/constants.ts new file mode 100644 index 0000000000..5d5fb3c693 --- /dev/null +++ b/src/features/swap/config/constants.ts @@ -0,0 +1 @@ +export const NATIVE_SWAPS_APP_ID = 100_000 diff --git a/src/features/swap/index.tsx b/src/features/swap/index.tsx index 4bf4acc661..4eb696928d 100644 --- a/src/features/swap/index.tsx +++ b/src/features/swap/index.tsx @@ -27,6 +27,7 @@ import { isBlockedAddress } from '@/services/ofac' import { selectSwapParams, setSwapParams, type SwapState } from './store/swapParamsSlice' import { setSwapOrder } from '@/store/swapOrderSlice' import useChainId from '@/hooks/useChainId' +import { NATIVE_SWAPS_APP_ID } from '@/features/swap/config/constants' const BASE_URL = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '' @@ -67,7 +68,7 @@ const SwapWidget = ({ sell }: Params) => { const appData: SafeAppData = useMemo( () => ({ - id: 1, + id: NATIVE_SWAPS_APP_ID, url: 'https://app.safe.global', name: SWAP_TITLE, iconUrl: darkMode ? './images/common/safe-swap-dark.svg' : './images/common/safe-swap.svg', From b1f486e516244331205c375c8a6f3723baffc975 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Mon, 3 Jun 2024 17:11:28 +0200 Subject: [PATCH 08/21] fix: failing apps test --- src/pages/apps/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 127d2a2f3c..be7d28195b 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -22,12 +22,12 @@ const SafeApps: NextPage = () => { const allApps = [swapsCardDetails, ...remoteSafeApps] const { filteredApps, query, setQuery, setSelectedCategories, setOptimizedWithBatchFilter, selectedCategories } = useSafeAppsFilters(allApps) - const isFiltered = filteredApps.length !== remoteSafeApps.length + const isFiltered = filteredApps.length !== allApps.length const isSafeAppsEnabled = useHasFeature(FEATURES.SAFE_APPS) const nonPinnedApps = useMemo( - () => remoteSafeApps.filter((app) => !pinnedSafeAppIds.has(app.id)), - [remoteSafeApps, pinnedSafeAppIds], + () => allApps.filter((app) => !pinnedSafeAppIds.has(app.id)), + [allApps, pinnedSafeAppIds], ) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -60,7 +60,7 @@ const SafeApps: NextPage = () => { onChangeFilterCategory={setSelectedCategories} onChangeOptimizedWithBatch={setOptimizedWithBatchFilter} selectedCategories={selectedCategories} - safeAppsList={remoteSafeApps} + safeAppsList={allApps} /> {/* Pinned apps */} From 944d1ec443c640e114eac291d7b743f108507088 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Tue, 4 Jun 2024 15:15:01 +0100 Subject: [PATCH 09/21] fix lint error and add tags to swaps card --- src/components/safe-apps/hooks/useNativeSwapsAppCard.ts | 6 +++--- src/pages/apps/index.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/safe-apps/hooks/useNativeSwapsAppCard.ts b/src/components/safe-apps/hooks/useNativeSwapsAppCard.ts index ce51f38bea..e5c521d58e 100644 --- a/src/components/safe-apps/hooks/useNativeSwapsAppCard.ts +++ b/src/components/safe-apps/hooks/useNativeSwapsAppCard.ts @@ -2,7 +2,7 @@ import { AppRoutes } from '@/config/routes' import useChainId from '@/hooks/useChainId' import useLocalStorage from '@/services/local-storage/useLocalStorage' import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' -import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' +import { SafeAppAccessPolicyTypes, SafeAppFeatures } from '@safe-global/safe-gateway-typescript-sdk' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' import { NATIVE_SWAPS_APP_ID } from '@/features/swap/config/constants' @@ -24,8 +24,8 @@ export function useNativeSwapsAppCard() { name: 'Native swaps are here!', description: 'Experience seamless trading with better decoding and security in native swaps.', accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, - tags: ['DeFi'], - features: [], + tags: ['DeFi', 'DEX'], + features: [SafeAppFeatures.BATCHED_TRANSACTIONS], socialProfiles: [], developerWebsite: '', chainIds: [chainId], diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index be7d28195b..cf5c8edb51 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -19,7 +19,7 @@ const SafeApps: NextPage = () => { const router = useRouter() const { swapsCardDetails } = useNativeSwapsAppCard() const { remoteSafeApps, remoteSafeAppsLoading, pinnedSafeApps, pinnedSafeAppIds, togglePin } = useSafeApps() - const allApps = [swapsCardDetails, ...remoteSafeApps] + const allApps = useMemo(() => [swapsCardDetails, ...remoteSafeApps], [remoteSafeApps, swapsCardDetails]) const { filteredApps, query, setQuery, setSelectedCategories, setOptimizedWithBatchFilter, selectedCategories } = useSafeAppsFilters(allApps) const isFiltered = filteredApps.length !== allApps.length From af6047ad9d99e31f12a96fbbd446bcd7575fc6f5 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 08:29:45 +0100 Subject: [PATCH 10/21] render the swaps card first --- .../safe-apps/SafeAppList/index.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index c8b4872aa7..1cfed979f7 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -64,6 +64,8 @@ const SafeAppList = ({ [router], ) + const nativeFeatureCard = safeAppsList.find((item) => item.id === NATIVE_SWAPS_APP_ID) + return ( <> {/* Safe Apps List Header */} @@ -85,9 +87,18 @@ const SafeAppList = ({
  • ))} + {nativeFeatureCard && isVisible && ( + handleNativeAppClick(nativeFeatureCard.url)} + onDismiss={() => setIsVisible(false)} + /> + )} + {/* Flat list filtered by search query */} {safeAppsList.map((safeApp) => { - return safeApp.id !== NATIVE_SWAPS_APP_ID ? ( + if (safeApp.id === NATIVE_SWAPS_APP_ID) return null + return (
  • - ) : isVisible ? ( -
  • - handleNativeAppClick(safeApp.url)} - onDismiss={() => setIsVisible(false)} - /> -
  • - ) : null + ) })} From fdb26ea6ba29d94ac0e461d2834317b744b964de Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 13:39:56 +0100 Subject: [PATCH 11/21] separate native app promotion from safe app cards --- src/components/safe-apps/SafeAppList/index.tsx | 4 ++-- src/pages/apps/index.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index 1cfed979f7..3a12dab8ff 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -24,6 +24,7 @@ type SafeAppListProps = { removeCustomApp?: (safeApp: SafeAppData) => void title: string query?: string + nativeFeatureCard?: SafeAppData } const SafeAppList = ({ @@ -35,6 +36,7 @@ const SafeAppList = ({ removeCustomApp, title, query, + nativeFeatureCard, }: SafeAppListProps) => { const { isPreviewDrawerOpen, previewDrawerApp, openPreviewDrawer, closePreviewDrawer } = useSafeAppPreviewDrawer() const { openedSafeAppIds } = useOpenedSafeApps() @@ -64,8 +66,6 @@ const SafeAppList = ({ [router], ) - const nativeFeatureCard = safeAppsList.find((item) => item.id === NATIVE_SWAPS_APP_ID) - return ( <> {/* Safe Apps List Header */} diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index cf5c8edb51..f37885efb5 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -19,15 +19,14 @@ const SafeApps: NextPage = () => { const router = useRouter() const { swapsCardDetails } = useNativeSwapsAppCard() const { remoteSafeApps, remoteSafeAppsLoading, pinnedSafeApps, pinnedSafeAppIds, togglePin } = useSafeApps() - const allApps = useMemo(() => [swapsCardDetails, ...remoteSafeApps], [remoteSafeApps, swapsCardDetails]) const { filteredApps, query, setQuery, setSelectedCategories, setOptimizedWithBatchFilter, selectedCategories } = - useSafeAppsFilters(allApps) - const isFiltered = filteredApps.length !== allApps.length + useSafeAppsFilters(remoteSafeApps) + const isFiltered = filteredApps.length !== remoteSafeApps.length const isSafeAppsEnabled = useHasFeature(FEATURES.SAFE_APPS) const nonPinnedApps = useMemo( - () => allApps.filter((app) => !pinnedSafeAppIds.has(app.id)), - [allApps, pinnedSafeAppIds], + () => remoteSafeApps.filter((app) => !pinnedSafeAppIds.has(app.id)), + [pinnedSafeAppIds, remoteSafeApps], ) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -60,7 +59,7 @@ const SafeApps: NextPage = () => { onChangeFilterCategory={setSelectedCategories} onChangeOptimizedWithBatch={setOptimizedWithBatchFilter} selectedCategories={selectedCategories} - safeAppsList={allApps} + safeAppsList={remoteSafeApps} /> {/* Pinned apps */} @@ -76,6 +75,7 @@ const SafeApps: NextPage = () => { {/* All apps */} Date: Mon, 10 Jun 2024 14:26:43 +0100 Subject: [PATCH 12/21] remove swap card hook --- .../NativeFeatureCard/index.stories.tsx | 46 ------------------- .../NativeSwapsCard/index.stories.tsx | 28 +++++++++++ .../index.tsx | 32 +++++++------ .../styles.module.css | 0 .../safe-apps/SafeAppList/index.tsx | 37 +++++---------- .../safe-apps/hooks/useNativeSwapsAppCard.ts | 40 ---------------- src/pages/apps/index.tsx | 4 +- src/services/analytics/events/swaps.ts | 1 + 8 files changed, 60 insertions(+), 128 deletions(-) delete mode 100644 src/components/safe-apps/NativeFeatureCard/index.stories.tsx create mode 100644 src/components/safe-apps/NativeSwapsCard/index.stories.tsx rename src/components/safe-apps/{NativeFeatureCard => NativeSwapsCard}/index.tsx (51%) rename src/components/safe-apps/{NativeFeatureCard => NativeSwapsCard}/styles.module.css (100%) delete mode 100644 src/components/safe-apps/hooks/useNativeSwapsAppCard.ts diff --git a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx b/src/components/safe-apps/NativeFeatureCard/index.stories.tsx deleted file mode 100644 index f53a88e226..0000000000 --- a/src/components/safe-apps/NativeFeatureCard/index.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import NativeFeatureCard from './index' -import { Box } from '@mui/material' -import { SafeAppAccessPolicyTypes } from '@safe-global/safe-gateway-typescript-sdk' -import { AppRoutes } from '@/config/routes' - -const meta = { - component: NativeFeatureCard, - parameters: { - componentSubtitle: 'Renders an order id with an external link and a copy button', - }, - - decorators: [ - (Story) => { - return ( - - - - ) - }, - ], - tags: ['autodocs'], -} satisfies Meta - -export default meta -type Story = StoryObj - -export const Default: Story = { - args: { - details: { - id: 100_000, - url: AppRoutes.swap, - name: 'Native swaps are here!', - description: 'Experience seamless trading with better decoding and security in native swaps.', - accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, - tags: ['DeFi'], - features: [], - socialProfiles: [], - developerWebsite: '', - chainIds: ['11155111'], - iconUrl: '/images/common/swap.svg', - }, - onClick: () => {}, - onDismiss: () => {}, - }, -} diff --git a/src/components/safe-apps/NativeSwapsCard/index.stories.tsx b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx new file mode 100644 index 0000000000..3d0472ff9d --- /dev/null +++ b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/react' +import NativeSwapsCard from './index' +import { Box } from '@mui/material' + +const meta = { + component: NativeSwapsCard, + parameters: { + componentSubtitle: 'Renders an order id with an external link and a copy button', + }, + + decorators: [ + (Story) => { + return ( + + + + ) + }, + ], + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { onDismiss: () => {} }, +} diff --git a/src/components/safe-apps/NativeFeatureCard/index.tsx b/src/components/safe-apps/NativeSwapsCard/index.tsx similarity index 51% rename from src/components/safe-apps/NativeFeatureCard/index.tsx rename to src/components/safe-apps/NativeSwapsCard/index.tsx index 2b3a4749c4..9472bb765f 100644 --- a/src/components/safe-apps/NativeFeatureCard/index.tsx +++ b/src/components/safe-apps/NativeSwapsCard/index.tsx @@ -2,41 +2,45 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import Typography from '@mui/material/Typography' import { Button, Paper, Stack } from '@mui/material' -import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' import SafeAppIconCard from '../SafeAppIconCard' import css from './styles.module.css' +import { SWAP_EVENTS, SWAP_LABELS } from '@/services/analytics/events/swaps' +import Track from '@/components/common/Track' +import Link from 'next/link' +import { AppRoutes } from '@/config/routes' +import { useRouter } from 'next/router' -type NativeFeatureCardProps = { - details: SafeAppData - onClick: () => void - onDismiss: () => void -} +const NativeSwapsCard = ({ onDismiss }: { onDismiss: () => void }) => { + const router = useRouter() -const NativeFeatureCard = ({ details, onClick, onDismiss }: NativeFeatureCardProps) => { return ( - + } /> - {details.name} + Native swaps are here! - {details.description} + Experience seamless trading with better decoding and security in native swaps. - + + + + + @@ -46,4 +50,4 @@ const NativeFeatureCard = ({ details, onClick, onDismiss }: NativeFeatureCardPro ) } -export default NativeFeatureCard +export default NativeSwapsCard diff --git a/src/components/safe-apps/NativeFeatureCard/styles.module.css b/src/components/safe-apps/NativeSwapsCard/styles.module.css similarity index 100% rename from src/components/safe-apps/NativeFeatureCard/styles.module.css rename to src/components/safe-apps/NativeSwapsCard/styles.module.css diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index 3a12dab8ff..305eb155e6 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -10,10 +10,10 @@ import useSafeAppPreviewDrawer from '@/hooks/safe-apps/useSafeAppPreviewDrawer' import css from './styles.module.css' import { Skeleton } from '@mui/material' import { useOpenedSafeApps } from '@/hooks/safe-apps/useOpenedSafeApps' -import NativeFeatureCard from '../NativeFeatureCard' -import { useNativeSwapsAppCard } from '../hooks/useNativeSwapsAppCard' -import { useRouter } from 'next/router' -import { NATIVE_SWAPS_APP_ID } from '@/features/swap/config/constants' +import NativeSwapsCard from '@/components/safe-apps/NativeSwapsCard' +import useLocalStorage from '@/services/local-storage/useLocalStorage' +import { useHasFeature } from '@/hooks/useChains' +import { FEATURES } from '@/utils/chains' type SafeAppListProps = { safeAppsList: SafeAppData[] @@ -24,9 +24,11 @@ type SafeAppListProps = { removeCustomApp?: (safeApp: SafeAppData) => void title: string query?: string - nativeFeatureCard?: SafeAppData + isFiltered?: boolean } +const SWAPS_APP_CARD_STORAGE_KEY = 'showSwapsAppCard' + const SafeAppList = ({ safeAppsList, safeAppsListLoading, @@ -36,12 +38,12 @@ const SafeAppList = ({ removeCustomApp, title, query, - nativeFeatureCard, + isFiltered = false, }: SafeAppListProps) => { const { isPreviewDrawerOpen, previewDrawerApp, openPreviewDrawer, closePreviewDrawer } = useSafeAppPreviewDrawer() const { openedSafeAppIds } = useOpenedSafeApps() - const { isVisible, setIsVisible } = useNativeSwapsAppCard() - const router = useRouter() + let [isSwapsCardVisible = true, setIsSwapsCardVisible] = useLocalStorage(SWAPS_APP_CARD_STORAGE_KEY) + const isSwapFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS) const showZeroResultsPlaceholder = query && safeAppsList.length === 0 @@ -56,16 +58,6 @@ const SafeAppList = ({ [openPreviewDrawer, openedSafeAppIds], ) - const handleNativeAppClick = useCallback( - (route: string) => { - router.push({ - pathname: route, - query: router.query, - }) - }, - [router], - ) - return ( <> {/* Safe Apps List Header */} @@ -87,17 +79,12 @@ const SafeAppList = ({ ))} - {nativeFeatureCard && isVisible && ( - handleNativeAppClick(nativeFeatureCard.url)} - onDismiss={() => setIsVisible(false)} - /> + {!isFiltered && isSwapsCardVisible && isSwapFeatureEnabled && ( + setIsSwapsCardVisible(false)} /> )} {/* Flat list filtered by search query */} {safeAppsList.map((safeApp) => { - if (safeApp.id === NATIVE_SWAPS_APP_ID) return null return (
  • (SWAPS_APP_CARD_STORAGE_KEY) - const isSwapFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS) - - if (isVisible && !isSwapFeatureEnabled) { - isVisible = false - } - - const swapsCard: SafeAppData = { - id: NATIVE_SWAPS_APP_ID, - url: AppRoutes.swap, - name: 'Native swaps are here!', - description: 'Experience seamless trading with better decoding and security in native swaps.', - accessControl: { type: SafeAppAccessPolicyTypes.NoRestrictions }, - tags: ['DeFi', 'DEX'], - features: [SafeAppFeatures.BATCHED_TRANSACTIONS], - socialProfiles: [], - developerWebsite: '', - chainIds: [chainId], - iconUrl: '/images/common/swap.svg', - } - - return { - swapsCardDetails: swapsCard, - isVisible, - setIsVisible, - } -} diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index f37885efb5..174ecebfd9 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -13,11 +13,9 @@ import useSafeAppsFilters from '@/hooks/safe-apps/useSafeAppsFilters' import SafeAppsFilters from '@/components/safe-apps/SafeAppsFilters' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' -import { useNativeSwapsAppCard } from '@/components/safe-apps/hooks/useNativeSwapsAppCard' const SafeApps: NextPage = () => { const router = useRouter() - const { swapsCardDetails } = useNativeSwapsAppCard() const { remoteSafeApps, remoteSafeAppsLoading, pinnedSafeApps, pinnedSafeAppIds, togglePin } = useSafeApps() const { filteredApps, query, setQuery, setSelectedCategories, setOptimizedWithBatchFilter, selectedCategories } = useSafeAppsFilters(remoteSafeApps) @@ -75,7 +73,7 @@ const SafeApps: NextPage = () => { {/* All apps */} Date: Mon, 10 Jun 2024 14:31:28 +0100 Subject: [PATCH 13/21] revert changes to safe apps card map --- .../safe-apps/SafeAppList/index.tsx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index 305eb155e6..4d4a4c2367 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -84,20 +84,18 @@ const SafeAppList = ({ )} {/* Flat list filtered by search query */} - {safeAppsList.map((safeApp) => { - return ( -
  • - -
  • - ) - })} + {safeAppsList.map((safeApp) => ( +
  • + +
  • + ))} {/* Zero results placeholder */} From 5e5a4530b173686b1e674308dcdfa5b1640ce614 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 14:40:11 +0100 Subject: [PATCH 14/21] Update src/features/swap/index.tsx Co-authored-by: katspaugh <381895+katspaugh@users.noreply.github.com> --- src/features/swap/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/swap/index.tsx b/src/features/swap/index.tsx index 034ba733d6..3d7123574f 100644 --- a/src/features/swap/index.tsx +++ b/src/features/swap/index.tsx @@ -95,7 +95,7 @@ const SwapWidget = ({ sell }: Params) => { const appData: SafeAppData = useMemo( () => ({ - id: NATIVE_SWAPS_APP_ID, + id: 1, url: 'https://app.safe.global', name: SWAP_TITLE, iconUrl: darkMode ? './images/common/safe-swap-dark.svg' : './images/common/safe-swap.svg', From 0badc49c07df3e1ce417ff8a71bb335073bf0041 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 14:40:24 +0100 Subject: [PATCH 15/21] Update src/pages/apps/index.tsx Co-authored-by: katspaugh <381895+katspaugh@users.noreply.github.com> --- src/pages/apps/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 174ecebfd9..ea2f82b7a1 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -24,7 +24,7 @@ const SafeApps: NextPage = () => { const nonPinnedApps = useMemo( () => remoteSafeApps.filter((app) => !pinnedSafeAppIds.has(app.id)), - [pinnedSafeAppIds, remoteSafeApps], + [remoteSafeApps, pinnedSafeAppIds], ) // eslint-disable-next-line react-hooks/exhaustive-deps From 2d71a138de5db5bf082620c85767f14c6a55ec9a Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 15:38:25 +0100 Subject: [PATCH 16/21] use localstorage flag within card component and fix storybook --- .../safe-apps/NativeSwapsCard/index.stories.tsx | 14 +++++++++----- src/components/safe-apps/NativeSwapsCard/index.tsx | 12 ++++++++++-- src/components/safe-apps/SafeAppList/index.tsx | 11 +---------- src/features/swap/index.tsx | 1 - src/hooks/useChains.ts | 4 +++- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/components/safe-apps/NativeSwapsCard/index.stories.tsx b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx index 3d0472ff9d..f38ec3535e 100644 --- a/src/components/safe-apps/NativeSwapsCard/index.stories.tsx +++ b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx @@ -1,19 +1,22 @@ import type { Meta, StoryObj } from '@storybook/react' import NativeSwapsCard from './index' import { Box } from '@mui/material' +import { StoreDecorator } from '@/stories/storeDecorator' const meta = { component: NativeSwapsCard, parameters: { - componentSubtitle: 'Renders an order id with an external link and a copy button', + componentSubtitle: 'Renders a promo card for native swaps', }, decorators: [ (Story) => { return ( - - - + + + + + ) }, ], @@ -24,5 +27,6 @@ export default meta type Story = StoryObj export const Default: Story = { - args: { onDismiss: () => {} }, + args: {}, + // args: { onDismiss: () => {} }, } diff --git a/src/components/safe-apps/NativeSwapsCard/index.tsx b/src/components/safe-apps/NativeSwapsCard/index.tsx index 9472bb765f..25f029ae0d 100644 --- a/src/components/safe-apps/NativeSwapsCard/index.tsx +++ b/src/components/safe-apps/NativeSwapsCard/index.tsx @@ -9,9 +9,17 @@ import Track from '@/components/common/Track' import Link from 'next/link' import { AppRoutes } from '@/config/routes' import { useRouter } from 'next/router' +import useLocalStorage from '@/services/local-storage/useLocalStorage' +import { useHasFeature } from '@/hooks/useChains' +import { FEATURES } from '@/utils/chains' -const NativeSwapsCard = ({ onDismiss }: { onDismiss: () => void }) => { +const SWAPS_APP_CARD_STORAGE_KEY = 'showSwapsAppCard' + +const NativeSwapsCard = () => { const router = useRouter() + const isSwapFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS) + const [isSwapsCardVisible = true, setIsSwapsCardVisible] = useLocalStorage(SWAPS_APP_CARD_STORAGE_KEY) + if (!isSwapFeatureEnabled || !isSwapsCardVisible) return null return ( @@ -41,7 +49,7 @@ const NativeSwapsCard = ({ onDismiss }: { onDismiss: () => void }) => { -
    diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index 4d4a4c2367..1f57227405 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -11,9 +11,6 @@ import css from './styles.module.css' import { Skeleton } from '@mui/material' import { useOpenedSafeApps } from '@/hooks/safe-apps/useOpenedSafeApps' import NativeSwapsCard from '@/components/safe-apps/NativeSwapsCard' -import useLocalStorage from '@/services/local-storage/useLocalStorage' -import { useHasFeature } from '@/hooks/useChains' -import { FEATURES } from '@/utils/chains' type SafeAppListProps = { safeAppsList: SafeAppData[] @@ -27,8 +24,6 @@ type SafeAppListProps = { isFiltered?: boolean } -const SWAPS_APP_CARD_STORAGE_KEY = 'showSwapsAppCard' - const SafeAppList = ({ safeAppsList, safeAppsListLoading, @@ -42,8 +37,6 @@ const SafeAppList = ({ }: SafeAppListProps) => { const { isPreviewDrawerOpen, previewDrawerApp, openPreviewDrawer, closePreviewDrawer } = useSafeAppPreviewDrawer() const { openedSafeAppIds } = useOpenedSafeApps() - let [isSwapsCardVisible = true, setIsSwapsCardVisible] = useLocalStorage(SWAPS_APP_CARD_STORAGE_KEY) - const isSwapFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS) const showZeroResultsPlaceholder = query && safeAppsList.length === 0 @@ -79,9 +72,7 @@ const SafeAppList = ({ ))} - {!isFiltered && isSwapsCardVisible && isSwapFeatureEnabled && ( - setIsSwapsCardVisible(false)} /> - )} + {!isFiltered && } {/* Flat list filtered by search query */} {safeAppsList.map((safeApp) => ( diff --git a/src/features/swap/index.tsx b/src/features/swap/index.tsx index 3d7123574f..7734b0aafe 100644 --- a/src/features/swap/index.tsx +++ b/src/features/swap/index.tsx @@ -27,7 +27,6 @@ import { isBlockedAddress } from '@/services/ofac' import { selectSwapParams, setSwapParams, type SwapState } from './store/swapParamsSlice' import { setSwapOrder } from '@/store/swapOrderSlice' import useChainId from '@/hooks/useChainId' -import { NATIVE_SWAPS_APP_ID } from '@/features/swap/config/constants' import { type BaseTransaction } from '@safe-global/safe-apps-sdk' import { APPROVAL_SIGNATURE_HASH } from '@/components/tx/ApprovalEditor/utils/approvals' import { id } from 'ethers' diff --git a/src/hooks/useChains.ts b/src/hooks/useChains.ts index 669c99865a..b455f01804 100644 --- a/src/hooks/useChains.ts +++ b/src/hooks/useChains.ts @@ -27,7 +27,9 @@ export const useChain = (chainId: string): ChainInfo | undefined => { export const useCurrentChain = (): ChainInfo | undefined => { const chainId = useChainId() - const chainInfo = useAppSelector((state) => selectChainById(state, chainId), isEqual) + const chainInfo = useAppSelector((state) => { + return selectChainById(state, chainId) + }, isEqual) return chainInfo } From 69b642705a68620f0d7c11014830d60f472272c2 Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 15:40:01 +0100 Subject: [PATCH 17/21] remove commented code --- src/components/safe-apps/NativeSwapsCard/index.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/safe-apps/NativeSwapsCard/index.stories.tsx b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx index f38ec3535e..9781c14934 100644 --- a/src/components/safe-apps/NativeSwapsCard/index.stories.tsx +++ b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx @@ -28,5 +28,4 @@ type Story = StoryObj export const Default: Story = { args: {}, - // args: { onDismiss: () => {} }, } From 85d543ef8a6deabb95941ad5c610556bf236f00e Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 15:41:09 +0100 Subject: [PATCH 18/21] revert changes to useCurrentChain --- src/hooks/useChains.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hooks/useChains.ts b/src/hooks/useChains.ts index b455f01804..669c99865a 100644 --- a/src/hooks/useChains.ts +++ b/src/hooks/useChains.ts @@ -27,9 +27,7 @@ export const useChain = (chainId: string): ChainInfo | undefined => { export const useCurrentChain = (): ChainInfo | undefined => { const chainId = useChainId() - const chainInfo = useAppSelector((state) => { - return selectChainById(state, chainId) - }, isEqual) + const chainInfo = useAppSelector((state) => selectChainById(state, chainId), isEqual) return chainInfo } From 19aeebdbac0489a4c0388d9143264d751aa0456b Mon Sep 17 00:00:00 2001 From: James Mealy Date: Mon, 10 Jun 2024 15:47:46 +0100 Subject: [PATCH 19/21] remove unused app id --- src/features/swap/config/constants.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/features/swap/config/constants.ts diff --git a/src/features/swap/config/constants.ts b/src/features/swap/config/constants.ts deleted file mode 100644 index 5d5fb3c693..0000000000 --- a/src/features/swap/config/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const NATIVE_SWAPS_APP_ID = 100_000 From 8da90760eb77ea33ea88df26a02f2e551dd581bb Mon Sep 17 00:00:00 2001 From: katspaugh Date: Tue, 11 Jun 2024 08:28:18 +0200 Subject: [PATCH 20/21] Move SAP banner down --- .../dashboard/ActivityRewardsSection/index.tsx | 4 ++-- src/components/dashboard/index.tsx | 12 +++++++----- .../safe-apps/NativeSwapsCard/styles.module.css | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/dashboard/ActivityRewardsSection/index.tsx b/src/components/dashboard/ActivityRewardsSection/index.tsx index e64132e8db..ece466568c 100644 --- a/src/components/dashboard/ActivityRewardsSection/index.tsx +++ b/src/components/dashboard/ActivityRewardsSection/index.tsx @@ -51,7 +51,7 @@ const ActivityRewardsSection = () => { } return ( - + <> { - + ) } diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index a03adb52d9..827272af18 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -44,13 +44,11 @@ const Dashboard = (): ReactElement => { - - - - {safe.deployed && ( <> - + + + @@ -60,6 +58,10 @@ const Dashboard = (): ReactElement => { + + + + {showSafeApps && ( diff --git a/src/components/safe-apps/NativeSwapsCard/styles.module.css b/src/components/safe-apps/NativeSwapsCard/styles.module.css index f7490cc955..68b767d1a5 100644 --- a/src/components/safe-apps/NativeSwapsCard/styles.module.css +++ b/src/components/safe-apps/NativeSwapsCard/styles.module.css @@ -46,4 +46,5 @@ .buttons { padding-top: var(--space-2); + white-space: nowrap; } From d8f3a18a5fd0bd9badb2abcdf5410d2b4e72131a Mon Sep 17 00:00:00 2001 From: katspaugh Date: Tue, 11 Jun 2024 09:43:18 +0200 Subject: [PATCH 21/21] Adjust banners --- .../ActivityRewardsSection/styles.module.css | 2 +- src/components/dashboard/index.tsx | 14 ++++--- .../swap/components/SwapWidget/index.tsx | 22 ++++++----- .../components/SwapWidget/styles.module.css | 37 ++++++++++++------- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/components/dashboard/ActivityRewardsSection/styles.module.css b/src/components/dashboard/ActivityRewardsSection/styles.module.css index 7b62eaec1e..92c6ac1639 100644 --- a/src/components/dashboard/ActivityRewardsSection/styles.module.css +++ b/src/components/dashboard/ActivityRewardsSection/styles.module.css @@ -67,7 +67,7 @@ .links { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; margin-top: var(--space-3); text-wrap: nowrap; diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index 827272af18..50e27e0f27 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -18,9 +18,9 @@ import ActivityRewardsSection from '@/components/dashboard/ActivityRewardsSectio import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' import css from './styles.module.css' +import SwapWidget from '@/features/swap/components/SwapWidget' const RecoveryHeader = dynamic(() => import('@/features/recovery/components/RecoveryHeader')) -const SwapWidget = dynamic(() => import('@/features/swap/components/SwapWidget')) const Dashboard = (): ReactElement => { const router = useRouter() @@ -46,10 +46,16 @@ const Dashboard = (): ReactElement => { {safe.deployed && ( <> - + + + + + + + @@ -58,10 +64,6 @@ const Dashboard = (): ReactElement => { - - - - {showSafeApps && ( diff --git a/src/features/swap/components/SwapWidget/index.tsx b/src/features/swap/components/SwapWidget/index.tsx index e306a38854..7e7d8eb206 100644 --- a/src/features/swap/components/SwapWidget/index.tsx +++ b/src/features/swap/components/SwapWidget/index.tsx @@ -14,10 +14,10 @@ import Track from '@/components/common/Track' import { useHasFeature } from '@/hooks/useChains' import { FEATURES } from '@/utils/chains' -const SWAP_PROMO_WIDGET_IS_HIDDEN = 'SWAP_PROMO_WIDGET_IS_HIDDEN' +const SWAPS_PROMO_WIDGET_IS_HIDDEN = 'swapsPromoWidgetIsHidden' function SwapWidget(): ReactElement | null { - const [isHidden = false, setIsHidden] = useLocalStorage(SWAP_PROMO_WIDGET_IS_HIDDEN) + const [isHidden = false, setIsHidden] = useLocalStorage(SWAPS_PROMO_WIDGET_IS_HIDDEN) const isSwapFeatureEnabled = useHasFeature(FEATURES.NATIVE_SWAPS) const onClick = useCallback(() => { @@ -36,7 +36,7 @@ function SwapWidget(): ReactElement | null { - + Introducing native swaps @@ -44,11 +44,14 @@ function SwapWidget(): ReactElement | null { - + Experience our native swaps, powered by CoW Protocol! Trade seamlessly and efficiently with decoded transactions that are easy to understand. + + + - + - - - Swap + + + Swap diff --git a/src/features/swap/components/SwapWidget/styles.module.css b/src/features/swap/components/SwapWidget/styles.module.css index c6fb8c2608..a276f52fba 100644 --- a/src/features/swap/components/SwapWidget/styles.module.css +++ b/src/features/swap/components/SwapWidget/styles.module.css @@ -10,6 +10,7 @@ .grid { display: flex; + flex-wrap: nowrap; height: inherit; gap: var(--space-3); } @@ -27,31 +28,39 @@ margin-right: var(--space-1); } -.imageContainer { - display: flex; - align-items: flex-end; -} - .buttonContainer { display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-start; gap: var(--space-2); + white-space: nowrap; + position: relative; + z-index: 2; } -@media (max-width: 599.95px) { - .imageContainer { - width: 100%; - justify-content: flex-end; - } +.imageContainer { + position: relative; + z-index: 1; +} - .buttonContainer { - gap: 0; - justify-content: space-between; - } +.imageContainer img { + display: block; + position: absolute; + z-index: 0; + right: 0; + bottom: 0; + max-width: 500px; +} +@media (max-width: 599.95px) { .wrapper { padding: var(--space-3); } } + +@media (max-width: 970px) { + .imageContainer { + display: none; + } +}