@rockerone/xprnkit 0.3.3 → 0.3.4
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/build/components/identity/index.d.ts +2 -0
- package/build/components/identity/index.js +2 -0
- package/build/components/identity/xprn-account-list.d.ts +48 -0
- package/build/components/identity/xprn-account-list.js +106 -0
- package/build/components/identity/xprn-identity-proof-gate.d.ts +67 -0
- package/build/components/identity/xprn-identity-proof-gate.js +77 -0
- package/build/components/identity/xprn-identity.d.ts +4 -0
- package/build/components/identity/xprn-identity.js +5 -4
- package/build/components/identity/xprn-session-name.d.ts +23 -2
- package/build/components/identity/xprn-session-name.js +48 -3
- package/build/components/ui/dropdown.d.ts +3 -1
- package/build/components/ui/dropdown.js +4 -1
- package/build/components/xprn-transaction.js +3 -1
- package/build/global.css +118 -0
- package/build/providers/XPRNProvider.d.ts +60 -26
- package/build/providers/XPRNProvider.js +324 -267
- package/build/services/identity-proof/create-identity-proof.js +1 -0
- package/build/services/identity-proof/index.d.ts +5 -1
- package/build/services/identity-proof/index.js +3 -0
- package/build/services/identity-proof/token-utils.d.ts +48 -0
- package/build/services/identity-proof/token-utils.js +85 -0
- package/build/services/identity-proof/types.d.ts +25 -2
- package/build/services/identity-proof/use-identity-proof.js +5 -3
- package/build/services/identity-proof/validate-identity-proof.d.ts +51 -0
- package/build/services/identity-proof/validate-identity-proof.js +93 -0
- package/build/services/identity-proof/verify-identity-proof.d.ts +4 -4
- package/build/services/identity-proof/verify-identity-proof.js +15 -3
- package/build/utils/auth-storage.d.ts +126 -0
- package/build/utils/auth-storage.js +216 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.js +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type SessionStorageEntry } from "../../utils/auth-storage";
|
|
3
|
+
type XPRNAccountListProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
/** Show remove button for each account */
|
|
5
|
+
showRemove?: boolean;
|
|
6
|
+
/** Show add account button */
|
|
7
|
+
showAddAccount?: boolean;
|
|
8
|
+
/** Custom label for add account button */
|
|
9
|
+
addAccountLabel?: string;
|
|
10
|
+
/** Class name for add account button */
|
|
11
|
+
addAccountClassName?: string;
|
|
12
|
+
/** Custom render for each account item */
|
|
13
|
+
renderItem?: (props: AccountItemRenderProps) => React.ReactNode;
|
|
14
|
+
/** Called when an account is selected */
|
|
15
|
+
onSelect?: (entry: SessionStorageEntry) => void;
|
|
16
|
+
/** Called when an account is removed */
|
|
17
|
+
onRemove?: (entry: SessionStorageEntry) => void;
|
|
18
|
+
/** Called when add account is clicked */
|
|
19
|
+
onAddAccount?: () => void;
|
|
20
|
+
/** Class name for account items */
|
|
21
|
+
itemClassName?: string;
|
|
22
|
+
/** Class name for active account item */
|
|
23
|
+
activeClassName?: string;
|
|
24
|
+
};
|
|
25
|
+
type AccountItemRenderProps = {
|
|
26
|
+
entry: SessionStorageEntry;
|
|
27
|
+
isActive: boolean;
|
|
28
|
+
onSelect: () => void;
|
|
29
|
+
onRemove?: () => void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Component that lists all stored accounts and allows switching between them
|
|
33
|
+
*/
|
|
34
|
+
export declare const XPRNAccountList: React.FunctionComponent<XPRNAccountListProps>;
|
|
35
|
+
type XPRNAccountItemProps = {
|
|
36
|
+
entry: SessionStorageEntry;
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
isSwitching?: boolean;
|
|
39
|
+
onSelect: () => void;
|
|
40
|
+
onRemove?: () => void;
|
|
41
|
+
className?: string;
|
|
42
|
+
activeClassName?: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Single account item in the list
|
|
46
|
+
*/
|
|
47
|
+
export declare const XPRNAccountItem: React.FunctionComponent<XPRNAccountItemProps>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
import { useXPRN } from "../../providers/XPRNProvider";
|
|
6
|
+
import { sessionStorage } from "../../utils/auth-storage";
|
|
7
|
+
import { XPRNAvatarWrapper, XPRNAvatarImage, XPRNAvatarFallback } from "./xprn-avatar";
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
|
+
/**
|
|
10
|
+
* Component that lists all stored accounts and allows switching between them
|
|
11
|
+
*/
|
|
12
|
+
export const XPRNAccountList = ({ className, showRemove = false, showAddAccount = false, addAccountLabel = "Add Account", addAccountClassName, renderItem, onSelect, onRemove, onAddAccount, itemClassName, activeClassName, ...props }) => {
|
|
13
|
+
const { config, session, switchToSession, disconnect, connect } = useXPRN();
|
|
14
|
+
const [entries, setEntries] = React.useState([]);
|
|
15
|
+
const [switching, setSwitching] = React.useState(null);
|
|
16
|
+
const dAppName = config?.requesterAccount ?? '';
|
|
17
|
+
// Load entries on mount and when session changes
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
if (typeof window === 'undefined' || !dAppName)
|
|
20
|
+
return;
|
|
21
|
+
const storedEntries = sessionStorage.list(dAppName);
|
|
22
|
+
setEntries(storedEntries);
|
|
23
|
+
}, [session, dAppName]);
|
|
24
|
+
const currentAuth = session ? {
|
|
25
|
+
actor: session.auth.actor.toString(),
|
|
26
|
+
permission: session.auth.permission.toString(),
|
|
27
|
+
} : null;
|
|
28
|
+
const currentChainId = session?.chainId?.toString();
|
|
29
|
+
const isActive = (entry) => {
|
|
30
|
+
if (!currentAuth || !currentChainId)
|
|
31
|
+
return false;
|
|
32
|
+
return (entry.auth.actor === currentAuth.actor &&
|
|
33
|
+
entry.auth.permission === currentAuth.permission &&
|
|
34
|
+
entry.chainId === currentChainId);
|
|
35
|
+
};
|
|
36
|
+
const handleSelect = async (entry) => {
|
|
37
|
+
if (isActive(entry))
|
|
38
|
+
return;
|
|
39
|
+
const authString = `${entry.auth.actor}@${entry.auth.permission}`;
|
|
40
|
+
setSwitching(authString);
|
|
41
|
+
try {
|
|
42
|
+
await switchToSession(authString, entry.chainId);
|
|
43
|
+
onSelect?.(entry);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('Failed to switch session:', error);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
setSwitching(null);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const handleRemove = async (entry) => {
|
|
53
|
+
if (!dAppName)
|
|
54
|
+
return;
|
|
55
|
+
// If removing active session, disconnect first
|
|
56
|
+
if (isActive(entry)) {
|
|
57
|
+
await disconnect();
|
|
58
|
+
}
|
|
59
|
+
// Remove from storage
|
|
60
|
+
sessionStorage.remove(dAppName, entry.auth, entry.chainId);
|
|
61
|
+
// Update local state
|
|
62
|
+
setEntries(prev => prev.filter(e => !(e.auth.actor === entry.auth.actor &&
|
|
63
|
+
e.auth.permission === entry.auth.permission &&
|
|
64
|
+
e.chainId === entry.chainId)));
|
|
65
|
+
onRemove?.(entry);
|
|
66
|
+
};
|
|
67
|
+
const handleAddAccount = () => {
|
|
68
|
+
connect(false);
|
|
69
|
+
onAddAccount?.();
|
|
70
|
+
};
|
|
71
|
+
const rootClasses = classNames({
|
|
72
|
+
"flex flex-col gap-2 bg-black p-2 rounded-lg": true,
|
|
73
|
+
[`${className}`]: className,
|
|
74
|
+
});
|
|
75
|
+
// Show add account button even if no entries
|
|
76
|
+
if (entries.length === 0 && !showAddAccount) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const addButtonClasses = cn("flex items-center justify-center gap-2 p-2 rounded-lg cursor-pointer transition-colors", "hover:bg-gray-800 text-gray-400", addAccountClassName);
|
|
80
|
+
return (_jsxs("div", { className: rootClasses, ...props, children: [entries.map((entry) => {
|
|
81
|
+
const active = isActive(entry);
|
|
82
|
+
const authString = `${entry.auth.actor}@${entry.auth.permission}`;
|
|
83
|
+
const isSwitching = switching === authString;
|
|
84
|
+
if (renderItem) {
|
|
85
|
+
return (_jsx(React.Fragment, { children: renderItem({
|
|
86
|
+
entry,
|
|
87
|
+
isActive: active,
|
|
88
|
+
onSelect: () => handleSelect(entry),
|
|
89
|
+
onRemove: showRemove ? () => handleRemove(entry) : undefined,
|
|
90
|
+
}) }, authString));
|
|
91
|
+
}
|
|
92
|
+
return (_jsx(XPRNAccountItem, { entry: entry, isActive: active, isSwitching: isSwitching, onSelect: () => handleSelect(entry), onRemove: showRemove ? () => handleRemove(entry) : undefined, className: itemClassName, activeClassName: activeClassName }, authString));
|
|
93
|
+
}), showAddAccount && (_jsxs("button", { type: "button", className: addButtonClasses, onClick: handleAddAccount, children: [_jsxs("svg", { className: "h-5 w-5", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M12 5v14" }), _jsx("path", { d: "M5 12h14" })] }), addAccountLabel] }))] }));
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Single account item in the list
|
|
97
|
+
*/
|
|
98
|
+
export const XPRNAccountItem = ({ entry, isActive, isSwitching, onSelect, onRemove, className, activeClassName, }) => {
|
|
99
|
+
const itemClasses = cn("flex items-center gap-3 p-2 rounded-lg cursor-pointer transition-colors text-white", "hover:bg-gray-800", isActive && "bg-gray-900", isActive && activeClassName, className);
|
|
100
|
+
return (_jsxs("div", { className: itemClasses, onClick: onSelect, children: [_jsxs(XPRNAvatarWrapper, { className: "w-10 h-10 shrink-0", children: [_jsx(XPRNAvatarFallback, { children: entry.auth.actor[0]?.toUpperCase() }), entry.profile?.avatar && (_jsx(XPRNAvatarImage, { src: `data:image/png;base64,${entry.profile.avatar}` }))] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-1", children: [entry.profile?.displayName || entry.auth.actor, entry.profile?.isKyc && (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-gray-300 shrink-0", children: [_jsx("path", { d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" }), _jsx("path", { d: "m9 12 2 2 4-4" })] })), entry.identityProofToken && (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-white shrink-0", children: [_jsx("path", { d: "M16 10h2" }), _jsx("path", { d: "M16 14h2" }), _jsx("path", { d: "M6.17 15a3 3 0 0 1 5.66 0" }), _jsx("circle", { cx: "9", cy: "11", r: "2" }), _jsx("rect", { x: "2", y: "5", width: "20", height: "14", rx: "2" })] }))] }), _jsxs("div", { className: "text-sm text-gray-300 truncate", children: [entry.auth.actor, "@", entry.auth.permission] })] }), isSwitching && (_jsxs("svg", { className: "animate-spin h-5 w-5 text-gray-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] })), isActive && !isSwitching && (_jsx("svg", { className: "h-5 w-5 text-green-500", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })), onRemove && !isActive && (_jsx("button", { onClick: (e) => {
|
|
101
|
+
e.stopPropagation();
|
|
102
|
+
onRemove();
|
|
103
|
+
}, className: "p-1 hover:bg-gray-700 rounded", title: "Remove account", children: _jsxs("svg", { className: "h-4 w-4 text-gray-400", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] }) }))] }));
|
|
104
|
+
};
|
|
105
|
+
XPRNAccountList.displayName = "XPRNAccountList";
|
|
106
|
+
XPRNAccountItem.displayName = "XPRNAccountItem";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type XPRNIdentityProofGateProps = {
|
|
3
|
+
/**
|
|
4
|
+
* Content to render when identity proof is obtained
|
|
5
|
+
*/
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Content to render when identity proof is required but not yet obtained.
|
|
9
|
+
* If not provided, renders nothing.
|
|
10
|
+
*/
|
|
11
|
+
fallback?: React.ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Content to render while identity proof is in progress (signing/verifying).
|
|
14
|
+
* If not provided, uses fallback.
|
|
15
|
+
*/
|
|
16
|
+
loading?: React.ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Content to render when identity proof failed.
|
|
19
|
+
* If not provided, uses fallback.
|
|
20
|
+
*/
|
|
21
|
+
error?: React.ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Content to render when no session is connected.
|
|
24
|
+
* If not provided, uses fallback.
|
|
25
|
+
*/
|
|
26
|
+
notConnected?: React.ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* If true, only gates when identityProof.required is true in config.
|
|
29
|
+
* If false (default), gates whenever identity proof is configured.
|
|
30
|
+
*/
|
|
31
|
+
onlyWhenRequired?: boolean;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Conditionally renders children based on identity proof status.
|
|
35
|
+
*
|
|
36
|
+
* Use this component to gate access to features that require identity proof.
|
|
37
|
+
* When identity proof is not obtained, shows fallback content (or nothing).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* <XPRNIdentityProofGate
|
|
42
|
+
* fallback={<button onClick={() => requestIdentityProof(...)}>Verify Identity</button>}
|
|
43
|
+
* loading={<span>Verifying...</span>}
|
|
44
|
+
* >
|
|
45
|
+
* <ProtectedFeature />
|
|
46
|
+
* </XPRNIdentityProofGate>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare const XPRNIdentityProofGate: React.FunctionComponent<XPRNIdentityProofGateProps>;
|
|
50
|
+
/**
|
|
51
|
+
* Hook to check identity proof gate status
|
|
52
|
+
* Returns the same information used by XPRNIdentityProofGate
|
|
53
|
+
*/
|
|
54
|
+
export declare function useIdentityProofGate(options?: {
|
|
55
|
+
onlyWhenRequired?: boolean;
|
|
56
|
+
}): {
|
|
57
|
+
isGateActive: boolean;
|
|
58
|
+
isConnected: boolean;
|
|
59
|
+
isInProgress: boolean;
|
|
60
|
+
hasError: boolean;
|
|
61
|
+
isVerified: boolean;
|
|
62
|
+
shouldRenderChildren: boolean;
|
|
63
|
+
needsIdentityProof: boolean;
|
|
64
|
+
requestIdentityProof: (success: (res: import("../../providers/XPRNProvider").XPRNIdentityProof) => void, fail: (e: any) => void) => void;
|
|
65
|
+
identityProofStatus: import("../..").IdentityProofStatus;
|
|
66
|
+
};
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useXPRN } from "../../providers/XPRNProvider";
|
|
4
|
+
/**
|
|
5
|
+
* Conditionally renders children based on identity proof status.
|
|
6
|
+
*
|
|
7
|
+
* Use this component to gate access to features that require identity proof.
|
|
8
|
+
* When identity proof is not obtained, shows fallback content (or nothing).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <XPRNIdentityProofGate
|
|
13
|
+
* fallback={<button onClick={() => requestIdentityProof(...)}>Verify Identity</button>}
|
|
14
|
+
* loading={<span>Verifying...</span>}
|
|
15
|
+
* >
|
|
16
|
+
* <ProtectedFeature />
|
|
17
|
+
* </XPRNIdentityProofGate>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const XPRNIdentityProofGate = ({ children, fallback = null, loading, error, notConnected, onlyWhenRequired = false, }) => {
|
|
21
|
+
const { session, identityProof, identityProofStatus, isIdentityProofEnabled, isIdentityProofRequired, } = useXPRN();
|
|
22
|
+
// If identity proof is not enabled, render children directly
|
|
23
|
+
if (!isIdentityProofEnabled) {
|
|
24
|
+
return _jsx(_Fragment, { children: children });
|
|
25
|
+
}
|
|
26
|
+
// If onlyWhenRequired is true and identity proof is not required, render children
|
|
27
|
+
if (onlyWhenRequired && !isIdentityProofRequired) {
|
|
28
|
+
return _jsx(_Fragment, { children: children });
|
|
29
|
+
}
|
|
30
|
+
// No session connected
|
|
31
|
+
if (!session) {
|
|
32
|
+
return _jsx(_Fragment, { children: notConnected ?? fallback });
|
|
33
|
+
}
|
|
34
|
+
// Identity proof in progress
|
|
35
|
+
if (identityProofStatus === "signing" || identityProofStatus === "verifying" || identityProofStatus === "validating") {
|
|
36
|
+
return _jsx(_Fragment, { children: loading ?? fallback });
|
|
37
|
+
}
|
|
38
|
+
// Identity proof failed
|
|
39
|
+
if (identityProofStatus === "error" || identityProofStatus === "expired") {
|
|
40
|
+
return _jsx(_Fragment, { children: error ?? fallback });
|
|
41
|
+
}
|
|
42
|
+
// Identity proof obtained
|
|
43
|
+
if (identityProof) {
|
|
44
|
+
return _jsx(_Fragment, { children: children });
|
|
45
|
+
}
|
|
46
|
+
// Identity proof needed but not obtained
|
|
47
|
+
return _jsx(_Fragment, { children: fallback });
|
|
48
|
+
};
|
|
49
|
+
XPRNIdentityProofGate.displayName = "XPRNIdentityProofGate";
|
|
50
|
+
/**
|
|
51
|
+
* Hook to check identity proof gate status
|
|
52
|
+
* Returns the same information used by XPRNIdentityProofGate
|
|
53
|
+
*/
|
|
54
|
+
export function useIdentityProofGate(options) {
|
|
55
|
+
const { session, identityProof, identityProofStatus, isIdentityProofEnabled, isIdentityProofRequired, needsIdentityProof, requestIdentityProof, } = useXPRN();
|
|
56
|
+
const onlyWhenRequired = options?.onlyWhenRequired ?? false;
|
|
57
|
+
// Determine if gating should be active
|
|
58
|
+
const isGateActive = isIdentityProofEnabled && (!onlyWhenRequired || isIdentityProofRequired);
|
|
59
|
+
// Determine current state
|
|
60
|
+
const isConnected = session !== null;
|
|
61
|
+
const isInProgress = identityProofStatus === "signing" || identityProofStatus === "verifying" || identityProofStatus === "validating";
|
|
62
|
+
const hasError = identityProofStatus === "error" || identityProofStatus === "expired";
|
|
63
|
+
const isVerified = identityProof !== null;
|
|
64
|
+
// Should render protected content?
|
|
65
|
+
const shouldRenderChildren = !isGateActive || isVerified;
|
|
66
|
+
return {
|
|
67
|
+
isGateActive,
|
|
68
|
+
isConnected,
|
|
69
|
+
isInProgress,
|
|
70
|
+
hasError,
|
|
71
|
+
isVerified,
|
|
72
|
+
shouldRenderChildren,
|
|
73
|
+
needsIdentityProof: isGateActive && needsIdentityProof,
|
|
74
|
+
requestIdentityProof,
|
|
75
|
+
identityProofStatus,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -4,6 +4,10 @@ type XPRNIdentityProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
4
4
|
activeSessionClassName?: string;
|
|
5
5
|
dropdownClassName?: string;
|
|
6
6
|
avatarClassName?: string;
|
|
7
|
+
/** Match dropdown width to trigger width */
|
|
8
|
+
matchDropdownWidth?: boolean;
|
|
9
|
+
/** Close dropdown when children are clicked */
|
|
10
|
+
closeOnSelect?: boolean;
|
|
7
11
|
};
|
|
8
12
|
export declare const XPRNIdentity: React.FunctionComponent<XPRNIdentityProps>;
|
|
9
13
|
export {};
|
|
@@ -8,7 +8,8 @@ import { XPRNSessionName } from "./xprn-session-name";
|
|
|
8
8
|
import { useXPRN } from "../../providers/XPRNProvider";
|
|
9
9
|
import { XPRNConnectButton } from "./../xprn-session";
|
|
10
10
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "./../ui/dropdown";
|
|
11
|
-
export const XPRNIdentity = ({ children, className, showLogout, activeSessionClassName, dropdownClassName, avatarClassName, }) => {
|
|
11
|
+
export const XPRNIdentity = ({ children, className, showLogout, activeSessionClassName, dropdownClassName, avatarClassName, matchDropdownWidth = true, closeOnSelect = true, }) => {
|
|
12
|
+
const [open, setOpen] = React.useState(false);
|
|
12
13
|
const rootClasses = classNames({
|
|
13
14
|
"grid grid-cols-[min-content,1fr] gap-4 items-center inline": true,
|
|
14
15
|
[`${className}`]: className,
|
|
@@ -18,13 +19,13 @@ export const XPRNIdentity = ({ children, className, showLogout, activeSessionCla
|
|
|
18
19
|
[`${activeSessionClassName}`]: activeSessionClassName,
|
|
19
20
|
});
|
|
20
21
|
const dropdownClasses = classNames({
|
|
21
|
-
"flex flex-col
|
|
22
|
+
"flex flex-col gap-2": true,
|
|
22
23
|
[`${dropdownClassName}`]: dropdownClassName,
|
|
23
24
|
});
|
|
24
|
-
const { session, profile, disconnect
|
|
25
|
+
const { session, profile, disconnect } = useXPRN();
|
|
25
26
|
const handleDisconnect = React.useCallback((e) => {
|
|
26
27
|
e.preventDefault();
|
|
27
28
|
disconnect();
|
|
28
29
|
}, [disconnect]);
|
|
29
|
-
return (_jsx(_Fragment, { children: session ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline w-auto flex-grow-0", children: _jsxs("div", { className: activeSessionClasses, children: [_jsx("div", { className: "w-10 h-10", children: _jsx(XPRNAvatar, { className: avatarClassName }) }), _jsxs("div", { className: "flex flex-col", children: [_jsx(XPRNSessionName, { className: "text-md font-bold" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(XPRNSessionActor, { className: "text-md" }), showLogout && (_jsx("a", { href: "#", className: "underline", onClick: e => handleDisconnect(e), children: "Log out" }))] })] })] }) }), children && (_jsx(DropdownMenuContent, { className: dropdownClasses, children: children }))] })) : (_jsx(XPRNConnectButton, { className: rootClasses, children: "Connect" })) }));
|
|
30
|
+
return (_jsx(_Fragment, { children: session ? (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { className: "inline w-auto flex-grow-0", children: _jsxs("div", { className: activeSessionClasses, children: [_jsx("div", { className: "w-10 h-10", children: _jsx(XPRNAvatar, { className: avatarClassName }) }), _jsxs("div", { className: "flex flex-col", children: [_jsx(XPRNSessionName, { className: "text-md font-bold" }), _jsxs("div", { className: "flex gap-2", children: [_jsx(XPRNSessionActor, { className: "text-md" }), showLogout && (_jsx("a", { href: "#", className: "underline", onClick: e => handleDisconnect(e), children: "Log out" }))] })] })] }) }), children && (_jsx(DropdownMenuContent, { className: dropdownClasses, matchTriggerWidth: matchDropdownWidth, onClick: () => closeOnSelect && setOpen(false), children: children }))] })) : (_jsx(XPRNConnectButton, { className: rootClasses, children: "Connect" })) }));
|
|
30
31
|
};
|
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
type XPRNSessionNameProps = React.HTMLAttributes<HTMLHeadElement> & {
|
|
2
|
+
type XPRNSessionNameProps = React.HTMLAttributes<HTMLHeadElement> & {
|
|
3
|
+
/** Show identity proof badge with status indicator */
|
|
4
|
+
showIdentityProof?: boolean;
|
|
5
|
+
/** @deprecated Use showIdentityProof instead */
|
|
6
|
+
showAuthentication?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Get badge styling based on identity proof status
|
|
10
|
+
*/
|
|
11
|
+
declare function useIdentityProofBadgeStyle(): {
|
|
12
|
+
visible: boolean;
|
|
13
|
+
className: string;
|
|
14
|
+
isPending?: undefined;
|
|
15
|
+
isSuccess?: undefined;
|
|
16
|
+
isError?: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
visible: boolean;
|
|
19
|
+
className: string;
|
|
20
|
+
isPending: boolean;
|
|
21
|
+
isSuccess: boolean;
|
|
22
|
+
isError: boolean;
|
|
23
|
+
};
|
|
3
24
|
export declare const XPRNSessionName: React.FunctionComponent<XPRNSessionNameProps>;
|
|
4
|
-
export {};
|
|
25
|
+
export { useIdentityProofBadgeStyle };
|
|
@@ -1,14 +1,59 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
3
4
|
import classNames from "classnames";
|
|
4
5
|
import { useXPRN } from "../../providers/XPRNProvider";
|
|
6
|
+
/**
|
|
7
|
+
* Get badge styling based on identity proof status
|
|
8
|
+
*/
|
|
9
|
+
function useIdentityProofBadgeStyle() {
|
|
10
|
+
const { identityProofStatus, isIdentityProofEnabled, needsIdentityProof } = useXPRN();
|
|
11
|
+
return useMemo(() => {
|
|
12
|
+
if (!isIdentityProofEnabled) {
|
|
13
|
+
return { visible: false, className: "" };
|
|
14
|
+
}
|
|
15
|
+
const isPending = identityProofStatus === "signing" ||
|
|
16
|
+
identityProofStatus === "verifying" ||
|
|
17
|
+
identityProofStatus === "validating";
|
|
18
|
+
const isSuccess = identityProofStatus === "success";
|
|
19
|
+
const isError = identityProofStatus === "error" || identityProofStatus === "expired";
|
|
20
|
+
const isIdle = identityProofStatus === "idle";
|
|
21
|
+
// Build className based on status
|
|
22
|
+
let className = "";
|
|
23
|
+
if (isPending) {
|
|
24
|
+
className = "animate-pulse text-gray-400";
|
|
25
|
+
}
|
|
26
|
+
else if (isSuccess) {
|
|
27
|
+
className = "text-white";
|
|
28
|
+
}
|
|
29
|
+
else if (isError) {
|
|
30
|
+
className = "text-red-500";
|
|
31
|
+
}
|
|
32
|
+
else if (isIdle && needsIdentityProof) {
|
|
33
|
+
className = "text-gray-500";
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
className = "text-white";
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
visible: true,
|
|
40
|
+
className,
|
|
41
|
+
isPending,
|
|
42
|
+
isSuccess,
|
|
43
|
+
isError,
|
|
44
|
+
};
|
|
45
|
+
}, [identityProofStatus, isIdentityProofEnabled, needsIdentityProof]);
|
|
46
|
+
}
|
|
5
47
|
export const XPRNSessionName = ({ children, className, }) => {
|
|
6
|
-
const { profile,
|
|
48
|
+
const { profile, session, config } = useXPRN();
|
|
49
|
+
const badgeStyle = useIdentityProofBadgeStyle();
|
|
50
|
+
const showBadge = (config?.identityProof?.required || config?.identityProof?.enforceOnConnect) && badgeStyle.visible;
|
|
7
51
|
const rootClasses = classNames({
|
|
8
52
|
"flex gap-2 items-center justify-center": true,
|
|
9
53
|
[`${className}`]: className,
|
|
10
54
|
});
|
|
11
|
-
if (!profile)
|
|
55
|
+
if (!profile || !session)
|
|
12
56
|
return _jsx(_Fragment, {});
|
|
13
|
-
return (_jsxs("div", { className: `${rootClasses}`, children: [
|
|
57
|
+
return (_jsxs("div", { className: `${rootClasses}`, children: [_jsx("h3", { children: profile.displayName ? profile.displayName.toString() : session.auth.actor.toString() }), profile.isKyc && (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-badge-check", children: [_jsx("path", { d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" }), _jsx("path", { d: "m9 12 2 2 4-4" })] })), showBadge && (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: classNames("lucide lucide-id-card", badgeStyle.className), children: [_jsx("path", { d: "M16 10h2" }), _jsx("path", { d: "M16 14h2" }), _jsx("path", { d: "M6.17 15a3 3 0 0 1 5.66 0" }), _jsx("circle", { cx: "9", cy: "11", r: "2" }), _jsx("rect", { x: "2", y: "5", width: "20", height: "14", rx: "2" })] }))] }));
|
|
14
58
|
};
|
|
59
|
+
export { useIdentityProofBadgeStyle };
|
|
@@ -10,7 +10,9 @@ declare const DropdownMenuSubTrigger: React.ForwardRefExoticComponent<Omit<Dropd
|
|
|
10
10
|
inset?: boolean;
|
|
11
11
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
12
12
|
declare const DropdownMenuSubContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSubContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
13
|
-
declare const DropdownMenuContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuContentProps & React.RefAttributes<HTMLDivElement>, "ref"> &
|
|
13
|
+
declare const DropdownMenuContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
14
|
+
matchTriggerWidth?: boolean;
|
|
15
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
14
16
|
declare const DropdownMenuItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
|
|
15
17
|
inset?: boolean;
|
|
16
18
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -16,7 +16,10 @@ DropdownMenuSubTrigger.displayName =
|
|
|
16
16
|
const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => (_jsx(DropdownMenuPrimitive.SubContent, { ref: ref, className: cn("z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className), ...props })));
|
|
17
17
|
DropdownMenuSubContent.displayName =
|
|
18
18
|
DropdownMenuPrimitive.SubContent.displayName;
|
|
19
|
-
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50
|
|
19
|
+
const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, matchTriggerWidth = false, style, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { ref: ref, sideOffset: sideOffset, className: cn("z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", !matchTriggerWidth && "min-w-[8rem]", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className), style: {
|
|
20
|
+
...style,
|
|
21
|
+
...(matchTriggerWidth && { width: 'var(--radix-dropdown-menu-trigger-width)' }),
|
|
22
|
+
}, ...props }) })));
|
|
20
23
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
21
24
|
const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => (_jsx(DropdownMenuPrimitive.Item, { ref: ref, className: cn("relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", inset && "pl-8", className), ...props })));
|
|
22
25
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
@@ -14,7 +14,7 @@ const XPRNTransactionVariants = cva("inline-flex items-center justify-center whi
|
|
|
14
14
|
});
|
|
15
15
|
const XPRNTransaction = React.forwardRef(({ className, variant, size, asChild = false, onClick, children, actions, onTransactionStart, onTransactionSuccess, onTransactionFail, ...props }, ref) => {
|
|
16
16
|
const Comp = asChild ? Slot : "button";
|
|
17
|
-
const { session, connect, addTransactionError
|
|
17
|
+
const { session, connect, addTransactionError } = useXPRN();
|
|
18
18
|
const [txStatus, setTxStatus] = React.useState('idle');
|
|
19
19
|
const TxStatusNode = React.useMemo(() => {
|
|
20
20
|
if (txStatus == 'idle')
|
|
@@ -37,12 +37,14 @@ const XPRNTransaction = React.forwardRef(({ className, variant, size, asChild =
|
|
|
37
37
|
onTransactionSuccess(res);
|
|
38
38
|
setTxStatus('success');
|
|
39
39
|
}).catch((e) => {
|
|
40
|
+
console.error(e);
|
|
40
41
|
setTxStatus('fail');
|
|
41
42
|
addTransactionError(e);
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
catch (e) {
|
|
45
46
|
setTxStatus('fail');
|
|
47
|
+
console.error(e);
|
|
46
48
|
addTransactionError(e.toString());
|
|
47
49
|
if (onTransactionFail)
|
|
48
50
|
onTransactionFail(e);
|