Access Receipt
A copy-paste React session receipt for Knock Codes access screens — unlock time, storage mode, timeout. Zero dependencies.
Best used for A visible confirmation after unlock — proof for the user (and for you, while testing) of which mode actually ran and how long the session will last.
Requires a <KnockCodesProvider> ancestor — it reports on a session something else already established, it
doesn't create one. Pair it with the same config passed to the gate sharing that session, since the receipt
can't infer storage mode, timeout, or verification strategy from the session record alone.
Minimal setup
<KnockCodesProvider expectedHash={hash} timeout={1_800_000}>
<KnockCodes>
<Dashboard />
<AccessReceipt timeout={1_800_000} storageMode="localStorage" />
</KnockCodes>
</KnockCodesProvider>
- components
- knock-codes
- core
- react
"use client";
import { useKnockCodesContext } from "./KnockCodesProvider.tsx";
import type { StorageMode } from "../core/storage.ts";
import { DEFAULT_TIMEOUT_MS } from "./types.ts";
import { cx } from "./cx.ts";
export interface AccessReceiptProps {
/** Storage backend the active session used. @default "localStorage" */
storageMode?: StorageMode;
/** Session lifetime in ms, for the "Timeout" line. @default 1_800_000 (30 minutes) */
timeout?: number;
/** Whether this session came from the default local hash check or a custom `verify` function. @default "local-hash" */
verificationStrategy?: "local-hash" | "server-verify";
className?: string;
}
function formatDuration(ms: number): string {
const minutes = Math.round(ms / 60_000);
if (minutes < 1) return `${Math.round(ms / 1000)}s`;
if (minutes < 60) return `${minutes}m`;
return `${Math.round(minutes / 60)}h`;
}
function formatTimestamp(epochMs: number): string {
return new Date(epochMs).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "medium" });
}
/**
* A small "receipt" shown after unlock — a session audit strip, not a
* verification surface of its own. Requires a `<KnockCodesProvider>`
* ancestor and renders nothing while locked, since there's no session yet
* to report on.
*/
export function AccessReceipt({
storageMode = "localStorage",
timeout = DEFAULT_TIMEOUT_MS,
verificationStrategy = "local-hash",
className,
}: AccessReceiptProps) {
const { session } = useKnockCodesContext();
if (!session) return null;
const rows = [
{ label: "Unlocked at", value: formatTimestamp(session.unlockedAt) },
{ label: "Storage", value: storageMode },
{ label: "Timeout", value: formatDuration(timeout) },
{ label: "Verification", value: verificationStrategy === "server-verify" ? "Server verify" : "Local hash" },
];
return (
<div
style={{ fontFamily: "var(--ag-font, inherit)" }}
className={cx(
"w-full max-w-sm rounded-[var(--ag-radius,0.5rem)] border border-[var(--ag-border,#e5e7eb)] bg-[var(--ag-card,#ffffff)] shadow-sm dark:border-[var(--ag-border-dark,#1f2937)] dark:bg-[var(--ag-card-dark,#030712)]",
className
)}
>
<div className="flex items-center justify-between border-b border-dashed border-[var(--ag-border,#e5e7eb)] px-4 py-2 dark:border-[var(--ag-border-dark,#1f2937)]">
<span className="inline-flex items-center gap-1.5 font-mono text-[10px] font-medium tracking-wider text-emerald-600 uppercase dark:text-emerald-400">
<span aria-hidden="true" className="h-1.5 w-1.5 shrink-0 rounded-full bg-emerald-600 dark:bg-emerald-400" />
Session active
</span>
<span className="font-mono text-[10px] text-gray-400 dark:text-gray-500">
#{session.unlockedAt.toString(36).toUpperCase()}
</span>
</div>
<dl className="divide-y divide-dashed divide-gray-200 px-4 dark:divide-gray-800">
{rows.map((row) => (
<div key={row.label} className="flex items-center justify-between py-2 text-sm">
<dt className="text-gray-500 dark:text-gray-400">{row.label}</dt>
<dd className="font-mono text-gray-900 dark:text-gray-100">{row.value}</dd>
</div>
))}
</dl>
</div>
);
}
Add this block to your project
Recommended
npx shadcn@latest add @knock-codes/access-receiptAlso installs
- Knock Codes Core
- Knock Codes Types
- useKnockCodes
- Session Provider
These install together as one atomic unit — even a presentational or read-only piece needs the full verification stack (hook, types, core) behind it to actually run.
Files created (9)
- components/knock-codes/core/hash.ts
- components/knock-codes/core/verify.ts
- components/knock-codes/core/session.ts
- components/knock-codes/core/storage.ts
- components/knock-codes/react/types.ts
- components/knock-codes/react/useKnockCodes.ts
- components/knock-codes/react/KnockCodesContext.tsx
- components/knock-codes/react/KnockCodesProvider.tsx
- components/knock-codes/react/AccessReceipt.tsx
Other ways
GitHub shorthand
npx shadcn@latest add trivedi-vatsal/knock-codes/access-receiptCopy the files by hand
- Open the Code tab in the preview above.
- Create each path listed below in your project and paste its contents in.
- Do the same for anything listed under “Also installs”, if present.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| storageMode | "localStorage" | "sessionStorage" | "memory" | "localStorage" | Storage backend the active session used — shown on the Storage line. |
| timeout | number | 1800000 | Session lifetime in milliseconds, formatted on the Timeout line. |
| verificationStrategy | "local-hash" | "server-verify" | "local-hash" | Which verification strategy produced this session. |
| className | string | — | Extra classes on the outer card. |
Accessibility
Read-only summary content — a definition list, not an interactive control. Renders nothing while locked, so it never announces a session that doesn't exist yet.
Customization
Pass `storageMode`, `timeout`, and `verificationStrategy` to match the config given to the `<KnockCodes>`/`useKnockCodes` call sharing this session — the receipt has no way to read those back off the session itself, since the session record intentionally only ever holds `unlockedAt`, `expiresAt`, and an optional token.
Need a hash? Use the hash generator on Getting Started — computed locally, never sent anywhere.
The honest version
Knock Codes stops casual visitors, search engines, and forwarded links. Local mode does not stop anyone who opens DevTools — the hash ships in your client bundle by design. Server mode (swap one prop) hides the hash from the client; children you already bundled are still in the JavaScript, and a forged session works unless you wire validateSession. A velvet rope, with an optional real lock. Never marketed as more than that.
Blocks that pair well with this one
These combine naturally with this block, whether as a shared shell, a shared session, or a common fallback.
- Session ProviderA copy-paste React session provider for Knock Codes access screens — shares one unlock state across a gate, a logout button, and a timeout banner. Zero dependencies.
- Session Timeout BannerA copy-paste React warning banner for Knock Codes access screens — alerts before a shared session expires, with a one-click logout. Zero dependencies.
- Logout ButtonA copy-paste React logout control for Knock Codes access screens — clears the shared session on click. Zero dependencies.