Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

import { useReducer } from 'react';

import { Badge } from '../../../components/ui/badge';
import { Button } from '../../../components/ui/button';
import { Badge } from '../../../../components/ui/badge';
import { Button } from '../../../../components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../../components/ui/card';
} from '../../../../components/ui/card';
import {
approvers,
createInitialTheatre,
theatreReducer,
} from '../../../lib/demo';
} from '../../../../lib/demo';

import type { ApprovalScenario } from './approval-scenarios';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { HederaEvidenceDetail } from '../../components/hedera-evidence';
import { Badge } from '../../components/ui/badge';
import { HederaEvidenceDetail } from '../../../components/hedera-evidence';
import { Badge } from '../../../components/ui/badge';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../components/ui/card';
import { auditEvents } from '../../lib/demo';
import { loadHederaEvidence } from '../../lib/mirror-evidence.server';
} from '../../../components/ui/card';
import { auditEvents } from '../../../lib/demo';
import { loadHederaEvidence } from '../../../lib/mirror-evidence.server';

const kindBadge = {
allow: ['Allowed', 'default'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import Link from 'next/link';

import { HederaEvidenceSummary } from '../components/hedera-evidence';
import { Badge } from '../components/ui/badge';
import { Button } from '../components/ui/button';
import { HederaEvidenceSummary } from '../../../components/hedera-evidence';
import { Badge } from '../../../components/ui/badge';
import { Button } from '../../../components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../components/ui/card';
} from '../../../components/ui/card';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../components/ui/table';
import { auditEvents, dayInvoices, initialQueue } from '../lib/demo';
import { loadHederaEvidence } from '../lib/mirror-evidence.server';
} from '../../../components/ui/table';
import { auditEvents, dayInvoices, initialQueue } from '../../../lib/demo';
import { loadHederaEvidence } from '../../../lib/mirror-evidence.server';

export const dynamic = 'force-dynamic';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@
import Link from 'next/link';
import { useState } from 'react';

import { Badge } from '../../components/ui/badge';
import { Button } from '../../components/ui/button';
import { Badge } from '../../../components/ui/badge';
import { Button } from '../../../components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../components/ui/card';
} from '../../../components/ui/card';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../../components/ui/table';
} from '../../../components/ui/table';
import {
dayInvoices,
HELD_INVOICE_ID,
initialQueue,
type QueueInvoice,
} from '../../lib/demo';
} from '../../../lib/demo';

export default function QueuePage() {
const [rows, setRows] = useState<readonly QueueInvoice[]>(initialQueue);
Expand Down
35 changes: 35 additions & 0 deletions apps/web/src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ReactNode } from 'react';

import { Sidebar } from '../../components/sidebar';

/**
* The workspace chrome.
*
* Everything under this group is the product operating inside one
* organisation. The marketing group deliberately does not get the sidebar, so
* a first-time visitor lands on an explanation rather than inside someone
* else's ledger.
*/
export default function WorkspaceLayout({
children,
}: Readonly<{ children: ReactNode }>): ReactNode {
return (
<>
<Sidebar />
<main className="min-h-screen pb-16 md:ml-[70px] md:pb-0">
<div className="mx-auto max-w-[1200px] px-4 py-6 md:px-8">
<div className="mb-6 flex flex-wrap items-center gap-x-4 gap-y-2 border border-border px-3 py-2">
<p className="microlabel text-foreground">
Demo organisation · synthetic AP records
</p>
<span className="hidden h-3 w-px bg-border sm:block" />
<p className="microlabel">
Live evidence · public Hedera Testnet facts only · no real funds
</p>
</div>
{children}
</div>
</main>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { HederaEvidenceDetail } from '../../components/hedera-evidence';
import { Badge } from '../../components/ui/badge';
import { HederaEvidenceDetail } from '../../../components/hedera-evidence';
import { Badge } from '../../../components/ui/badge';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../components/ui/card';
import { loadHederaEvidence } from '../../lib/mirror-evidence.server';
} from '../../../components/ui/card';
import { loadHederaEvidence } from '../../../lib/mirror-evidence.server';

export const dynamic = 'force-dynamic';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@

import { useCallback, useEffect, useMemo, useState } from 'react';

import { Badge } from '../../components/ui/badge';
import { Button } from '../../components/ui/button';
import { Badge } from '../../../components/ui/badge';
import { Button } from '../../../components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../components/ui/card';
} from '../../../components/ui/card';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../../components/ui/table';
} from '../../../components/ui/table';
import {
APPROVER_ROLES,
distinctApprovingHumans,
findHumanCollisions,
type Person,
type PersonRole,
} from '../../lib/people';
} from '../../../lib/people';

const REQUIRED_DISTINCT_HUMANS = 2;
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Badge } from '../../components/ui/badge';
import { Badge } from '../../../components/ui/badge';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../components/ui/card';
import { POLICY_VERSION, policyRules } from '../../lib/demo';
} from '../../../components/ui/card';
import { POLICY_VERSION, policyRules } from '../../../lib/demo';

export default function PoliciesPage() {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Badge } from '../../components/ui/badge';
import { Badge } from '../../../components/ui/badge';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '../../components/ui/card';
} from '../../../components/ui/card';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../../components/ui/table';
import { suppliers } from '../../lib/demo';
} from '../../../components/ui/table';
import { suppliers } from '../../../lib/demo';

