From 53ef8f47dcf6d23cd9d7c91b41829b6caa458846 Mon Sep 17 00:00:00 2001 From: TheRealToxicDev Date: Sat, 11 Jul 2026 23:31:50 -0600 Subject: [PATCH 1/3] feat(add): updated stuff --- app/layout.tsx | 10 +- app/partners/page.tsx | 11 + app/sitemap.ts | 1 + packages/core/constants/partners.ts | 50 +++++ packages/core/lib/bytepay.ts | 41 +++- packages/core/lib/spec-parser.ts | 110 ++++++---- .../Layouts/Dedicated/dedicated-hub.tsx | 6 +- .../ui/components/Layouts/Partners/index.ts | 1 + .../Layouts/Partners/partners-page.tsx | 196 ++++++++++++++++++ .../ui/components/Layouts/VPS/vps-hub.tsx | 6 +- packages/ui/components/Static/footer.tsx | 12 +- packages/ui/components/Static/navigation.tsx | 131 ++---------- packages/ui/components/layout-chrome.tsx | 5 +- public/partners/placeholder.svg | 4 + 14 files changed, 399 insertions(+), 185 deletions(-) create mode 100644 app/partners/page.tsx create mode 100644 packages/core/constants/partners.ts create mode 100644 packages/ui/components/Layouts/Partners/index.ts create mode 100644 packages/ui/components/Layouts/Partners/partners-page.tsx create mode 100644 public/partners/placeholder.svg diff --git a/app/layout.tsx b/app/layout.tsx index 5f03d09..9c353b4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -14,8 +14,6 @@ import { ThemeProvider } from "@/packages/ui/components/theme-provider" import { CurrencyProvider } from "@/packages/core/hooks/use-currency" import { LocaleProvider } from "@/packages/core/hooks/use-locale" import { LayoutChrome } from "@/packages/ui/components/layout-chrome" -import { getCategoryHub } from "@/packages/core/lib/bytepay" -import { GAME_HUB_SLUGS } from "@/packages/core/constants/catalog-hubs" const geist = Geist({ subsets: ["latin"], @@ -95,12 +93,6 @@ export default async function RootLayout({ const locale = await getLocale() const messages = await getMessages() - // Never let a billing-panel outage take down every page on the site — the - // nav just falls back to no games submenu entries if this fails. - const gamesNav = await getCategoryHub(GAME_HUB_SLUGS) - .then((hub) => (hub?.children ?? []).map((c) => ({ slug: c.slug, name: c.name }))) - .catch(() => []) - const htmlClass = [geist.variable, geistMono.variable, themeClass].filter(Boolean).join(" ") return ( @@ -156,7 +148,7 @@ export default async function RootLayout({ - + {children} diff --git a/app/partners/page.tsx b/app/partners/page.tsx new file mode 100644 index 0000000..cc76176 --- /dev/null +++ b/app/partners/page.tsx @@ -0,0 +1,11 @@ +import { PartnersPage } from "@/packages/ui/components/Layouts/Partners" +import type { Metadata } from "next" + +export const metadata: Metadata = { + title: "Partners & Sponsors", + description: "Meet the communities, creators, and projects we partner with, and learn how to join the NodeByte Partnership Program.", +} + +export default function Partners() { + return +} diff --git a/app/sitemap.ts b/app/sitemap.ts index b0c3e7d..8a2e2ad 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -17,6 +17,7 @@ const staticRoutes: Array<{ { path: "/vps/amd", priority: 0.9, changeFrequency: "weekly" }, { path: "/vps/intel", priority: 0.8, changeFrequency: "monthly" }, { path: "/about", priority: 0.6, changeFrequency: "monthly" }, + { path: "/partners", priority: 0.5, changeFrequency: "monthly" }, { path: "/contact", priority: 0.7, changeFrequency: "monthly" }, { path: "/changelog", priority: 0.5, changeFrequency: "weekly" }, { path: "/kb", priority: 0.7, changeFrequency: "weekly" }, diff --git a/packages/core/constants/partners.ts b/packages/core/constants/partners.ts new file mode 100644 index 0000000..1433dc8 --- /dev/null +++ b/packages/core/constants/partners.ts @@ -0,0 +1,50 @@ +/** + * Partners & sponsors shown on /partners. + * + * Add or remove entries here — the page groups them by `tier` automatically + * and shows an empty-state invite for any tier with zero entries, so this + * file can be edited freely without touching the page component. + * + * There's no real partner/sponsor data yet — the entries below are clearly + * marked placeholders. Replace them (or delete down to an empty array) once + * real partners sign on. + */ + +export type PartnerTier = "sponsor" | "partner" + +export interface PartnerEntry { + id: string + name: string + tier: PartnerTier + /** Path under /public, e.g. "/partners/example.svg" */ + logo: string + url: string + description: string +} + +export const PARTNERS: PartnerEntry[] = [ + { + id: "placeholder-sponsor-1", + name: "Placeholder Sponsor", + tier: "sponsor", + logo: "/partners/placeholder.svg", + url: "https://nodebyte.host", + description: "Placeholder entry — replace with a real sponsor in packages/core/constants/partners.ts.", + }, + { + id: "placeholder-partner-1", + name: "Placeholder Community", + tier: "partner", + logo: "/partners/placeholder.svg", + url: "https://nodebyte.host", + description: "Placeholder entry — replace with a real partner in packages/core/constants/partners.ts.", + }, + { + id: "placeholder-partner-2", + name: "Placeholder Project", + tier: "partner", + logo: "/partners/placeholder.svg", + url: "https://nodebyte.host", + description: "Placeholder entry — replace with a real partner in packages/core/constants/partners.ts.", + }, +] diff --git a/packages/core/lib/bytepay.ts b/packages/core/lib/bytepay.ts index 7a60723..be34034 100644 --- a/packages/core/lib/bytepay.ts +++ b/packages/core/lib/bytepay.ts @@ -204,13 +204,21 @@ async function fetchCategoryTree(): Promise { return categories .filter((cat) => !cat.parentId) - .map((hub) => ({ - id: hub.id, - name: hub.name, - slug: hub.slug, - description: hub.description, - children: byParent.get(hub.id) ?? [], - })) + .map((hub) => { + const children = byParent.get(hub.id) ?? [] + return { + id: hub.id, + name: hub.name, + slug: hub.slug, + description: hub.description, + // A hub with no sub-categories in Paymenter IS the leaf its products + // belong to directly (e.g. bare-metal servers filed straight under + // "Dedicated Servers" with no tiers underneath yet) — treat it as + // its own single child so pages that iterate `hub.children` still + // find those products instead of seeing an empty list. + children: children.length > 0 ? children : [{ id: hub.id, name: hub.name, slug: hub.slug, description: hub.description, parentId: null }], + } + }) } export const getCachedCategoryTree = unstable_cache(fetchCategoryTree, ["billing-category-tree"], { @@ -221,11 +229,28 @@ export const getCachedCategoryTree = unstable_cache(fetchCategoryTree, ["billing * Find a hub by its slugified name — accepts one or more acceptable aliases * (e.g. "vps-hosting" or "vps") since the exact parent category name is * whatever's configured in Paymenter. + * + * Falls back to a substring match (a hub whose slug *contains* one of the + * aliases) if no exact match is found, so a rename like "VPS Servers" → + * "VPS Plans 2026" doesn't silently empty out the whole section — it just + * warns, since the fallback match is a guess rather than a guarantee. */ export async function getCategoryHub(hubSlugOrAliases: string | string[]): Promise { const aliases = Array.isArray(hubSlugOrAliases) ? hubSlugOrAliases : [hubSlugOrAliases] const tree = await getCachedCategoryTree() - return tree.find((hub) => aliases.includes(hub.slug)) ?? null + + const exact = tree.find((hub) => aliases.includes(hub.slug)) + if (exact) return exact + + const fuzzy = tree.find((hub) => aliases.some((alias) => hub.slug.includes(alias))) + if (fuzzy) { + console.warn( + `[bytepay] No category exactly matched hub aliases [${aliases.join(", ")}] — falling back to "${fuzzy.name}" (slug "${fuzzy.slug}") via substring match. Rename the Paymenter category, or add its slug to the alias list, to make this exact.`, + ) + return fuzzy + } + + return null } /** diff --git a/packages/core/lib/spec-parser.ts b/packages/core/lib/spec-parser.ts index 8fd7928..21999d4 100644 --- a/packages/core/lib/spec-parser.ts +++ b/packages/core/lib/spec-parser.ts @@ -33,23 +33,52 @@ function stripHtml(html: string): string { return html.replace(/<[^>]*>/g, " ").replace(/&/g, "&").replace(/\s+/g, " ").trim() } -/** Split stripped text into individual bullet lines for more reliable matching. */ -function bulletLines(text: string): string[] { +/** + * Split into individual bullet lines. Paymenter descriptions are usually a + * `