From ff1b01329b258580d07103ca7e902bc95ce00d28 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Wed, 10 Dec 2025 02:16:11 +0100 Subject: [PATCH 01/33] Redesign layout; add build explorer and project pages; refactor cookies modal, privacy policy and notification generator --- app/api/builds/builds.ts | 91 ++++ app/blog/page.tsx | 124 ++--- app/builds/page.tsx | 352 ++++++++++++++ app/docs/[...slug]/page.tsx | 2 +- app/docs/layout.tsx | 13 +- app/layout.tsx | 2 - app/not-found.tsx | 51 +- app/notification-generator/layout.tsx | 17 +- app/notification-generator/page.tsx | 151 +++--- app/projects/eternalcombat/ConfigPreview.tsx | 297 ++++++++++++ app/projects/eternalcombat/page.tsx | 332 +++++++++++++ app/projects/eternalcore/ConfigPreview.tsx | 448 ++++++++++++++++++ app/projects/eternalcore/page.tsx | 268 +++++++++++ app/projects/page.tsx | 26 - app/team/page.tsx | 24 +- bun.lock | 3 + components/CookieConsentModal.tsx | 355 ++++++++------ components/CookiePreferencesMenu.tsx | 156 ------ components/SpeedInsights.tsx | 2 +- components/blog/BlogPostContent.tsx | 2 +- components/docs/content/EditOnGitHub.tsx | 28 +- components/footer/Footer.tsx | 270 ++++++----- components/hero/AnnouncementBanner.tsx | 42 +- components/hero/Hero.tsx | 117 ++--- components/hero/Navbar.tsx | 207 ++++++-- components/hero/ProjectsDropdown.tsx | 27 ++ components/hero/ToolsDropdown.tsx | 103 +--- components/hero/terminal/Terminal.tsx | 76 ++- components/home/AnimatedHome.tsx | 35 +- components/home/about/About.tsx | 153 ++++-- components/home/about/PolandMap.tsx | 26 + components/home/cta/Cta.tsx | 40 ++ components/home/faq/AnimatedChevron.tsx | 32 -- components/home/faq/Faq.tsx | 106 ++--- components/home/faq/animations.ts | 57 --- components/home/features/Features.tsx | 74 ++- components/home/sponsors/Sponsors.tsx | 111 +++++ .../FloatingPreview.tsx | 148 ++++++ .../NotificationGeneratedCode.tsx | 89 ++-- .../NotificationGenerator.tsx | 67 +-- .../notification-generator/form/FormField.tsx | 30 +- .../form/SliderField.tsx | 2 +- .../preview/MinecraftPreview.tsx | 2 +- .../notification-generator/tabs/Tab.tsx | 26 +- .../tabs/advanced/AdvancedTab.tsx | 31 +- components/projects/ProjectButton.tsx | 16 - components/projects/ProjectItem.tsx | 57 --- components/projects/Projects.tsx | 54 --- components/projects/ProjectsError.tsx | 17 - components/projects/ProjectsSkeleton.tsx | 32 -- components/projects/index.ts | 3 - components/projects/projectService.ts | 23 - components/projects/types.ts | 20 - components/team/Team.tsx | 111 ++++- components/team/TeamError.tsx | 28 +- components/team/TeamHero.tsx | 32 ++ components/team/TeamMember.tsx | 118 ++--- components/team/TeamSkeleton.tsx | 61 ++- components/team/animations.ts | 28 -- components/team/index.ts | 7 - components/team/types.ts | 2 +- components/ui/accordion.tsx | 142 ++++++ components/ui/button.tsx | 214 ++++----- components/ui/dropdown.tsx | 92 ++-- components/{ => ui}/mdx/Callout.tsx | 0 components/{ => ui}/mdx/Card.tsx | 0 components/{ => ui}/mdx/CodeBlock.tsx | 0 components/{ => ui}/mdx/CodeTabs.tsx | 0 components/{ => ui}/mdx/Heading.tsx | 0 components/{ => ui}/mdx/Inline.tsx | 0 components/{ => ui}/mdx/Steps.tsx | 0 components/{ => ui}/mdx/mdx-components.tsx | 16 +- components/ui/motion/MotionComponents.tsx | 8 +- components/ui/switch.tsx | 33 ++ components/ui/tabs.tsx | 126 +++++ content/docs/eternalcombat/using-api.mdx | 2 +- content/docs/notification/notifications.mdx | 2 +- lib/animations/variants.ts | 32 +- package.json | 1 + public/eternalcore/readme-banner.png | Bin 0 -> 72991 bytes public/poland map.svg | 3 + 81 files changed, 4000 insertions(+), 1895 deletions(-) create mode 100644 app/api/builds/builds.ts create mode 100644 app/builds/page.tsx create mode 100644 app/projects/eternalcombat/ConfigPreview.tsx create mode 100644 app/projects/eternalcombat/page.tsx create mode 100644 app/projects/eternalcore/ConfigPreview.tsx create mode 100644 app/projects/eternalcore/page.tsx delete mode 100644 app/projects/page.tsx delete mode 100644 components/CookiePreferencesMenu.tsx create mode 100644 components/hero/ProjectsDropdown.tsx create mode 100644 components/home/about/PolandMap.tsx create mode 100644 components/home/cta/Cta.tsx delete mode 100644 components/home/faq/AnimatedChevron.tsx delete mode 100644 components/home/faq/animations.ts create mode 100644 components/home/sponsors/Sponsors.tsx create mode 100644 components/notification-generator/FloatingPreview.tsx delete mode 100644 components/projects/ProjectButton.tsx delete mode 100644 components/projects/ProjectItem.tsx delete mode 100644 components/projects/Projects.tsx delete mode 100644 components/projects/ProjectsError.tsx delete mode 100644 components/projects/ProjectsSkeleton.tsx delete mode 100644 components/projects/index.ts delete mode 100644 components/projects/projectService.ts delete mode 100644 components/projects/types.ts create mode 100644 components/team/TeamHero.tsx delete mode 100644 components/team/animations.ts delete mode 100644 components/team/index.ts create mode 100644 components/ui/accordion.tsx rename components/{ => ui}/mdx/Callout.tsx (100%) rename components/{ => ui}/mdx/Card.tsx (100%) rename components/{ => ui}/mdx/CodeBlock.tsx (100%) rename components/{ => ui}/mdx/CodeTabs.tsx (100%) rename components/{ => ui}/mdx/Heading.tsx (100%) rename components/{ => ui}/mdx/Inline.tsx (100%) rename components/{ => ui}/mdx/Steps.tsx (100%) rename components/{ => ui}/mdx/mdx-components.tsx (88%) create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 public/eternalcore/readme-banner.png create mode 100644 public/poland map.svg diff --git a/app/api/builds/builds.ts b/app/api/builds/builds.ts new file mode 100644 index 00000000..46fe666a --- /dev/null +++ b/app/api/builds/builds.ts @@ -0,0 +1,91 @@ +export interface BuildArtifact { + id: string; + name: string; + url: string; // The API url to the artifact + size: number; +} + +export interface BuildRun { + id: number; + name: string; + status: string; // "completed", "in_progress", etc. + conclusion: string | null; // "success", "failure", etc. + head_branch: string; + head_sha: string; + created_at: string; + html_url: string; + artifacts_url: string; + display_title?: string; // Sometimes present in GH API +} + +interface GithubRunsResponse { + workflow_runs: BuildRun[]; +} + +export interface ModrinthVersion { + id: string; + name: string; + version_number: string; + date_published: string; + files: { + url: string; + filename: string; + primary: boolean; + }[]; +} + +export interface Project { + id: string; + name: string; + githubRepo: string; + modrinthId?: string; +} + +export const PROJECTS: Project[] = [ + { + id: "eternalcore", + name: "EternalCore", + githubRepo: "EternalCodeTeam/EternalCore", + modrinthId: "eternalcore", + }, + { + id: "eternalcombat", + name: "EternalCombat", + githubRepo: "EternalCodeTeam/EternalCombat", + modrinthId: "eternalcombat", + }, +]; + +export async function fetchDevBuilds(project: Project): Promise { + try { + const res = await fetch( + `https://api.github.com/repos/${project.githubRepo}/actions/runs?per_page=20&status=success&branch=master` + ); + if (!res.ok) { + console.error(`Failed to fetch Github Actions for ${project.name}`, await res.text()); + return []; + } + const data = (await res.json()) as GithubRunsResponse; + return data.workflow_runs || []; + } catch (error) { + console.error(`Error fetching dev builds for ${project.name}`, error); + return []; + } +} + +export async function fetchStableBuilds(project: Project): Promise { + if (!project.modrinthId) return []; + + try { + const res = await fetch(`https://api.modrinth.com/v2/project/${project.modrinthId}/version`); + if (!res.ok) { + if (res.status === 404) return []; // Project might not exist yet + console.error(`Failed to fetch Modrinth versions for ${project.name}`, await res.text()); + return []; + } + return (await res.json()) as ModrinthVersion[]; + } catch (error) { + console.error(`Error fetching stable builds for ${project.name}`, error); + return []; + } +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx index 0aea595c..db8bf68e 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -2,6 +2,7 @@ import { BookOpen, Users, Tag } from "lucide-react"; import type { Metadata } from "next"; import { SlideIn, StaggerContainer } from "@/components/ui/motion/MotionComponents"; +import { FacadePattern } from "@/components/ui/facade-pattern"; import BlogPostCard from "@/components/blog/BlogPostCard"; import { getBlogPosts, getBlogTags, getAuthors } from "@/lib/strapi"; @@ -47,68 +48,79 @@ export default async function BlogPage() { const authors = await getAuthors(); return ( -
- -
-
-
-

- Blog -

-
- - {posts.length} articles - - {authors.length > 0 && ( +
+ {/* Background Decor */} +
+ {/* Top Right - Orange/Amber for "Reading/Warmth" */} +
+ {/* Middle Left - Blue for "Tech" */} +
+ {/* Bottom Center - Purple */} +
+ + +
+ +
+ +
+
+
+

+ Blog +

+
- {authors.length} authors + {posts.length} articles - )} - - {tags.length} topics - + {authors.length > 0 && ( + + {authors.length} authors + + )} + + {tags.length} topics + +
+
+
+

+ Discover the latest insights, tutorials, and articles from our team of experts. +

-
-
-

- Discover the latest insights, tutorials, and articles from our team of experts. -

-
- + -
-
- {posts.length > 0 ? ( - - {posts.map((post, i) => ( - - - - ))} - - ) : ( -
- -

- No articles yet -

-

- We're working on some great content. Check back soon! -

-
- )} -
-
+
+
+ {posts.length > 0 ? ( + + {posts.map((post, i) => ( + + + + ))} + + ) : ( +
+ +

+ No articles yet +

+

+ We're working on some great content. Check back soon! +

+
+ )} +
+
+
); } diff --git a/app/builds/page.tsx b/app/builds/page.tsx new file mode 100644 index 00000000..09e022d1 --- /dev/null +++ b/app/builds/page.tsx @@ -0,0 +1,352 @@ +"use client"; + +import { format } from "date-fns"; +import { AnimatePresence, motion } from "framer-motion"; +import { Calendar, Download, GitBranch, Loader2, Package } from "lucide-react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { Suspense, useEffect, useState } from "react"; +import { fetchDevBuilds, fetchStableBuilds, PROJECTS, type Project } from "@/app/api/builds/builds"; +import { Button } from "@/components/ui/button"; +import { Dropdown } from "@/components/ui/dropdown"; +import { FacadePattern } from "@/components/ui/facade-pattern"; +import { FadeIn } from "@/components/ui/motion/MotionComponents"; + +interface Build { + id: string; + name: string; + type: "STABLE" | "DEV"; + date: string; + downloadUrl: string; + version?: string; + commit?: string; + runUrl?: string; +} + +function BuildExplorerContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + + const projectIdParam = searchParams.get("project"); + const initialProject = PROJECTS.find((p) => p.id === projectIdParam) || PROJECTS[0]; + + const [activeProject, setActiveProject] = useState(initialProject); + const [activeTab, setActiveTab] = useState<"STABLE" | "DEV">("STABLE"); + const [builds, setBuilds] = useState([]); + const [loading, setLoading] = useState(true); + const [lastDownloadedId, setLastDownloadedId] = useState(null); + + useEffect(() => { + const p = PROJECTS.find((p) => p.id === projectIdParam); + if (p && p.id !== activeProject.id) { + setActiveProject(p); + } + }, [projectIdParam, activeProject.id]); + + useEffect(() => { + const stored = localStorage.getItem(`last_download_${activeProject.id}`); + setLastDownloadedId(stored); + }, [activeProject.id]); + + const handleProjectChange = (projectId: string) => { + const project = PROJECTS.find((p) => p.id === projectId); + if (!project) return; + + setActiveProject(project); + const params = new URLSearchParams(searchParams.toString()); + params.set("project", project.id); + router.push(`/builds?${params.toString()}`); + }; + + useEffect(() => { + async function fetchData() { + setLoading(true); + setBuilds([]); // Clear previous builds + try { + if (activeTab === "STABLE") { + const data = await fetchStableBuilds(activeProject); + setBuilds( + data.map((version) => ({ + id: version.id, + name: version.name, + type: "STABLE", + date: version.date_published, + downloadUrl: version.files?.[0]?.url || "#", + version: version.version_number, + })) + ); + } else { + const runs = await fetchDevBuilds(activeProject); + setBuilds( + runs.map((run) => { + const displayTitle = run.display_title; + const artifactName = `${activeProject.name} Dev Build`; // Heuristic name, usually + + return { + id: run.id.toString(), + name: displayTitle || run.name || `Run #${run.id}`, + type: "DEV", + date: run.created_at, + downloadUrl: `https://nightly.link/${activeProject.githubRepo}/actions/runs/${run.id}/${encodeURIComponent(artifactName)}.zip`, + commit: run.head_sha.substring(0, 7), + runUrl: run.html_url, + }; + }) + ); + } + } catch (e) { + console.error("Failed to load builds", e); + } finally { + setLoading(false); + } + } + fetchData(); + }, [activeTab, activeProject]); + + const projectOptions = PROJECTS.map((p) => ({ + value: p.id, + label: p.name, + icon: , + })); + + return ( +
+ {/* Background Decor */} +
+
+
+
+ +
+ +
+
+ +

