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> <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> <style>
.loader { .loader {
display: block; display: block;

View File

@ -21,5 +21,5 @@
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#141414", "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' import styles from './index.module.scss'
interface AssetProps { interface AssetProps {
location: Location
match: { match: {
params: { params: {
did: string did: string

View File

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

View File

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

View File

@ -1,51 +1,59 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet'
import Content from '../atoms/Content' import Content from '../atoms/Content'
import styles from './Route.module.scss' import styles from './Route.module.scss'
import meta from '../../data/meta.json'
import Markdown from '../atoms/Markdown' 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 = ({ const Route = ({
title, title,
description, description,
image, image,
shareImage,
wide, wide,
children, children,
className className
}: { }: RouteProps) => {
title: string // Strip HTML from passed title
description?: string const titleSanitized = title.replace(/(<([^>]+)>)/gi, '')
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>
<article> return (
<header className={styles.header}> <div className={className}>
<Content wide={wide}> <Seo
<h1 className={styles.title}>{title}</h1> title={titleSanitized}
description={description}
shareImage={shareImage}
/>
{image && image} <article>
<header className={styles.header}>
<Content wide={wide}>
<h1 className={styles.title}>{titleSanitized}</h1>
{description && ( {image && image}
<Markdown
text={description}
className={styles.description}
/>
)}
</Content>
</header>
{children} {description && (
</article> <Markdown
</div> text={description}
) className={styles.description}
/>
)}
</Content>
</header>
{children}
</article>
</div>
)
}
export default Route export default Route

View File

@ -2,6 +2,7 @@
"title": "Commons", "title": "Commons",
"description": "A marketplace to find and publish open data sets in the Ocean Network.", "description": "A marketplace to find and publish open data sets in the Ocean Network.",
"company": "Ocean Protocol Foundation Ltd.", "company": "Ocean Protocol Foundation Ltd.",
"url": "https://commons.oceanprotocol.com",
"social": [ "social": [
{ {
"title": "Site", "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 React from 'react'
import { render } from '@testing-library/react' import { render } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import About from './About' import About from './About'
describe('About', () => { describe('About', () => {
it('renders without crashing', () => { it('renders without crashing', () => {
const { container } = render(<About />) const { container } = render(
<MemoryRouter>
<About />
</MemoryRouter>
)
expect(container.firstChild).toBeInTheDocument() expect(container.firstChild).toBeInTheDocument()
}) })
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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