@tollbooth-dpyc/web 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/react/AppShell.d.ts +7 -1
- package/dist/react/AppShell.js +3 -3
- package/dist/react/NpubGate.d.ts +33 -3
- package/dist/react/NpubGate.js +37 -5
- package/dist/react/SignInLinks.d.ts +35 -0
- package/dist/react/SignInLinks.js +6 -0
- package/dist/react/SignInSteps.d.ts +38 -0
- package/dist/react/SignInSteps.js +20 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/index.js +4 -0
- package/dist/react/signInFlow.d.ts +28 -0
- package/dist/react/signInFlow.js +42 -0
- package/dist/react/signInTools.d.ts +26 -0
- package/dist/react/signInTools.js +34 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -247,6 +247,20 @@ function Nav() {
|
|
|
247
247
|
`classNames.spinning`), is disabled and `aria-busy` while `onRefresh`'s
|
|
248
248
|
promise runs or `busy` is set; `label` is the tooltip and accessible name.
|
|
249
249
|
|
|
250
|
+
### Sign-in gate
|
|
251
|
+
|
|
252
|
+
`NpubGate` (or `AppShell`'s `gateOptions`) takes `welcome` — the site's own
|
|
253
|
+
words about what this place is for, above the card. Under the card it shows
|
|
254
|
+
`SignInSteps`, the nine-step sequence (you enter your npub; this page asks the
|
|
255
|
+
operator for a proof; a message reaches your Nostr client; you approve it for
|
|
256
|
+
a time you choose; your signed reply travels back; you tap Verify; the
|
|
257
|
+
operator checks the signature and hands the page a proof; the page unlocks)
|
|
258
|
+
with the step under way lit from the gate's real stage, and `SignInLinks` —
|
|
259
|
+
0xchat and Pricing Studio on the App Store, with the trademark credit
|
|
260
|
+
(`linksCredit={false}` where the page's footer already carries it). Both are
|
|
261
|
+
headless and exported on their own; `classNames.steps` / `classNames.links`
|
|
262
|
+
restyle them inside the gate. `steps={false}` / `links={false}` remove them.
|
|
263
|
+
|
|
250
264
|
### Debug panel
|
|
251
265
|
|
|
252
266
|
`callTool` logs to a shared, scrubbed ring buffer; `DebugPanel` shows it as a
|
package/dist/react/AppShell.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { type ReactNode } from "react";
|
|
|
24
24
|
import { type ServiceStatus } from "../standardTools.ts";
|
|
25
25
|
import { type Theme } from "../theme.ts";
|
|
26
26
|
import { type DebugPanelProps } from "./DebugPanel.tsx";
|
|
27
|
+
import { type NpubGateProps } from "./NpubGate.tsx";
|
|
27
28
|
import { type ShellFit } from "./shellLayout.ts";
|
|
28
29
|
import { type StatusState } from "./statusCheck.ts";
|
|
29
30
|
import { type Session } from "./useSession.ts";
|
|
@@ -59,8 +60,13 @@ export interface AppShellProps {
|
|
|
59
60
|
fit?: ShellFit;
|
|
60
61
|
/** Arrive at the gate with a new key already made. */
|
|
61
62
|
startFresh?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The gate's own words and look: the site's `welcome` above the card, whether
|
|
65
|
+
* the sequence strip and the tool links show, and their `classNames`.
|
|
66
|
+
*/
|
|
67
|
+
gateOptions?: Pick<NpubGateProps, "welcome" | "steps" | "links" | "linksCredit" | "classNames">;
|
|
62
68
|
classNames?: AppShellClassNames;
|
|
63
69
|
}
|
|
64
70
|
/** The shell's session, status and gate, for any component inside `AppShell`. */
|
|
65
71
|
export declare function useAppShell(): AppShellContext;
|
|
66
|
-
export default function AppShell({ children, signedOut, footer, debug, theme, fit, startFresh, classNames: c, }: AppShellProps): import("react").JSX.Element;
|
|
72
|
+
export default function AppShell({ children, signedOut, footer, debug, theme, fit, startFresh, gateOptions, classNames: c, }: AppShellProps): import("react").JSX.Element;
|
package/dist/react/AppShell.js
CHANGED
|
@@ -27,7 +27,7 @@ import { debugPush } from "../debugLog.js";
|
|
|
27
27
|
import { serviceStatus } from "../standardTools.js";
|
|
28
28
|
import { bootstrapTheme } from "../theme.js";
|
|
29
29
|
import DebugPanel, {} from "./DebugPanel.js";
|
|
30
|
-
import NpubGate from "./NpubGate.js";
|
|
30
|
+
import NpubGate, {} from "./NpubGate.js";
|
|
31
31
|
import { shellLayout } from "./shellLayout.js";
|
|
32
32
|
import { STATUS_CONNECTING, statusReducer } from "./statusCheck.js";
|
|
33
33
|
import { useSession } from "./useSession.js";
|
|
@@ -40,7 +40,7 @@ export function useAppShell() {
|
|
|
40
40
|
throw new Error("useAppShell must be used inside <AppShell>");
|
|
41
41
|
return shell;
|
|
42
42
|
}
|
|
43
|
-
export default function AppShell({ children, signedOut, footer, debug = {}, theme = "dark", fit = "page", startFresh, classNames: c = {}, }) {
|
|
43
|
+
export default function AppShell({ children, signedOut, footer, debug = {}, theme = "dark", fit = "page", startFresh, gateOptions, classNames: c = {}, }) {
|
|
44
44
|
const session = useSession();
|
|
45
45
|
const [check, dispatch] = useReducer(statusReducer, STATUS_CONNECTING);
|
|
46
46
|
const attempt = useRef(0);
|
|
@@ -75,7 +75,7 @@ export default function AppShell({ children, signedOut, footer, debug = {}, them
|
|
|
75
75
|
void hydrateAvatarFromNostr(npub);
|
|
76
76
|
}, [signedIn, npub]);
|
|
77
77
|
const { status, state: statusState, error: statusError } = check;
|
|
78
|
-
const gate = (_jsx(NpubGate, { onLogin: refresh, operatorHash: status?.operator_npub_hash, notice: notice, startFresh: startFresh }));
|
|
78
|
+
const gate = (_jsx(NpubGate, { onLogin: refresh, operatorHash: status?.operator_npub_hash, notice: notice, startFresh: startFresh, ...gateOptions }));
|
|
79
79
|
const shell = { session, status, statusState, statusError, retryStatus, gate };
|
|
80
80
|
const layout = shellLayout(fit);
|
|
81
81
|
return (_jsx(Ctx.Provider, { value: shell, children: _jsxs("div", { className: c.root, style: layout.root, children: [_jsx("div", { className: c.body, style: layout.body, children: signedIn ? children(shell) : signedOut ? signedOut(shell) : gate }), typeof footer === "function" ? footer(shell) : footer, debug !== false && _jsx(DebugPanel, { ...debug })] }) }));
|
package/dist/react/NpubGate.d.ts
CHANGED
|
@@ -4,13 +4,43 @@
|
|
|
4
4
|
* begin → request_npub_proof → awaiting reply → receive_npub_proof → app
|
|
5
5
|
*
|
|
6
6
|
* Success is not a `verified` flag but "no `error` and a token came back".
|
|
7
|
+
*
|
|
8
|
+
* Around the card: the site's `welcome` (its own words about what this place
|
|
9
|
+
* is for), the network's register line, and — below — `SignInSteps`, which
|
|
10
|
+
* shows the sequence above with the step under way lit from this component's
|
|
11
|
+
* real stage, and `SignInLinks`, the two tools a newcomer needs. Both take the
|
|
12
|
+
* gate's default look here; a site restyles them through `classNames`.
|
|
7
13
|
*/
|
|
8
|
-
|
|
14
|
+
import { type ReactNode } from "react";
|
|
15
|
+
import { type SignInLinksClassNames } from "./SignInLinks.tsx";
|
|
16
|
+
import { type SignInStepsClassNames } from "./SignInSteps.tsx";
|
|
17
|
+
export interface NpubGateClassNames {
|
|
18
|
+
root?: string;
|
|
19
|
+
/** Around the site's `welcome`. */
|
|
20
|
+
welcome?: string;
|
|
21
|
+
/** The network's register line under the heading. */
|
|
22
|
+
register?: string;
|
|
23
|
+
/** The sequence strip's parts; the gate's defaults apply where unset. */
|
|
24
|
+
steps?: SignInStepsClassNames;
|
|
25
|
+
/** The tools list's parts; the gate's defaults apply where unset. */
|
|
26
|
+
links?: SignInLinksClassNames;
|
|
27
|
+
}
|
|
28
|
+
export interface NpubGateProps {
|
|
9
29
|
onLogin: () => void;
|
|
10
30
|
/** Arrive with a key already made, for a first-timer who asked for one. */
|
|
11
31
|
startFresh?: boolean;
|
|
12
|
-
/** Operator fingerprint, shown so the patron can check who sent the
|
|
32
|
+
/** Operator fingerprint, shown so the patron can check who sent the message. */
|
|
13
33
|
operatorHash?: string;
|
|
14
34
|
/** A routine re-auth prompt, drawn as a calm note rather than an error. */
|
|
15
35
|
notice?: string;
|
|
16
|
-
|
|
36
|
+
/** The site's own words about what this place is for, above the card. */
|
|
37
|
+
welcome?: ReactNode;
|
|
38
|
+
/** The sequence strip under the card. Default true. */
|
|
39
|
+
steps?: boolean;
|
|
40
|
+
/** The 0xchat / Pricing Studio links under the strip. Default true. */
|
|
41
|
+
links?: boolean;
|
|
42
|
+
/** The trademark credit under the links. Default true; false when the page's footer carries it. */
|
|
43
|
+
linksCredit?: boolean;
|
|
44
|
+
classNames?: NpubGateClassNames;
|
|
45
|
+
}
|
|
46
|
+
export default function NpubGate({ onLogin, startFresh, operatorHash, notice, welcome, steps, links, linksCredit, classNames: c, }: NpubGateProps): import("react").JSX.Element;
|
package/dist/react/NpubGate.js
CHANGED
|
@@ -5,6 +5,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
5
5
|
* begin → request_npub_proof → awaiting reply → receive_npub_proof → app
|
|
6
6
|
*
|
|
7
7
|
* Success is not a `verified` flag but "no `error` and a token came back".
|
|
8
|
+
*
|
|
9
|
+
* Around the card: the site's `welcome` (its own words about what this place
|
|
10
|
+
* is for), the network's register line, and — below — `SignInSteps`, which
|
|
11
|
+
* shows the sequence above with the step under way lit from this component's
|
|
12
|
+
* real stage, and `SignInLinks`, the two tools a newcomer needs. Both take the
|
|
13
|
+
* gate's default look here; a site restyles them through `classNames`.
|
|
8
14
|
*/
|
|
9
15
|
import { useEffect, useRef, useState } from "react";
|
|
10
16
|
import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools";
|
|
@@ -13,8 +19,36 @@ import { forgetRecentLogin, getLastTypedNpub, getValidRecentLogins, recordRecent
|
|
|
13
19
|
import { setSessionNsec } from "../sessionNsec.js";
|
|
14
20
|
import { readSignInFailure } from "../signInSituation.js";
|
|
15
21
|
import { receiveNpubProof, requestNpubProof } from "../standardTools.js";
|
|
22
|
+
import { cx } from "./cx.js";
|
|
23
|
+
import SignInLinks, {} from "./SignInLinks.js";
|
|
24
|
+
import SignInSteps, {} from "./SignInSteps.js";
|
|
25
|
+
import { gateStage } from "./signInFlow.js";
|
|
16
26
|
import { card, errBox, ghost, input, muted, primary, warnBox } from "./ui.js";
|
|
17
|
-
|
|
27
|
+
// Left to right, three tiles to a row, wrapping; the tile under way is the
|
|
28
|
+
// only one drawn in ink and accent — the rest recede.
|
|
29
|
+
const STEPS_LOOK = {
|
|
30
|
+
root: "mt-6 grid grid-cols-3 gap-y-3",
|
|
31
|
+
step: "relative flex flex-col items-center px-1 text-center transition-colors",
|
|
32
|
+
done: "text-[var(--tb-muted)] opacity-60",
|
|
33
|
+
current: "text-[var(--tb-ink)]",
|
|
34
|
+
todo: "text-[var(--tb-muted)]",
|
|
35
|
+
glyph: "text-2xl leading-none",
|
|
36
|
+
label: "mt-1 text-[11px] leading-tight font-medium",
|
|
37
|
+
actor: "text-[10px] leading-tight opacity-70",
|
|
38
|
+
connector: "absolute right-[-0.35rem] top-2 text-base leading-none text-[var(--tb-muted)] opacity-50 [li:nth-child(3n)_&]:hidden",
|
|
39
|
+
};
|
|
40
|
+
const LINKS_LOOK = {
|
|
41
|
+
root: "mt-6 space-y-2",
|
|
42
|
+
lead: `text-xs ${muted}`,
|
|
43
|
+
list: "grid grid-cols-1 sm:grid-cols-2 gap-2",
|
|
44
|
+
link: `${card} flex items-center gap-3 px-3 py-2.5 hover:border-[var(--tb-accent)] transition-colors`,
|
|
45
|
+
logo: "h-10 w-10 shrink-0 rounded-[10px]",
|
|
46
|
+
text: "min-w-0",
|
|
47
|
+
name: "block text-sm font-medium",
|
|
48
|
+
line: `block text-xs leading-snug ${muted}`,
|
|
49
|
+
credit: `text-[10px] leading-relaxed ${muted}`,
|
|
50
|
+
};
|
|
51
|
+
export default function NpubGate({ onLogin, startFresh = false, operatorHash, notice, welcome, steps = true, links = true, linksCredit = true, classNames: c = {}, }) {
|
|
18
52
|
const { appName } = tollboothConfig();
|
|
19
53
|
// Prefilled from what was typed last, NOT from the stored identity.
|
|
20
54
|
const [value, setValue] = useState(getLastTypedNpub());
|
|
@@ -133,7 +167,7 @@ export default function NpubGate({ onLogin, startFresh = false, operatorHash, no
|
|
|
133
167
|
setPendingProof("");
|
|
134
168
|
setError("");
|
|
135
169
|
}
|
|
136
|
-
return (_jsxs("div", { className: "max-w-md mx-auto mt-12 px-4", children: [_jsxs("h1", { className: "text-xl font-semibold mb-1", children: ["Sign in to ", appName] }), _jsx("p", { className: `text-sm mb-5 ${muted}`, children: "
|
|
170
|
+
return (_jsxs("div", { className: cx("max-w-md mx-auto mt-12 px-4", c.root), children: [_jsxs("h1", { className: "text-xl font-semibold mb-1", children: ["Sign in to ", appName] }), welcome && _jsx("div", { className: cx("text-sm mb-3", c.welcome), children: welcome }), _jsxs("p", { className: cx(`text-sm mb-5 ${muted}`, c.register), children: [appName, " is an operator on the Tollbooth DPYC\u2122 network. DPYC\u2122 \u2014 Don't Pester Your Customer\u2122: no email, no password, no KYC. Your Nostr key is your identity."] }), notice && _jsx("div", { className: `mb-5 ${warnBox}`, children: notice }), stage === "begin" && recents.length > 0 && (_jsxs("div", { className: "mb-5", children: [_jsx("div", { className: `text-xs uppercase tracking-wider mb-2 ${muted}`, children: "Recent identities" }), _jsx("div", { className: "space-y-1.5", children: recents.map((e) => (_jsxs("div", { className: "flex gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => reuseRecent(e), disabled: busy, title: `Re-enter as ${e.npub} on the cached proof`, className: `${card} flex-1 flex items-center justify-between px-3 py-2.5 text-left hover:border-[var(--tb-accent)]`, children: [_jsxs("span", { className: "font-mono text-xs truncate", children: [e.npub.slice(0, 12), "\u2026", e.npub.slice(-6)] }), _jsxs("span", { className: `text-xs shrink-0 ml-2 ${muted}`, children: [ttl(e.expiresAt), " left"] })] }), _jsx("button", { type: "button", onClick: () => forget(e.npub), disabled: busy, title: "Forget this identity", "aria-label": "Forget this identity", className: `w-9 rounded-lg border border-[var(--tb-line)] ${muted}`, children: "\u00D7" })] }, e.npub))) })] })), _jsxs("div", { className: `${card} p-4 space-y-3`, children: [stage === "begin" ? (_jsxs(_Fragment, { children: [_jsx("label", { className: `block text-xs uppercase tracking-wider ${muted}`, htmlFor: "tb-npub-field", children: "Enter npub (secure) or nsec (convenient)" }), _jsx("input", { id: "tb-npub-field", type: isNsec ? "password" : "text", value: value, onChange: (e) => {
|
|
137
171
|
setValue(e.target.value);
|
|
138
172
|
setGeneratedHint(false);
|
|
139
173
|
}, onKeyDown: (e) => {
|
|
@@ -143,9 +177,7 @@ export default function NpubGate({ onLogin, startFresh = false, operatorHash, no
|
|
|
143
177
|
signInWithNsec();
|
|
144
178
|
else
|
|
145
179
|
void begin();
|
|
146
|
-
}, placeholder: "npub1\u2026
|
|
147
|
-
? "Your nsec stays in this browser and signs each call."
|
|
148
|
-
: "We send a Nostr DM to your npub. Reply from your Nostr client — your signature is the proof." }), _jsx("button", { type: "button", onClick: generateKey, className: ghost, children: "Generate a new key" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: `${warnBox} space-y-1.5`, children: [_jsx("div", { className: "font-medium", children: "DM sent \u2014 check your Nostr client." }), _jsx("div", { children: "Reply with any text. Your signature on that DM is the proof." }), operatorHash && (_jsxs("div", { children: ["Sender fingerprint: ", _jsxs("span", { className: "font-mono", children: ["\uD83D\uDD12 ", operatorHash] })] }))] }), pendingProof && (_jsxs("div", { className: "rounded-lg p-3 text-xs border border-[var(--tb-accent)] space-y-2", children: [_jsx("div", { className: `uppercase tracking-wider text-[10px] ${muted}`, children: "Confirmation code" }), _jsx("div", { className: "font-mono text-base select-all", children: pendingProof }), _jsxs("div", { className: `leading-relaxed ${muted}`, children: ["The same code appears in the DM. ", _jsx("b", { children: "Approve the DM only if the codes match." }), " If they differ \u2014 or the DM points you somewhere other than this site \u2014 do not reply."] })] })), _jsx("button", { type: "button", onClick: () => void finish(), disabled: busy, className: primary, children: busy ? "Checking…" : "I've replied — verify" }), _jsx("button", { type: "button", onClick: () => void begin(), disabled: busy, className: ghost, children: "Resend DM" }), _jsx("button", { type: "button", onClick: reset, disabled: busy, className: `w-full text-xs py-1.5 ${muted}`, children: "Use a different npub" })] })), error && _jsx(Trouble, { raw: error, appName: appName }), note && _jsx("div", { className: `text-xs text-center italic ${muted}`, children: note })] })] }));
|
|
180
|
+
}, placeholder: "npub1\u2026", spellCheck: false, autoCapitalize: "off", autoCorrect: "off", className: input }), generatedHint && (_jsxs("div", { className: warnBox, children: ["New key generated. ", _jsx("b", { children: "Save this nsec" }), " somewhere safe \u2014 it's the only copy and it lives only in this browser."] })), _jsx("button", { type: "button", onClick: () => (isNsec ? signInWithNsec() : void begin()), disabled: !valid || busy, className: primary, children: busy ? "Sending…" : isNpub ? "Send message" : "Sign in" }), isNsec && _jsx("p", { className: `text-xs ${muted}`, children: "Your nsec stays in this browser and signs each call." }), isNpub && (_jsx("p", { className: `text-xs ${muted}`, children: "We send a message to your npub. Approve it from your Nostr client \u2014 your signature is the proof." })), _jsx("p", { className: `text-xs pt-1 ${muted}`, children: "Need a new identity?" }), _jsx("button", { type: "button", onClick: generateKey, className: ghost, children: "Generate a new key" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: `${warnBox} space-y-1.5`, children: [_jsx("div", { className: "font-medium", children: "Message sent \u2014 open your Nostr client." }), _jsx("div", { children: "Reply with any text. Your signature on that reply is the proof." }), operatorHash && (_jsxs("div", { children: ["Sender fingerprint: ", _jsxs("span", { className: "font-mono", children: ["\uD83D\uDD12 ", operatorHash] })] }))] }), pendingProof && (_jsxs("div", { className: "rounded-lg p-3 text-xs border border-[var(--tb-accent)] space-y-2", children: [_jsx("div", { className: `uppercase tracking-wider text-[10px] ${muted}`, children: "Confirmation code" }), _jsx("div", { className: "font-mono text-base select-all", children: pendingProof }), _jsxs("div", { className: `leading-relaxed ${muted}`, children: ["The same code appears in the message. ", _jsx("b", { children: "Approve it only if the codes match." }), " If they differ \u2014 or the message points you somewhere other than this site \u2014 do not reply."] })] })), _jsx("button", { type: "button", onClick: () => void finish(), disabled: busy, className: primary, children: busy ? "Checking…" : "I've replied — verify" }), _jsx("button", { type: "button", onClick: () => void begin(), disabled: busy, className: ghost, children: "Resend" }), _jsx("button", { type: "button", onClick: reset, disabled: busy, className: `w-full text-xs py-1.5 ${muted}`, children: "Use a different npub" })] })), error && _jsx(Trouble, { raw: error, appName: appName }), note && _jsx("div", { className: `text-xs text-center italic ${muted}`, children: note })] }), steps && _jsx(SignInSteps, { stage: gateStage(stage, busy), classNames: { ...STEPS_LOOK, ...c.steps } }), links && _jsx(SignInLinks, { credit: linksCredit, classNames: { ...LINKS_LOOK, ...c.links } })] }));
|
|
149
181
|
}
|
|
150
182
|
/** A failure, read to the person: the lead first, the service's own words underneath. */
|
|
151
183
|
function Trouble({ raw, appName }) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two tools a newcomer needs, offered under the sign-in card: a Nostr
|
|
3
|
+
* client and Pricing Studio. Links open in a new tab; the credit line for
|
|
4
|
+
* other companies' marks is on by default and off where the page's own legal
|
|
5
|
+
* footer already carries it.
|
|
6
|
+
*
|
|
7
|
+
* Mechanics only: the list, the targets and the credit are the package's;
|
|
8
|
+
* every class is the site's.
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import { type SignInLink } from "./signInTools.ts";
|
|
12
|
+
export interface SignInLinksClassNames {
|
|
13
|
+
root?: string;
|
|
14
|
+
lead?: string;
|
|
15
|
+
list?: string;
|
|
16
|
+
item?: string;
|
|
17
|
+
link?: string;
|
|
18
|
+
/** The app's mark. */
|
|
19
|
+
logo?: string;
|
|
20
|
+
/** Around the name and line, beside the mark. */
|
|
21
|
+
text?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
line?: string;
|
|
24
|
+
credit?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SignInLinksProps {
|
|
27
|
+
/** Default: 0xchat and Pricing Studio. */
|
|
28
|
+
links?: readonly SignInLink[];
|
|
29
|
+
/** A line above the list. Default "New to Nostr? These two are all it takes." */
|
|
30
|
+
lead?: ReactNode;
|
|
31
|
+
/** The trademark credit. Default true; false when the page's footer carries it. */
|
|
32
|
+
credit?: boolean;
|
|
33
|
+
classNames?: SignInLinksClassNames;
|
|
34
|
+
}
|
|
35
|
+
export default function SignInLinks({ links, lead, credit, classNames: c, }: SignInLinksProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "./cx.js";
|
|
3
|
+
import { SIGN_IN_LINKS, SIGN_IN_LINKS_CREDIT } from "./signInTools.js";
|
|
4
|
+
export default function SignInLinks({ links = SIGN_IN_LINKS, lead = "New to Nostr? These two are all it takes.", credit = true, classNames: c = {}, }) {
|
|
5
|
+
return (_jsxs("nav", { "aria-label": "Tools for signing in", className: c.root, children: [lead && _jsx("p", { className: c.lead, children: lead }), _jsx("ul", { className: c.list, children: links.map((l) => (_jsx("li", { className: c.item, children: _jsxs("a", { href: l.href, target: "_blank", rel: "noopener noreferrer", className: cx(c.link), children: [l.logo && _jsx("img", { src: l.logo, alt: "", width: 40, height: 40, className: c.logo }), _jsxs("span", { className: c.text, children: [_jsx("span", { className: c.name, children: l.name }), _jsx("span", { className: c.line, children: l.line })] })] }) }, l.id))) }), credit && _jsx("p", { className: c.credit, children: SIGN_IN_LINKS_CREDIT })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sequence under the sign-in card: what happens, who does it, and which
|
|
3
|
+
* step is under way now — read from the gate's real stage, never guessed.
|
|
4
|
+
*
|
|
5
|
+
* Mechanics only. It renders an ordered list — meant to run left to right and
|
|
6
|
+
* wrap — with `data-state` on each step (`done` / `current` / `todo`),
|
|
7
|
+
* `aria-current="step"` on the ones under way, a glyph, the label, the actor
|
|
8
|
+
* as a caption, and a `connector` between tiles; every class is the site's
|
|
9
|
+
* through `classNames`. Without them the markup is plain and inherits from
|
|
10
|
+
* the page.
|
|
11
|
+
*/
|
|
12
|
+
import { type SignInActor, type SignInStage, type SignInStep } from "./signInFlow.ts";
|
|
13
|
+
export interface SignInStepsClassNames {
|
|
14
|
+
root?: string;
|
|
15
|
+
step?: string;
|
|
16
|
+
/** Added to a step by its state. */
|
|
17
|
+
done?: string;
|
|
18
|
+
current?: string;
|
|
19
|
+
todo?: string;
|
|
20
|
+
/** The emoji. */
|
|
21
|
+
glyph?: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
/** Who acts, as a caption. */
|
|
24
|
+
actor?: string;
|
|
25
|
+
/** The mark between tiles (not after the last). */
|
|
26
|
+
connector?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SignInStepsProps {
|
|
29
|
+
stage: SignInStage;
|
|
30
|
+
/** Default: the package's nine. */
|
|
31
|
+
steps?: readonly SignInStep[];
|
|
32
|
+
/** Names for the actors. Default: You / This page / The operator / Your Nostr client. */
|
|
33
|
+
actors?: Readonly<Record<SignInActor, string>>;
|
|
34
|
+
/** Accessible name of the list. Default "How signing in works". */
|
|
35
|
+
label?: string;
|
|
36
|
+
classNames?: SignInStepsClassNames;
|
|
37
|
+
}
|
|
38
|
+
export default function SignInSteps({ stage, steps, actors, label, classNames: c, }: SignInStepsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The sequence under the sign-in card: what happens, who does it, and which
|
|
4
|
+
* step is under way now — read from the gate's real stage, never guessed.
|
|
5
|
+
*
|
|
6
|
+
* Mechanics only. It renders an ordered list — meant to run left to right and
|
|
7
|
+
* wrap — with `data-state` on each step (`done` / `current` / `todo`),
|
|
8
|
+
* `aria-current="step"` on the ones under way, a glyph, the label, the actor
|
|
9
|
+
* as a caption, and a `connector` between tiles; every class is the site's
|
|
10
|
+
* through `classNames`. Without them the markup is plain and inherits from
|
|
11
|
+
* the page.
|
|
12
|
+
*/
|
|
13
|
+
import { cx } from "./cx.js";
|
|
14
|
+
import { SIGN_IN_ACTORS, SIGN_IN_STEPS, stepState, } from "./signInFlow.js";
|
|
15
|
+
export default function SignInSteps({ stage, steps = SIGN_IN_STEPS, actors = SIGN_IN_ACTORS, label = "How signing in works", classNames: c = {}, }) {
|
|
16
|
+
return (_jsx("ol", { "aria-label": label, className: c.root, children: steps.map((s, i) => {
|
|
17
|
+
const state = stepState(s, stage);
|
|
18
|
+
return (_jsxs("li", { "data-state": state, "aria-current": state === "current" ? "step" : undefined, className: cx(c.step, c[state]), children: [_jsx("span", { "aria-hidden": true, className: c.glyph, children: s.glyph }), _jsx("span", { className: c.label, children: s.label }), _jsx("span", { className: c.actor, children: actors[s.actor] }), i < steps.length - 1 && (_jsx("span", { "aria-hidden": true, className: c.connector, children: "\u203A" }))] }, s.id));
|
|
19
|
+
}) }));
|
|
20
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -10,7 +10,11 @@ export { default as CouponsPanel, type CouponsPanelProps, type CouponsPanelClass
|
|
|
10
10
|
export { default as DebugPanel, type DebugPanelProps } from "./DebugPanel.tsx";
|
|
11
11
|
export { default as ErrorBoundary, type ErrorBoundaryProps, type ErrorBoundaryClassNames, type ErrorFallbackProps, } from "./ErrorBoundary.tsx";
|
|
12
12
|
export { OperatorFundingStatus, PatronFundingStatus, type FundingStatusClassNames, type FundingStatusProps, type OperatorFundingStatusProps, type PatronFundingStatusProps, } from "./FundingStatus.tsx";
|
|
13
|
-
export { default as NpubGate } from "./NpubGate.tsx";
|
|
13
|
+
export { default as NpubGate, type NpubGateClassNames, type NpubGateProps } from "./NpubGate.tsx";
|
|
14
|
+
export { default as SignInSteps, type SignInStepsClassNames, type SignInStepsProps } from "./SignInSteps.tsx";
|
|
15
|
+
export { gateStage, SIGN_IN_ACTORS, SIGN_IN_STEPS, stepState, type SignInActor, type SignInStage, type SignInStep, type SignInStepState, } from "./signInFlow.ts";
|
|
16
|
+
export { default as SignInLinks, type SignInLinksClassNames, type SignInLinksProps } from "./SignInLinks.tsx";
|
|
17
|
+
export { SIGN_IN_LINKS, SIGN_IN_LINKS_CREDIT, type SignInLink } from "./signInTools.ts";
|
|
14
18
|
export { default as NostrProfilePanel } from "./NostrProfilePanel.tsx";
|
|
15
19
|
export { PageControls, SortHeader, TableShell, type PageControlsProps, type PageControlsClassNames, type SortHeaderProps, type SortHeaderClassNames, type TableShellClassNames, } from "./PagedTable.tsx";
|
|
16
20
|
export { default as RefreshButton, type RefreshButtonClassNames, type RefreshButtonProps, } from "./RefreshButton.tsx";
|
package/dist/react/index.js
CHANGED
|
@@ -10,6 +10,10 @@ export { default as DebugPanel } from "./DebugPanel.js";
|
|
|
10
10
|
export { default as ErrorBoundary, } from "./ErrorBoundary.js";
|
|
11
11
|
export { OperatorFundingStatus, PatronFundingStatus, } from "./FundingStatus.js";
|
|
12
12
|
export { default as NpubGate } from "./NpubGate.js";
|
|
13
|
+
export { default as SignInSteps } from "./SignInSteps.js";
|
|
14
|
+
export { gateStage, SIGN_IN_ACTORS, SIGN_IN_STEPS, stepState, } from "./signInFlow.js";
|
|
15
|
+
export { default as SignInLinks } from "./SignInLinks.js";
|
|
16
|
+
export { SIGN_IN_LINKS, SIGN_IN_LINKS_CREDIT } from "./signInTools.js";
|
|
13
17
|
export { default as NostrProfilePanel } from "./NostrProfilePanel.js";
|
|
14
18
|
export { PageControls, SortHeader, TableShell, } from "./PagedTable.js";
|
|
15
19
|
export { default as RefreshButton, } from "./RefreshButton.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of `SignInSteps` (named apart from it: the filesystem is case-blind): what happens when someone signs in, in the
|
|
3
|
+
* order it happens, and which step is under way for a given gate stage.
|
|
4
|
+
*
|
|
5
|
+
* Nine steps, four actors, one emoji each. The words are the visitor's, not the protocol's:
|
|
6
|
+
* "the operator" (never MCP), "a proof" (never dpop), "this page" (never FE).
|
|
7
|
+
* A site may pass its own list to `SignInSteps`; this one is the default.
|
|
8
|
+
*/
|
|
9
|
+
/** Where the gate stands. `sending` is `begin` with a request in flight. */
|
|
10
|
+
export type SignInStage = "begin" | "sending" | "awaiting" | "checking" | "done";
|
|
11
|
+
export type SignInActor = "you" | "page" | "operator" | "client";
|
|
12
|
+
export interface SignInStep {
|
|
13
|
+
id: string;
|
|
14
|
+
actor: SignInActor;
|
|
15
|
+
/** One emoji that pictures the step. */
|
|
16
|
+
glyph: string;
|
|
17
|
+
/** Two or three words, on the tile. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** The stage during which this step is the one under way. */
|
|
20
|
+
at: SignInStage;
|
|
21
|
+
}
|
|
22
|
+
export type SignInStepState = "done" | "current" | "todo";
|
|
23
|
+
export declare const SIGN_IN_ACTORS: Readonly<Record<SignInActor, string>>;
|
|
24
|
+
export declare const SIGN_IN_STEPS: readonly SignInStep[];
|
|
25
|
+
/** Whether `step` is behind, under way, or ahead of `stage`. */
|
|
26
|
+
export declare function stepState(step: SignInStep, stage: SignInStage): SignInStepState;
|
|
27
|
+
/** The gate's private stage and busy flag, read as one `SignInStage`. */
|
|
28
|
+
export declare function gateStage(stage: "begin" | "awaiting" | "checking", busy: boolean): SignInStage;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of `SignInSteps` (named apart from it: the filesystem is case-blind): what happens when someone signs in, in the
|
|
3
|
+
* order it happens, and which step is under way for a given gate stage.
|
|
4
|
+
*
|
|
5
|
+
* Nine steps, four actors, one emoji each. The words are the visitor's, not the protocol's:
|
|
6
|
+
* "the operator" (never MCP), "a proof" (never dpop), "this page" (never FE).
|
|
7
|
+
* A site may pass its own list to `SignInSteps`; this one is the default.
|
|
8
|
+
*/
|
|
9
|
+
export const SIGN_IN_ACTORS = {
|
|
10
|
+
you: "You",
|
|
11
|
+
page: "This page",
|
|
12
|
+
operator: "The operator",
|
|
13
|
+
client: "Your Nostr client",
|
|
14
|
+
};
|
|
15
|
+
export const SIGN_IN_STEPS = [
|
|
16
|
+
{ id: "enter", actor: "you", glyph: "\u{1F511}", label: "Enter your npub", at: "begin" },
|
|
17
|
+
{ id: "ask", actor: "page", glyph: "\u{1F4E8}", label: "Ask for a proof", at: "sending" },
|
|
18
|
+
{ id: "message", actor: "operator", glyph: "\u{1F4AC}", label: "Message to your client", at: "awaiting" },
|
|
19
|
+
{ id: "approve", actor: "you", glyph: "\u2705", label: "Approve, for a while", at: "awaiting" },
|
|
20
|
+
{ id: "reply", actor: "client", glyph: "\u21A9\uFE0F", label: "Signed reply returns", at: "awaiting" },
|
|
21
|
+
{ id: "verify", actor: "you", glyph: "\u{1F446}", label: "Tap Verify", at: "awaiting" },
|
|
22
|
+
{ id: "check", actor: "operator", glyph: "\u{1F50D}", label: "Signature checked", at: "checking" },
|
|
23
|
+
{ id: "token", actor: "operator", glyph: "\u{1F39F}\uFE0F", label: "Proof handed over", at: "checking" },
|
|
24
|
+
{ id: "unlock", actor: "page", glyph: "\u{1F513}", label: "Unlocked", at: "done" },
|
|
25
|
+
];
|
|
26
|
+
const ORDER = ["begin", "sending", "awaiting", "checking", "done"];
|
|
27
|
+
/** Whether `step` is behind, under way, or ahead of `stage`. */
|
|
28
|
+
export function stepState(step, stage) {
|
|
29
|
+
const now = ORDER.indexOf(stage);
|
|
30
|
+
const at = ORDER.indexOf(step.at);
|
|
31
|
+
if (at < now)
|
|
32
|
+
return "done";
|
|
33
|
+
if (at === now)
|
|
34
|
+
return "current";
|
|
35
|
+
return "todo";
|
|
36
|
+
}
|
|
37
|
+
/** The gate's private stage and busy flag, read as one `SignInStage`. */
|
|
38
|
+
export function gateStage(stage, busy) {
|
|
39
|
+
if (stage === "begin")
|
|
40
|
+
return busy ? "sending" : "begin";
|
|
41
|
+
return stage;
|
|
42
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a visitor gets the two tools the sign-in leans on: a Nostr client that
|
|
3
|
+
* holds their key and answers the message, and Pricing Studio, where the
|
|
4
|
+
* operators of this network price their tools.
|
|
5
|
+
*
|
|
6
|
+
* 0xchat links to its own download page rather than to one store, so the
|
|
7
|
+
* link stays right as its listings move. Pricing Studio's App Store page is
|
|
8
|
+
* public (id 6760925205). Apple's wording rule: an app is *available on the
|
|
9
|
+
* App Store*, never *at*.
|
|
10
|
+
*
|
|
11
|
+
* Each link carries its app's own mark as a 96 px PNG data URI, bundled so the
|
|
12
|
+
* sign-in page makes no third-party request: 0xchat's from its GitHub
|
|
13
|
+
* organisation, Pricing Studio's from its own asset catalog.
|
|
14
|
+
*/
|
|
15
|
+
export interface SignInLink {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
href: string;
|
|
19
|
+
/** One short line under the name. */
|
|
20
|
+
line: string;
|
|
21
|
+
/** The app's own mark, as a data URI (bundled, so no third-party request on sign-in). */
|
|
22
|
+
logo?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const SIGN_IN_LINKS: readonly SignInLink[];
|
|
25
|
+
/** Shown once, small, wherever the links are — unless the page already carries a legal footer. */
|
|
26
|
+
export declare const SIGN_IN_LINKS_CREDIT = "App Store is a trademark of Apple Inc., registered in the U.S. and other countries. 0xchat is a trademark of its owner.";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a visitor gets the two tools the sign-in leans on: a Nostr client that
|
|
3
|
+
* holds their key and answers the message, and Pricing Studio, where the
|
|
4
|
+
* operators of this network price their tools.
|
|
5
|
+
*
|
|
6
|
+
* 0xchat links to its own download page rather than to one store, so the
|
|
7
|
+
* link stays right as its listings move. Pricing Studio's App Store page is
|
|
8
|
+
* public (id 6760925205). Apple's wording rule: an app is *available on the
|
|
9
|
+
* App Store*, never *at*.
|
|
10
|
+
*
|
|
11
|
+
* Each link carries its app's own mark as a 96 px PNG data URI, bundled so the
|
|
12
|
+
* sign-in page makes no third-party request: 0xchat's from its GitHub
|
|
13
|
+
* organisation, Pricing Studio's from its own asset catalog.
|
|
14
|
+
*/
|
|
15
|
+
const OXCHAT_MARK = "data:image/png;base64,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";
|
|
16
|
+
const PRICING_STUDIO_MARK = "data:image/png;base64,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";
|
|
17
|
+
export const SIGN_IN_LINKS = [
|
|
18
|
+
{
|
|
19
|
+
id: "0xchat",
|
|
20
|
+
name: "0xchat",
|
|
21
|
+
href: "https://0xchat.com",
|
|
22
|
+
line: "A Nostr client for iPhone, Android and desktop. It holds your key and answers the sign-in message.",
|
|
23
|
+
logo: OXCHAT_MARK,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "pricing-studio",
|
|
27
|
+
name: "Pricing Studio",
|
|
28
|
+
href: "https://apps.apple.com/us/app/pricing-studio/id6760925205",
|
|
29
|
+
line: "Where operators price their tools, live. Available on the App Store.",
|
|
30
|
+
logo: PRICING_STUDIO_MARK,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
/** Shown once, small, wherever the links are — unless the page already carries a legal footer. */
|
|
34
|
+
export const SIGN_IN_LINKS_CREDIT = "App Store is a trademark of Apple Inc., registered in the U.S. and other countries. 0xchat is a trademark of its owner.";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tollbooth-dpyc/web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Shared browser SDK for Tollbooth DPYC operator front ends: npub sign-in, proofs, MCP client, profile and wallet",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Lonnie VanZandt",
|