mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2026-10-15 11:15:45 -04:00
refactor: simplify comment form state, auth client exports, and feed builder
- Replace CommentFormContext/Provider with a plain `useCommentFormState` hook; the context abstraction was unused outside the file - Narrow auth-client exports to only `signIn`; `authClient` and `useSession` are no longer exported - Add `getPosts()` as a canonical sorted-posts helper and use it in `getFrontMatter` and `buildFeed`, removing the redundant sort in the latter - Drop JSON feed support from `createFeedHandler` since no JSON feed route exists - Rewrite `FrontMatter` as a `Pick` over the content-collections `Post` type to stay in sync automatically
This commit is contained in:
6 files changed
+449
-458
No files matched your search
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { IconInfoCircle, IconMarkdown } from "@tabler/icons-react";
|
||||
import { createContext, useContext, useMemo, useState, useTransition } from "react";
|
||||
import { useState, useTransition } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
@@ -13,61 +13,12 @@ import { createComment, updateComment } from "@/lib/server/comments";
|
||||
|
||||
import { CommentAvatar } from "./comment-avatar";
|
||||
|
||||
// Context for lifting form state to parent components
|
||||
type CommentFormContextValue = {
|
||||
content: string;
|
||||
setContent: (value: string) => void;
|
||||
isPending: boolean;
|
||||
startTransition: React.TransitionStartFunction;
|
||||
};
|
||||
|
||||
const CommentFormContext = createContext<CommentFormContextValue | null>(null);
|
||||
|
||||
// Provider for sharing form state with sibling components (preview, character counter, etc.)
|
||||
const CommentFormProvider = ({
|
||||
children,
|
||||
initialContent = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
initialContent?: string;
|
||||
}) => {
|
||||
// Local form state shared by the create/reply/edit forms
|
||||
const useCommentFormState = (initialContent: string = "") => {
|
||||
const [content, setContent] = useState(initialContent);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({ content, setContent, isPending, startTransition }),
|
||||
[content, setContent, isPending, startTransition],
|
||||
);
|
||||
|
||||
return <CommentFormContext.Provider value={contextValue}>{children}</CommentFormContext.Provider>;
|
||||
};
|
||||
|
||||
// Hook to access form state from context (for sibling components like preview panels)
|
||||
const useCommentForm = () => {
|
||||
const context = useContext(CommentFormContext);
|
||||
if (!context) {
|
||||
throw new Error("useCommentForm must be used within a CommentFormProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
// Internal hook - uses context if available, otherwise creates local state
|
||||
const useCommentFormState = (initialContent: string = "") => {
|
||||
const context = useContext(CommentFormContext);
|
||||
const [localContent, setLocalContent] = useState(initialContent);
|
||||
const [localIsPending, localStartTransition] = useTransition();
|
||||
|
||||
// If wrapped in provider, use context; otherwise use local state
|
||||
if (context) {
|
||||
return context;
|
||||
}
|
||||
|
||||
return {
|
||||
content: localContent,
|
||||
setContent: setLocalContent,
|
||||
isPending: localIsPending,
|
||||
startTransition: localStartTransition,
|
||||
};
|
||||
return { content, setContent, isPending, startTransition };
|
||||
};
|
||||
|
||||
// Shared textarea component
|
||||
@@ -384,4 +335,4 @@ const EditCommentForm = ({
|
||||
);
|
||||
};
|
||||
|
||||
export { NewCommentForm, ReplyForm, EditCommentForm, CommentFormProvider, useCommentForm };
|
||||
export { NewCommentForm, ReplyForm, EditCommentForm };
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
const authClient = createAuthClient({
|
||||
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
});
|
||||
|
||||
export const { signIn, signUp, useSession } = authClient;
|
||||
export const { signIn } = authClient;
|
||||
+28
-37
@@ -1,17 +1,17 @@
|
||||
import { Feed, type Item as FeedItem } from "feed";
|
||||
import { Feed } from "feed";
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
|
||||
import ogImage from "@/app/opengraph-image.jpg";
|
||||
import authorConfig from "@/lib/config/author";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { getPost, getFrontMatter } from "@/lib/posts";
|
||||
import { getPosts } from "@/lib/posts";
|
||||
|
||||
/**
|
||||
* Returns a `Feed` object, which can then be processed with `feed.rss2()`, `feed.atom1()`, or `feed.json1()`.
|
||||
* Returns a `Feed` object, which can then be processed with `feed.rss2()` or `feed.atom1()`.
|
||||
* @see https://github.com/jpmonette/feed#example
|
||||
*/
|
||||
const buildFeed = (): Feed => {
|
||||
const frontmatter = getFrontMatter();
|
||||
const posts = getPosts();
|
||||
|
||||
const feed = new Feed({
|
||||
id: `${process.env.NEXT_PUBLIC_BASE_URL}`,
|
||||
@@ -19,7 +19,7 @@ const buildFeed = (): Feed => {
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
copyright: `https://spdx.org/licenses/${siteConfig.license}.html`,
|
||||
updated: frontmatter[0] ? new Date(frontmatter[0].date) : undefined,
|
||||
updated: posts[0] ? new Date(posts[0].date) : undefined,
|
||||
image: `${process.env.NEXT_PUBLIC_BASE_URL}${ogImage.src}`,
|
||||
feedLinks: {
|
||||
rss: `${process.env.NEXT_PUBLIC_BASE_URL}/feed.xml`,
|
||||
@@ -32,50 +32,41 @@ const buildFeed = (): Feed => {
|
||||
},
|
||||
});
|
||||
|
||||
// parse posts into feed items
|
||||
const posts: FeedItem[] = frontmatter.map((post) => ({
|
||||
guid: post.permalink,
|
||||
link: post.permalink,
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
author: [
|
||||
{
|
||||
name: authorConfig.name,
|
||||
link: `${process.env.NEXT_PUBLIC_BASE_URL}`,
|
||||
},
|
||||
],
|
||||
date: new Date(post.date),
|
||||
content: `
|
||||
${getPost(post.slug)?.feedHtml}
|
||||
// posts are already sorted reverse chronologically
|
||||
for (const post of posts) {
|
||||
feed.addItem({
|
||||
guid: post.permalink,
|
||||
link: post.permalink,
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
author: [
|
||||
{
|
||||
name: authorConfig.name,
|
||||
link: `${process.env.NEXT_PUBLIC_BASE_URL}`,
|
||||
},
|
||||
],
|
||||
date: new Date(post.date),
|
||||
content: `
|
||||
${post.feedHtml}
|
||||
<p><a href="${post.permalink}"><strong>Continue reading...</strong></a></p>
|
||||
`.trim(),
|
||||
}));
|
||||
|
||||
// sort posts reverse chronologically in case the promises resolved out of order
|
||||
posts.sort((post1, post2) => new Date(post2.date).getTime() - new Date(post1.date).getTime());
|
||||
|
||||
// officially add each post to the feed
|
||||
posts.forEach((post) => {
|
||||
feed.addItem(post);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return feed;
|
||||
};
|
||||
|
||||
export const createFeedHandler = (
|
||||
feedType: "rss" | "atom" | "json",
|
||||
feedType: "rss" | "atom",
|
||||
): { GET: (request: NextRequest) => Promise<Response> } => {
|
||||
return {
|
||||
GET: async () => {
|
||||
const feed = buildFeed();
|
||||
return new NextResponse(
|
||||
feed[feedType === "rss" ? "rss2" : feedType === "atom" ? "atom1" : "json1"](),
|
||||
{
|
||||
headers: {
|
||||
"content-type": `application/${feedType}+xml; charset=utf-8`,
|
||||
},
|
||||
return new NextResponse(feedType === "rss" ? feed.rss2() : feed.atom1(), {
|
||||
headers: {
|
||||
"content-type": `application/${feedType}+xml; charset=utf-8`,
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
+21
-15
@@ -1,19 +1,21 @@
|
||||
import { allPosts } from "content-collections";
|
||||
|
||||
export type FrontMatter = {
|
||||
slug: string;
|
||||
permalink: string;
|
||||
date: string;
|
||||
title: string;
|
||||
htmlTitle?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
image?: string;
|
||||
noComments?: boolean;
|
||||
};
|
||||
|
||||
export type Post = (typeof allPosts)[number];
|
||||
|
||||
/** The subset of a post's fields used for listings, metadata and the OG image (everything except the body). */
|
||||
export type FrontMatter = Pick<
|
||||
Post,
|
||||
| "slug"
|
||||
| "permalink"
|
||||
| "date"
|
||||
| "title"
|
||||
| "htmlTitle"
|
||||
| "description"
|
||||
| "tags"
|
||||
| "image"
|
||||
| "noComments"
|
||||
>;
|
||||
|
||||
/** Path to directory with .mdx files, relative to project root. */
|
||||
export const POSTS_DIR = "notes" as const;
|
||||
|
||||
@@ -37,6 +39,12 @@ export const getPostByPageSlug = (pageSlug: string): Post | undefined => {
|
||||
return getPost(pageSlug.slice(prefix.length));
|
||||
};
|
||||
|
||||
/** Returns ALL posts, sorted reverse chronologically. */
|
||||
export const getPosts = (): Post[] =>
|
||||
allPosts.toSorted(
|
||||
(post1, post2) => new Date(post2.date).getTime() - new Date(post1.date).getTime(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the front matter of ALL posts, sorted reverse chronologically.
|
||||
*/
|
||||
@@ -64,9 +72,7 @@ export function getFrontMatter(slug?: string): FrontMatter[] | FrontMatter | und
|
||||
}
|
||||
|
||||
if (!slug) {
|
||||
return allPosts
|
||||
.toSorted((post1, post2) => new Date(post2.date).getTime() - new Date(post1.date).getTime())
|
||||
.map(toFrontMatter);
|
||||
return getPosts().map(toFrontMatter);
|
||||
}
|
||||
|
||||
throw new Error("getFrontMatter() called with invalid argument.");
|
||||
|
||||
+3
-3
@@ -70,8 +70,8 @@
|
||||
"remark-smartypants": "^3.0.3",
|
||||
"remark-strip-mdx-imports-exports": "^1.0.1",
|
||||
"server-only": "0.0.1",
|
||||
"shadcn": "^4.21.0",
|
||||
"shiki": "^4.4.3",
|
||||
"shadcn": "^4.21.1",
|
||||
"shiki": "^4.5.0",
|
||||
"tinyglobby": "^0.2.17",
|
||||
"unified": "^11.0.5",
|
||||
"zod": "^4.6.5"
|
||||
@@ -83,7 +83,7 @@
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
"@types/mdx": "^2.0.14",
|
||||
"@types/node": "^26.6.3",
|
||||
"@types/node": "^26.6.4",
|
||||
"@types/pg": "^8.23.1",
|
||||
"@types/react": "19.3.0",
|
||||
"@types/react-dom": "19.3.0",
|
||||
|
||||
Generated
+390
-347
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user