Skip to main content

Three layers: a short note at the top, the key lines with our take in the middle, the full source at the bottom.

Source

demo/page.tsx

The /demo route. Renders the guided tour over hand-authored fixtures — no upload surface, no dropzone, no way for a cold visitor to send a real invoice.

Repo path apps/web/app/(marketing)/demo/page.tsxLanguage TypeScript

What this is

The source code for the /demo route. It is the page a visitor lands on when they click any 'see the demo' link. It renders a guided tour over hand-authored sample data and has no upload form, no file input, and no way to send a real invoice.

What it proves

Backs the promise that the /demo page never asks for your invoice. The shape of the file is the proof — there is no upload surface here to take one. Read the promise →

What to look for in the source below

  • The page imports a tour component, not a dropzone.
  • No file-input element, no drag-and-drop handlers, no storage writes.
  • Sample data is loaded from a fixtures file that lives in the repo, not from any visitor's device.
Show the full file (126 lines)

125 lines

import type { Metadata } from "next";
import { cookies, headers } from "next/headers";
import { MarketingLayout } from "../_components/MarketingLayout";
import { canonicalMetadata } from "@/lib/canonical";
import { getCopy, resolveLocale, LOCALE_COOKIE } from "@/lib/i18n";
import { breadcrumbList, howTo } from "@/lib/seo-schema";
import DemoTour from "./DemoTour";
import { isPreLaunch } from "@/lib/launch";

export const metadata: Metadata = {
  title: "Try it",
  description:
    "Walk through a vendor invoice from scan to filed in five steps. No upload needed; the tour uses hand-authored fixtures so you can see how Muntin Ledger reads, catches, and files an invoice before signing in.",
  ...canonicalMetadata({ path: "/demo", hasES: true }),
};

/**
 * /demo — the guided product tour.
 *
 * Previously this page carried a URL-state mode toggle (?mode=
 * tour|live) that mounted either the pre-canned tour OR a live
 * dropzone that round-tripped a real upload through the engine.
 * The live mode was removed on 2026-05-25 — every cold visitor
 * could anonymously consume engine budget by dragging anything
 * in. That was a real abuse vector and the "no upload needed"
 * primary CTA implied we had already closed it.
 *
 * Now the page is just the guided tour. The hand-authored
 * fixtures in tour-data.ts walk the visitor from read to filed
 * with zero bytes moved on either side. When a visitor wants to
 * sign in and try the engine on a real invoice, the sign-in path
 * lands them in /inbox where the upload flow is rate-limited per
 * authenticated account.
 */
export default async function DemoPage() {
  const [jar, hdrs] = await Promise.all([cookies(), headers()]);
  const locale = resolveLocale({
    cookieValue: jar.get(LOCALE_COOKIE)?.value,
    acceptLanguage: hdrs.get("accept-language"),
  });
  const c = getCopy(locale);
  const t = c.landing.tour;
  const g = c.landing.glossary.confidenceRing;
  const u = c.ui.confidenceRing;

  return (
    <MarketingLayout width="wide">
      <script
        type="application/ld+json"
        // eslint-disable-next-line react/no-danger
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(
            breadcrumbList([
              { name: "Home", path: "/" },
              { name: "Try it", path: "/demo" },
            ]),
          ),
        }}
      />
      <script
        type="application/ld+json"
        // eslint-disable-next-line react/no-danger
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(
            howTo({
              name: "How to file a vendor invoice with Muntin Ledger",
              description:
                "Walk a vendor invoice from a scan or photo to a filed ledger row in five steps, then post the bill to QuickBooks Online or Xero.",
              totalTime: "PT2M",
              steps: [
                {
                  name: "Pick or snap a vendor invoice",
                  text: "Drag a PDF into the drop zone or snap a photo of a paper receipt with the iPhone app.",
                },
                {
                  name: "Watch us read it",
                  text: "We extract vendor, totals, line items, and posting hints with templates and rules. Confidence rings show how sure each field is.",
                },
                {
                  name: "Review the catch",
                  text: "We highlight any anomaly: a vendor total that does not match the line-item sum, a unit price that jumped, a missing tax field.",
                },
                {
                  name: "File it to your ledger",
                  text: "Confirm and the row lands in your searchable ledger with the original PDF attached and the audit chain updated.",
                },
                {
                  name: "Post the bill when ready",
                  text: "One-click post to QuickBooks Online or Xero. Dry-run by default so you can review before anything writes to your books.",
                },
              ],
            }),
          ),
        }}
      />
      <div className="py-[var(--mun-section-pad-y)]" data-tier="expressive">
        <p className="font-body text-eyebrow uppercase tracking-[0.06em] text-rust-text">
          {t.eyebrow}
        </p>
        <h1 className="mt-3 max-w-[28ch] font-display text-h1 leading-[1.05] tracking-[-0.01em]">
          {t.headline}
        </h1>
        <p className="mt-5 max-w-[62ch] font-body text-body leading-[1.6] text-ink-soft">
          {t.intro}
        </p>

        <div className="mt-10 max-w-[var(--mun-measure-prose)]">
          <DemoTour
            copy={t}
            locale={locale}
            preLaunch={isPreLaunch()}
            ringExplain={{
              openLabel: u.openLabel,
              title: g.label,
              high: g.high,
              medium: g.medium,
              low: g.low,
              bands: g.bands,
            }}
          />
        </div>
      </div>
    </MarketingLayout>
  );
}

See also

This is the file as it lives at the moment of this build. The canonical history lives in git. If you want the full history or a specific commit, write to hello@muntin.digital.

demo/page.tsx · Verify · Muntin Ledger · Muntin