wow a footer!
Some checks failed
Deploy Frontend / docker (17, 3.8.5) (push) Has been cancelled

This commit is contained in:
Braydon 2024-04-18 20:12:25 -04:00
parent ab56798ae9
commit f1cd72e8b3
31 changed files with 860 additions and 725 deletions

4
Frontend/.gitignore vendored

@ -1,6 +1,8 @@
node_modules node_modules
.next/ .idea/
.fleet/ .fleet/
.vscode/ .vscode/
.VSCodeCounter/
.next/
.env*.local .env*.local
next-env.d.ts next-env.d.ts

Binary file not shown.

@ -49,5 +49,17 @@
"image": "/media/featured/server.jpg", "image": "/media/featured/server.jpg",
"href": "/server" "href": "/server"
} }
] ],
"footerLinks": {
"Utilities": {
"Player Query": "/player",
"Server Query": "/server",
"Mojang Status": "/mojang"
},
"Resources": {
"Source Code": "https://github.com/Rainnny7/RESTfulMC",
"Wiki": "https://git.rainnny.club/Rainnny/RESTfulMC/wiki",
"SwaggerUI": "https://api.restfulmc.cc/swagger-ui.html"
}
}
} }

@ -41,7 +41,7 @@
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"eslint": "^9.0.0", "eslint": "8.57.0",
"eslint-config-next": "14.2.2", "eslint-config-next": "14.2.2",
"postcss": "^8", "postcss": "^8",
"sleep-promise": "^9.1.0", "sleep-promise": "^9.1.0",

