Changes
This commit is contained in:
parent
6a9ec1fe9f
commit
b560341068
1
Frontend/.gitignore
vendored
1
Frontend/.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.next/
|
.next/
|
||||||
|
.fleet/
|
||||||
.vscode/
|
.vscode/
|
||||||
.env*.local
|
.env*.local
|
||||||
next-env.d.ts
|
next-env.d.ts
|
4
Frontend/.prettierrc
Normal file
4
Frontend/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4
|
||||||
|
}
|
@ -3,12 +3,13 @@ import { minecrafter } from "@/font/fonts";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page metadata.
|
* Page metadata.
|
||||||
*/
|
*/
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Docs",
|
title: "Docs",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,34 +17,34 @@ export const metadata: Metadata = {
|
|||||||
*
|
*
|
||||||
* @returns the page jsx
|
* @returns the page jsx
|
||||||
*/
|
*/
|
||||||
const DocsPage = (): JSX.Element => (
|
const DocsPage = (): ReactElement => (
|
||||||
<main className="h-[64vh] flex flex-col gap-3 justify-center items-center">
|
<main className="h-[64vh] flex flex-col gap-3 justify-center items-center">
|
||||||
{/* Creeper */}
|
{/* Creeper */}
|
||||||
<div className="absolute left-28 bottom-16 pointer-events-none">
|
<div className="absolute left-28 bottom-16 pointer-events-none">
|
||||||
<Creeper />
|
<Creeper />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<h1
|
<h1
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-6xl text-minecraft-green-3 pointer-events-none",
|
"text-6xl text-minecraft-green-3 pointer-events-none",
|
||||||
minecrafter.className
|
minecrafter.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Documentation
|
Documentation
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<h2 className="text-xl">
|
<h2 className="text-xl">
|
||||||
This page is still under construction, however we do have a{" "}
|
This page is still under construction, however we do have a{" "}
|
||||||
<Link
|
<Link
|
||||||
className="text-minecraft-green-4"
|
className="text-minecraft-green-4"
|
||||||
href="https://git.rainnny.club/Rainnny/RESTfulMC/wiki"
|
href="https://git.rainnny.club/Rainnny/RESTfulMC/wiki"
|
||||||
>
|
>
|
||||||
Wiki
|
Wiki
|
||||||
</Link>
|
</Link>
|
||||||
!
|
!
|
||||||
</h2>
|
</h2>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
export default DocsPage;
|
export default DocsPage;
|
||||||
|
@ -7,11 +7,11 @@ import StatisticCounters from "@/components/landing/statistic-counters";
|
|||||||
*
|
*
|
||||||
* @returns the page jsx
|
* @returns the page jsx
|
||||||
*/
|
*/
|
||||||
const LandingPage = (): JSX.Element => (
|
const LandingPage = (): ReactElement => (
|
||||||
<main className="px-3">
|
<main className="px-3">
|
||||||
<Hero />
|
<Hero />
|
||||||
<FeaturedContent />
|
<FeaturedContent />
|
||||||
<StatisticCounters />
|
<StatisticCounters />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
export default LandingPage;
|
export default LandingPage;
|
||||||
|
@ -7,62 +7,63 @@ import { PageProps } from "@/types/page";
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { CachedPlayer, getPlayer, type RestfulMCAPIError } from "restfulmc-lib";
|
import { CachedPlayer, getPlayer, type RestfulMCAPIError } from "restfulmc-lib";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The page to lookup a player.
|
* The page to lookup a player.
|
||||||
*
|
*
|
||||||
* @returns the page jsx
|
* @returns the page jsx
|
||||||
*/
|
*/
|
||||||
const PlayerPage = async ({ params }: PageProps): Promise<JSX.Element> => {
|
const PlayerPage = async ({ params }: PageProps): Promise<ReactElement> => {
|
||||||
let error: string | undefined = undefined; // The error to display
|
let error: string | undefined = undefined; // The error to display
|
||||||
let result: CachedPlayer | undefined = undefined; // The player to display
|
let result: CachedPlayer | undefined = undefined; // The player to display
|
||||||
const query: string | undefined = trimQuery(params.slug?.[0]); // The query to search for
|
const query: string | undefined = trimQuery(params.slug?.[0]); // The query to search for
|
||||||
|
|
||||||
// Try and get the player to display
|
// Try and get the player to display
|
||||||
try {
|
try {
|
||||||
result = query ? await getPlayer(query) : undefined;
|
result = query ? await getPlayer(query) : undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = (err as RestfulMCAPIError).message; // Set the error message
|
error = (err as RestfulMCAPIError).message; // Set the error message
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the page
|
// Render the page
|
||||||
return (
|
return (
|
||||||
<main className="px-3 h-screen flex justify-center items-center">
|
<main className="px-3 h-screen flex justify-center items-center">
|
||||||
<div className="mt-0 sm:mt-[45rem] xl:mt-0 flex flex-col xl:flex-row xl:gap-24 2xl:gap-48 transition-all transform-gpu">
|
<div className="mt-0 sm:mt-[45rem] xl:mt-0 flex flex-col xl:flex-row xl:gap-24 2xl:gap-48 transition-all transform-gpu">
|
||||||
{/* Banner */}
|
{/* Banner */}
|
||||||
<Image
|
<Image
|
||||||
className="hidden sm:flex xl:my-auto h-[28rem] pointer-events-none"
|
className="hidden sm:flex xl:my-auto h-[28rem] pointer-events-none"
|
||||||
src="/media/players.webp"
|
src="/media/players.webp"
|
||||||
alt="Minecraft Players"
|
alt="Minecraft Players"
|
||||||
width={632}
|
width={632}
|
||||||
height={632}
|
height={632}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="pb-16 xl:pb-0 flex flex-col gap-7">
|
<div className="pb-16 xl:pb-0 flex flex-col gap-7">
|
||||||
<h1
|
<h1
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-20 text-6xl text-minecraft-green-3 text-center pointer-events-none",
|
"mt-20 text-6xl text-minecraft-green-3 text-center pointer-events-none",
|
||||||
minecrafter.className
|
minecrafter.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Player Lookup
|
Player Lookup
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="flex flex-col gap-5 px-10 xs:px-0">
|
<div className="flex flex-col gap-5 px-10 xs:px-0">
|
||||||
{/* Error */}
|
{/* Error */}
|
||||||
{error && <p className="text-red-500">{error}</p>}
|
{error && <p className="text-red-500">{error}</p>}
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<PlayerSearch query={query} />
|
<PlayerSearch query={query} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Player Result */}
|
{/* Player Result */}
|
||||||
{result && <PlayerResult player={result} />}
|
{result && <PlayerResult player={result} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,35 +74,35 @@ const PlayerPage = async ({ params }: PageProps): Promise<JSX.Element> => {
|
|||||||
* @returns the generated metadata
|
* @returns the generated metadata
|
||||||
*/
|
*/
|
||||||
export const generateMetadata = async ({
|
export const generateMetadata = async ({
|
||||||
params,
|
params,
|
||||||
}: PageProps): Promise<Metadata> => {
|
}: PageProps): Promise<Metadata> => {
|
||||||
const query: string | undefined = trimQuery(params.slug?.[0]); // The query to embed for
|
const query: string | undefined = trimQuery(params.slug?.[0]); // The query to embed for
|
||||||
|
|
||||||
// Try and get the player to display
|
// Try and get the player to display
|
||||||
if (query) {
|
if (query) {
|
||||||
try {
|
try {
|
||||||
const player: CachedPlayer = await getPlayer(query); // Get the player to embed
|
const player: CachedPlayer = await getPlayer(query); // Get the player to embed
|
||||||
return Embed({
|
return Embed({
|
||||||
title: `${player.username}'s Profile`,
|
title: `${player.username}'s Profile`,
|
||||||
description: `UUID: ${player.uniqueId}\n\nClick to view data about this player.`,
|
description: `UUID: ${player.uniqueId}\n\nClick to view data about this player.`,
|
||||||
thumbnail: player.skin.parts.HEAD,
|
thumbnail: player.skin.parts.HEAD,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code: number = (err as RestfulMCAPIError).code; // Get the error status code
|
const code: number = (err as RestfulMCAPIError).code; // Get the error status code
|
||||||
if (code === 400) {
|
if (code === 400) {
|
||||||
return Embed({
|
return Embed({
|
||||||
title: "Invalid Player",
|
title: "Invalid Player",
|
||||||
description: "The player you searched for is invalid.",
|
description: "The player you searched for is invalid.",
|
||||||
});
|
});
|
||||||
} else if (code === 404) {
|
} else if (code === 404) {
|
||||||
return Embed({
|
return Embed({
|
||||||
title: "Player Not Found",
|
title: "Player Not Found",
|
||||||
description: "The player you searched for was not found.",
|
description: "The player you searched for was not found.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,11 +112,11 @@ export const generateMetadata = async ({
|
|||||||
* @returns the trimmed query
|
* @returns the trimmed query
|
||||||
*/
|
*/
|
||||||
const trimQuery = (query: string | undefined): string | undefined => {
|
const trimQuery = (query: string | undefined): string | undefined => {
|
||||||
// Limit the query to 36 chars
|
// Limit the query to 36 chars
|
||||||
if (query && query.length > 36) {
|
if (query && query.length > 36) {
|
||||||
query = query.substr(0, 36);
|
query = query.substr(0, 36);
|
||||||
}
|
}
|
||||||
return query;
|
return query;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PlayerPage;
|
export default PlayerPage;
|
||||||
|
@ -3,28 +3,29 @@
|
|||||||
import { minecrafter } from "@/font/fonts";
|
import { minecrafter } from "@/font/fonts";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import CountUp from "react-countup";
|
import CountUp from "react-countup";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for the counter.
|
* Props for the counter.
|
||||||
*/
|
*/
|
||||||
type CounterProps = {
|
type CounterProps = {
|
||||||
/**
|
/**
|
||||||
* The name of the counter.
|
* The name of the counter.
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The amount to count up to.
|
* The amount to count up to.
|
||||||
*/
|
*/
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The optional duration of the count up.
|
* The optional duration of the count up.
|
||||||
* <p>
|
* <p>
|
||||||
* Uses the default duration if not provided.
|
* Uses the default duration if not provided.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
duration?: number | undefined;
|
duration?: number | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,16 +35,19 @@ type CounterProps = {
|
|||||||
* @param duration the optional duration of the count up
|
* @param duration the optional duration of the count up
|
||||||
* @returns the counter jsx
|
* @returns the counter jsx
|
||||||
*/
|
*/
|
||||||
const Counter = ({ name, amount, duration }: CounterProps): JSX.Element => (
|
const Counter = ({ name, amount, duration }: CounterProps): ReactElement => (
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<h1
|
<h1
|
||||||
className={cn("text-6xl text-minecraft-green-3", minecrafter.className)}
|
className={cn(
|
||||||
>
|
"text-6xl text-minecraft-green-3",
|
||||||
{name}
|
minecrafter.className
|
||||||
</h1>
|
)}
|
||||||
<h2 className="text-4xl font-semibold uppercase">
|
>
|
||||||
<CountUp start={0} end={amount} duration={duration} />
|
{name}
|
||||||
</h2>
|
</h1>
|
||||||
</div>
|
<h2 className="text-4xl font-semibold uppercase">
|
||||||
|
<CountUp start={0} end={amount} duration={duration} />
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
export default Counter;
|
export default Counter;
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A creeper image.
|
* A creeper image.
|
||||||
*
|
*
|
||||||
* @returns the creeper jsx
|
* @returns the creeper jsx
|
||||||
*/
|
*/
|
||||||
const Creeper = (): JSX.Element => (
|
const Creeper = (): ReactElement => (
|
||||||
<Image
|
<Image
|
||||||
src="/media/creeper.png"
|
src="/media/creeper.png"
|
||||||
alt="A Minecraft Creeper"
|
alt="A Minecraft Creeper"
|
||||||
width={216}
|
width={216}
|
||||||
height={216}
|
height={216}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
export default Creeper;
|
export default Creeper;
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
const Footer = (): JSX.Element => <footer>FOOTER</footer>;
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
|
const Footer = (): ReactElement => <footer>FOOTER</footer>;
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
@ -4,28 +4,30 @@ import { StarIcon } from "@heroicons/react/24/outline";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
const GitHubStarButton = async (): Promise<JSX.Element> => {
|
const GitHubStarButton = async (): Promise<ReactElement> => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/Rainnny7/RESTfulMC"
|
href="https://github.com/Rainnny7/RESTfulMC"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<MinecraftButton className="flex gap-1.5 items-center group/star">
|
<MinecraftButton className="flex gap-1.5 items-center group/star">
|
||||||
{/* Star Count */}
|
{/* Star Count */}
|
||||||
<Suspense fallback={<Skeleton className="w-4 h-5 rounded-md" />}>
|
<Suspense
|
||||||
<GitHubStarCount />
|
fallback={<Skeleton className="w-4 h-5 rounded-md" />}
|
||||||
</Suspense>
|
>
|
||||||
|
<GitHubStarCount />
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
<StarIcon
|
<StarIcon
|
||||||
className="group-hover/star:text-orange-400 delay-0 transition-all transform-gpu"
|
className="group-hover/star:text-orange-400 delay-0 transition-all transform-gpu"
|
||||||
width={22}
|
width={22}
|
||||||
height={22}
|
height={22}
|
||||||
/>
|
/>
|
||||||
<span>Star on GitHub</span>
|
<span>Star on GitHub</span>
|
||||||
</MinecraftButton>
|
</MinecraftButton>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,10 +36,10 @@ const GitHubStarButton = async (): Promise<JSX.Element> => {
|
|||||||
* @returns the star count jsx
|
* @returns the star count jsx
|
||||||
*/
|
*/
|
||||||
const GitHubStarCount = async (): Promise<JSX.Element> => {
|
const GitHubStarCount = async (): Promise<JSX.Element> => {
|
||||||
const stars: number = await getStarCount(); // Get the repo star count
|
const stars: number = await getStarCount(); // Get the repo star count
|
||||||
return (
|
return (
|
||||||
<code className="px-1 rounded-md bg-minecraft-green-3/80">{stars}</code>
|
<code className="px-1 rounded-md bg-minecraft-green-3/80">{stars}</code>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,12 +49,12 @@ const GitHubStarCount = async (): Promise<JSX.Element> => {
|
|||||||
* @returns the star count
|
* @returns the star count
|
||||||
*/
|
*/
|
||||||
const getStarCount = async (): Promise<number> => {
|
const getStarCount = async (): Promise<number> => {
|
||||||
const response: Response = await fetch(
|
const response: Response = await fetch(
|
||||||
"https://api.github.com/repos/Rainnny7/RESTfulMC",
|
"https://api.github.com/repos/Rainnny7/RESTfulMC",
|
||||||
{ next: { revalidate: 300 } } // Revalidate every 5 minutes
|
{ next: { revalidate: 300 } } // Revalidate every 5 minutes
|
||||||
);
|
);
|
||||||
const json: any = await response.json(); // Get the JSON response
|
const json: any = await response.json(); // Get the JSON response
|
||||||
return json.stargazers_count; // Return the stars
|
return json.stargazers_count; // Return the stars
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GitHubStarButton;
|
export default GitHubStarButton;
|
||||||
|
@ -3,20 +3,23 @@ import { minecrafter } from "@/font/fonts";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { FeaturedItemProps } from "@/types/config";
|
import { FeaturedItemProps } from "@/types/config";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The featured content component.
|
* The featured content component.
|
||||||
*
|
*
|
||||||
* @returns the featured content jsx
|
* @returns the featured content jsx
|
||||||
*/
|
*/
|
||||||
const FeaturedContent = (): JSX.Element => (
|
const FeaturedContent = (): ReactElement => (
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
<div className="max-w-2xl flex flex-wrap justify-center gap-5">
|
<div className="max-w-2xl flex flex-wrap justify-center gap-5">
|
||||||
{config.featuredItems.map((item, index) => (
|
{config.featuredItems.map(
|
||||||
<FeaturedItem key={index} {...item} />
|
(item: FeaturedItemProps, index: number) => (
|
||||||
))}
|
<FeaturedItem key={index} {...item} />
|
||||||
</div>
|
)
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,25 +29,28 @@ const FeaturedContent = (): JSX.Element => (
|
|||||||
* @returns the item jsx
|
* @returns the item jsx
|
||||||
*/
|
*/
|
||||||
const FeaturedItem = ({
|
const FeaturedItem = ({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
image,
|
image,
|
||||||
href,
|
href,
|
||||||
}: FeaturedItemProps): JSX.Element => (
|
}: FeaturedItemProps): ReactElement => (
|
||||||
<Link
|
<Link
|
||||||
className="pt-28 w-[19rem] h-80 flex flex-col gap-1 items-center bg-center bg-cover bg-no-repeat rounded-3xl text-center backdrop-blur-md hover:scale-[1.01] transition-all transform-gpu"
|
className="pt-28 w-[19rem] h-80 flex flex-col gap-1 items-center bg-center bg-cover bg-no-repeat rounded-3xl text-center backdrop-blur-md hover:scale-[1.01] transition-all transform-gpu"
|
||||||
href={href}
|
href={href}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${image})`,
|
backgroundImage: `url(${image})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1
|
<h1
|
||||||
className={cn("text-3xl font-semibold text-white", minecrafter.className)}
|
className={cn(
|
||||||
>
|
"text-3xl font-semibold text-white",
|
||||||
{name}
|
minecrafter.className
|
||||||
</h1>
|
)}
|
||||||
<h2 className="text-md max-w-[15rem]">{description}</h2>
|
>
|
||||||
</Link>
|
{name}
|
||||||
|
</h1>
|
||||||
|
<h2 className="text-md max-w-[15rem]">{description}</h2>
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default FeaturedContent;
|
export default FeaturedContent;
|
||||||
|
@ -4,40 +4,43 @@ import config from "@/config";
|
|||||||
import { minecrafter } from "@/font/fonts";
|
import { minecrafter } from "@/font/fonts";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hero content.
|
* The hero content.
|
||||||
*
|
*
|
||||||
* @returns the hero jsx
|
* @returns the hero jsx
|
||||||
*/
|
*/
|
||||||
const Hero = (): JSX.Element => (
|
const Hero = (): ReactElement => (
|
||||||
<div className="pt-56 pb-40 flex flex-col gap-8 justify-center items-center">
|
<div className="pt-56 pb-40 flex flex-col gap-8 justify-center items-center">
|
||||||
<div className="flex flex-col gap-4 items-center text-center">
|
<div className="flex flex-col gap-4 items-center text-center">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<h1
|
<h1
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-5xl sm:text-6xl text-minecraft-green-3",
|
"text-5xl sm:text-6xl text-minecraft-green-3",
|
||||||
minecrafter.className
|
minecrafter.className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{config.siteName}
|
{config.siteName}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* Subtitle */}
|
{/* Subtitle */}
|
||||||
<h2 className="text-xl">{config.metadata.description}</h2>
|
<h2 className="text-xl">{config.metadata.description}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Links */}
|
{/* Links */}
|
||||||
<div className="flex gap-5 xs:gap-10">
|
<div className="flex gap-5 xs:gap-10">
|
||||||
<Link href="/docs">
|
<Link href="/docs">
|
||||||
<MinecraftButton className="w-44 h-12">Get Started</MinecraftButton>
|
<MinecraftButton className="w-44 h-12">
|
||||||
</Link>
|
Get Started
|
||||||
|
</MinecraftButton>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Star on Github <3 */}
|
{/* Star on Github <3 */}
|
||||||
<div className="md:hidden">
|
<div className="md:hidden">
|
||||||
<GitHubStarButton />
|
<GitHubStarButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
export default Hero;
|
export default Hero;
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import Counter from "@/components/counter";
|
import Counter from "@/components/counter";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The statistic counters component.
|
* The statistic counters component.
|
||||||
*
|
*
|
||||||
* @returns the counters jsx
|
* @returns the counters jsx
|
||||||
*/
|
*/
|
||||||
const StatisticCounters = (): JSX.Element => (
|
const StatisticCounters = (): ReactElement => (
|
||||||
<div className="py-56 flex justify-center items-center">
|
<div className="py-56 flex justify-center items-center">
|
||||||
<div className="grid grid-flow-row grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-24">
|
<div className="grid grid-flow-row grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-24">
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
export default StatisticCounters;
|
export default StatisticCounters;
|
||||||
|
@ -7,66 +7,69 @@ import { cn } from "@/lib/utils";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The navbar for the site.
|
* The navbar for the site.
|
||||||
*
|
*
|
||||||
* @returns the navbar jsx
|
* @returns the navbar jsx
|
||||||
*/
|
*/
|
||||||
const Navbar = (): JSX.Element => {
|
const Navbar = (): ReactElement => {
|
||||||
const path: string = usePathname(); // Get the current path
|
const path: string = usePathname(); // Get the current path
|
||||||
return (
|
return (
|
||||||
<nav className="fixed inset-x-0 flex h-16 sm:px-12 justify-center sm:justify-between items-center bg-navbar-background z-50">
|
<nav className="fixed inset-x-0 flex h-16 sm:px-12 justify-center sm:justify-between items-center bg-navbar-background z-50">
|
||||||
{/* Left */}
|
{/* Left */}
|
||||||
<div className="flex gap-3 xs:gap-7 lg:gap-12 items-center transition-all transform-gpu">
|
<div className="flex gap-3 xs:gap-7 lg:gap-12 items-center transition-all transform-gpu">
|
||||||
{/* App Branding */}
|
{/* App Branding */}
|
||||||
<Link
|
<Link
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-3xl text-minecraft-green-3 hover:opacity-85 transition-all transform-gpu",
|
"text-3xl text-minecraft-green-3 hover:opacity-85 transition-all transform-gpu",
|
||||||
minecrafter.className
|
minecrafter.className
|
||||||
)}
|
)}
|
||||||
href="/"
|
href="/"
|
||||||
>
|
>
|
||||||
{/* Small Screens */}
|
{/* Small Screens */}
|
||||||
<Image
|
<Image
|
||||||
className="lg:hidden"
|
className="lg:hidden"
|
||||||
src="/media/logo.webp"
|
src="/media/logo.webp"
|
||||||
alt="Site Logo"
|
alt="Site Logo"
|
||||||
width={42}
|
width={42}
|
||||||
height={42}
|
height={42}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Large Screens */}
|
{/* Large Screens */}
|
||||||
<span className="hidden lg:flex">{config.siteName}</span>
|
<span className="hidden lg:flex">{config.siteName}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Links */}
|
{/* Links */}
|
||||||
<div className="flex gap-7">
|
<div className="flex gap-7">
|
||||||
{Object.entries(config.navbarLinks).map((link, index) => {
|
{Object.entries(config.navbarLinks).map(
|
||||||
const url: string = link[1]; // The href of the link
|
(link: [string, string], index: number) => {
|
||||||
let active: boolean = path.startsWith(url); // Is this the active link?
|
const url: string = link[1]; // The href of the link
|
||||||
return (
|
let active: boolean = path.startsWith(url); // Is this the active link?
|
||||||
<Link
|
return (
|
||||||
key={index}
|
<Link
|
||||||
className={cn(
|
key={index}
|
||||||
"font-semibold uppercase hover:text-minecraft-green-4 transition-all transform-gpu",
|
className={cn(
|
||||||
active && "text-minecraft-green-4"
|
"font-semibold uppercase hover:text-minecraft-green-4 transition-all transform-gpu",
|
||||||
)}
|
active && "text-minecraft-green-4"
|
||||||
href={url}
|
)}
|
||||||
>
|
href={url}
|
||||||
{link[0]}
|
>
|
||||||
</Link>
|
{link[0]}
|
||||||
);
|
</Link>
|
||||||
})}
|
);
|
||||||
</div>
|
}
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Social Buttons - Right */}
|
{/* Social Buttons - Right */}
|
||||||
<div className="hidden md:flex">
|
<div className="hidden md:flex">
|
||||||
{/* Star on Github <3 */}
|
{/* Star on Github <3 */}
|
||||||
<GitHubStarButton />
|
<GitHubStarButton />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Navbar;
|
export default Navbar;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { CachedPlayer, SkinPart } from "restfulmc-lib";
|
import { CachedPlayer, SkinPart } from "restfulmc-lib";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a player search.
|
* The result of a player search.
|
||||||
@ -10,62 +11,68 @@ import { CachedPlayer, SkinPart } from "restfulmc-lib";
|
|||||||
* @returns the player result jsx
|
* @returns the player result jsx
|
||||||
*/
|
*/
|
||||||
const PlayerResult = ({
|
const PlayerResult = ({
|
||||||
player: {
|
player: {
|
||||||
uniqueId,
|
uniqueId,
|
||||||
username,
|
username,
|
||||||
skin: { parts },
|
skin: { parts },
|
||||||
legacy,
|
legacy,
|
||||||
},
|
},
|
||||||
}: {
|
}: {
|
||||||
player: CachedPlayer;
|
player: CachedPlayer;
|
||||||
}): JSX.Element => (
|
}): ReactElement => (
|
||||||
<div className="px-2 py-3 flex flex-col gap-3 items-center bg-muted rounded-xl divide-y divide-zinc-700">
|
<div className="px-2 py-3 flex flex-col gap-3 items-center bg-muted rounded-xl divide-y divide-zinc-700">
|
||||||
{/* Details */}
|
{/* Details */}
|
||||||
<div className="flex gap-5 items-center">
|
<div className="flex gap-5 items-center">
|
||||||
{/* Player Head */}
|
{/* Player Head */}
|
||||||
<Image
|
<Image
|
||||||
className="w-24 h-24 sm:w-28 sm:h-28 md:w-32 md:h-32"
|
className="w-24 h-24 sm:w-28 sm:h-28 md:w-32 md:h-32"
|
||||||
src={parts.HEAD}
|
src={parts.HEAD}
|
||||||
alt={`${username}'s Head`}
|
alt={`${username}'s Head`}
|
||||||
width={128}
|
width={128}
|
||||||
height={128}
|
height={128}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Name, Unique ID, and Badges */}
|
{/* Name, Unique ID, and Badges */}
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<h1 className="text-xl font-bold text-minecraft-green-3">{username}</h1>
|
<h1 className="text-xl font-bold text-minecraft-green-3">
|
||||||
<code className="text-xs xs:text-sm text-zinc-300">{uniqueId}</code>
|
{username}
|
||||||
|
</h1>
|
||||||
|
<code className="text-xs xs:text-sm text-zinc-300">
|
||||||
|
{uniqueId}
|
||||||
|
</code>
|
||||||
|
|
||||||
{/* Legacy Badge */}
|
{/* Legacy Badge */}
|
||||||
{legacy && <p className="text-sm font-semibold uppercase">Legacy</p>}
|
{legacy && (
|
||||||
</div>
|
<p className="text-sm font-semibold uppercase">Legacy</p>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Skin Parts */}
|
{/* Skin Parts */}
|
||||||
<div className="pt-3 w-[90%] flex flex-col gap-3">
|
<div className="pt-3 w-[90%] flex flex-col gap-3">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<h1 className="font-semibold uppercase">Skin Parts</h1>
|
<h1 className="font-semibold uppercase">Skin Parts</h1>
|
||||||
|
|
||||||
{/* Skin Parts */}
|
{/* Skin Parts */}
|
||||||
<div className="flex gap-5">
|
<div className="flex gap-5">
|
||||||
{Object.entries(parts)
|
{Object.entries(parts)
|
||||||
.filter(
|
.filter(
|
||||||
([part]) =>
|
([part]) =>
|
||||||
part === SkinPart.HEAD ||
|
part === SkinPart.HEAD ||
|
||||||
part === SkinPart.FACE ||
|
part === SkinPart.FACE ||
|
||||||
part === SkinPart.BODY_FLAT
|
part === SkinPart.BODY_FLAT
|
||||||
)
|
)
|
||||||
.map(([part, url], index) => (
|
.map(([part, url], index) => (
|
||||||
<Link key={index} href={url} target="_blank">
|
<Link key={index} href={url} target="_blank">
|
||||||
<img
|
<img
|
||||||
className="h-20 sm:h-24 md:h-28 hover:scale-[1.02] transition-all transform-gpu"
|
className="h-20 sm:h-24 md:h-28 hover:scale-[1.02] transition-all transform-gpu"
|
||||||
src={url}
|
src={url}
|
||||||
alt={`${username}'s ${part}`}
|
alt={`${username}'s ${part}`}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
export default PlayerResult;
|
export default PlayerResult;
|
||||||
|
@ -11,31 +11,31 @@ import { redirect } from "next/navigation";
|
|||||||
* @returns the search component jsx
|
* @returns the search component jsx
|
||||||
*/
|
*/
|
||||||
const PlayerSearch = ({
|
const PlayerSearch = ({
|
||||||
query,
|
query,
|
||||||
}: {
|
}: {
|
||||||
query: string | undefined;
|
query: string | undefined;
|
||||||
}): JSX.Element => {
|
}): ReactElement => {
|
||||||
const handleRedirect = async (form: FormData) => {
|
const handleRedirect = async (form: FormData) => {
|
||||||
"use server";
|
"use server";
|
||||||
redirect(`/player/${form.get("query")}`);
|
redirect(`/player/${form.get("query")}`);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="flex flex-col gap-7 justify-center items-center"
|
className="flex flex-col gap-7 justify-center items-center"
|
||||||
action={handleRedirect}
|
action={handleRedirect}
|
||||||
>
|
>
|
||||||
<div className="w-full flex flex-col gap-3">
|
<div className="w-full flex flex-col gap-3">
|
||||||
<Label htmlFor="query">Username or UUID</Label>
|
<Label htmlFor="query">Username or UUID</Label>
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
name="query"
|
name="query"
|
||||||
placeholder="Query..."
|
placeholder="Query..."
|
||||||
defaultValue={query}
|
defaultValue={query}
|
||||||
maxLength={36}
|
maxLength={36}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MinecraftButton type="submit">Search</MinecraftButton>
|
<MinecraftButton type="submit">Search</MinecraftButton>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default PlayerSearch;
|
export default PlayerSearch;
|
||||||
|
@ -3,76 +3,76 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 30 5% 9%;
|
--background: 30 5% 9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
--card: 240 10% 3.9%;
|
--card: 240 10% 3.9%;
|
||||||
--card-foreground: 0 0% 98%;
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
--popover: 240 10% 3.9%;
|
--popover: 240 10% 3.9%;
|
||||||
--popover-foreground: 0 0% 98%;
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
--primary: 0 0% 98%;
|
--primary: 0 0% 98%;
|
||||||
--primary-foreground: 240 5.9% 10%;
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
--secondary: 240 3.7% 15.9%;
|
--secondary: 240 3.7% 15.9%;
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
--muted: 20 4% 14%;
|
--muted: 20 4% 14%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
|
||||||
--accent: 240 3.7% 15.9%;
|
--accent: 240 3.7% 15.9%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 240 3.7% 15.9%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 240 3.7% 15.9%;
|
||||||
--ring: 240 4.9% 83.9%;
|
--ring: 240 4.9% 83.9%;
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
/* Navbar */
|
/* Navbar */
|
||||||
--navbar-background: 0 0% 7%;
|
--navbar-background: 0 0% 7%;
|
||||||
|
|
||||||
/* Minecraft Colors (Dark -> Light) */
|
/* Minecraft Colors (Dark -> Light) */
|
||||||
--minecraft-green-1: 108 56% 25%;
|
--minecraft-green-1: 108 56% 25%;
|
||||||
--minecraft-green-2: 107 55% 34%;
|
--minecraft-green-2: 107 55% 34%;
|
||||||
--minecraft-green-3: 104 51% 43%;
|
--minecraft-green-3: 104 51% 43%;
|
||||||
--minecraft-green-4: 103 50% 53%;
|
--minecraft-green-4: 103 50% 53%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
|
|
||||||
/* Scrollbar (Firefox) */
|
/* Scrollbar (Firefox) */
|
||||||
scrollbar-color: hsl(var(--minecraft-green-2)) hsl(var(--background));
|
scrollbar-color: hsl(var(--minecraft-green-2)) hsl(var(--background));
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar (Chrome & Safari) */
|
/* Scrollbar (Chrome & Safari) */
|
||||||
@layer base {
|
@layer base {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@apply w-1.5;
|
@apply w-1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@apply bg-inherit;
|
@apply bg-inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
@apply bg-minecraft-green-2 rounded-3xl;
|
@apply bg-minecraft-green-2 rounded-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
@apply bg-opacity-80;
|
@apply bg-opacity-80;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user