Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.

Commit dcc745b

Browse files
author
Greg Rickaby
authored
Merge pull request #40 from WebDevStudios/feature/algolia
Feature/algolia
2 parents ed92e93 + bbd379c commit dcc745b

39 files changed

+1622
-30
lines changed

api/algolia/connector.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import algoliasearch from 'algoliasearch/lite'
2+
import getEnvVar from '@/functions/getEnvVar'
3+
4+
// Define env vars.
5+
export const algoliaIndexName = getEnvVar('ALGOLIA_INDEX_NAME', true)
6+
export const algoliaSearchKey = process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_KEY
7+
export const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID
8+
9+
const algoliaClient = algoliasearch(algoliaAppId, algoliaSearchKey)
10+
11+
export const searchClient = {
12+
search(requests) {
13+
if (requests.every(({params}) => !params.query)) {
14+
return Promise.resolve({
15+
results: requests.map(() => ({
16+
hits: [],
17+
nbHits: 0,
18+
nbPages: 0,
19+
page: 0,
20+
processingTimeMS: 0
21+
}))
22+
})
23+
}
24+
25+
return algoliaClient.search(requests)
26+
}
27+
}
28+
29+
export const searchResultsClient = {
30+
search(requests) {
31+
return algoliaClient.search(requests)
32+
}
33+
}

api/wordpress/_global/getPostTypeStaticProps.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {algoliaIndexName} from '@/api/algolia/connector'
12
import getPostTypeById from './getPostTypeById'
23
import getPostTypeArchive from './getPostTypeArchive'
34
import {addApolloState} from '@/api/apolloConfig'
@@ -50,6 +51,11 @@ export default async function getPostTypeStaticProps(
5051
props.error = false
5152
}
5253

