diff --git a/.env b/.env index 509d982..2d0cc9f 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ NEXT_PUBLIC_SERVER_BASE_URL=https://api.bbuddy.expert/api NEXT_PUBLIC_AGORA_APPID=ed90c9dc42634e5687d4e2e0766b363f -CONTENTFUL_SPACE_ID = voxpxjq7y7vf -CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc -CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE \ No newline at end of file +NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf +NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc +NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index d1573dc..fe5f562 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -6,11 +6,6 @@ import {fetchBlogPost, fetchBlogPosts, Widget} from "../../../../lib/contentful/ import Util from "node:util"; import RichText from "../../../../lib/contentful/RichText"; -export const metadata: Metadata = { - title: 'Bbuddy - Blog item', - description: 'Bbuddy desc blog item' -}; - interface BlogPostPageParams { slug: string } @@ -19,11 +14,19 @@ interface BlogPostPageProps { params: BlogPostPageParams } -export async function generateStaticParams(): Promise { - const blogPosts = await fetchBlogPosts({ preview: false }) +export async function generateMetadata({ params }: BlogPostPageProps, parent: ResolvingMetadata): Promise { + const blogPost = await fetchBlogPost({ slug: params.slug, preview: draftMode().isEnabled }) - return blogPosts.map((post) => ({ slug: post.slug })) + if (!blogPost) { + return notFound() + } + + return { + title: blogPost.title + } } + + function renderWidget (widget: Widget) { switch (widget.type){ case 'widgetParagraph': diff --git a/src/app/[locale]/blog/category/[slug]/page.tsx b/src/app/[locale]/blog/category/[slug]/page.tsx index 696247c..2c897ed 100644 --- a/src/app/[locale]/blog/category/[slug]/page.tsx +++ b/src/app/[locale]/blog/category/[slug]/page.tsx @@ -5,6 +5,7 @@ import {unstable_setRequestLocale} from "next-intl/server"; import Link from "next/link"; import {fetchBlogPosts} from "../../../../../lib/contentful/blogPosts"; import {fetchBlogPostCategories} from "../../../../../lib/contentful/blogPostsCategories"; +import {BlogPosts} from "../../../../../components/BlogPosts/BlogPosts"; export const metadata: Metadata = { title: 'Bbuddy - Blog', @@ -19,83 +20,10 @@ interface BlogPostPageProps { params: BlogPostPageParams } -export default async function Blog({params}: { params: BlogPostPageParams }) { +export default async function Blog({params, searchParams}: { params: BlogPostPageParams, searhParams?: {page: number} }) { unstable_setRequestLocale(params.locale); - const data = await fetchBlogPosts({ preview: draftMode().isEnabled, locale: params.locale, category: params.slug }) - const cats = await fetchBlogPostCategories(false) + const page = searchParams.page || undefined return ( -
-
-
-

- Mentorship, Career
- Development & Coaching. -

-
-

The ins-and-outs of building a career in tech, gaining
experience

-

from a mentor, and getting your feet wet with coaching.