@ -18,7 +18,7 @@ export const metadata: Metadata = {
* @returns the page jsx * @returns the page jsx
*/ */
const DocsPage = (): ReactElement => ( const DocsPage = (): ReactElement => (
<main className="h-[84vh] flex flex-col gap-3 justify-center items-center text-center pointer-events-none"> <main className="h-screen flex flex-col gap-3 justify-center items-center text-center pointer-events-none">
{/* Creeper */} {/* Creeper */}
<Creeper /> <Creeper />

@ -4,9 +4,9 @@ import { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { ReactElement } from "react"; import { ReactElement } from "react";
import { import {
getMojangServerStatus,
MojangServerStatus, MojangServerStatus,
MojangServerStatusResponse, MojangServerStatusResponse,
getMojangServerStatus,
} from "restfulmc-lib"; } from "restfulmc-lib";
/** /**

@ -1,8 +1,8 @@
import Background from "@/components/landing/background";
import FeaturedContent from "@/components/landing/featured-content"; import FeaturedContent from "@/components/landing/featured-content";
import Hero from "@/components/landing/hero"; import Hero from "@/components/landing/hero";
import StatisticCounters from "@/components/landing/statistic-counters"; import StatisticCounters from "@/components/landing/statistic-counters";
import { ReactElement } from "react"; import { ReactElement } from "react";
import HeroBackground from "../components/landing/background";
/** /**
* The landing page. * The landing page.
@ -13,7 +13,7 @@ const LandingPage = (): ReactElement => (
<main className="flex flex-col gap-10"> <main className="flex flex-col gap-10">
{/* Hero */} {/* Hero */}
<div className="relative"> <div className="relative">
<Background /> <HeroBackground />
<Hero /> <Hero />
</div> </div>

@ -12,9 +12,9 @@ import { ReactElement } from "react";
import { import {
CachedBedrockMinecraftServer, CachedBedrockMinecraftServer,
CachedJavaMinecraftServer, CachedJavaMinecraftServer,
ServerPlatform,
getMinecraftServer, getMinecraftServer,
type RestfulMCAPIError, type RestfulMCAPIError,
ServerPlatform,
} from "restfulmc-lib"; } from "restfulmc-lib";
/** /**

@ -1,4 +1,86 @@
import Image from "next/image";
import Link from "next/link";
import { ReactElement } from "react"; import { ReactElement } from "react";
import config from "@/config";
import { minecrafter } from "@/font/fonts";
import { cn } from "@/lib/utils";
import { FooterLinks } from "@/types/config";
const Footer = (): ReactElement => <footer>FOOTER</footer>; const Footer = (): ReactElement => (
<footer
className={cn(
`before:absolute before:top-0 before:left-0 before:w-full before:h-full before:bg-[url("/media/dark-wool-background.png")] before:bg-center before:bg-repeat`, // Background
"relative inset-x-0 bottom-0 h-72 flex justify-center items-center", // Styling
`after:absolute after:top-0 after:left-0 after:-translate-y-20 after:w-full after:h-20 after:bg-[url("/media/dark-wool-transition.png")] after:bg-center after:bg-repeat` // Top Border
)}
>
<div className="xl:px-40 pb-14 md:pb-0 flex flex-col md:flex-row justify-around items-center z-50 w-full h-full transition-all transform-gpu">
{/* Branding */}
<div className="flex flex-col justify-center pointer-events-none">
{/* Logo & Site Name */}
<div className="flex gap-7 items-center">
<Image
src="/media/logo.webp"
alt="Site Logo"
width={56}
height={56}
/>
<h1
className={cn(
"text-5xl md:text-4xl lg:text-5xl text-minecraft-green-3",
minecrafter.className
)}
>
{config.siteName}
</h1>
</div>
{/* Disclaimer */}
<p className="absolute bottom-7 font-medium opacity-65">
Not affiliated with Mojang or Microsoft.
</p>
</div>
{/* Links */}
<div className="flex gap-16">
{Object.keys(config.footerLinks).map(
(header: string, groupIndex: number): ReactElement => (
<div key={groupIndex} className="flex flex-col gap-2">
{/* Header */}
<h1
className={cn(
"text-3xl text-minecraft-green-3 pointer-events-none",
minecrafter.className
)}
>
{header}
</h1>
{/* Links */}
<div className="flex flex-col gap-1">
{Object.entries(
(config.footerLinks as FooterLinks)[header]
).map(
(
[name, url]: [string, string],
linkIndex: number
): ReactElement => (
<Link
key={linkIndex}
className="font-semibold hover:opacity-85 transition-all transform-gpu"
href={url}
>
{name}
</Link>
)
)}
</div>
</div>
)
)}
</div>
</div>
</footer>
);
export default Footer; export default Footer;

@ -33,7 +33,6 @@ const GitHubStarButton = (): ReactElement => {
); );
const json: any = await response.json(); // Get the JSON response const json: any = await response.json(); // Get the JSON response
setStars(json.stargazers_count); // Set the stars setStars(json.stargazers_count); // Set the stars
console.log("fetch stars");
}; };
if (stars === undefined) { if (stars === undefined) {
fetchStars(); // Fetch the stars fetchStars(); // Fetch the stars

@ -2,11 +2,11 @@ import { ReactElement } from "react";
import { cn } from "../../lib/utils"; import { cn } from "../../lib/utils";
/** /**
* The background component. * The background hero component.
* *
* @returns the background jsx * @returns the background jsx
*/ */
const Background = (): ReactElement => ( const HeroBackground = (): ReactElement => (
<div <div
className={cn( className={cn(
"before:absolute before:left-0 before:top-0 before:w-full before:h-full before:bg-black/65", // Dark Overlay "before:absolute before:left-0 before:top-0 before:w-full before:h-full before:bg-black/65", // Dark Overlay
@ -15,4 +15,4 @@ const Background = (): ReactElement => (
)} )}
/> />
); );
export default Background; export default HeroBackground;

@ -7,7 +7,7 @@ import { ReactElement } from "react";
* @returns the counters jsx * @returns the counters jsx
*/ */
const StatisticCounters = (): ReactElement => ( const StatisticCounters = (): ReactElement => (
<div className="py-56 flex justify-center items-center"> <div className="py-56 pb-80 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 pointer-events-none"> <div className="grid grid-flow-row grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-24 pointer-events-none">
<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} />

@ -1,7 +1,7 @@
import * as React from "react" import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
@ -21,7 +21,7 @@ const badgeVariants = cva(
variant: "default", variant: "default",
}, },
} }
) );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>,
@ -30,7 +30,7 @@ export interface BadgeProps
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <div className={cn(badgeVariants({ variant }), className)} {...props} />
) );
} }
export { Badge, badgeVariants } export { Badge, badgeVariants };

