mirror of
				https://github.com/r-freeman/portfolio.git
				synced 2025-11-04 10:51:11 +00:00 
			
		
		
		
	Added Article navigation
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build And Publish / BuildAndPublish (push) Successful in 2m45s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build And Publish / BuildAndPublish (push) Successful in 2m45s
				
			This commit is contained in:
		
							parent
							
								
									9bd9188921
								
							
						
					
					
						commit
						5e8da79a82
					
				
							
								
								
									
										7
									
								
								app/api/articles/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/api/articles/route.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import {getAllArticles} from '@/lib/getAllArticles'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function GET(request: Request) {
 | 
				
			||||||
 | 
					    const articles = await getAllArticles(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Response(JSON.stringify(articles), {status: 200})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -5,6 +5,7 @@ import {Prose} from '@/components/ui/Prose'
 | 
				
			|||||||
import {Views} from '@/components/ui/Views'
 | 
					import {Views} from '@/components/ui/Views'
 | 
				
			||||||
import {ArrowDownIcon} from '@/components/icons/ArrowDownIcon'
 | 
					import {ArrowDownIcon} from '@/components/icons/ArrowDownIcon'
 | 
				
			||||||
import {formatDate} from '@/lib/formatDate'
 | 
					import {formatDate} from '@/lib/formatDate'
 | 
				
			||||||
 | 
					import ArticleNav from '@/components/ui/ArticleNav'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ArticleLayout = {
 | 
					type ArticleLayout = {
 | 
				
			||||||
    title: string
 | 
					    title: string
 | 
				
			||||||
@ -12,7 +13,6 @@ type ArticleLayout = {
 | 
				
			|||||||
    description: string
 | 
					    description: string
 | 
				
			||||||
    slug: string
 | 
					    slug: string
 | 
				
			||||||
    children?: ReactNode
 | 
					    children?: ReactNode
 | 
				
			||||||
    ogImage?: string
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const gradients = [
 | 
					const gradients = [
 | 
				
			||||||
@ -26,10 +26,8 @@ const gradients = [
 | 
				
			|||||||
export function ArticleLayout({
 | 
					export function ArticleLayout({
 | 
				
			||||||
                                  title,
 | 
					                                  title,
 | 
				
			||||||
                                  date,
 | 
					                                  date,
 | 
				
			||||||
                                  description,
 | 
					 | 
				
			||||||
                                  slug,
 | 
					                                  slug,
 | 
				
			||||||
                                  children,
 | 
					                                  children,
 | 
				
			||||||
                                  ogImage
 | 
					 | 
				
			||||||
                              }: ArticleLayout) {
 | 
					                              }: ArticleLayout) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
@ -60,6 +58,7 @@ export function ArticleLayout({
 | 
				
			|||||||
                        </header>
 | 
					                        </header>
 | 
				
			||||||
                        <Prose className="mt-8" data-mdx-content>{children}</Prose>
 | 
					                        <Prose className="mt-8" data-mdx-content>{children}</Prose>
 | 
				
			||||||
                    </article>
 | 
					                    </article>
 | 
				
			||||||
 | 
					                    <ArticleNav slug={slug}/>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </Container>
 | 
					        </Container>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										89
									
								
								components/ui/ArticleNav.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								components/ui/ArticleNav.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React, {ReactElement, useEffect, useState} from 'react'
 | 
				
			||||||
 | 
					import fetcher from '@/lib/fetcher'
 | 
				
			||||||
 | 
					import useSWR from 'swr'
 | 
				
			||||||
 | 
					import {Card} from '@/components/ui/Card'
 | 
				
			||||||
 | 
					import clsx from 'clsx'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Article = {
 | 
				
			||||||
 | 
					    slug: string
 | 
				
			||||||
 | 
					    authors: string
 | 
				
			||||||
 | 
					    title: string
 | 
				
			||||||
 | 
					    date: string
 | 
				
			||||||
 | 
					    description: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FetchArticlesResponse = {
 | 
				
			||||||
 | 
					    data: Article[]
 | 
				
			||||||
 | 
					    error: string
 | 
				
			||||||
 | 
					    isLoading: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ArticleNavProps = {
 | 
				
			||||||
 | 
					    slug: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PaginationProps = {
 | 
				
			||||||
 | 
					    next: Article | null
 | 
				
			||||||
 | 
					    prev: Article | null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useFetchArticles() {
 | 
				
			||||||
 | 
					    const {data, error, isLoading} = useSWR(`/api/articles/`, fetcher) as FetchArticlesResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        articles: data,
 | 
				
			||||||
 | 
					        isLoading,
 | 
				
			||||||
 | 
					        isError: error
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function ArticleNav({slug}: ArticleNavProps): ReactElement | null {
 | 
				
			||||||
 | 
					    const {articles, isLoading, isError} = useFetchArticles()
 | 
				
			||||||
 | 
					    const [{next, prev}, setPagination] = useState<PaginationProps>({next: null, prev: null})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        const findAdjacentArticles = (articles: Article[], slug: string) => {
 | 
				
			||||||
 | 
					            if (articles) {
 | 
				
			||||||
 | 
					                const index = articles.findIndex(article => article.slug === slug)
 | 
				
			||||||
 | 
					                const next = index < articles.length - 1 ? articles[index + 1] : null
 | 
				
			||||||
 | 
					                const prev = index > 0 ? articles[index - 1] : null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                setPagination({next, prev})
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        findAdjacentArticles(articles, slug)
 | 
				
			||||||
 | 
					    }, [articles, slug])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isError) return null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <section className="mt-24">
 | 
				
			||||||
 | 
					            <ul
 | 
				
			||||||
 | 
					                role="list"
 | 
				
			||||||
 | 
					                className={clsx('grid grid-cols-1 gap-x-12 gap-y-16',
 | 
				
			||||||
 | 
					                    (prev !== null && next !== null) ? 'sm:grid-cols-2' : '')}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                {prev !== null &&
 | 
				
			||||||
 | 
					                    <Card as="li" key={prev.slug}>
 | 
				
			||||||
 | 
					                        <h2 className="text-base font-semibold transition group-hover:text-indigo-500 text-zinc-800 dark:text-zinc-100">
 | 
				
			||||||
 | 
					                            <Card.Link href={`/writing/${prev.slug}`}
 | 
				
			||||||
 | 
					                                       ariaLabel={`Previous article: ${prev.title}`}>{prev.title}</Card.Link>
 | 
				
			||||||
 | 
					                        </h2>
 | 
				
			||||||
 | 
					                    </Card>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                {next !== null &&
 | 
				
			||||||
 | 
					                    <Card as="li" key={next.slug}>
 | 
				
			||||||
 | 
					                        <h2 className="text-base font-semibold transition group-hover:text-indigo-500 text-zinc-800 dark:text-zinc-100">
 | 
				
			||||||
 | 
					                            <Card.Link href={`/writing/${next.slug}`}
 | 
				
			||||||
 | 
					                                       ariaLabel={`Next article: ${next.title}`}>{next.title}</Card.Link>
 | 
				
			||||||
 | 
					                        </h2>
 | 
				
			||||||
 | 
					                    </Card>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					        </section>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -13,6 +13,7 @@ type Card = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type CardLink = {
 | 
					type CardLink = {
 | 
				
			||||||
    href: string
 | 
					    href: string
 | 
				
			||||||
 | 
					    ariaLabel?: string
 | 
				
			||||||
    children: ReactNode
 | 
					    children: ReactNode
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,12 +75,12 @@ export function Card({
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Card.Link = function CardLink({href, children}: CardLink) {
 | 
					Card.Link = function CardLink({href, children, ariaLabel}: CardLink) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
                className="absolute -inset-y-6 -inset-x-4 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl"/>
 | 
					                className="absolute -inset-y-6 -inset-x-4 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl"/>
 | 
				
			||||||
            <Link href={href}>
 | 
					            <Link href={href} aria-label={ariaLabel}>
 | 
				
			||||||
                <span className="absolute -inset-y-6 -inset-x-4 z-20 sm:-inset-x-6 sm:rounded-2xl"/>
 | 
					                <span className="absolute -inset-y-6 -inset-x-4 z-20 sm:-inset-x-6 sm:rounded-2xl"/>
 | 
				
			||||||
                <span className="relative z-10">{children}</span>
 | 
					                <span className="relative z-10">{children}</span>
 | 
				
			||||||
            </Link>
 | 
					            </Link>
 | 
				
			||||||
@ -89,7 +90,8 @@ Card.Link = function CardLink({href, children}: CardLink) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Card.Title = function CardTitle({as: Component = 'h2', href, children}: CardTitle) {
 | 
					Card.Title = function CardTitle({as: Component = 'h2', href, children}: CardTitle) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Component className="group-hover:text-indigo-500 text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
 | 
					        <Component
 | 
				
			||||||
 | 
					            className="group-hover:text-indigo-500 text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
 | 
				
			||||||
            {href ? <Card.Link href={href}>{children}</Card.Link> : children}
 | 
					            {href ? <Card.Link href={href}>{children}</Card.Link> : children}
 | 
				
			||||||
        </Component>
 | 
					        </Component>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
				
			|||||||
@ -12,12 +12,13 @@ async function importArticle(articleFilename: string) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getAllArticles() {
 | 
					export async function getAllArticles(dateDesc = true) {
 | 
				
			||||||
    let articleFilenames = await glob(['*.mdx', '*/page.mdx'], {
 | 
					    let articleFilenames = await glob(['*.mdx', '*/page.mdx'], {
 | 
				
			||||||
        cwd: path.join(process.cwd(), './app/writing'),
 | 
					        cwd: path.join(process.cwd(), './app/writing'),
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let articles = await Promise.all(articleFilenames.map(importArticle))
 | 
					    let articles = await Promise.all(articleFilenames.map(importArticle))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return articles.sort((a, z) => a.date < z.date ? 1 : -1)
 | 
					    return dateDesc ? articles.sort((a, z) => a.date < z.date ? 1 : -1)
 | 
				
			||||||
 | 
					        : articles.sort((a, z) => a.date > z.date ? 1 : -1)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user