Skip to content

Commit

Permalink
feat(wallet): FeatureCards component (#484)
Browse files Browse the repository at this point in the history
* feat: FeatureCards component

* feat: ecosystem projects card

* feat: rest of Featured cards

* feat: cta to wallet app

* Update src/components/Wallet/FeatureCards/index.tsx

Co-authored-by: Aaron Cook <aaron@safe.global>

* Apply suggestions from code review

Co-authored-by: Aaron Cook <aaron@safe.global>

---------

Co-authored-by: Aaron Cook <aaron@safe.global>
  • Loading branch information
DiogoSoaress and iamacook authored Oct 31, 2024
1 parent 58bb207 commit ba095b6
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 1 deletion.
Binary file added public/images/Wallet/Features/earn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/Wallet/Features/optimise.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/Wallet/Features/safe-apps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/Wallet/Features/trade.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
110 changes: 110 additions & 0 deletions src/components/Wallet/FeatureCards/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Button, Container, Grid, Typography } from '@mui/material'
import LinkButton from '@/components/common/LinkButton'
import { WALLET_LINK } from '@/config/constants'
import type { BaseBlock } from '@/components/Home/types'
import layoutCss from '@/components/common/styles.module.css'
import css from './styles.module.css'

const CardContent = ({ caption, title, text, link }: Partial<BaseBlock>) => (
<div className={css.cardContent}>
<Typography variant="caption" className={css.caption}>
{caption}
</Typography>

<Typography variant="h4" className={css.title}>
{title}
</Typography>

<Typography color="primary.light">{text}</Typography>

{link ? (
<LinkButton underline={false} href={link.href}>
{link.title}
</LinkButton>
) : undefined}
</div>
)

type FeatureCardsProps = Omit<BaseBlock, 'items'> & {
items: Array<Partial<BaseBlock> & { isNew: boolean; fullWidth: boolean }>
}

const FeatureCards = ({ title, text, items = [] }: FeatureCardsProps) => {
// extract the last item from the array
const lastItem = items[items.length - 1]
const restItems = items.slice(0, -1)

return (
<Container className={layoutCss.containerMedium}>
<Grid container justifyContent="space-between" columnSpacing="30px" rowGap="30px">
<Grid item xs={12} md={7}>
<Typography variant="h2">{title}</Typography>
</Grid>

<Grid item xs={12} md={5} alignContent="flex-end">
<Typography variant="h5">{text}</Typography>
</Grid>
</Grid>

<Grid container mt={{ xs: '50px', md: '100px' }} columnSpacing="30px" rowGap="30px">
{restItems.map((item, index) => {
if (item.fullWidth) {
return (
<Grid container item key={index} xs={12} width="100%">
<div className={`${css.card} ${css.fullWidth}`}>
{item.isNew && <div className={css.newBadge}>New</div>}

<Grid item md={1} />
<Grid item xs={12} md={4} display="contents">
<img className={css.image} src={item.image?.src} alt={item.image?.alt} />
</Grid>
<Grid item md={1} />

<Grid item xs={12} md={6} className={css.centerContent}>
<CardContent {...item} />
</Grid>
</div>
</Grid>
)
}

return (
<Grid item key={index} xs={12} md={6}>
<div className={`${css.card} ${css.stacked}`}>
{item.isNew && <div className={css.newBadge}>New</div>}

<img className={css.image} src={item.image?.src} alt={item.image?.alt} />

<CardContent {...item} />
</div>
</Grid>
)
})}

{/* last item is part of the component */}
{lastItem && (
<Grid item xs={12}>
<div className={`${css.card} ${css.lastCard}`}>
<img className={css.image} src={lastItem.image?.src} alt={lastItem.image?.alt} />

<div className={css.centerContent}>
<CardContent {...lastItem} />
</div>
</div>
</Grid>
)}
</Grid>

<div className={css.cta}>
<Typography variant="h5">
Read to have a <b>{`Safe{Wallet}`}</b>?
</Typography>
<Button variant="contained" size="large" href={WALLET_LINK}>
Get started
</Button>
</div>
</Container>
)
}

export default FeatureCards
101 changes: 101 additions & 0 deletions src/components/Wallet/FeatureCards/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
.card {
background: var(--mui-palette-border-background);
box-shadow: inset 0 0 0 1px var(--mui-palette-border-light);
border-radius: 16px;
padding: 40px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 40px;
position: relative;
transition: var(--transition-duration);
}

.newBadge {
font-size: 12px;
line-height: 14px;
position: absolute;
top: 40px;
right: 40px;
background-color: #b0ffc9;
color: var(--mui-palette-text-dark);
border-radius: 4px;
width: 45px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}

.lastCard {
padding: 0;
}

.lastCard .cardContent {
padding: 40px;
max-width: 516px;
}

.image {
height: 100%;
}

.cardContent {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
}

.caption {
background-image: linear-gradient(260.13deg, #12ff80 1.24%, #5fddff 102.14%);
background-clip: text;
color: transparent;
}

.stacked {
display: flex;
flex-direction: column;
gap: 16px;
}

.stacked .image {
height: 170px;
margin: 0 auto;
}

.cta {
margin-top: 100px;
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}

@media (min-width: 900px) {
.card {
height: 400px;
flex-direction: row;
}

.stacked {
display: flex;
flex-direction: column;
gap: 16px;
}

.fullWidth {
width: 100%;
}

.fullWidth .title {
font-size: 48px;
line-height: 56px;
}

.centerContent {
height: 100%;
display: flex;
align-items: center;
}
}
2 changes: 1 addition & 1 deletion src/components/Wallet/VerticalSlide/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.title {
text-align: center;
max-width: 650px;
margin-bottom: 48px;
margin: 0 auto 48px;
}

.image {
Expand Down
51 changes: 51 additions & 0 deletions src/content/wallet.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,57 @@
}
]
},
{
"component": "Wallet/FeatureCards",
"title": "<b>More features beyond</b> multisigs",
"text": "Safe is more than just secure asset storage. It's your entry point to onchain activities.",
"items": [
{
"isNew": true,
"fullWidth": true,
"caption": "Trade seamlessly",
"title": "Swap like a pro",
"text": "with MEV protection and security, ensuring your intent-based transactions will not fail.",
"image": {
"src": "/images/Wallet/Features/trade.png",
"alt": "Token swap modal"
}
},
{
"caption": "Optimize",
"title": "Batch transactions",
"text": "To save gas and to avoid losing time gathering signatures from everyone.",
"image": {
"src": "/images/Wallet/Features/optimise.png",
"alt": "Token swap modal"
}
},
{
"isNew": true,
"caption": "Earn",
"title": "Stake ETH Securely",
"text": "Stake directly from your Safe{Wallet} with robust multisig security.",
"image": {
"src": "/images/Wallet/Features/earn.png",
"alt": "Token swap modal"
}
},
{
"caption": "All-in-one",
"title": "Explore Safe Apps",
"text": "Swapping, staking, NFTs, identity and more. Interact with 200+ apps integrated with Safe.",
"link": {
"title": "View ecosystem projects",
"href": "https://safe.global/ecosystem",
"variant": "link"
},
"image": {
"src": "/images/Wallet/Features/safe-apps.png",
"alt": "Safe Apps logos"
}
}
]
},
{
"title": "Introducing<br>Native Swaps",
"caption": "New feature",
Expand Down

0 comments on commit ba095b6

Please sign in to comment.