diff --git a/src/Web/autopilot-monitor-web/app/buy/layout.tsx b/src/Web/autopilot-monitor-web/app/buy/layout.tsx
new file mode 100644
index 00000000..ed5eec5c
--- /dev/null
+++ b/src/Web/autopilot-monitor-web/app/buy/layout.tsx
@@ -0,0 +1,33 @@
+import type { Metadata } from "next";
+import { SITE_URL } from "@/utils/config";
+
+export const metadata: Metadata = {
+ title: "Get Pro – Purchase Options",
+ description:
+ "How to purchase Autopilot Monitor Pro: direct online purchase and the Microsoft commercial marketplace — both coming soon. Pricing will be announced; Community stays free.",
+ keywords: [
+ "Autopilot Monitor Pro",
+ "Autopilot Monitor buy",
+ "Autopilot Monitor purchase",
+ "Autopilot Monitor marketplace",
+ "Windows Autopilot monitoring Pro",
+ ],
+ openGraph: {
+ title: "Get Pro – Autopilot Monitor",
+ description:
+ "How to purchase Autopilot Monitor Pro: direct online purchase and the Microsoft commercial marketplace — both coming soon.",
+ url: `${SITE_URL}/buy`,
+ },
+ twitter: {
+ title: "Get Pro – Autopilot Monitor",
+ description:
+ "How to purchase Autopilot Monitor Pro: direct online purchase and the Microsoft commercial marketplace — both coming soon.",
+ },
+ alternates: {
+ canonical: `${SITE_URL}/buy`,
+ },
+};
+
+export default function BuyLayout({ children }: { children: React.ReactNode }) {
+ return <>{children}>;
+}
diff --git a/src/Web/autopilot-monitor-web/app/buy/page.tsx b/src/Web/autopilot-monitor-web/app/buy/page.tsx
new file mode 100644
index 00000000..c14499bb
--- /dev/null
+++ b/src/Web/autopilot-monitor-web/app/buy/page.tsx
@@ -0,0 +1,85 @@
+import Link from "next/link";
+import { LandingNavbar } from "../../components/landing/LandingNavbar";
+import { SiteFooter } from "../../components/SiteFooter";
+
+/**
+ * Purchase page for Pro. Linked from the public /plans page and from the
+ * portal's Plan section (absolute www URL — /buy is public surface).
+ *
+ * Both purchase channels are honest "coming soon" placeholders until the
+ * checkout links exist. When they do: the provider host must be registered in
+ * utils/config.ts, the CSP in staticwebapp.config.json, the swaConfig guard
+ * test, and the dev CSP in next.config.ts — see tasks/todo.md follow-up.
+ */
+const CHANNELS = [
+ {
+ label: "Direct purchase",
+ description:
+ "Buy Pro online with a credit card or on invoice — a fast, self-service checkout for a single organization.",
+ },
+ {
+ label: "Microsoft Marketplace",
+ description:
+ "Purchase through the Microsoft commercial marketplace using your organization's existing Microsoft billing relationship.",
+ },
+];
+
+export default function BuyPage() {
+ return (
+
+
+
+
+
Pro
+
+ Get Autopilot Monitor Pro
+
+
+ Pro will be available through two purchase channels. Neither is open yet — pricing
+ and availability will be announced. Until then, Community is the full product, free
+ for everyone.
+
+ Interested in Pro for your organization or an MSP scenario, or want to know when it
+ opens? Reach out through any channel on the{" "}
+
+ Help & Support
+ {" "}
+ page — you'll talk directly to the person building the platform. And if you
+ haven't compared the plans yet, the{" "}
+
+ plans overview
+ {" "}
+ shows exactly what Pro adds.
+
+ );
+}
diff --git a/src/Web/autopilot-monitor-web/app/robots.ts b/src/Web/autopilot-monitor-web/app/robots.ts
index 4a4193c9..647aa763 100644
--- a/src/Web/autopilot-monitor-web/app/robots.ts
+++ b/src/Web/autopilot-monitor-web/app/robots.ts
@@ -9,7 +9,7 @@ export default function robots(): MetadataRoute.Robots {
rules: [
{
userAgent: "*",
- allow: ["/", "/about", "/get-started", "/privacy", "/terms"],
+ allow: ["/", "/about", "/buy", "/get-started", "/help", "/plans", "/privacy", "/terms"],
disallow: [
"/dashboard",
"/fleet-health",
diff --git a/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionPlan.tsx b/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionPlan.tsx
index 3e9a484e..6c414657 100644
--- a/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionPlan.tsx
+++ b/src/Web/autopilot-monitor-web/app/settings/tenant/sections/SectionPlan.tsx
@@ -4,6 +4,8 @@ import { useState } from "react";
import Link from "next/link";
import { useTenantConfig } from "../../TenantConfigContext";
import { trialDaysLeft } from "@/lib/edition";
+import { PlanCards } from "@/components/plans/PlanCards";
+import { SITE_URL } from "@/utils/config";
/**
* Self-service Pro trial switch. The Pro feature set is not finalized yet, so the
@@ -14,65 +16,13 @@ import { trialDaysLeft } from "@/lib/edition";
const TRIAL_SELF_SERVICE_ENABLED = false;
/**
- * Retention baselines per plan tier. These describe the two PLANS on the comparison cards, so they
- * must not depend on the viewer's current edition — they mirror the backend catalog
- * (FeatureEntitlementCatalog: Community RetentionCapDays = 90, Pro = 365).
- */
-const COMMUNITY_RETENTION_DAYS = 90;
-const PRO_RETENTION_DAYS = 365;
-
-function CheckIcon({ className }: { className: string }) {
- return (
-
- );
-}
-
-function PlusIcon({ className }: { className: string }) {
- return (
-
- );
-}
-
-function FeatureList({ features, checkClass, muted = false }: { features: string[]; checkClass: string; muted?: boolean }) {
- return (
-
- {features.map((f) => (
-
-
- {f}
-
- ))}
-
- );
-}
-
-/** The Pro delta: same layout as FeatureList but "+" bullets and emphasized text. */
-function PlusList({ features }: { features: string[] }) {
- return (
-
- {features.map((f) => (
-
-
- {f}
-
- ))}
-
- );
-}
-
-/**
- * Plan section: two side-by-side plan cards (Community and Pro). The tenant's current plan
- * is highlighted so the edition is obvious at a glance; the Pro card teases what it adds.
+ * Plan section: the shared Community/Pro comparison cards (components/plans/PlanCards —
+ * also rendered on the public /plans page) with the tenant's current plan highlighted,
+ * plus the portal-only trial CTA and the purchase handoff.
*
- * Dark mode: the tinted card surfaces use slash-opacity utilities (bg-purple-50/40), which the
- * global `.dark .bg-*` override map in globals.css does NOT match — they are separate class tokens.
- * Without the explicit `dark:` counterparts below the cards render as a washed-out light overlay on
- * the dark page. Secondary copy uses text-gray-600 rather than -400/-500: both of those collapse to
- * the same dim #64748b in dark mode, which fails contrast on a tinted card.
+ * The purchase link is an ABSOLUTE www URL on purpose: /buy is a public path, and
+ * HostRoutingGuard never bounces an authenticated user off the portal origin, so a
+ * relative link would try (and fail) to serve the marketing page on the portal host.
*/
export function SectionPlan() {
const { editionInfo, startTrial, startingTrial, user } = useTenantConfig();
@@ -84,31 +34,104 @@ export function SectionPlan() {
const canStartTrial =
editionInfo.trialAvailable && (user?.isTenantAdmin === true || user?.isGlobalAdmin === true);
- // Features shared by both plans verbatim. Plan-specific items (retention, support tier) are NOT
- // in here — repeating "90-day retention" on the Pro card would be factually wrong; their
- // Pro counterparts live in proExtras instead.
- const sharedFeatures = [
- "Live session monitoring & progress portal",
- "Full rules engine, including custom rules",
- "Fleet analytics, notifications & diagnostics",
- "AI integration (MCP) within usage limits",
- ];
+ const communityBadge = !isPro ? (
+
+ Current plan
+
+ ) : undefined;
- const communityFeatures = [
- ...sharedFeatures,
- `${COMMUNITY_RETENTION_DAYS}-day data retention`,
- "Community support (GitHub)",
- ];
+ const proBadge = isPro ? (
+
+ {editionInfo.isTrial ? `Trial — ${daysLeft} day${daysLeft === 1 ? "" : "s"} left` : "Current plan"}
+
+ ) : (
+
+ Coming soon
+
+ );
- const proExtras = [
- `Extended data retention — ${PRO_RETENTION_DAYS} days (vs ${COMMUNITY_RETENTION_DAYS})`,
- "Higher portal & agent API rate limits",
- "Larger AI (MCP) usage quota",
- "Delegated (MSP) administration across tenants",
- "OOBE bootstrap sessions — run the agent already before MDM enrollment",
- "Unrestricted Mode for advanced data collection (activated on request)",
- "Reliability commitments & priority support",
- ];
+ // CTA — only meaningful while the tenant is on Community
+ const proCta = !isPro ? (
+ <>
+ {trialConsumed ? (
+
+ This tenant has already used its Pro trial. To move to Pro,{" "}
+
+ get in touch
+
+ .
+
+ ) : !TRIAL_SELF_SERVICE_ENABLED ? (
+
+ ) : canStartTrial && !editionInfo.contactEmailSet ? (
+ // Pro-requires-contact gate (backend enforces the same via 409
+ // ContactEmailRequired — this branch just makes the path obvious).
+
+ Pro requires a contact address.{" "}
+ Set one under{" "}
+
+ Contact
+ {" "}
+ so we can reach you about service or security matters — then start your trial here.
+
-
- {isPro ? "Active" : "Pricing TBA"}
-
- {!isPro && — to be announced}
-
-
-
- Everything in Community
-
-
-
-
-
- Plus
-
-
-
-
- {/* CTA — only meaningful while the tenant is on Community */}
- {!isPro && (
-
- {trialConsumed ? (
-
- This tenant has already used its Pro trial. To move to Pro,{" "}
-
- get in touch
-
- .
-
- ) : !TRIAL_SELF_SERVICE_ENABLED ? (
-
- ) : canStartTrial && !editionInfo.contactEmailSet ? (
- // Pro-requires-contact gate (backend enforces the same via 409
- // ContactEmailRequired — this branch just makes the path obvious).
-
- Pro requires a contact address.{" "}
- Set one under{" "}
-
- Contact
- {" "}
- so we can reach you about service or security matters — then start your trial here.
-