diff --git a/apps/web/src/app/[locale]/invite/[urlId]/layout.tsx b/apps/web/src/app/[locale]/invite/[urlId]/layout.tsx
deleted file mode 100644
index 78beaefd9..000000000
--- a/apps/web/src/app/[locale]/invite/[urlId]/layout.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { dehydrate, Hydrate } from "@tanstack/react-query";
-import { notFound } from "next/navigation";
-
-import { createSSRHelper } from "@/trpc/server/create-ssr-helper";
-
-import Providers from "./providers";
-
-export default async function Layout({
- children,
- params,
-}: {
- params: { urlId: string };
- children: React.ReactNode;
-}) {
- const trpc = await createSSRHelper();
-
- const [poll] = await Promise.all([
- trpc.polls.get.fetch({ urlId: params.urlId }),
- trpc.polls.participants.list.prefetch({ pollId: params.urlId }),
- trpc.polls.comments.list.prefetch({ pollId: params.urlId }),
- ]);
-
- if (!poll || poll.deleted || poll.user?.banned) {
- notFound();
- }
-
- return (
-
- {children}
-
- );
-}
diff --git a/apps/web/src/app/[locale]/invite/[urlId]/logo-color.svg b/apps/web/src/app/[locale]/invite/[urlId]/logo-color.svg
new file mode 100644
index 000000000..979eacec9
--- /dev/null
+++ b/apps/web/src/app/[locale]/invite/[urlId]/logo-color.svg
@@ -0,0 +1,10 @@
+
diff --git a/apps/web/src/app/[locale]/invite/[urlId]/opengraph-image.tsx b/apps/web/src/app/[locale]/invite/[urlId]/opengraph-image.tsx
new file mode 100644
index 000000000..c8d832b5d
--- /dev/null
+++ b/apps/web/src/app/[locale]/invite/[urlId]/opengraph-image.tsx
@@ -0,0 +1,109 @@
+import { prisma } from "@rallly/database";
+import { ImageResponse } from "next/og";
+import { readFile } from "node:fs/promises";
+import { join } from "node:path";
+import * as React from "react";
+
+import Logo from "./logo-color.svg";
+
+export const contentType = "image/png";
+export const size = {
+ width: 1200,
+ height: 630,
+};
+
+const regularFont = readFile(
+ join(process.cwd(), "public/static/fonts/inter-regular.ttf"),
+);
+
+const boldFont = readFile(
+ join(process.cwd(), "public/static/fonts/inter-bold.ttf"),
+);
+
+export default async function OgImage({
+ params,
+}: {
+ params: { urlId: string; locale: string };
+}) {
+ const [regularFontData, boldFontData] = await Promise.all([
+ regularFont,
+ boldFont,
+ ]);
+
+ const poll = await prisma.poll.findUnique({
+ where: {
+ id: params.urlId as string,
+ },
+ select: {
+ title: true,
+ deleted: true,
+ user: {
+ select: {
+ name: true,
+ banned: true,
+ },
+ },
+ },
+ });
+
+ if (!poll || poll.deleted || poll.user?.banned) {
+ return new Response("Not Found", { status: 404 });
+ }
+
+ const title = poll.title;
+ const author = poll.user?.name;
+
+ return new ImageResponse(
+ (
+
+
+
+
+ {author ? (
+
+ By {author}
+
+ ) : null}
+
+ {title}
+
+
+
+
+ ),
+ {
+ width: 1200,
+ height: 630,
+ fonts: [
+ {
+ name: "Inter",
+ data: regularFontData,
+ weight: 400,
+ },
+ {
+ name: "Inter",
+ data: boldFontData,
+ weight: 700,
+ },
+ ],
+ },
+ );
+}
diff --git a/apps/web/src/app/[locale]/invite/[urlId]/page.tsx b/apps/web/src/app/[locale]/invite/[urlId]/page.tsx
index 25b05c955..926441d03 100644
--- a/apps/web/src/app/[locale]/invite/[urlId]/page.tsx
+++ b/apps/web/src/app/[locale]/invite/[urlId]/page.tsx
@@ -1,5 +1,6 @@
import { prisma } from "@rallly/database";
import { absoluteUrl } from "@rallly/utils/absolute-url";
+import { dehydrate, Hydrate } from "@tanstack/react-query";
import { notFound } from "next/navigation";
import { InvitePage } from "@/app/[locale]/invite/[urlId]/invite-page";
@@ -7,6 +8,8 @@ import { PermissionProvider } from "@/contexts/permissions";
import { getTranslation } from "@/i18n/server";
import { createSSRHelper } from "@/trpc/server/create-ssr-helper";
+import Providers from "./providers";
+
const PermissionContext = async ({
children,
token,
@@ -25,15 +28,32 @@ const PermissionContext = async ({
};
export default async function Page({
+ params,
searchParams,
}: {
params: { urlId: string };
searchParams: { token: string };
}) {
+ const trpc = await createSSRHelper();
+
+ const [poll] = await Promise.all([
+ trpc.polls.get.fetch({ urlId: params.urlId }),
+ trpc.polls.participants.list.prefetch({ pollId: params.urlId }),
+ trpc.polls.comments.list.prefetch({ pollId: params.urlId }),
+ ]);
+
+ if (!poll || poll.deleted || poll.user?.banned) {
+ notFound();
+ }
+
return (
-
-
-
+
+
+
+
+
+
+
);
}
@@ -53,6 +73,7 @@ export async function generateMetadata({
id: true,
title: true,
deleted: true,
+ updatedAt: true,
user: {
select: {
name: true,
@@ -77,11 +98,6 @@ export async function generateMetadata({
defaultValue: "Guest",
});
- const ogImageUrl = absoluteUrl("/api/og-image-poll", {
- title,
- author,
- });
-
return {
title,
metadataBase: new URL(absoluteUrl()),
@@ -91,11 +107,10 @@ export async function generateMetadata({
url: `/invite/${id}`,
images: [
{
- url: ogImageUrl,
+ url: `/invite/${id}/opengraph-image?${poll.updatedAt.getTime()}`,
width: 1200,
height: 630,
alt: title,
- type: "image/png",
},
],
},