mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
Merge pull request #88 from KieIO/feature/m4-get-blog-list
Feature/m4 get blog list
This commit is contained in:
@@ -9,6 +9,10 @@ import type {
|
||||
GetAllProductsOperation,
|
||||
GetProductOperation,
|
||||
} from '../types/product'
|
||||
import type {
|
||||
GetAllBlogsOperation,
|
||||
GetFeaturedBlogsOperation
|
||||
} from '../types/blogs'
|
||||
import type { APIProvider, CommerceAPI } from '.'
|
||||
import { GetAllCollectionsOperation } from '@commerce/types/collection';
|
||||
|
||||
@@ -27,7 +31,8 @@ export const OPERATIONS = [
|
||||
'getProduct',
|
||||
'getAllFacets',
|
||||
'getAllCollections',
|
||||
|
||||
'getAllBlogs',
|
||||
'getFeaturedBlog'
|
||||
] as const
|
||||
|
||||
export const defaultOperations = OPERATIONS.reduce((ops, k) => {
|
||||
@@ -144,6 +149,41 @@ export type Operations<P extends APIProvider> = {
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
|
||||
getAllBlogs: {
|
||||
<T extends GetAllBlogsOperation>(opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetAllBlogsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getFeaturedBlog: {
|
||||
<T extends GetFeaturedBlogsOperation>(opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetFeaturedBlogsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
|
||||
|
||||
getProduct: {
|
||||
<T extends GetProductOperation>(opts: {
|
||||
variables: T['variables']
|
||||
|
31
framework/commerce/types/blogs.ts
Normal file
31
framework/commerce/types/blogs.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Asset, BlogTranslation, Maybe, Product } from './../../vendure/schema.d';
|
||||
|
||||
export type BlogList = Node &{
|
||||
id: string
|
||||
featuredAsset?: Maybe<Asset>
|
||||
isPublic:Boolean
|
||||
translations: BlogTranslation[]
|
||||
authorName: string
|
||||
authorAvatarAsset:Asset[]
|
||||
relevantProducts: Product
|
||||
}
|
||||
export type BlogsType = {
|
||||
items: BlogList
|
||||
totalItems: number
|
||||
}
|
||||
export type GetAllBlogsOperation<T extends BlogsType = BlogsType> = {
|
||||
data: { items: T['items'][] }
|
||||
variables: {
|
||||
take?: number
|
||||
skip?: number
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type GetFeaturedBlogsOperation<T extends BlogsType = BlogsType> = {
|
||||
data: { items: T['items'][] }
|
||||
variables: {
|
||||
take?: number
|
||||
skip?: number
|
||||
}
|
||||
}
|
@@ -11,7 +11,8 @@ import getProduct from './operations/get-product'
|
||||
import getSiteInfo from './operations/get-site-info'
|
||||
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 {}
|
||||
|
||||
@@ -44,6 +45,8 @@ const operations = {
|
||||
getProduct,
|
||||
getAllFacets,
|
||||
getAllCollections,
|
||||
getAllBlogs,
|
||||
getFeaturedBlog
|
||||
}
|
||||
|
||||
export const provider = { config, operations }
|
||||
|
61
framework/vendure/api/operations/get-all-blogs.ts
Normal file
61
framework/vendure/api/operations/get-all-blogs.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Provider, VendureConfig } from '..'
|
||||
import { GetAllBlogsQuery,BlogList } from '../../schema'
|
||||
import { getAllBlogsQuery } from '../../utils/queries/get-all-blog-query'
|
||||
|
||||
export type BlogVariables = {
|
||||
excludeBlogIds?: string[],
|
||||
take?: number,
|
||||
skip?:number
|
||||
}
|
||||
|
||||
export default function getAllBlogsOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllBlogs(opts?: {
|
||||
variables?: BlogVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<{ blogs: GetAllBlogsQuery[],totalItems:number }>
|
||||
|
||||
async function getAllBlogs({
|
||||
query = getAllBlogsQuery,
|
||||
variables: { ...vars } = {},
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: BlogVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<{ blogs: GetAllBlogsQuery[] | any[] ,totalItems?:number }> {
|
||||
|
||||
const config = commerce.getConfig(cfg)
|
||||
const variables = {
|
||||
excludeBlogIds: vars.excludeBlogIds,
|
||||
options: {
|
||||
take: vars.take,
|
||||
skip: vars.skip,
|
||||
},
|
||||
}
|
||||
const { data } = await config.fetch<GetAllBlogsQuery>(query, {
|
||||
variables,
|
||||
})
|
||||
return {
|
||||
blogs: 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,
|
||||
createdAt: val.createdAt
|
||||
})),
|
||||
totalItems: data?.blogs?.totalItems || null
|
||||
}
|
||||
}
|
||||
|
||||
return getAllBlogs
|
||||
}
|
56
framework/vendure/api/operations/get-featured-blog.ts
Normal file
56
framework/vendure/api/operations/get-featured-blog.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Provider, VendureConfig } from '..'
|
||||
import { GetFeaturedBlogQuery,BlogList } from '../../schema'
|
||||
import { getFeatuedBlogsQuery } from '../../utils/queries/get-featued-query'
|
||||
|
||||
export type BlogVariables = {
|
||||
take?: number,
|
||||
skip?:number
|
||||
}
|
||||
|
||||
export default function getFeaturedBlogOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getFeaturedBlog(opts?: {
|
||||
variables?: BlogVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
}): Promise<{ featuredBlogs: GetFeaturedBlogQuery[],totalItems:number }>
|
||||
|
||||
async function getFeaturedBlog({
|
||||
query = getFeatuedBlogsQuery,
|
||||
variables: { ...vars } = {},
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: BlogVariables
|
||||
config?: Partial<VendureConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<{ featuredBlogs: GetFeaturedBlogQuery[] | any[] ,totalItems?:number }> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
const variables = {
|
||||
options: {
|
||||
take: vars.take,
|
||||
},
|
||||
}
|
||||
const { data } = await config.fetch<GetFeaturedBlogQuery>(query, {
|
||||
variables,
|
||||
})
|
||||
return {
|
||||
featuredBlogs: data?.featuredBlogs?.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 getFeaturedBlog
|
||||
}
|
50
framework/vendure/schema.d.ts
vendored
50
framework/vendure/schema.d.ts
vendored
@@ -2371,6 +2371,55 @@ export type Product = Node & {
|
||||
customFields?: Maybe<Scalars['JSON']>
|
||||
}
|
||||
|
||||
export type BlogList = Node &{
|
||||
id: ID!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
featuredAsset?: Maybe<Asset>
|
||||
isPublish:Boolean
|
||||
translations: Array<BlogTranslation>
|
||||
authorName: Scalars['String']
|
||||
authorAvatarAsset:Asset
|
||||
relevantProducts: Product
|
||||
isFeatured: Boolean
|
||||
}
|
||||
|
||||
export type BlogTranslation = {
|
||||
__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' } & {
|
||||
items: Array<{ __typename?: 'Blog' } & BlogList!>,
|
||||
'totalItems'
|
||||
}
|
||||
}
|
||||
export type GetFeaturedBlogQuery = PaginatedList & {
|
||||
id:string,
|
||||
featuredBlogs: { __typename?: 'BlogList' } & {
|
||||
items: Array<{ __typename?: 'Blog' } & BlogList!>,
|
||||
'totalItems'
|
||||
}
|
||||
}
|
||||
|
||||
export type QueryBlogs = {
|
||||
excludeBlogIds:Array,
|
||||
options: BlogListOptions
|
||||
}
|
||||
|
||||
export type BlogListOptions = {
|
||||
skip?: Maybe<Scalars['Int']>
|
||||
take?: Maybe<Scalars['Int']>
|
||||
}
|
||||
|
||||
export type ProductTranslation = {
|
||||
__typename?: 'ProductTranslation'
|
||||
id: Scalars['ID']
|
||||
@@ -3485,3 +3534,4 @@ export type SearchQuery = { __typename?: 'Query' } & {
|
||||
'totalItems'
|
||||
> & { items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment> }
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Cart } from '@commerce/types/cart'
|
||||
import { ProductCard, Product } from '@commerce/types/product'
|
||||
import { CartFragment, SearchResultFragment,Favorite } from '../schema'
|
||||
import { CartFragment, SearchResultFragment,Favorite, BlogList } from '../schema'
|
||||
|
||||
export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
|
||||
return {
|
||||
@@ -83,4 +83,19 @@ export function normalizeProductCard(product: Product): ProductCard {
|
||||
facetValueIds: product.facetValueIds,
|
||||
collectionIds: product.collectionIds,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeBlogList(blog: BlogList) {
|
||||
return {
|
||||
id: blog.id,
|
||||
title: blog.translations[0]?.title,
|
||||
imageSrc: blog.featuredAsset?.preview ?? null,
|
||||
slug: blog.translations[0]?.slug,
|
||||
description: blog.translations[0]?.description,
|
||||
isPublish: blog.isPublish,
|
||||
isFeatured:blog.isFeatured,
|
||||
authorName: blog.authorName,
|
||||
authorAvatarAsset : blog.authorAvatarAsset?.preview,
|
||||
createdAt: blog.createdAt
|
||||
}
|
||||
}
|
26
framework/vendure/utils/queries/get-all-blog-query.ts
Normal file
26
framework/vendure/utils/queries/get-all-blog-query.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export const getAllBlogsQuery = /* GraphQL */ `
|
||||
query GetBlogs($excludeBlogIds: [ID]!, $options: BlogListOptions) {
|
||||
blogs(excludeBlogIds: $excludeBlogIds, options: $options) {
|
||||
totalItems
|
||||
items {
|
||||
id
|
||||
isPublish
|
||||
isFeatured
|
||||
authorName
|
||||
createdAt
|
||||
authorAvatarAsset{
|
||||
preview
|
||||
}
|
||||
featuredAsset {
|
||||
preview
|
||||
}
|
||||
translations {
|
||||
title
|
||||
slug
|
||||
description
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
25
framework/vendure/utils/queries/get-featued-query.ts
Normal file
25
framework/vendure/utils/queries/get-featued-query.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const getFeatuedBlogsQuery = /* GraphQL */ `
|
||||
query GetFeaturedBlogs($options: BlogListOptions) {
|
||||
featuredBlogs( options: $options){
|
||||
items {
|
||||
id
|
||||
isPublish
|
||||
isFeatured
|
||||
authorName
|
||||
createdAt
|
||||
authorAvatarAsset{
|
||||
preview
|
||||
}
|
||||
featuredAsset {
|
||||
preview
|
||||
}
|
||||
translations {
|
||||
title
|
||||
slug
|
||||
description
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
3
next-env.d.ts
vendored
3
next-env.d.ts
vendored
@@ -1,6 +1,3 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
@@ -1,14 +1,91 @@
|
||||
import commerce from '@lib/api/commerce';
|
||||
import { GetStaticPropsContext } from 'next';
|
||||
import { Layout } from 'src/components/common';
|
||||
import { BlogsList, FeaturedCardBlog, BlogHeading, BlogBreadCrumb } from 'src/components/modules/blogs';
|
||||
import { BlogCardProps } from 'src/components/common/CardBlog/CardBlog';
|
||||
import { BlogBreadCrumb, BlogHeading, BlogsList, FeaturedCardBlog } from 'src/components/modules/blogs';
|
||||
import { DEFAULT_BLOG_PAGE_SIZE } from "src/utils/constanst.utils";
|
||||
import { getAllPromies } from 'src/utils/funtion.utils';
|
||||
import { PromiseWithKey } from 'src/utils/types.utils';
|
||||
|
||||
export default function BlogsPage() {
|
||||
interface Props {
|
||||
blogs?: BlogCardProps[],
|
||||
featuredBlog?: BlogCardProps[],
|
||||
totalItems: number
|
||||
}
|
||||
export default function BlogsPage({ blogs, featuredBlog, totalItems }:Props) {
|
||||
|
||||
let date = new Date(featuredBlog?.[0]?.createdAt ?? '' );
|
||||
let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear();
|
||||
|
||||
return(
|
||||
<>
|
||||
<BlogBreadCrumb />
|
||||
<BlogHeading />
|
||||
<FeaturedCardBlog />
|
||||
<BlogsList />
|
||||
<FeaturedCardBlog
|
||||
title={featuredBlog?.[0]?.title}
|
||||
slug={featuredBlog?.[0]?.slug}
|
||||
imgSrc={featuredBlog?.[0]?.imageSrc ?? ''}
|
||||
content={featuredBlog?.[0]?.description}
|
||||
imgAuthor={featuredBlog?.[0]?.authorAvatarAsset}
|
||||
authorName={featuredBlog?.[0]?.authorName}
|
||||
date={fullDate}
|
||||
/>
|
||||
<BlogsList blogList={blogs} total={totalItems} idFeatured={featuredBlog?.[0]?.id} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
BlogsPage.Layout = Layout
|
||||
|
||||
|
||||
export async function getStaticProps({
|
||||
preview,
|
||||
locale,
|
||||
locales,
|
||||
}: GetStaticPropsContext) {
|
||||
const config = { locale, locales }
|
||||
let promisesWithKey = [] as PromiseWithKey[]
|
||||
let props = {} as any;
|
||||
|
||||
const {featuredBlogs} = await commerce.getFeaturedBlog({
|
||||
variables: {
|
||||
take: 1
|
||||
},
|
||||
config,
|
||||
preview,
|
||||
})
|
||||
|
||||
// Blogs
|
||||
const idFeaturedBlog = featuredBlogs?.[0]?.id;
|
||||
const blogsPromise = commerce.getAllBlogs({
|
||||
variables: {
|
||||
excludeBlogIds: [idFeaturedBlog],
|
||||
take: DEFAULT_BLOG_PAGE_SIZE
|
||||
},
|
||||
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
|
||||
})
|
||||
|
||||
props.featuredBlog = featuredBlogs;
|
||||
|
||||
console.log(props)
|
||||
return {
|
||||
props,
|
||||
revalidate: 60
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BlogsPage.Layout = Layout
|
||||
|
@@ -12,6 +12,7 @@ import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED,COLLECTION_SLUG_SPICE } from '
|
||||
import { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getAllPromies, getFreshFacetId } from 'src/utils/funtion.utils';
|
||||
import { PromiseWithKey } from 'src/utils/types.utils';
|
||||
|
||||
|
||||
interface Props {
|
||||
featuredAndDiscountFacetsValue: FacetValue[],
|
||||
freshProducts: ProductCard[],
|
||||
@@ -63,7 +64,7 @@ export async function getStaticProps({
|
||||
|
||||
|
||||
props.featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets)
|
||||
|
||||
|
||||
// fresh products
|
||||
const freshProductvariables: ProductVariables = {}
|
||||
const freshFacetId = getFreshFacetId(facets)
|
||||
|
@@ -6,17 +6,20 @@ import { ImgWithLink } from '..'
|
||||
import s from './CardBlog.module.scss'
|
||||
|
||||
export interface BlogCardProps extends BlogProps {
|
||||
// todo: edit when intergrate API
|
||||
|
||||
isPublish?:Boolean,
|
||||
isFeatured?:Boolean,
|
||||
authorAvatarAsset?:string,
|
||||
authorName?:string,
|
||||
createdAt?:string
|
||||
}
|
||||
|
||||
const CardBlog = ({ imageSrc, title, description, slug }: BlogCardProps) => {
|
||||
return (
|
||||
<div className={s.cardBlogWarpper}>
|
||||
<Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
|
||||
<a>
|
||||
<a>
|
||||
<div className={s.image}>
|
||||
<ImgWithLink src={imageSrc} alt="image cardblog" />
|
||||
<ImgWithLink src={imageSrc ?? ''} alt={title} />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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 (
|
||||
<div className={classNames(s.listBlogCardSkeleton, { [s.wrap]: isWrap })}>
|
||||
{
|
||||
Array.from(Array(count).keys()).map(item => <ProductCardSkeleton key={item} />)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListBlogCardSkeleton
|
@@ -1,7 +1,7 @@
|
||||
.listProductCardSkeleton {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&.wrap {
|
||||
flex-wrap: wrap;
|
||||
overflow: unset;
|
||||
@@ -9,4 +9,11 @@
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
}
|
||||
&.isBlog{
|
||||
width: 90%;
|
||||
justify-content: space-between;
|
||||
div{
|
||||
min-width: 32rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,15 +4,14 @@ import s from './ListProductCardSkeleton.module.scss'
|
||||
|
||||
type Props = {
|
||||
count?: number
|
||||
isWrap?: boolean
|
||||
isWrap?: boolean,
|
||||
}
|
||||
const ListProductCardSkeleton = ({ count = 5, isWrap }: Props) => {
|
||||
const ListProductCardSkeleton = ({ count = 3, isWrap }: Props) => {
|
||||
|
||||
return (
|
||||
<div className={classNames(s.listProductCardSkeleton, { [s.wrap]: isWrap })}>
|
||||
{
|
||||
Array.from(Array(count).keys()).map(item => <ProductCardSkeleton key={item} />)
|
||||
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import SkeletonImage from "../SkeletonCommon/SkeletonImage/SkeletonImage"
|
||||
import SkeletonParagraph from "../SkeletonCommon/SkeletonParagraph/SkeletonParagraph"
|
||||
import s from './ProductCardSkeleton.module.scss'
|
||||
|
||||
const ProductCardSkeleton = ({ }) => {
|
||||
type Props = {
|
||||
isBlog?:boolean
|
||||
}
|
||||
const ProductCardSkeleton = ({isBlog=false }) => {
|
||||
|
||||
return (
|
||||
<div className={s.productCardSkeleton}>
|
||||
<SkeletonImage />
|
||||
<div className={s.content}>
|
||||
<SkeletonParagraph rows={3} />
|
||||
<SkeletonParagraph rows={isBlog ? 2 : 3} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@@ -55,4 +55,5 @@ export { default as FormForgot} from './ForgotPassword/FormForgot/FormForgot'
|
||||
export { default as FormResetPassword} from './ForgotPassword/FormResetPassword/FormResetPassword'
|
||||
export { default as ProductCardSkeleton} from './ProductCardSkeleton/ProductCardSkeleton'
|
||||
export { default as ListProductCardSkeleton} from './ListProductCardSkeleton/ListProductCardSkeleton'
|
||||
export { default as ListBlogCardSkeleton} from './ListBlogCardSkeleton/ListBlogCardSkeleton'
|
||||
|
||||
|
1
src/components/hooks/blog/index.ts
Normal file
1
src/components/hooks/blog/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {default as useGetBlogList }from "./useGetBlogList"
|
18
src/components/hooks/blog/useGetBlogList.tsx
Normal file
18
src/components/hooks/blog/useGetBlogList.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GetAllBlogsQuery, QueryBlogs,BlogList } from '@framework/schema'
|
||||
import { normalizeBlogList } 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 useGetBlogList = (options?: QueryBlogs) => {
|
||||
const { data, isValidating, ...rest } = useSWR<GetAllBlogsQuery>([getAllBlogsQuery, options], gglFetcher)
|
||||
|
||||
return {
|
||||
blogs: data?.blogs?.items?.map((blog:BlogList)=>normalizeBlogList(blog)),
|
||||
totalItems: data?.blogs?.totalItems || null,
|
||||
loading: isValidating,
|
||||
...rest
|
||||
}
|
||||
}
|
||||
|
||||
export default useGetBlogList
|
@@ -1,151 +1,96 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState,useRef, useMemo } 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 { DEFAULT_BLOG_PAGE_SIZE } from 'src/utils/constanst.utils'
|
||||
import { QueryBlogs } from '@framework/schema'
|
||||
import { useGetBlogList } from 'src/components/hooks/blog'
|
||||
import { getPageFromQuery } from 'src/utils/funtion.utils'
|
||||
import { ListBlogCardSkeleton } from 'src/components/common'
|
||||
|
||||
interface BlogsListProps {
|
||||
data?: BlogCardProps[],
|
||||
blogList?: BlogCardProps[],
|
||||
total?: number,
|
||||
idFeatured?:string
|
||||
}
|
||||
|
||||
const BLOGSLIST_DATA = [
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185783-8100ef4e-7a72-4dc1-bb12-2ca46b56b393.png",
|
||||
title: "1",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185911-df505d10-fdcd-4312-add3-7c62ad8af71e.png",
|
||||
title: "2",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185959-7ad75580-ca6d-4684-83d9-3f64500bbc97.png",
|
||||
title: "3",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186410-d8718d90-82fb-46cb-a0f2-0ec96356ae89.png",
|
||||
title: "4",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186474-b2d89bbc-32ed-4174-a05e-3d388c0a39ff.png",
|
||||
title: "5",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186545-d860f4ee-222c-4d72-a876-808af0f397a0.png",
|
||||
title: "6",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185783-8100ef4e-7a72-4dc1-bb12-2ca46b56b393.png",
|
||||
title: "7",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185911-df505d10-fdcd-4312-add3-7c62ad8af71e.png",
|
||||
title: "8",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185959-7ad75580-ca6d-4684-83d9-3f64500bbc97.png",
|
||||
title: "9",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186545-d860f4ee-222c-4d72-a876-808af0f397a0.png",
|
||||
title: "10",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186410-d8718d90-82fb-46cb-a0f2-0ec96356ae89.png",
|
||||
title: "11",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186474-b2d89bbc-32ed-4174-a05e-3d388c0a39ff.png",
|
||||
title: "12",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185783-8100ef4e-7a72-4dc1-bb12-2ca46b56b393.png",
|
||||
title: "13",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185911-df505d10-fdcd-4312-add3-7c62ad8af71e.png",
|
||||
title: "14",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133185959-7ad75580-ca6d-4684-83d9-3f64500bbc97.png",
|
||||
title: "15",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186410-d8718d90-82fb-46cb-a0f2-0ec96356ae89.png",
|
||||
title: "16",
|
||||
description: "The DEBM diet stands for "+"Delicious Happy Fun Diet"+". This diet was popularized by Robert...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186545-d860f4ee-222c-4d72-a876-808af0f397a0.png",
|
||||
title: "17",
|
||||
description: "Dragon fruit is a type of fruit that is a favorite for many people because of its delicious and fresh...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
{
|
||||
imageSrc: "https://user-images.githubusercontent.com/46085455/133186474-b2d89bbc-32ed-4174-a05e-3d388c0a39ff.png",
|
||||
title: "18",
|
||||
description: "Aloe vera or aloe vera is a green plant, has thorns on the side of the skin with yellowish patches and...",
|
||||
slug: "happy-diet"
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const BlogsList = ({ data = BLOGSLIST_DATA }:BlogsListProps) => {
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
|
||||
const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => {
|
||||
|
||||
const DEFAULT_BLOGS_ARGS = useMemo(()=> ({
|
||||
excludeBlogIds: [idFeatured],
|
||||
options:{
|
||||
skip: 1, take: DEFAULT_BLOG_PAGE_SIZE
|
||||
}
|
||||
}),[idFeatured]);
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true)
|
||||
|
||||
const [optionQueryBlog, setOptionQueryBlog] = useState<QueryBlogs>(DEFAULT_BLOGS_ARGS)
|
||||
const { blogs, totalItems, loading } = useGetBlogList(optionQueryBlog);
|
||||
|
||||
|
||||
|
||||
const onPageChange = (page:number) => {
|
||||
setCurrentPage(page)
|
||||
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 (
|
||||
<section>
|
||||
<div className={s.wrapper}>
|
||||
{(!initialQueryFlag && loading && !blogs) && <ListBlogCardSkeleton count={DEFAULT_BLOG_PAGE_SIZE} isWrap />}
|
||||
<div className={s.list}>
|
||||
|
||||
{
|
||||
data.slice(currentPage*DEFAULT_BLOG_PAGE_SIZE,(currentPage+1)*DEFAULT_BLOG_PAGE_SIZE).map((product,index)=> {
|
||||
return(
|
||||
data?.map((product,index)=> {
|
||||
return(
|
||||
<div className={s.card} key={`${product.title}-${index}`}>
|
||||
<CardBlog {...product} />
|
||||
{product.isPublish && <CardBlog {...product} /> }
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div className={s.pagination}>
|
||||
<PaginationCommon total={data.length} pageSize={DEFAULT_BLOG_PAGE_SIZE} onChange={onPageChange}/>
|
||||
<PaginationCommon total={totalItems !== undefined ? totalItems : total} pageSize={DEFAULT_BLOG_PAGE_SIZE} onChange={onPageChange}/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlogsList
|
||||
export default BlogsList
|
||||
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import s from './FeaturedCardBlog.module.scss'
|
||||
import { Author, DateTime, ImgWithLink } from 'src/components/common'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
interface FeaturedCardBlogProps{
|
||||
title?: string,
|
||||
slug?:string,
|
||||
content?: string,
|
||||
imgSrc?: string,
|
||||
imgAuthor?: string,
|
||||
@@ -10,33 +12,31 @@ interface FeaturedCardBlogProps{
|
||||
authorName?: string,
|
||||
}
|
||||
|
||||
const FEATURED_DATA = {
|
||||
title: "Flammekueche with green asparagus",
|
||||
content: "Traditionally, the Flammekueche is made with rapeseed oil, which, contrary to popular belief, is indeed an oil that can be cooked hot and is not limited to seasoning. It is important to vary the oils in the kitchen to take advantage of the benefits of each. Rapeseed oil is an oil rich in omega 3 which participate in the proper functioning of the cardiovascular system as well as in vitamins E which contributes to the protection of cells against oxidative stress. In short, oils are your friends 😉",
|
||||
imgSrc: "https://user-images.githubusercontent.com/46085455/133186666-1ea8081f-4319-4617-8644-d20ed14b1825.png",
|
||||
imgAuthor: "https://user-images.githubusercontent.com/46085455/133186783-d0c71d43-b7bc-44b6-b560-818c71bd162f.png",
|
||||
date: "APRIL 30, 2021",
|
||||
author: "Alessandro Del Piero"
|
||||
}
|
||||
|
||||
const FeaturedCardBlog = ({
|
||||
title = FEATURED_DATA.title,
|
||||
content = FEATURED_DATA.content,
|
||||
imgSrc = FEATURED_DATA.imgSrc,
|
||||
imgAuthor = FEATURED_DATA.imgAuthor,
|
||||
date = FEATURED_DATA.date,
|
||||
authorName = FEATURED_DATA.author
|
||||
title,
|
||||
slug,
|
||||
content,
|
||||
imgSrc = '',
|
||||
imgAuthor = '',
|
||||
date = '',
|
||||
authorName = ''
|
||||
}: FeaturedCardBlogProps) => {
|
||||
return (
|
||||
<section className={s.featuredCard}>
|
||||
<div className={s.featuredCardWrapper}>
|
||||
<div className={s.left}>
|
||||
<ImgWithLink src={imgSrc} alt="image feature card"/>
|
||||
</div>
|
||||
<Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
|
||||
<a>
|
||||
<div className={s.left}>
|
||||
<ImgWithLink src={imgSrc} alt="image feature card"/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
<div className={s.right}>
|
||||
<div className={s.titleWrapper}>
|
||||
<DateTime date={date}/>
|
||||
<a className={s.title}>{title}</a>
|
||||
<DateTime date={date }/>
|
||||
<Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
|
||||
<a className={s.title}>{title}</a>
|
||||
</Link>
|
||||
</div>
|
||||
<Author name={authorName} image={imgAuthor}/>
|
||||
<div className={s.content}>{content}</div>
|
||||
|
@@ -184,6 +184,8 @@ export const FEATURED = [
|
||||
|
||||
export const DEFAULT_BLOG_PAGE_SIZE = 6;
|
||||
|
||||
export const DEFAULT_FEATURED_BLOG_PAGE_SIZE = 1;
|
||||
|
||||
export const FILTER_PAGE = [ROUTE.HOME, ROUTE.PRODUCTS]
|
||||
|
||||
export const STATE_OPTIONS = [
|
||||
|
@@ -143,6 +143,10 @@ export function getAllPromies(promies: PromiseWithKey[]) {
|
||||
return promies.map(item => item.promise)
|
||||
}
|
||||
|
||||
export function getIdFeaturedBlog(blog: BlogList) {
|
||||
return blog?.id
|
||||
}
|
||||
|
||||
export const FilterOneVatiant = (products:ProductCard[]) => {
|
||||
let idList:string[] = []
|
||||
let filtedProduct: ProductCard[]=[]
|
||||
@@ -173,4 +177,4 @@ export function getProductVariant(product: Product, opts: SelectedOptions) {
|
||||
)
|
||||
})
|
||||
return variant
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ export interface BlogProps {
|
||||
title: string
|
||||
slug: string
|
||||
description: string
|
||||
imageSrc: string
|
||||
imageSrc: string | null,
|
||||
}
|
||||
|
||||
export interface CheckOutForm {
|
||||
|
Reference in New Issue
Block a user