mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
feat(web): Pro is available — /buy hands off to Microsoft Marketplace and Cleverbridge (no price on the page), the plan cards show the list price of €149 / month, the self-service 30-day trial opens in the portal Plan section, and the get-started sign-in waits for a Terms of Use + DPA tick (D-251)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
Submodule internal updated: 66ebb0511e...f76aac66ad
@@ -4,7 +4,7 @@ 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.",
|
||||
"How to purchase Autopilot Monitor Pro: through Microsoft Marketplace or online via Cleverbridge — or try it first with a free 30-day trial. Community stays free.",
|
||||
keywords: [
|
||||
"Autopilot Monitor Pro",
|
||||
"Autopilot Monitor buy",
|
||||
@@ -15,13 +15,13 @@ export const metadata: Metadata = {
|
||||
openGraph: {
|
||||
title: "Get Pro – Autopilot Monitor",
|
||||
description:
|
||||
"How to purchase Autopilot Monitor Pro: direct online purchase and the Microsoft commercial marketplace — both coming soon.",
|
||||
"How to purchase Autopilot Monitor Pro: through Microsoft Marketplace or online via Cleverbridge.",
|
||||
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.",
|
||||
"How to purchase Autopilot Monitor Pro: through Microsoft Marketplace or online via Cleverbridge.",
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/buy`,
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
import Link from "next/link";
|
||||
import { LandingNavbar } from "../../components/landing/LandingNavbar";
|
||||
import { SiteFooter } from "../../components/SiteFooter";
|
||||
import { DOCS_URL } from "@/utils/config";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Purchase page for Pro: hands the buyer off to one of the two purchase channels. Linked from
|
||||
* the public /plans page and from the portal's Plan section (absolute www URL — /buy is public
|
||||
* surface). It names no price on purpose: the plan cards carry the list price, the channel's
|
||||
* checkout the binding one. Plain outbound links, so neither host needs a CSP entry.
|
||||
*/
|
||||
const PURCHASE_GUIDE_URL = `${DOCS_URL}/troubleshooting-and-support/how-to-purchase`;
|
||||
|
||||
const CHANNELS = [
|
||||
{
|
||||
label: "Microsoft Marketplace",
|
||||
description:
|
||||
"Purchase through the Microsoft commercial marketplace using your organization's existing Microsoft billing relationship.",
|
||||
"Subscribe through the Microsoft commercial marketplace and pay through your organization's existing Azure billing.",
|
||||
href: "https://marketplace.microsoft.com/en-us/product/saas/glueckkanja-gabag.autopilot-monitor-transactable-prod?tab=Overview",
|
||||
cta: "Buy on Microsoft Marketplace",
|
||||
guide: `${PURCHASE_GUIDE_URL}/microsoft-marketplace`,
|
||||
},
|
||||
{
|
||||
label: "Cleverbridge",
|
||||
description:
|
||||
"Buy Pro online with a credit card or on invoice — a fast, self-service checkout for a single organization.",
|
||||
"Buy Pro online by credit card, PayPal or bank transfer — a fast, self-service checkout for a single organization.",
|
||||
href: "https://www.cleverbridge.com/306/purl-Autopilot-Monitor-Buy-Y",
|
||||
cta: "Buy with Cleverbridge",
|
||||
guide: `${PURCHASE_GUIDE_URL}/cleverbridge`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -35,9 +41,8 @@ export default function BuyPage() {
|
||||
Get Autopilot Monitor Pro
|
||||
</h1>
|
||||
<p className="mt-4 max-w-2xl text-[15px] text-gray-600 leading-relaxed">
|
||||
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.
|
||||
Pick the channel that fits how your organization buys software. Both lead to the same
|
||||
Pro plan on your existing tenant — your data stays where it is.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
@@ -48,34 +53,42 @@ export default function BuyPage() {
|
||||
{CHANNELS.map((channel) => (
|
||||
<div key={channel.label} className="bg-[var(--lp-surface)] border border-[var(--lp-line-soft)] rounded-xl p-8 flex flex-col">
|
||||
<h2 className="text-lg font-bold text-gray-900 mb-3">{channel.label}</h2>
|
||||
<p className="text-sm text-gray-600 leading-relaxed mb-4 flex-1">{channel.description}</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled
|
||||
title="Available once Pro pricing is announced."
|
||||
className="w-full rounded-lg border border-[var(--lp-line-soft)] bg-[var(--lp-surface-2)] px-4 py-2.5 text-sm font-semibold text-[var(--lp-ink-faint)] cursor-not-allowed"
|
||||
<p className="text-sm text-gray-600 leading-relaxed mb-5 flex-1">{channel.description}</p>
|
||||
<a
|
||||
href={channel.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block w-full text-center text-sm font-semibold text-white bg-purple-600 rounded-lg px-4 py-2.5 hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
Coming soon
|
||||
</button>
|
||||
{channel.cta}
|
||||
</a>
|
||||
<a
|
||||
href={channel.guide}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-3 text-center text-xs text-gray-600 hover:text-gray-900 underline"
|
||||
>
|
||||
Purchase guide: prerequisites, payment and cancellation
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
{/* Interim path */}
|
||||
{/* Trial + questions */}
|
||||
<section className="bg-[var(--lp-surface)] border border-[var(--lp-line-soft)] rounded-xl p-8">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">Questions About Pro?</h2>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">Try Pro First</h2>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Interested in Pro for your organization or an MSP scenario, or want to know when it
|
||||
opens? Reach out through any channel on the{" "}
|
||||
A tenant administrator can start a one-time, free 30-day Pro trial in the portal under
|
||||
Settings → Tenant → Plan. When it ends, the tenant returns to Community automatically.
|
||||
Questions about Pro or an MSP scenario? Reach out through any channel on the{" "}
|
||||
<Link href="/help" className="text-[var(--lp-accent-ink)] hover:opacity-80 underline">
|
||||
Help & Support
|
||||
</Link>{" "}
|
||||
page — you'll talk directly to the person building the platform. And if you
|
||||
haven't compared the plans yet, the{" "}
|
||||
page, or compare the plans in the{" "}
|
||||
<Link href="/plans" className="text-[var(--lp-accent-ink)] hover:opacity-80 underline">
|
||||
plans overview
|
||||
</Link>{" "}
|
||||
shows exactly what Pro adds.
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import { LandingNavbar } from "../../components/landing/LandingNavbar";
|
||||
import { LoginButton } from "../../components/landing/LoginButton";
|
||||
import { SignupConsentCta } from "../../components/landing/SignupConsentCta";
|
||||
import { SiteFooter } from "../../components/SiteFooter";
|
||||
import { DOCS_URL, SITE_URL } from "@/utils/config";
|
||||
|
||||
@@ -85,11 +85,8 @@ export default function GetStartedPage() {
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-14 flex flex-wrap items-center gap-3">
|
||||
<LoginButton signup className="px-7 py-3 rounded-lg bg-[var(--lp-accent-ink)] hover:brightness-110 hover:shadow-lg text-white font-semibold shadow-md transition-all">
|
||||
Sign in to get started
|
||||
</LoginButton>
|
||||
{/* CTA — sign-in waits for the Terms + DPA tick */}
|
||||
<SignupConsentCta>
|
||||
<a
|
||||
href={DOCS_URL}
|
||||
target="_blank"
|
||||
@@ -98,7 +95,7 @@ export default function GetStartedPage() {
|
||||
>
|
||||
Read the docs
|
||||
</a>
|
||||
</div>
|
||||
</SignupConsentCta>
|
||||
|
||||
<p className="mt-6 text-sm text-[var(--lp-ink-faint)] leading-relaxed max-w-xl">
|
||||
When you need more later: the{" "}
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function PlansPage() {
|
||||
}
|
||||
proBadge={
|
||||
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700 border border-purple-200">
|
||||
Coming soon
|
||||
Free 30-day trial
|
||||
</span>
|
||||
}
|
||||
proCta={
|
||||
@@ -53,7 +53,8 @@ export default function PlansPage() {
|
||||
/>
|
||||
|
||||
<p className="text-sm text-gray-600">
|
||||
Pricing and availability for Pro will be announced. Community stays free.{" "}
|
||||
Pro is sold through Microsoft Marketplace and Cleverbridge; a tenant administrator can
|
||||
try it free for 30 days from the portal. Community stays free.{" "}
|
||||
For a feature-by-feature reference, see the{" "}
|
||||
<a
|
||||
href={`${DOCS_URL}/plans`}
|
||||
|
||||
@@ -9,14 +9,6 @@ import { SITE_URL } from "@/utils/config";
|
||||
import { SectionCardHeader } from "@/components/SectionCardHeader";
|
||||
import { DOCS_PATHS } from "@/lib/docsPaths";
|
||||
|
||||
/**
|
||||
* Self-service Pro trial switch. The Pro feature set is not finalized yet, so the
|
||||
* trial CTA is teased but not actionable. Flip this to `true` (a one-line change) to open the
|
||||
* self-service 30-day trial — the backend POST /trial endpoint and the startTrial() wiring are
|
||||
* already in place; only this gate keeps the button inert.
|
||||
*/
|
||||
const TRIAL_SELF_SERVICE_ENABLED = false;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@@ -55,38 +47,22 @@ export function SectionPlan() {
|
||||
? `Trial — ${daysLeft} day${daysLeft === 1 ? "" : "s"} left`
|
||||
: "Current plan"}
|
||||
</span>
|
||||
) : (
|
||||
) : editionInfo.trialAvailable ? (
|
||||
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-700 border border-purple-200">
|
||||
Coming soon
|
||||
Free 30-day trial
|
||||
</span>
|
||||
);
|
||||
) : undefined;
|
||||
|
||||
// CTA — only meaningful while the tenant is on Community
|
||||
const proCta = !isPro ? (
|
||||
<>
|
||||
{trialConsumed ? (
|
||||
<p className="text-sm text-gray-600">This tenant has already used its Pro trial.</p>
|
||||
) : !canStartTrial ? (
|
||||
<p className="text-sm text-gray-600">
|
||||
This tenant has already used its Pro trial. To move to Pro,{" "}
|
||||
<a
|
||||
href="https://github.com/okieselbach/AutopilotMonitor/issues"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-purple-700 hover:underline"
|
||||
>
|
||||
get in touch
|
||||
</a>
|
||||
.
|
||||
A tenant administrator can start the one-time 30-day Pro trial here.
|
||||
</p>
|
||||
) : !TRIAL_SELF_SERVICE_ENABLED ? (
|
||||
<button
|
||||
type="button"
|
||||
disabled
|
||||
title="Available soon — the Pro trial opens once the feature set is finalized."
|
||||
className="w-full text-sm font-medium text-white bg-purple-400 rounded-lg px-4 py-2.5 cursor-not-allowed opacity-70"
|
||||
>
|
||||
Start 30-day Pro trial — coming soon
|
||||
</button>
|
||||
) : canStartTrial && missingProfile.length > 0 ? (
|
||||
) : missingProfile.length > 0 ? (
|
||||
// Pro-requires-contact-profile gate (backend enforces the same via 409
|
||||
// ContactProfileRequired — this branch just makes the path obvious).
|
||||
<div className="text-sm bg-amber-50 border border-amber-200 rounded-lg p-3 text-amber-900 dark:bg-amber-950/30 dark:border-amber-700/50 dark:text-amber-200">
|
||||
@@ -97,7 +73,7 @@ export function SectionPlan() {
|
||||
</Link>{" "}
|
||||
so we can reach and identify you for service or security matters — then start your trial here.
|
||||
</div>
|
||||
) : canStartTrial && !confirming ? (
|
||||
) : !confirming ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirming(true)}
|
||||
@@ -105,7 +81,7 @@ export function SectionPlan() {
|
||||
>
|
||||
Start 30-day Pro trial
|
||||
</button>
|
||||
) : canStartTrial && confirming ? (
|
||||
) : (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600">One-time trial — start now?</span>
|
||||
<button
|
||||
@@ -128,16 +104,16 @@ export function SectionPlan() {
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
<p className="mt-3 text-xs text-gray-600">
|
||||
Curious how Pro will be sold?{" "}
|
||||
Ready to buy? Pro is sold through Microsoft Marketplace and Cleverbridge —{" "}
|
||||
<a
|
||||
href={`${SITE_URL}/buy`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-purple-700 hover:underline dark:text-purple-300"
|
||||
>
|
||||
View purchase options
|
||||
view purchase options
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
@@ -167,7 +143,7 @@ export function SectionPlan() {
|
||||
<p className="text-xs text-gray-600 mt-5">
|
||||
{isPro && editionInfo.isTrial
|
||||
? "When the trial ends, the tenant returns to Community automatically."
|
||||
: "Scope, pricing and timeline for Pro will be announced. Community stays free."}
|
||||
: "After a purchase this tenant moves to Pro and keeps its data. Community stays free."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,9 +8,12 @@ export function LoginButton({
|
||||
className,
|
||||
children,
|
||||
signup = false,
|
||||
disabled = false,
|
||||
}: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
/** Inert until the caller's precondition holds (get-started: Terms + DPA accepted). */
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Signup-funnel CTA (get-started "Sign in to get started"): routes the login through the
|
||||
* PRIMARY app registration so brand-new tenants consent the NEW app as part of the expected
|
||||
@@ -43,7 +46,7 @@ export function LoginButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={handleClick} className={className}>
|
||||
<button type="button" onClick={handleClick} disabled={disabled} className={className}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { LoginButton } from "./LoginButton";
|
||||
import { DOCS_URL } from "@/utils/config";
|
||||
|
||||
const LINK_CLASS = "text-[var(--lp-accent-ink)] hover:opacity-80 underline";
|
||||
|
||||
/**
|
||||
* Get-started CTA: the signup sign-in stays inert until the visitor ticks that they accept the
|
||||
* Terms of Use and the Data Processing Agreement. A client-side gate only — nothing is recorded
|
||||
* here. Both links open in a new tab so the tick survives reading them.
|
||||
*/
|
||||
export function SignupConsentCta({ children }: { children?: React.ReactNode }) {
|
||||
const [accepted, setAccepted] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="mt-14">
|
||||
<label className="flex items-start gap-3 max-w-xl text-sm text-[var(--lp-ink-soft)] leading-relaxed cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={accepted}
|
||||
onChange={(e) => setAccepted(e.target.checked)}
|
||||
className="mt-0.5 h-4 w-4 shrink-0 cursor-pointer accent-[var(--lp-accent-ink)]"
|
||||
/>
|
||||
<span>
|
||||
I agree to the{" "}
|
||||
<a href="/terms" target="_blank" rel="noopener noreferrer" className={LINK_CLASS}>
|
||||
Terms of Use
|
||||
</a>{" "}
|
||||
and the{" "}
|
||||
<a
|
||||
href={`${DOCS_URL}/legal/data-privacy-agreement-dpa`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={LINK_CLASS}
|
||||
>
|
||||
Data Processing Agreement
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className="mt-5 flex flex-wrap items-center gap-3">
|
||||
<LoginButton
|
||||
signup
|
||||
disabled={!accepted}
|
||||
className="px-7 py-3 rounded-lg bg-[var(--lp-accent-ink)] text-white font-semibold shadow-md transition-all enabled:hover:brightness-110 enabled:hover:shadow-lg disabled:opacity-40 disabled:shadow-none disabled:cursor-not-allowed"
|
||||
>
|
||||
Sign in to get started
|
||||
</LoginButton>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -128,17 +128,10 @@ export function PlanCards({ surface, highlight = null, communityBadge, proBadge,
|
||||
|
||||
<div className="mt-4 mb-5">
|
||||
{proPrice ?? (
|
||||
PRO_PRICE ? (
|
||||
<>
|
||||
<span className="text-2xl font-bold text-purple-900">{PRO_PRICE.amount}</span>
|
||||
<span className="text-sm text-gray-600"> {PRO_PRICE.suffix}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-2xl font-bold text-purple-900">Pricing</span>
|
||||
<span className="text-sm text-gray-600"> — announced soon</span>
|
||||
</>
|
||||
)
|
||||
<>
|
||||
<span className="text-2xl font-bold text-purple-900">{PRO_PRICE.amount}</span>
|
||||
<span className="text-sm text-gray-600"> {PRO_PRICE.suffix}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -39,9 +39,11 @@ export const proExtras = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Pro list price, shown on the plan cards only (public /plans and the portal Plan section).
|
||||
* The /buy page deliberately names no price — the checkout of the purchase channel carries
|
||||
* the binding one.
|
||||
*/
|
||||
export const PRO_PRICE: { amount: string; suffix: string } | null = null;
|
||||
export const PRO_PRICE: { amount: string; suffix: string } = {
|
||||
amount: "€149",
|
||||
suffix: "/ month, excl. VAT",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user