SEO component

This commit is contained in:
Matthias Kretschmann 2019-06-18 00:05:40 +02:00
parent 30249447ce
commit b8113798b6
Signed by: m
GPG Key ID: 606EEEF3C479A91F
16 changed files with 176 additions and 90 deletions

View File

@ -12,39 +12,6 @@
<title>Commons</title>
<meta
content="A marketplace to find and publish open data sets in the Ocean Network."
name="description"
/>
<meta
content="https://commons.oceanprotocol.com/share.png"
name="image"
/>
<link href="https://commons.oceanprotocol.com" rel="canonical" />
<meta content="https://commons.oceanprotocol.com" property="og:url" />
<meta content="Commons" property="og:title" />
<meta
content="A marketplace to find and publish open data sets in the Ocean Network."
property="og:description"
/>
<meta
content="https://commons.oceanprotocol.com/share.png"
property="og:image"
/>
<meta content="summary_large_image" name="twitter:card" />
<meta content="@oceanprotocol" name="twitter:creator" />
<meta content="Commons" name="twitter:title" />
<meta
content="A marketplace to find and publish open data sets in the Ocean Network."
name="twitter:description"
/>
<meta
content="https://commons.oceanprotocol.com/share.png"
name="twitter:image"
/>
<style>
.loader {
display: block;

View File

@ -21,5 +21,5 @@
"start_url": ".",
"display": "standalone",
"theme_color": "#141414",
"background_color": "#141414"
"background_color": "#ffffff"
}

View File

