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( + ( +
+
+
+ +
+ Invite +
+
+
+ {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", }, ], },