diff --git a/app/changelogs/[owner]/[repo]/[changelogId]/opengraph-image.tsx b/app/changelogs/[owner]/[repo]/[changelogId]/opengraph-image.tsx
new file mode 100644
index 0000000..0ec1973
--- /dev/null
+++ b/app/changelogs/[owner]/[repo]/[changelogId]/opengraph-image.tsx
@@ -0,0 +1,233 @@
+import { ImageResponse } from "next/server";
+import { TriggerWordmark } from "../../../../components/logos/Trigger";
+import { stripMarkdown } from "@/lib/utils";
+
+// Route segment config
+export const runtime = "edge";
+
+// Image metadata
+export const alt = "Generate a changelog using AI";
+export const size = {
+ width: 1200,
+ height: 630,
+};
+
+export const contentType = "image/png";
+
+type Props = {
+ params: {
+ owner: string;
+ repo: string;
+ changelogId: number;
+ };
+};
+
+// Image generation
+export default async function OG({
+ params: { owner, repo, changelogId },
+}: Props) {
+ // Fonts
+ const poppinsSemiBold = fetch(
+ new URL("/public/Poppins-SemiBold.ttf", import.meta.url)
+ ).then((res) => res.arrayBuffer());
+
+ const poppinsRegular = fetch(
+ new URL("/public/Poppins-Regular.ttf", import.meta.url)
+ ).then((res) => res.arrayBuffer());
+
+ // Fetch changelog
+ const supabaseRestEndpoint = `https://${process.env.SUPABASE_ID}.supabase.co/rest/v1/changelogs`;
+ const supabaseParams = `?select=id,start_date,end_date,markdown,repo:repos(id,repo_url)&id=eq.${changelogId}`;
+
+ const changelogRecord = await fetch(supabaseRestEndpoint + supabaseParams, {
+ headers: {
+ apikey: process.env.SUPABASE_KEY,
+ Authorization: `Bearer ${process.env.SUPABASE_KEY}`,
+ } as HeadersInit,
+ });
+
+ const records = await changelogRecord?.json();
+ const markdown = records?.[0]?.markdown;
+ const samples = markdown
+ ? stripMarkdown(markdown)?.split("\n").slice(1, 4)
+ : null;
+
+ return new ImageResponse(
+ (
+ // ImageResponse JSX element
+
+
+
+
+
+ {owner}/
+
+
16 ? 36 : 64,
+ }}
+ >
+ {repo}
+
+
+ {samples && samples.length > 0 ? (
+
+ {samples?.map((sample, i) => (
+
+ • {sample}
+
+ ))}
+ • ...
+
+ ) : null}
+
+
+ AutoChangelog
+
+
by
+
+
+
+
+ {/* Browser screenshot */}
+
+ {Array(3)
+ .fill(null)
+ .map((_, i) => (
+
+ ))}
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+
+
+ ),
+ {
+ ...size,
+ fonts: [
+ {
+ name: "poppins",
+ data: await poppinsRegular,
+ style: "normal",
+ weight: 400,
+ },
+ {
+ name: "poppins",
+ data: await poppinsSemiBold,
+ weight: 600,
+ },
+ ],
+ }
+ );
+}
diff --git a/app/changelogs/[owner]/[repo]/[changelogId]/twitter-image.tsx b/app/changelogs/[owner]/[repo]/[changelogId]/twitter-image.tsx
new file mode 100644
index 0000000..0ec1973
--- /dev/null
+++ b/app/changelogs/[owner]/[repo]/[changelogId]/twitter-image.tsx
@@ -0,0 +1,233 @@
+import { ImageResponse } from "next/server";
+import { TriggerWordmark } from "../../../../components/logos/Trigger";
+import { stripMarkdown } from "@/lib/utils";
+
+// Route segment config
+export const runtime = "edge";
+
+// Image metadata
+export const alt = "Generate a changelog using AI";
+export const size = {
+ width: 1200,
+ height: 630,
+};
+
+export const contentType = "image/png";
+
+type Props = {
+ params: {
+ owner: string;
+ repo: string;
+ changelogId: number;
+ };
+};
+
+// Image generation
+export default async function OG({
+ params: { owner, repo, changelogId },
+}: Props) {
+ // Fonts
+ const poppinsSemiBold = fetch(
+ new URL("/public/Poppins-SemiBold.ttf", import.meta.url)
+ ).then((res) => res.arrayBuffer());
+
+ const poppinsRegular = fetch(
+ new URL("/public/Poppins-Regular.ttf", import.meta.url)
+ ).then((res) => res.arrayBuffer());
+
+ // Fetch changelog
+ const supabaseRestEndpoint = `https://${process.env.SUPABASE_ID}.supabase.co/rest/v1/changelogs`;
+ const supabaseParams = `?select=id,start_date,end_date,markdown,repo:repos(id,repo_url)&id=eq.${changelogId}`;
+
+ const changelogRecord = await fetch(supabaseRestEndpoint + supabaseParams, {
+ headers: {
+ apikey: process.env.SUPABASE_KEY,
+ Authorization: `Bearer ${process.env.SUPABASE_KEY}`,
+ } as HeadersInit,
+ });
+
+ const records = await changelogRecord?.json();
+ const markdown = records?.[0]?.markdown;
+ const samples = markdown
+ ? stripMarkdown(markdown)?.split("\n").slice(1, 4)
+ : null;
+
+ return new ImageResponse(
+ (
+ // ImageResponse JSX element
+
+
+
+
+
+ {owner}/
+
+
16 ? 36 : 64,
+ }}
+ >
+ {repo}
+
+
+ {samples && samples.length > 0 ? (
+
+ {samples?.map((sample, i) => (
+
+ • {sample}
+
+ ))}
+ • ...
+
+ ) : null}
+
+
+ AutoChangelog
+
+
by
+
+
+
+
+ {/* Browser screenshot */}
+
+ {Array(3)
+ .fill(null)
+ .map((_, i) => (
+
+ ))}
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+
+
+ ),
+ {
+ ...size,
+ fonts: [
+ {
+ name: "poppins",
+ data: await poppinsRegular,
+ style: "normal",
+ weight: 400,
+ },
+ {
+ name: "poppins",
+ data: await poppinsSemiBold,
+ weight: 600,
+ },
+ ],
+ }
+ );
+}
diff --git a/app/components/logos/Trigger.tsx b/app/components/logos/Trigger.tsx
index 1c33e57..606b8db 100644
--- a/app/components/logos/Trigger.tsx
+++ b/app/components/logos/Trigger.tsx
@@ -63,6 +63,208 @@ export function TriggerLogo({ className }: Props) {
);
}
+export function TriggerWordmark({
+ className,
+ style,
+}: Props & { style?: React.CSSProperties }) {
+ return (
+
+ );
+}
+
export function TriggerIcon({ className }: Props) {
return (