+1
-1
app/leaderboard/[guildId]/api.ts
+1
-1
app/leaderboard/[guildId]/api.ts
···
1
1
import { ApiV1GuildsGetResponse, ApiV1GuildsModulesLeaderboardGetResponse, ApiV1GuildsTopmembersGetResponse, ApiV1GuildsTopmembersPaginationGetResponse } from "@/typings";
2
2
3
-
export async function getGuild(guildId: string): Promise<ApiV1GuildsGetResponse> {
3
+
export async function getGuild(guildId: string): Promise<ApiV1GuildsGetResponse | undefined> {
4
4
const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}`, {
5
5
headers: { Authorization: process.env.API_SECRET as string },
6
6
next: { revalidate: 60 * 60 }
+10
-5
app/leaderboard/[guildId]/layout.tsx
+10
-5
app/leaderboard/[guildId]/layout.tsx
···
12
12
import { getDesign, getGuild, getPagination } from "./api";
13
13
import Side from "./side.component";
14
14
15
-
interface LeaderboardProps {
15
+
export interface LeaderboardProps {
16
16
params: { guildId: string },
17
17
children: React.ReactNode;
18
18
}
···
25
25
const guild = await getGuild(params.guildId);
26
26
27
27
const title = `${guild?.name || "Unknown"}'s Leaderboard`;
28
-
const description = `Effortlessly discover the most active chatters, voice timers, and acknowledge top inviters. Explore the vibrant community dynamics of the ${guild?.name || "unknown"} discord server right from your web browser.`;
28
+
const description = `Discover the most active chatters, voice timers, and top inviters. ${guild?.name ? `Explore the community of the ${guild.name} discord server right from your web browser.` : ""}`;
29
29
const url = getCanonicalUrl("leaderboard", params.guildId);
30
30
31
31
return {
···
39
39
description,
40
40
url,
41
41
type: "website",
42
-
images: guild?.icon ? `https://cdn.discordapp.com/icons/${guild?.id}/${guild?.icon}.webp?size=256` : "/discord.png"
42
+
images: {
43
+
url: `https://4099-2001-871-21c-d364-74bb-b3f8-c7d7-1ffe.ngrok-free.app/leaderboard/${params.guildId}/open-graph.png`,
44
+
width: 1200,
45
+
height: 630,
46
+
type: "image/png"
47
+
}
43
48
},
44
49
twitter: {
45
-
card: "summary",
50
+
card: "summary_large_image",
46
51
title,
47
52
description
48
53
},
49
-
robots: guild.name ? "index, follow" : "noindex"
54
+
robots: guild?.name ? "index, follow" : "noindex"
50
55
};
51
56
};
52
57
+75
app/leaderboard/[guildId]/open-graph.png/route.tsx
+75
app/leaderboard/[guildId]/open-graph.png/route.tsx
···
1
+
/* eslint-disable @next/next/no-img-element */
2
+
3
+
import { readFile } from "fs/promises";
4
+
import { ImageResponse } from "next/og";
5
+
import { NextRequest } from "next/server";
6
+
7
+
import { getGuild, getTopMembers } from "../api";
8
+
import { LeaderboardProps } from "../layout";
9
+
10
+
// export const revalidate = 3600; // 1 hour
11
+
12
+
export async function GET(
13
+
request: NextRequest,
14
+
{ params }: LeaderboardProps
15
+
) {
16
+
const guild = await getGuild(params.guildId);
17
+
const members = await getTopMembers(params.guildId, { page: 1, type: "messages" });
18
+
19
+
const intl = new Intl.NumberFormat("en", { notation: "standard" });
20
+
21
+
return new ImageResponse(
22
+
(
23
+
<div tw="bg-[#0d0f11] p-18 flex flex-col w-full h-full text-6xl text-white">
24
+
<div tw="flex mb-6">
25
+
<span tw="text-3xl bg-violet-400/75 opacity-80 pt-2 px-4 rounded-xl w-min" style={{ fontWeight: 500 }}>Leaderboard</span>
26
+
</div>
27
+
<div tw="flex mb-2 items-center">
28
+
{guild?.icon && <img src={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png`} tw="h-20 w-20 rounded-2xl relative bottom-3 mr-5" alt="" />}
29
+
<div style={{ fontWeight: 800, fontSize: "5rem" }}>{guild?.name || "unknown"}</div>
30
+
</div>
31
+
<div tw="text-4xl text-gray-500" style={{ fontWeight: 500 }}>Explore the vibrant community dynamics</div>
32
+
33
+
<div tw="flex justify-between mt-42">
34
+
{members.slice(0, 3).map((member) => (
35
+
<div key={member.id} tw="flex flex-col">
36
+
<div tw="flex mb-2 text-5xl" style={{ fontWeight: 600 }}>@{member.username}</div>
37
+
<div tw="text-2xl text-gray-400 flex text-3xl" style={{ fontWeight: 500 }}>{intl.format(member.activity.messages)} messages</div>
38
+
</div>
39
+
))}
40
+
</div>
41
+
42
+
</div>
43
+
),
44
+
{
45
+
width: 1200,
46
+
height: 630,
47
+
fonts: [
48
+
{
49
+
name: "Poppins",
50
+
data: await readFile(process.cwd() + "/assets/Poppins-Regular.ttf"),
51
+
style: "normal",
52
+
weight: 400
53
+
},
54
+
{
55
+
name: "Poppins",
56
+
data: await readFile(process.cwd() + "/assets/Poppins-Medium.ttf"),
57
+
style: "normal",
58
+
weight: 500
59
+
},
60
+
{
61
+
name: "Poppins",
62
+
data: await readFile(process.cwd() + "/assets/Poppins-SemiBold.ttf"),
63
+
style: "normal",
64
+
weight: 600
65
+
},
66
+
{
67
+
name: "Poppins",
68
+
data: await readFile(process.cwd() + "/assets/Poppins-ExtraBold.ttf"),
69
+
style: "normal",
70
+
weight: 800
71
+
}
72
+
]
73
+
}
74
+
);
75
+
}
assets/Poppins-ExtraBold.ttf
assets/Poppins-ExtraBold.ttf
This is a binary file and will not be displayed.
assets/Poppins-Medium.ttf
assets/Poppins-Medium.ttf
This is a binary file and will not be displayed.
assets/Poppins-Regular.ttf
assets/Poppins-Regular.ttf
This is a binary file and will not be displayed.
assets/Poppins-SemiBold.ttf
assets/Poppins-SemiBold.ttf
This is a binary file and will not be displayed.
public/shiggy.webm
public/shiggy.webm
This is a binary file and will not be displayed.