mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-28 17:56:37 +02:00
♻️ Invite page improvements (#1609)
This commit is contained in:
parent
547a24c6e1
commit
53e50cd8c7
4 changed files with 144 additions and 42 deletions
|
@ -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 (
|
|
||||||
<Hydrate state={dehydrate(trpc.queryClient)}>
|
|
||||||
<Providers>{children}</Providers>
|
|
||||||
</Hydrate>
|
|
||||||
);
|
|
||||||
}
|
|
10
apps/web/src/app/[locale]/invite/[urlId]/logo-color.svg
Normal file
10
apps/web/src/app/[locale]/invite/[urlId]/logo-color.svg
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="27" fill="none" viewBox="0 0 120 27">
|
||||||
|
<g clip-path="url(#a)">
|
||||||
|
<path fill="#4F46E5" fill-rule="evenodd" d="M4.7 2.842a1.855 1.855 0 0 0-1.858 1.853v17.61c0 1.023.83 1.853 1.857 1.853h17.646a1.855 1.855 0 0 0 1.857-1.853V4.695c0-1.023-.83-1.853-1.857-1.853H4.699Zm17.56 4.844H4.784V22.22H22.26V7.686Zm-2.702-2.063a.928.928 0 0 1-1.597.677.926.926 0 0 1 .669-1.571.93.93 0 0 1 .928.894Zm-12.073.926a.93.93 0 0 0 .963-.927.926.926 0 0 0-.963-.927.93.93 0 0 0-.895.927.926.926 0 0 0 .895.927Zm1.258 8.668a.463.463 0 0 1 0-.655l.657-.655a.463.463 0 0 1 .656 0l2.342 2.337 5.128-5.117a.465.465 0 0 1 .656 0l.657.655a.463.463 0 0 1 0 .656l-6.11 6.095a.465.465 0 0 1-.656 0l-3.33-3.316Zm31.671-8.112h-4.888v12.79h2.54v-4.832h2.428l2.625 4.832h2.727v-.124l-2.968-5.287c.384-.16.73-.353 1.037-.58a3.41 3.41 0 0 0 1.044-1.22c.246-.486.37-1.061.37-1.722 0-.838-.198-1.544-.592-2.117-.394-.574-.958-1.008-1.691-1.3-.733-.292-1.61-.44-2.632-.44Zm-2.348 6.053h2.375c.524 0 .958-.088 1.303-.265a1.79 1.79 0 0 0 .776-.728c.178-.31.268-.665.268-1.063 0-.427-.086-.797-.26-1.107a1.715 1.715 0 0 0-.784-.729c-.351-.17-.795-.255-1.33-.255h-2.348v4.147Zm18.608-6.053h.628l5.052 12.79h-2.696l-1.011-2.846h-5.023l-1.006 2.846H49.93l5.026-12.79h1.72Zm-2.376 8.037h3.672L56.13 9.96l-1.832 5.183Zm21.527 2.856v1.897H67.29V7.105h2.54v10.893h5.996Zm13.782 1.897v-1.898h-5.995V7.107h-2.54v12.788h8.535Zm13.785-1.898v1.898h-8.538V7.105h2.543v10.893l5.995-.001Zm7.371-4.927-2.957-5.965h-2.808l4.481 8.107v4.683h2.577v-4.682l4.47-8.108h-2.788l-2.975 5.965Z" clip-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h115.105v21.316H0z" transform="translate(2.842 2.842)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
109
apps/web/src/app/[locale]/invite/[urlId]/opengraph-image.tsx
Normal file
109
apps/web/src/app/[locale]/invite/[urlId]/opengraph-image.tsx
Normal file
|
@ -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(
|
||||||
|
(
|
||||||
|
<div tw="flex relative flex-col bg-gray-100 w-full h-full px-[80px] py-[70px] items-start justify-center">
|
||||||
|
<div tw="h-full flex flex-col w-full justify-start">
|
||||||
|
<div tw="flex justify-between items-center w-full">
|
||||||
|
<Logo height={81} width={370} />
|
||||||
|
<div tw="flex text-gray-800 text-3xl tracking-tight font-bold">
|
||||||
|
<span tw="bg-gray-200 px-6 py-3 rounded-full">Invite</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div tw="relative flex w-full flex-col mt-auto">
|
||||||
|
{author ? (
|
||||||
|
<div
|
||||||
|
tw="flex text-gray-500 text-[48px] w-[1040px] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
width: 1000,
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
By {author}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
tw="flex mt-3 text-[64px] font-bold w-[1040px] overflow-hidden"
|
||||||
|
style={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: "Inter",
|
||||||
|
data: regularFontData,
|
||||||
|
weight: 400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Inter",
|
||||||
|
data: boldFontData,
|
||||||
|
weight: 700,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { prisma } from "@rallly/database";
|
import { prisma } from "@rallly/database";
|
||||||
import { absoluteUrl } from "@rallly/utils/absolute-url";
|
import { absoluteUrl } from "@rallly/utils/absolute-url";
|
||||||
|
import { dehydrate, Hydrate } from "@tanstack/react-query";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
import { InvitePage } from "@/app/[locale]/invite/[urlId]/invite-page";
|
import { InvitePage } from "@/app/[locale]/invite/[urlId]/invite-page";
|
||||||
|
@ -7,6 +8,8 @@ import { PermissionProvider } from "@/contexts/permissions";
|
||||||
import { getTranslation } from "@/i18n/server";
|
import { getTranslation } from "@/i18n/server";
|
||||||
import { createSSRHelper } from "@/trpc/server/create-ssr-helper";
|
import { createSSRHelper } from "@/trpc/server/create-ssr-helper";
|
||||||
|
|
||||||
|
import Providers from "./providers";
|
||||||
|
|
||||||
const PermissionContext = async ({
|
const PermissionContext = async ({
|
||||||
children,
|
children,
|
||||||
token,
|
token,
|
||||||
|
@ -25,15 +28,32 @@ const PermissionContext = async ({
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
|
params,
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
params: { urlId: string };
|
params: { urlId: string };
|
||||||
searchParams: { token: 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 (
|
return (
|
||||||
|
<Hydrate state={dehydrate(trpc.queryClient)}>
|
||||||
|
<Providers>
|
||||||
<PermissionContext token={searchParams.token}>
|
<PermissionContext token={searchParams.token}>
|
||||||
<InvitePage />
|
<InvitePage />
|
||||||
</PermissionContext>
|
</PermissionContext>
|
||||||
|
</Providers>
|
||||||
|
</Hydrate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +73,7 @@ export async function generateMetadata({
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
title: true,
|
||||||
deleted: true,
|
deleted: true,
|
||||||
|
updatedAt: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
|
@ -77,11 +98,6 @@ export async function generateMetadata({
|
||||||
defaultValue: "Guest",
|
defaultValue: "Guest",
|
||||||
});
|
});
|
||||||
|
|
||||||
const ogImageUrl = absoluteUrl("/api/og-image-poll", {
|
|
||||||
title,
|
|
||||||
author,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
metadataBase: new URL(absoluteUrl()),
|
metadataBase: new URL(absoluteUrl()),
|
||||||
|
@ -91,11 +107,10 @@ export async function generateMetadata({
|
||||||
url: `/invite/${id}`,
|
url: `/invite/${id}`,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: ogImageUrl,
|
url: `/invite/${id}/opengraph-image?${poll.updatedAt.getTime()}`,
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
alt: title,
|
alt: title,
|
||||||
type: "image/png",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Reference in a new issue