diff --git a/src/components/connect/connect-modal.tsx b/src/components/connect/connect-modal.tsx index 30c377b..11eba29 100644 --- a/src/components/connect/connect-modal.tsx +++ b/src/components/connect/connect-modal.tsx @@ -17,7 +17,7 @@ type ConnectState = 'init' | 'loading' | 'qr'; * Presents three clear paths: * 1. Quick Start — create a local DID (primary CTA) * 2. Same Browser — open wallet selector / DWebConnect popup - * 3. Scan QR — cross-device relay connect flow + * 3. Phone Wallet — cross-device or same-device native wallet flow * * Designed for future extraction to @enbox/react. */ @@ -137,7 +137,7 @@ export const ConnectModal: React.FC = ({ open, onClose }) =>
Phone Wallet
-
Scan QR code
+
QR code or open app
diff --git a/src/components/connect/qr-connect-dialog.tsx b/src/components/connect/qr-connect-dialog.tsx index d636f48..6425546 100644 --- a/src/components/connect/qr-connect-dialog.tsx +++ b/src/components/connect/qr-connect-dialog.tsx @@ -6,6 +6,7 @@ import { useEnbox } from '@/enbox'; import { CashuWalletDefinition } from '@/protocol/cashu-wallet-protocol'; import { CashuTransferDefinition } from '@/protocol/cashu-transfer-protocol'; import { brand } from '@/lib/brand'; +import { toastError, toastSuccess } from '@/lib/utils'; // Connect relay servers — must include /connect path segment. // WalletConnect.initClient appends /par, /authorize, /token to this base. @@ -31,13 +32,12 @@ type QRPhase = 'generating' | 'waiting' | 'pin' | 'connecting' | 'error'; * 1. Generates a connect URI -> renders as QR code * 2. Wallet scans QR -> creates delegate grants * 3. User enters PIN from wallet -> session established - * - * Designed for future extraction to @enbox/react. */ export const QRConnectDialog: React.FC = ({ onBack, onClose }) => { const { auth, applySession } = useEnbox(); const [phase, setPhase] = useState('generating'); const [qrDataUrl, setQrDataUrl] = useState(''); + const [walletUri, setWalletUri] = useState(''); const [pin, setPin] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const pinResolveRef = useRef<((pin: string) => void) | null>(null); @@ -45,7 +45,6 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos const onBackRef = useRef(onBack); onBackRef.current = onBack; - // Start the wallet connect flow const startConnect = useCallback(async () => { if (!auth) { setErrorMessage('Auth not ready. Please try again.'); @@ -57,22 +56,19 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos setPhase('generating'); try { - // Build agent-level permission requests from the dapp protocol - // definitions using normalizeProtocolRequests, which handles - // the ProtocolDefinition -> ConnectPermissionRequest conversion - // including default scopes (read, write, delete, query, subscribe, configure). const permissionRequests = normalizeProtocolRequests(DAPP_PROTOCOLS); const session = await auth.walletConnect({ - displayName : brand.name, - connectServerUrl : RELAY_SERVERS[0], + displayName: brand.name, + connectServerUrl: RELAY_SERVERS[0], permissionRequests, - onWalletUriReady : async (uri: string) => { + onWalletUriReady: async (uri: string) => { + setWalletUri(uri); const dataUrl = await QRCode.toDataURL(uri, { - width : 280, - margin : 2, - color : { dark: '#ffffff', light: '#00000000' }, - errorCorrectionLevel : 'M', + width: 280, + margin: 2, + color: { dark: '#ffffff', light: '#00000000' }, + errorCorrectionLevel: 'M', }); setQrDataUrl(dataUrl); setPhase('waiting'); @@ -90,8 +86,6 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos } catch (err) { if (!abortRef.current) { const msg = (err as Error).message || 'Connection failed.'; - // If the wallet explicitly denied, go back to the connect modal - // instead of showing a scary error screen. if (msg.includes('denied by the wallet')) { onBackRef.current(); } else { @@ -104,7 +98,9 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos useEffect(() => { startConnect(); - return () => { abortRef.current = true; }; + return () => { + abortRef.current = true; + }; }, [startConnect]); const handlePinSubmit = () => { @@ -115,9 +111,27 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos } }; + const handleOpenMobileWallet = async () => { + if (!walletUri) return; + try { + window.location.assign(walletUri); + } catch (error) { + toastError('Could not open mobile wallet', error); + } + }; + + const handleCopyLink = async () => { + if (!walletUri) return; + try { + await navigator.clipboard.writeText(walletUri); + toastSuccess('Connect link copied'); + } catch (error) { + toastError('Could not copy connect link', error); + } + }; + return (
- {/* Header with back button */}
- {/* Generating QR */} {phase === 'generating' && (
@@ -136,15 +149,28 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos
)} - {/* QR code display */} {phase === 'waiting' && qrDataUrl && (
Connect QR code

- Scan this QR code with your Enbox wallet app + Scan this QR code with your Enbox mobile wallet, or open it on this device.

+
+ + +
Waiting for wallet... @@ -152,7 +178,6 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos
)} - {/* PIN input */} {phase === 'pin' && (

@@ -164,7 +189,11 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos maxLength={4} value={pin} onChange={(e) => setPin(e.target.value.replace(/\D/g, '').slice(0, 4))} - onKeyDown={(e) => { if (e.key === 'Enter') { handlePinSubmit(); } }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handlePinSubmit(); + } + }} className="w-40 text-center text-3xl font-bold tracking-[0.5em] bg-muted border border-border rounded-xl py-3 px-4 text-foreground focus:outline-none focus:ring-2 focus:ring-primary" autoFocus placeholder="----" @@ -179,7 +208,6 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos

)} - {/* Connecting */} {phase === 'connecting' && (
@@ -187,12 +215,14 @@ export const QRConnectDialog: React.FC = ({ onBack, onClos
)} - {/* Error */} {phase === 'error' && (

{errorMessage}