@ -1,15 +1,16 @@
import * as React from "react" import * as React from "react";
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default:
"bg-primary text-primary-foreground hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90", "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: outline:
@ -31,26 +32,26 @@ const buttonVariants = cva(
size: "default", size: "default",
}, },
} }
) );
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean asChild?: boolean;
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref} ref={ref}
{...props} {...props}
/> />
) );
} }
) );
Button.displayName = "Button" Button.displayName = "Button";
export { Button, buttonVariants } export { Button, buttonVariants };

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
const ContextMenu = ContextMenuPrimitive.Root const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef< const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>, React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger <ContextMenuPrimitive.SubTrigger
@ -36,8 +36,8 @@ const ContextMenuSubTrigger = React.forwardRef<
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger> </ContextMenuPrimitive.SubTrigger>
)) ));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef< const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>, React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
@ -51,8 +51,8 @@ const ContextMenuSubContent = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef< const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>, React.ElementRef<typeof ContextMenuPrimitive.Content>,
@ -68,13 +68,13 @@ const ContextMenuContent = React.forwardRef<
{...props} {...props}
/> />
</ContextMenuPrimitive.Portal> </ContextMenuPrimitive.Portal>
)) ));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef< const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>, React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item <ContextMenuPrimitive.Item
@ -86,8 +86,8 @@ const ContextMenuItem = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef< const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>, React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
@ -109,9 +109,9 @@ const ContextMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.CheckboxItem> </ContextMenuPrimitive.CheckboxItem>
)) ));
ContextMenuCheckboxItem.displayName = ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef< const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>, React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
@ -132,13 +132,13 @@ const ContextMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.RadioItem> </ContextMenuPrimitive.RadioItem>
)) ));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef< const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>, React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
@ -150,8 +150,8 @@ const ContextMenuLabel = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef< const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>, React.ElementRef<typeof ContextMenuPrimitive.Separator>,
@ -162,8 +162,8 @@ const ContextMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-border", className)} className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) ));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ const ContextMenuShortcut = ({
className, className,
@ -177,9 +177,9 @@ const ContextMenuShortcut = ({
)} )}
{...props} {...props}
/> />
) );
} };
ContextMenuShortcut.displayName = "ContextMenuShortcut" ContextMenuShortcut.displayName = "ContextMenuShortcut";
export { export {
ContextMenu, ContextMenu,
@ -197,4 +197,4 @@ export {
ContextMenuSubContent, ContextMenuSubContent,
ContextMenuSubTrigger, ContextMenuSubTrigger,
ContextMenuRadioGroup, ContextMenuRadioGroup,
} };

@ -1,16 +1,16 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select" import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown, ChevronUp } from "lucide-react" import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
const Select = SelectPrimitive.Root const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef< const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
@ -29,8 +29,8 @@ const SelectTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)) ));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef< const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
@ -46,8 +46,8 @@ const SelectScrollUpButton = React.forwardRef<
> >
<ChevronUp className="h-4 w-4" /> <ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
)) ));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef< const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
@ -63,9 +63,9 @@ const SelectScrollDownButton = React.forwardRef<
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)) ));
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
@ -96,8 +96,8 @@ const SelectContent = React.forwardRef<
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
)) ));
SelectContent.displayName = SelectPrimitive.Content.displayName SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef< const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>, React.ElementRef<typeof SelectPrimitive.Label>,
@ -108,8 +108,8 @@ const SelectLabel = React.forwardRef<
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)} className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props} {...props}
/> />
)) ));
SelectLabel.displayName = SelectPrimitive.Label.displayName SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef< const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>, React.ElementRef<typeof SelectPrimitive.Item>,
@ -131,8 +131,8 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
)) ));
SelectItem.displayName = SelectPrimitive.Item.displayName SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef< const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>, React.ElementRef<typeof SelectPrimitive.Separator>,
@ -143,8 +143,8 @@ const SelectSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export { export {
Select, Select,
@ -157,4 +157,4 @@ export {
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
} };

@ -1,4 +1,4 @@
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
function Skeleton({ function Skeleton({
className, className,
@ -9,7 +9,7 @@ function Skeleton({
className={cn("animate-pulse rounded-md bg-muted", className)} className={cn("animate-pulse rounded-md bg-muted", className)}
{...props} {...props}
/> />
) );
} }
export { Skeleton } export { Skeleton };

@ -1,12 +1,12 @@
"use client" "use client";
import { useTheme } from "next-themes" import { useTheme } from "next-themes";
import { Toaster as Sonner } from "sonner" import { Toaster as Sonner } from "sonner";
type ToasterProps = React.ComponentProps<typeof Sonner> type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = "system" } = useTheme();
return ( return (
<Sonner <Sonner
@ -14,8 +14,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
className="toaster group" className="toaster group"
toastOptions={{ toastOptions={{
classNames: { classNames: {
toast: toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground", description: "group-[.toast]:text-muted-foreground",
actionButton: actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
@ -25,7 +24,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}} }}
{...props} {...props}
/> />
) );
} };
export { Toaster } export { Toaster };

@ -1,13 +1,13 @@
"use client" "use client";
import * as ToastPrimitives from "@radix-ui/react-toast" import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react" import { X } from "lucide-react";
import * as React from "react" import * as React from "react";
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
const ToastProvider = ToastPrimitives.Provider const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef< const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>, React.ElementRef<typeof ToastPrimitives.Viewport>,
@ -21,8 +21,8 @@ const ToastViewport = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva( const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
@ -38,7 +38,7 @@ const toastVariants = cva(
variant: "default", variant: "default",
}, },
} }
) );
const Toast = React.forwardRef< const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>, React.ElementRef<typeof ToastPrimitives.Root>,
@ -51,9 +51,9 @@ const Toast = React.forwardRef<
className={cn(toastVariants({ variant }), className)} className={cn(toastVariants({ variant }), className)}
{...props} {...props}
/> />
) );
}) });
Toast.displayName = ToastPrimitives.Root.displayName Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef< const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>, React.ElementRef<typeof ToastPrimitives.Action>,
@ -67,8 +67,8 @@ const ToastAction = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ToastAction.displayName = ToastPrimitives.Action.displayName ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef< const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>, React.ElementRef<typeof ToastPrimitives.Close>,
@ -85,8 +85,8 @@ const ToastClose = React.forwardRef<
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</ToastPrimitives.Close> </ToastPrimitives.Close>
)) ));
ToastClose.displayName = ToastPrimitives.Close.displayName ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef< const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>, React.ElementRef<typeof ToastPrimitives.Title>,
@ -97,8 +97,8 @@ const ToastTitle = React.forwardRef<
className={cn("text-sm font-semibold", className)} className={cn("text-sm font-semibold", className)}
{...props} {...props}
/> />
)) ));
ToastTitle.displayName = ToastPrimitives.Title.displayName ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef< const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>, React.ElementRef<typeof ToastPrimitives.Description>,
@ -109,14 +109,21 @@ const ToastDescription = React.forwardRef<
className={cn("text-sm opacity-90", className)} className={cn("text-sm opacity-90", className)}
{...props} {...props}
/> />
)) ));
ToastDescription.displayName = ToastPrimitives.Description.displayName ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction> type ToastActionElement = React.ReactElement<typeof ToastAction>;
export { export {
Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, type ToastActionElement, type ToastProps Toast,
} ToastAction,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
type ToastActionElement,
type ToastProps,
};