-
-
- -
-
-
-
-
-
- { - cats.map((cat, i)=>( - {cat.title} - )) - } -
-
-
-
-
-
- {data.map((item, i) => ( -
  • - -
    - {item.listImage?.alt}/ -
    -
    -
    -
    - {item.title} -
    -
    {item.category}
    -
    - {item.excerpt} -
    -
    -
    -
    - -
    -
    {item.author.name}
    -
    {item.createdAt}
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    - -
  • - ))} -
    -
    -
    -
    + ); } diff --git a/src/app/[locale]/blog/page.tsx b/src/app/[locale]/blog/page.tsx index 3ce264d..f23d5f1 100644 --- a/src/app/[locale]/blog/page.tsx +++ b/src/app/[locale]/blog/page.tsx @@ -5,91 +5,39 @@ import {fetchBlogPosts} from "../../../lib/contentful/blogPosts"; import {unstable_setRequestLocale} from "next-intl/server"; import Link from "next/link"; import {fetchBlogPostCategories} from "../../../lib/contentful/blogPostsCategories"; - -export const metadata: Metadata = { - title: 'Bbuddy - Blog', - description: 'Bbuddy desc blog' -}; +import {CustomPagination} from "../../../components/view/CustomPagination"; +import {DEFAULT_PAGE_SIZE} from "../../../constants/common"; +import {BlogPosts} from "../../../components/BlogPosts/BlogPosts"; +interface BlogPostPageParams { + slug: string +} -export default async function Blog({ params: { locale } }: { params: { locale: string } }) { +interface BlogPostPageProps { + params: BlogPostPageParams +} + +export async function generateStaticParams(): Promise { + const blogPosts = await fetchBlogPosts({ preview: false }) + + return blogPosts.data.map((post) => ({ slug: post.slug })) +} + + +export default async function Blog({ params: { locale }, searchParams }: { params: { locale: string }, searhParams?: {page: number} }) { unstable_setRequestLocale(locale); - const data = await fetchBlogPosts(false, locale) - const cats = await fetchBlogPostCategories(false) + const pageSize = DEFAULT_PAGE_SIZE + const page = searchParams.page || undefined + // BlogPosts('/'+locale+'/blog/', locale, pageSize) return ( -
    -
    -
    -

    - Mentorship, Career
    - Development & Coaching. -

    -
    -

    The ins-and-outs of building a career in tech, gaining
    experience

    -

    from a mentor, and getting your feet wet with coaching.

    -
    -
    - -
    -
    -
    -
    -
    -
    - { - cats.map((cat, i)=>( - {cat.title} - )) - } -
    -
    -
    -
    -
    -
    - {data.map((item, i) => ( -
  • - -
    - {item.listImage?.alt}/ -
    -
    -
    -
    - {item.title} -
    -
    {item.category}
    -
    - {item.excerpt} -
    -
    -
    -
    - -
    -
    {item.author.name}
    -
    {item.createdAt}
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    - -
  • - ))} -
    -
    -
    -
    + + + ); } diff --git a/src/app/sitemap.jsx b/src/app/sitemap.jsx new file mode 100644 index 0000000..84e710a --- /dev/null +++ b/src/app/sitemap.jsx @@ -0,0 +1,27 @@ +import {fetchBlogPosts} from "../lib/contentful/blogPosts"; + +export default async function sitemap() { + const paths = [ + { + url: process.env.NEXT_PUBLIC_HOST, + lastModified: new Date(), + changeFrequency: "monthly", + priority: 1 + } + ] + + + const blogPosts = await fetchBlogPosts({ preview: false }) + + blogPosts.data.forEach((item) => { + + paths.push({ + url: `${process.env.NEXT_PUBLIC_HOST}${item.slug}`, + lastModified: item.createdAt.split('T')[0], + changeFrequency: 'daily', + priority: '1.0' + }) + }) + + return paths +} \ No newline at end of file diff --git a/src/components/BlogPosts/BlogPostCategories.tsx b/src/components/BlogPosts/BlogPostCategories.tsx new file mode 100644 index 0000000..3f31eb3 --- /dev/null +++ b/src/components/BlogPosts/BlogPostCategories.tsx @@ -0,0 +1,35 @@ +'use client'; +import React, {useState} from "react"; +import {Languages} from "../../types/tags"; +import {BlogPostCategory} from "../../types/blogPostCategory"; +import Link from "next/link"; + +type Props = { + languages?: Languages; + basePath: string; + locale: string; + cats: BlogPostCategory[], + slug: string +}; + +export const BlogPostCategories = ({ basePath = '/', cats = [], slug = '' }: Props) => { + const [currentCat, setCurrentCat] = useState(slug); + return ( +
    +
    +
    + { + cats.map((cat, i)=>( + + {cat.title} + + )) + } +
    +
    +
    + ) +} diff --git a/src/components/BlogPosts/BlogPosts.tsx b/src/components/BlogPosts/BlogPosts.tsx new file mode 100644 index 0000000..0ec761c --- /dev/null +++ b/src/components/BlogPosts/BlogPosts.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { DEFAULT_PAGE_SIZE } from '../../constants/common'; +import {getLanguages} from "../../actions/tags"; +import {fetchBlogPosts} from "../../lib/contentful/blogPosts"; +import {fetchBlogPostCategories} from "../../lib/contentful/blogPostsCategories"; +import {BlogPostsList} from "./BlogPostsList"; +import {BlogPostCategories} from "./BlogPostCategories"; + +type PostsProps = { + basePath: string; + locale: string; + pageSize?: number; + currentCat: string; + page?: number +}; + +export const BlogPosts = async ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_SIZE, currentCat = '', page = 1 }: PostsProps) => { + const languages = await getLanguages(locale); + const {data, total} = await fetchBlogPosts({preview: false, category: currentCat, page: page}) + const cats = await fetchBlogPostCategories(false) + + return ( +
    +
    +
    +

    + Mentorship, Career
    + Development & Coaching +

    +
    +

    The ins-and-outs of building a career in tech, gaining
    experience

    +

    from a mentor, and getting your feet wet with coaching.

    +
    +
    + +
    +
    +
    + + +
    + ) +} \ No newline at end of file diff --git a/src/components/BlogPosts/BlogPostsList.tsx b/src/components/BlogPosts/BlogPostsList.tsx new file mode 100644 index 0000000..70f85da --- /dev/null +++ b/src/components/BlogPosts/BlogPostsList.tsx @@ -0,0 +1,79 @@ +'use client'; + +import React from 'react'; +import { DEFAULT_PAGE_SIZE } from '../../constants/common'; +import {Languages, SearchData} from "../../types/tags"; +import {BlogPost} from "../../types/blogPost"; +import Link from "next/link"; +import {CustomPagination} from "../view/CustomPagination"; + +type Props = { + searchData?: SearchData; + languages?: Languages; + basePath: string; + locale: string; + data: BlogPost[], + total: number +}; + +export const BlogPostsList = ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_SIZE, data = [], total= 0 }: Props) => { + const currentPage = 1 + const onChangePage = (page: number) => { + router.push(page === 1 ? basePath : basePath+'?page='+page); + }; + + return ( +
    +
    +
    + {data.map((item, i) => ( +
  • + +
    + {item.listImage?.alt}/ +
    +
    +
    +
    + {item.title} +
    +
    {item.category}
    +
    + {item.excerpt} +
    +
    +
    +
    + +
    +
    {item.author.name}
    +
    {item.createdAt}
    +
    +
    +
    +
    + + 165 +
    +
    + + Share +
    +
    +
    +
    + +
  • + ))} +
    + {total > pageSize && ( + )} +
    +
    + ) +} \ No newline at end of file diff --git a/src/lib/contentful/blogPosts.ts b/src/lib/contentful/blogPosts.ts index 6fa939b..415792c 100644 --- a/src/lib/contentful/blogPosts.ts +++ b/src/lib/contentful/blogPosts.ts @@ -8,7 +8,9 @@ import {WidgetMedia, WidgetMediaEntry} from "../../types/blogWidgets/widgetMedia import {WidgetParagraph} from "../../types/blogWidgets/widgetParagraph"; import entry from "next/dist/server/typescript/rules/entry"; import Util from "node:util"; +import {DEFAULT_PAGE_SIZE} from "../../constants/common"; +const pageSize = DEFAULT_PAGE_SIZE type PostEntry = BlogPostEntry//Entry type widgetEnum = WidgetParagraph | WidgetMedia export type Widget = { @@ -65,8 +67,12 @@ interface FetchBlogPostsOptions { preview: boolean local?: string category?: string + page?: number } -export async function fetchBlogPosts({ preview, category }: FetchBlogPostsOptions): Promise { +export async function fetchBlogPosts({ preview, category, page }: FetchBlogPostsOptions): Promise<{ + total: number; + data: BlogPost[] +}> { const contentful = contentfulClient({ preview }) const query = { content_type: 'blogPost', @@ -77,9 +83,19 @@ export async function fetchBlogPosts({ preview, category }: FetchBlogPostsOption query['fields.category.fields.slug'] = category query['fields.category.sys.contentType.sys.id']='blogPostCategory' } + + if(page){ + query['limit'] = pageSize + query['skip'] = pageSize * (page - 1) + } + const blogPostsResult = await contentful.getEntries(query) - return blogPostsResult.items.map((blogPostEntry) => parseContentfulBlogPost(blogPostEntry) as BlogPost) + const data = blogPostsResult.items.map((blogPostEntry) => parseContentfulBlogPost(blogPostEntry) as BlogPost) + return { + total: blogPostsResult.total, + data + } } interface FetchBlogPostOptions { diff --git a/src/lib/contentful/contentfulClient.ts b/src/lib/contentful/contentfulClient.ts index 61fcb2e..c8abf51 100644 --- a/src/lib/contentful/contentfulClient.ts +++ b/src/lib/contentful/contentfulClient.ts @@ -1,19 +1,19 @@ import { createClient } from 'contentful' -const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_PREVIEW_ACCESS_TOKEN } = process.env +const { NEXT_PUBLIC_CONTENTFUL_SPACE_ID, NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN, NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN } = process.env // This is the standard Contentful client. It fetches // content that has been published. const client = createClient({ - space: CONTENTFUL_SPACE_ID!, - accessToken: CONTENTFUL_ACCESS_TOKEN!, + space: NEXT_PUBLIC_CONTENTFUL_SPACE_ID!, + accessToken: NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!, }) // This is a Contentful client that's been configured // to fetch drafts and unpublished content. const previewClient = createClient({ - space: CONTENTFUL_SPACE_ID!, - accessToken: CONTENTFUL_PREVIEW_ACCESS_TOKEN!, + space: NEXT_PUBLIC_CONTENTFUL_SPACE_ID!, + accessToken: NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN!, host: 'preview.contentful.com', })