Skip to content

Commit

Permalink
add featured servers
Browse files Browse the repository at this point in the history
  • Loading branch information
almeidx committed May 19, 2024
1 parent 835f7d2 commit 5e17dca
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 22 deletions.
102 changes: 82 additions & 20 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ import { AttachMoney, Brush, CloudDownload, Launch, MilitaryTech, Speed, Trendin
import Image from "next/image";
import Link from "next/link";
import { BOT_INVITE } from "../../shared-links.mjs";
import { guildIcon, type Snowflake } from "@/utils/discord-cdn.ts";
import { ImageWithFallback } from "@/components/ImageWithFallback.tsx";
import verifiedSvg from "@/assets/guild-badges/verified.svg";
import { formatToNearestOrderOfMagnitude } from "@/utils/format-to-nearest-order-of-magnitude.ts";

export default async function Homepage() {
const { guildCount, memberCount, messageCount, uptime } = await getStats();
const {
stats: { guildCount, memberCount, messageCount, uptime },
featured,
} = await getData();

return (
<div className="flex flex-col items-center gap-10 mb-8 py-4">
Expand Down Expand Up @@ -75,6 +82,20 @@ export default async function Homepage() {
</div>
</div>

{featured.length ? (
<>
<p className="text-3xl xl:text-4xl font-bold text-shadow-regular xl:whitespace-nowrap">
Trusted by Discord Servers <span className="italic">you</span> know!
</p>

<div className="container flex flex-row flex-wrap gap-y-4 gap-x-6 justify-center mb-4">
{featured.map((guild) => (
<FeaturedGuild key={guild.id} {...guild} />
))}
</div>
</>
) : null}

<div className="flex flex-col items-center gap-2 px-6 text-center [text-wrap:balance]">
<Showcase
index={0}
Expand Down Expand Up @@ -163,23 +184,61 @@ function StartLevelingButton() {
);
}

async function getStats(): Promise<StatsResponse> {
const response = await makeApiRequest("/stats", null, {
next: {
revalidate: 6 * 60 * 60, // 6 hours
},
}).catch(() => undefined);

if (!response?.ok) {
return {
guildCount: 24_000,
messageCount: 400_000_000,
memberCount: 10_000_000,
uptime: 99.9,
};
}

return response.json();
function ShowcaseSeparator() {
return <div className="my-4 h-6 w-[2px] bg-gradient-radial" />;
}

function FeaturedGuild({ id, icon, name, memberCount }: FeaturedGuild) {
return (
<div className="flex items-center gap-6 bg-darker px-3 py-2 border border-white/25 rounded-lg">
<ImageWithFallback
alt={`${name}'s icon`}
className="rounded-full size-16"
height={64}
src={guildIcon(id, icon, { size: 64 })}
width={64}
/>

<div className="flex flex-col">
<p className="flex items-center gap-2 text-2xl font-bold">
<Image src={verifiedSvg} alt="Verified guild badge" width={20} height={20} className="size-5 rounded-full" />
{name}
</p>
<p className="text-lg text-white/50">
{formatToNearestOrderOfMagnitude(memberCount).toLocaleString("en")} members
</p>
</div>
</div>
);
}

async function getData() {
const [statsResponse, featuredResponse] = await Promise.allSettled([
makeApiRequest("/stats", null, {
next: {
revalidate: 6 * 60 * 60, // 6 hours
},
}),
makeApiRequest("/guilds/featured", null, {
next: {
revalidate: 60 * 60, // 1 hour
},
}),
]);

const stats =
statsResponse.status === "fulfilled"
? ((await statsResponse.value.json()) as StatsResponse)
: {
guildCount: 24_000,
messageCount: 400_000_000,
memberCount: 10_000_000,
uptime: 99.9,
};
const featured =
featuredResponse.status === "fulfilled" ? ((await featuredResponse.value.json()) as FeaturedGuild[]) : [];

return { stats, featured };
}

interface StatsResponse {
Expand All @@ -189,6 +248,9 @@ interface StatsResponse {
uptime: number;
}

function ShowcaseSeparator() {
return <div className="my-4 h-6 w-[2px] bg-gradient-radial" />;
interface FeaturedGuild {
id: Snowflake;
name: string;
icon: string | null;
memberCount: number;
}
25 changes: 25 additions & 0 deletions src/assets/guild-badges/discoverable.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/assets/guild-badges/partner.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/assets/guild-badges/verified.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions src/components/ImageWithFallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import Image, { type StaticImageData } from "next/image";
import { type ComponentProps, useEffect, useState } from "react";
import fallbackAvatarImg from "@/assets/fallback-avatar.png";

/**
* A wrapper around Next.js' Image component that allows for a fallback image to be used if the original image fails to load.
*
* @remarks
* Uses `fallback` if `src` is nullable.
*/
export function ImageWithFallback({ src, fallback, ...props }: ImageWithFallbackProps) {
export function ImageWithFallback({ src, fallback = fallbackAvatarImg, ...props }: ImageWithFallbackProps) {
const [image, setImage] = useState(src ?? fallback);

// biome-ignore lint/correctness/useExhaustiveDependencies: This is intended
Expand All @@ -23,6 +24,6 @@ export function ImageWithFallback({ src, fallback, ...props }: ImageWithFallback
type NextImageProps = ComponentProps<typeof Image>;

interface ImageWithFallbackProps extends Omit<NextImageProps, "src"> {
readonly fallback: StaticImageData;
readonly fallback?: StaticImageData;
readonly src: NextImageProps["src"] | null;
}
22 changes: 22 additions & 0 deletions src/utils/format-to-nearest-order-of-magnitude.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Formats a number to the nearest order of magnitude.
*
* @example
* formatToNearestOrderOfMagnitude(1234); // 1000
* formatToNearestOrderOfMagnitude(12345); // 12000
* formatToNearestOrderOfMagnitude(123456); // 120000
* formatToNearestOrderOfMagnitude(1234567); // 1200000
* formatToNearestOrderOfMagnitude(12345678); // 12000000
*
* @param num - The number to be formatted.
* @returns The formatted number.
*/
export function formatToNearestOrderOfMagnitude(num: number) {
if (num < 10) {
return num;
}

const power = Math.floor(Math.log10(num)) - 1;
const base = 10 ** power;
return Math.floor(num / base) * base;
}

0 comments on commit 5e17dca

Please sign in to comment.