54+
// Add Algolia env vars.
55+
props.algolia = {
56+
indexName: algoliaIndexName
57+
}
58+
5359
// Merge in query results as Apollo state.
5460
return addApolloState(apolloClient, {
5561
props,

components/common/AlgoliaProvider.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import PropTypes from 'prop-types'
2+
import {createContext} from 'react'
3+
4+
// Initialize Algolia context object.
5+
export const AlgoliaContext = createContext({
6+
algolia: null
7+
})
8+
9+
/**
10+
* Provide indexName env var.
11+
*
12+
* @param {Object} props The component attributes as props.
13+
* @return {Element} The child elements wrapped in a context provider.
14+
*/
15+
export default function AlgoliaProvider(props) {
16+
const {value, children} = props
17+
18+
return (
19+
<AlgoliaContext.Provider value={value}>{children}</AlgoliaContext.Provider>
20+
)
21+
}
22+
23+
AlgoliaProvider.propTypes = {
24+
indexName: PropTypes.string,
25+
children: PropTypes.object,
26+
value: PropTypes.object
27+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {searchResultsClient} from '@/api/algolia/connector'
2+
import cn from 'classnames'
3+
import PropTypes from 'prop-types'
4+
import {AlgoliaContext} from '@/components/common/AlgoliaProvider'
5+
import React, {useContext} from 'react'
6+
import {Configure, InstantSearch} from 'react-instantsearch-dom'
7+
import styles from './AlgoliaResults.module.css'
8+
import NoResults from './templates/NoResults'
9+
import SearchResults from './templates/SearchResults'
10+
11+
// TODO: Create Storybook for this component.
12+
13+
export default function AlgoliaResults({config}) {
14+
const {indexName} = useContext(AlgoliaContext)
15+
return (
16+
<section className={cn('container', styles.algoliaResults)}>
17+
{config.query !== '' && (
18+
<InstantSearch
19+
searchClient={config.query !== '' ? searchResultsClient : ''}
20+
indexName={indexName}
21+
>
22+
<Configure {...config} />
23+
<SearchResults indexName={indexName} />
24+
</InstantSearch>
25+
)}
26+
{config.query === '' && <NoResults query={config.query} />}
27+
</section>
28+
)
29+
}
30+
31+
AlgoliaResults.propTypes = {
32+
config: PropTypes.shape({
33+
query: PropTypes.string,
34+
hitsPerPage: PropTypes.number.isRequired
35+
})
36+
}
37+
38+
AlgoliaResults.defaultProps = {
39+
config: {
40+
query: '',
41+
hitsPerPage: 15
42+
}
43+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
.algoliaResults {
2+
@apply pt-20 pb-40;
3+
4+
& h1 {
5+
@apply mb-8;
6+
7+
font-size: 30px;
8+
}
9+
10+
/* Results Header */
11+
& .resultsHeader {
12+
@apply flex justify-between items-center;
13+
@apply mb-32;
14+
15+
& .total {
16+
& span {
17+
@apply font-semibold;
18+
}
19+
}
20+
}
21+
22+
/* Results Listing */
23+
& .results {
24+
@apply grid grid-cols-12 gap-24;
25+
26+
& > aside.sidebar {
27+
@apply col-span-3;
28+
29+
& .filterPanel {
30+
@apply mb-16;
31+
32+
& h3 {
33+
@apply font-bold mb-8;
34+
}
35+
}
36+
}
37+
38+
& > .content {
39+
@apply col-span-9;
40+
}
41+
42+
/* Hits/Results */
43+
& .aisHits {
44+
@apply text-center;
45+
46+
& > ul {
47+
@apply grid gap-12 grid-cols-1 text-left;
48+
}
49+
50+
/* Hit */
51+
& .hit {
52+
@apply p-20 border;
53+
54+
& h3 {
55+
@apply font-semibold;
56+
}
57+
58+
& .date {
59+
@apply italic mb-8;
60+
}
61+
}
62+
63+
& > button {
64+
@apply inline-block m-auto mt-44 border rounded px-24 py-8 transition-colors duration-200 ease-in-out;
65+
66+
&[disabled] {
67+
@apply opacity-25 cursor-not-allowed;
68+
}
69+
}
70+
}
71+
}
72+
73+
/* No results */
74+
& .empty {
75+
@apply py-20;
76+
}
77+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import PropTypes from 'prop-types'
2+
import CustomRefinementList from '../refinements/CustomRefinementList'
3+
4+
/**
5+
* Component for displaying Post Type facets.
6+
*/
7+
export default function Authors({refinements, defaultRefinement, className}) {
8+
const data = {
9+
title: 'Authors',
10+
attribute: 'post_author.display_name',
11+
showMore: true,
12+
limit: refinements.limit,
13+
translations: refinements.translations,
14+
defaultRefinement: defaultRefinement ? [defaultRefinement] : [],
15+
className: className
16+
}
17+
return <CustomRefinementList {...data} />
18+
}
19+
20+
Authors.propTypes = {
21+
refinements: PropTypes.shape({
22+
limit: PropTypes.number,
23+
translations: PropTypes.any
24+
}),
25+
defaultRefinement: PropTypes.string,
26+
className: PropTypes.string
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import PropTypes from 'prop-types'
2+
import CustomRefinementList from '../refinements/CustomRefinementList'
3+
4+
/**
5+
* Component for displaying Post Type facets.
6+
*/
7+
export default function PostType({refinements, defaultRefinement, className}) {
8+
const data = {
9+
title: 'Content Type',
10+
attribute: 'post_type_label',
11+
showMore: true,
12+
limit: refinements.limit,
13+
translations: refinements.translations,
14+
defaultRefinement: defaultRefinement ? [defaultRefinement] : [],
15+
className: className
16+
}
17+
return <CustomRefinementList {...data} />
18+
}
19+
20+
PostType.propTypes = {
21+
refinements: PropTypes.shape({
22+
limit: PropTypes.number,
23+
translations: PropTypes.any
24+
}),
25+
defaultRefinement: PropTypes.string,
26+
className: PropTypes.string
27+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import PropTypes from 'prop-types'
2+
import {SortBy} from 'react-instantsearch-dom'
3+
4+
/**
5+
* Component for displaying Post Type facets.
6+
*/
7+
export default function Sort({index, defaultRefinement}) {
8+
return (
9+
<SortBy
10+
items={[
11+
{
12+
value: index,
13+
label: '-- Sort by -- '
14+
},
15+
{
16+
value: `${index}_title_asc`,
17+
label: 'Alphabetical'
18+
},
19+
{
20+
value: `${index}_date_desc`,
21+
label: 'Most Recent'
22+
}
23+
]}
24+
defaultRefinement={
25+
defaultRefinement ? `${index}${defaultRefinement}` : index
26+
}
27+
/>
28+
)
29+
}
30+
31+
Sort.propTypes = {
32+
index: PropTypes.string.isRequired,
33+
defaultRefinement: PropTypes.string
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {default} from './AlgoliaResults'
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import PropTypes from 'prop-types'
2+
import {connectCurrentRefinements} from 'react-instantsearch-dom'
3+
import styles from '../AlgoliaResults.module.css'
4+
5+
/**
6+
* Custom display of Algolia [ClearRefinements](https://www.algolia.com/doc/api-reference/widgets/clear-refinements/react/) widget.
7+
*/
8+
function ClearRefinements({items, refine}) {
9+
return (
10+
<>
11+
{!!items?.length && (
12+
<button
13+
type="button"
14+
onClick={() => refine(items)}
15+
disabled={!items.length}
16+
className={styles.clearBtn}
17+
>
18+
Clear All Filters
19+
</button>
20+
)}
21+
</>
22+
)
23+
}
24+
25+
ClearRefinements.propTypes = {
26+
items: PropTypes.any.isRequired,
27+
refine: PropTypes.func
28+
}
29+
30+
const CustomClearRefinements = connectCurrentRefinements(ClearRefinements)
31+
export default CustomClearRefinements

0 commit comments

Comments
 (0)