fix: address react-doctor violations

This commit is contained in:
jake committed 2026-09-26 12:54:00 -04:00
1 parent 82eedf02bb
commit 2403842f39
7 files changed
+186 -228

No files matched your search

+174 -179
View File
@@ -67,210 +67,205 @@ export const generateStaticParams = () => {
};
const OpenGraphImage = async ({ params }: { params: Promise<{ slug: string }> }) => {
try {
const { slug } = await params;
const { slug } = await params;
// get the post's title and image filename from its frontmatter
const frontmatter = getFrontMatter(slug);
if (!frontmatter) notFound();
// get the post's title and image filename from its frontmatter
const frontmatter = getFrontMatter(slug);
if (!frontmatter) notFound();
// IMPORTANT: include these exact paths in next.config.ts under "outputFileTracingIncludes"
const [postImg, avatarImg] = await Promise.all([
frontmatter.image ? getLocalImage(`${POSTS_DIR}/${slug}/${frontmatter.image}`) : null,
getLocalImage("app/avatar.jpg"),
]);
// IMPORTANT: include these exact image paths in next.config.ts under "outputFileTracingIncludes"
const [postImg, avatarImg, fontRegular, fontSemibold] = await Promise.all([
frontmatter.image ? getLocalImage(`${POSTS_DIR}/${slug}/${frontmatter.image}`) : null,
getLocalImage("app/avatar.jpg"),
loadInterFont(400),
loadInterFont(600),
]);
// template is HEAVILY inspired by https://og-new.clerkstage.dev/
return new ImageResponse(
<div
style={{
...size,
display: "flex",
flexDirection: "column",
background:
"linear-gradient(to top right, rgb(134, 239, 172), rgb(59, 130, 246), rgb(147, 51, 234))",
}}
>
<div
style={{
height: "100%",
width: "100%",
position: "absolute",
inset: 0,
filter: "brightness(100%) contrast(150%)",
opacity: "0.1",
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><filter id="noise" x="0" y="0"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/><feBlend mode="screen"/></filter><rect width="500" height="500" filter="url(#noise)" opacity="1"/></svg>')`,
backgroundRepeat: "repeat",
}}
></div>
<div
style={{
height: "100%",
width: "100%",
position: "absolute",
opacity: "0.4",
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill-rule="evenodd" fill="#6b7280" fill-opacity="0.4"><g><path opacity="0.5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z"/><path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z"/></g></g></svg>')`,
maskImage: "radial-gradient(rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0) 80%)",
}}
></div>
const [fontRegular, fontSemibold] = await Promise.all([loadInterFont(400), loadInterFont(600)]);
// template is HEAVILY inspired by https://og-new.clerkstage.dev/
return new ImageResponse(
<div
style={{
...size,
display: "flex",
flexDirection: "column",
background:
"linear-gradient(to top right, rgb(134, 239, 172), rgb(59, 130, 246), rgb(147, 51, 234))",
width: "100%",
gap: "1.5rem",
paddingLeft: "2rem",
}}
>
<div
style={{
height: "100%",
width: "100%",
position: "absolute",
inset: 0,
filter: "brightness(100%) contrast(150%)",
opacity: "0.1",
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><filter id="noise" x="0" y="0"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/><feBlend mode="screen"/></filter><rect width="500" height="500" filter="url(#noise)" opacity="1"/></svg>')`,
backgroundRepeat: "repeat",
}}
></div>
<div
style={{
height: "100%",
width: "100%",
position: "absolute",
opacity: "0.4",
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill-rule="evenodd" fill="#6b7280" fill-opacity="0.4"><g><path opacity="0.5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z"/><path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z"/></g></g></svg>')`,
maskImage: "radial-gradient(rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0) 80%)",
}}
></div>
<div
style={{
display: "flex",
width: "100%",
gap: "1.5rem",
paddingLeft: "2rem",
flexDirection: "column",
rowGap: "1.5rem",
flexShrink: 0,
paddingTop: "2rem",
// don't wrap the title text if there's no image to leave room for
width: postImg ? "35%" : "100%",
marginRight: "0.75rem",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
rowGap: "1.5rem",
flexShrink: 0,
paddingTop: "2rem",
// don't wrap the title text if there's no image to leave room for
width: postImg ? "35%" : "100%",
marginRight: "0.75rem",
marginBottom: "0.75rem",
}}
>
<div
style={{
display: "flex",
marginBottom: "0.75rem",
}}
>
{avatarImg && (
// oxlint-disable-next-line nextjs/no-img-element - Satori/ImageResponse requires raw <img> tags
<img
// @ts-expect-error
src={avatarImg}
alt=""
style={{
width: "3rem",
height: "3rem",
borderRadius: "50%",
}}
/>
)}
<span
style={{
fontSize: "1.925rem",
fontWeight: 400,
lineHeight: "3rem",
letterSpacing: "-0.025em",
marginLeft: "0.75rem",
}}
>
{siteConfig.name}
</span>
</div>
<div
style={{
display: "flex",
flexGrow: 0,
fontWeight: 600,
fontSize: "48px",
color: "#030712",
letterSpacing: "-0.025em",
lineHeight: "1.2",
}}
>
{frontmatter.title}
</div>
<div
style={{
display: "flex",
flexGrow: 0,
}}
>
<span
style={{
fontWeight: 400,
fontSize: "20px",
color: "#030712",
border: "solid",
borderRadius: "100",
borderWidth: "2px",
paddingRight: "16px",
paddingLeft: "16px",
paddingTop: "5px",
paddingBottom: "5px",
}}
>
{POSTS_DIR.charAt(0).toUpperCase() + POSTS_DIR.slice(1)}
</span>
</div>
<div
style={{
display: "flex",
flexGrow: 0,
fontWeight: 400,
fontSize: "24px",
color: "#030712",
letterSpacing: "-0.025em",
lineHeight: "1.2",
}}
>
{new Date(frontmatter.date).toLocaleDateString(process.env.NEXT_PUBLIC_SITE_LOCALE, {
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
</div>
{postImg && (
<div
style={{
display: "flex",
width: "100%", // less than half in reality, but this gives the image the overflow look
flexGrow: 0,
}}
>
{/* oxlint-disable-next-line nextjs/no-img-element - Satori/ImageResponse requires raw <img> tags */}
{avatarImg && (
// oxlint-disable-next-line nextjs/no-img-element - Satori/ImageResponse requires raw <img> tags
<img
// @ts-expect-error
src={postImg}
src={avatarImg}
alt=""
style={{
maxHeight: "100%",
minHeight: 630,
width: "auto",
width: "3rem",
height: "3rem",
borderRadius: "50%",
}}
/>
</div>
)}
)}
<span
style={{
fontSize: "1.925rem",
fontWeight: 400,
lineHeight: "3rem",
letterSpacing: "-0.025em",
marginLeft: "0.75rem",
}}
>
{siteConfig.name}
</span>
</div>
<div
style={{
display: "flex",
flexGrow: 0,
fontWeight: 600,
fontSize: "48px",
color: "#030712",
letterSpacing: "-0.025em",
lineHeight: "1.2",
}}
>
{frontmatter.title}
</div>
<div
style={{
display: "flex",
flexGrow: 0,
}}
>
<span
style={{
fontWeight: 400,
fontSize: "20px",
color: "#030712",
border: "solid",
borderRadius: "100",
borderWidth: "2px",
paddingRight: "16px",
paddingLeft: "16px",
paddingTop: "5px",
paddingBottom: "5px",
}}
>
{POSTS_DIR.charAt(0).toUpperCase() + POSTS_DIR.slice(1)}
</span>
</div>
<div
style={{
display: "flex",
flexGrow: 0,
fontWeight: 400,
fontSize: "24px",
color: "#030712",
letterSpacing: "-0.025em",
lineHeight: "1.2",
}}
>
{new Date(frontmatter.date).toLocaleDateString(process.env.NEXT_PUBLIC_SITE_LOCALE, {
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
</div>
</div>,
{
...size,
fonts: [
{
name: "Inter",
data: fontRegular,
style: "normal",
weight: 400,
},
{
name: "Inter",
data: fontSemibold,
style: "normal",
weight: 600,
},
],
},
);
} catch (error) {
console.error("[/notes/[slug]/opengraph-image] error generating open graph image:", error);
return notFound();
}
{postImg && (
<div
style={{
display: "flex",
width: "100%", // less than half in reality, but this gives the image the overflow look
flexGrow: 0,
}}
>
{/* oxlint-disable-next-line nextjs/no-img-element - Satori/ImageResponse requires raw <img> tags */}
<img
// @ts-expect-error
src={postImg}
alt=""
style={{
maxHeight: "100%",
minHeight: 630,
width: "auto",
}}
/>
</div>
)}
</div>
</div>,
{
...size,
fonts: [
{
name: "Inter",
data: fontRegular,
style: "normal",
weight: 400,
},
{
name: "Inter",
data: fontSemibold,
style: "normal",
weight: 600,
},
],
},
);
};
export default OpenGraphImage;
+4
View File
@@ -26,6 +26,10 @@ export const getContributions = async (): Promise<
},
);
if (!response.ok) {
throw new Error(`GitHub request failed: ${response.status}`);
}
const $ = cheerio.load(await response.text());
const days = $(".js-calendar-graph-table .ContributionCalendar-day")
+2 -2
View File
@@ -133,7 +133,7 @@ const SubmitButton = ({
// Markdown help popover (only shown for new comments)
const MarkdownHelp = () => (
<p className="text-[0.8rem] leading-relaxed text-muted-foreground">
<div className="text-[0.8rem] leading-relaxed text-muted-foreground">
<IconMarkdown className="mr-1.5 inline-block size-4 align-text-top" />
<span className="max-md:hidden">Basic&nbsp;</span>
<Popover>
@@ -192,7 +192,7 @@ const MarkdownHelp = () => (
<span>&nbsp;is supported</span>
<span className="max-md:hidden">&nbsp;here</span>
<span>.</span>
</p>
</div>
);
// New comment form - for creating top-level comments
+1
View File
@@ -22,6 +22,7 @@ const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
} catch (error) {
console.error("Error signing in:", error);
toast.add({ title: "There was a problem signing in.", type: "error" });
} finally {
setIsLoading(false);
}
};
+3 -1
View File
@@ -190,7 +190,9 @@ function FieldError({
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map((error, index) => error?.message && <li key={index}>{error.message}</li>)}
{uniqueErrors.map(
(error) => error?.message && <li key={error.message}>{error.message}</li>,
)}
</ul>
);
}, [children, errors]);
+1 -28
View File
@@ -1,6 +1,6 @@
"use server";
import { desc, eq, inArray, sql } from "drizzle-orm";
import { desc, eq, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { headers } from "next/headers";
@@ -58,33 +58,6 @@ export const getCommentCount = async (slug: string): Promise<number> => {
}
};
/**
* Retrieves the numbers of comments for an array of slugs
*/
export const getCommentCountsForSlugs = async (
slugs: string[],
): Promise<Record<string, number>> => {
try {
const rows = await db
.select({
pageSlug: schema.comment.pageSlug,
count: sql<number>`cast(count(${schema.comment.id}) as int)`,
})
.from(schema.comment)
.where(inArray(schema.comment.pageSlug, slugs))
.groupBy(schema.comment.pageSlug);
const map: Record<string, number> = Object.fromEntries(slugs.map((s) => [s, 0]));
for (const row of rows) {
map[row.pageSlug] = row.count ?? 0;
}
return map;
} catch (error) {
console.error("[server/comments] error fetching comment counts:", error);
return Object.fromEntries(slugs.map((s) => [s, 0]));
}
};
/**
* Retrieves the numbers of comments for ALL slugs
*/
+1 -18
View File
@@ -1,6 +1,6 @@
"use server";
import { eq, inArray, sql } from "drizzle-orm";
import { eq, sql } from "drizzle-orm";
import { revalidateTag } from "next/cache";
import { db } from "@/lib/db";
@@ -19,23 +19,6 @@ export const getViewCount = async (slug: string): Promise<number> => {
}
};
/**
* Retrieves the numbers of views for an array of slugs, returning 0 for any that don't exist
*/
export const getViewCountsForSlugs = async (slugs: string[]): Promise<Record<string, number>> => {
try {
const pages = await db.select().from(page).where(inArray(page.slug, slugs));
const viewMap: Record<string, number> = Object.fromEntries(slugs.map((s) => [s, 0]));
for (const p of pages) {
viewMap[p.slug] = p.views;
}
return viewMap;
} catch (error) {
console.error("[server/views] fatal error:", error);
return Object.fromEntries(slugs.map((s) => [s, 0]));
}
};
/**
* Retrieves the numbers of views for ALL slugs
*/