@ -1,4 +1,4 @@
"use client" "use client";
import { import {
Toast, Toast,
@ -7,29 +7,37 @@ import {
ToastProvider, ToastProvider,
ToastTitle, ToastTitle,
ToastViewport, ToastViewport,
} from "@/app/components/ui/toast" } from "@/app/components/ui/toast";
import { useToast } from "@/app/components/ui/use-toast" import { useToast } from "@/app/components/ui/use-toast";
export function Toaster() { export function Toaster() {
const { toasts } = useToast() const { toasts } = useToast();
return ( return (
<ToastProvider> <ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) { {toasts.map(function ({
id,
title,
description,
action,
...props
}) {
return ( return (
<Toast key={id} {...props}> <Toast key={id} {...props}>
<div className="grid gap-1"> <div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>} {title && <ToastTitle>{title}</ToastTitle>}
{description && ( {description && (
<ToastDescription>{description}</ToastDescription> <ToastDescription>
{description}
</ToastDescription>
)} )}
</div> </div>
{action} {action}
<ToastClose /> <ToastClose />
</Toast> </Toast>
) );
})} })}
<ToastViewport /> <ToastViewport />
</ToastProvider> </ToastProvider>
) );
} }

@ -1,15 +1,15 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip" import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/app/lib/utils" import { cn } from "@/app/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef< const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>, React.ElementRef<typeof TooltipPrimitive.Content>,
@ -24,7 +24,7 @@ const TooltipContent = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
TooltipContent.displayName = TooltipPrimitive.Content.displayName TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

