From 9720343fa967c6100a25fab3a1de15a3a5bae15a Mon Sep 17 00:00:00 2001 From: James Mealy Date: Tue, 11 Jun 2024 09:13:08 +0100 Subject: [PATCH] Feat: add swaps card to safe apps list (#3786) --- .../ActivityRewardsSection/index.tsx | 4 +- .../ActivityRewardsSection/styles.module.css | 2 +- src/components/dashboard/index.tsx | 16 +++-- .../NativeSwapsCard/index.stories.tsx | 31 ++++++++++ .../safe-apps/NativeSwapsCard/index.tsx | 61 +++++++++++++++++++ .../NativeSwapsCard/styles.module.css | 50 +++++++++++++++ .../safe-apps/SafeAppList/index.tsx | 5 ++ .../swap/components/SwapWidget/index.tsx | 22 ++++--- .../components/SwapWidget/styles.module.css | 37 ++++++----- src/pages/apps/index.tsx | 1 + src/services/analytics/events/swaps.ts | 1 + 11 files changed, 197 insertions(+), 33 deletions(-) create mode 100644 src/components/safe-apps/NativeSwapsCard/index.stories.tsx create mode 100644 src/components/safe-apps/NativeSwapsCard/index.tsx create mode 100644 src/components/safe-apps/NativeSwapsCard/styles.module.css 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/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 a03adb52d9..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() @@ -44,13 +44,17 @@ const Dashboard = (): ReactElement => { - - - - {safe.deployed && ( <> - + + + + + + + + + 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..9781c14934 --- /dev/null +++ b/src/components/safe-apps/NativeSwapsCard/index.stories.tsx @@ -0,0 +1,31 @@ +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 a promo card for native swaps', + }, + + decorators: [ + (Story) => { + return ( + + + + + + ) + }, + ], + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, +} diff --git a/src/components/safe-apps/NativeSwapsCard/index.tsx b/src/components/safe-apps/NativeSwapsCard/index.tsx new file mode 100644 index 0000000000..25f029ae0d --- /dev/null +++ b/src/components/safe-apps/NativeSwapsCard/index.tsx @@ -0,0 +1,61 @@ +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 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' +import useLocalStorage from '@/services/local-storage/useLocalStorage' +import { useHasFeature } from '@/hooks/useChains' +import { FEATURES } from '@/utils/chains' + +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 ( + + + + + } + /> + + + + Native swaps are here! + + + + Experience seamless trading with better decoding and security in native swaps. + + + + + + + + + + + + + ) +} + +export default NativeSwapsCard diff --git a/src/components/safe-apps/NativeSwapsCard/styles.module.css b/src/components/safe-apps/NativeSwapsCard/styles.module.css new file mode 100644 index 0000000000..68b767d1a5 --- /dev/null +++ b/src/components/safe-apps/NativeSwapsCard/styles.module.css @@ -0,0 +1,50 @@ +.container { + transition: background-color 0.3s ease-in-out, border 0.3s ease-in-out; + border: 1px solid transparent; + height: 100%; +} + +.container:hover { + background-color: var(--color-background-light); + border: 1px solid var(--color-secondary-light); +} + +.header { + padding: var(--space-3) var(--space-2) var(--space-1) var(--space-2); +} + +.content { + padding: var(--space-2); +} + +.iconContainer { + position: relative; + background: var(--color-secondary-light); + border-radius: 50%; + display: flex; + padding: var(--space-1); +} + +.title { + line-height: 175%; + margin: 0; + + flex-grow: 1; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.description { + /* 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); + white-space: nowrap; +} diff --git a/src/components/safe-apps/SafeAppList/index.tsx b/src/components/safe-apps/SafeAppList/index.tsx index c9a7daf84e..1f57227405 100644 --- a/src/components/safe-apps/SafeAppList/index.tsx +++ b/src/components/safe-apps/SafeAppList/index.tsx @@ -10,6 +10,7 @@ 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 NativeSwapsCard from '@/components/safe-apps/NativeSwapsCard' type SafeAppListProps = { safeAppsList: SafeAppData[] @@ -20,6 +21,7 @@ type SafeAppListProps = { removeCustomApp?: (safeApp: SafeAppData) => void title: string query?: string + isFiltered?: boolean } const SafeAppList = ({ @@ -31,6 +33,7 @@ const SafeAppList = ({ removeCustomApp, title, query, + isFiltered = false, }: SafeAppListProps) => { const { isPreviewDrawerOpen, previewDrawerApp, openPreviewDrawer, closePreviewDrawer } = useSafeAppPreviewDrawer() const { openedSafeAppIds } = useOpenedSafeApps() @@ -69,6 +72,8 @@ const SafeAppList = ({ ))} + {!isFiltered && } + {/* Flat list filtered by search query */} {safeAppsList.map((safeApp) => (
  • 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; + } +} diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 4839e57974..ea2f82b7a1 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -73,6 +73,7 @@ const SafeApps: NextPage = () => { {/* All apps */}