Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Preserve search parameters in external links #258

Merged
merged 16 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const createJestConfig = nextJest({

/** @type {import('jest').Config} */
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/public/(.*)$': '<rootDir>/public/$1',
'^@/(.*)$': '<rootDir>/src/$1',
Expand Down
3 changes: 3 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Used for __tests__/testing-library.js
// Learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'
6 changes: 3 additions & 3 deletions src/components/Campaign/Hero/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Button, Chip, Container, Grid, Typography } from '@mui/material'
import Link from 'next/link'
import css from './styles.module.css'
import type { TypeHeroSkeleton } from '@/contentful/types'
import type { Entry } from 'contentful'
Expand All @@ -9,6 +8,7 @@ import RichText from '@/components/Campaign/RichText'
import { createImageData } from '@/lib/createImageData'
import { SOCIAL_LOGIN_EVENTS } from '@/services/analytics/events/socialLogin'
import { trackEvent } from '@/services/analytics/trackEvent'
import SafeLink from '@/components/common/SafeLink'

type HeroEntry = Entry<TypeHeroSkeleton, undefined, string>

Expand Down Expand Up @@ -44,7 +44,7 @@ const Hero = (props: HeroEntry) => {
</Typography>

{isEntryTypeButton(button) ? (
<Link href={button.fields.btnHref} target="_blank" rel="noreferrer" passHref>
<SafeLink href={button.fields.btnHref}>
<Button
variant="contained"
size="large"
Expand All @@ -57,7 +57,7 @@ const Hero = (props: HeroEntry) => {
>
{button.fields.btnCopy}
</Button>
</Link>
</SafeLink>
) : undefined}
</Grid>

Expand Down
4 changes: 4 additions & 0 deletions src/components/Campaign/Hero/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
margin: 0;
}

.textBlock a {
width: fit-content;
usame-algan marked this conversation as resolved.
Show resolved Hide resolved
}

.image {
max-width: 500px;
}
Expand Down
7 changes: 3 additions & 4 deletions src/components/Campaign/TextBlockBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Button, Container, Typography } from '@mui/material'
import React from 'react'
import layoutCss from '@/components/common/styles.module.css'
import css from './styles.module.css'
import { isAsset, isEntryTypeButton } from '@/lib/typeGuards'
import type { Entry } from 'contentful'
import Image from 'next/image'
import Link from 'next/link'
import type { TypeTextBlockBannerSkeleton } from '@/contentful/types'
import { trackEvent } from '@/services/analytics/trackEvent'
import { SOCIAL_LOGIN_EVENTS } from '@/services/analytics/events/socialLogin'
import SafeLink from '@/components/common/SafeLink'

type TextBlockBannerEntry = Entry<TypeTextBlockBannerSkeleton, undefined, string>

Expand All @@ -32,7 +31,7 @@ const TextBlockBanner = (props: TextBlockBannerEntry) => {
</Typography>

{isEntryTypeButton(button) ? (
<Link href={button.fields.btnHref} target="_blank" rel="noreferrer" passHref>
<SafeLink href={button.fields.btnHref}>
<Button
variant="contained"
size="large"
Expand All @@ -46,7 +45,7 @@ const TextBlockBanner = (props: TextBlockBannerEntry) => {
>
{button.fields.btnCopy}
</Button>
</Link>
</SafeLink>
) : undefined}
</div>
</Container>
Expand Down
7 changes: 3 additions & 4 deletions src/components/Campaign/TextBlockCentered/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React from 'react'
import { Button, Container, Typography } from '@mui/material'
import Image from 'next/image'
import Link from 'next/link'
import css from './styles.module.css'
import layoutCss from '@/components/common/styles.module.css'
import type { Entry } from 'contentful'
import type { TypeTextBlockCenteredSkeleton } from '@/contentful/types'
import { isAsset, isEntryTypeButton } from '@/lib/typeGuards'
import { trackEvent } from '@/services/analytics/trackEvent'
import { SOCIAL_LOGIN_EVENTS } from '@/services/analytics/events/socialLogin'
import SafeLink from '@/components/common/SafeLink'

type TextBlockCenteredEntry = Entry<TypeTextBlockCenteredSkeleton, undefined, string>

Expand All @@ -33,7 +32,7 @@ const TextBlockCentered = (props: TextBlockCenteredEntry) => {
<Typography color="primary.light">{description}</Typography>

{isEntryTypeButton(button) ? (
<Link href={button.fields.btnHref} target="_blank" rel="noreferrer" passHref>
<SafeLink href={button.fields.btnHref}>
<Button
variant="contained"
size="large"
Expand All @@ -46,7 +45,7 @@ const TextBlockCentered = (props: TextBlockCenteredEntry) => {
>
{button.fields.btnCopy}
</Button>
</Link>
</SafeLink>
) : undefined}
</div>
</Container>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import homeContent from '@/content/home.json'
import type { getStaticProps } from '@/pages/index'
import type { InferGetStaticPropsType } from 'next'
import ChainsContext from '../common/ChainsContext'
import ChainsContext from '@/contexts/ChainsContext'
import PageContent from '../common/PageContent'
import SafeStatsContext from '@/components/common/SafeStatsContext'
import SafeStatsContext from '@/contexts/SafeStatsContext'

export const Home = (props: InferGetStaticPropsType<typeof getStaticProps>) => (
<SafeStatsContext.Provider value={props.safeStatsData}>
Expand Down
18 changes: 7 additions & 11 deletions src/components/Wallet/Intro/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { ReactElement } from 'react'
import { type ReactElement } from 'react'
import { Button, Container, Divider, Grid, Typography } from '@mui/material'
import { IOS_LINK, GPLAY_LINK } from '@/config/constants'
import IOSDownload from '@/public/images/ios-download.svg'
import GPlayDownload from '@/public/images/google-play-download.svg'
import type { BaseBlock } from '@/components/Home/types'
import css from './styles.module.css'
import HeaderParticles from '@/public/images/header_particles.svg'
import SafeLink from '@/components/common/SafeLink'

const Intro = ({ image, title, buttons }: BaseBlock): ReactElement => {
return (
Expand Down Expand Up @@ -33,16 +34,11 @@ const Intro = ({ image, title, buttons }: BaseBlock): ReactElement => {
<Grid container gap={{ xs: 4, md: '10px' }}>
<Grid item>
{buttons?.map(({ text, href }) => (
<Button
key={text}
href={href}
variant="contained"
target="_blank"
rel="noreferrer"
className={css.button}
>
{text}
</Button>
<SafeLink key={text} href={href}>
<Button variant="contained" className={css.button}>
{text}
</Button>
</SafeLink>
))}
</Grid>
<div className={css.downloads}>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Wallet/TextRadialAnimation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import layoutCss from '@/components/common/styles.module.css'
import type { BaseBlock } from '@/components/Home/types'
import RadialAnimation from './RadialAnimation'
import LinkButton from '@/components/common/LinkButton'
import Link from 'next/link'
import SafeLink from '@/components/common/SafeLink'

const TextRadialAnimation = ({ title, text, link }: BaseBlock) => {
return (
Expand All @@ -20,9 +20,9 @@ const TextRadialAnimation = ({ title, text, link }: BaseBlock) => {
</Typography>
<Typography mb={{ xs: 3, md: 5 }}>{text}</Typography>
{link && (
<Link href={link.href} passHref target="_blank" rel="noreferrer">
<SafeLink href={link.href}>
<LinkButton>{link.title}</LinkButton>
</Link>
</SafeLink>
)}
</Grid>
<Grid item md={1} display={{ xs: 'none', md: 'block' }} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Wallet/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import walletContent from '@/content/wallet.json'
import type { getStaticProps } from '@/pages/wallet'
import type { InferGetStaticPropsType } from 'next'
import ChainsContext from '../common/ChainsContext'
import ChainsContext from '@/contexts/ChainsContext'
import PageContent from '../common/PageContent'

export const Wallet = (props: InferGetStaticPropsType<typeof getStaticProps>) => (
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/ButtonsWrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Button as ButtonType } from '@/components/Home/types'
import LinkButton from '@/components/common/LinkButton'
import { Button } from '@mui/material'
import Link from 'next/link'
import css from './styles.module.css'
import SafeLink from '@/components/common/SafeLink'

type ButtonsWrapperProps = {
buttons?: ButtonType[]
Expand All @@ -19,15 +19,15 @@ const ButtonsWrapper = ({ buttons, mobileDirection }: ButtonsWrapperProps) => {
const isButton = variant === 'button'

return (
<Link key={index} href={href} target="_blank" rel="noreferrer" passHref>
<SafeLink key={index} href={href}>
{isButton ? (
<Button variant="contained" size="large" color={color}>
{text}
</Button>
) : (
<LinkButton>{text}</LinkButton>
)}
</Link>
</SafeLink>
)
})}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/CookieBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Paper, Typography, FormControlLabel, Checkbox, Button } from '@mui/mate
import { useEffect, useState } from 'react'
import type { ReactElement } from 'react'

import { useCookieBannerContext } from './CookieBannerContext'
import { useCookieBannerContext } from '@/contexts/CookieBannerContext'

import css from './styles.module.css'
import { AppRoutes } from '@/config/routes'
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
TWITTER_LINK,
YOUTUBE_LINK,
} from '@/config/constants'
import { useCookieBannerContext } from '../CookieBanner/CookieBannerContext'
import { useCookieBannerContext } from '@/contexts/CookieBannerContext'
import Logo from '@/public/images/logo.svg'

const COOKIE_PREFERENCES = '#cookies'
Expand Down
13 changes: 9 additions & 4 deletions src/components/common/HeaderCTA/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import type { BaseBlock } from '@/components/Home/types'
import LinkButton from '@/components/common/LinkButton'
import { Grid, Typography } from '@mui/material'
import Link from 'next/link'
import css from './styles.module.css'
import SafeLink from '@/components/common/SafeLink'

const HeaderCTA = (props: BaseBlock & { bigTitle?: boolean; onClick?: () => void }) => {
type HeaderCTAProps = BaseBlock & {
bigTitle?: boolean
onClick?: () => void
}

const HeaderCTA = (props: HeaderCTAProps) => {
return (
<Grid container mb={{ sm: 5, md: 7 }}>
<Grid item xs={12} md={!props.bigTitle ? 8 : undefined}>
Expand All @@ -17,11 +22,11 @@ const HeaderCTA = (props: BaseBlock & { bigTitle?: boolean; onClick?: () => void
</Grid>
{props.link && (
<Grid item xs={12} md={4} className={`${css.linkButton} ${!props.bigTitle && css.alignEnd}`}>
<Link href={props.link.href} target="_blank" rel="noreferrer" passHref>
<SafeLink href={props.link.href}>
<LinkButton className={css.shortPadding} onClick={props.onClick}>
{props.link.title}
</LinkButton>
</Link>
</SafeLink>
</Grid>
)}
</Grid>
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/HeaderCTA/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
@media (min-width: 900px) {
.alignEnd {
display: flex;
align-items: end;
justify-content: end;
align-items: flex-end;
justify-content: flex-end;
}

.bigTitle {
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Networks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import css from './styles.module.css'
import layoutCss from '@/components/common/styles.module.css'
import { type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { useContext } from 'react'
import ChainsContext from '../ChainsContext'
import ChainsContext from '@/contexts/ChainsContext'

export type ChainProps = {
chainName: ChainInfo['chainName']
Expand Down
18 changes: 18 additions & 0 deletions src/components/common/SafeLink/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import SearchParamsContext from '@/contexts/SearchParamsContext'
import { type ReactNode, useContext } from 'react'
import Link from 'next/link'
import { appendSearchParamsToURL } from '@/lib/appendSearchParamsToURL'

const SafeLink = ({ href, children }: { href: string; children: ReactNode }) => {
const searchParams = useContext(SearchParamsContext)

const finalHref = appendSearchParamsToURL(href, searchParams)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useMemo perhaps?


return (
<Link href={finalHref} target="_blank" rel="noreferrer">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove rel="noreferrer" for app.safe.global links.

{children}
</Link>
)
}

export default SafeLink
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext } from 'react'
import type { ChainProps } from '../Networks'
import type { ChainProps } from '../components/common/Networks'

const ChainsContext = createContext<ChainProps[]>([])

Expand Down
30 changes: 30 additions & 0 deletions src/contexts/SearchParamsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { sessionItem } from '@/services/Storage/session'
import { useSearchParams } from 'next/navigation'
import { type ReactNode, createContext, useEffect, useState } from 'react'

const SEARCH_PARAMS_KEY = 'searchParams'
const sessionSearchParams = sessionItem<string>(SEARCH_PARAMS_KEY)

const SearchParamsContext = createContext<string>('')

export const SearchParamsContextProvider = ({ children }: { children: ReactNode }) => {
const searchParams = useSearchParams()
const [searchParamsValue, setSearchParamsValue] = useState<string>('')

useEffect(() => {
const initialSearchParams = sessionSearchParams.get() || ''
const finalUtmParams = new URLSearchParams(initialSearchParams)

// merge search params
for (const [key, value] of Array.from(searchParams.entries())) {
finalUtmParams.set(key, value)
}

setSearchParamsValue(finalUtmParams.toString())
sessionSearchParams.set(finalUtmParams.toString())
}, [searchParams])

return <SearchParamsContext.Provider value={searchParamsValue}>{children}</SearchParamsContext.Provider>
}

export default SearchParamsContext
Loading
Loading