feat: react helmet implementation (#223)
* feat: dynamic page metadata implementation * cleanup: unused imports * cleanup: more unused imports
This commit is contained in:
parent
e38b8be496
commit
999d936f85
32
__tests__/components/PageMetadata.test.tsx
Normal file
32
__tests__/components/PageMetadata.test.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import * as rrd from 'react-router-dom'
|
||||
|
||||
import PageMetadata from 'components/PageMetadata'
|
||||
import PAGE_METADATA from 'constants/pageMetadata'
|
||||
|
||||
jest.mock('react-router-dom')
|
||||
const mockedUseLocation = rrd.useLocation as jest.Mock
|
||||
|
||||
describe('<PageMetadata />', () => {
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
Object.keys(PAGE_METADATA).forEach((page) => {
|
||||
it(`should render correct ${page} metadata`, () => {
|
||||
const pageKey = page as keyof typeof PAGE_METADATA
|
||||
const pageMetadata = PAGE_METADATA[pageKey]
|
||||
|
||||
mockedUseLocation.mockReturnValue({ pathname: pageKey })
|
||||
|
||||
const { container } = render(<PageMetadata />)
|
||||
const titleElement = container.querySelector('title')
|
||||
const descriptionElement = container.querySelector('meta[name="description"]')
|
||||
const keywordsElement = container.querySelector('meta[name="keywords"]')
|
||||
|
||||
expect(titleElement).toHaveTextContent(pageMetadata.title)
|
||||
expect(descriptionElement).toHaveAttribute('content', pageMetadata.description)
|
||||
expect(keywordsElement).toHaveAttribute('content', pageMetadata.keywords)
|
||||
})
|
||||
})
|
||||
})
|
@ -1 +1,19 @@
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
|
||||
jest.mock('react-helmet', () => {
|
||||
const React = require('react')
|
||||
const plugin = jest.requireActual('react-helmet')
|
||||
const mockHelmet = ({ children, ...props }) =>
|
||||
React.createElement(
|
||||
'div',
|
||||
{
|
||||
...props,
|
||||
className: 'mock-helmet',
|
||||
},
|
||||
children,
|
||||
)
|
||||
return {
|
||||
...plugin,
|
||||
Helmet: jest.fn().mockImplementation(mockHelmet),
|
||||
}
|
||||
})
|
||||
|
@ -27,6 +27,7 @@
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-draggable": "^4.4.5",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-router-dom": "^6.11.2",
|
||||
"react-spring": "^9.7.1",
|
||||
"react-toastify": "^9.1.2",
|
||||
@ -43,6 +44,7 @@
|
||||
"@types/node": "^20.2.3",
|
||||
"@types/react": "18.2.6",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-jest": "^29.5.0",
|
||||
"dotenv": "^16.0.3",
|
||||
|
25
src/components/DefaultPageHead.tsx
Normal file
25
src/components/DefaultPageHead.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import Head from 'next/head'
|
||||
|
||||
function DefaultPageHead() {
|
||||
return (
|
||||
<Head>
|
||||
<title>Mars Protocol V2</title>
|
||||
<meta charSet='utf-8' />
|
||||
<link href='/favicon.svg' rel='icon' />
|
||||
<link href='/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
||||
<link href='/site.webmanifest' rel='manifest' />
|
||||
<link color='#dd5b65' href='/safari-pinned-tab.svg' rel='mask-icon' />
|
||||
<meta content='index,follow' name='robots' />
|
||||
<meta content='summary_large_image' name='twitter:card' />
|
||||
<meta content='@mars_protocol' name='twitter:site' />
|
||||
<meta content='@mars_protocol' name='twitter:creator' />
|
||||
<meta content='https://osmosis.marsprotocol.io' property='og:url' />
|
||||
<meta content='https://osmosis.marsprotocol.io/banner.png' property='og:image' />
|
||||
<meta content='Mars Protocol V2' property='og:site_name' />
|
||||
<meta content='#ffffff' name='msapplication-TileColor' />
|
||||
<meta content='#ffffff' name='theme-color' />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
export default DefaultPageHead
|
25
src/components/PageMetadata.tsx
Normal file
25
src/components/PageMetadata.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import PAGE_METADATA from 'constants/pageMetadata'
|
||||
|
||||
function PageMetadata() {
|
||||
const location = useLocation()
|
||||
|
||||
const metadata = useMemo(() => {
|
||||
const route = location.pathname.split('/').reverse()[0] as keyof typeof PAGE_METADATA
|
||||
return PAGE_METADATA[route] || PAGE_METADATA['trade']
|
||||
}, [location])
|
||||
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{metadata.title}</title>
|
||||
<meta content={metadata.title} property='og:title' />
|
||||
<meta name='description' content={metadata.description} property='og:description' />
|
||||
<meta name='keywords' content={metadata.keywords} property='og:keywords' />
|
||||
</Helmet>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageMetadata
|
45
src/constants/pageMetadata.ts
Normal file
45
src/constants/pageMetadata.ts
Normal file
@ -0,0 +1,45 @@
|
||||
const PAGE_METADATA = {
|
||||
trade: {
|
||||
title: 'Mars Protocol V2 - Trade',
|
||||
description:
|
||||
'Create a Rover credit account to begin spot and margin trading crypto assets with cross-collateralized positions and a single liquidation point.',
|
||||
keywords:
|
||||
'crypto trading, spot trading, margin trading, decentralized trading, defi, cross-collateralization, cosmos tokens',
|
||||
},
|
||||
farm: {
|
||||
title: 'Mars Protocol V2 - Farm',
|
||||
description:
|
||||
"Lend your crypto assets or deploy advanced defi strategies including leveraged yield farming and leveraged staking in Mars' Farm vaults.",
|
||||
keywords:
|
||||
'leveraged yield farming, crypto yield, leveraged staking, mars farm vaults, crypto lending, cosmos lending',
|
||||
},
|
||||
lend: {
|
||||
title: 'Mars Protocol V2 - Lend',
|
||||
description:
|
||||
"Lend your crypto assets or deploy advanced defi strategies including leveraged yield farming and leveraged staking in Mars' Farm vaults.",
|
||||
keywords:
|
||||
'leveraged yield farming, crypto yield, leveraged staking, mars farm vaults, crypto lending, cosmos lending',
|
||||
},
|
||||
borrow: {
|
||||
title: 'Mars Protocol V2 - Borrow',
|
||||
description:
|
||||
"Use your positions on Mars as collateral to borrow other assets. Then, use them for trading or put them to work in Mars' Farm Vaults.",
|
||||
keywords:
|
||||
'borrow crypto, defi, cosmos credit protocol, cosmos lending, decentralized finance, crypto collateral, mars farm vaults, crypto lending',
|
||||
},
|
||||
portfolio: {
|
||||
title: 'Mars Protocol V2 - Portfolio',
|
||||
description:
|
||||
'Review all of your positions on Mars at a glance and make adjustments to keep your Rover credit accounts healthy and productive.',
|
||||
keywords:
|
||||
'rover credit accounts, mars credit accounts, mars, defi, crypto lending, crypto yield',
|
||||
},
|
||||
council: {
|
||||
title: 'Mars Protocol V2 - Council',
|
||||
description:
|
||||
'Stake MARS token to ascend to the Martian Council and help govern key changes to the protocol.',
|
||||
keywords: 'martian council, mars governance, cosmos governance, mars voting, mars staking',
|
||||
},
|
||||
}
|
||||
|
||||
export default PAGE_METADATA
|
@ -1,7 +1,8 @@
|
||||
import Head from 'next/head'
|
||||
import { AppProps } from 'next/app'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import DefaultPageHead from 'components/DefaultPageHead'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.min.css'
|
||||
import 'styles/globals.css'
|
||||
|
||||
@ -15,32 +16,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Mars Protocol V2</title>
|
||||
<meta charSet='utf-8' />
|
||||
<link href='/favicon.svg' rel='icon' />
|
||||
<link href='/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
||||
<link href='/site.webmanifest' rel='manifest' />
|
||||
<link color='#dd5b65' href='/safari-pinned-tab.svg' rel='mask-icon' />
|
||||
<meta content='index,follow' name='robots' />
|
||||
<meta
|
||||
content="Lend, borrow and earn on the galaxy's most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies."
|
||||
name='description'
|
||||
/>
|
||||
<meta content='summary_large_image' name='twitter:card' />
|
||||
<meta content='@mars_protocol' name='twitter:site' />
|
||||
<meta content='@mars_protocol' name='twitter:creator' />
|
||||
<meta content='https://osmosis.marsprotocol.io' property='og:url' />
|
||||
<meta content='Mars Protocol V2 - Powered by Osmosis' property='og:title' />
|
||||
<meta
|
||||
content="Lend, borrow and earn on the galaxy's most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies."
|
||||
property='og:description'
|
||||
/>
|
||||
<meta content='https://osmosis.marsprotocol.io/banner.png' property='og:image' />
|
||||
<meta content='Mars Protocol V2' property='og:site_name' />
|
||||
<meta content='#ffffff' name='msapplication-TileColor' />
|
||||
<meta content='#ffffff' name='theme-color' />
|
||||
</Head>
|
||||
<DefaultPageHead />
|
||||
<div suppressHydrationWarning>
|
||||
{typeof window === 'undefined' ? null : <PageComponent {...pageProps} />}
|
||||
</div>
|
||||
|
@ -5,11 +5,13 @@ import Background from 'components/Background'
|
||||
import Footer from 'components/Footer'
|
||||
import DesktopHeader from 'components/Header/DesktopHeader'
|
||||
import ModalsContainer from 'components/Modals/ModalsContainer'
|
||||
import PageMetadata from 'components/PageMetadata'
|
||||
import Toaster from 'components/Toaster'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<PageMetadata />
|
||||
<Background />
|
||||
<DesktopHeader />
|
||||
<main
|
||||
|
27
yarn.lock
27
yarn.lock
@ -3524,6 +3524,13 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-helmet@^6.1.6":
|
||||
version "6.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.6.tgz#7d1afd8cbf099616894e8240e9ef70e3c6d7506d"
|
||||
integrity sha512-ZKcoOdW/Tg+kiUbkFCBtvDw0k3nD4HJ/h/B9yWxN4uDO8OkRksWTO+EL+z/Qu3aHTeTll3Ro0Cc/8UhwBCMG5A==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*":
|
||||
version "18.0.26"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917"
|
||||
@ -7852,6 +7859,21 @@ react-draggable@^4.4.5:
|
||||
clsx "^1.1.1"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-fast-compare@^3.1.1:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
|
||||
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
|
||||
|
||||
react-helmet@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
|
||||
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.7.2"
|
||||
react-fast-compare "^3.1.1"
|
||||
react-side-effect "^2.1.0"
|
||||
|
||||
react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
@ -7912,6 +7934,11 @@ react-router@6.11.2:
|
||||
dependencies:
|
||||
"@remix-run/router" "1.6.2"
|
||||
|
||||
react-side-effect@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a"
|
||||
integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==
|
||||
|
||||
react-smooth@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.2.tgz#0ef24213628cb13bf4305194a050e1db4302a3a1"
|
||||
|
Loading…
Reference in New Issue
Block a user