+ Build Explorer +

+

+ Access stable releases and development builds for all our projects. +

+
+
+ + {/* Controls Grid */} + +
+ {/* Project Selector - Left */} +
+ +
+ + {/* Tabs - Right */} +
+ + +
+
+
+ + {/* Table */} +
+ {loading ? ( +
+ +

Fetching builds for {activeProject.name}...

+
+ ) : ( +
+
+ + + + + + + + + + + + {builds.length === 0 ? ( + + + + ) : ( + builds.map((build, i) => ( + + + + + + + )) + )} + + +
Name + Date + + Ref + Action
+ No builds found for this project. +
+
+
+ {build.type === "STABLE" ? ( + + ) : ( + + )} +
+
+ {build.runUrl ? ( +
+ + {build.name} + + {lastDownloadedId === build.id && ( + + Last Downloaded + + )} +
+ ) : ( +
+ + {build.name} + + {lastDownloadedId === build.id && ( + + Last Downloaded + + )} +
+ )} + + {/* Mobile Meta (Date/Commit) */} +
+ + + {build.date && !Number.isNaN(new Date(build.date).getTime()) + ? format(new Date(build.date), "MMM d") + : "Unknown"} + + {build.commit && ( + <> + + {build.commit} + + )} +
+
+
+
+
+ + {build.date && !Number.isNaN(new Date(build.date).getTime()) + ? format(new Date(build.date), "MMM d, yyyy HH:mm") + : "Unknown Date"} +
+
+ {build.commit ? ( + + {build.commit} + + ) : ( + - + )} + +
+ +
+
+
+
+ )} +
+
+
+ ); +} + +export default function BuildExplorerPage() { + return ( + + +
+ } + > + + + ); +} diff --git a/app/docs/[...slug]/page.tsx b/app/docs/[...slug]/page.tsx index bb41e80b..aca756c9 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/docs/[...slug]/page.tsx @@ -12,7 +12,7 @@ import { DocsNavigation } from "@/components/docs/content/DocsNavigation"; import { EditOnGitHub } from "@/components/docs/content/EditOnGitHub"; import { ErrorBoundary } from "@/components/docs/content/ErrorBoundary"; import { ReadingTime } from "@/components/docs/content/ReadingTime"; -import { components, mdxOptions } from "@/components/mdx/mdx-components"; +import { components, mdxOptions } from "@/components/ui/mdx/mdx-components"; import { docsStructure } from "@/lib/sidebar-structure"; interface DocMeta { diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index 7f5abe0a..34bbf626 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -35,12 +35,21 @@ export const metadata: Metadata = { }, }; +import { FacadePattern } from "@/components/ui/facade-pattern"; + export default function DocsLayout({ children }: { children: ReactNode }) { return (
-
+ {/* Background Decor */} +
+
+
+ +
+ +
diff --git a/app/layout.tsx b/app/layout.tsx index 444086c5..f4a555c4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,7 +7,6 @@ import type React from "react"; import { Analytics } from "@/components/Analytics"; import "./prism-languages"; import { CookieConsentModal } from "@/components/CookieConsentModal"; -import { CookiePreferencesMenu } from "@/components/CookiePreferencesMenu"; import Footer from "@/components/footer/Footer"; import Navbar from "@/components/hero/Navbar"; import { SpeedInsights } from "@/components/SpeedInsights"; @@ -131,7 +130,6 @@ export default function RootLayout({