Compare commits
2 commits
9ebd996a49
...
43eec03edd
Author | SHA1 | Date | |
---|---|---|---|
43eec03edd | |||
935aceb794 |
5 changed files with 214 additions and 214 deletions
|
@ -1,9 +1,9 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card"
|
} from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Link } from "react-router"
|
import { Link } from "react-router"
|
||||||
|
@ -11,45 +11,45 @@ import { Link } from "react-router"
|
||||||
import { convertDate } from "@/utils/convertDate"
|
import { convertDate } from "@/utils/convertDate"
|
||||||
|
|
||||||
const PostListing = ({ posts, title, showAllButton }) => {
|
const PostListing = ({ posts, title, showAllButton }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-5 ">
|
<div className="mb-5 ">
|
||||||
<h2 className="scroll-m-20 text-2xl font-bold lg:text-2xl border-b pb-3">
|
<h2 className="scroll-m-20 text-2xl font-semibold lg:text-2xl border-b pb-3">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-1 gap-3">
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/posts/${post.slug}`}
|
to={`/posts/${post.slug}`}
|
||||||
key={post.slug}
|
key={post.slug}
|
||||||
className="block no-underline"
|
className="block no-underline"
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
key={post.slug}
|
key={post.slug}
|
||||||
className="flex flex-col h-full hover:bg-primary/5 py-4 px-0"
|
className="flex flex-col h-full hover:bg-primary/5 py-4 rounded-none"
|
||||||
>
|
>
|
||||||
<CardHeader className="px-4 md:px-6">
|
<CardHeader className="">
|
||||||
<CardTitle className="leading-snug font-semibold ">
|
<CardTitle className="leading-snug font-semibold ">
|
||||||
{post.title}
|
{post.title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-sm text-muted-foreground">
|
<CardDescription className="text-sm text-muted-foreground">
|
||||||
<div className="flex justify-between gap-2">
|
<div className="flex justify-between gap-2">
|
||||||
<span className="text-sm">{convertDate(post.date)}</span>
|
<span className="text-sm">{convertDate(post.date)}</span>
|
||||||
</div>
|
</div>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{showAllButton && (
|
{showAllButton && (
|
||||||
<Button asChild variant="" className="w-full mt-4">
|
<Button asChild variant="" className="w-full mt-4">
|
||||||
<Link to="/posts/page/1">View all</Link>
|
<Link to="/posts/page/1">View all</Link>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PostListing
|
export default PostListing
|
||||||
|
|
|
@ -5,7 +5,7 @@ const AboutPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="mb-5 ">
|
<div className="mb-5 ">
|
||||||
<h2 className="scroll-m-20 text-2xl font-bold lg:text-2xl border-b pb-3">
|
<h2 className="scroll-m-20 text-2xl font-semibold lg:text-2xl border-b pb-3">
|
||||||
About
|
About
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,36 +6,36 @@ import { Button } from "@/components/ui/button"
|
||||||
import { Link } from "react-router"
|
import { Link } from "react-router"
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<Card className="mb-8">
|
<Card className="mb-8 rounded-none">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<h1 className="scroll-m-20 text-left text-3xl font-bold text-balance">
|
<h1 className="scroll-m-20 text-left text-3xl font-semibold">
|
||||||
Another software engineer with a blog
|
Another software engineer with a blog
|
||||||
</h1>
|
</h1>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="leading-[1.5] [&:not(:first-child)]:mt-6">
|
<p className="leading-[1.5] [&:not(:first-child)]:mt-6">
|
||||||
I'm a self-taught software engineer currently working at ITV,
|
I'm a self-taught software engineer currently working at ITV,
|
||||||
previously at the BBC. This blog is a technical scrapbook and
|
previously at the BBC. This blog is a technical scrapbook and
|
||||||
digital garden.
|
digital garden.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
<Link to="/about">About</Link>
|
<Link to="/about">About</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<PostListing
|
<PostListing
|
||||||
title="Recent posts"
|
title="Recent posts"
|
||||||
posts={posts.slice(0, 5)}
|
posts={posts.slice(0, 5)}
|
||||||
showAllButton
|
showAllButton
|
||||||
/>
|
/>
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { HomePage }
|
export { HomePage }
|
||||||
|
|
|
@ -4,128 +4,128 @@ import MainTemplate from "@/templates/MainTemplate"
|
||||||
import { useParams, useNavigate } from "react-router"
|
import { useParams, useNavigate } from "react-router"
|
||||||
import { convertDate } from "@/utils/convertDate"
|
import { convertDate } from "@/utils/convertDate"
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
PaginationItem,
|
PaginationItem,
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination"
|
} from "@/components/ui/pagination"
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card"
|
} from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Link } from "react-router"
|
import { Link } from "react-router"
|
||||||
import { usePosts } from "@/hooks/usePosts"
|
import { usePosts } from "@/hooks/usePosts"
|
||||||
|
|
||||||
const PostsPage = () => {
|
const PostsPage = () => {
|
||||||
const { page } = useParams()
|
const { page } = useParams()
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
const postsPerPage = 10
|
const postsPerPage = 10
|
||||||
|
|
||||||
const currentPage = Number(page) || 1
|
const currentPage = Number(page) || 1
|
||||||
const totalPages = Math.ceil(posts.length / postsPerPage)
|
const totalPages = Math.ceil(posts.length / postsPerPage)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPage < 1 || currentPage > totalPages) {
|
if (currentPage < 1 || currentPage > totalPages) {
|
||||||
navigate(`/posts/page/1`, { replace: true })
|
navigate(`/posts/page/1`, { replace: true })
|
||||||
}
|
}
|
||||||
}, [currentPage, totalPages, navigate])
|
}, [currentPage, totalPages, navigate])
|
||||||
|
|
||||||
const currentPosts = useMemo(() => {
|
const currentPosts = useMemo(() => {
|
||||||
const startIndex = (currentPage - 1) * postsPerPage
|
const startIndex = (currentPage - 1) * postsPerPage
|
||||||
const endIndex = startIndex + postsPerPage
|
const endIndex = startIndex + postsPerPage
|
||||||
return posts.slice(startIndex, endIndex)
|
return posts.slice(startIndex, endIndex)
|
||||||
}, [posts, currentPage, postsPerPage])
|
}, [posts, currentPage, postsPerPage])
|
||||||
|
|
||||||
const hasNextPage = currentPage < totalPages
|
const hasNextPage = currentPage < totalPages
|
||||||
const hasPrevPage = currentPage > 1
|
const hasPrevPage = currentPage > 1
|
||||||
|
|
||||||
const goToNextPage = () => {
|
const goToNextPage = () => {
|
||||||
if (hasNextPage) {
|
if (hasNextPage) {
|
||||||
navigate(`/posts/page/${currentPage + 1}`)
|
navigate(`/posts/page/${currentPage + 1}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToPrevPage = () => {
|
const goToPrevPage = () => {
|
||||||
if (hasPrevPage) {
|
if (hasPrevPage) {
|
||||||
navigate(`/posts/page/${currentPage - 1}`)
|
navigate(`/posts/page/${currentPage - 1}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="mb-5 ">
|
<div className="mb-5 ">
|
||||||
<h2 className="scroll-m-20 text-2xl font-bold lg:text-2xl border-b pb-3">
|
<h2 className="scroll-m-20 text-2xl font-semibold lg:text-2xl border-b pb-3">
|
||||||
All posts
|
All posts
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-[calc(100vh-200px)] flex flex-col">
|
<div className="min-h-[calc(100vh-200px)] flex flex-col">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-1 gap-3 flex-grow">
|
<div className="grid grid-cols-1 md:grid-cols-1 gap-3 flex-grow">
|
||||||
{currentPosts.map((post) => (
|
{currentPosts.map((post) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/posts/${post.slug}`}
|
to={`/posts/${post.slug}`}
|
||||||
key={post.slug}
|
key={post.slug}
|
||||||
className="block no-underline"
|
className="block no-underline"
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
key={post.slug}
|
key={post.slug}
|
||||||
className="flex flex-col h-full hover:bg-primary/5 py-4 px-0"
|
className="flex flex-col h-full hover:bg-primary/5 py-4 px-0 rounded-none"
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="leading-snug font-semibold">
|
<CardTitle className="leading-snug font-semibold">
|
||||||
{post.title}
|
{post.title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-sm text-muted-foreground">
|
<CardDescription className="text-sm text-muted-foreground">
|
||||||
<div className="flex justify-between gap-2">
|
<div className="flex justify-between gap-2">
|
||||||
<span className="text-sm">{convertDate(post.date)}</span>
|
<span className="text-sm">{convertDate(post.date)}</span>
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
{post.tags.map((tag, i) => (
|
{post.tags.map((tag, i) => (
|
||||||
<Badge
|
<Badge
|
||||||
className="ml-2 cursor-pointer"
|
className="ml-2 cursor-pointer"
|
||||||
key={i}
|
key={i}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
navigate(`/tags/${tag}`)
|
navigate(`/tags/${tag}`)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Pagination className="mt-4">
|
<Pagination className="mt-4">
|
||||||
<PaginationContent>
|
<PaginationContent>
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationPrevious
|
<PaginationPrevious
|
||||||
className={`select-none ${hasPrevPage ? "cursor-pointer" : "cursor-not-allowed opacity-50"}`}
|
className={`select-none ${hasPrevPage ? "cursor-pointer" : "cursor-not-allowed opacity-50"}`}
|
||||||
onClick={goToPrevPage}
|
onClick={goToPrevPage}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationNext
|
<PaginationNext
|
||||||
className={`select-none ${hasNextPage ? "cursor-pointer" : "cursor-not-allowed opacity-50"}`}
|
className={`select-none ${hasNextPage ? "cursor-pointer" : "cursor-not-allowed opacity-50"}`}
|
||||||
onClick={goToNextPage}
|
onClick={goToNextPage}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
</Pagination>
|
</Pagination>
|
||||||
</div>
|
</div>
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { PostsPage }
|
export { PostsPage }
|
||||||
|
|
|
@ -6,37 +6,37 @@ import { convertDate } from "@/utils/convertDate"
|
||||||
import { usePosts } from "@/hooks/usePosts"
|
import { usePosts } from "@/hooks/usePosts"
|
||||||
|
|
||||||
const BlogTemplate = () => {
|
const BlogTemplate = () => {
|
||||||
const { slug } = useParams()
|
const { slug } = useParams()
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
const post = posts?.find((x) => x.slug === slug)
|
const post = posts?.find((x) => x.slug === slug)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
{!post ? (
|
{!post ? (
|
||||||
<div>Loading...</div>
|
<div>Loading...</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<h2 className="text-2xl font-bold lg:text-2xl border-b pb-3">
|
<h2 className="text-2xl font-semibold lg:text-2xl border-b pb-3">
|
||||||
{post?.title}
|
{post?.title}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex md:flex-row md:justify-between flex-col mb-8">
|
<div className="flex md:flex-row md:justify-between flex-col mb-8">
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{convertDate(post?.date)}
|
{convertDate(post?.date)}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-1 mt-3 md:mt-0">
|
<div className="flex gap-1 mt-3 md:mt-0">
|
||||||
{post?.tags?.map((tag, i) => (
|
{post?.tags?.map((tag, i) => (
|
||||||
<Badge asChild variant="secondary">
|
<Badge asChild variant="secondary">
|
||||||
<Link key={i} to={`/tags/${tag}`}>
|
<Link key={i} to={`/tags/${tag}`}>
|
||||||
{tag}
|
{tag}
|
||||||
</Link>
|
</Link>
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="
|
className="
|
||||||
[&>h2]:text-xl [&>h2]:border-b [&>h2]:pb-2 [&>h2]:font-semibold [&>h2]:first:mt-0 [&>h2:not(:first-child)]:mt-8
|
[&>h2]:text-xl [&>h2]:border-b [&>h2]:pb-2 [&>h2]:font-semibold [&>h2]:first:mt-0 [&>h2:not(:first-child)]:mt-8
|
||||||
[&>h3]:text-xl [&>h3]:sm:text-1xl [&>h3]:font-semibold [&>h3:not(:first-child)]:mt-5
|
[&>h3]:text-xl [&>h3]:sm:text-1xl [&>h3]:font-semibold [&>h3:not(:first-child)]:mt-5
|
||||||
[&>h4]:text-lg [&>h4]:sm:text-xl [&>h4]:font-semibold [&>h4:not(:first-child)]:mt-4
|
[&>h4]:text-lg [&>h4]:sm:text-xl [&>h4]:font-semibold [&>h4:not(:first-child)]:mt-4
|
||||||
|
@ -66,12 +66,12 @@ const BlogTemplate = () => {
|
||||||
[&>figure>figcaption]:text-sm [&>figure>figcaption]:text-muted-foreground [&>figure>figcaption]:mt-3 [&>figure>figcaption]:text-center
|
[&>figure>figcaption]:text-sm [&>figure>figcaption]:text-muted-foreground [&>figure>figcaption]:mt-3 [&>figure>figcaption]:text-center
|
||||||
[&>figure>figcaption>a]:text-primary [&>figure>figcaption>a:hover]:text-primary/80
|
[&>figure>figcaption>a]:text-primary [&>figure>figcaption>a:hover]:text-primary/80
|
||||||
"
|
"
|
||||||
dangerouslySetInnerHTML={{ __html: post?.html }}
|
dangerouslySetInnerHTML={{ __html: post?.html }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BlogTemplate
|
export default BlogTemplate
|
||||||
|
|
Loading…
Add table
Reference in a new issue