diff --git a/apps/www/package.json b/apps/www/package.json index 2fa2438..791543e 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -32,9 +32,11 @@ "@radix-ui/react-menubar": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-select": "2.1.2", - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "1.1.0", "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", "@tanstack/react-query": "^5.69.0", "@trpc/client": "^11.0.0", "@trpc/next": "^11.0.0", @@ -51,6 +53,7 @@ "@vercel/functions": "^2.0.0", "@vercel/og": "^0.6.5", "ag-grid-react": "^33.0.3", + "class-variance-authority": "^0.7.1", "contentlayer": "^0.3.4", "cron": "^3.1.7", "discord.js": "^14.15.3", @@ -59,13 +62,14 @@ "input-otp": "^1.2.4", "json-beautify": "^1.1.1", "lodash": "^4.17.21", - "lucide-react": "^0.479.0", + "lucide-react": "^0.487.0", "lz-string": "^1.5.0", "mini-svg-data-uri": "^1.4.4", "minimessage-2-html": "1.6.0", "minimessage-js": "^1.1.3", "monaco-editor": "^0.52.2", "mongodb": "^6.8.0", + "motion": "^12.7.4", "next": "15.2.0", "next-contentlayer": "^0.3.4", "next-css-obfuscator": "^2.2.16", diff --git a/apps/www/public/branding/section-1/filter-demo-dark.png b/apps/www/public/branding/section-1/filter-demo-dark.png new file mode 100644 index 0000000..4cb6046 Binary files /dev/null and b/apps/www/public/branding/section-1/filter-demo-dark.png differ diff --git a/apps/www/public/branding/section-1/filter-demo-light.png b/apps/www/public/branding/section-1/filter-demo-light.png new file mode 100644 index 0000000..ef8aebe Binary files /dev/null and b/apps/www/public/branding/section-1/filter-demo-light.png differ diff --git a/apps/www/public/branding/section-2/alert-demo-dark.png b/apps/www/public/branding/section-2/alert-demo-dark.png new file mode 100644 index 0000000..18da126 Binary files /dev/null and b/apps/www/public/branding/section-2/alert-demo-dark.png differ diff --git a/apps/www/public/branding/section-2/alert-demo-light.png b/apps/www/public/branding/section-2/alert-demo-light.png new file mode 100644 index 0000000..8bc390e Binary files /dev/null and b/apps/www/public/branding/section-2/alert-demo-light.png differ diff --git a/apps/www/public/branding/section-2/interactive-demo-dark.png b/apps/www/public/branding/section-2/interactive-demo-dark.png new file mode 100644 index 0000000..9b46658 Binary files /dev/null and b/apps/www/public/branding/section-2/interactive-demo-dark.png differ diff --git a/apps/www/public/branding/section-2/interactive-demo-light.png b/apps/www/public/branding/section-2/interactive-demo-light.png new file mode 100644 index 0000000..a859b2f Binary files /dev/null and b/apps/www/public/branding/section-2/interactive-demo-light.png differ diff --git a/apps/www/src/app/(dashboard)/dashboard/layout.tsx b/apps/www/src/app/(dashboard)/dashboard/layout.tsx new file mode 100644 index 0000000..f429730 --- /dev/null +++ b/apps/www/src/app/(dashboard)/dashboard/layout.tsx @@ -0,0 +1,148 @@ +/* + * MHSF, Minehut Server List + * All external content is rather licensed under the ECA Agreement + * located here: https://mhsf.app/docs/legal/external-content-agreement + * + * All code under MHSF is licensed under the MIT License + * by open source contributors + * + * Copyright (c) 2025 dvelo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +"use client"; +import "../../globals.css"; +import { useSearchParams } from "next/navigation"; +import { Placeholder } from "@/components/ui/placeholder"; +import { Command, X } from "lucide-react"; +import { IsScript } from "@/components/util/is-script"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import { NavBar } from "@/components/feat/navbar/navbar"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { ThemeProvider } from "@/components/util/theme-provider"; +import { FontBoundary } from "@/components/util/font-boundary"; +import { ClerkProvider } from "@/components/util/clerk-provider"; +import { Toaster } from "sonner"; +import { Footer } from "@/components/feat/footer/footer"; +import { NuqsAdapter } from "nuqs/adapters/next/app"; +import { IframeProtector } from "@/components/util/iframe-protector"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarHeader, + SidebarInset, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarProvider, +} from "@/components/ui/sidebar"; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + const searchParams = useSearchParams(); + const search = searchParams?.get("theme") || "light"; + + return ( + + + + + + + + + + + + + + + +
+ +
+
+ + Acme Inc + + + Enterprise + +
+
+
+
+
+
+ + + + + + + + a + + + + + + + +
+ + +
{children}
+
+
+
+
+
+
+
+
+ + ); +} diff --git a/apps/www/src/lib/mhsf.ts b/apps/www/src/app/(dashboard)/dashboard/page.tsx similarity index 79% rename from apps/www/src/lib/mhsf.ts rename to apps/www/src/app/(dashboard)/dashboard/page.tsx index 4d8d6bd..c5b5a77 100644 --- a/apps/www/src/lib/mhsf.ts +++ b/apps/www/src/app/(dashboard)/dashboard/page.tsx @@ -28,20 +28,6 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -export class MHSF { - private favorites: number = 0; - private customization: any = {}; - - getMHSF() { - return {favorites: this.favorites, customization: this.customization} - } - - setFavorites(num: number) { - this.favorites = num; - - } - - setCustomizations(num: object) { - this.customization = num; - } +export default function Dashboard() { + return <>Hello world } \ No newline at end of file diff --git a/apps/www/src/app/(main)/home/actions.tsx b/apps/www/src/app/(main)/home/actions.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/www/src/app/(sl-modification-frame)/layout.tsx b/apps/www/src/app/(sl-modification-frame)/layout.tsx index 4efce4a..9bda8f8 100644 --- a/apps/www/src/app/(sl-modification-frame)/layout.tsx +++ b/apps/www/src/app/(sl-modification-frame)/layout.tsx @@ -45,6 +45,7 @@ import { Toaster } from "sonner"; import { Footer } from "@/components/feat/footer/footer"; import { NuqsAdapter } from "nuqs/adapters/next/app"; import { IframeProtector } from "@/components/util/iframe-protector"; +import NextTopLoader from "@/components/util/top-loader"; export default function RootLayout({ children, @@ -78,10 +79,12 @@ export default function RootLayout({ - + + +
{children}
diff --git a/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/modification/[mod]/page.tsx b/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/modification/[mod]/page.tsx index 22a09c7..9815bc8 100644 --- a/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/modification/[mod]/page.tsx +++ b/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/modification/[mod]/page.tsx @@ -59,7 +59,7 @@ export default function ModificationPage({ ); return ( -
+
+
- @@ -146,6 +164,7 @@ export default function ModificationPage({ }, }); toast.success(`Deleted in ${Date.now() - time}ms`); + router.push(backRoute); }} > Delete @@ -185,9 +204,7 @@ export default function ModificationPage({ File name diff --git a/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/page.tsx b/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/page.tsx index d7f0bc9..aad2733 100644 --- a/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/page.tsx +++ b/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/page.tsx @@ -51,9 +51,9 @@ export default async function ServerListCategoryFrame({ const categoryObj = serverModDB.find( (c) => c.displayTitle === atob(decodeURIComponent(category)), ); - +`` return ( -
+

@@ -62,7 +62,7 @@ export default async function ServerListCategoryFrame({

{categoryObj?.description} - + {categoryObj?.entries.map((m) => ( setSyntaxErrors(await validateCode(monacoRef, filename)))(); // biome-ignore lint: L useEffect(() => { setSuccessfullyLinted(false); debouncedSave(); + (async () => setSyntaxErrors(await validateCode(monacoRef, filename)))(); }, [value]); return ( -
+
@@ -167,9 +199,7 @@ export default function CustomFilePage({ {syntaxErrors !== null && syntaxErrors.length !== 0 && ( )} @@ -192,6 +222,7 @@ export default function CustomFilePage({ diff --git a/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/files/page.tsx b/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/files/page.tsx index 2918326..13cdf9b 100644 --- a/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/files/page.tsx +++ b/apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/files/page.tsx @@ -55,6 +55,8 @@ import { import { use, useEffect, useState } from "react"; import { toast } from "sonner"; import { findSupportedOperations } from "../file/[filename]/page"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; export default function ServerListModificationFrame() { const { user } = useUser(); @@ -62,9 +64,8 @@ export default function ServerListModificationFrame() { (user?.unsafeMetadata.customFiles as Array) ?? []; const operations = usePlatforms(files); - return ( -
+

@@ -81,20 +82,23 @@ export default function ServerListModificationFrame() { )} {files.map((c, i) => ( - {operations[i].filter && } - {operations[i].sort && } + {operations.length !== 0 && ( + <> + {operations[i].filter && } + {operations[i].sort && } + + )} {c.name}.ts - @@ -60,55 +60,35 @@ export default function ServerListModificationFrame() { features, as well. - {serverModDB.map((c) => ( -
-

- {c.displayTitle} - - - View more - -

-
- {c.entries.map((m) => ( - - router.push( - `/servers/embedded/sl-modification-frame/category/${btoa(c.displayTitle)}/modification/${btoa(m.name)}`, - ) - } - > -
+ (!c.__custom || + (c.__custom && + ( + (user?.unsafeMetadata + .activatedModifications as ClerkCustomActivatedModification[]) ?? + [] + ).length !== 0)) && ( +
+

+ {c.displayTitle} + - -

- - {m.name} - - - ))} - - {c.__custom && - ( - (user?.unsafeMetadata - .activatedModifications as ClerkCustomActivatedModification[]) ?? - [] - ).map((m) => ( + + View more + +

+
+ {c.entries.map((m) => ( router.push( - `/servers/embedded/sl-modification-frame/category/${btoa(c.displayTitle)}/modification/custom/${btoa(m.friendlyName)}`, + `/servers/embedded/sl-modification-frame/category/${btoa(c.displayTitle)}/modification/${btoa(m.name)}`, ) } > @@ -116,17 +96,46 @@ export default function ServerListModificationFrame() { className="w-full h-[40px] mb-2 rounded-lg items-center text-center justify-center" style={{ backgroundColor: m.color }} > - +
- {m.friendlyName} + {m.name} ))} - -
-
- ))} + + {c.__custom && + ( + (user?.unsafeMetadata + .activatedModifications as ClerkCustomActivatedModification[]) ?? + [] + ).map((m) => ( + + router.push( + `/servers/embedded/sl-modification-frame/category/${btoa(c.displayTitle)}/modification/custom/${btoa(m.friendlyName)}`, + ) + } + > +
+ +
+ + {m.friendlyName} + +
+ ))} +
+
+
+ ), + )}
); diff --git a/apps/www/src/app/api/og/server/[id]/players/route.tsx b/apps/www/src/app/api/og/server/[id]/players/route.tsx index 80df2fc..b23a5a7 100644 --- a/apps/www/src/app/api/og/server/[id]/players/route.tsx +++ b/apps/www/src/app/api/og/server/[id]/players/route.tsx @@ -100,7 +100,7 @@ export async function GET( // Connect to MongoDB const mongo = new MongoClient(process.env.MONGO_DB as string); await mongo.connect(); - const db = mongo.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); + const db = mongo.db("mhsf"); // Get player data (last 60 entries) const historyCollection = db.collection("history"); diff --git a/apps/www/src/app/globals.css b/apps/www/src/app/globals.css index 45d3b87..5f3d351 100644 --- a/apps/www/src/app/globals.css +++ b/apps/www/src/app/globals.css @@ -77,6 +77,8 @@ /* Workaround for Tailwind being stupid */ border-color: hsl(214.3 31.8% 91.4%); } + + --sidebar: hsl(0 0% 98%) } .dark { --border: 216 34% 17%; @@ -105,12 +107,6 @@ --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; - *, - ::before, - ::after { - @apply border-zinc-800; - } - --sidebar-background: 240 5.9% 10%; --sidebar-foreground: 240 4.8% 95.9%; @@ -126,6 +122,14 @@ --sidebar-border: 240 3.7% 15.9%; --sidebar-ring: 217.2 91.2% 59.8%; + + --sidebar: hsl(240 5.9% 10%); + + *, + ::before, + ::after { + @apply border-zinc-800; + } } @theme { @@ -9327,3 +9331,40 @@ body { @apply bg-background text-foreground; } } + +@keyframes aurora { + 0% { + background-position: 0% 50%; + transform: rotate(-5deg) scale(0.9); + } + 25% { + background-position: 50% 100%; + transform: rotate(5deg) scale(1.1); + } + 50% { + background-position: 100% 50%; + transform: rotate(-3deg) scale(0.95); + } + 75% { + background-position: 50% 0%; + transform: rotate(3deg) scale(1.05); + } + 100% { + background-position: 0% 50%; + transform: rotate(-5deg) scale(0.9); + } +} +/* + ---break--- + */ +@theme inline { + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --animate-aurora: aurora 8s ease-in-out infinite alternate; +} \ No newline at end of file diff --git a/apps/www/src/components/feat/home-page/animated-list.tsx b/apps/www/src/components/feat/home-page/animated-list.tsx new file mode 100644 index 0000000..0befeff --- /dev/null +++ b/apps/www/src/components/feat/home-page/animated-list.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { AnimatePresence, motion } from "motion/react"; +import React, { + type ComponentPropsWithoutRef, + useEffect, + useMemo, + useState, +} from "react"; + +export function AnimatedListItem({ children }: { children: React.ReactNode }) { + const animations = { + initial: { scale: 0, opacity: 0 }, + animate: { scale: 1, opacity: 1, originY: 0 }, + exit: { scale: 0, opacity: 0 }, + transition: { type: "spring", stiffness: 350, damping: 40 }, + }; + + return ( + + {children} + + ); +} + +export interface AnimatedListProps extends ComponentPropsWithoutRef<"div"> { + children: React.ReactNode; + delay?: number; +} + +export const AnimatedList = React.memo( + ({ children, className, delay = 1000, ...props }: AnimatedListProps) => { + const [index, setIndex] = useState(0); + const childrenArray = useMemo( + () => React.Children.toArray(children), + [children], + ); + + useEffect(() => { + if (index < childrenArray.length - 1) { + const timeout = setTimeout(() => { + setIndex((prevIndex) => (prevIndex + 1) % childrenArray.length); + }, delay); + + return () => clearTimeout(timeout); + } + }, [index, delay, childrenArray.length]); + + const itemsToShow = useMemo(() => { + const result = childrenArray.slice(0, index + 1).reverse(); + return result; + }, [index, childrenArray]); + + return ( +
+ + {itemsToShow.map((item) => ( + + {item} + + ))} + +
+ ); + }, +); + +AnimatedList.displayName = "AnimatedList"; diff --git a/apps/www/src/components/feat/home-page/aurora-text.tsx b/apps/www/src/components/feat/home-page/aurora-text.tsx new file mode 100644 index 0000000..4b37963 --- /dev/null +++ b/apps/www/src/components/feat/home-page/aurora-text.tsx @@ -0,0 +1,43 @@ +"use client"; + +import React, { memo } from "react"; + +interface AuroraTextProps { + children: React.ReactNode; + className?: string; + colors?: string[]; + speed?: number; +} + +export const AuroraText = memo( + ({ + children, + className = "", + colors = ["#FF0080", "#7928CA", "#0070F3", "#38bdf8"], + speed = 1, + }: AuroraTextProps) => { + const gradientStyle = { + backgroundImage: `linear-gradient(135deg, ${colors.join(", ")}, ${ + colors[0] + })`, + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + animationDuration: `${10 / speed}s`, + }; + + return ( + + {children} + + + ); + }, +); + +AuroraText.displayName = "AuroraText"; diff --git a/apps/www/src/components/feat/home-page/avatar-circles.tsx b/apps/www/src/components/feat/home-page/avatar-circles.tsx new file mode 100644 index 0000000..f33078d --- /dev/null +++ b/apps/www/src/components/feat/home-page/avatar-circles.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { cn } from "@/lib/utils"; + +export interface Avatar { + imageUrl: string; + profileUrl: string; +} +interface AvatarCirclesProps { + className?: string; + numPeople?: number; + avatarUrls: Avatar[]; +} + +export const AvatarCircles = ({ + numPeople, + className, + avatarUrls, +}: AvatarCirclesProps) => { + return ( +
+ {avatarUrls.map((url, index) => ( + + {`Avatar + + ))} + {(numPeople ?? 0) > 0 && ( + + +{numPeople} + + )} +
+ ); +}; diff --git a/apps/www/src/components/feat/home-page/example-chart.tsx b/apps/www/src/components/feat/home-page/example-chart.tsx new file mode 100644 index 0000000..942bb62 --- /dev/null +++ b/apps/www/src/components/feat/home-page/example-chart.tsx @@ -0,0 +1,172 @@ +/* + * MHSF, Minehut Server List + * All external content is rather licensed under the ECA Agreement + * located here: https://mhsf.app/docs/legal/external-content-agreement + * + * All code under MHSF is licensed under the MIT License + * by open source contributors + * + * Copyright (c) 2025 dvelo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */"use client" + +import * as React from "react" +import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" + +import { + ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +const chartData = [ + { date: "2024-04-01", player_count: 91 }, + { date: "2024-04-02", player_count: 106 }, + { date: "2024-04-03", player_count: 104 }, + { date: "2024-04-04", player_count: 111 }, + { date: "2024-04-05", player_count: 113 }, + { date: "2024-04-06", player_count: 114 }, + { date: "2024-04-07", player_count: 108 }, + { date: "2024-04-08", player_count: 89 }, + { date: "2024-04-09", player_count: 96 }, + { date: "2024-04-10", player_count: 123 }, + { date: "2024-04-11", player_count: 120 }, + { date: "2024-04-12", player_count: 140 }, + { date: "2024-04-13", player_count: 128 }, + { date: "2024-04-14", player_count: 130 }, + { date: "2024-04-15", player_count: 114 }, + { date: "2024-04-16", player_count: 98 }, + { date: "2024-04-17", player_count: 102 }, + { date: "2024-04-18", player_count: 103 }, + { date: "2024-04-19", player_count: 102 }, + { date: "2024-04-20", player_count: 112 }, + { date: "2024-04-21", player_count: 117 }, + { date: "2024-04-22", player_count: 119 }, + { date: "2024-04-23", player_count: 129 }, + { date: "2024-04-24", player_count: 121 }, + { date: "2024-04-25", player_count: 126 }, + { date: "2024-04-26", player_count: 98 }, + { date: "2024-04-27", player_count: 102 }, + { date: "2024-04-28", player_count: 100 }, + { date: "2024-04-29", player_count: 101 }, + { date: "2024-04-30", player_count: 104 }, + { date: "2024-05-01", player_count: 109 }, + { date: "2024-05-02", player_count: 86 }, + { date: "2024-05-03", player_count: 93 }, + { date: "2024-05-04", player_count: 108 }, + { date: "2024-05-05", player_count: 112 }, + { date: "2024-05-06", player_count: 111 }, + { date: "2024-05-07", player_count: 96 }, + { date: "2024-05-08", player_count: 100 }, + { date: "2024-05-09", player_count: 124 }, + { date: "2024-05-10", player_count: 134 }, + { date: "2024-05-11", player_count: 144 }, + { date: "2024-05-12", player_count: 156 }, + { date: "2024-05-13", player_count: 180 }, + { date: "2024-05-14", player_count: 167 }, + { date: "2024-05-15", player_count: 154 }, + { date: "2024-05-16", player_count: 124 }, + { date: "2024-05-17", player_count: 112 }, + { date: "2024-05-18", player_count: 114 }, + { date: "2024-05-19", player_count: 121 }, + { date: "2024-05-20", player_count: 96 }, + { date: "2024-05-21", player_count: 102 }, + { date: "2024-05-22", player_count: 131 }, +] + +const chartConfig = { + player_count: { + label: "Players", + color: "hsl(var(--chart-1))", + } +} satisfies ChartConfig + +export function ExampleChart() { + return ( + + + + + + + + + + { + const date = new Date(value) + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }) + }} + /> + { + return new Date(value).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }) + }} + indicator="dot" + /> + } + /> + + } /> + + + ) +} \ No newline at end of file diff --git a/apps/www/src/components/feat/home-page/home-page.tsx b/apps/www/src/components/feat/home-page/home-page.tsx index eaf56c6..be5d9b7 100644 --- a/apps/www/src/components/feat/home-page/home-page.tsx +++ b/apps/www/src/components/feat/home-page/home-page.tsx @@ -33,107 +33,319 @@ import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { useClerk, useUser } from "@clerk/nextjs"; -import { ArrowDown, GalleryVertical } from "lucide-react"; +import { ArrowDown, GalleryVertical, Star } from "lucide-react"; import { useTheme } from "@/lib/hooks/use-theme"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { use, useEffect, useState } from "react"; import { Gradient } from "stripe-gradient"; +import { Material } from "@/components/ui/material"; +import { Badge } from "@/components/ui/badge"; +import { AuroraText } from "./aurora-text"; +import { AnimatedList } from "./animated-list"; +import { cn } from "@/lib/utils"; +import { ExampleChart } from "./example-chart"; +import { Link } from "@/components/util/link"; +import {type Avatar, AvatarCircles } from "./avatar-circles"; + +const getGitHubDetails = async () => { + const githubRepo = await (await fetch("https://api.github.com/repos/DeveloLongScript/mhsf")).json() + const githubStargazers = await (await fetch("https://api.github.com/repos/DeveloLongScript/mhsf/stargazers")).json() + + return { + stars: githubRepo.stargazers_count as number, + stargazers: (githubStargazers as Array<{avatar_url: string, html_url: string}>).map((c) => {return {imageUrl: c.avatar_url, profileUrl: c.html_url}}) + } +} export default function HomePageComponent() { - const clerk = useClerk(); - const router = useRouter(); - const { isSignedIn } = useUser(); - const { resolvedTheme } = useTheme(); - const [gradientId, setGradientId] = useState("gradient-banner"); + const clerk = useClerk(); + const router = useRouter(); + const { isSignedIn } = useUser(); + const theme = useTheme(); + const { resolvedTheme } = useTheme(); + const [stars, setStars] = useState(0); + const [stargazers, setStargazers] = useState([]); + const [gradientId, setGradientId] = useState("gradient-banner"); + + useEffect(() => { + setGradientId("gradient-banner"); + const gradient = new Gradient(); + gradient.initGradient(`#${gradientId}`); + }, [gradientId]); useEffect(() => { - setGradientId("gradient-banner"); - const gradient = new Gradient(); - gradient.initGradient("#" + gradientId); - }, [gradientId]); + getGitHubDetails().then((c) => { + setStars(c.stars); + setStargazers(c.stargazers); + }) + }, []) - return ( -
- -
-
+ return ( +
+ +
+
-

- Meet MHSF,
- the modern server list -

-

- MHSF is the next generation Minehut server list wrapper, with
- interactive filters, customizable web-pages, a modern interface and{" "} -
- everything in-between. -

+

+ Meet MHSF,
+ the modern server list +

+

+ MHSF is the next generation Minehut server list wrapper, with
+ interactive filters, customizable web-pages, a modern interface and{" "} +
everything in-between. +

- - - - -
-
- See more - - - -
-
-
-
- - For players - -
- - -

- Find what you want now.
- Not later. -

-

- MHSF is built for finding servers, and only that, along with
- allowing for maximum customizability with
- both your experience and the webpages you interact with.
-

-
- -
-
- ); + + + + +
+
+ +
+
+
+
+
+
+

+ An open-source unofficial project brought to you by dvelo +

+
+
+ + For server hunters + +
+
+
+ + +

+ Find what you want now.
+ Not later. +

+

+ MHSF is built for finding servers, and only that, along with{" "} +
+ allowing for maximum customizability with
+ both your experience and the webpages you interact with.{" "} +
+

+
+ + {" "} + Filter Demo{" "} + +
+
+
+
+ +

+ Build your dream server list +

+

+ Server lists are massive. Using custom filters and sorting + systems
+ allow you to shrink the amount of information you see in the way{" "} +
+ you want it. +

+
+
+
+
+
+ + + {Array.from({ length: 100 }, () => [ + { name: "Cannot find name 'flse'.", code: "2304" }, + { + name: "Type 'string' is not assignable to type 'boolean'.", + code: "2322", + }, + { + name: "'mhsf' has no exported member named 'Mincehut'. Did you mean 'Minehut'?", + code: "2724", + }, + { name: "Cannot find namespace 'React'.", code: "2503" }, + { + name: "'server' is declared but its value is never read.", + code: "6133", + }, + { + name: "This comparison appears to be unintentional because the types 'string' and 'boolean' have no overlap", + code: "2367", + }, + ]) + .flat() + .reverse() + .map((c) => ( + + ))} + + + + + Type-safety across the board + +

+ Completely safe TypeScript code is easily achieveable when + using MHSF custom modification with fully functioning + TypeScript error detection. +

+
+
+ + Alert Demo + + + + Lint your code instantly + +

+ Worried your code is broken? Run a simulation of your + modification or lint it very quickly. +

+
+
+ + Interactive Demo + + + + Interactively edit your code + +

+ MHSF uses the Monaco Editor as the editor of choice for all + custom modifications; the same editor that powers the Visual + Studio Code editor. +

+
+
+
+
+
+
+ + For data hunters + +
+
+ +

+ Your data? No problem. +

+

+ Data for servers are openly accessible behind no paywall or{" "} +
+ verification for thousands of servers over millions of total{" "} +
+ entries. +

+ +
+
+
+
+ +

+ Don't trust us? We're open-source. +

+

+ MHSF's entire codebase from microservice to frontend is
+ completely open-source under the MIT License. +

+ + + + + + + + + +
+
+
+
+
+ ); +} + +function TypeScriptError({ name, code }: { name: string; code: string }) { + return ( +
+
+
+

+ {name} + ยท + ts({code}) +

+
+
+
+ ); } diff --git a/apps/www/src/components/feat/server-list/modification/custom-files/custom-errors.tsx b/apps/www/src/components/feat/server-list/modification/custom-files/custom-errors.tsx index 37f5e8f..51bbbcb 100644 --- a/apps/www/src/components/feat/server-list/modification/custom-files/custom-errors.tsx +++ b/apps/www/src/components/feat/server-list/modification/custom-files/custom-errors.tsx @@ -51,18 +51,8 @@ import type { MonacoRefType } from "@/app/(sl-modification-frame)/servers/embedd export type SyntaxErrorInterface = languages.typescript.Diagnostic[] | null; -export function CustomErrors({ - value, - monacoRef, - filename -}: { - value: string; - monacoRef: RefObject; - filename: string; -}) { - const [syntaxErrors, setSyntaxErrors] = useState(); - - const validateCode = () => { +export const validateCode = (monacoRef: RefObject, filename: string) => { + return new Promise((re, rj) => { if (!monacoRef.current) return; monacoRef.current.languages.typescript @@ -78,16 +68,20 @@ export function CustomErrors({ ).toString(), ) .then((diags) => { - setSyntaxErrors(diags); + re(diags); }); }); }); - }; + }) + +}; - validateCode(); +export function CustomErrors({ + syntaxErrors +}: { + syntaxErrors: SyntaxErrorInterface; +}) { - // biome-ignore lint: L - useEffect(validateCode, [value]); if (syntaxErrors !== null && syntaxErrors !== undefined) return ( @@ -149,5 +143,5 @@ export function CustomErrors({ ); - return null; + return <>; } diff --git a/apps/www/src/components/feat/server-list/modification/custom-files/custom-test.tsx b/apps/www/src/components/feat/server-list/modification/custom-files/custom-test.tsx index b008c3b..4e66d2d 100644 --- a/apps/www/src/components/feat/server-list/modification/custom-files/custom-test.tsx +++ b/apps/www/src/components/feat/server-list/modification/custom-files/custom-test.tsx @@ -40,12 +40,11 @@ import { useEffect, useState } from "react"; import { toast } from "sonner"; import { CustomTestSuccess } from "./custom-test-success"; -export function CustomTest({value, successfullyLinted}: {value: string, successfullyLinted: boolean}) { +export function CustomTest({value, successfullyLinted, filename}: {value: string, successfullyLinted: boolean; filename: string}) { const [open, setOpen] = useState(false); const [filterEnabled, setFilterEnabled] = useState(true); const [sortEnabled, setSortEnabled] = useState(true); const [success, setSuccess] = useState(false); - const [fileName, setFileName] = useState(""); const [testMode, setTestMode] = useState<"filter" | "sort" | "">(""); // biome-ignore lint: values needed (but not shown by linter) @@ -173,7 +172,7 @@ export function CustomTest({value, successfullyLinted}: {value: string, successf {success && }Test {success && ( - + )} diff --git a/apps/www/src/components/feat/server-list/modification/modification-button.tsx b/apps/www/src/components/feat/server-list/modification/modification-button.tsx index fbd024f..2f874c1 100644 --- a/apps/www/src/components/feat/server-list/modification/modification-button.tsx +++ b/apps/www/src/components/feat/server-list/modification/modification-button.tsx @@ -39,7 +39,7 @@ export function ModificationButton({disabled}: {disabled?: boolean}) { - + diff --git a/apps/www/src/components/feat/server-list/modification/modification-frame.tsx b/apps/www/src/components/feat/server-list/modification/modification-frame.tsx index 7b11fd7..d1d6640 100644 --- a/apps/www/src/components/feat/server-list/modification/modification-frame.tsx +++ b/apps/www/src/components/feat/server-list/modification/modification-frame.tsx @@ -43,5 +43,5 @@ export function ModificationFrame() { }) }, [ref]) - return