diff --git a/public/images/GasStation/gas-sign.png b/public/images/GasStation/gas-sign.png new file mode 100644 index 000000000..94fd02ac7 Binary files /dev/null and b/public/images/GasStation/gas-sign.png differ diff --git a/public/images/GasStation/gas-station.png b/public/images/GasStation/gas-station.png new file mode 100644 index 000000000..c6ca58fc5 Binary files /dev/null and b/public/images/GasStation/gas-station.png differ diff --git a/public/images/GasStation/network-gas-pump-md.png b/public/images/GasStation/network-gas-pump-md.png new file mode 100644 index 000000000..1e4a3052b Binary files /dev/null and b/public/images/GasStation/network-gas-pump-md.png differ diff --git a/public/images/GasStation/network-gas-pump-sm.png b/public/images/GasStation/network-gas-pump-sm.png new file mode 100644 index 000000000..9a9a90922 Binary files /dev/null and b/public/images/GasStation/network-gas-pump-sm.png differ diff --git a/public/images/GasStation/safe-pimlico.png b/public/images/GasStation/safe-pimlico.png new file mode 100644 index 000000000..492ec42de Binary files /dev/null and b/public/images/GasStation/safe-pimlico.png differ diff --git a/public/images/GasStation/safe-refrigerator.png b/public/images/GasStation/safe-refrigerator.png new file mode 100644 index 000000000..7f63dddda Binary files /dev/null and b/public/images/GasStation/safe-refrigerator.png differ diff --git a/public/images/GasStation/your-tank.png b/public/images/GasStation/your-tank.png new file mode 100644 index 000000000..9840a4684 Binary files /dev/null and b/public/images/GasStation/your-tank.png differ diff --git a/public/images/Header/gas-station-icon.svg b/public/images/Header/gas-station-icon.svg new file mode 100644 index 000000000..d2afdcb11 --- /dev/null +++ b/public/images/Header/gas-station-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/gnosis-logo.png b/public/images/gnosis-logo.png new file mode 100644 index 000000000..6de1ef616 Binary files /dev/null and b/public/images/gnosis-logo.png differ diff --git a/public/images/pimlico-logo.png b/public/images/pimlico-logo.png new file mode 100644 index 000000000..90b5d4644 Binary files /dev/null and b/public/images/pimlico-logo.png differ diff --git a/public/images/x-icon.svg b/public/images/x-icon.svg index b5700350c..9d2f6d794 100644 --- a/public/images/x-icon.svg +++ b/public/images/x-icon.svg @@ -1,3 +1,3 @@ - + diff --git a/public/videos/GasStation/gas-station.mp4 b/public/videos/GasStation/gas-station.mp4 new file mode 100644 index 000000000..b8b06cd42 Binary files /dev/null and b/public/videos/GasStation/gas-station.mp4 differ diff --git a/public/videos/GasStation/gas-station.webm b/public/videos/GasStation/gas-station.webm new file mode 100644 index 000000000..11ddc5675 Binary files /dev/null and b/public/videos/GasStation/gas-station.webm differ diff --git a/src/components/Blog/Socials/index.tsx b/src/components/Blog/Socials/index.tsx index f539915e2..49a217b9a 100644 --- a/src/components/Blog/Socials/index.tsx +++ b/src/components/Blog/Socials/index.tsx @@ -4,30 +4,21 @@ import { type Entry } from 'contentful' import { type TypeAuthorSkeleton } from '@/contentful/types' import { IconButton, SvgIcon } from '@mui/material' import css from '../styles.module.css' -import { useEffect, useState } from 'react' - -const twitterSharingUrl = (currentUrl: string, sharingText: string) => - `https://twitter.com/intent/tweet?url=${encodeURIComponent(currentUrl)}&text=${encodeURIComponent(sharingText)}` +import useCurrentUrl from '@/hooks/useCurrentUrl' +import { xSharingUrl } from '@/lib/xSharingUrl' const linkedInSharingUrl = (currentUrl: string) => `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(currentUrl)}` const Socials = ({ title, authors }: { title: string; authors: Entry[] }) => { - const [currentUrl, setCurrentUrl] = useState('') - - useEffect(() => { - // Check if running in the browser environment - if (typeof window !== 'undefined') { - setCurrentUrl(window.location.href) - } - }, []) + const currentUrl = useCurrentUrl() const sharingText = `${title} by @${authors .map((author) => author.fields.name) .join(', @') .toString()}` - const xUrl = twitterSharingUrl(currentUrl, sharingText) + const xUrl = xSharingUrl(currentUrl, sharingText) const linkedInUrl = linkedInSharingUrl(currentUrl) return ( diff --git a/src/components/Blog/styles.module.css b/src/components/Blog/styles.module.css index 3db11cb32..5e80ef3a6 100644 --- a/src/components/Blog/styles.module.css +++ b/src/components/Blog/styles.module.css @@ -110,6 +110,7 @@ .socials svg { width: 24px; height: 24px; + color: var(--mui-palette-text-primary); } .questionBox { diff --git a/src/components/GasStation/Hero/index.tsx b/src/components/GasStation/Hero/index.tsx new file mode 100644 index 000000000..228bc8d09 --- /dev/null +++ b/src/components/GasStation/Hero/index.tsx @@ -0,0 +1,37 @@ +import type { BaseBlock } from '@/components/Home/types' +import Slider from '@/components/GasStation/Slider' +import { Button, Typography } from '@mui/material' +import clsx from 'clsx' +import css from './styles.module.css' + +const Hero = ({ title, text, link }: BaseBlock) => { + return ( +
+
+ + +
+ +
+ +
+ + {title} + + + {link && ( + + )} +
+
+
+ ) +} + +export default Hero diff --git a/src/components/GasStation/Hero/styles.module.css b/src/components/GasStation/Hero/styles.module.css new file mode 100644 index 000000000..b21eec4e8 --- /dev/null +++ b/src/components/GasStation/Hero/styles.module.css @@ -0,0 +1,55 @@ +.videoWrapper { + position: relative; + z-index: -1; +} + +.video { + width: 100%; +} + +.sliderWrapper { + margin-top: -8px; +} + +.textContainer { + text-align: center; + padding: 0 16px; +} + +@media (min-width: 900px) { + .gradientBase { + position: absolute; + width: 94px; + top: 0; + bottom: 0; + left: 0; + background: linear-gradient(270deg, rgba(18, 19, 18, 0) 0%, var(--mui-palette-background-default) 100%); + z-index: 1; + } + + .gradientFlipped { + left: auto; + right: 0; + bottom: 0; + width: 138px; + transform: scaleX(-1); + } + + .videoWrapper { + margin-top: calc(-1 * var(--header-height)); + } + + .textContainer { + position: absolute; + top: 32%; + left: calc(50% - 450px); + max-width: 900px; + } +} + +@media (min-width: 1440px) { + .videoWrapper { + margin-left: -104px; + margin-right: -104px; + } +} diff --git a/src/components/GasStation/ImageTextTop/index.tsx b/src/components/GasStation/ImageTextTop/index.tsx new file mode 100644 index 000000000..9a74fc95c --- /dev/null +++ b/src/components/GasStation/ImageTextTop/index.tsx @@ -0,0 +1,25 @@ +import type { BaseBlock } from '@/components/Home/types' +import useResponsiveImages, { type ImageObj } from '@/hooks/useResponsiveImages' +import { Container, Typography } from '@mui/material' +import layoutCss from '@/components/common/styles.module.css' +import css from './styles.module.css' + +export const ImageTextTop = ({ + title, + backgroundImage, +}: BaseBlock & { + backgroundImage: ImageObj +}) => { + const [bgImage] = useResponsiveImages(backgroundImage) + + return ( + +
+
+ {title} +
+ + ) +} + +export default ImageTextTop diff --git a/src/components/GasStation/ImageTextTop/styles.module.css b/src/components/GasStation/ImageTextTop/styles.module.css new file mode 100644 index 000000000..e1b7c6ced --- /dev/null +++ b/src/components/GasStation/ImageTextTop/styles.module.css @@ -0,0 +1,29 @@ +.container { + position: relative; +} + +.bg { + background-repeat: no-repeat; + overflow: hidden; + background-size: contain; + min-height: 500px; + max-height: 90vh; + background-position: center top 190px; +} + +@media (min-width: 900px) { + .bg { + min-height: 650px; + background-position: center top; + } + + .spot1 { + position: absolute; + left: -300px; + z-index: -1; + width: 1000px; + height: 1000px; + background-image: radial-gradient(at left, rgba(18, 255, 128, 0.4) 0%, rgba(246, 247, 248, 0) 80%); + filter: blur(50px); + } +} diff --git a/src/components/GasStation/LinkCard/index.tsx b/src/components/GasStation/LinkCard/index.tsx new file mode 100644 index 000000000..5721bb589 --- /dev/null +++ b/src/components/GasStation/LinkCard/index.tsx @@ -0,0 +1,30 @@ +import { Typography } from '@mui/material' +import LinkButton from '@/components/common/LinkButton' +import SafeLink from '@/components/common/SafeLink' +import css from './styles.module.css' +import type { Link } from '@/components/Home/types' + +export type CardProps = { + title: string | JSX.Element + link?: Link +} + +const LinkCard = ({ title, link }: CardProps) => ( +
+
+ + {title} + + + {link ? ( + + + {link.title} + + + ) : null} +
+
+) + +export default LinkCard diff --git a/src/components/GasStation/LinkCard/styles.module.css b/src/components/GasStation/LinkCard/styles.module.css new file mode 100644 index 000000000..77df80eea --- /dev/null +++ b/src/components/GasStation/LinkCard/styles.module.css @@ -0,0 +1,60 @@ +.card { + background: var(--mui-palette-border-background); + outline: 1px solid var(--mui-palette-border-light); + padding: 24px; + display: flex; + flex-direction: column; + gap: 40px; + border-radius: 8px; + width: 100%; + height: 100%; + position: relative; + transition-duration: var(--transition-duration); +} + +.card::before { + content: ''; + position: absolute; + border-radius: 8px; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + z-index: -1; + background: linear-gradient(to bottom left, #12ff80, #5fddff); + opacity: 0; +} + +.card:hover::before { + opacity: 1; +} + +.cardBody { + display: flex; + flex-direction: column; + gap: 16px; +} + +/* Outline */ +.outline:hover { + box-shadow: inset 0 0 0 2px var(--mui-palette-primary-main); +} + +.outline:hover .arrow path { + fill: var(--mui-palette-primary-main); +} + +.outline .link div::after { + background-color: var(--mui-palette-primary-main) !important; +} + +.outline .link:hover path { + fill: var(--mui-palette-primary-main) !important; +} + +@media (min-width: 600px) { + .card { + width: 410px; + height: min-content; + } +} diff --git a/src/components/GasStation/PartnersCardsImage/index.tsx b/src/components/GasStation/PartnersCardsImage/index.tsx new file mode 100644 index 000000000..6dd5ed53b --- /dev/null +++ b/src/components/GasStation/PartnersCardsImage/index.tsx @@ -0,0 +1,58 @@ +import { Container, Grid, Typography } from '@mui/material' +import clsx from 'clsx' +import type { BaseBlock, BlockWithVariant } from '@/components/Home/types' +import layoutCss from '@/components/common/styles.module.css' +import css from './styles.module.css' +import LinkCard from '@/components/GasStation/LinkCard' + +export type PartnersCardsImageProps = BaseBlock & + BlockWithVariant & { + partnersImage: BaseBlock['image'] + } + +const PartnersCardsImage = ({ + caption, + partnersImage, + items, + variant, + mobileVariant, + image, +}: PartnersCardsImageProps) => { + return ( + +
+ + + {caption && ( + + {caption} + + )} + + {partnersImage ? {partnersImage.alt} : null} + + {items?.map((item, index) => ( + + ))} + + + {image ? ( + + {image.alt} + + ) : null} + + + ) +} + +export default PartnersCardsImage diff --git a/src/components/GasStation/PartnersCardsImage/styles.module.css b/src/components/GasStation/PartnersCardsImage/styles.module.css new file mode 100644 index 000000000..934d2500c --- /dev/null +++ b/src/components/GasStation/PartnersCardsImage/styles.module.css @@ -0,0 +1,45 @@ +.container { + position: relative; +} + +.textBlock a { + text-decoration: underline; + color: var(--mui-palette-primary-main); +} + +.textFirst, +.imageFirst { + flex-direction: column-reverse; +} + +.textFirstMobile { + flex-direction: column; +} + +.gradientCaption { + background: linear-gradient(to left, #12ff80, #5fddff); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +@media (min-width: 600px) { + .textFirst { + flex-direction: row; + } + + .imageFirst { + flex-direction: row-reverse; + } + + .spot1 { + position: absolute; + right: -50px; + top: -650px; + z-index: -1; + width: 1600px; + height: 1600px; + background-image: radial-gradient(at right, rgba(18, 255, 128, 0.3) 0%, rgba(246, 247, 248, 0) 50%); + filter: blur(70px); + } +} diff --git a/src/components/GasStation/Slider/index.tsx b/src/components/GasStation/Slider/index.tsx new file mode 100644 index 000000000..32551f046 --- /dev/null +++ b/src/components/GasStation/Slider/index.tsx @@ -0,0 +1,21 @@ +import type { BaseBlock } from '@/components/Home/types' +import { Typography } from '@mui/material' +import css from './styles.module.css' + +const SLIDER_ITEMS = 10 + +const Slider = ({ text }: Pick) => ( +
+
+
+ {Array.from({ length: SLIDER_ITEMS }).map((_, idx) => ( + + {text} + + ))} +
+
+
+) + +export default Slider diff --git a/src/components/GasStation/Slider/styles.module.css b/src/components/GasStation/Slider/styles.module.css new file mode 100644 index 000000000..c963cb334 --- /dev/null +++ b/src/components/GasStation/Slider/styles.module.css @@ -0,0 +1,39 @@ +.wrapper { + --items-gap: 16px; + overflow: hidden; + background-color: var(--mui-palette-background-main); +} + +.animation { + animation-iteration-count: infinite; + animation-timing-function: linear; + width: max-content; + min-width: 200%; + animation-duration: 100s; + animation-delay: 0s; + animation-play-state: running; + animation-name: slide; +} + +.slider { + height: 40px; + display: flex; + gap: var(--items-gap); + align-items: center; +} + +.item { + color: var(--mui-palette-primary-main); +} + +.item::before { + content: '•'; + color: var(--mui-palette-primary-main); + margin-right: var(--items-gap); +} + +@keyframes slide { + to { + transform: translate(-50%); + } +} diff --git a/src/components/GasStation/TextBlockImagePartners/PartnersElement.tsx b/src/components/GasStation/TextBlockImagePartners/PartnersElement.tsx new file mode 100644 index 000000000..4fd8e3533 --- /dev/null +++ b/src/components/GasStation/TextBlockImagePartners/PartnersElement.tsx @@ -0,0 +1,14 @@ +import { type BaseBlock } from '@/components/Home/types' +import css from './styles.module.css' + +const PartnersElement = ({ image }: { image: BaseBlock['image'] }) => ( + <> + {image ? ( +
+ {image.alt} +
+ ) : null} + +) + +export default PartnersElement diff --git a/src/components/GasStation/TextBlockImagePartners/index.tsx b/src/components/GasStation/TextBlockImagePartners/index.tsx new file mode 100644 index 000000000..1b43e3165 --- /dev/null +++ b/src/components/GasStation/TextBlockImagePartners/index.tsx @@ -0,0 +1,61 @@ +import { Container, Grid, Typography } from '@mui/material' +import clsx from 'clsx' +import type { BaseBlock, BlockWithVariant } from '@/components/Home/types' +import PartnersElement from '@/components/GasStation/TextBlockImagePartners/PartnersElement' +import layoutCss from '@/components/common/styles.module.css' +import css from './styles.module.css' + +export type TextBlockImagePartnersProps = BaseBlock & BlockWithVariant + +const TextBlockImagePartners = ({ title, text, items, image, variant, mobileVariant }: TextBlockImagePartnersProps) => ( + +
+
+ + +
+ {title} + {text} +
+ +
+ + Our partners + + + {/* Logos */} + {items ? ( +
+ {items.map(({ image }, index) => ( + {image?.alt + ))} +
+ ) : undefined} +
+
+ + + + +
+ +) + +export default TextBlockImagePartners diff --git a/src/components/GasStation/TextBlockImagePartners/styles.module.css b/src/components/GasStation/TextBlockImagePartners/styles.module.css new file mode 100644 index 000000000..b0782f9d0 --- /dev/null +++ b/src/components/GasStation/TextBlockImagePartners/styles.module.css @@ -0,0 +1,90 @@ +.container { + position: relative; +} + +.textBlock { + display: flex; + flex-direction: column; + gap: 24px; +} + +.textBlock a { + text-decoration: underline; + color: var(--mui-palette-primary-main); +} + +.partnersBlock { + display: flex; + flex-direction: column; + gap: 16px; +} + +.textFirst, +.imageFirst { + flex-direction: column-reverse; +} + +.textFirstMobile { + flex-direction: column; +} + +.logos { + display: flex; + flex-wrap: wrap; + gap: 24px; +} + +.logo { + height: 18px; +} + +.imageWrapper { + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.image { + max-width: 305px; +} + +@media (min-width: 600px) { + .textFirst { + flex-direction: row; + } + + .imageFirst { + flex-direction: row-reverse; + } + + .logo { + height: 24px; + } + + .image { + width: 100%; + max-width: 405px; + } + + .spot1 { + position: absolute; + left: -300px; + top: -200px; + width: 1000px; + height: 1000px; + background-image: radial-gradient(at top left, rgba(18, 255, 128, 0.3) 0%, rgba(246, 247, 248, 0) 50%); + filter: blur(70px); + } + + .spot2 { + position: absolute; + right: -50px; + top: -250px; + width: 1000px; + height: 1000px; + background-image: radial-gradient(at top right, rgba(41, 182, 246, 0.3) 0%, rgba(246, 247, 248, 0) 50%); + filter: blur(70px); + } +} diff --git a/src/components/GasStation/TextBlockImageYourStation/index.tsx b/src/components/GasStation/TextBlockImageYourStation/index.tsx new file mode 100644 index 000000000..77a09c760 --- /dev/null +++ b/src/components/GasStation/TextBlockImageYourStation/index.tsx @@ -0,0 +1,17 @@ +import { Box } from '@mui/material' +import TextBlockImage, { type TextBlockImageProps } from '@/components/common/TextBlockImage' +import css from './styles.module.css' + +const TextBlockImageYourStation = (props: TextBlockImageProps) => { + const { image } = props + + return ( + + + {image ? {image.alt} : null} + + + ) +} + +export default TextBlockImageYourStation diff --git a/src/components/GasStation/TextBlockImageYourStation/styles.module.css b/src/components/GasStation/TextBlockImageYourStation/styles.module.css new file mode 100644 index 000000000..fadc283e5 --- /dev/null +++ b/src/components/GasStation/TextBlockImageYourStation/styles.module.css @@ -0,0 +1,11 @@ +.image { + width: 100%; + max-width: 675px; + margin-top: -30px; +} + +@media (min-width: 900px) { + .image { + margin-top: auto; + } +} diff --git a/src/components/GasStation/TwitterCTA/index.tsx b/src/components/GasStation/TwitterCTA/index.tsx new file mode 100644 index 000000000..1da5b509d --- /dev/null +++ b/src/components/GasStation/TwitterCTA/index.tsx @@ -0,0 +1,45 @@ +import { Button, Container, SvgIcon, Typography } from '@mui/material' +import type { BaseBlock } from '@/components/Home/types' +import layoutCss from '@/components/common/styles.module.css' +import SafeLink from '@/components/common/SafeLink' +import { xSharingUrl } from '@/lib/xSharingUrl' +import XIcon from '@/public/images/x-icon.svg' +import useCurrentUrl from '@/hooks/useCurrentUrl' +import Slider from '@/components/GasStation/Slider' +import clsx from 'clsx' +import css from './styles.module.css' + +const socialMsg = 'Just applied to get gas credits from @safe Safe{Core} gas station ⛽️' + +const TwitterCTA = ({ title, text }: BaseBlock) => { + const currentUrl = useCurrentUrl() + + const xUrl = xSharingUrl(currentUrl, socialMsg) + + return ( + <> + + + {title} + + + + + + + +
+
+ + + +
+
+ + ) +} + +export default TwitterCTA diff --git a/src/components/GasStation/TwitterCTA/styles.module.css b/src/components/GasStation/TwitterCTA/styles.module.css new file mode 100644 index 000000000..ae65991ca --- /dev/null +++ b/src/components/GasStation/TwitterCTA/styles.module.css @@ -0,0 +1,57 @@ +.title { + text-align: center; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + gap: 40px; +} + +.button { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} +.sliderWrapper { + margin-top: 96px; + margin-bottom: -80px; +} + +@media (min-width: 600px) { + .gradientBase { + position: absolute; + width: 94px; + top: 0; + bottom: 0; + left: 0; + background: linear-gradient(270deg, rgba(18, 19, 18, 0) 0%, var(--mui-palette-background-default) 100%); + z-index: 1; + } + + .gradientFlipped { + left: auto; + right: 0; + bottom: 0; + width: 138px; + transform: scaleX(-1); + } + + .title { + max-width: 800px; + } + + .sliderWrapper { + position: relative; + margin-bottom: -120px; + } +} + +@media (min-width: 1440px) { + .sliderWrapper { + margin-left: -104px; + margin-right: -104px; + } +} diff --git a/src/components/GasStation/index.tsx b/src/components/GasStation/index.tsx new file mode 100644 index 000000000..9c6f097ba --- /dev/null +++ b/src/components/GasStation/index.tsx @@ -0,0 +1,7 @@ +import type { ReactElement } from 'react' +import PageContent from '@/components/common/PageContent' +import gasStationContent from '@/content/gas-station.json' + +export const GasStation = (): ReactElement => { + return +} diff --git a/src/components/Home/types.d.ts b/src/components/Home/types.ts similarity index 73% rename from src/components/Home/types.d.ts rename to src/components/Home/types.ts index 7978db90a..27a3f1be8 100644 --- a/src/components/Home/types.d.ts +++ b/src/components/Home/types.ts @@ -1,7 +1,7 @@ import type { DetailedHTMLProps, ImgHTMLAttributes } from 'react' import type { ButtonProps } from '@mui/material' -type BaseBlock = { +export type BaseBlock = { title: string | JSX.Element text: string | JSX.Element caption?: string @@ -11,15 +11,20 @@ type BaseBlock = { items?: Array> } -type Link = { +export type Link = { title?: string href: string } -type Button = { +export type Button = { text?: string href: string variant: 'button' | 'link' color?: ButtonProps['color'] isDisabled?: boolean } + +export type BlockWithVariant = { + variant: 'image-text' | 'text-image' + mobileVariant?: 'image-text' | 'text-image' +} diff --git a/src/components/common/Header/navCategories.tsx b/src/components/common/Header/navCategories.tsx index 152a7c36c..d3a0dc5ce 100644 --- a/src/components/common/Header/navCategories.tsx +++ b/src/components/common/Header/navCategories.tsx @@ -11,6 +11,7 @@ import SafeConIcon from '@/public/images/Header/safe-con-icon.svg' import CareersIcon from '@/public/images/Header/careers-icon.svg' import PressRoomIcon from '@/public/images/Header/press-room-icon.svg' import HelpCenterIcon from '@/public/images/Header/help-center-icon.svg' +import GasStationIcon from '@/public/images/Header/gas-station-icon.svg' export type NavItem = { label: string @@ -60,6 +61,11 @@ export const navCategories: NavCategory[] = [ rel: 'noreferrer', icon: , }, + { + label: 'Gas Station', + href: AppRoutes.gasStation, + icon: , + }, ], }, { diff --git a/src/components/common/Masthead/index.tsx b/src/components/common/Masthead/index.tsx index 046b8a4fb..5e0971c3b 100644 --- a/src/components/common/Masthead/index.tsx +++ b/src/components/common/Masthead/index.tsx @@ -6,8 +6,7 @@ import layoutCss from '@/components/common/styles.module.css' import css from './styles.module.css' import type { BaseBlock } from '@/components/Home/types' import ButtonsWrapper from '@/components/common/ButtonsWrapper' -import { getImageSource, type ImageObj } from '@/lib/getImageSource' -import { useIsMediumScreen } from '@/hooks/useMaxWidth' +import useResponsiveImages, { type ImageObj } from '@/hooks/useResponsiveImages' type FooterProps = { text: string @@ -48,10 +47,7 @@ export const Masthead = ({ backgroundImage: ImageObj footer: FooterProps }): ReactElement => { - const isMediumScreen = useIsMediumScreen() - - const bgImage = getImageSource(isMediumScreen, backgroundImage) - const imageSrc = getImageSource(isMediumScreen, image) + const [bgImage, imageSrc] = useResponsiveImages(backgroundImage, image) return ( diff --git a/src/components/common/ParallaxTextOld/index.tsx b/src/components/common/ParallaxTextOld/index.tsx index d42f92893..57c7eec58 100644 --- a/src/components/common/ParallaxTextOld/index.tsx +++ b/src/components/common/ParallaxTextOld/index.tsx @@ -2,15 +2,12 @@ import type { ReactNode } from 'react' import { Container, Grid, Typography } from '@mui/material' import layoutCss from '@/components/common/styles.module.css' import css from './styles.module.css' -import type { BaseBlock } from '@/components/Home/types' +import type { BaseBlock, BlockWithVariant } from '@/components/Home/types' import Stepper, { type StepsType } from '@/components/Wallet/Stepper' import clsx from 'clsx' import ButtonsWrapper from '@/components/common/ButtonsWrapper' -export type ParallaxTextProps = BaseBlock & { - variant: 'image-text' | 'text-image' - mobileVariant?: 'image-text' | 'text-image' -} +export type ParallaxTextProps = BaseBlock & BlockWithVariant // TODO: This component should be deleted when every page content is fetched from the CMS rather than hardcoded in the /content folder const ParallaxText = ({ diff --git a/src/components/common/TextBlockImage/index.tsx b/src/components/common/TextBlockImage/index.tsx index f60ecea03..c9936b68a 100644 --- a/src/components/common/TextBlockImage/index.tsx +++ b/src/components/common/TextBlockImage/index.tsx @@ -1,15 +1,14 @@ import { Container, Grid, Typography } from '@mui/material' import clsx from 'clsx' import ButtonsWrapper from '@/components/common/ButtonsWrapper' -import type { BaseBlock } from '@/components/Home/types' +import type { BaseBlock, BlockWithVariant } from '@/components/Home/types' import layoutCss from '@/components/common/styles.module.css' import css from './styles.module.css' -export type TextBlockImageProps = BaseBlock & { - variant: 'image-text' | 'text-image' - mobileVariant?: 'image-text' | 'text-image' - children: React.ReactNode -} +export type TextBlockImageProps = BaseBlock & + BlockWithVariant & { + children: React.ReactNode + } const TextBlockImage = ({ caption, @@ -40,9 +39,11 @@ const TextBlockImage = ({ )} {title} - - {text} - + {text && ( + + {text} + + )} {/* Logos */} {items ? ( diff --git a/src/config/routes.ts b/src/config/routes.ts index 049e343f3..629958c2f 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -10,6 +10,7 @@ export const AppRoutes = { index: '/', imprint: '/imprint', governance: '/governance', + gasStation: '/gas-station', ecosystem: '/ecosystem', disclaimer: '/disclaimer', core: '/core', diff --git a/src/content/gas-station.json b/src/content/gas-station.json new file mode 100644 index 000000000..58738705a --- /dev/null +++ b/src/content/gas-station.json @@ -0,0 +1,109 @@ +[ + { + "pageTitle": "Safe Gas Station", + "description": "Get gas credits to make your app gasless", + "image": "https://safe.global/images/GasStation/gas-station.png", + "component": "common/MetaTags" + }, + { + "component": "GasStation/Hero", + "title": "Multi-chain gas credits to make your app go gasless", + "text": "User should not pay gas for using your app", + "link": { + "title": "Get gas credits", + "href": "https://wn2n6ocviur.typeform.com/gasstationapp" + } + }, + { + "component": "GasStation/TextBlockImagePartners", + "variant": "image-text", + "title": "Why sponsor gas?", + "text": "ERC 4337 and smart accounts can make user experience completely gasless by sponsoring transactions", + "items": [ + { + "image": { + "src": "/images/pimlico-logo.png", + "alt": "Pimlico" + } + }, + { + "image": { + "src": "/images/gnosis-logo.png", + "alt": "Gnosis" + } + }, + { + "image": { + "src": "/images/polygon.svg", + "alt": "Polygon" + } + } + ], + "image": { + "src": "/images/GasStation/gas-sign.png", + "alt": "Sign pointing to gasless apps" + }, + "mobileVariant": "text-image" + }, + { + "component": "GasStation/ImageTextTop", + "title": "Network credits
Available", + "backgroundImage": { + "sm": "/images/GasStation/network-gas-pump-sm.png", + "md": "/images/GasStation/network-gas-pump-md.png", + "alt": "Gnosis and Polygon gas pumps" + } + }, + { + "component": "GasStation/TextBlockImageYourStation", + "variant": "image-text", + "title": "Get your network
on the gas station", + "buttons": [ + { + "text": "Become a network partner", + "href": "https://wn2n6ocviur.typeform.com/to/fBPBSB8l", + "variant": "button" + } + ], + "image": { + "src": "/images/GasStation/your-tank.png", + "alt": "New network partner gas tank" + }, + "mobileVariant": "text-image" + }, + { + "component": "GasStation/PartnersCardsImage", + "variant": "text-image", + "caption": "Integrate to build gasless apps", + "partnersImage": { + "src": "/images/GasStation/safe-pimlico.png", + "alt": "Safe and Pimlico" + }, + "items": [ + { + "title": "Explore ERC 4337 + Safe", + "link": { + "title": "View docs", + "href": "https://docs.safe.global/home/4337-safe" + } + }, + { + "title": "How to integrate Safe{Core} Stack", + "link": { + "title": "View docs", + "href": "https://docs.safe.global/home/4337-guides/safe-sdk" + } + } + ], + "image": { + "src": "/images/GasStation/safe-refrigerator.png", + "alt": "Safe refrigerator" + }, + "mobileVariant": "text-image" + }, + { + "component": "GasStation/TwitterCTA", + "title": "Share and boost your chances to get gas", + "text": "User should not pay gas for using your app" + } +] diff --git a/src/content/home.json b/src/content/home.json index f2b06d9ad..dd9a780a8 100644 --- a/src/content/home.json +++ b/src/content/home.json @@ -225,7 +225,7 @@ ], "image": { "src": "/images/Home/ecosystem-partners.png", - "alt": "Hands holding code brackets" + "alt": "Ecosystem partners cards" }, "mobileVariant": "text-image", "component": "Home/TextBlockImageEcosystem" diff --git a/src/hooks/useCurrentUrl.ts b/src/hooks/useCurrentUrl.ts new file mode 100644 index 000000000..bbaf6e897 --- /dev/null +++ b/src/hooks/useCurrentUrl.ts @@ -0,0 +1,20 @@ +import { useState, useEffect } from 'react' + +/** + * A custom hook to get the current URL + * @returns {string} The current URL + */ +const useCurrentUrl = () => { + const [currentUrl, setCurrentUrl] = useState('') + + useEffect(() => { + // Check if running in the browser environment + if (typeof window !== 'undefined') { + setCurrentUrl(window.location.href) + } + }, []) + + return currentUrl +} + +export default useCurrentUrl diff --git a/src/hooks/useResponsiveImages.ts b/src/hooks/useResponsiveImages.ts new file mode 100644 index 000000000..9513edf29 --- /dev/null +++ b/src/hooks/useResponsiveImages.ts @@ -0,0 +1,34 @@ +import { useIsMediumScreen } from '@/hooks/useMaxWidth' + +export type ImageObj = { + sm: string + md: string + alt?: string +} + +/** + * Get the source URL for an image based on its size and screen condition. + * @param {ImageObj} imageObj - The object containing URLs for different image sizes. + * @param {boolean} isSmall - A flag indicating whether the small image shall be used. + * @returns {string|undefined} - The source URL for the image, or undefined. + */ +export function getImageSource(isSmall: boolean, imageObj?: ImageObj) { + return imageObj ? (isSmall ? imageObj.sm : imageObj.md) : undefined +} + +/** + * Get the source URLs for images based on their sizes and screen conditions. + * @param {ImageObj[]} images - The objects containing URLs for different image sizes. + * @returns {string[]} - The source URLs for the images. + */ +const useResponsiveImages = (...images: Array) => { + const isMediumScreen = useIsMediumScreen() + + return images.reduce>((acc: Array, image: ImageObj) => { + const source = getImageSource(isMediumScreen, image) + + return source ? acc.concat(source) : acc + }, []) +} + +export default useResponsiveImages diff --git a/src/lib/__test__/getImageSource.test.ts b/src/lib/__test__/getImageSource.test.ts index b5981585e..de521162c 100644 --- a/src/lib/__test__/getImageSource.test.ts +++ b/src/lib/__test__/getImageSource.test.ts @@ -1,4 +1,4 @@ -import { type ImageObj, getImageSource } from '@/lib/getImageSource' +import { getImageSource, type ImageObj } from '@/hooks/useResponsiveImages' describe('getImageSource', () => { it('should return small image URL when image object exists and isSmall is true', () => { diff --git a/src/lib/getImageSource.ts b/src/lib/getImageSource.ts deleted file mode 100644 index 9c5bd7501..000000000 --- a/src/lib/getImageSource.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type ImageObj = { - sm: string - md: string - alt?: string -} - -/** - * Get the source URL for an image based on its size and screen condition. - * @param {ImageObj} imageObj - The object containing URLs for different image sizes. - * @param {boolean} isSmall - A flag indicating whether the small image shall be used. - * @returns {string|undefined} - The source URL for the image, or undefined. - */ -export function getImageSource(isSmall: boolean, imageObj?: ImageObj) { - return imageObj ? (isSmall ? imageObj.sm : imageObj.md) : undefined -} diff --git a/src/lib/xSharingUrl.ts b/src/lib/xSharingUrl.ts new file mode 100644 index 000000000..30362c16f --- /dev/null +++ b/src/lib/xSharingUrl.ts @@ -0,0 +1,2 @@ +export const xSharingUrl = (currentUrl: string, sharingText: string) => + `https://x.com/intent/tweet?url=${encodeURIComponent(currentUrl)}&text=${encodeURIComponent(sharingText)}` diff --git a/src/pages/gas-station.tsx b/src/pages/gas-station.tsx new file mode 100644 index 000000000..51e3ca50b --- /dev/null +++ b/src/pages/gas-station.tsx @@ -0,0 +1,8 @@ +import type { NextPage } from 'next' +import { GasStation } from '@/components/GasStation' + +const GasStationPage: NextPage = () => { + return +} + +export default GasStationPage