diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index ed4ab67..5b86a46 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,9 +1,14 @@ +import Link from "next/link"; import Shell from "@/components/Shell"; +import SocialLinks from "@/components/SocialLinks"; import { getTheme } from "@/themes/server"; import { THEMES } from "@/themes/registry"; +import { getIntegrationConfig, hasIntegrations } from "@/lib/integrations"; export default async function AboutPage() { const theme = await getTheme(); + const showBio = hasIntegrations(); + const cfg = showBio ? getIntegrationConfig() : null; return (
@@ -30,6 +35,19 @@ export default async function AboutPage() { file and registering it — no markup changes required.

+ + {showBio && cfg && ( +
+

About the webmaster

+

+ {cfg.displayName ? {cfg.displayName} : "The webmaster"}{" "} + keeps a living gamer profile here — recently played titles, + achievements, the console shelf, and all-time favorites.{" "} + See the full bio → +

+ +
+ )}
); diff --git a/src/app/admin/(panel)/integrations/page.tsx b/src/app/admin/(panel)/integrations/page.tsx new file mode 100644 index 0000000..b63c7b1 --- /dev/null +++ b/src/app/admin/(panel)/integrations/page.tsx @@ -0,0 +1,263 @@ +import { getIntegrationConfig, getProfile } from "@/lib/integrations"; +import { SOCIAL_NETWORKS } from "@/lib/integrations/social"; +import type { + ConsoleItem, + FavoriteGame, + SocialLink, +} from "@/lib/integrations/types"; +import { + saveIntegrationsAction, + refreshIntegrationsAction, +} from "../../actions"; + +function socialToText(links: SocialLink[]): string { + return links + .map((l) => [l.network, l.url, l.label].filter(Boolean).join(" | ")) + .join("\n"); +} +function namedToText(items: (ConsoleItem | FavoriteGame)[]): string { + return items.map((i) => [i.name, i.note].filter(Boolean).join(" | ")).join("\n"); +} + +function fmtAge(iso: string): string { + const t = new Date(iso).getTime(); + if (Number.isNaN(t)) return "—"; + const mins = Math.round((Date.now() - t) / 60000); + if (mins < 1) return "just now"; + if (mins < 60) return `${mins} min ago`; + const hrs = Math.round(mins / 60); + return hrs < 24 ? `${hrs}h ago` : `${Math.round(hrs / 24)}d ago`; +} + +function Banner({ saved, refreshed }: { saved?: string; refreshed?: string }) { + if (saved) return

Integrations saved. Caches cleared.

; + if (refreshed) return

Platform caches cleared — data refetches on next view.

; + return null; +} + +export default async function IntegrationsPage({ + searchParams, +}: { + searchParams: Promise<{ saved?: string; refreshed?: string }>; +}) { + const { saved, refreshed } = await searchParams; + const cfg = getIntegrationConfig(); + // Loads (cached) platform data so we can show freshness + any fetch errors. + const profile = await getProfile(); + const networkIds = SOCIAL_NETWORKS.map((n) => n.id).join(", "); + + return ( +
+

Integrations

+ +

+ Build your gamer bio: a profile, social links, console list, favorite + games, and live data pulled from gaming platforms. Everything here powers + the public bio page. +

+ + {/* ---- platform status ---- */} + {profile.platforms.length > 0 && ( +
+

Platform status

+ + + + + + + + + + + + {profile.platforms.map((p) => ( + + + + + + + + ))} + +
PlatformGamesAchievementsFetchedStatus
{p.platform.toUpperCase()}{p.games.length}{p.achievements.length}{fmtAge(p.fetchedAt)} + {p.error ? ( + + {p.error} + + ) : ( + "OK" + )} +
+
+ +
+
+ )} + +
+ {/* ---- profile ---- */} +

Profile

+
+ + +
+