@ -1,78 +1,75 @@
"use client" "use client";
// Inspired by react-hot-toast library // Inspired by react-hot-toast library
import * as React from "react" import * as React from "react";
import type { import type { ToastActionElement, ToastProps } from "@/app/components/ui/toast";
ToastActionElement,
ToastProps,
} from "@/app/components/ui/toast"
const TOAST_LIMIT = 1 const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000 const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & { type ToasterToast = ToastProps & {
id: string id: string;
title?: React.ReactNode title?: React.ReactNode;
description?: React.ReactNode description?: React.ReactNode;
action?: ToastActionElement action?: ToastActionElement;
} };
const actionTypes = { const actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST", REMOVE_TOAST: "REMOVE_TOAST",
} as const } as const;
let count = 0 let count = 0;
function genId() { function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString() return count.toString();
} }
type ActionType = typeof actionTypes type ActionType = typeof actionTypes;
type Action = type Action =
| { | {
type: ActionType["ADD_TOAST"] type: ActionType["ADD_TOAST"];
toast: ToasterToast toast: ToasterToast;
} }
| { | {
type: ActionType["UPDATE_TOAST"] type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast> toast: Partial<ToasterToast>;
} }
| { | {
type: ActionType["DISMISS_TOAST"] type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} }
| { | {
type: ActionType["REMOVE_TOAST"] type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} };
interface State { interface State {
toasts: ToasterToast[] toasts: ToasterToast[];
} }
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => { const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) { if (toastTimeouts.has(toastId)) {
return return;
} }
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
toastTimeouts.delete(toastId) toastTimeouts.delete(toastId);
dispatch({ dispatch({
type: "REMOVE_TOAST", type: "REMOVE_TOAST",
toastId: toastId, toastId: toastId,
}) });
}, TOAST_REMOVE_DELAY) }, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout) toastTimeouts.set(toastId, timeout);
} };
export const reducer = (state: State, action: Action): State => { export const reducer = (state: State, action: Action): State => {
switch (action.type) { switch (action.type) {
@ -80,7 +77,7 @@ export const reducer = (state: State, action: Action): State => {
return { return {
...state, ...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
} };
case "UPDATE_TOAST": case "UPDATE_TOAST":
return { return {
@ -88,19 +85,19 @@ export const reducer = (state: State, action: Action): State => {
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t t.id === action.toast.id ? { ...t, ...action.toast } : t
), ),
} };
case "DISMISS_TOAST": { case "DISMISS_TOAST": {
const { toastId } = action const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action, // ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity // but I'll keep it here for simplicity
if (toastId) { if (toastId) {
addToRemoveQueue(toastId) addToRemoveQueue(toastId);
} else { } else {
state.toasts.forEach((toast) => { state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id) addToRemoveQueue(toast.id);
}) });
} }
return { return {
@ -113,44 +110,44 @@ export const reducer = (state: State, action: Action): State => {
} }
: t : t
), ),
} };
} }
case "REMOVE_TOAST": case "REMOVE_TOAST":
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
toasts: [], toasts: [],
} };
} }
return { return {
...state, ...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId), toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
} }
} };
}
const listeners: Array<(state: State) => void> = [] const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] } let memoryState: State = { toasts: [] };
function dispatch(action: Action) { function dispatch(action: Action) {
memoryState = reducer(memoryState, action) memoryState = reducer(memoryState, action);
listeners.forEach((listener) => { listeners.forEach((listener) => {
listener(memoryState) listener(memoryState);
}) });
} }
type Toast = Omit<ToasterToast, "id"> type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) { function toast({ ...props }: Toast) {
const id = genId() const id = genId();
const update = (props: ToasterToast) => const update = (props: ToasterToast) =>
dispatch({ dispatch({
type: "UPDATE_TOAST", type: "UPDATE_TOAST",
toast: { ...props, id }, toast: { ...props, id },
}) });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({ dispatch({
type: "ADD_TOAST", type: "ADD_TOAST",
@ -159,36 +156,37 @@ function toast({ ...props }: Toast) {
id, id,
open: true, open: true,
onOpenChange: (open) => { onOpenChange: (open) => {
if (!open) dismiss() if (!open) dismiss();
}, },
}, },
}) });
return { return {
id: id, id: id,
dismiss, dismiss,
update, update,
} };
} }
function useToast() { function useToast() {
const [state, setState] = React.useState<State>(memoryState) const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => { React.useEffect(() => {
listeners.push(setState) listeners.push(setState);
return () => { return () => {
const index = listeners.indexOf(setState) const index = listeners.indexOf(setState);
if (index > -1) { if (index > -1) {
listeners.splice(index, 1) listeners.splice(index, 1);
} }
} };
}, [state]) }, [state]);
return { return {
...state, ...state,
toast, toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), dismiss: (toastId?: string) =>
} dispatch({ type: "DISMISS_TOAST", toastId }),
};
} }
export { useToast, toast } export { useToast, toast };

