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. +

+
+
+ +
+ {/* Purchase channels */} +
+ {CHANNELS.map((channel) => ( +
+

{channel.label}

+

{channel.description}

+ +
+ ))} +
+ + {/* Interim path */} +
+

Questions About Pro?

+

+ 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/get-started/page.tsx b/src/Web/autopilot-monitor-web/app/get-started/page.tsx index 50239424..9fa8998d 100644 --- a/src/Web/autopilot-monitor-web/app/get-started/page.tsx +++ b/src/Web/autopilot-monitor-web/app/get-started/page.tsx @@ -103,9 +103,7 @@ export default function GetStartedPage() {

When you need more later: the{" "} Pro plan diff --git a/src/Web/autopilot-monitor-web/app/globals.css b/src/Web/autopilot-monitor-web/app/globals.css index db394d4e..55cd5271 100644 --- a/src/Web/autopilot-monitor-web/app/globals.css +++ b/src/Web/autopilot-monitor-web/app/globals.css @@ -861,6 +861,14 @@ html:hover ::-webkit-scrollbar-thumb { .dark .landing-v2 .border-gray-300 { border-color: #d1d5db !important; } .dark .landing-v2 .hover\:bg-gray-50:hover { background-color: #f9fafb !important; } .dark .landing-v2 .hover\:bg-gray-100:hover { background-color: #f3f4f6 !important; } +/* Purple utilities on the shared plan cards (/plans): only the classes that + HAVE a global .dark override need neutralizing here — the slash-opacity + surfaces (bg-purple-50/20) are separate tokens the override map never + matched in the first place. */ +.dark .landing-v2 .text-purple-900 { color: #581c87 !important; } +.dark .landing-v2 .text-purple-700 { color: #7e22ce !important; } +.dark .landing-v2 .bg-purple-100 { background-color: #f3e8ff !important; } +.dark .landing-v2 .border-purple-200 { border-color: #e9d5ff !important; } /* Scroll-triggered reveal: Reveal.tsx adds .lp-in when the element enters the viewport. Reduced motion renders everything visible. */ diff --git a/src/Web/autopilot-monitor-web/app/plans/layout.tsx b/src/Web/autopilot-monitor-web/app/plans/layout.tsx new file mode 100644 index 00000000..941eafae --- /dev/null +++ b/src/Web/autopilot-monitor-web/app/plans/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { SITE_URL } from "@/utils/config"; + +export const metadata: Metadata = { + title: "Plans – Community & Pro", + description: + "Autopilot Monitor plans: Community is the full product and free — live session monitoring, rules engine, fleet analytics, and AI integration. Pro adds extended retention, higher limits, MSP delegation, and priority support.", + keywords: [ + "Autopilot Monitor plans", + "Autopilot Monitor pricing", + "Autopilot Monitor free", + "Autopilot Monitor Pro", + "Windows Autopilot monitoring free", + "Autopilot monitoring MSP", + ], + openGraph: { + title: "Plans – Autopilot Monitor", + description: + "Community is the full product and free — and stays free. Pro adds extended retention, higher limits, MSP delegation, and priority support.", + url: `${SITE_URL}/plans`, + }, + twitter: { + title: "Plans – Autopilot Monitor", + description: + "Community is the full product and free — and stays free. Pro adds extended retention, higher limits, MSP delegation, and priority support.", + }, + alternates: { + canonical: `${SITE_URL}/plans`, + }, +}; + +export default function PlansLayout({ children }: { children: React.ReactNode }) { + return <>{children}; +} diff --git a/src/Web/autopilot-monitor-web/app/plans/page.tsx b/src/Web/autopilot-monitor-web/app/plans/page.tsx new file mode 100644 index 00000000..5f758232 --- /dev/null +++ b/src/Web/autopilot-monitor-web/app/plans/page.tsx @@ -0,0 +1,72 @@ +import Link from "next/link"; +import { LandingNavbar } from "../../components/landing/LandingNavbar"; +import { SiteFooter } from "../../components/SiteFooter"; +import { PlanCards } from "../../components/plans/PlanCards"; +import { DOCS_URL } from "@/utils/config"; + +/** + * Public plans page. Deliberately named "Plans" (not "Pricing"): the message is + * free-first — Community is the full product and stays free; Pro is the optional + * step up. The cards are the shared PlanCards, so this page can never drift from + * the portal's Plan section. + */ +export default function PlansPage() { + return ( +

+ +
+
+

Plans

+

+ The full product is free — and stays free. +

+

+ Community is not a trial and not a cut-down teaser: live monitoring, the full rules + engine, fleet analytics, and AI integration are free for everyone. Pro is the optional + step up for organizations that want longer retention, higher limits, MSP delegation, + and reliability commitments. +

+
+
+ +
+ + Free forever + + } + proBadge={ + + Coming soon + + } + proCta={ + + How to get Pro + + } + /> + +

+ Pricing and availability for Pro will be announced. Community stays free.{" "} + For a feature-by-feature reference, see the{" "} + + plans page in the documentation + + . +

+
+ +
+ ); +} 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 ( - - ); -} - -/** The Pro delta: same layout as FeatureList but "+" bullets and emphasized text. */ -function PlusList({ features }: { features: string[] }) { - return ( - - ); -} - -/** - * 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. +
+ ) : canStartTrial && !confirming ? ( + + ) : canStartTrial && confirming ? ( +
+ One-time trial — start now? + + +
+ ) : null} +

+ Curious how Pro will be sold?{" "} + + View purchase options + + . +

+ + ) : undefined; return (
@@ -125,150 +148,14 @@ export function SectionPlan() {
-
- {/* Community card */} -
-
-
-

