Update: Blog page featured layout ui, mobile menu padding and font change

This commit is contained in:
Wayne Sutton
2025-12-26 16:41:06 -08:00
parent bfe88d0217
commit b35dd17332
24 changed files with 748 additions and 73 deletions

View File

@@ -105,10 +105,25 @@ export const generateResponse = action({
},
returns: v.string(),
handler: async (ctx, args) => {
// Get API key
// Get API key - return friendly message if not configured
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error("API key is not set");
const notConfiguredMessage =
"**AI chat is not configured.**\n\n" +
"To enable AI responses, add your `ANTHROPIC_API_KEY` to the Convex environment variables.\n\n" +
"**Setup steps:**\n" +
"1. Get an API key from [Anthropic Console](https://console.anthropic.com/)\n" +
"2. Add it to Convex: `npx convex env set ANTHROPIC_API_KEY your-key-here`\n" +
"3. For production, set it in the [Convex Dashboard](https://dashboard.convex.dev/)\n\n" +
"See the [Convex environment variables docs](https://docs.convex.dev/production/environment-variables) for more details.";
// Save the message to chat history so it appears in the conversation
await ctx.runMutation(internal.aiChats.addAssistantMessage, {
chatId: args.chatId,
content: notConfiguredMessage,
});
return notConfiguredMessage;
}
// Get chat history

View File

@@ -25,6 +25,7 @@ export const getAllPosts = query({
rightSidebar: v.optional(v.boolean()),
showFooter: v.optional(v.boolean()),
footer: v.optional(v.string()),
blogFeatured: v.optional(v.boolean()),
}),
),
handler: async (ctx) => {
@@ -58,6 +59,53 @@ export const getAllPosts = query({
layout: post.layout,
rightSidebar: post.rightSidebar,
showFooter: post.showFooter,
blogFeatured: post.blogFeatured,
}));
},
});
// Get all blog featured posts for the /blog page (hero + featured row)
// Returns posts with blogFeatured: true, sorted by date descending
export const getBlogFeaturedPosts = query({
args: {},
returns: v.array(
v.object({
_id: v.id("posts"),
slug: v.string(),
title: v.string(),
description: v.string(),
date: v.string(),
tags: v.array(v.string()),
readTime: v.optional(v.string()),
image: v.optional(v.string()),
excerpt: v.optional(v.string()),
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
}),
),
handler: async (ctx) => {
const posts = await ctx.db
.query("posts")
.withIndex("by_blogFeatured", (q) => q.eq("blogFeatured", true))
.collect();
// Filter to only published posts and sort by date descending
const publishedFeatured = posts
.filter((p) => p.published)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return publishedFeatured.map((post) => ({
_id: post._id,
slug: post.slug,
title: post.title,
description: post.description,
date: post.date,
tags: post.tags,
readTime: post.readTime,
image: post.image,
excerpt: post.excerpt,
authorName: post.authorName,
authorImage: post.authorImage,
}));
},
});
@@ -194,6 +242,7 @@ export const syncPosts = internalMutation({
showFooter: v.optional(v.boolean()),
footer: v.optional(v.string()),
aiChat: v.optional(v.boolean()),
blogFeatured: v.optional(v.boolean()),
}),
),
},
@@ -239,6 +288,7 @@ export const syncPosts = internalMutation({
showFooter: post.showFooter,
footer: post.footer,
aiChat: post.aiChat,
blogFeatured: post.blogFeatured,
lastSyncedAt: now,
});
updated++;
@@ -288,6 +338,7 @@ export const syncPostsPublic = mutation({
showFooter: v.optional(v.boolean()),
footer: v.optional(v.string()),
aiChat: v.optional(v.boolean()),
blogFeatured: v.optional(v.boolean()),
}),
),
},
@@ -333,6 +384,7 @@ export const syncPostsPublic = mutation({
showFooter: post.showFooter,
footer: post.footer,
aiChat: post.aiChat,
blogFeatured: post.blogFeatured,
lastSyncedAt: now,
});
updated++;

View File

@@ -23,12 +23,14 @@ export default defineSchema({
showFooter: v.optional(v.boolean()), // Show footer on this post (overrides siteConfig default)
footer: v.optional(v.string()), // Footer markdown content (overrides siteConfig defaultContent)
aiChat: v.optional(v.boolean()), // Enable AI chat in right sidebar
blogFeatured: v.optional(v.boolean()), // Show as hero featured post on /blog page
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
.index("by_date", ["date"])
.index("by_published", ["published"])
.index("by_featured", ["featured"])
.index("by_blogFeatured", ["blogFeatured"])
.searchIndex("search_content", {
searchField: "content",
filterFields: ["published"],