@ -2,4 +2,5 @@
* The configuration for this app. * The configuration for this app.
*/ */
import config from "@/configJson"; import config from "@/configJson";
export default config; export default config;

@ -1,14 +1,15 @@
import Footer from "@/components/footer";
import Navbar from "@/components/navbar"; import Navbar from "@/components/navbar";
import { Toaster } from "@/components/ui/sonner" import { Toaster } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import config from "@/config"; import config from "@/config";
import { notoSans } from "@/font/fonts"; import { notoSans } from "@/font/fonts";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import ThemeProvider from "@/provider/theme-provider";
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import PlausibleProvider from "next-plausible"; import PlausibleProvider from "next-plausible";
import { ReactElement, ReactNode } from "react"; import { ReactElement, ReactNode } from "react";
import "./globals.css"; import "./globals.css";
import ThemeProvider from "@/provider/theme-provider";
/** /**
* Site metadata & viewport. * Site metadata & viewport.
@ -44,7 +45,7 @@ const RootLayout = ({
<TooltipProvider> <TooltipProvider>
<Navbar /> <Navbar />
{children} {children}
{/*<Footer />*/} <Footer />
</TooltipProvider> </TooltipProvider>
{/* Toasts */} {/* Toasts */}

@ -1,4 +1,4 @@
import { clsx, type ClassValue } from "clsx"; import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]): string { export function cn(...inputs: ClassValue[]): string {

@ -9,7 +9,7 @@ import { ReactElement } from "react";
* @returns the page jsx * @returns the page jsx
*/ */
const NotFoundPage = (): ReactElement => ( const NotFoundPage = (): ReactElement => (
<main className="h-[84vh] flex flex-col gap-3 justify-center items-center text-center pointer-events-none"> <main className="h-screen flex flex-col gap-3 justify-center items-center text-center pointer-events-none">
{/* Creeper */} {/* Creeper */}
<Creeper /> <Creeper />

@ -41,16 +41,40 @@ interface Config {
* link, and the value is the URL. * link, and the value is the URL.
* </p> * </p>
*/ */
navbarLinks: { navbarLinks: NavbarLink;
[name: string]: string;
};
/** /**
* Featured items for the landing page. * Featured items for the landing page.
*/ */
featuredItems: FeaturedItemProps[]; featuredItems: FeaturedItemProps[];
/**
* Links to display on the footer.
*/
footerLinks: FooterLink;
} }
/**
* The links for the navbar.
*/
type NavbarLinks = {
[name: string]: string;
};
/**
* Links for the footer.
*/
type FooterLinks = {
[header: string]: FooterLink;
};
/**
* A link on the footer.
*/
type FooterLink = {
[name: string]: string;
};
/** /**
* Props for a featured item * Props for a featured item
* on the landing page. * on the landing page.

@ -1,4 +1,5 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
const { screens } = require("tailwindcss/defaultTheme"); const { screens } = require("tailwindcss/defaultTheme");
const config = { const config = {