const standingBadge = {
flagged: ['Account change pending', 'destructive'],
Expand Down
129 changes: 129 additions & 0 deletions apps/web/src/app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import Link from 'next/link';

export const metadata = {
title: 'Remit · Let agents pay the invoices',
description:
'Let agents pay the invoices, and prove they paid the right thing.',
};

const REFUSALS = [
{
code: 'ACTION_DIGEST_MISMATCH',
title: 'Change the account after approval',
body: 'The digest is the payment. Change one character and every approval collected against it is void.',
},
{
code: 'ACTION_HUMAN_NOT_DISTINCT',
title: 'Approve twice from one person',
body: 'Two company logins, two agent wallets, one human. World AgentKit resolves both. Quorum stays at one.',
},
{
code: 'REPLAY_DETECTED',
title: 'Reuse an approval to reach quorum',
body: 'An approval is single-use and bound to one action. Reusing it is refused, not counted.',
},
];

export default function WelcomePage() {
return (
<main className="mx-auto flex min-h-screen max-w-[880px] flex-col justify-center gap-14 px-6 py-20">
<header className="flex flex-col gap-5">
<span className="flex h-7 w-7 items-center justify-center bg-primary font-mono text-xs font-semibold text-primary-foreground">
R
</span>
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">
Let agents pay the invoices.
<br />
Prove they paid the right thing.
</h1>
<p className="max-w-[62ch] text-base text-muted-foreground">
Every company says it has a control: two people must approve. That
only ever verified that two <em>accounts</em> clicked a button. Point
an agent at it and a second independent approver costs one{' '}
<code className="font-mono text-sm">new Wallet()</code>.
</p>
</header>

<section className="grid gap-4 sm:grid-cols-2">
<div className="border border-border p-5">
<p className="microlabel">The 990</p>
<p className="mt-2 text-sm">
Known supplier, unchanged bank account, within tolerance. The agent
settles it on Hedera unattended. Nobody is asked anything.
</p>
</div>
<div className="border border-border p-5">
<p className="microlabel">The 10</p>
<p className="mt-2 text-sm">
Anything that changes where money can go escalates and demands two{' '}
<strong>provably distinct humans</strong> — established through
World AgentKit, not through two logins.
</p>
</div>
</section>

<section className="flex flex-col gap-4">
<h2 className="text-sm font-semibold">
The demo is not the happy path. It is watching it refuse.
</h2>
<div className="flex flex-col divide-y divide-border border border-border">
{REFUSALS.map((refusal) => (
<div className="flex flex-col gap-1 p-5" key={refusal.code}>
<p className="font-mono text-xs text-destructive">
{refusal.code}
</p>
<p className="text-sm font-medium">{refusal.title}</p>
<p className="text-sm text-muted-foreground">{refusal.body}</p>
</div>
))}
</div>
<p className="text-xs text-muted-foreground">
Each refusal is returned by the shipped domain package, not asserted
by a script.
</p>
</section>

<section className="flex flex-col gap-4 border border-border p-6">
<div>
<h2 className="text-sm font-semibold">Have a look around</h2>
<p className="mt-1 text-sm text-muted-foreground">
There is no account system yet. The demo organisation is a shared,
read-mostly workspace holding synthetic AP records — the Hedera
evidence in it is real and public, the invoices are not.
</p>
</div>
<div className="flex flex-wrap gap-3">
<Link
className="border border-foreground bg-foreground px-4 py-2 text-sm font-medium text-background"
href="/dashboard"
>
Open the demo organisation
</Link>
<Link
className="border border-border px-4 py-2 text-sm font-medium"
href="/people"
>
See who can approve
</Link>
<a
className="border border-border px-4 py-2 text-sm font-medium"
href="https://github.com/Lyons800/remit"
rel="noreferrer"
target="_blank"
>
Read the code
</a>
</div>
</section>

<footer className="flex flex-col gap-2 text-xs text-muted-foreground">
<p>
Remit proves the integrity of the authorisation path. It does not
prove that a supplier owns a bank account, that an invoice is genuine,
or that a verification service is truthful.
</p>
<p>ETHGlobal Lisbon 2026 · Hedera Testnet · World Chain · ENS</p>
</footer>
</main>
);
}
18 changes: 1 addition & 17 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import type { Metadata } from 'next';
import { Geist_Mono, Hedvig_Letters_Sans } from 'next/font/google';
import type { ReactNode } from 'react';

import { Sidebar } from '../components/sidebar';

import './styles.css';

/* midday maps its type to hedvig-sans; the open release is Hedvig Letters. */
Expand All @@ -28,21 +26,7 @@ export default function RootLayout({
return (
<html className={`${hedvig.variable} ${mono.variable}`} lang="en">
<body style={{ fontFamily: 'var(--font-sans), sans-serif' }}>
<Sidebar />
<main className="min-h-screen pb-16 md:ml-[70px] md:pb-0">
<div className="mx-auto max-w-[1200px] px-4 py-6 md:px-8">
<div className="mb-6 flex flex-wrap items-center gap-x-4 gap-y-2 border border-border px-3 py-2">
<p className="microlabel text-foreground">
Product scenario · synthetic AP records
</p>
<span className="hidden h-3 w-px bg-border sm:block" />
<p className="microlabel">
Live evidence · public Hedera Testnet facts only · no real funds
</p>
</div>
{children}
</div>
</main>
{children}
</body>
</html>
);
Expand Down
Loading
Loading