Skip to content

Commit

Permalink
GH-54 feat(reviews): read reviews data and use it to populate structu…
Browse files Browse the repository at this point in the history
…red data
  • Loading branch information
ciampo committed Jul 6, 2020
1 parent 925a370 commit 3921be3
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 73 deletions.
90 changes: 90 additions & 0 deletions components/blog-post/blog-post-reviews-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { useReducer, useContext, createContext } from 'react';

type Action =
| {
type: 'FETCH_INIT';
}
| {
type: 'FETCH_SUCCESS';
payload: {
reviewCount: number;
ratingValue: number;
documentId: null | string;
};
}
| {
type: 'FETCH_ERROR';
};
type Dispatch = (action: Action) => void;
type State = {
isLoading: boolean;
isError: boolean;
data: {
reviewCount: number;
ratingValue: number;
documentId: null | string;
};
};

const PostReviewsStateContext = createContext<State | undefined>(undefined);
const PostReviewsDispatchContext = createContext<Dispatch | undefined>(undefined);

const postReviewsContext = (state: State, action: Action): State => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isError: false,
isLoading: true,
};
case 'FETCH_SUCCESS':
return {
...state,
isError: false,
isLoading: false,
data: action.payload || [],
};
case 'FETCH_ERROR':
return {
...state,
isError: true,
isLoading: false,
};
}
};

const PostReviewsProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(postReviewsContext, {
isLoading: false,
isError: false,
data: {
reviewCount: 0,
ratingValue: -1,
documentId: null,
},
});

return (
<PostReviewsStateContext.Provider value={state}>
<PostReviewsDispatchContext.Provider value={dispatch}>
{children}
</PostReviewsDispatchContext.Provider>
</PostReviewsStateContext.Provider>
);
};

function usePostReviewsState(): State {
const context = useContext(PostReviewsStateContext);
if (context === undefined) {
throw new Error('usePostReviewsState must be used within a PostReviewsProvider');
}
return context;
}
function usePostReviewsDispatch(): Dispatch {
const context = useContext(PostReviewsDispatchContext);
if (context === undefined) {
throw new Error('usePostReviewsDispatch must be used within a PostReviewsProvider');
}
return context;
}
export { PostReviewsProvider, usePostReviewsState, usePostReviewsDispatch };
15 changes: 15 additions & 0 deletions components/blog-post/sanity-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import sanityClient from '@sanity/client';

const client = sanityClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || '',
dataset: 'production',
token: process.env.NEXT_PUBLIC_SANITY_READ_TOKEN || '',
// Always use the freshest data (as we're going to save it to disk)
useCdn: false,
});

export const getPostReviews = async (postId: string): Promise<{ reviews: number[] }> =>
await client.fetch(`*[_id == "${postId}"] {reviews[]}[0]`);

// export const submitPostReview = async (postId: string, rating: number): Promise<unknown> =>
// await client.patch(postId).setIfMissing({ reviews: [] }).append('reviews', [rating]).commit();
109 changes: 43 additions & 66 deletions pages/[categoryId]/[postId].tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { memo, useEffect, useMemo, useReducer } from 'react';
import React, { memo, useEffect, useMemo } from 'react';
import { GetStaticProps, GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import ReactGA from 'react-ga';
import sanityClient from '@sanity/client';

import PageMeta from '../../components/meta/PageMeta';
import AccessibleImage from '../../components/media/AccessibleImage';
Expand All @@ -13,6 +12,12 @@ import { AllSharingButtons } from '../../components/sharing/sharing-links';
import RichPortableText from '../../components/portable-text/RichPortableText';
import { ArticleContentContainer } from '../../components/layouts/Containers';
import { useNavVariantDispatch } from '../../components/nav/nav-variant-context';
import {
PostReviewsProvider,
usePostReviewsState,
usePostReviewsDispatch,
} from '../../components/blog-post/blog-post-reviews-context';
import { getPostReviews } from '../../components/blog-post/sanity-client';

import { joinUrl, postDateToHumanString } from '../../scripts/utils';

Expand All @@ -33,53 +38,6 @@ import {
} from '../../typings';

const BLOG_POST_PAGE_ROUTE = '/[categoryId]/[postId]';
const BLOG_POST_REVIEWS_QUERY = /* groq */ `*[_type == "tag"] {
_id,
name,
"slug": slug.current
}`;

const client = sanityClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || '',
dataset: 'production',
token: process.env.NEXT_PUBLIC_SANITY_READ_TOKEN || '',
// Always use the freshest data (as we're going to save it to disk)
useCdn: false,
});