@ -0,0 +1,71 @@
import React from 'react'
import Helmet from 'react-helmet'
import { withRouter, RouteComponentProps } from 'react-router-dom'
import meta from '../../data/meta.json'
import imageDefault from '../../img/share.png'
const MetaTags = ({
title,
description,
url,
image
}: {
title: string
description: string
url: string
image: string
}) => (
<Helmet defaultTitle={meta.title} titleTemplate={`%s - ${meta.title}`}>
<html lang="en" />
{title && <title>{title}</title>}
{/* General tags */}
<meta name="description" content={description} />
<meta name="image" content={image} />
<link rel="canonical" href={url} />
{/* OpenGraph tags */}
<meta property="og:url" content={url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
{/* Twitter Card tags */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@oceanprotocol" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
{/* Prevent search engine indexing except for live */}
{window.location.hostname !== 'commons.oceanprotocol.com' && (
<meta name="robots" content="noindex,nofollow" />
)}
</Helmet>
)
interface SeoProps extends RouteComponentProps {
title?: string
description?: string
shareImage?: string
}
const Seo = ({ title, description, shareImage, location }: SeoProps) => {
title = title || meta.title
description = description || meta.description
shareImage = shareImage || meta.url + imageDefault
const url = meta.url + location.pathname + location.search
return (
<MetaTags
title={title}
description={description}
url={url}
image={shareImage}
/>
)
}
export default withRouter(Seo)

View File

@ -10,7 +10,6 @@ import CategoryImage from '../../atoms/CategoryImage'
import styles from './index.module.scss'
interface AssetProps {
location: Location
match: {
params: {
did: string

View File

@ -4,6 +4,7 @@ import Channel from './Channel'
import { User } from '../../context'
import { createMemoryHistory } from 'history'
import { userMockConnected } from '../../../__mocks__/user-mock'
import { MemoryRouter } from 'react-router'
describe('Channel', () => {
it('renders without crashing', () => {
@ -11,12 +12,14 @@ describe('Channel', () => {
const { container } = render(
<User.Provider value={userMockConnected}>
<Channel
match={{
params: { channel: 'ai-for-good' }
}}
history={history}
/>
<MemoryRouter>
<Channel
match={{
params: { channel: 'ai-for-good' }
}}
history={history}
/>
</MemoryRouter>
</User.Provider>
)
expect(container.firstChild).toBeInTheDocument()

View File

@ -1,18 +1,25 @@
import React from 'react'
import { render } from '@testing-library/react'
import Route from './Route'
import { BrowserRouter as Router } from 'react-router-dom'
describe('Route', () => {
it('renders without crashing', () => {
const { container } = render(<Route title="Hello Title">Hello</Route>)
const { container } = render(
<Router>
<Route title="Hello Title">Hello</Route>
</Router>
)
expect(container.firstChild).toBeInTheDocument()
})
it('renders title & description', () => {
const { container } = render(
<Route title="Hello Title" description="Hello Description">
Hello
</Route>
<Router>
<Route title="Hello Title" description="Hello Description">
Hello
</Route>
</Router>
)
expect(container.querySelector('.title')).toHaveTextContent(
'Hello Title'

View File

@ -1,51 +1,59 @@
import React from 'react'
import Helmet from 'react-helmet'
import Content from '../atoms/Content'
import styles from './Route.module.scss'
import meta from '../../data/meta.json'
import Markdown from '../atoms/Markdown'
import Seo from '../atoms/Seo'
interface RouteProps {
title: string
description?: string
image?: any
shareImage?: string
children: any
wide?: boolean
className?: string
}
const Route = ({
title,
description,
image,
shareImage,
wide,
children,
className
}: {
title: string
description?: string
image?: any
children: any
wide?: boolean
className?: string
}) => (
<div className={className}>
<Helmet defaultTitle={meta.title} titleTemplate={`%s - ${meta.title}`}>
{/* Strip HTML from passed title */}
<title>{title.replace(/(<([^>]+)>)/gi, '')}</title>
{description && <meta name="description" content={description} />}
</Helmet>
}: RouteProps) => {
// Strip HTML from passed title
const titleSanitized = title.replace(/(<([^>]+)>)/gi, '')
<article>
<header className={styles.header}>
<Content wide={wide}>
<h1 className={styles.title}>{title}</h1>
return (
<div className={className}>
<Seo
title={titleSanitized}
description={description}
shareImage={shareImage}
/>
{image && image}
<article>
<header className={styles.header}>
<Content wide={wide}>
<h1 className={styles.title}>{titleSanitized}</h1>
{description && (
<Markdown
text={description}
className={styles.description}
/>
)}
</Content>
</header>
{image && image}
{children}
</article>
</div>
)
{description && (
<Markdown
text={description}
className={styles.description}
/>
)}
</Content>
</header>
{children}
</article>
</div>
)
}
export default Route

View File

@ -2,6 +2,7 @@
"title": "Commons",
"description": "A marketplace to find and publish open data sets in the Ocean Network.",
"company": "Ocean Protocol Foundation Ltd.",
"url": "https://commons.oceanprotocol.com",
"social": [
{
"title": "Site",

BIN
client/src/img/share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

View File

@ -1,10 +1,15 @@
import React from 'react'
import { render } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import About from './About'
describe('About', () => {
it('renders without crashing', () => {
const { container } = render(<About />)
const { container } = render(
<MemoryRouter>
<About />
</MemoryRouter>
)
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -1,5 +1,5 @@
import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import { MemoryRouter } from 'react-router'
import { render } from '@testing-library/react'
import Channels from './Channels'
import { User } from '../context'
@ -9,9 +9,9 @@ describe('Channels', () => {
it('renders without crashing', () => {
const { container } = render(
<User.Provider value={userMockConnected}>
<Router>
<MemoryRouter>
<Channels />
</Router>
</MemoryRouter>
</User.Provider>
)
expect(container.firstChild).toBeInTheDocument()

View File

@ -1,5 +1,6 @@
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import Faucet from './Faucet'
import { User } from '../context'
import { userMockConnected } from '../../__mocks__/user-mock'
@ -7,7 +8,9 @@ import { userMockConnected } from '../../__mocks__/user-mock'
const setup = () => {
const utils = render(
<User.Provider value={userMockConnected}>
<Faucet />
<MemoryRouter>
<Faucet />
</MemoryRouter>
</User.Provider>
)
const button = utils.getByText('Request Ether')
@ -21,7 +24,7 @@ const setup = () => {
describe('Faucet', () => {
it('renders without crashing', () => {
const { container } = render(<Faucet />)
const { container } = setup()
expect(container.firstChild).toBeInTheDocument()
})

View File

@ -1,11 +1,16 @@
import React from 'react'
import { render } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import { User } from '../context'
import History from './History'
describe('History', () => {
it('renders without crashing', () => {
const { container } = render(<History />)
const { container } = render(
<MemoryRouter>
<History />
</MemoryRouter>
)
expect(container.firstChild).toBeInTheDocument()
})
@ -27,7 +32,9 @@ describe('History', () => {
const { container } = render(
<User.Provider value={context}>
<History />
<MemoryRouter>
<History />
</MemoryRouter>
</User.Provider>
)
expect(container.querySelector('.message')).toBeInTheDocument()

View File

@ -1,10 +1,15 @@
import React from 'react'
import { render } from '@testing-library/react'
import NotFound from './NotFound'
import { MemoryRouter } from 'react-router'
describe('NotFound', () => {
it('renders without crashing', () => {
const { container } = render(<NotFound />)
const { container } = render(
<MemoryRouter>
<NotFound />
</MemoryRouter>
)
expect(container.firstChild).toBeInTheDocument()
})
})

View File

@ -1,4 +1,5 @@
import React from 'react'
import { MemoryRouter } from 'react-router'
import { render, fireEvent } from '@testing-library/react'
import Publish from '.'
import { User } from '../../context'
@ -8,7 +9,9 @@ describe('Publish', () => {
it('renders without crashing', () => {
const { container, getByText } = render(
<User.Provider value={userMockConnected}>
<Publish />
<MemoryRouter>
<Publish />
</MemoryRouter>
</User.Provider>
)
expect(container.firstChild).toBeInTheDocument()
@ -18,7 +21,9 @@ describe('Publish', () => {
it('next button works', () => {
const { getByText, getByLabelText, getByTestId } = render(
<User.Provider value={userMockConnected}>
<Publish />
<MemoryRouter>
<Publish />
</MemoryRouter>
</User.Provider>
)

View File

@ -1,10 +1,15 @@
import React from 'react'
import { render } from '@testing-library/react'
import Styleguide from './Styleguide'
import { MemoryRouter } from 'react-router'
describe('Styleguide', () => {
it('renders without crashing', () => {
const { container } = render(<Styleguide />)
const { container } = render(
<MemoryRouter>
<Styleguide />
</MemoryRouter>
)
expect(container.firstChild).toBeInTheDocument()
})
})