11import React , { useState } from 'react' ;
2- import { Alert , Linking } from 'react-native' ;
2+ import {
3+ Alert ,
4+ Linking ,
5+ TextInput ,
6+ StyleSheet ,
7+ TouchableOpacity ,
8+ } from 'react-native' ;
39import {
410 FxBox ,
511 FxButton ,
612 FxSafeAreaBox ,
713 FxText ,
14+ useFxTheme ,
815} from '@functionland/component-library' ;
916import { NativeStackScreenProps } from '@react-navigation/native-stack' ;
1017import { blockchain , fula } from '@functionland/react-native-fula' ;
@@ -14,6 +21,8 @@ import {
1421} from '../../../navigation/navigationConfig' ;
1522import { useBloxsStore } from '../../../stores/useBloxsStore' ;
1623import { useDAppsStore } from '../../../stores/dAppsSettingsStore' ;
24+ import { copyToClipboard } from '../../../utils/clipboard' ;
25+ import { QRScannerModal } from './QRScannerModal' ;
1726
1827type Props = NativeStackScreenProps <
1928 SettingsStackParamList ,
@@ -24,19 +33,32 @@ export const AutoPinPairingScreen = ({ route, navigation }: Props) => {
2433 const [ loading , setLoading ] = useState ( false ) ;
2534 const [ error , setError ] = useState < string | null > ( null ) ;
2635 const [ success , setSuccess ] = useState ( false ) ;
36+ const [ scannerVisible , setScannerVisible ] = useState ( false ) ;
37+ const [ copied , setCopied ] = useState ( false ) ;
38+
39+ // Manual mode state
40+ const [ tokenInput , setTokenInput ] = useState ( '' ) ;
41+ const [ endpointInput , setEndpointInput ] = useState ( '' ) ;
42+ const [ pairingSecret , setPairingSecret ] = useState < string | null > ( null ) ;
2743
2844 const bloxs = useBloxsStore ( ( state ) => state . bloxs ) ;
2945 const currentBloxPeerId = useBloxsStore ( ( state ) => state . currentBloxPeerId ) ;
3046 const addOrUpdateDApp = useDAppsStore ( ( state ) => state . addOrUpdateDApp ) ;
3147
48+ const theme = useFxTheme ( ) ;
49+
50+ // Deep link params
3251 const token = route ?. params ?. token ;
3352 const endpoint = route ?. params ?. endpoint ;
3453 const returnUrl = route ?. params ?. returnUrl ;
3554
55+ const isDeepLinkMode = ! ! token ;
56+
3657 const currentBlox = bloxs [ currentBloxPeerId ] ;
3758 const bloxName = currentBlox ?. name || 'My Blox' ;
3859
39- const handlePair = async ( ) => {
60+ // Deep link mode handler (existing behavior)
61+ const handleDeepLinkPair = async ( ) => {
4062 if ( ! token || ! endpoint ) {
4163 setError ( 'Missing pairing parameters' ) ;
4264 return ;
@@ -106,9 +128,102 @@ export const AutoPinPairingScreen = ({ route, navigation }: Props) => {
106128 }
107129 } ;
108130
109- // Note: We intentionally don't auto-trigger pairing.
110- // User must read the confirmation and press the button.
131+ // Manual mode handler
132+ const handleManualPair = async ( ) => {
133+ if ( ! tokenInput || ! endpointInput ) {
134+ setError ( 'Please fill in both API Key and Endpoint' ) ;
135+ return ;
136+ }
137+
138+ setLoading ( true ) ;
139+ setError ( null ) ;
140+ setPairingSecret ( null ) ;
141+
142+ try {
143+ await fula . isReady ( false ) ;
111144
145+ const result = await blockchain . autoPinPair ( tokenInput , endpointInput ) ;
146+
147+ if ( result ?. pairing_secret ) {
148+ setPairingSecret ( result . pairing_secret ) ;
149+ } else {
150+ setError ( 'Unexpected response from blox' ) ;
151+ }
152+ } catch ( e : any ) {
153+ const msg = e ?. message || e ?. toString ( ) || 'Unknown error' ;
154+ setError ( msg ) ;
155+ } finally {
156+ setLoading ( false ) ;
157+ }
158+ } ;
159+
160+ const handleCopy = ( ) => {
161+ if ( pairingSecret ) {
162+ copyToClipboard ( pairingSecret ) ;
163+ setCopied ( true ) ;
164+ setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
165+ }
166+ } ;
167+
168+ const handleQRScanned = ( api : string , qrEndpoint : string ) => {
169+ setTokenInput ( api ) ;
170+ setEndpointInput ( qrEndpoint ) ;
171+ setScannerVisible ( false ) ;
172+ } ;
173+
174+ // Deep link mode UI (unchanged behavior)
175+ if ( isDeepLinkMode ) {
176+ return (
177+ < FxSafeAreaBox flex = { 1 } edges = { [ 'top' ] } paddingHorizontal = "20" >
178+ < FxBox marginTop = "32" >
179+ < FxText variant = "h300" marginBottom = "16" >
180+ Auto-Pin Pairing
181+ </ FxText >
182+
183+ < FxText variant = "bodyMediumRegular" marginBottom = "24" color = "content2" >
184+ FxFiles wants to enable auto-pinning on your blox. This will
185+ automatically pin your uploaded files to{ ' ' }
186+ < FxText variant = "bodySmallSemibold" > { bloxName } </ FxText > , allowing you to
187+ download them directly from your local network.
188+ </ FxText >
189+
190+ { error && (
191+ < FxBox
192+ backgroundColor = "errorBase"
193+ padding = "16"
194+ borderRadius = "s"
195+ marginBottom = "16"
196+ >
197+ < FxText color = "content1" > { error } </ FxText >
198+ </ FxBox >
199+ ) }
200+
201+ { success ? (
202+ < FxBox
203+ backgroundColor = "greenBackground"
204+ padding = "16"
205+ borderRadius = "s"
206+ >
207+ < FxText color = "content1" >
208+ Auto-pinning is enabled! Your files will be automatically pinned
209+ to this blox.
210+ </ FxText >
211+ </ FxBox >
212+ ) : (
213+ < FxButton
214+ size = "large"
215+ onPress = { handleDeepLinkPair }
216+ disabled = { loading || ! token || ! endpoint }
217+ >
218+ { loading ? 'Pairing...' : 'Enable Auto-Pin' }
219+ </ FxButton >
220+ ) }
221+ </ FxBox >
222+ </ FxSafeAreaBox >
223+ ) ;
224+ }
225+
226+ // Manual mode UI
112227 return (
113228 < FxSafeAreaBox flex = { 1 } edges = { [ 'top' ] } paddingHorizontal = "20" >
114229 < FxBox marginTop = "32" >
@@ -117,12 +232,59 @@ export const AutoPinPairingScreen = ({ route, navigation }: Props) => {
117232 </ FxText >
118233
119234 < FxText variant = "bodyMediumRegular" marginBottom = "24" color = "content2" >
120- FxFiles wants to enable auto-pinning on your blox. This will
121- automatically pin your uploaded files to{ ' ' }
122- < FxText variant = "bodySmallSemibold" > { bloxName } </ FxText > , allowing you to
123- download them directly from your local network.
235+ Scan a QR code or enter the API key and endpoint manually to pair an
236+ app for auto-pinning.
124237 </ FxText >
125238
239+ < FxButton
240+ size = "large"
241+ marginBottom = "24"
242+ onPress = { ( ) => setScannerVisible ( true ) }
243+ >
244+ Scan QR Code
245+ </ FxButton >
246+
247+ < FxText variant = "bodySmallSemibold" marginBottom = "8" color = "content2" >
248+ API Key
249+ </ FxText >
250+ < TextInput
251+ style = { [
252+ styles . input ,
253+ {
254+ color : theme . colors . content1 ,
255+ borderColor : theme . colors . border ,
256+ backgroundColor : theme . colors . backgroundSecondary ,
257+ } ,
258+ ] }
259+ value = { tokenInput }
260+ onChangeText = { setTokenInput }
261+ placeholder = "Enter API key"
262+ placeholderTextColor = { theme . colors . content3 }
263+ autoCapitalize = "none"
264+ autoCorrect = { false }
265+ />
266+
267+ < FxText variant = "bodySmallSemibold" marginBottom = "8" color = "content2" >
268+ Endpoint
269+ </ FxText >
270+ < TextInput
271+ style = { [
272+ styles . input ,
273+ {
274+ color : theme . colors . content1 ,
275+ borderColor : theme . colors . border ,
276+ backgroundColor : theme . colors . backgroundSecondary ,
277+ } ,
278+ ] }
279+ value = { endpointInput }
280+ onChangeText = { setEndpointInput }
281+ placeholder = "Enter endpoint URL"
282+ placeholderTextColor = { theme . colors . content3 }
283+ autoCapitalize = "none"
284+ autoCorrect = { false }
285+ keyboardType = "url"
286+ />
287+
126288 { error && (
127289 < FxBox
128290 backgroundColor = "errorBase"
@@ -134,27 +296,67 @@ export const AutoPinPairingScreen = ({ route, navigation }: Props) => {
134296 </ FxBox >
135297 ) }
136298
137- { success ? (
138- < FxBox
139- backgroundColor = "greenBackground"
140- padding = "16"
141- borderRadius = "s"
142- >
143- < FxText color = "content1" >
144- Auto-pinning is enabled! Your files will be automatically pinned
145- to this blox.
299+ { pairingSecret ? (
300+ < FxBox marginBottom = "16" >
301+ < FxText variant = "bodySmallSemibold" marginBottom = "8" color = "content2" >
302+ Pairing Secret
146303 </ FxText >
304+ < TouchableOpacity onPress = { handleCopy } activeOpacity = { 0.7 } >
305+ < FxBox
306+ backgroundColor = "backgroundSecondary"
307+ padding = "16"
308+ borderRadius = "s"
309+ flexDirection = "row"
310+ alignItems = "center"
311+ justifyContent = "space-between"
312+ >
313+ < FxText
314+ variant = "bodyMediumRegular"
315+ color = "content1"
316+ style = { styles . secretText }
317+ >
318+ { pairingSecret }
319+ </ FxText >
320+ < FxText
321+ variant = "bodySmallSemibold"
322+ color = { copied ? 'greenBase' : 'primary' }
323+ >
324+ { copied ? 'Copied!' : 'Copy' }
325+ </ FxText >
326+ </ FxBox >
327+ </ TouchableOpacity >
147328 </ FxBox >
148329 ) : (
149330 < FxButton
150331 size = "large"
151- onPress = { handlePair }
152- disabled = { loading || ! token || ! endpoint }
332+ onPress = { handleManualPair }
333+ disabled = { loading || ! tokenInput || ! endpointInput }
153334 >
154- { loading ? 'Pairing ...' : 'Enable Auto-Pin ' }
335+ { loading ? 'Getting Secret ...' : 'Get Secret ' }
155336 </ FxButton >
156337 ) }
157338 </ FxBox >
339+
340+ < QRScannerModal
341+ visible = { scannerVisible }
342+ onScanned = { handleQRScanned }
343+ onClose = { ( ) => setScannerVisible ( false ) }
344+ />
158345 </ FxSafeAreaBox >
159346 ) ;
160347} ;
348+
349+ const styles = StyleSheet . create ( {
350+ input : {
351+ borderWidth : 1 ,
352+ borderRadius : 8 ,
353+ paddingHorizontal : 12 ,
354+ paddingVertical : 10 ,
355+ fontSize : 14 ,
356+ marginBottom : 16 ,
357+ } ,
358+ secretText : {
359+ flex : 1 ,
360+ marginRight : 12 ,
361+ } ,
362+ } ) ;
0 commit comments