type DataFetchState = {
isLoading: boolean;
isError: boolean;
data: unknown[];
};
type DataFetchAction = {
type: 'FETCH_INIT' | 'FETCH_SUCCESS' | 'FETCH_ERROR';
payload?: unknown[];
};

const dataFetchReducer = (state: DataFetchState, action: DataFetchAction): DataFetchState => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isError: false,
isLoading: true,
};
case 'FETCH_SUCCESS':
return {
...state,
isError: false,
isLoading: false,
data: action.payload || [],
};
case 'FETCH_ERROR':
return {
...state,
isError: true,
isLoading: false,
};
}
};

const BasicArticleEl: React.FC = memo((props) => <article {...props} />);
BasicArticleEl.displayName = 'memo(BasicArticleEl)';
Expand All @@ -92,16 +50,9 @@ type PageBlogPostProps = {
path: string;
structuredData: StructuredData[];
};
const BlogPost: NextComponentTypeWithLayout<PageBlogPostProps> = ({
blogPostData,
path,
structuredData,
}) => {
const [dataFetchState, dataFetchDispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: [],
});
const BlogPostWrapped: React.FC<PageBlogPostProps> = ({ blogPostData, path, structuredData }) => {
const postReviewsState = usePostReviewsState();
const postReviewsDispatch = usePostReviewsDispatch();
const setVariant = useNavVariantDispatch();
useEffect(() => {
setVariant('transparent');
Expand All @@ -114,22 +65,42 @@ const BlogPost: NextComponentTypeWithLayout<PageBlogPostProps> = ({
[blogPostData]
);

if (structuredData.length && postReviewsState.data.reviewCount > 0) {
structuredData[structuredData.length - 1].aggregateRating = {
'@type': 'AggregateRating',
reviewCount: postReviewsState.data.reviewCount,
ratingValue: postReviewsState.data.ratingValue,
};
}

useEffect(() => {
const fetchData = async (): Promise<void> => {
dataFetchDispatch({ type: 'FETCH_INIT' });
postReviewsDispatch({ type: 'FETCH_INIT' });

try {
const reviews = await client.fetch(BLOG_POST_REVIEWS_QUERY);
dataFetchDispatch({ type: 'FETCH_SUCCESS', payload: reviews });
const { reviews } = await getPostReviews(blogPostData._id);

let ratingValue = -1;
if (reviews.length) {
// average
ratingValue = reviews.reduce((acc, curr) => acc + curr, 0) / reviews.length;
}

postReviewsDispatch({
type: 'FETCH_SUCCESS',
payload: {
ratingValue,
reviewCount: reviews.length,
documentId: blogPostData._id,
},
});
} catch (error) {
dataFetchDispatch({ type: 'FETCH_ERROR' });
postReviewsDispatch({ type: 'FETCH_ERROR' });
}
};

fetchData();
}, []);

console.log(dataFetchState);
}, [postReviewsDispatch, blogPostData._id]);

return (
<>
Expand Down Expand Up @@ -261,6 +232,12 @@ const BlogPost: NextComponentTypeWithLayout<PageBlogPostProps> = ({
);
};

const BlogPost: NextComponentTypeWithLayout<PageBlogPostProps> = (props) => (
<PostReviewsProvider>
<BlogPostWrapped {...props} />
</PostReviewsProvider>
);

export const getStaticPaths: GetStaticPaths = async () => {
const blogPostRoute = routesConfig.find(({ route }) => route === '/[categoryId]/[postId]');

Expand Down
7 changes: 0 additions & 7 deletions scripts/structured-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,6 @@ export function generateRecipeStructuredData({
},
datePublished: blogPostData.datePublished,
// Missing: video
// Missing: review[]
// TODO
// aggregateRating: {
// '@type': 'AggregateRating',
// reviewCount: 3,
// ratingValue: 4.5,
// },
};
}

Expand Down

0 comments on commit 3921be3

Please sign in to comment.