@orangecheck/auth-client 0.2.0 → 0.4.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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import * as React2 from 'react';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
 
4
4
  // src/provider.tsx
@@ -20,7 +20,7 @@ function buildSignInUrl(cfg, returnTo) {
20
20
  u.searchParams.set("return_to", returnTo);
21
21
  return u.toString();
22
22
  }
23
- var SessionContext = React.createContext(null);
23
+ var SessionContext = React2.createContext(null);
24
24
  function normalizeAccount(raw) {
25
25
  if (!raw) return null;
26
26
  const address = raw.btc_address ?? raw.address;
@@ -38,11 +38,11 @@ function OcSessionProvider({
38
38
  config,
39
39
  defaultReturnTo
40
40
  }) {
41
- const cfg = React.useMemo(() => resolveConfig(config), [config]);
42
- const [account, setAccount] = React.useState(null);
43
- const [status, setStatus] = React.useState("loading");
44
- const [error, setError] = React.useState(null);
45
- const refresh = React.useCallback(async () => {
41
+ const cfg = React2.useMemo(() => resolveConfig(config), [config]);
42
+ const [account, setAccount] = React2.useState(null);
43
+ const [status, setStatus] = React2.useState("loading");
44
+ const [error, setError] = React2.useState(null);
45
+ const refresh = React2.useCallback(async () => {
46
46
  if (typeof window === "undefined") return;
47
47
  try {
48
48
  const res = await fetch(cfg.mePath, {
@@ -71,10 +71,10 @@ function OcSessionProvider({
71
71
  setError(err instanceof Error ? err : new Error(String(err)));
72
72
  }
73
73
  }, [cfg.mePath]);
74
- React.useEffect(() => {
74
+ React2.useEffect(() => {
75
75
  void refresh();
76
76
  }, [refresh]);
77
- const signOut = React.useCallback(async () => {
77
+ const signOut = React2.useCallback(async () => {
78
78
  try {
79
79
  await fetch(`${cfg.authOrigin}${cfg.logoutPath}`, {
80
80
  method: "POST",
@@ -85,7 +85,7 @@ function OcSessionProvider({
85
85
  setAccount(null);
86
86
  setStatus("anonymous");
87
87
  }, [cfg.authOrigin, cfg.logoutPath]);
88
- const value = React.useMemo(() => {
88
+ const value = React2.useMemo(() => {
89
89
  const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
90
90
  return {
91
91
  status,
@@ -99,7 +99,7 @@ function OcSessionProvider({
99
99
  return /* @__PURE__ */ jsx(SessionContext.Provider, { value, children });
100
100
  }
101
101
  function useOcSession() {
102
- const ctx = React.useContext(SessionContext);
102
+ const ctx = React2.useContext(SessionContext);
103
103
  if (!ctx) {
104
104
  throw new Error(
105
105
  "[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
@@ -108,7 +108,7 @@ function useOcSession() {
108
108
  return ctx;
109
109
  }
110
110
  function useOptionalOcSession() {
111
- return React.useContext(SessionContext);
111
+ return React2.useContext(SessionContext);
112
112
  }
113
113
  function shortenAddress(addr) {
114
114
  if (addr.length <= 12) return addr;
@@ -123,9 +123,243 @@ function isPrefixOf(value, target) {
123
123
  }
124
124
  var listboxIdCounter = 0;
125
125
  function useUniqueId(prefix) {
126
- const [id] = React.useState(() => `${prefix}-${++listboxIdCounter}`);
126
+ const [id] = React2.useState(() => `${prefix}-${++listboxIdCounter}`);
127
127
  return id;
128
128
  }
129
+ function OcAccountChip({
130
+ dashboardUrl = "https://ochk.io/dashboard",
131
+ signInLabel = "sign in",
132
+ className,
133
+ triggerClassName,
134
+ popoverClassName,
135
+ menuItemClassName
136
+ }) {
137
+ const { status, account, signInUrl, signOut } = useOcSession();
138
+ const [open, setOpen] = React2.useState(false);
139
+ const wrapRef = React2.useRef(null);
140
+ React2.useEffect(() => {
141
+ if (!open) return;
142
+ function handleClickOutside(e) {
143
+ if (wrapRef.current && !wrapRef.current.contains(e.target)) {
144
+ setOpen(false);
145
+ }
146
+ }
147
+ function handleEscape(e) {
148
+ if (e.key === "Escape") setOpen(false);
149
+ }
150
+ document.addEventListener("mousedown", handleClickOutside);
151
+ document.addEventListener("keydown", handleEscape);
152
+ return () => {
153
+ document.removeEventListener("mousedown", handleClickOutside);
154
+ document.removeEventListener("keydown", handleEscape);
155
+ };
156
+ }, [open]);
157
+ if (status === "loading") return null;
158
+ if (status !== "authenticated" || !account) {
159
+ return /* @__PURE__ */ jsx(
160
+ "a",
161
+ {
162
+ href: signInUrl,
163
+ className: triggerClassName ?? className,
164
+ "data-oc-account-chip-signin": "",
165
+ children: signInLabel
166
+ }
167
+ );
168
+ }
169
+ return /* @__PURE__ */ jsxs(
170
+ "div",
171
+ {
172
+ ref: wrapRef,
173
+ className,
174
+ "data-oc-account-chip": "",
175
+ style: { position: "relative", display: "inline-block" },
176
+ children: [
177
+ /* @__PURE__ */ jsxs(
178
+ "button",
179
+ {
180
+ type: "button",
181
+ onClick: () => setOpen((v) => !v),
182
+ "aria-haspopup": "menu",
183
+ "aria-expanded": open,
184
+ "aria-label": `Signed in as ${account.address}. Open account menu.`,
185
+ className: triggerClassName,
186
+ "data-oc-account-chip-trigger": "",
187
+ style: triggerClassName ? void 0 : {
188
+ display: "inline-flex",
189
+ alignItems: "center",
190
+ gap: "0.4rem",
191
+ padding: "0.25rem 0.625rem",
192
+ borderRadius: "9999px",
193
+ border: "1px solid #27272a",
194
+ background: "rgba(39, 39, 42, 0.4)",
195
+ color: "#fafafa",
196
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
197
+ fontSize: 12,
198
+ cursor: "pointer"
199
+ },
200
+ children: [
201
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: "#22c55e", fontSize: 8 }, children: "\u25CF" }),
202
+ /* @__PURE__ */ jsx("span", { children: shortenAddress(account.address) }),
203
+ /* @__PURE__ */ jsx(
204
+ "svg",
205
+ {
206
+ width: "9",
207
+ height: "9",
208
+ viewBox: "0 0 10 10",
209
+ "aria-hidden": "true",
210
+ style: {
211
+ transition: "transform 120ms",
212
+ transform: open ? "rotate(180deg)" : "rotate(0)",
213
+ opacity: 0.6
214
+ },
215
+ children: /* @__PURE__ */ jsx(
216
+ "path",
217
+ {
218
+ d: "M2 4 L5 7 L8 4",
219
+ stroke: "currentColor",
220
+ strokeWidth: "1.5",
221
+ fill: "none",
222
+ strokeLinecap: "round"
223
+ }
224
+ )
225
+ }
226
+ )
227
+ ]
228
+ }
229
+ ),
230
+ open && /* @__PURE__ */ jsxs(
231
+ "div",
232
+ {
233
+ role: "menu",
234
+ "aria-label": "Account menu",
235
+ className: popoverClassName,
236
+ "data-oc-account-chip-popover": "",
237
+ style: popoverClassName ? { position: "absolute", zIndex: 50, top: "calc(100% + 6px)", right: 0 } : {
238
+ position: "absolute",
239
+ zIndex: 50,
240
+ top: "calc(100% + 6px)",
241
+ right: 0,
242
+ minWidth: 240,
243
+ background: "#0a0a0a",
244
+ border: "1px solid #27272a",
245
+ boxShadow: "0 10px 32px -4px rgba(0,0,0,0.6)",
246
+ fontFamily: "ui-monospace, SFMono-Regular, monospace"
247
+ },
248
+ children: [
249
+ /* @__PURE__ */ jsxs(
250
+ "div",
251
+ {
252
+ "data-oc-account-chip-header": "",
253
+ style: { padding: "0.75rem 0.75rem 0.625rem", borderBottom: "1px solid #27272a" },
254
+ children: [
255
+ /* @__PURE__ */ jsx(
256
+ "div",
257
+ {
258
+ "data-oc-account-chip-label": "",
259
+ style: {
260
+ color: "#f97316",
261
+ fontSize: 10,
262
+ letterSpacing: "0.18em",
263
+ textTransform: "uppercase",
264
+ marginBottom: 4
265
+ },
266
+ children: "\xA7 signed in"
267
+ }
268
+ ),
269
+ /* @__PURE__ */ jsx(
270
+ "div",
271
+ {
272
+ "data-oc-account-chip-address": "",
273
+ style: {
274
+ color: "#fafafa",
275
+ fontSize: 11,
276
+ wordBreak: "break-all",
277
+ lineHeight: 1.35
278
+ },
279
+ children: account.address
280
+ }
281
+ ),
282
+ account.displayName ? /* @__PURE__ */ jsx(
283
+ "div",
284
+ {
285
+ style: {
286
+ color: "#a1a1aa",
287
+ fontSize: 10,
288
+ marginTop: 4,
289
+ letterSpacing: "0.06em"
290
+ },
291
+ children: account.displayName
292
+ }
293
+ ) : null
294
+ ]
295
+ }
296
+ ),
297
+ /* @__PURE__ */ jsxs("div", { role: "none", style: { padding: "0.25rem" }, children: [
298
+ dashboardUrl ? /* @__PURE__ */ jsxs(
299
+ "a",
300
+ {
301
+ role: "menuitem",
302
+ href: dashboardUrl,
303
+ onClick: () => setOpen(false),
304
+ className: menuItemClassName,
305
+ "data-oc-account-chip-item": "",
306
+ style: menuItemClassName ? void 0 : {
307
+ display: "flex",
308
+ alignItems: "center",
309
+ gap: "0.5rem",
310
+ padding: "0.5rem 0.625rem",
311
+ fontSize: 12,
312
+ color: "#fafafa",
313
+ textDecoration: "none",
314
+ cursor: "pointer"
315
+ },
316
+ children: [
317
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: "#a1a1aa" }, children: "\u2192" }),
318
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: "dashboard" }),
319
+ /* @__PURE__ */ jsx("span", { style: { color: "#a1a1aa", fontSize: 10 }, children: "\u2197" })
320
+ ]
321
+ }
322
+ ) : null,
323
+ /* @__PURE__ */ jsxs(
324
+ "button",
325
+ {
326
+ type: "button",
327
+ role: "menuitem",
328
+ onClick: () => {
329
+ setOpen(false);
330
+ void signOut();
331
+ },
332
+ className: menuItemClassName,
333
+ "data-oc-account-chip-item": "",
334
+ "data-oc-account-chip-signout": "",
335
+ style: menuItemClassName ? void 0 : {
336
+ display: "flex",
337
+ alignItems: "center",
338
+ gap: "0.5rem",
339
+ width: "100%",
340
+ padding: "0.5rem 0.625rem",
341
+ fontSize: 12,
342
+ color: "#fafafa",
343
+ background: "transparent",
344
+ border: 0,
345
+ fontFamily: "inherit",
346
+ textAlign: "left",
347
+ cursor: "pointer"
348
+ },
349
+ children: [
350
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: "#a1a1aa" }, children: "\u2192" }),
351
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: "sign out" })
352
+ ]
353
+ }
354
+ )
355
+ ] })
356
+ ]
357
+ }
358
+ )
359
+ ]
360
+ }
361
+ );
362
+ }
129
363
  function OcSignInButton({
130
364
  label = "sign in with bitcoin",
131
365
  eager = false,
@@ -187,7 +421,134 @@ function OcAccountPill({
187
421
  }
188
422
  );
189
423
  }
190
- var OcAddressInput = React.forwardRef(
424
+ function useOcAddressSuggestion(options) {
425
+ const {
426
+ value,
427
+ onValueChange,
428
+ suggestionLabel = "use your address",
429
+ popoverClassName,
430
+ suggestionClassName
431
+ } = options;
432
+ const { status, account } = useOcSession();
433
+ const sessionAddress = status === "authenticated" ? account?.address ?? null : null;
434
+ const [open, setOpen] = React2.useState(false);
435
+ const [highlighted, setHighlighted] = React2.useState(false);
436
+ const blurTimer = React2.useRef(null);
437
+ const listboxId = useUniqueId("oc-addr-listbox");
438
+ const optionId = `${listboxId}-opt`;
439
+ const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
440
+ const canSuggest = sessionAddress != null && sessionAddress.length > 0 && !valueMatchesSession && isPrefixOf(value, sessionAddress);
441
+ const showPopover = open && canSuggest;
442
+ function selectSuggestion() {
443
+ if (!sessionAddress) return;
444
+ onValueChange(sessionAddress);
445
+ setOpen(false);
446
+ setHighlighted(false);
447
+ }
448
+ React2.useEffect(() => {
449
+ return () => {
450
+ if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
451
+ };
452
+ }, []);
453
+ const inputProps = {
454
+ onFocus: () => {
455
+ if (blurTimer.current != null) {
456
+ window.clearTimeout(blurTimer.current);
457
+ blurTimer.current = null;
458
+ }
459
+ setOpen(true);
460
+ },
461
+ onBlur: () => {
462
+ blurTimer.current = window.setTimeout(() => {
463
+ setOpen(false);
464
+ setHighlighted(false);
465
+ }, 120);
466
+ },
467
+ onKeyDown: (e) => {
468
+ if (!showPopover) return;
469
+ if (e.key === "ArrowDown") {
470
+ e.preventDefault();
471
+ setHighlighted(true);
472
+ } else if (e.key === "ArrowUp") {
473
+ e.preventDefault();
474
+ setHighlighted(false);
475
+ } else if (e.key === "Escape") {
476
+ e.preventDefault();
477
+ setOpen(false);
478
+ setHighlighted(false);
479
+ } else if (e.key === "Enter" && highlighted) {
480
+ e.preventDefault();
481
+ selectSuggestion();
482
+ }
483
+ },
484
+ role: "combobox",
485
+ "aria-haspopup": "listbox",
486
+ "aria-expanded": showPopover,
487
+ "aria-controls": showPopover ? listboxId : void 0,
488
+ "aria-activedescendant": highlighted ? optionId : void 0,
489
+ autoComplete: "off",
490
+ spellCheck: false
491
+ };
492
+ const popover = showPopover && sessionAddress ? /* @__PURE__ */ jsx(
493
+ "div",
494
+ {
495
+ id: listboxId,
496
+ role: "listbox",
497
+ "data-oc-address-popover": "",
498
+ className: popoverClassName,
499
+ style: {
500
+ position: "absolute",
501
+ zIndex: 50,
502
+ top: "calc(100% + 4px)",
503
+ left: 0,
504
+ right: 0
505
+ },
506
+ onMouseDown: (e) => e.preventDefault(),
507
+ children: /* @__PURE__ */ jsxs(
508
+ "button",
509
+ {
510
+ type: "button",
511
+ id: optionId,
512
+ role: "option",
513
+ "aria-selected": highlighted,
514
+ "data-oc-address-suggestion": "",
515
+ "data-highlighted": highlighted ? "" : void 0,
516
+ className: suggestionClassName,
517
+ onClick: selectSuggestion,
518
+ onMouseEnter: () => setHighlighted(true),
519
+ onMouseLeave: () => setHighlighted(false),
520
+ style: {
521
+ display: "flex",
522
+ alignItems: "center",
523
+ justifyContent: "space-between",
524
+ gap: "0.75rem",
525
+ width: "100%",
526
+ textAlign: "left",
527
+ cursor: "pointer",
528
+ font: "inherit",
529
+ background: "inherit",
530
+ color: "inherit",
531
+ border: 0,
532
+ padding: "inherit"
533
+ },
534
+ children: [
535
+ /* @__PURE__ */ jsx("span", { "data-oc-address-suggestion-label": "", children: suggestionLabel }),
536
+ /* @__PURE__ */ jsx(
537
+ "span",
538
+ {
539
+ "data-oc-address-suggestion-value": "",
540
+ style: { fontFamily: "ui-monospace, monospace" },
541
+ children: shortenAddressMid(sessionAddress)
542
+ }
543
+ )
544
+ ]
545
+ }
546
+ )
547
+ }
548
+ ) : null;
549
+ return { inputProps, popover };
550
+ }
551
+ var OcAddressInput = React2.forwardRef(
191
552
  function OcAddressInput2({
192
553
  value,
193
554
  onValueChange,
@@ -202,15 +563,15 @@ var OcAddressInput = React.forwardRef(
202
563
  }, forwardedRef) {
203
564
  const { status, account } = useOcSession();
204
565
  const sessionAddress = status === "authenticated" ? account?.address ?? null : null;
205
- const [open, setOpen] = React.useState(false);
206
- const [highlighted, setHighlighted] = React.useState(false);
207
- const innerRef = React.useRef(null);
566
+ const [open, setOpen] = React2.useState(false);
567
+ const [highlighted, setHighlighted] = React2.useState(false);
568
+ const innerRef = React2.useRef(null);
208
569
  const setRef = (node) => {
209
570
  innerRef.current = node;
210
571
  if (typeof forwardedRef === "function") forwardedRef(node);
211
572
  else if (forwardedRef) forwardedRef.current = node;
212
573
  };
213
- const blurTimer = React.useRef(null);
574
+ const blurTimer = React2.useRef(null);
214
575
  const listboxId = useUniqueId("oc-addr-listbox");
215
576
  const optionId = `${listboxId}-opt`;
216
577
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -257,7 +618,7 @@ var OcAddressInput = React.forwardRef(
257
618
  }
258
619
  onKeyDown?.(e);
259
620
  }
260
- React.useEffect(() => {
621
+ React2.useEffect(() => {
261
622
  return () => {
262
623
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
263
624
  };
@@ -351,6 +712,6 @@ var OcAddressInput = React.forwardRef(
351
712
  }
352
713
  );
353
714
 
354
- export { DEFAULT_CONFIG, OcAccountPill, OcAddressInput, OcSessionProvider, OcSignInButton, buildSignInUrl, useOcSession, useOptionalOcSession };
715
+ export { DEFAULT_CONFIG, OcAccountChip, OcAccountPill, OcAddressInput, OcSessionProvider, OcSignInButton, buildSignInUrl, useOcAddressSuggestion, useOcSession, useOptionalOcSession };
355
716
  //# sourceMappingURL=index.mjs.map
356
717
  //# sourceMappingURL=index.mjs.map