This commit is contained in:
Auggie2lbcf
2025-06-13 17:54:41 -05:00
parent 2efadfea59
commit 3e82713e7a
11 changed files with 259 additions and 147 deletions
+5
View File
@@ -111,4 +111,9 @@ export async function getPostData(id: string): Promise<PostData> {
export function getPostsByTag(tag: string): PostMetadata[] {
const allPosts = getSortedPostsData();
return allPosts.filter(post => post.tags.includes(tag));
}
export function getPostsByAuthor(author: string): PostMetadata[] {
const allPosts = getSortedPostsData();
return allPosts.filter(post => post.author.includes(author));
}
+155
View File
@@ -0,0 +1,155 @@
import React, { useEffect, useState } from 'react';
import { GetStaticProps, GetStaticPaths } from 'next';
import Layout from '@/components/Layout';
import PostCard from '@/components/PostCard';
import { getSortedPostsData, getPostsByAuthor } from '@/lib/markdown';
import { PostMetadata } from '@/types';
import { supabase } from '@/utils/supabase';
interface AuthorProfile {
name: string;
bio: string;
x_link?: string;
fb_link?: string;
insta_link?: string;
pfp_link?: string;
}
interface AuthorPageProps {
posts: PostMetadata[];
author: string;
}
const AuthorPage: React.FC<AuthorPageProps> = ({ posts, author }) => {
const [authorProfile, setAuthorProfile] = useState<AuthorProfile | null>(null);
const fetchAuthorProfile = async () => {
const { data, error } = await supabase
.from('authors')
.select('name, bio, x_link, fb_link, insta_link, pfp_link')
.eq('name', author) // Or use slug if you've switched to that
.single();
if (error) {
console.warn('No author profile found for:', author, '→', error.message);
}
if (data) {
setAuthorProfile(data);
}
};
useEffect(() => {
fetchAuthorProfile();
}, [author]);
return (
<Layout title={`${authorProfile?.name || author} | Confessions of Grace`}>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="mb-12 flex flex-col md:flex-row items-start md:items-center gap-6">
{/* Profile Picture */}
{authorProfile?.pfp_link ? (
<img
src={authorProfile.pfp_link}
alt={`${authorProfile.name}'s profile picture`}
className="w-24 h-24 rounded-full object-cover shadow-md"
/>
) : (
<div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center text-xl font-bold text-gray-500">
?
</div>
)}
<div>
{/* Author Name */}
<h1 className="text-3xl md:text-4xl font-bold mb-2">
{authorProfile?.name || author}
</h1>
{/* Author Bio */}
{authorProfile?.bio && (
<p className="text-primary-600 mb-2">{authorProfile.bio}</p>
)}
{/* Social Links */}
<div className="flex gap-4 mt-2">
{authorProfile?.x_link && (
<a
href={authorProfile.x_link}
target="_blank"
rel="noopener noreferrer"
>
<img src="/icons/x.svg" alt="X (Twitter)" className="w-5 h-5" />
</a>
)}
{authorProfile?.fb_link && (
<a
href={authorProfile.fb_link}
target="_blank"
rel="noopener noreferrer"
>
<img src="/icons/facebook.svg" alt="Facebook" className="w-5 h-5" />
</a>
)}
{authorProfile?.insta_link && (
<a
href={authorProfile.insta_link}
target="_blank"
rel="noopener noreferrer"
>
<img src="/icons/instagram.svg" alt="Instagram" className="w-5 h-5" />
</a>
)}
</div>
</div>
</div>
{/* Posts */}
<p className="text-lg text-primary-600 mb-6">
{posts.length} {posts.length === 1 ? 'post' : 'posts'} authored by{' '}
"{authorProfile?.name || author}"
</p>
{posts.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
) : (
<div className="text-center py-12">
<p className="text-xl text-primary-500">No posts found for this author.</p>
</div>
)}
</div>
</Layout>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const posts = getSortedPostsData();
const authors = Array.from(new Set(posts.flatMap(post => post.author)));
const paths = authors.map((author) => ({
params: { author },
}));
return {
paths,
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const author = params?.author as string;
const posts = getPostsByAuthor(author);
return {
props: {
posts,
author,
},
};
};
export default AuthorPage;
+77
View File
@@ -0,0 +1,77 @@
import React from 'react';
import { GetStaticProps } from 'next';
import Link from 'next/link';
import Image from 'next/image';
import Layout from '@/components/Layout';
import { supabase } from '@/utils/supabase';
interface AuthorProfile {
name: string;
bio: string;
x_link?: string;
fb_link?: string;
insta_link?: string;
pfp_link?: string;
}
interface AuthorsPageProps {
authors: AuthorProfile[];
}
const AuthorsPage: React.FC<AuthorsPageProps> = ({ authors }) => {
return (
<Layout title="Authors | Confessions of Grace">
<div className="max-w-5xl mx-auto px-4">
<h1 className="text-4xl font-bold mb-10 text-center">Meet the Authors</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{authors.map((author) => (
<Link
key={author.name}
href={`/authors/${author.name}`}
className="bg-white rounded-lg shadow-md p-5 flex flex-col items-center hover:shadow-lg transition-shadow"
>
<div className="w-24 h-24 mb-4 relative">
<Image
src={author.pfp_link || '/images/authors/default.jpg'}
alt={`${author.name}'s profile`}
fill
className="rounded-full object-cover"
sizes="96px"
/>
</div>
<h2 className="text-lg font-semibold">{author.name}</h2>
{/* You can include bio or social icons here */}
</Link>
))}
</div>
<div className="mt-12 text-center">
<Link href="/archive" className="button">
View All Posts
</Link>
</div>
</div>
</Layout>
);
};
export const getStaticProps: GetStaticProps = async () => {
const { data: authorsData, error } = await supabase
.from('authors')
.select('name, bio, x_link, fb_link, insta_link, pfp_link');
if (error || !authorsData) {
console.error('Error fetching authors:', error);
return { props: { authors: [] } };
}
return {
props: {
authors: authorsData,
},
revalidate: 60,
};
};
export default AuthorsPage;
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: "My Top 25 Books (Outside the Bible) in No Particular Order"
date: "2025-04-22"
author: "@Auggie2LBCF"
author: "Auggie2LBCF"
excerpt: "A curated list of my top 25 books (outside the Bible), spanning theology, Christian living, and even some fiction. These works have deeply shaped my faith, ministry, and personal growth."
tags: ["books", "fiction", "theology", "personal life"]
coverImage: "/images/25-books.png"
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: "Review - A Certain Sound by Ryan Denton"
date: "2025-05-09"
author: "@Auggie2LBCF"
author: "Auggie2LBCF"
excerpt: "The title itself, A Certain Sound, draws from 1 Corinthians 14:8, which states, \"For if the trumpet give an uncertain sound, who shall prepare himself to the battle?\""
tags: ["books", "review", "evangelism", "preaching"]
coverImage: "/images/a-certain-sound.jpg"
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: "Against Ethnocentric Christian Nationalism"
date: "2025-04-05"
author: "@Auggie2LBCF"
author: "Auggie2LBCF"
excerpt: "While Christian principles have historically informed public life within Reformed traditions, Reformed theology itself, grounded in doctrines like the Imago Dei, the universal scope of redemption, and the nature of the Church, stands in opposition to ethnocentric expressions of Christian Nationalism."
tags: ["christianity", "nationalism", "theology", "ethics", "identity", "political philosophy", "church and state"]
coverImage: "/images/templar.jpeg"
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: "Frank Turek & The Fourfold State of Man"
date: "2025-03-18"
author: "@Auggie2LBCF"
author: "Auggie2LBCF"
excerpt: "The 'contradiction' that Geisler suggests, is not a contradiction but an incomplete understanding of the abilities of Adam, the abilities of Adam's offspring, and the abilities of those who receive God's abundant provision of grace, who are given the responsibility to freely offer that abundant provision of grace to all those who are Adam's offspring."
tags: ["sin", "calvinism", "creation", "fourfold"]
coverImage: "/images/thomas-boston.jpg"
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: "Valley of Vision - Part 1: Adoration"
date: "2025-05-13"
author: "@Auggie2LBCF"
author: "Auggie2LBCF"
excerpt: "I must reverently adore God, as a Being transcendently bright and blessed, self-existent and self-sufficient, an infinite and eternal Spirit who has all perfections in himself, and give him the glory of his titles and attributes."
tags: ["the valley of vision", "prayer", "puritans", "adoration"]
coverImage: "/images/valley-of-vision.png"