Community

-

The full product, free

-
- {!isPro && ( - - Current plan - - )} -
- -
- Free - — and stays free -
- - -
- - {/* Pro card */} -
-
-
-

Pro

-

Higher limits, support & MSP

-
- {isPro ? ( - - {editionInfo.isTrial ? `Trial — ${daysLeft} day${daysLeft === 1 ? "" : "s"} left` : "Current plan"} - - ) : ( - - Coming soon - - )} -
- -
- - {isPro ? "Active" : "Pricing TBA"} - - {!isPro && — to be announced} -
- -

- Everything in Community -

- - - - - - {/* 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. -
- ) : canStartTrial && !confirming ? ( - - ) : canStartTrial && confirming ? ( -
- One-time trial — start now? - - -
- ) : null} -
- )} -
-
+ Active : undefined} + proCta={proCta} + />

{isPro && editionInfo.isTrial diff --git a/src/Web/autopilot-monitor-web/app/sitemap.ts b/src/Web/autopilot-monitor-web/app/sitemap.ts index 409df1cf..2f8d6409 100644 --- a/src/Web/autopilot-monitor-web/app/sitemap.ts +++ b/src/Web/autopilot-monitor-web/app/sitemap.ts @@ -34,6 +34,24 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "monthly", priority: 0.8, }, + { + url: `${BASE_URL}/plans`, + lastModified: lastmod("/plans"), + changeFrequency: "monthly", + priority: 0.8, + }, + { + url: `${BASE_URL}/buy`, + lastModified: lastmod("/buy"), + changeFrequency: "monthly", + priority: 0.6, + }, + { + url: `${BASE_URL}/help`, + lastModified: lastmod("/help"), + changeFrequency: "monthly", + priority: 0.5, + }, { url: `${BASE_URL}/privacy`, lastModified: lastmod("/privacy"), diff --git a/src/Web/autopilot-monitor-web/components/SiteFooter.tsx b/src/Web/autopilot-monitor-web/components/SiteFooter.tsx index b1dcdd68..c561deb2 100644 --- a/src/Web/autopilot-monitor-web/components/SiteFooter.tsx +++ b/src/Web/autopilot-monitor-web/components/SiteFooter.tsx @@ -8,7 +8,7 @@ const LINK_COLUMNS = [ { label: "The Story", href: "/#story" }, { label: "Capabilities", href: "/#features" }, { label: "Compare", href: "/#comparison" }, - { label: "Plans", href: `${DOCS_URL}/plans`, external: true }, + { label: "Plans", href: "/plans" }, ], }, { diff --git a/src/Web/autopilot-monitor-web/components/landing/FinalCta.tsx b/src/Web/autopilot-monitor-web/components/landing/FinalCta.tsx index 54b7889f..ab4c54c1 100644 --- a/src/Web/autopilot-monitor-web/components/landing/FinalCta.tsx +++ b/src/Web/autopilot-monitor-web/components/landing/FinalCta.tsx @@ -1,5 +1,3 @@ -import { DOCS_URL } from "@/utils/config"; - export function FinalCta() { return (

@@ -19,9 +17,7 @@ export function FinalCta() { Need SLAs, support, or MSP delegation?{" "} There's a Pro plan diff --git a/src/Web/autopilot-monitor-web/components/landing/LandingNavbar.tsx b/src/Web/autopilot-monitor-web/components/landing/LandingNavbar.tsx index ae7bfb95..17313148 100644 --- a/src/Web/autopilot-monitor-web/components/landing/LandingNavbar.tsx +++ b/src/Web/autopilot-monitor-web/components/landing/LandingNavbar.tsx @@ -13,6 +13,7 @@ const NAV_LINKS = [ { href: "/#story", label: "Product" }, { href: "/#features", label: "Capabilities" }, { href: "/#comparison", label: "Compare" }, + { href: "/plans", label: "Plans" }, { href: DOCS_URL, label: "Docs", external: true }, ]; diff --git a/src/Web/autopilot-monitor-web/components/plans/PlanCards.tsx b/src/Web/autopilot-monitor-web/components/plans/PlanCards.tsx new file mode 100644 index 00000000..93330df8 --- /dev/null +++ b/src/Web/autopilot-monitor-web/components/plans/PlanCards.tsx @@ -0,0 +1,161 @@ +import type { ReactNode } from "react"; +import { communityFeatures, PRO_PRICE, proExtras, sharedFeatures } from "./planData"; + +/** + * Shared Community/Pro comparison cards — the ONE place the two plans are laid + * out. The portal Plan section and the public /plans page both render this; + * plan copy itself lives in planData.ts. + * + * Surfaces: + * - "portal": plain Tailwind palette + explicit `dark:` variants. 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 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. + * - "marketing": renders inside the light-only `.landing-v2` shell, so no + * `dark:` variants (they key on `.dark` at and would bleed through). + * The purple utilities used here are neutralized under `.dark .landing-v2` + * in globals.css, like the gray/white ones. + */ + +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, iconClass }: { features: string[]; iconClass: string }) { + return ( +
    + {features.map((f) => ( +
  • + + {f} +
  • + ))} +
+ ); +} + +export interface PlanCardsProps { + surface: "marketing" | "portal"; + /** Which card gets the "this is yours" highlight treatment (portal only). */ + highlight?: "community" | "pro" | null; + communityBadge?: ReactNode; + proBadge?: ReactNode; + /** Overrides the Pro price line (portal shows "Active" while on Pro). */ + proPrice?: ReactNode; + /** Rendered at the bottom of the Pro card — trial CTA on portal, buy link on marketing. */ + proCta?: ReactNode; +} + +export function PlanCards({ surface, highlight = null, communityBadge, proBadge, proPrice, proCta }: PlanCardsProps) { + const marketing = surface === "marketing"; + + const communityCardClass = + highlight === "community" + ? "border-gray-800 ring-1 ring-gray-800 bg-gray-50/60 dark:border-slate-500 dark:ring-slate-500 dark:bg-slate-900/40" + : marketing + ? "border-[var(--lp-line-soft)] bg-[var(--lp-surface)]" + : "border-gray-200"; + + const proCardClass = + highlight === "pro" + ? "border-purple-500 ring-1 ring-purple-500 bg-purple-50/40 dark:bg-purple-950/40" + : marketing + ? "border-purple-200 bg-purple-50/20" + : "border-purple-200 bg-purple-50/20 dark:bg-purple-950/20"; + + const plusIconClass = marketing ? "text-purple-600" : "text-purple-600 dark:text-purple-400"; + const dividerClass = marketing ? "bg-purple-200" : "bg-purple-200 dark:bg-purple-800"; + const dividerLabelClass = marketing ? "text-purple-600" : "text-purple-600 dark:text-purple-300"; + + return ( +
+ {/* Community card */} +
+
+
+

Community

+

The full product, free

+
+ {communityBadge} +
+ +
+ Free + — and stays free +
+ + +
+ + {/* Pro card */} +
+
+
+

Pro

+

Higher limits, support & MSP

+
+ {proBadge} +
+ +
+ {proPrice ?? ( + PRO_PRICE ? ( + <> + {PRO_PRICE.amount} + {PRO_PRICE.suffix} + + ) : ( + <> + Pricing + — announced soon + + ) + )} +
+ +

+ Everything in Community +

+ + + + + + {proCta &&
{proCta}
} +
+
+ ); +} diff --git a/src/Web/autopilot-monitor-web/components/plans/planData.ts b/src/Web/autopilot-monitor-web/components/plans/planData.ts new file mode 100644 index 00000000..b32e8287 --- /dev/null +++ b/src/Web/autopilot-monitor-web/components/plans/planData.ts @@ -0,0 +1,47 @@ +/** + * Single source of truth for the Community/Pro plan comparison. Rendered by + * PlanCards on both the public /plans page and the portal Plan section + * (settings/tenant/plan), so the two surfaces cannot drift apart. + */ + +/** + * 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). + */ +export const COMMUNITY_RETENTION_DAYS = 90; +export const PRO_RETENTION_DAYS = 365; + +// 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. +export const sharedFeatures = [ + "Live session monitoring & progress portal", + "Full rules engine, including custom rules", + "Fleet analytics, notifications & diagnostics", + "AI integration (MCP) within usage limits", +]; + +export const communityFeatures = [ + ...sharedFeatures, + `${COMMUNITY_RETENTION_DAYS}-day data retention`, + "Community support (GitHub)", +]; + +export 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", +]; + +/** + * Pro price. Stays null until pricing is announced — PlanCards then renders the + * "Pricing — announced soon" teaser. Once the price is final, set it here (e.g. + * `{ amount: "€X", suffix: "per device / month" }`) and every surface — landing + * and portal — updates in the same one-line change. + */ +export const PRO_PRICE: { amount: string; suffix: string } | null = null; diff --git a/src/Web/autopilot-monitor-web/lib/__tests__/hostRouting.guard.test.ts b/src/Web/autopilot-monitor-web/lib/__tests__/hostRouting.guard.test.ts index bbdcdd9c..a6b18518 100644 --- a/src/Web/autopilot-monitor-web/lib/__tests__/hostRouting.guard.test.ts +++ b/src/Web/autopilot-monitor-web/lib/__tests__/hostRouting.guard.test.ts @@ -128,6 +128,8 @@ describe("hostRouting public/portal guard", () => { // Prefix matching: subpaths and generated asset variants match… expect(isPublicPath("/about")).toBe(true); expect(isPublicPath("/help")).toBe(true); + expect(isPublicPath("/plans")).toBe(true); + expect(isPublicPath("/buy")).toBe(true); expect(isPublicPath("/docs/setup")).toBe(true); expect(isPublicPath("/icon-192.png")).toBe(true); expect(isPublicPath("/opengraph-image.png")).toBe(true); diff --git a/src/Web/autopilot-monitor-web/lib/hostRouting.ts b/src/Web/autopilot-monitor-web/lib/hostRouting.ts index cb46934c..4d033b3a 100644 --- a/src/Web/autopilot-monitor-web/lib/hostRouting.ts +++ b/src/Web/autopilot-monitor-web/lib/hostRouting.ts @@ -22,10 +22,12 @@ import { PORTAL_URL, SITE_URL } from "@/utils/config"; */ export const PUBLIC_PATH_PREFIXES = [ "/about", + "/buy", "/changelog", "/docs", "/get-started", "/help", + "/plans", "/privacy", "/terms", "/roadmap", diff --git a/src/Web/autopilot-monitor-web/scripts/generate-lastmod.js b/src/Web/autopilot-monitor-web/scripts/generate-lastmod.js index 0b51607a..1c4dd600 100644 --- a/src/Web/autopilot-monitor-web/scripts/generate-lastmod.js +++ b/src/Web/autopilot-monitor-web/scripts/generate-lastmod.js @@ -18,6 +18,9 @@ const OUTPUT_FILE = path.join(WEB_ROOT, "utils/page-lastmod.generated.ts"); const PAGE_MAP = { "/": ["app/page.tsx"], "/about": ["app/about/page.tsx"], + "/buy": ["app/buy/page.tsx"], + "/help": ["app/help/page.tsx"], + "/plans": ["app/plans/page.tsx", "components/plans/planData.ts"], "/roadmap": ["app/roadmap/page.tsx"], "/changelog": ["app/changelog/page.tsx"], "/privacy": ["app/privacy/page.tsx"],