From a6a0121e9605225b6e16662e12beddc706492c15 Mon Sep 17 00:00:00 2001 From: AbuJulaybeeb Date: Fri, 26 Jun 2026 03:59:38 +0100 Subject: [PATCH 1/2] feat(seo): add PWA web app manifest and theme-color metadata --- README.md | 3 ++- src/app/layout.test.tsx | 16 +++++++++++++++- src/app/layout.tsx | 13 ++++++++++++- src/app/manifest.test.ts | 27 +++++++++++++++++++++++++++ src/app/manifest.ts | 20 ++++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/app/manifest.test.ts create mode 100644 src/app/manifest.ts diff --git a/README.md b/README.md index 7918e06..7c0a305 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,8 @@ control: - `src/app/robots.ts` allows public crawling from `/` and explicitly disallows operator-only dashboard surfaces: `/admin`, `/api-keys`, `/webhooks`, and `/settings`. -- Both metadata routes derive absolute URLs from +- `src/app/manifest.ts` serves the PWA web app manifest with name, description, branding colors matching the dark/light palette in `src/app/globals.css`, and `favicon.ico` icon entry to enable installability as a PWA. +- Both `sitemap.ts` and `robots.ts` derive absolute URLs from `NEXT_PUBLIC_AGENTPAY_SITE_ORIGIN`, defaulting to `http://localhost:3000` for local development rather than hard-coding a production domain. diff --git a/src/app/layout.test.tsx b/src/app/layout.test.tsx index 7713367..5221ec5 100644 --- a/src/app/layout.test.tsx +++ b/src/app/layout.test.tsx @@ -1,4 +1,4 @@ -import { metadata } from "./layout"; +import { metadata, viewport } from "./layout"; describe("root layout metadata", () => { it("keeps the home route on the default AgentPay title", () => { @@ -7,4 +7,18 @@ describe("root layout metadata", () => { template: "%s — AgentPay", }); }); + + it("configures the manifest and apple-touch icon in metadata", () => { + expect(metadata.manifest).toBe("/manifest.webmanifest"); + expect(metadata.icons).toMatchObject({ + apple: "/favicon.ico", + }); + }); + + it("configures the themeColor in viewport", () => { + expect(viewport.themeColor).toEqual([ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#0a0a0a" }, + ]); + }); }); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e68e2a4..e831c77 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,4 @@ -import type { Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { Header } from "@/components/Header"; @@ -16,6 +16,13 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#ffffff" }, + { media: "(prefers-color-scheme: dark)", color: "#0a0a0a" }, + ], +}; + export const metadata: Metadata = { title: { default: "AgentPay", @@ -24,6 +31,10 @@ export const metadata: Metadata = { description: "Machine-to-machine payment protocol on Stellar", applicationName: "AgentPay", authors: [{ name: "AgentPay" }], + manifest: "/manifest.webmanifest", + icons: { + apple: "/favicon.ico", + }, openGraph: { title: "AgentPay", description: "Pay-per-request billing for AI agents and APIs on Stellar.", diff --git a/src/app/manifest.test.ts b/src/app/manifest.test.ts new file mode 100644 index 0000000..eedf254 --- /dev/null +++ b/src/app/manifest.test.ts @@ -0,0 +1,27 @@ +import manifest from "./manifest"; + +describe("manifest metadata route", () => { + it("returns a valid manifest configuration", () => { + const config = manifest(); + + // Required fields assertion + expect(config.name).toBe("AgentPay"); + expect(config.short_name).toBe("AgentPay"); + expect(config.description).toBe("Machine-to-machine payment protocol on Stellar"); + expect(config.start_url).toBe("/"); + expect(config.display).toBe("standalone"); + + // Colors matching globals.css dark/light palette + const allowedColors = ["#0a0a0a", "#ffffff"]; + expect(allowedColors).toContain(config.background_color); + expect(allowedColors).toContain(config.theme_color); + + // Icon entries check + expect(config.icons).toBeDefined(); + expect(config.icons!.length).toBeGreaterThan(0); + const hasFavicon = config.icons!.some( + (icon) => icon.src === "/favicon.ico" + ); + expect(hasFavicon).toBe(true); + }); +}); diff --git a/src/app/manifest.ts b/src/app/manifest.ts new file mode 100644 index 0000000..2923e33 --- /dev/null +++ b/src/app/manifest.ts @@ -0,0 +1,20 @@ +import type { MetadataRoute } from "next"; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: "AgentPay", + short_name: "AgentPay", + description: "Machine-to-machine payment protocol on Stellar", + start_url: "/", + display: "standalone", + background_color: "#0a0a0a", + theme_color: "#0a0a0a", + icons: [ + { + src: "/favicon.ico", + sizes: "any", + type: "image/x-icon", + }, + ], + }; +} From 46d3e836b2245b59621c24a80da5c3bfc239dd69 Mon Sep 17 00:00:00 2001 From: AbuJulaybeeb Date: Fri, 26 Jun 2026 05:46:15 +0100 Subject: [PATCH 2/2] refactor(components): extract a shared PageShell main wrapper --- docs/components.md | 17 ++++ src/app/docs/page.tsx | 10 +-- src/app/services/new/page.tsx | 9 +-- src/app/settings/page.tsx | 9 +-- src/components/PageShell.tsx | 59 ++++++++++++++ src/components/__tests__/PageShell.test.tsx | 90 +++++++++++++++++++++ 6 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 src/components/PageShell.tsx create mode 100644 src/components/__tests__/PageShell.test.tsx diff --git a/docs/components.md b/docs/components.md index 61df192..0912a22 100644 --- a/docs/components.md +++ b/docs/components.md @@ -37,6 +37,23 @@ Use `Header` once in the app shell. It already exposes the nav with