/** * NostrConnectModal — UI for connecting a Nostr identity on mobile. * * Android: offers "Connect with Amber" (NIP-55) as the primary action, * with manual nsec entry as a secondary option. * iOS / other: manual nsec entry only. */ import React, { useCallback, useState } from "react"; import { ActivityIndicator, Modal, Platform, Pressable, StyleSheet, Text, TextInput, View, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { Colors } from "@/constants/colors"; import { useNostr } from "@/context/NostrContext"; // ─── Props ──────────────────────────────────────────────────────────────────── type Props = { visible: boolean; onClose: () => void; }; // ─── Component ──────────────────────────────────────────────────────────────── export function NostrConnectModal({ visible, onClose }: Props) { const C = Colors.dark; const { connectWithAmber, connectWithNsec, canUseAmber } = useNostr(); const [showNsecForm, setShowNsecForm] = useState(!canUseAmber); const [nsecInput, setNsecInput] = useState(""); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const handleClose = useCallback(() => { setNsecInput(""); setError(null); setShowNsecForm(!canUseAmber); onClose(); }, [canUseAmber, onClose]); const handleAmberPress = useCallback(async () => { setError(null); setLoading(true); try { await connectWithAmber(); // Amber opens; the result arrives via deep-link callback. // Close the modal — NostrContext handles the incoming URL. handleClose(); } finally { setLoading(false); } }, [connectWithAmber, handleClose]); const handleNsecConnect = useCallback(async () => { if (!nsecInput.trim()) { setError("Please enter your nsec key"); return; } setError(null); setLoading(true); const result = await connectWithNsec(nsecInput.trim()); setLoading(false); if (result.success) { handleClose(); } else { setError(result.error); } }, [nsecInput, connectWithNsec, handleClose]); return ( {/* Header */} Connect Nostr Identity {/* Android: Amber option */} {canUseAmber && !showNsecForm && ( Connect using{" "} Amber{" "} — your keys stay in Amber and are never exposed to this app. [ styles.primaryButton, { backgroundColor: C.accent, opacity: pressed || loading ? 0.75 : 1 }, ]} > {loading ? ( ) : ( <> Connect with Amber )} setShowNsecForm(true)} style={styles.secondaryLink} > Enter nsec manually instead )} {/* nsec form */} {showNsecForm && ( {canUseAmber && ( { setShowNsecForm(false); setError(null); }} style={styles.backLink} > Use Amber instead )} Paste your{" "} nsec1…{" "} private key. It will be stored only in the device secure keystore and never logged or transmitted. { setNsecInput(t); setError(null); }} autoCapitalize="none" autoCorrect={false} secureTextEntry editable={!loading} /> {error && ( {error} )} [ styles.primaryButton, { backgroundColor: C.accent, opacity: pressed || loading ? 0.75 : 1 }, ]} > {loading ? ( ) : ( Connect )} )} ); } // ─── Styles ─────────────────────────────────────────────────────────────────── const styles = StyleSheet.create({ overlay: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,0.6)", }, sheet: { borderTopLeftRadius: 20, borderTopRightRadius: 20, borderWidth: 1, borderBottomWidth: 0, paddingHorizontal: 24, paddingTop: 20, paddingBottom: Platform.OS === "ios" ? 40 : 24, }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 16, }, title: { fontSize: 18, fontWeight: "700", }, body: { gap: 14, }, description: { fontSize: 14, lineHeight: 20, }, primaryButton: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8, paddingVertical: 14, borderRadius: 10, }, buttonText: { fontSize: 16, fontWeight: "600", }, secondaryLink: { alignItems: "center", paddingVertical: 4, }, secondaryLinkText: { fontSize: 14, }, backLink: { flexDirection: "row", alignItems: "center", gap: 4, }, input: { borderWidth: 1, borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, fontSize: 14, fontFamily: Platform.OS === "ios" ? "Courier" : "monospace", }, errorText: { fontSize: 13, }, });