From 7ea734d0adb12f288d6bf8452deaaa78860c2a52 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Wed, 20 Oct 2021 10:11:07 +0700 Subject: [PATCH 1/9] feat: get-blog-detail and relevant blogs --- framework/commerce/api/operations.ts | 57 +++++++++- framework/commerce/types/blogs.ts | 26 ++++- framework/commerce/types/product.ts | 3 + framework/vendure/api/index.ts | 14 ++- .../api/operations/get-all-blog-paths.ts | 53 +++++++++ .../vendure/api/operations/get-blog-detail.ts | 55 +++++++++ .../api/operations/get-relevant-blogs.ts | 54 +++++++++ framework/vendure/schema.d.ts | 25 ++++- .../utils/queries/get-all-blog-paths-query.ts | 11 ++ .../vendure/utils/queries/get-blog-detail.ts | 26 +++++ .../utils/queries/get-relevant-blogs.ts | 25 +++++ pages/blog/[slug].tsx | 105 +++++++++++++++++- .../common/Author/Author.module.scss | 4 +- .../RelevantBlogPosts/RelevantBlogPosts.tsx | 37 +----- .../blog-detail/BlogContent/BlogContent.tsx | 55 ++------- .../BlogDetailImg/BlogDetailImg.tsx | 7 +- 16 files changed, 459 insertions(+), 98 deletions(-) create mode 100644 framework/vendure/api/operations/get-all-blog-paths.ts create mode 100644 framework/vendure/api/operations/get-blog-detail.ts create mode 100644 framework/vendure/api/operations/get-relevant-blogs.ts create mode 100644 framework/vendure/utils/queries/get-all-blog-paths-query.ts create mode 100644 framework/vendure/utils/queries/get-blog-detail.ts create mode 100644 framework/vendure/utils/queries/get-relevant-blogs.ts diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index 898c322e3..34a549f2c 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -11,8 +11,12 @@ import type { } from '../types/product' import type { GetAllBlogsOperation, - GetFeaturedOperation + GetFeaturedOperation, + GetAllBlogPathsOperation, + GetBlogDetailOperation, + GetRelevantBlogsOperation } from '../types/blogs' + import type { APIProvider, CommerceAPI } from '.' import { GetAllCollectionsOperation } from '@commerce/types/collection'; @@ -32,7 +36,10 @@ export const OPERATIONS = [ 'getAllFacets', 'getAllCollections', 'getAllBlogs', - 'getFeaturedBlog' + 'getFeaturedBlog', + 'getAllBlogPaths', + 'getBlogDetail', + 'getRelevantBlogs' ] as const export const defaultOperations = OPERATIONS.reduce((ops, k) => { @@ -133,6 +140,21 @@ export type Operations

= { ): Promise } + + getAllBlogPaths: { + (opts: { + variables?: T['variables'] + config?: P['config'] + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + } & OperationOptions + ): Promise + } + getAllProducts: { (opts: { variables?: T['variables'] @@ -166,6 +188,22 @@ export type Operations

= { ): Promise } + getRelevantBlogs: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } + getFeaturedBlog: { (opts: { variables?: T['variables'] @@ -182,6 +220,21 @@ export type Operations

= { ): Promise } + getBlogDetail: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } getProduct: { diff --git a/framework/commerce/types/blogs.ts b/framework/commerce/types/blogs.ts index 9b6ce14c1..485388012 100644 --- a/framework/commerce/types/blogs.ts +++ b/framework/commerce/types/blogs.ts @@ -1,4 +1,4 @@ -import { SearchResultSortParameter } from "@framework/schema"; + import { Asset, BlogTranslation, Maybe, Product } from './../../vendure/schema.d'; export type BlogList = Node &{ @@ -15,13 +15,27 @@ export type BlogsType = { totalItems: number } export type GetAllBlogsOperation = { - data: { items: T['items'][] } + data: { items: T['items'][], totalItems: number } + variables: { + productId: number, + take?: number + skip?: number + } +} +export type GetRelevantBlogsOperation = { + data: { items: T['items'][], totalItems: number } variables: { take?: number skip?: number } } +export type GetBlogDetailOperation = { + data: T['items'], + variables: { + slug?: string + } +} export type GetFeaturedOperation = { data: { items: T['items'][] } @@ -29,4 +43,10 @@ export type GetFeaturedOperation = { take?: number skip?: number } -} \ No newline at end of file +} +export type GetAllBlogPathsOperation< +T extends BlogsType = BlogsType +> = { + data: { blogs: Pick[] } + variables: { first?: number } +} diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index e31f34df9..421100581 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -1,3 +1,4 @@ +import { BlogsType } from './blogs'; import { CurrencyCode } from './../../vendure/schema.d'; import { FacetValueFilterInput, LogicalOperator, SearchResultSortParameter } from "@framework/schema" @@ -110,6 +111,8 @@ export type GetAllProductPathsOperation< variables: { first?: number } } + + export type GetAllProductsOperation = { data: { products: T['product'][] } variables: { diff --git a/framework/vendure/api/index.ts b/framework/vendure/api/index.ts index edf67d14f..384f76b98 100644 --- a/framework/vendure/api/index.ts +++ b/framework/vendure/api/index.ts @@ -1,18 +1,21 @@ import type { CommerceAPIConfig } from '@commerce/api' import { CommerceAPI, getCommerceApi as commerceApi } from '@commerce/api' -import getAllFacets from './operations/get-all-facets' +import getAllBlogs from './operations/get-all-blogs' import getAllCollections from './operations/get-all-collection' +import getAllFacets from './operations/get-all-facets' import getAllPages from './operations/get-all-pages' import getAllProductPaths from './operations/get-all-product-paths' import getAllProducts from './operations/get-all-products' +import getBlogDetail from './operations/get-blog-detail' import getCustomerWishlist from './operations/get-customer-wishlist' +import getFeaturedBlog from './operations/get-featured-blog' import getPage from './operations/get-page' import getProduct from './operations/get-product' import getSiteInfo from './operations/get-site-info' +import getAllBlogPaths from './operations/get-all-blog-paths' +import getRelevantBlogs from './operations/get-relevant-blogs' import login from './operations/login' import fetchGraphqlApi from './utils/fetch-graphql-api' -import getAllBlogs from './operations/get-all-blogs' -import getFeaturedBlog from './operations/get-featured-blog' export interface VendureConfig extends CommerceAPIConfig {} @@ -46,7 +49,10 @@ const operations = { getAllFacets, getAllCollections, getAllBlogs, - getFeaturedBlog + getFeaturedBlog, + getBlogDetail, + getAllBlogPaths, + getRelevantBlogs } export const provider = { config, operations } diff --git a/framework/vendure/api/operations/get-all-blog-paths.ts b/framework/vendure/api/operations/get-all-blog-paths.ts new file mode 100644 index 000000000..cdfce36f7 --- /dev/null +++ b/framework/vendure/api/operations/get-all-blog-paths.ts @@ -0,0 +1,53 @@ +import { BlogList } from './../../schema.d'; +import { OperationContext,OperationOptions } from '@commerce/api/operations'; +import { BigcommerceConfig } from '../../../bigcommerce/api'; +import type { GetAllBlogPathsQuery,BlogTranslation } from '../../schema'; +import { getAllBlogPathsQuery } from '../../utils/queries/get-all-blog-paths-query'; +import { Provider } from '../index'; +import { GetAllBlogPathsOperation } from './../../../commerce/types/blogs'; + +export type GetAllBlogPathsResult = { + blogs: Array<{ node: { path: string } }> +} + +export default function getAllBlogPathsOperation({ + commerce, +}: OperationContext) { + async function getAllBlogPaths< + T extends GetAllBlogPathsOperation + >(opts?: { + variables?: T['variables'] + config?: BigcommerceConfig + }): Promise + + async function getAllBlogPaths( + opts: { + variables?: T['variables'] + config?: BigcommerceConfig + } & OperationOptions + ): Promise + + async function getAllBlogPaths({ + query = getAllBlogPathsQuery, + variables, + config: cfg, + }: { + query?: string + variables?: T['variables'] + config?: BigcommerceConfig + } = {}): Promise { + const config = commerce.getConfig(cfg) + + const { data } = await config.fetch(query, { + variables, + }) + + const blogs = data.blogs.items; + + return { + blogs: blogs?.map(val=>val.translations.map((p:BlogTranslation) => ({ path: `/${p.slug}` }))) + } + } + + return getAllBlogPaths +} diff --git a/framework/vendure/api/operations/get-blog-detail.ts b/framework/vendure/api/operations/get-blog-detail.ts new file mode 100644 index 000000000..6b8e92967 --- /dev/null +++ b/framework/vendure/api/operations/get-blog-detail.ts @@ -0,0 +1,55 @@ +import { OperationContext } from '@commerce/api/operations' +import { Provider, VendureConfig } from '..' +import { GetBlogQuery,BlogList } from '../../schema' +import { getBlogDetailQuery } from '../../utils/queries/get-blog-detail' + +export type BlogVariables = { + slug?: string, +} + +export default function getBlogDetailOperation({ + commerce, +}: OperationContext) { + async function getBlogDetail(opts?: { + variables?: BlogVariables + config?: Partial + preview?: boolean + }): Promise<{ blogDetail: BlogList}> + + async function getBlogDetail({ + query = getBlogDetailQuery, + variables: { ...vars } = {}, + config: cfg, + }: { + query?: string + variables?: BlogVariables + config?: Partial + preview?: boolean + } = {}): Promise<{ blogDetail: BlogList | any }> { + + const config = commerce.getConfig(cfg) + const variables = { + slug: vars.slug + } + const { data } = await config.fetch(query, { + variables, + }) + return { + blogDetail: { + id:data?.blog?.id, + title: data?.blog?.translations[0].title, + imageSrc: data?.blog?.featuredAsset?.preview ?? null, + slug: data?.blog?.translations[0]?.slug, + description: data?.blog?.translations[0]?.description, + isPublish: data?.blog?.isPublish, + isFeatured: data?.blog?.isFeatured, + authorName: data?.blog?.authorName, + authorAvatarAsset : data?.blog?.authorAvatarAsset?.preview, + createdAt: data?.blog?.createdAt, + relevantProducts: data?.blog?.relevantProducts.map(val=>val.id) + } + } + } + + return getBlogDetail +} diff --git a/framework/vendure/api/operations/get-relevant-blogs.ts b/framework/vendure/api/operations/get-relevant-blogs.ts new file mode 100644 index 000000000..3b9259c6a --- /dev/null +++ b/framework/vendure/api/operations/get-relevant-blogs.ts @@ -0,0 +1,54 @@ +import { OperationContext } from '@commerce/api/operations' +import { Provider, VendureConfig } from '..' +import { BlogList,GetRelevantBlogsQuery } from '../../schema' +import { getRelevantBlogsQuery } from '../../utils/queries/get-relevant-blogs' + +export type BlogVariables = { + productId?: number, +} + +export default function getRelevantBlogsOperation({ + commerce, +}: OperationContext) { + async function getRelevantBlogs(opts?: { + variables?: BlogVariables + config?: Partial + preview?: boolean + }): Promise<{ relevantBlogs: GetRelevantBlogsQuery[]}> + + async function getRelevantBlogs({ + query = getRelevantBlogsQuery, + variables: { ...vars } = {}, + config: cfg, + }: { + query?: string + variables?: BlogVariables + config?: Partial + preview?: boolean + } = {}): Promise<{ relevantBlogs: GetRelevantBlogsQuery[] | any[] }> { + + const config = commerce.getConfig(cfg) + const variables = { + productId: vars.productId, + } + const { data } = await config.fetch(query, { + variables, + }) + return { + relevantBlogs: data?.relevantBlogs?.items?.map((val:BlogList)=>({ + id: val.id, + title: val.translations[0]?.title, + imageSrc: val.featuredAsset?.preview ?? null, + slug: val.translations[0]?.slug, + description: val.translations[0]?.description, + isPublish: val.isPublish, + isFeatured: val.isFeatured, + authorName: val.authorName, + authorAvatarAsset : val.authorAvatarAsset?.preview, + createdAt: val.createdAt + })), + } + } + + return getRelevantBlogs +} diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 542af51fb..ab827f870 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -2337,10 +2337,19 @@ export type BlogList = Node &{ translations: Array authorName: Scalars['String'] authorAvatarAsset:Asset - relevantProducts: Product + relevantProducts: Product[] isFeatured: Boolean } +export type GetBlogQuery = { __typename?: 'Query' } & { + blog?: Maybe< + { __typename?: 'Blog' } & BlogList + > +} + + + + export type BlogTranslation = { __typename?: 'BlogTranslation' id: Scalars['ID'] @@ -2359,6 +2368,13 @@ export type GetAllBlogsQuery = PaginatedList & { 'totalItems' } } + +export type GetRelevantBlogsQuery = PaginatedList & { + relevantBlogs: { __typename?: 'BlogList' } & { + items: Array<{ __typename?: 'Blog' } & BlogList!>, + } +} + export type GetFeaturedBlogQuery = PaginatedList & { id:string, featuredBlogs: { __typename?: 'BlogList' } & { @@ -2367,6 +2383,7 @@ export type GetFeaturedBlogQuery = PaginatedList & { } } + export type QueryBlogs = { excludeBlogIds:Array, options: BlogListOptions @@ -3339,6 +3356,12 @@ export type GetAllProductPathsQuery = { __typename?: 'Query' } & { items: Array<{ __typename?: 'Product' } & Pick> } } + +export type GetAllBlogPathsQuery = { __typename?: 'Query' } & { + blogs: { __typename?: 'BlogList' } & { + items: Array<{ __typename?: 'Blog' } & Pick> + } +} export type GetAllProductsQueryVariables = Exact<{ input: SearchInput diff --git a/framework/vendure/utils/queries/get-all-blog-paths-query.ts b/framework/vendure/utils/queries/get-all-blog-paths-query.ts new file mode 100644 index 000000000..69d89ca36 --- /dev/null +++ b/framework/vendure/utils/queries/get-all-blog-paths-query.ts @@ -0,0 +1,11 @@ +export const getAllBlogPathsQuery = /* GraphQL */ ` +query getAllBlogPaths($excludeBlogIds: [ID]! = [],$options:BlogListOptions){ + blogs(excludeBlogIds: $excludeBlogIds,options: $options){ + items{ + translations { + slug + } + } + } +} +` diff --git a/framework/vendure/utils/queries/get-blog-detail.ts b/framework/vendure/utils/queries/get-blog-detail.ts new file mode 100644 index 000000000..a8b8fbe53 --- /dev/null +++ b/framework/vendure/utils/queries/get-blog-detail.ts @@ -0,0 +1,26 @@ +export const getBlogDetailQuery = /* GraphQL */ ` +query getBlog($slug: String ){ + blog(slug: $slug){ + id + isPublish + isFeatured + authorName + createdAt + authorAvatarAsset{ + preview + } + featuredAsset { + preview + } + translations { + title + slug + description + content + } + relevantProducts{ + id + } + } + } +` \ No newline at end of file diff --git a/framework/vendure/utils/queries/get-relevant-blogs.ts b/framework/vendure/utils/queries/get-relevant-blogs.ts new file mode 100644 index 000000000..91c8ebe16 --- /dev/null +++ b/framework/vendure/utils/queries/get-relevant-blogs.ts @@ -0,0 +1,25 @@ +export const getRelevantBlogsQuery = /* GraphQL */ ` +query relevantBlogs($productId: ID!){ + relevantBlogs(productId:$productId){ + items { + id + isPublish + isFeatured + authorName + createdAt + authorAvatarAsset{ + preview + } + featuredAsset { + preview + } + translations { + title + slug + description + content + } + } + } +} +` diff --git a/pages/blog/[slug].tsx b/pages/blog/[slug].tsx index 8aaca04a9..f09e91375 100644 --- a/pages/blog/[slug].tsx +++ b/pages/blog/[slug].tsx @@ -2,16 +2,111 @@ import { Layout, RelevantBlogPosts } from 'src/components/common'; import BlogContent from 'src/components/modules/blog-detail/BlogContent/BlogContent'; import BlogDetailImg from 'src/components/modules/blog-detail/BlogDetailImg/BlogDetailImg'; import { BLOGS_DATA_TEST } from 'src/utils/demo-data' +import { GetStaticPropsContext,GetStaticPathsContext } from 'next'; +import { PromiseWithKey } from 'src/utils/types.utils'; +import { getAllPromies } from 'src/utils/funtion.utils'; +import commerce from '@lib/api/commerce'; +import { BlogCardProps } from 'src/components/common/CardBlog/CardBlog'; +import { REVALIDATE_TIME } from 'src/utils/constanst.utils' +interface Props { + blog:{blogDetail?: BlogCardProps}, + relevantBlogs:{blogDetail?:BlogCardProps[]} +} +export default function BlogDetailPage({blog,relevantBlogs}:Props) { - -export default function BlogDetailPage() { + let date = new Date(blog?.blogDetail?.createdAt ?? '' ); + let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear(); + return ( <> - - - + + + {relevantBlogs.relevantBlogs?.length> 0 && } ) } + +export async function getStaticProps({ + params, + locale, + locales, + preview, +}: GetStaticPropsContext<{ slug: string }> ) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + // Blog detail + const blogDetailPromise = await commerce.getBlogDetail({ + variables: { slug: params!.slug }, + config, + preview, + }) + props.blog = blogDetailPromise; + + if (!blogDetailPromise) { + throw new Error(`Blog with slug '${params!.slug}' not found`) + } + + // Relevant Blogs + const relevantProductId = blogDetailPromise.blogDetail.relevantProducts?.[0]; + if (relevantProductId && blogDetailPromise.blogDetail.relevantProducts.length > 0) { + + const relevantBlogs = commerce.getRelevantBlogs({ + variables: { productId: relevantProductId }, + config, + preview, + }) + promisesWithKey.push({ key: 'relevantBlogs', promise: relevantBlogs}) + + }else { + props.relevantBlogs = []; + } + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + + console.log(props.relevantBlogs); + return { + props, + revalidate: REVALIDATE_TIME, + } + } catch (err) { + + } +} + +export async function getStaticPaths({ locales }: GetStaticPathsContext) { + + const { blogs } = await commerce.getAllBlogPaths() + + return { + paths: locales + ? locales.reduce((arr, locale) => { + blogs.forEach((blog: any) => { + arr.push(`/${locale}/blog/${blog.slug}`) + }) + return arr + }, []) + : blogs.map((product: any) => `/blog/${product.path}`), + fallback: 'blocking', + } +} + + + BlogDetailPage.Layout = Layout diff --git a/src/components/common/Author/Author.module.scss b/src/components/common/Author/Author.module.scss index 5a2dad969..037d988e7 100644 --- a/src/components/common/Author/Author.module.scss +++ b/src/components/common/Author/Author.module.scss @@ -5,7 +5,9 @@ .authorImage { width:3.2rem; height:3.2rem; - border-radius:3.2rem; + img{ + border-radius:3.2rem !important; + } div{ min-width:3.2rem !important; } diff --git a/src/components/common/RelevantBlogPosts/RelevantBlogPosts.tsx b/src/components/common/RelevantBlogPosts/RelevantBlogPosts.tsx index ab21590c3..c99cd83ac 100644 --- a/src/components/common/RelevantBlogPosts/RelevantBlogPosts.tsx +++ b/src/components/common/RelevantBlogPosts/RelevantBlogPosts.tsx @@ -14,40 +14,7 @@ interface RelevantProps { bgcolor?: "default" | "cream" } -const recipe:BlogCardProps[] = [ -{ - title: "Want to Lose Weight? Here are 10 DEBM Diet Guidelines for Beginners", - slug: 'have-a-nice-lunch', - description:"The DEBM diet stands for "+'"Delicious Happy Fun Diet"'+". This diet was popularized by Robert...", - imageSrc: "https://user-images.githubusercontent.com/46085455/133185783-8100ef4e-7a72-4dc1-bb12-2ca46b56b393.png", -},{ - title: "9 Ways to Make an Aloe Vera Mask at Home", - slug: 'have-a-nice-lunch', - description:"Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...", - imageSrc: "https://user-images.githubusercontent.com/46085455/133185911-df505d10-fdcd-4312-add3-7c62ad8af71e.png", -},{ - title: "Don't Buy Wrong, Here Are 7 Ways to Choose a Ripe Dragon Fruit", - slug: 'have-a-nice-lunch', - description:"Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...", - imageSrc: "https://user-images.githubusercontent.com/46085455/133185959-7ad75580-ca6d-4684-83d9-3f64500bbc97.png", -},{ - title: "Want to Lose Weight? Here are 10 DEBM Diet Guidelines for Beginners", - slug: 'have-a-nice-lunch', - description:"The DEBM diet stands for "+'"Delicious Happy Fun Diet"'+". This diet was popularized by Robert...", - imageSrc: "https://user-images.githubusercontent.com/46085455/133185783-8100ef4e-7a72-4dc1-bb12-2ca46b56b393.png", -},{ - title: "9 Ways to Make an Aloe Vera Mask at Home", - slug: 'have-a-nice-lunch', - description:"Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...", - imageSrc: "https://user-images.githubusercontent.com/46085455/133185911-df505d10-fdcd-4312-add3-7c62ad8af71e.png", -},{ - title: "Don't Buy Wrong, Here Are 7 Ways to Choose a Ripe Dragon Fruit", - slug: 'have-a-nice-lunch', - description:"Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...", - imageSrc: "https://user-images.githubusercontent.com/46085455/133185959-7ad75580-ca6d-4684-83d9-3f64500bbc97.png", -}] - - const RelevantBlogPosts = ({ data = recipe, itemKey="detail-relevant", title="Relevant Blog Posts", bgcolor = "default" }: RelevantProps) => { + const RelevantBlogPosts = ({ data , itemKey="detail-relevant", title="Relevant Blog Posts", bgcolor = "default" }: RelevantProps) => { return (

- + }
) diff --git a/src/components/modules/blog-detail/BlogContent/BlogContent.tsx b/src/components/modules/blog-detail/BlogContent/BlogContent.tsx index cb9d6b8d8..4a38ab5b1 100644 --- a/src/components/modules/blog-detail/BlogContent/BlogContent.tsx +++ b/src/components/modules/blog-detail/BlogContent/BlogContent.tsx @@ -9,57 +9,24 @@ import Link from 'next/link'; interface BlogContentProps { className?: string children?: any, + title?: string, + content?: string, + imgAuthor?: string, + date?: string, + authorName?: string, } -const BlogContent = ({}:BlogContentProps) => { +const BlogContent = ({title,date='',content,imgAuthor='',authorName='' }:BlogContentProps) => { + return ( <>
- -

The Best Sesame Soy Broccoli Salad

+ +

{title}

- +
- -

When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces. -
-
- - Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to. -
-
- This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing. -

- -
-
- -

What is broccoli salad

-
-

When you’re trying to eat healthier but want something more substantial than a leafy green salad, broccoli salad is there for you. I love the crunch and heft of broccoli, especially when it’s cut up into bite size spoonable pieces. -
-
- - Some people aren’t into raw broccoli, but I love it! I always go for the raw broccoli on those vegetable platters that seem to be at every potluck/party you go to. -
-
- This is a simple broccoli salad: you have the bulk of it, raw broccoli; crunchy red onions for a bit of acidity and raw crunch, craisins for sweetness, almonds for a nutty counter point; and a sweet and tangy soy-rice vinegar-sesame dressing. -

- -
-
- -

What about broccoli stems?

-
-

- You can eat broccoli stems. In fact, they are delicious. Just use a peeler to peel off the outsides and then trim the stalks into small 1/4”-1/2” cubes. -

-
- -
- -
- + {content}
diff --git a/src/components/modules/blog-detail/BlogDetailImg/BlogDetailImg.tsx b/src/components/modules/blog-detail/BlogDetailImg/BlogDetailImg.tsx index ed9b88a29..57180025a 100644 --- a/src/components/modules/blog-detail/BlogDetailImg/BlogDetailImg.tsx +++ b/src/components/modules/blog-detail/BlogDetailImg/BlogDetailImg.tsx @@ -5,7 +5,8 @@ import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbC import s from './BlogDetailImg.module.scss'; interface Props { className?: string - children?: any + children?: any, + imgSrc?:string } const CRUMBS =[ @@ -14,14 +15,14 @@ const CRUMBS =[ link:"/blog" } ] -const BlogDetailImg = ({}:Props ) => { +const BlogDetailImg = ({imgSrc = ''}:Props ) => { return ( <>
- +
) From 5b5aeec0d2684c7933c29b11a40a4a8ec490af15 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Wed, 20 Oct 2021 10:56:34 +0700 Subject: [PATCH 2/9] bug: fix show featured blogs --- framework/vendure/api/operations/get-all-blogs.ts | 6 +++++- pages/blog/[slug].tsx | 8 ++++---- pages/blogs.tsx | 14 ++++++++++++-- .../modules/blogs/BlogsList/BlogsList.tsx | 15 ++++++++++----- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/framework/vendure/api/operations/get-all-blogs.ts b/framework/vendure/api/operations/get-all-blogs.ts index d3998aa59..37857f488 100644 --- a/framework/vendure/api/operations/get-all-blogs.ts +++ b/framework/vendure/api/operations/get-all-blogs.ts @@ -6,7 +6,8 @@ import { getAllBlogsQuery } from '../../utils/queries/get-all-blog-query' export type BlogVariables = { excludeBlogIds?: string[], take?: number, - skip?:number + skip?:number, + isFeatured?:{eq?:Boolean}, } export default function getAllBlogsOperation({ @@ -35,6 +36,9 @@ export default function getAllBlogsOperation({ options: { take: vars.take, skip: vars.skip, + filter: { + isFeatured: vars.isFeatured + } }, } const { data } = await config.fetch(query, { diff --git a/pages/blog/[slug].tsx b/pages/blog/[slug].tsx index f09e91375..26325f5be 100644 --- a/pages/blog/[slug].tsx +++ b/pages/blog/[slug].tsx @@ -10,9 +10,9 @@ import { BlogCardProps } from 'src/components/common/CardBlog/CardBlog'; import { REVALIDATE_TIME } from 'src/utils/constanst.utils' interface Props { blog:{blogDetail?: BlogCardProps}, - relevantBlogs:{blogDetail?:BlogCardProps[]} + relevant:{relevantBlogs?:BlogCardProps[]} } -export default function BlogDetailPage({blog,relevantBlogs}:Props) { +export default function BlogDetailPage({blog,relevant}:Props) { let date = new Date(blog?.blogDetail?.createdAt ?? '' ); let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear(); @@ -27,7 +27,7 @@ export default function BlogDetailPage({blog,relevantBlogs}:Props) { authorName={blog?.blogDetail?.authorName} date={fullDate} /> - {relevantBlogs.relevantBlogs?.length> 0 && } + {relevant.relevantBlogs?.length> 0 && } ) } @@ -64,7 +64,7 @@ export async function getStaticProps({ config, preview, }) - promisesWithKey.push({ key: 'relevantBlogs', promise: relevantBlogs}) + promisesWithKey.push({ key: 'relevant', promise: relevantBlogs}) }else { props.relevantBlogs = []; diff --git a/pages/blogs.tsx b/pages/blogs.tsx index 7c3483a94..8f07e2d5b 100644 --- a/pages/blogs.tsx +++ b/pages/blogs.tsx @@ -46,7 +46,12 @@ export async function getStaticProps({ const {featuredBlogs} = await commerce.getFeaturedBlog({ variables: { - take: DEFAULT_BLOG_PAGE_SIZE + take: DEFAULT_BLOG_PAGE_SIZE, + filter: { + isFeatured: { + eq:true + } + } }, config, preview, @@ -57,7 +62,12 @@ export async function getStaticProps({ const blogsPromise = commerce.getAllBlogs({ variables: { excludeBlogIds: [idFeaturedBlog], - take: DEFAULT_BLOG_PAGE_SIZE + take: DEFAULT_BLOG_PAGE_SIZE, + filter: { + isFeatured: { + eq:false + } + } }, config, preview, diff --git a/src/components/modules/blogs/BlogsList/BlogsList.tsx b/src/components/modules/blogs/BlogsList/BlogsList.tsx index dd88d073b..a5f26b3f7 100644 --- a/src/components/modules/blogs/BlogsList/BlogsList.tsx +++ b/src/components/modules/blogs/BlogsList/BlogsList.tsx @@ -22,7 +22,12 @@ const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { const DEFAULT_BLOGS_ARGS = { excludeBlogIds: [idFeatured], options:{ - skip: 1, take: DEFAULT_BLOG_PAGE_SIZE + take: DEFAULT_BLOG_PAGE_SIZE, + filter: { + isFeatured: { + eq:false + } + } } } @@ -34,6 +39,7 @@ const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { + const onPageChange = (page:number) => { router.push({ pathname: ROUTE.BLOGS, @@ -65,7 +71,6 @@ const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { data = blogs } - return (
@@ -73,10 +78,10 @@ const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => {
{ - data?.map((product,index)=> { + data?.map((blog,index)=> { return( -
- {product.isPublish && } +
+ {blog.isPublish && !blog.isFeatured && }
) }) From a343079d343ef876da6ce00b069f556b1932a715 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Thu, 21 Oct 2021 09:49:45 +0700 Subject: [PATCH 3/9] bug: fix bug blog --- .../vendure/api/operations/get-all-blogs.ts | 9 +- .../api/operations/get-featured-blog.ts | 10 +- package.json | 2 +- pages/blogs.tsx | 11 +- .../modules/blogs/BlogsList/BlogsList.tsx | 100 ------------------ 5 files changed, 23 insertions(+), 109 deletions(-) delete mode 100644 src/components/modules/blogs/BlogsList/BlogsList.tsx diff --git a/framework/vendure/api/operations/get-all-blogs.ts b/framework/vendure/api/operations/get-all-blogs.ts index 37857f488..92a6ef9dc 100644 --- a/framework/vendure/api/operations/get-all-blogs.ts +++ b/framework/vendure/api/operations/get-all-blogs.ts @@ -7,7 +7,11 @@ export type BlogVariables = { excludeBlogIds?: string[], take?: number, skip?:number, - isFeatured?:{eq?:Boolean}, + filter?:{ + isFeatured?:{ + eq?:Boolean + } + }, } export default function getAllBlogsOperation({ @@ -35,9 +39,8 @@ export default function getAllBlogsOperation({ excludeBlogIds: vars.excludeBlogIds, options: { take: vars.take, - skip: vars.skip, filter: { - isFeatured: vars.isFeatured + isFeatured: vars.filter?.isFeatured } }, } diff --git a/framework/vendure/api/operations/get-featured-blog.ts b/framework/vendure/api/operations/get-featured-blog.ts index 8f26e4288..b6fbd8904 100644 --- a/framework/vendure/api/operations/get-featured-blog.ts +++ b/framework/vendure/api/operations/get-featured-blog.ts @@ -5,7 +5,12 @@ import { getFeatuedBlogQuery } from '../../utils/queries/get-featued-query' export type BlogVariables = { take?: number, - skip?:number + skip?:number, + filter?:{ + isFeatured?:{ + eq?:Boolean + } + }, } export default function getFeaturedBlogOperation({ @@ -31,6 +36,9 @@ export default function getFeaturedBlogOperation({ const variables = { options: { take: vars.take, + filter: { + isFeatured: vars.filter?.isFeatured + } }, } const { data } = await config.fetch(query, { diff --git a/package.json b/package.json index 8474be667..acca8bea2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nextjs-commerce", "version": "1.0.0", "scripts": { - "dev": "NODE_OPTIONS='--inspect' PORT=3005 next dev", + "dev": "set NODE_OPTIONS='--inspect' && set PORT=3005 && next dev", "dev-windows": "set NODE_OPTIONS='--inspect' && set PORT=3005 && next dev", "build": "next build", "start": "PORT=3005 next start", diff --git a/pages/blogs.tsx b/pages/blogs.tsx index 8f07e2d5b..ad002445a 100644 --- a/pages/blogs.tsx +++ b/pages/blogs.tsx @@ -11,23 +11,26 @@ interface Props { blogsResult: { blogs?: BlogCardProps[],featuredBlog?: BlogCardProps[] }, totalItems: number } -export default function BlogsPage({blogsResult,totalItems}:Props) { +export default function BlogsPage( { blogsResult, totalItems }:Props ) { + let date = new Date(blogsResult.featuredBlog?.[0]?.createdAt ?? '' ); let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear(); - + return( <> + { (blogsResult.featuredBlog?.length !=0 ) && + } ) @@ -58,7 +61,7 @@ export async function getStaticProps({ }) // Blogs - const idFeaturedBlog = featuredBlogs[0].id; + const idFeaturedBlog = featuredBlogs?.[0]?.id; const blogsPromise = commerce.getAllBlogs({ variables: { excludeBlogIds: [idFeaturedBlog], diff --git a/src/components/modules/blogs/BlogsList/BlogsList.tsx b/src/components/modules/blogs/BlogsList/BlogsList.tsx deleted file mode 100644 index a5f26b3f7..000000000 --- a/src/components/modules/blogs/BlogsList/BlogsList.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useRouter } from 'next/router' -import React, { useEffect, useState,useRef } from 'react' -import CardBlog, { BlogCardProps } from 'src/components/common/CardBlog/CardBlog' -import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon' -import { DEFAULT_BLOG_PAGE_SIZE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' -import s from "./BlogsList.module.scss" -import { QueryBlogs } from '@framework/schema' -import { useGetBlogList } from 'src/components/hooks/blog' -import { getPageFromQuery } from 'src/utils/funtion.utils' -import { ListProductCardSkeleton } from 'src/components/common' - -interface BlogsListProps { - blogList?: BlogCardProps[], - total?: number, - idFeatured?:string -} - - - -const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { - - const DEFAULT_BLOGS_ARGS = { - excludeBlogIds: [idFeatured], - options:{ - take: DEFAULT_BLOG_PAGE_SIZE, - filter: { - isFeatured: { - eq:false - } - } - } - } - - const router = useRouter(); - const [initialQueryFlag, setInitialQueryFlag] = useState(true) - - const [optionQueryBlog, setOptionQueryBlog] = useState(DEFAULT_BLOGS_ARGS) - const { blogs, totalItems, loading } = useGetBlogList(optionQueryBlog); - - - - - const onPageChange = (page:number) => { - router.push({ - pathname: ROUTE.BLOGS, - query: { - ...router.query, - [QUERY_KEY.PAGE]: page - } - }, - undefined, { shallow: true } - ) - } - - // skip - const firstRender = useRef(true); - useEffect(() => { - firstRender.current = false; - const query = { ...DEFAULT_BLOGS_ARGS } as QueryBlogs; - const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string); - query.options.skip = page * DEFAULT_BLOG_PAGE_SIZE; - setOptionQueryBlog(query); - setInitialQueryFlag(false); - },[router.query]) - - - let data; - if(initialQueryFlag == true){ - data = blogList; - }else{ - data = blogs - } - - return ( -
-
- {(!initialQueryFlag && loading && !blogs) && } -
- - { - data?.map((blog,index)=> { - return( -
- {blog.isPublish && !blog.isFeatured && } -
- ) - }) - } -
-
- -
-
-
- ) -} - -export default BlogsList - - From 69a37dca75f70d1ed221a386be3becc38d44d58e Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Thu, 21 Oct 2021 10:20:34 +0700 Subject: [PATCH 4/9] bug: fix bug blogs --- .../vendure/api/operations/get-all-blogs.ts | 2 +- .../api/operations/get-featured-blog.ts | 2 +- .../modules/blogs/BlogsList/BlogsList.tsx | 93 +++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/components/modules/blogs/BlogsList/BlogsList.tsx diff --git a/framework/vendure/api/operations/get-all-blogs.ts b/framework/vendure/api/operations/get-all-blogs.ts index 92a6ef9dc..1bc0b556a 100644 --- a/framework/vendure/api/operations/get-all-blogs.ts +++ b/framework/vendure/api/operations/get-all-blogs.ts @@ -57,7 +57,7 @@ export default function getAllBlogsOperation({ isPublish: val.isPublish, isFeatured: val.isFeatured, authorName: val.authorName, - authorAvatarAsset : val.authorAvatarAsset?.preview, + authorAvatarAsset : val.authorAvatarAsset?.preview ?? null, createdAt: val.createdAt })), totalItems: data?.blogs?.totalItems || null diff --git a/framework/vendure/api/operations/get-featured-blog.ts b/framework/vendure/api/operations/get-featured-blog.ts index b6fbd8904..436d3cf7b 100644 --- a/framework/vendure/api/operations/get-featured-blog.ts +++ b/framework/vendure/api/operations/get-featured-blog.ts @@ -54,7 +54,7 @@ export default function getFeaturedBlogOperation({ isPublish: val.isPublish, isFeatured: val.isFeatured, authorName: val.authorName, - authorAvatarAsset : val.authorAvatarAsset?.preview, + authorAvatarAsset : val.authorAvatarAsset?.preview ?? null, createdAt: val.createdAt })) } diff --git a/src/components/modules/blogs/BlogsList/BlogsList.tsx b/src/components/modules/blogs/BlogsList/BlogsList.tsx new file mode 100644 index 000000000..1fe0c2f83 --- /dev/null +++ b/src/components/modules/blogs/BlogsList/BlogsList.tsx @@ -0,0 +1,93 @@ +import { useRouter } from 'next/router' +import React, { useEffect, useState,useRef } from 'react' +import CardBlog, { BlogCardProps } from 'src/components/common/CardBlog/CardBlog' +import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon' +import { DEFAULT_BLOG_PAGE_SIZE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import s from "./BlogsList.module.scss" +import { QueryBlogs } from '@framework/schema' +import { useGetBlogList } from 'src/components/hooks/blog' +import { getPageFromQuery } from 'src/utils/funtion.utils' +import { ListProductCardSkeleton } from 'src/components/common' + +interface BlogsListProps { + blogList?: BlogCardProps[], + total?: number, + idFeatured?:string +} + + + +const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { + + const DEFAULT_BLOGS_ARGS = { + excludeBlogIds: [idFeatured], + options:{ + skip: 1, take: DEFAULT_BLOG_PAGE_SIZE + } + } + + const router = useRouter(); + const [initialQueryFlag, setInitialQueryFlag] = useState(true) + + const [optionQueryBlog, setOptionQueryBlog] = useState(DEFAULT_BLOGS_ARGS) + const { blogs, totalItems, loading } = useGetBlogList(optionQueryBlog); + + + + const onPageChange = (page:number) => { + router.push({ + pathname: ROUTE.BLOGS, + query: { + ...router.query, + [QUERY_KEY.PAGE]: page + } + }, + undefined, { shallow: true } + ) + } + + // skip + const firstRender = useRef(true); + useEffect(() => { + firstRender.current = false; + const query = { ...DEFAULT_BLOGS_ARGS } as QueryBlogs; + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string); + query.options.skip = page * DEFAULT_BLOG_PAGE_SIZE; + setOptionQueryBlog(query); + setInitialQueryFlag(false); + },[router.query]) + + + let data; + if(initialQueryFlag == true){ + data = blogList; + }else{ + data = blogs + } + + + return ( +
+
+ {(!initialQueryFlag && loading && !blogs) && } +
+ + { + data?.map((product,index)=> { + return( +
+ {product.isPublish && } +
+ ) + }) + } +
+
+ +
+
+
+ ) +} + +export default BlogsList From e540208ff33f2d42ac11040293eddc83c229540f Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Thu, 21 Oct 2021 11:54:05 +0700 Subject: [PATCH 5/9] feat: get-all-recipes --- framework/commerce/api/operations.ts | 22 +++++- framework/commerce/types/recipes.ts | 32 +++++++++ .../vendure/api/operations/get-all-recipe.ts | 68 +++++++++++++++++++ framework/vendure/schema.d.ts | 41 ++++++++++- pages/recipes.tsx | 53 ++++++++++++++- 5 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 framework/commerce/types/recipes.ts create mode 100644 framework/vendure/api/operations/get-all-recipe.ts diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index 34a549f2c..1fce40dc5 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -16,6 +16,9 @@ import type { GetBlogDetailOperation, GetRelevantBlogsOperation } from '../types/blogs' +import type { + GetAllRecipesOperation +} from '../types/recipes' import type { APIProvider, CommerceAPI } from '.' import { GetAllCollectionsOperation } from '@commerce/types/collection'; @@ -39,7 +42,8 @@ export const OPERATIONS = [ 'getFeaturedBlog', 'getAllBlogPaths', 'getBlogDetail', - 'getRelevantBlogs' + 'getRelevantBlogs', + 'getAllRecipes' ] as const export const defaultOperations = OPERATIONS.reduce((ops, k) => { @@ -188,6 +192,22 @@ export type Operations

= { ): Promise } + getAllRecipes: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } + getRelevantBlogs: { (opts: { variables?: T['variables'] diff --git a/framework/commerce/types/recipes.ts b/framework/commerce/types/recipes.ts new file mode 100644 index 000000000..ce143c434 --- /dev/null +++ b/framework/commerce/types/recipes.ts @@ -0,0 +1,32 @@ +import { Asset, BlogTranslation, Maybe, Product } from './../../vendure/schema.d'; + +export type RecipeList = Node &{ + id: string + featuredAsset?: Maybe + isPublish:Boolean + translations: Array + authorName: string + authorAvatarAsset:Array + relevantProducts: Product + link:String + minutes:Number + people:Number +} +export type RecipesType = { + items: RecipeList + totalItems: number +} + +export enum SortType { + ASC = 'ASC', + DESC = 'DESC', +} +export type GetAllRecipesOperation = { + data: { items: T['items'][], totalItems: number } + variables: { + take?: number + sort?: { + id: SortType + } + } +} \ No newline at end of file diff --git a/framework/vendure/api/operations/get-all-recipe.ts b/framework/vendure/api/operations/get-all-recipe.ts new file mode 100644 index 000000000..78326308e --- /dev/null +++ b/framework/vendure/api/operations/get-all-recipe.ts @@ -0,0 +1,68 @@ +import { OperationContext } from '@commerce/api/operations' +import { Provider, VendureConfig } from '..' +import { GetAllRecipesQuery,BlogList } from '../../schema' +import { getAllBlogsQuery } from '../../utils/queries/get-all-blog-query' + +export type BlogVariables = { + excludeBlogIds?: string[], + take?: number, + skip?:number, + filter?:{ + isFeatured?:{ + eq?:Boolean + } + }, +} + +export default function getAllRecipesOperation({ + commerce, +}: OperationContext) { + async function getAllRecipes(opts?: { + variables?: BlogVariables + config?: Partial + preview?: boolean + }): Promise<{ recipes: GetAllRecipesQuery[],totalItems:number }> + + async function getAllRecipes({ + query = getAllBlogsQuery, + variables: { ...vars } = {}, + config: cfg, + }: { + query?: string + variables?: BlogVariables + config?: Partial + preview?: boolean + } = {}): Promise<{ recipes: GetAllRecipesQuery[] | any[] ,totalItems?:number }> { + + const config = commerce.getConfig(cfg) + const variables = { + excludeBlogIds: vars.excludeBlogIds, + options: { + take: vars.take, + filter: { + isFeatured: vars.filter?.isFeatured + } + }, + } + const { data } = await config.fetch(query, { + variables, + }) + return { + recipes: data?.blogs?.items?.map((val:BlogList)=>({ + id: val.id, + title: val.translations[0]?.title, + imageSrc: val.featuredAsset?.preview ?? null, + slug: val.translations[0]?.slug, + description: val.translations[0]?.description, + isPublish: val.isPublish, + isFeatured: val.isFeatured, + authorName: val.authorName, + authorAvatarAsset : val.authorAvatarAsset?.preview ?? null, + createdAt: val.createdAt + })), + totalItems: data?.blogs?.totalItems || null + } + } + + return getAllRecipes +} diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index bde35416c..417fee519 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -2384,6 +2384,37 @@ export type BlogList = Node &{ isFeatured: Boolean } +export type RecipeList = Node &{ + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + featuredAsset?: Maybe + isPublish:Boolean + translations: Array + authorName: Scalars['String'] + authorAvatarAsset:Asset + relevantProducts: Product[] + link:String + minutes:Number + people:Number +} + + +export type RecipeTranslation = { + __typename?: 'BlogTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + title: Scalars['String'] + slug: Scalars['String'] + description: Scalars['String'] + content: Scalars['String'] + Ingredients:Scalars['String'] + Preparation:Scalars['String'] +} + + export type GetBlogQuery = { __typename?: 'Query' } & { blog?: Maybe< { __typename?: 'Blog' } & BlogList @@ -2391,8 +2422,6 @@ export type GetBlogQuery = { __typename?: 'Query' } & { } - - export type BlogTranslation = { __typename?: 'BlogTranslation' id: Scalars['ID'] @@ -2412,6 +2441,14 @@ export type GetAllBlogsQuery = PaginatedList & { } } +export type GetAllRecipesQuery = PaginatedList & { + recipes: { __typename?: 'RecipeList' } & { + items: Array<{ __typename?: 'Recipe' } & RecipeList!>, + 'totalItems' + } +} + + export type GetRelevantBlogsQuery = PaginatedList & { relevantBlogs: { __typename?: 'BlogList' } & { items: Array<{ __typename?: 'Blog' } & BlogList!>, diff --git a/pages/recipes.tsx b/pages/recipes.tsx index a4acece13..88dd8a18e 100644 --- a/pages/recipes.tsx +++ b/pages/recipes.tsx @@ -1,8 +1,11 @@ import { Layout } from 'src/components/common'; import RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner'; import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList'; - - +import { GetStaticPropsContext } from 'next'; +import { PromiseWithKey } from 'src/utils/types.utils'; +import { DEFAULT_BLOG_PAGE_SIZE } from "src/utils/constanst.utils"; +import commerce from '@lib/api/commerce'; +import { getAllPromies } from 'src/utils/funtion.utils'; export default function RecipeListPage() { return ( <> @@ -12,4 +15,50 @@ export default function RecipeListPage() { ) } +export async function getStaticProps({ + preview, + locale, + locales, +}: GetStaticPropsContext) { + + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any; + + + const blogsPromise = commerce.getAllBlogs({ + variables: { + excludeBlogIds: [], + take: DEFAULT_BLOG_PAGE_SIZE, + filter: { + isFeatured: { + eq:false + } + } + }, + config, + preview, + }) + promisesWithKey.push({ key: 'blogsResult', promise: blogsPromise }) + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + console.log(props); + return { + props, + revalidate: 60 + } + } catch (err) { + + } +} + + RecipeListPage.Layout = Layout From eb6b1e3f9b8b0e5f36790fdc16359fd020b9dab1 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Thu, 21 Oct 2021 14:29:39 +0700 Subject: [PATCH 6/9] :sparkles: feat: get recipe-list :%s --- framework/commerce/types/recipes.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/commerce/types/recipes.ts b/framework/commerce/types/recipes.ts index ce143c434..cc15e9a57 100644 --- a/framework/commerce/types/recipes.ts +++ b/framework/commerce/types/recipes.ts @@ -17,16 +17,15 @@ export type RecipesType = { totalItems: number } -export enum SortType { +export enum SortOrder { ASC = 'ASC', DESC = 'DESC', } export type GetAllRecipesOperation = { data: { items: T['items'][], totalItems: number } variables: { + excludeBlogIds:Array, take?: number - sort?: { - id: SortType - } + id?: SortOrder } } \ No newline at end of file From 7ba17af820c283a9042dc045f37676cae17e73dc Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Thu, 21 Oct 2021 14:34:34 +0700 Subject: [PATCH 7/9] :bug: bug: fix type of recipes :%s --- framework/commerce/types/recipes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/commerce/types/recipes.ts b/framework/commerce/types/recipes.ts index cc15e9a57..99f0fab6f 100644 --- a/framework/commerce/types/recipes.ts +++ b/framework/commerce/types/recipes.ts @@ -21,6 +21,7 @@ export enum SortOrder { ASC = 'ASC', DESC = 'DESC', } + export type GetAllRecipesOperation = { data: { items: T['items'][], totalItems: number } variables: { From 6c5c4dd4c2c5714c35882d6b25744c71cd6a6330 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Thu, 21 Oct 2021 16:33:24 +0700 Subject: [PATCH 8/9] :sparkles: feat: add blog card :%s --- .../ListBlogCardSkeleton.module.scss | 18 +++++++++++++++++ .../ListBlogCardSkeleton.tsx | 20 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.module.scss create mode 100644 src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.tsx diff --git a/src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.module.scss b/src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.module.scss new file mode 100644 index 000000000..2f6541e7e --- /dev/null +++ b/src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.module.scss @@ -0,0 +1,18 @@ +.listBlogCardSkeleton { + display: flex; + overflow: hidden; + width: 90%; + justify-content: space-between; + div{ + min-width: 32rem; + } + + &.wrap { + flex-wrap: wrap; + overflow: unset; + > div { + margin-bottom: 1.6rem; + } + } + +} diff --git a/src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.tsx b/src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.tsx new file mode 100644 index 000000000..015a97374 --- /dev/null +++ b/src/components/common/ListBlogCardSkeleton/ListBlogCardSkeleton.tsx @@ -0,0 +1,20 @@ +import classNames from 'classnames' +import { ProductCardSkeleton } from '..' +import s from './ListBlogCardSkeleton.module.scss' + +type Props = { + count?: number + isWrap?: boolean, +} +const ListBlogCardSkeleton = ({ count = 3, isWrap }: Props) => { + + return ( +

+ { + Array.from(Array(count).keys()).map(item => ) + } +
+ ) +} + +export default ListBlogCardSkeleton From a9f0ce7a48cef8524e48bd65669eae408a3b9583 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Fri, 22 Oct 2021 17:12:21 +0700 Subject: [PATCH 9/9] :sparkles: feat: get recipe list :%s --- framework/commerce/api/operations.ts | 46 +- framework/commerce/types/recipes.ts | 13 + framework/vendure/api/index.ts | 7 +- .../api/operations/get-all-recipe-paths.ts | 53 +++ .../vendure/api/operations/get-all-recipes.ts | 23 +- .../api/operations/get-recipe-detail.ts | 98 ++++ framework/vendure/schema.d.ts | 39 ++ framework/vendure/utils/normalize.ts | 16 +- pages/blogs.tsx | 2 +- pages/products.tsx | 1 - pages/recipe/[slug].tsx | 92 +++- pages/recipes.tsx | 14 +- .../common/RecipeDetail/RecipeDetail.tsx | 9 +- .../RecipeDetailInfo/RecipeDetailInfo.tsx | 10 +- .../RecommendedRecipes/RecommendedRecipes.tsx | 4 +- src/components/hooks/recipe/index.ts | 1 + .../hooks/recipe/useGetRecipeList.tsx | 18 + .../modules/blogs/BlogsList/BlogsList.tsx | 2 +- .../recipes-list/RecipesList/RecipesList.tsx | 449 +++++++++++------- src/utils/types.utils.ts | 4 + 20 files changed, 676 insertions(+), 225 deletions(-) create mode 100644 framework/vendure/api/operations/get-all-recipe-paths.ts create mode 100644 framework/vendure/api/operations/get-recipe-detail.ts create mode 100644 src/components/hooks/recipe/index.ts create mode 100644 src/components/hooks/recipe/useGetRecipeList.tsx diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index e240fa8c5..afd725081 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -1,7 +1,8 @@ +import { GetAllRecipePathsOperation, GetRecipeDetailOperation,GetAllRecipesOperation } from './../types/recipes'; import { GetAllFacetsOperation } from './../types/facet'; import type { ServerResponse } from 'http' import type { LoginOperation } from '../types/login' -import type { GetAllPagesOperation, GetPageOperation } from '../types/page' +import type { GetAllPagesOperation } from '../types/page' import type { GetSiteInfoOperation } from '../types/site' import type { GetCustomerWishlistOperation } from '../types/wishlist' import type { @@ -16,9 +17,7 @@ import type { GetBlogDetailOperation, GetRelevantBlogsOperation } from '../types/blogs' -import type { - GetAllRecipesOperation -} from '../types/recipes' + import type { APIProvider, CommerceAPI } from '.' import { GetAllCollectionsOperation } from '@commerce/types/collection'; @@ -43,7 +42,9 @@ export const OPERATIONS = [ 'getAllBlogPaths', 'getBlogDetail', 'getRelevantBlogs', - 'getAllRecipes' + 'getAllRecipes', + 'getAllRecipePaths', + 'getRecipeDetail' ] as const export const defaultOperations = OPERATIONS.reduce((ops, k) => { @@ -84,14 +85,14 @@ export type Operations

= { ): Promise } - getPage: { - (opts: { + getRecipeDetail: { + (opts: { variables: T['variables'] config?: P['config'] preview?: boolean }): Promise - ( + ( opts: { variables: T['variables'] config?: P['config'] @@ -159,6 +160,35 @@ export type Operations

= { ): Promise } + getAllRecipePaths: { + (opts: { + variables?: T['variables'] + config?: P['config'] + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + } & OperationOptions + ): Promise + } + + getAllRecipe: { + (opts: { + variables?: T['variables'] + config?: P['config'] + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + } & OperationOptions + ): Promise + } + + getAllProducts: { (opts: { variables?: T['variables'] diff --git a/framework/commerce/types/recipes.ts b/framework/commerce/types/recipes.ts index 820c7aac7..037855f32 100644 --- a/framework/commerce/types/recipes.ts +++ b/framework/commerce/types/recipes.ts @@ -29,4 +29,17 @@ export type GetAllRecipesOperation = { take?: number id?: SortRecipes } +} +export type GetAllRecipePathsOperation< +T extends RecipesType = RecipesType +> = { + data: { recipes: Pick[] } + variables: { first?: number } +} + +export type GetRecipeDetailOperation = { + data: T['items'], + variables: { + slug?: string + } } \ No newline at end of file diff --git a/framework/vendure/api/index.ts b/framework/vendure/api/index.ts index cb62f2dc0..3d9c9219f 100644 --- a/framework/vendure/api/index.ts +++ b/framework/vendure/api/index.ts @@ -15,6 +15,9 @@ import getSiteInfo from './operations/get-site-info' import getAllBlogPaths from './operations/get-all-blog-paths' import getRelevantBlogs from './operations/get-relevant-blogs' import getAllRecipes from './operations/get-all-recipes' +import getAllRecipePaths from './operations/get-all-recipe-paths' +import getRecipeDetail from './operations/get-recipe-detail' + import login from './operations/login' import fetchGraphqlApi from './utils/fetch-graphql-api' @@ -54,7 +57,9 @@ const operations = { getBlogDetail, getAllBlogPaths, getRelevantBlogs, - getAllRecipes + getAllRecipes, + getAllRecipePaths, + getRecipeDetail } export const provider = { config, operations } diff --git a/framework/vendure/api/operations/get-all-recipe-paths.ts b/framework/vendure/api/operations/get-all-recipe-paths.ts new file mode 100644 index 000000000..ac09dbd1c --- /dev/null +++ b/framework/vendure/api/operations/get-all-recipe-paths.ts @@ -0,0 +1,53 @@ +import { BlogList } from '../../schema'; +import { OperationContext,OperationOptions } from '@commerce/api/operations'; +import { BigcommerceConfig } from '../../../bigcommerce/api'; +import type { GetAllRecipePathsQuery,RecipeTranslation } from '../../schema'; +import { getAllBlogPathsQuery } from '../../utils/queries/get-all-blog-paths-query'; +import { Provider } from '../index'; +import { GetAllRecipesPathsOperation } from '../../../commerce/types/recipes'; + +export type GetAllBlogPathsResult = { + blogs: Array<{ node: { path: string } }> +} + +export default function getAllRecipePathsOperation({ + commerce, +}: OperationContext) { + async function getAllRecipePaths< + T extends GetAllRecipesPathsOperation + >(opts?: { + variables?: T['variables'] + config?: BigcommerceConfig + }): Promise + + async function getAllRecipePaths( + opts: { + variables?: T['variables'] + config?: BigcommerceConfig + } & OperationOptions + ): Promise + + async function getAllRecipePaths({ + query = getAllBlogPathsQuery, + variables, + config: cfg, + }: { + query?: string + variables?: T['variables'] + config?: BigcommerceConfig + } = {}): Promise { + const config = commerce.getConfig(cfg) + + const { data } = await config.fetch(query, { + variables, + }) + + const recipes = data.blogs.items; + + return { + recipes: recipes?.map(val=>val.translations.map((p:RecipeTranslation) => ({ path: `/${p.slug}` }))) + } + } + + return getAllRecipePaths +} diff --git a/framework/vendure/api/operations/get-all-recipes.ts b/framework/vendure/api/operations/get-all-recipes.ts index 3f9fcc90f..4998d793e 100644 --- a/framework/vendure/api/operations/get-all-recipes.ts +++ b/framework/vendure/api/operations/get-all-recipes.ts @@ -1,14 +1,13 @@ import { OperationContext } from '@commerce/api/operations' import { Provider, VendureConfig } from '..' -import { GetAllRecipesQuery,BlogList,SortRecipes } from '../../schema' +import { BlogList, GetAllRecipesQuery } from '../../schema' import { getAllBlogsQuery } from '../../utils/queries/get-all-blog-query' export type RecipesVariables = { excludeBlogIds?: string[], - take?: number, - sort?: { - id?: string - } + take?:number, + id?: string, + isPublish?:Boolean } export default function getAllRecipesOperation({ @@ -19,7 +18,7 @@ export default function getAllRecipesOperation({ config?: Partial preview?: boolean }): Promise<{ recipes: GetAllRecipesQuery[],totalItems:number }> - + async function getAllRecipes({ query = getAllBlogsQuery, variables: { ...vars } = {}, @@ -30,20 +29,27 @@ export default function getAllRecipesOperation({ config?: Partial preview?: boolean } = {}): Promise<{ recipes: GetAllRecipesQuery[] | any[] ,totalItems?:number }> { - + const config = commerce.getConfig(cfg) const variables = { excludeBlogIds: vars.excludeBlogIds, options: { take: vars.take, sort: { - id: vars.sort?.id + id: vars?.id + }, + filter:{ + isPublish: { + eq:vars.isPublish + } } }, } + const { data } = await config.fetch(query, { variables, }) + return { recipes: data?.blogs?.items?.map((val:BlogList)=>({ id: val.id, @@ -52,7 +58,6 @@ export default function getAllRecipesOperation({ slug: val.translations[0]?.slug, description: val.translations[0]?.description, isPublish: val.isPublish, - isFeatured: val.isFeatured, authorName: val.authorName, authorAvatarAsset : val.authorAvatarAsset?.preview ?? null, createdAt: val.createdAt diff --git a/framework/vendure/api/operations/get-recipe-detail.ts b/framework/vendure/api/operations/get-recipe-detail.ts new file mode 100644 index 000000000..e8b2b167f --- /dev/null +++ b/framework/vendure/api/operations/get-recipe-detail.ts @@ -0,0 +1,98 @@ +import { OperationContext } from '@commerce/api/operations' +import { Provider, VendureConfig } from '..' +import { GetRecipeQuery, RecipeList } from '../../schema' +import { getBlogDetailQuery } from '../../utils/queries/get-blog-detail' + + +export default function getRecipeDetailOperation({ + commerce, +}: OperationContext) { + + async function getRecipeDetail({ + query = getBlogDetailQuery, + variables, + config: cfg, + }: { + query?: string + variables: { slug: string } + config?: Partial + preview?: boolean + }): Promise<{recipeDetail: RecipeList | any }> { + const config = commerce.getConfig(cfg) + const { data } = await config.fetch(query, { + variables, + }) + const recipes = data.blog + + if (recipes) { + return { + recipeDetail: { + id: data?.blog?.id, + title: data?.blog?.translations[0].title, + imageSrc: data?.blog?.featuredAsset?.preview ?? null, + slug: data?.blog?.translations[0]?.slug, + description: data?.blog?.translations[0]?.description, + isPublish: data?.blog?.isPublish, + authorName: data?.blog?.authorName, + authorAvatarAsset : data?.blog?.authorAvatarAsset?.preview, + createdAt: data?.blog?.createdAt, + relevantProducts: data?.blog?.relevantProducts.map(val=>val.id) + } + } + }else{ + return {recipeDetail:null} + } + } + + return getRecipeDetail +} + + +// export type RecipeVariables = { +// slug?: string, +// } + +// export default function getRecipeDetailOperation({ +// commerce, +// }: OperationContext) { + +// async function getRecipeDetail(opts?: { +// variables?: RecipeVariables +// config?: Partial +// preview?: boolean +// }): Promise<{ recipeDetail: RecipeList}> + +// async function getRecipeDetail({ +// query = getBlogDetailQuery, +// variables: { ...vars } = {}, +// config: cfg, +// }: { +// query?: string +// variables?: RecipeVariables +// config?: Partial +// preview?: boolean +// } = {}): Promise<{ recipeDetail: RecipeList | any }> { + +// const config = commerce.getConfig(cfg) +// const variables = { +// slug: vars.slug +// } +// const { data } = await config.fetch(query, { +// variables, +// }) +// return { +// id: data?.blog?.id, +// title: data?.blog?.translations[0].title, +// imageSrc: data?.blog?.featuredAsset?.preview ?? null, +// slug: data?.blog?.translations[0]?.slug, +// description: data?.blog?.translations[0]?.description, +// isPublish: data?.blog?.isPublish, +// authorName: data?.blog?.authorName, +// authorAvatarAsset : data?.blog?.authorAvatarAsset?.preview, +// createdAt: data?.blog?.createdAt, +// relevantProducts: data?.blog?.relevantProducts.map(val=>val.id) +// } +// } + +// return getRecipeDetail +// } diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 4c69753e0..9817dae05 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -2433,6 +2433,12 @@ export type GetBlogQuery = { __typename?: 'Query' } & { > } +export type GetRecipeQuery = { __typename?: 'Query' } & { + recipeDetail?: Maybe< + { __typename?: 'Recipe' } & RecipeList + > +} + export type BlogTranslation = { __typename?: 'BlogTranslation' @@ -2445,6 +2451,18 @@ export type BlogTranslation = { description: Scalars['String'] content: Scalars['String'] } +export type RecipeTranslation = { + __typename?: 'BlogTranslation' + id: Scalars['ID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + languageCode: LanguageCode + title: Scalars['String'] + slug: Scalars['String'] + description: Scalars['String'] + content: Scalars['String'] +} + export type GetAllBlogsQuery = PaginatedList & { blogs: { __typename?: 'BlogList' } & { @@ -2481,6 +2499,20 @@ export type QueryBlogs = { options: BlogListOptions } +export type QueryRecipes = { + excludeBlogIds?:Maybe, + options: RecipeListOptions +} + +export type RecipeListOptions = { + skip?: Maybe + take?: Maybe + sort?: RecipesSort +} +export type RecipesSort = { + id?: Maybe +} + export type BlogListOptions = { skip?: Maybe take?: Maybe @@ -3455,6 +3487,13 @@ export type GetAllBlogPathsQuery = { __typename?: 'Query' } & { } } +export type GetAllRecipePathsQuery = { __typename?: 'Query' } & { + recipes: { __typename?: 'Recipes' } & { + items: Array<{ __typename?: 'Recipe' } & Pick> + } +} + + export type GetAllProductsQueryVariables = Exact<{ input: SearchInput }> diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index c095ec030..ccafdaf6d 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -1,6 +1,6 @@ import { Cart } from '@commerce/types/cart' import { ProductCard, Product } from '@commerce/types/product' -import { CartFragment, SearchResultFragment,Favorite, BlogList } from '../schema' +import { CartFragment, SearchResultFragment,Favorite, BlogList, RecipeList } from '../schema' export function normalizeSearchResult(item: SearchResultFragment): ProductCard { return { @@ -98,4 +98,18 @@ export function normalizeBlogList(blog: BlogList) { authorAvatarAsset : blog.authorAvatarAsset?.preview, createdAt: blog.createdAt } +} + +export function normalizeRecipeList(recipe: RecipeList) { + return { + id: recipe.id, + title: recipe.translations[0]?.title, + imageSrc: recipe.featuredAsset?.preview ?? null, + slug: recipe.translations[0]?.slug, + description: recipe.translations[0]?.description, + isPublish: recipe.isPublish, + authorName: recipe.authorName, + authorAvatarAsset : recipe.authorAvatarAsset?.preview, + createdAt: recipe.createdAt + } } \ No newline at end of file diff --git a/pages/blogs.tsx b/pages/blogs.tsx index 0fa7ad2c1..44a16a1e5 100644 --- a/pages/blogs.tsx +++ b/pages/blogs.tsx @@ -13,7 +13,7 @@ interface Props { totalItems: number } export default function BlogsPage({ blogs, featuredBlog, totalItems }:Props) { - console.log(blogs) + let date = new Date(featuredBlog?.[0]?.createdAt ?? '' ); let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear(); diff --git a/pages/products.tsx b/pages/products.tsx index 9766ab37d..035ab87e6 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -14,7 +14,6 @@ interface Props { facets: Facet[], collections: Collection[], productsResult: { products: ProductCard[], totalItems: number }, - } export default function Products({ facets, collections, productsResult }: Props) { diff --git a/pages/recipe/[slug].tsx b/pages/recipe/[slug].tsx index 1f71ba5be..d05de7210 100644 --- a/pages/recipe/[slug].tsx +++ b/pages/recipe/[slug].tsx @@ -1,12 +1,98 @@ +import { useRouter } from 'next/router' import { Layout, RecipeDetail, RecommendedRecipes } from 'src/components/common' import { INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data' +import commerce from '@lib/api/commerce'; +import { PromiseWithKey } from 'src/utils/types.utils'; +import { GetStaticPropsContext,GetStaticPathsContext } from 'next'; +import { getAllPromies } from 'src/utils/funtion.utils'; +import { REVALIDATE_TIME } from 'src/utils/constanst.utils' +import { RecipeCardProps } from 'src/components/common/RecipeCard/RecipeCard'; +interface Props { + recipe:{recipeDetail?: RecipeCardProps}, + relevant:{relevantBlogs?:RecipeCardProps[]} +} +export default function Slug({recipe,relevant}:Props) { -export default function Slug() { return

- - + +
} + +export async function getStaticProps({ + params, + locale, + locales, + preview, +}: GetStaticPropsContext<{ slug: string }> ) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + // Blog detail + const recipesPromise = await commerce.getRecipeDetail({ + variables: { slug: params!.slug }, + config, + preview, + }) + props.recipe = recipesPromise; + + if (recipesPromise.recipeDetail === null) { + return { notFound: true }; + } + + // // Relevant Blogs + const relevantProductId = recipesPromise?.recipeDetail?.relevantProducts?.[0]; + if (relevantProductId && recipesPromise?.recipeDetail?.relevantProducts?.length > 0) { + + const relevantBlogs = commerce.getRelevantBlogs({ + variables: { productId: relevantProductId }, + config, + preview, + }) + promisesWithKey.push({ key: 'relevant', promise: relevantBlogs}) + + }else { + props.relevantBlogs = []; + } + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + return { + props, + revalidate: REVALIDATE_TIME, + } + } catch (err) { + + } +} + +export async function getStaticPaths({ locales }: GetStaticPathsContext) { + + const { recipes } = await commerce.getAllRecipePaths() + return { + paths: locales + ? locales.reduce((arr, locale) => { + recipes.forEach((blog: any) => { + arr.push(`/${locale}/recipe/${blog.slug}`) + }) + return arr + }, []) + : recipes.map((product: any) => `/recipe/${product.path}`), + fallback: 'blocking', + } +} + + Slug.Layout = Layout diff --git a/pages/recipes.tsx b/pages/recipes.tsx index e49b903cd..7df69a9da 100644 --- a/pages/recipes.tsx +++ b/pages/recipes.tsx @@ -9,14 +9,14 @@ import { getAllPromies } from 'src/utils/funtion.utils'; import { RecipeCardProps } from 'src/components/common/RecipeCard/RecipeCard'; interface Props { - recipesResult ?: {recipes: RecipeCardProps[],totalItems?: number} , // it will chang when have recipes Props + recipesResult: {recipes: RecipeCardProps[] ,totalItems?: number}, } export default function RecipeListPage({recipesResult}:Props) { return ( <> - + ) } @@ -32,13 +32,12 @@ export async function getStaticProps({ let props = {} as any; - const recipesPromise = commerce.getAllRecipes({ + const recipesPromise = commerce.getAllRecipes({ variables: { excludeBlogIds: [], take: DEFAULT_BLOG_PAGE_SIZE, - sort: { - id: "DESC" - } + id: 'DESC', + isPublish:true }, config, preview, @@ -46,6 +45,7 @@ export async function getStaticProps({ promisesWithKey.push({ key: 'recipesResult', promise: recipesPromise}) + try { const promises = getAllPromies(promisesWithKey) const rs = await Promise.all(promises) @@ -54,7 +54,7 @@ export async function getStaticProps({ props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] return null }) - + return { props, revalidate: 60 diff --git a/src/components/common/RecipeDetail/RecipeDetail.tsx b/src/components/common/RecipeDetail/RecipeDetail.tsx index cdec99994..84d3e4f0d 100644 --- a/src/components/common/RecipeDetail/RecipeDetail.tsx +++ b/src/components/common/RecipeDetail/RecipeDetail.tsx @@ -1,20 +1,21 @@ import React from 'react' +import { RecipeProps } from 'src/utils/types.utils' import { ProductCardProps } from '../ProductCard/ProductCard' import RecipeDetailInfo from './components/RecipeDetailInfo/RecipeDetailInfo' import RecipeIngredient from './components/RecipeIngredient/RecipeIngredient' import s from './RecipeDetail.module.scss' -interface Props { +interface Props extends RecipeProps { className?: string children?: any, - ingredients: ProductCardProps[], + ingredients: ProductCardProps[] } -const RecipeDetail = ({ ingredients }: Props) => { +const RecipeDetail = ({ ingredients,...rest }: Props) => { return (
- +
) diff --git a/src/components/common/RecipeDetail/components/RecipeDetailInfo/RecipeDetailInfo.tsx b/src/components/common/RecipeDetail/components/RecipeDetailInfo/RecipeDetailInfo.tsx index a853ad869..046fa2b55 100644 --- a/src/components/common/RecipeDetail/components/RecipeDetailInfo/RecipeDetailInfo.tsx +++ b/src/components/common/RecipeDetail/components/RecipeDetailInfo/RecipeDetailInfo.tsx @@ -1,24 +1,26 @@ import React from 'react' import { ImgWithLink } from 'src/components/common' +import { RecipeProps } from 'src/utils/types.utils' import RecipeBriefInfo from '../RecipeBriefInfo/RecipeBriefInfo' import s from './RecipeDetailInfo.module.scss' -interface Props { +interface Prop extends RecipeProps { className?: string children?: any } -const RecipeDetailInfo = ({ }: Props) => { +const RecipeDetailInfo = ({ ...rest}: Prop) => { + return (
- +

- Crispy Fried Calamari + {rest.title}

diff --git a/src/components/common/RecommendedRecipes/RecommendedRecipes.tsx b/src/components/common/RecommendedRecipes/RecommendedRecipes.tsx index aa1df57d4..1b8494eeb 100644 --- a/src/components/common/RecommendedRecipes/RecommendedRecipes.tsx +++ b/src/components/common/RecommendedRecipes/RecommendedRecipes.tsx @@ -30,10 +30,10 @@ const RESPONSIVE: ResponsiveType = { }, } interface Props { - data: RecipeCardProps[], + data?: RecipeCardProps[], } -const RecommendedRecipes = ({ data }: Props) => { +const RecommendedRecipes = ({ data=[] }: Props) => { return (
diff --git a/src/components/hooks/recipe/index.ts b/src/components/hooks/recipe/index.ts new file mode 100644 index 000000000..a702a9644 --- /dev/null +++ b/src/components/hooks/recipe/index.ts @@ -0,0 +1 @@ +export {default as useGetRecipeList } from "./useGetRecipeList" \ No newline at end of file diff --git a/src/components/hooks/recipe/useGetRecipeList.tsx b/src/components/hooks/recipe/useGetRecipeList.tsx new file mode 100644 index 000000000..1db9052da --- /dev/null +++ b/src/components/hooks/recipe/useGetRecipeList.tsx @@ -0,0 +1,18 @@ +import { GetAllRecipesQuery,QueryRecipes, RecipeList } from '@framework/schema' +import { normalizeRecipeList } from '@framework/utils/normalize' +import { getAllBlogsQuery } from '@framework/utils/queries/get-all-blog-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useGetRecipeList = (options?: QueryRecipes) => { + const { data, isValidating, ...rest } = useSWR([getAllBlogsQuery, options], gglFetcher) + + return { + reicpes: data?.blogs?.items?.map((recipe:RecipeList)=>normalizeRecipeList(recipe)), + totalItems: data?.blogs?.totalItems || null, + loading: isValidating, + ...rest + } +} + +export default useGetRecipeList diff --git a/src/components/modules/blogs/BlogsList/BlogsList.tsx b/src/components/modules/blogs/BlogsList/BlogsList.tsx index 683b21132..245769b6e 100644 --- a/src/components/modules/blogs/BlogsList/BlogsList.tsx +++ b/src/components/modules/blogs/BlogsList/BlogsList.tsx @@ -18,7 +18,7 @@ interface BlogsListProps { const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { - console.log(blogList) + const DEFAULT_BLOGS_ARGS = useMemo(()=> ({ excludeBlogIds: [idFeatured], options:{ diff --git a/src/components/modules/recipes-list/RecipesList/RecipesList.tsx b/src/components/modules/recipes-list/RecipesList/RecipesList.tsx index 12ebed793..1499292f0 100644 --- a/src/components/modules/recipes-list/RecipesList/RecipesList.tsx +++ b/src/components/modules/recipes-list/RecipesList/RecipesList.tsx @@ -1,220 +1,303 @@ -import React from 'react'; -import { SelectCommon } from 'src/components/common'; -import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon'; -import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation'; -import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon'; -import { RecipeCardProps } from 'src/components/common/RecipeCard/RecipeCard'; -import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; -import HeadingCommon from "../../../common/HeadingCommon/HeadingCommon"; +import React, { useEffect, useState,useRef, useMemo } from 'react' +import { SelectCommon } from 'src/components/common' +import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon' +import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation' +import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon' +import { RecipeCardProps } from 'src/components/common/RecipeCard/RecipeCard' +import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import HeadingCommon from '../../../common/HeadingCommon/HeadingCommon' import { RecipeCard } from 'src/components/common' +import { DEFAULT_BLOG_PAGE_SIZE } from 'src/utils/constanst.utils' +import s from './RecipesList.module.scss' +import { useRouter } from 'next/router' +import { QueryRecipes } from '@framework/schema' +import { useGetRecipeList } from 'src/components/hooks/recipe' +import { getPageFromQuery } from 'src/utils/funtion.utils' +import { ListBlogCardSkeleton } from 'src/components/common' -import s from './RecipesList.module.scss'; +const recipe: RecipeCardProps[] = [ + { + title: 'Special Recipe of Vietnamese Phở', + description: + 'Alright, before we get to the actual recipe, let’s chat for a sec about the ingredients. To make this pho soup recipe, you will need:', + imageSrc: + 'https://user-images.githubusercontent.com/76729908/132159257-f92574c7-d00d-4142-8ea7-0ca9515fb737.png', + slug: 'special-recipe-of-vietnamese-pho', + }, + { + title: 'Original Recipe of Curry', + description: + 'Chicken curry is common to several countries including India, countries in Asia and the Caribbean. My favorite of them though is this aromatic Indian...', + imageSrc: + 'https://user-images.githubusercontent.com/76729908/132159259-ae4c986d-ab53-4758-9137-d06bafdd15d0.png', + slug: 'original-recipe-of-curry', + }, + { + title: 'The Best Recipe of Beef Noodle Soup', + description: + 'The broth for Bun Bo Hue is prepared by slowly simmering various types of beef and pork bones (ox tail, beef shank, pork neck bones, pork feet,...', + imageSrc: + 'https://user-images.githubusercontent.com/76729908/132159262-f28a9fb9-4852-47e6-80b5-d600521b548a.png', + slug: 'the-best-recipe-of-beef-noodle-soup', + }, + { + title: 'Special Recipe of Vietnamese Phở', + description: + 'Alright, before we get to the actual recipe, let’s chat for a sec about the ingredients. To make this pho soup recipe, you will need:', + imageSrc: + 'https://user-images.githubusercontent.com/76729908/132159257-f92574c7-d00d-4142-8ea7-0ca9515fb737.png', + slug: 'special-recipe-of-vietnamese-pho', + }, + { + title: 'Original Recipe of Curry', + description: + 'Chicken curry is common to several countries including India, countries in Asia and the Caribbean. My favorite of them though is this aromatic Indian...', + imageSrc: + 'https://user-images.githubusercontent.com/76729908/132159259-ae4c986d-ab53-4758-9137-d06bafdd15d0.png', + slug: 'original-recipe-of-curry', + }, + { + title: 'The Best Recipe of Beef Noodle Soup', + description: + 'The broth for Bun Bo Hue is prepared by slowly simmering various types of beef and pork bones (ox tail, beef shank, pork neck bones, pork feet,...', + imageSrc: + 'https://user-images.githubusercontent.com/76729908/132159262-f28a9fb9-4852-47e6-80b5-d600521b548a.png', + slug: 'the-best-recipe-of-beef-noodle-soup', + }, +] -const recipe:RecipeCardProps[] = [ -{ - title: "Special Recipe of Vietnamese Phở", - description: "Alright, before we get to the actual recipe, let’s chat for a sec about the ingredients. To make this pho soup recipe, you will need:", - imageSrc: 'https://user-images.githubusercontent.com/76729908/132159257-f92574c7-d00d-4142-8ea7-0ca9515fb737.png', - slug: "special-recipe-of-vietnamese-pho" -}, -{ - title: "Original Recipe of Curry", - description: "Chicken curry is common to several countries including India, countries in Asia and the Caribbean. My favorite of them though is this aromatic Indian...", - imageSrc: 'https://user-images.githubusercontent.com/76729908/132159259-ae4c986d-ab53-4758-9137-d06bafdd15d0.png', - slug:"original-recipe-of-curry" -}, -{ - title: "The Best Recipe of Beef Noodle Soup", - description: "The broth for Bun Bo Hue is prepared by slowly simmering various types of beef and pork bones (ox tail, beef shank, pork neck bones, pork feet,...", - imageSrc: 'https://user-images.githubusercontent.com/76729908/132159262-f28a9fb9-4852-47e6-80b5-d600521b548a.png', - slug:"the-best-recipe-of-beef-noodle-soup" -}, -{ - title: "Special Recipe of Vietnamese Phở", - description: "Alright, before we get to the actual recipe, let’s chat for a sec about the ingredients. To make this pho soup recipe, you will need:", - imageSrc: 'https://user-images.githubusercontent.com/76729908/132159257-f92574c7-d00d-4142-8ea7-0ca9515fb737.png', - slug: "special-recipe-of-vietnamese-pho" -}, -{ - title: "Original Recipe of Curry", - description: "Chicken curry is common to several countries including India, countries in Asia and the Caribbean. My favorite of them though is this aromatic Indian...", - imageSrc: 'https://user-images.githubusercontent.com/76729908/132159259-ae4c986d-ab53-4758-9137-d06bafdd15d0.png', - slug:"original-recipe-of-curry" -}, -{ - title: "The Best Recipe of Beef Noodle Soup", - description: "The broth for Bun Bo Hue is prepared by slowly simmering various types of beef and pork bones (ox tail, beef shank, pork neck bones, pork feet,...", - imageSrc: 'https://user-images.githubusercontent.com/76729908/132159262-f28a9fb9-4852-47e6-80b5-d600521b548a.png', - slug:"the-best-recipe-of-beef-noodle-soup" -},]; - -const DEFAULT_PAGESIZE_RECIPELIST = 6; +const DEFAULT_PAGESIZE_RECIPELIST = 6 const BREADCRUMB = [ - { - name: 'Special Recipes', - link: `#`, - }, -]; + { + name: 'Special Recipes', + link: `#`, + }, +] const CATEGORY = [ - { - name: 'All', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=${OPTION_ALL}`, - }, - { - name: 'Malaysian', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=malaysia`, - }, - { - name: 'Vietnamese', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=vietnamese`, - }, - { - name: 'Thailand', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=thailand`, - }, - { - name: 'Indian', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=indian`, - }, - { - name: 'Lao', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=lao`, - }, - { - name: 'Chinese', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=chinese`, - }, - { - name: 'Korean', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=korean`, - }, - { - name: 'Japanese', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=japanese`, - }, - { - name: 'Western', - link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=western`, - }, - ]; + { + name: 'All', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=${OPTION_ALL}`, + }, + { + name: 'Malaysian', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=malaysia`, + }, + { + name: 'Vietnamese', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=vietnamese`, + }, + { + name: 'Thailand', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=thailand`, + }, + { + name: 'Indian', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=indian`, + }, + { + name: 'Lao', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=lao`, + }, + { + name: 'Chinese', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=chinese`, + }, + { + name: 'Korean', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=korean`, + }, + { + name: 'Japanese', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=japanese`, + }, + { + name: 'Western', + link: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=western`, + }, +] const CATEGORYSELECT = [ -{ + { name: 'All', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=${OPTION_ALL}`, -}, -{ + }, + { name: 'Malaysian', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=malaysia`, -}, -{ + }, + { name: 'Vietnamese', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=vietnamese`, -}, -{ + }, + { name: 'Thailand', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=thailand`, -}, -{ + }, + { name: 'Indian', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=indian`, -}, -{ + }, + { name: 'Lao', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=lao`, -}, -{ + }, + { name: 'Chinese', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=chinese`, -}, -{ + }, + { name: 'Korean', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=korean`, -}, -{ + }, + { name: 'Japanese', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=japanese`, -}, -{ + }, + { name: 'Western', value: `${ROUTE.RECIPES}/?${QUERY_KEY.RECIPES}=western`, -}, -]; - -const OPTIONSLECT=[ - { - name:"Most Viewed", - value:"most-viewed" - }, - { - name:"Lastest Blogs", - value:"lastest-blogs" - }, - { - name:"Recent Blogs", - value:"recent-blogs" - }, -]; + }, +] -interface Props{ - recipes?: RecipeCardProps[], - total:number +const OPTIONSLECT = [ + { + name: 'Most Viewed', + value: 'most-viewed', + }, + { + name: 'Lastest Blogs', + value: 'lastest-blogs', + }, + { + name: 'Recent Blogs', + value: 'recent-blogs', + }, +] + +interface Props { + recipeList?: RecipeCardProps[] + total: number } +const RecipesList = ({ recipeList, total }: Props) => { + const DEFAULT_BLOGS_ARGS = useMemo( + () => ({ + excludeBlogIds: [], + options:{ + take: DEFAULT_BLOG_PAGE_SIZE, + sort: { + id: 'DESC', + }, + filter:{ + isPublish: { + eq:true + } + } + } + }), + [] + ) + const router = useRouter() + const [initialQueryFlag, setInitialQueryFlag] = useState(true) + const [optionQueryBlog, setOptionQueryBlog] = useState(DEFAULT_BLOGS_ARGS) + const { reicpes, totalItems, loading } = useGetRecipeList(optionQueryBlog) + -const RecipesList = ({ recipes,total }:Props) => { - console.log(recipes) - return ( - <> -
-
- -
-
- -
- -
- -
- SPECIAL RECIPES - -
-
- -
- -
-
-
- -
- -
-
-
- -
-
- {recipes?.map((item,index) => ( -
- -
- ))} -
-
-
- -
-
- -
- -
- + const onPageChange = (page: number) => { + router.push( + { + pathname: ROUTE.RECIPES, + query: { + ...router.query, + [QUERY_KEY.PAGE]: page, + }, + }, + undefined, + { shallow: true } ) + } + + // skip + const firstRender = useRef(true); + + useEffect(() => { + firstRender.current = false + const query = { ...DEFAULT_BLOGS_ARGS } as QueryRecipes + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string) + query.options.skip = page * DEFAULT_BLOG_PAGE_SIZE + setOptionQueryBlog(query) + setInitialQueryFlag(false) + }, [router.query]) + + + let data; + if(initialQueryFlag == true){ + data = recipeList; + }else{ + data = reicpes + } + + return ( + <> +
+
+ +
+
+
+ +
+ +
+ SPECIAL RECIPES + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ {(!initialQueryFlag && loading && !data) && } + {data?.map((item, index) => ( +
+ +
+ ))} +
+
+
+ +
+
+
+
+ + ) } export default RecipesList diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index 1b600b551..fee4cf2b1 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -24,6 +24,10 @@ export interface RecipeProps { slug: string description: string imageSrc: string + content?: string, + imgAuthor?: string, + date?: string, + authorName?: string, } export interface BlogProps {