Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions app/blogs/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { BlogContent } from "@/lib/notion-blog";
import { formatDate } from "@/lib/blog-utils";
import { getImageMetadata } from "@/lib/image-utils";
import {
AuthorAvatar,
BlogAuthorBio,
BlogRelatedPosts,
BlogShareButtons,
Expand Down Expand Up @@ -592,17 +593,10 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
<div className="flex flex-wrap items-center gap-4 mb-8">
{/* Author */}
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-linear-to-br from-accent to-accent-secondary flex items-center justify-center shrink-0">
<span className="text-sm font-bold text-white">
{post.author.name.charAt(0)}
</span>
</div>
<div>
<p className="text-sm font-medium text-text-primary">
{post.author.name}
</p>
<p className="text-xs text-text-muted">{post.author.role}</p>
</div>
<AuthorAvatar author={post.author} size="sm" />
<p className="text-sm font-medium text-text-primary">
{post.author.name}
</p>
</div>

{/* Separator */}
Expand Down Expand Up @@ -785,7 +779,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {

{/* Related Posts */}
{relatedPosts.length > 0 && (
<section className="relative py-12 sm:py-16 bg-surface">
<section className="relative py-16 sm:py-24 bg-surface">
<div className="max-w-7xl mx-auto px-4 sm:px-6">
<BlogRelatedPosts posts={relatedPosts} />
</div>
Expand Down
53 changes: 39 additions & 14 deletions components/blog/BlogAuthorBio.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
"use client";

import { m } from "framer-motion";
import Image from "next/image";
import { Author } from "@/lib/blog-types";

interface BlogAuthorBioProps {
author: Author;
}

const DEFAULT_AVATAR = "/team/default.jpg";

function AuthorAvatar({ author, size }: { author: Author; size: "sm" | "lg" }) {
const hasPhoto = author.avatar && author.avatar !== DEFAULT_AVATAR;
const px = size === "lg" ? 80 : 40;
const wrapperClass = size === "lg"
? "w-20 h-20 rounded-full shrink-0 overflow-hidden"
: "w-10 h-10 rounded-full shrink-0 overflow-hidden";

if (hasPhoto) {
return (
<div className={wrapperClass}>
<Image
src={author.avatar}
alt={author.name}
width={px}
height={px}
className="w-full h-full object-cover"
/>
</div>
);
}

const textSize = size === "lg" ? "text-2xl" : "text-sm";
return (
<div className={`${wrapperClass} bg-gradient-to-br from-accent to-accent-secondary flex items-center justify-center`}>
<span className={`${textSize} font-bold text-white`}>
{author.name.charAt(0)}
</span>
</div>
);
}

export { AuthorAvatar };

export function BlogAuthorBio({ author }: BlogAuthorBioProps) {
return (
<m.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
<div
className="mt-16 p-6 sm:p-8 rounded-2xl bg-surface-elevated border border-border"
>
<div className="flex flex-col sm:flex-row gap-6">
{/* Avatar */}
<div className="w-20 h-20 rounded-full bg-gradient-to-br from-accent to-accent-secondary flex items-center justify-center flex-shrink-0">
<span className="text-2xl font-bold text-white">
{author.name.charAt(0)}
</span>
</div>
<AuthorAvatar author={author} size="lg" />

{/* Content */}
<div className="flex-1">
Expand Down Expand Up @@ -80,6 +105,6 @@ export function BlogAuthorBio({ author }: BlogAuthorBioProps) {
</div>
</div>
</div>
</m.div>
</div>
);
}
12 changes: 2 additions & 10 deletions components/blog/BlogCTA.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"use client";

import Link from "next/link";
import { m } from "framer-motion";

interface CTAContent {
headingLine1: string;
Expand Down Expand Up @@ -81,12 +78,7 @@ export function BlogCTA({ categorySlug }: BlogCTAProps) {
return (
<section className="relative py-16 sm:py-24 bg-base">
<div className="max-w-4xl mx-auto px-4 sm:px-6 text-center">
<m.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<div>
<h2 className="text-2xl sm:text-3xl md:text-5xl font-bold text-text-primary mb-6">
{cta.headingLine1}
<br />
Expand Down Expand Up @@ -172,7 +164,7 @@ export function BlogCTA({ categorySlug }: BlogCTAProps) {
Talk with engineers, not sales
</div>
</div>
</m.div>
</div>
</div>
</section>
);
Expand Down
6 changes: 3 additions & 3 deletions components/blog/BlogPostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Link from "next/link";
import Image from "next/image";
import { m } from "framer-motion";
import { motion } from "framer-motion";
import { BlogPost } from "@/lib/blog-types";
import {
formatDateShort,
Expand All @@ -19,7 +19,7 @@ export function BlogPostCard({ post, index = 0 }: BlogPostCardProps) {
const categoryColors = getCategoryColor(post.category.color);

return (
<m.article
<motion.article
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
Expand Down Expand Up @@ -134,6 +134,6 @@ export function BlogPostCard({ post, index = 0 }: BlogPostCardProps) {
</div>
</div>
</Link>
</m.article>
</motion.article>
);
}
15 changes: 3 additions & 12 deletions components/blog/BlogRelatedPosts.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
"use client";

import { m } from "framer-motion";
import { BlogPost } from "@/lib/blog-types";
import { BlogPostCard } from "./BlogPostCard";

Expand All @@ -18,21 +15,15 @@ export function BlogRelatedPosts({
}

return (
<section className="mt-20 pt-16 border-t border-border">
<m.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="text-center mb-12"
>
<section>
<div className="text-center mb-12">
<h2 className="text-2xl sm:text-3xl font-bold text-text-primary">
{title}
</h2>
<p className="text-text-secondary mt-2">
More insights from the Procedure engineering team
</p>
</m.div>
</div>

<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8">
{posts.map((post, idx) => (
Expand Down
2 changes: 1 addition & 1 deletion components/blog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export { BlogGrid } from "./BlogGrid";
export { BlogCTA } from "./BlogCTA";
export { BlogPostContent } from "./BlogPostContent";
export { BlogTableOfContents } from "./BlogTableOfContents";
export { BlogAuthorBio } from "./BlogAuthorBio";
export { BlogAuthorBio, AuthorAvatar } from "./BlogAuthorBio";
export { BlogRelatedPosts } from "./BlogRelatedPosts";
export { BlogShareButtons } from "./BlogShareButtons";
export type { TOCHeading } from "./BlogTableOfContents";
2 changes: 1 addition & 1 deletion components/ui/tracing-beam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const TracingBeam = ({
return (
<motion.div
ref={ref}
className={cn("relative mx-auto h-full w-full max-w-4xl", className)}
className={cn("relative mx-auto w-full max-w-4xl", className)}
>
<div className="absolute top-3 -left-4 md:-left-20">
<motion.div
Expand Down
18 changes: 14 additions & 4 deletions lib/notion-blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
BlockObjectResponse,
ListBlockChildrenResponse,
} from "@notionhq/client/build/src/api-endpoints";
import { cacheBlogCover, cacheContentImages } from "./notion-image-cache";
import { cacheBlogCover, cacheAuthorPhoto, cacheContentImages } from "./notion-image-cache";

// =============================================================================
// Extended Types for Blog Detail Pages
Expand Down Expand Up @@ -238,7 +238,8 @@ const AUTHOR_MAP: Record<string, Author> = {
function mapAuthor(
authorName: string | null,
authorBio?: string,
authorTitle?: string
authorTitle?: string,
authorAvatar?: string | null
): Author {
if (!authorName) {
return AUTHOR_MAP["Procedure Team"];
Expand All @@ -250,12 +251,13 @@ function mapAuthor(
// Override with Notion values if provided
bio: authorBio || existingAuthor.bio,
role: authorTitle || existingAuthor.role,
avatar: authorAvatar || existingAuthor.avatar,
};
}
return {
id: authorName.toLowerCase().replace(/\s+/g, "-"),
name: authorName,
avatar: "/team/default.jpg",
avatar: authorAvatar || "/team/default.jpg",
role: authorTitle || "Engineer",
bio: authorBio || "",
};
Expand Down Expand Up @@ -346,6 +348,12 @@ async function transformNotionPageToBlogPost(
const readTime = getNumber(props["Read Time"]) || 5;
// Slug property (renamed from URL) - use rich text Slug as primary
const customSlug = getRichText(props["Slug"]) || getUrl(props["URL"]);
// Author photo from Notion files property
const authorImageUrl =
getFiles(props["Author image"]) ||
getFiles(props["Author Image"]) ||
getFiles(props["Author Photo"]);

// Cover image from Notion files property (primary) with fallbacks
const featuredImage =
getFiles(props["Cover"]) ||
Expand All @@ -369,7 +377,9 @@ async function transformNotionPageToBlogPost(

// Map category and author
const category = mapCategory(categoryName);
const author = mapAuthor(authorName, authorBio, authorTitle);
const authorId = authorName?.toLowerCase().replace(/\s+/g, "-") || "procedure-team";
const cachedAuthorPhoto = await cacheAuthorPhoto(authorImageUrl, authorId);
const author = mapAuthor(authorName, authorBio, authorTitle, cachedAuthorPhoto);

// Cache cover image to public folder (downloads from Notion and saves locally)
const cachedFeaturedImage = await cacheBlogCover(featuredImage, slug);
Expand Down
Loading
Loading