Skip to content

Commit

Permalink
Add the DataProvider page (#119)
Browse files Browse the repository at this point in the history
1.  Improves the store, data and pages infrastructure:

    1.1. Moves validation to `utils`
    1.2. Renames `repository-map` to `map`

2.  Improves general layout to fit the data provider page layout.

3.  Adds data-provider template for the individual data provider details
    page. The template includes:

    3.1. Pagination – brand new.
    3.2. Map card, improved from the existing component
    3.3. Claiming card (disabled at the moment)

4.  Adds an API module for the data provider data fetching. The API
    module's kept quite simple and based on the new APIv3.

5.  Upgrades @oacore/design to 'latest', flags the tag explicitly in
    the package.json so the library is always up to date.
  • Loading branch information
Joozty authored and viktor-yakubiv committed Jan 28, 2021
1 parent 6ed52fb commit 3b70e9a
Show file tree
Hide file tree
Showing 32 changed files with 1,058 additions and 376 deletions.
20 changes: 20 additions & 0 deletions api/data-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import request from './index'

// TODO: Move this to appropriate place when the APIv3 is ready
const API_V3_URL = process.env.API_URL.replace('/internal', '/v3')

const apiRequest = (url, ...args) => request(`${API_V3_URL}${url}`, ...args)

const fetchMetadata = async (id) => {
const { data } = await apiRequest(`/data-providers/${id}`)
return data
}

const fetchOutputs = async (id, searchParams) => {
const { data } = await apiRequest(`/data-providers/${id}/outputs`, {
searchParams,
})
return data
}

export { fetchMetadata, fetchOutputs }
8 changes: 7 additions & 1 deletion design.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
const path = require('path')

const icons = ['office-building']
const icons = [
'office-building',
'chevron-left',
'chevron-right',
'chevron-double-left',
'chevron-double-right',
]

const iconsRoot = path.join(
path.dirname(require.resolve('@mdi/svg/package.json')),
Expand Down
83 changes: 57 additions & 26 deletions hooks/use-sync-url-params-with-store/index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,64 @@
import { useEffect, useCallback } from 'react'
import { useEffect } from 'react'
import { useRouter } from 'next/router'

import useDebouncedEffect from '../use-debounced-effect'
import useDebouncedEffect from 'hooks/use-debounced-effect'

export const useSyncUrlParamsWithStore = (params) => {
export const useSyncUrlParamsWithStore = (paramsInstance) => {
const router = useRouter()
const setUrlParams = () => {
const newParams = Array.from(params.entries()).filter(
const { schema, ...params } = paramsInstance

const newParams = Object.entries(params).filter(
([, value]) => value != null
)
const query = Object.fromEntries(newParams)
router.push(
{ pathname: router.pathname, query },
{
// asPath contains url params so we need to get rid of them
// and replace them with the new ones.
pathname: new URL(router.route, window.location.origin).pathname,
query,
},
{ shallow: true }

const { pathname: pathnameUrl } = new URL(
router.asPath,
window.location.origin
)
const { pathname: pathnameAs } = new URL(
router.pathname,
window.location.origin
)

const urlParams = new URLSearchParams(newParams)

urlParams.sort()

if (
`${window.location.pathname}${window.location.search}` !==
`${pathnameUrl}?${urlParams.toString()}`
) {
const updateParams =
window.location.search === '' ? router.replace : router.push

updateParams(
{
pathname: `${pathnameAs}`,
query: { ...router.query, ...urlParams },
},
`${pathnameUrl}?${urlParams.toString()}`,

{ shallow: true }
)
}
}

const handleRouteChange = useCallback(
(nextUrl) => {
const { searchParams } = new URL(nextUrl, window.location.origin)
Array.from(searchParams.entries()).forEach(([key, value]) => {
const handleRouteChange = (nextUrl) => {
const { schema, ...params } = paramsInstance
const { searchParams } = new URL(nextUrl, window.location.origin)

const newParams = Array.from(searchParams.entries()).filter(
([key, value]) =>
// We want to compare like it because URLSearch params doesn't
// automatically convert param (e.g. size) to number.
// eslint-disable-next-line eqeqeq
if (params.has(key) && params[key] != value) params[key] = value
})
},
[params]
)
params[key] != value
)

if (newParams.length)
paramsInstance.changeParams(Object.fromEntries(newParams))
}

useEffect(() => {
router.events.on('routeChangeComplete', handleRouteChange)
Expand All @@ -43,9 +68,15 @@ export const useSyncUrlParamsWithStore = (params) => {
}, [])

// whenever any param changes in store reflect it to the URL
useDebouncedEffect(() => {
setUrlParams()
}, Array.from(params.values()))
useDebouncedEffect(
() => {
setUrlParams()
},
Object.keys(paramsInstance)
.sort()
.filter((key) => key !== 'schema')
.map((key) => paramsInstance[key])
)
}

export default useSyncUrlParamsWithStore
26 changes: 0 additions & 26 deletions main/hooks.js

This file was deleted.

63 changes: 63 additions & 0 deletions modules/authors-box/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState } from 'react'
import { Button, Link } from '@oacore/design'
import { classNames } from '@oacore/design/lib/utils'

import styles from './styles.module.css'

const AuthorLink = ({ name }) => (
<Link
href={`https://core.ac.uk/search?q=author:(${name})`}
className={styles.authorLink}
>
{name.replace(',', ' ')}
</Link>
)

const Authors = ({ authors }) => {
const [isExpanded, setIsExpanded] = useState(false)
if (authors.length <= 3) {
return authors.map((author, index) => (
<>
<AuthorLink name={author.name} />
{index < authors.length - 1 ? ', ' : ''}
</>
))
}

return (
<>
<AuthorLink name={authors[0].name} />,{' '}
<AuthorLink name={authors[1].name} />,{' '}
<span
className={classNames.use(
styles.moreAuthorsBox,
isExpanded && styles.moreAuthorsExpanded
)}
>
<Button
className={styles.showMore}
aria-controls="more-authors"
onClick={() => setIsExpanded(true)}
aria-hidden={isExpanded}
title="Show more authors"
>
+ {authors.length - 3} MORE
</Button>
<span
id="more-authors"
aria-hidden={!isExpanded}
className={styles.moreAuthors}
>
{authors.slice(2, -1).map((author) => (
<>
<AuthorLink name={author.name} />,{' '}
</>
))}
</span>
</span>
<AuthorLink name={authors[authors.length - 1].name} />
</>
)
}

export default Authors
36 changes: 36 additions & 0 deletions modules/authors-box/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.author-link {
color: var(--gray-800);
text-decoration: underline;
}

.show-more {
position: relative;
padding: 0 0.25rem;
margin: 0 0.25rem;
color: var(--gray-700);
text-transform: uppercase;
white-space: nowrap;
background: var(--gray-100);
}

.show-more::after {
position: absolute;
right: -0.05rem;
content: ',';
}

.more-authors {
display: none;
}

.more-authors-box {
display: contents;
}

.more-authors-expanded .show-more {
display: none;
}

.more-authors-expanded .more-authors {
display: inline-block;
}
2 changes: 0 additions & 2 deletions modules/repositories-map/index.jsx → modules/map/index.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React from 'react'
import dynamic from 'next/dynamic'
import { ProgressSpinner } from '@oacore/design'
import { classNames } from '@oacore/design/lib/utils'

import styles from './styles.module.css'

const Map = dynamic(() => import('./map'), {
ssr: false,
loading: () => <ProgressSpinner className={styles.loader} />,
})

const RepositoriesMap = React.memo(
Expand Down
43 changes: 18 additions & 25 deletions modules/repositories-map/map.jsx → modules/map/map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const markerIcon = L.icon({
popupAnchor: [0, -32],
})

const CustomMap = ({ dataProviders }) => {
const CustomMap = ({ locations }) => {
const mapContainerRef = useRef(null)
const map = useRef(null)

Expand All @@ -34,7 +34,10 @@ const CustomMap = ({ dataProviders }) => {
)

map.current = L.map(mapContainerRef.current, {
center: centerPosition,
center:
locations.length > 1
? centerPosition
: new L.LatLng(locations[0].latitude, locations[0].longitude),
zoom: 2,
maxBounds: [
[-90, -180],
Expand All @@ -52,40 +55,30 @@ const CustomMap = ({ dataProviders }) => {
icon: markerIcon,
})

dataProviders
.filter(
({ name, dataProviderLocation }) =>
dataProviderLocation != null &&
dataProviderLocation.latitude != null &&
dataProviderLocation.longitude != null &&
name
)
.forEach(({ id, name, dataProviderLocation }) => {
const marker = L.marker(
new L.LatLng(
dataProviderLocation.latitude,
dataProviderLocation.longitude
),
{
title: name,
icon: markerIcon,
}
)
locations.forEach(({ name, href, latitude, longitude }) => {
const marker = L.marker(new L.LatLng(latitude, longitude), {
title: name,
icon: markerIcon,
})

if (href) {
marker.bindPopup(
`<a
href="https://core.ac.uk/search?q=repositories.id:(${id})"
href={href}
target="_blank"
rel="noopener noreferrer"
>
${name}
</a>`
)
markers.addLayer(marker)
})
} else marker.bindPopup(name)

markers.addLayer(marker)
})

map.current.addLayer(markers)
return () => map.current.removeLayer(markers)
}, [dataProviders])
}, [locations])

return <div ref={mapContainerRef} className={styles.map} />
}
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions modules/search-layout/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Content from './content'
import Sidebar from './sidebar'
import Result from './result'
import Results from './results'
import Main from './main'
import Search from './search'
import ResultStats from './result-stats'

Search.Content = Content
Search.Sidebar = Sidebar
Search.Result = Result
Search.Results = Results
Search.Main = Main
Search.ResultStats = ResultStats

export default Search
12 changes: 12 additions & 0 deletions modules/search-layout/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react'
import { classNames } from '@oacore/design/lib/utils'

import styles from './styles.module.css'

const Main = ({ children, className, tag: Tag = 'div', ...restProps }) => (
<Tag className={classNames.use(styles.main, className)} {...restProps}>
{children}
</Tag>
)

export default Main
Loading

0 comments on commit 3b70e9a

Please sign in to comment.