33 * Displays in the center of the map with band/format selection options
44 */
55
6- import { useState , useEffect } from 'react' ;
6+ import { useState , useEffect , useRef , useCallback } from 'react' ;
77import { downloadTile , triggerDownload , checkBackendHealth } from '../utils/downloadApi' ;
88import { getCollection } from '../config/collections' ;
99import './DownloadModal.css' ;
1010
11+ const TURNSTILE_SITE_KEY = import . meta. env . VITE_TURNSTILE_SITE_KEY || '0x4AAAAAACW5aVJzaeavtikq' ;
12+
1113function DownloadModal ( { item, collection, onClose } ) {
1214 const [ selectedAsset , setSelectedAsset ] = useState ( null ) ;
1315 const [ selectedFormat , setSelectedFormat ] = useState ( 'geotiff' ) ;
1416 const [ isDownloading , setIsDownloading ] = useState ( false ) ;
1517 const [ error , setError ] = useState ( null ) ;
1618 const [ backendAvailable , setBackendAvailable ] = useState ( null ) ;
1719 const [ abortController , setAbortController ] = useState ( null ) ;
20+ const [ turnstileToken , setTurnstileToken ] = useState ( null ) ;
21+ const turnstileRef = useRef ( null ) ;
22+ const turnstileWidgetId = useRef ( null ) ;
23+
24+ // Load Turnstile script and render widget
25+ useEffect ( ( ) => {
26+ // Load Turnstile script if not already loaded
27+ if ( ! window . turnstile ) {
28+ const script = document . createElement ( 'script' ) ;
29+ script . src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit' ;
30+ script . async = true ;
31+ script . onload = ( ) => renderTurnstile ( ) ;
32+ document . head . appendChild ( script ) ;
33+ } else {
34+ renderTurnstile ( ) ;
35+ }
36+
37+ return ( ) => {
38+ // Cleanup widget on unmount
39+ if ( turnstileWidgetId . current && window . turnstile ) {
40+ window . turnstile . remove ( turnstileWidgetId . current ) ;
41+ }
42+ } ;
43+ } , [ ] ) ;
44+
45+ const renderTurnstile = useCallback ( ( ) => {
46+ if ( turnstileRef . current && window . turnstile && ! turnstileWidgetId . current ) {
47+ turnstileWidgetId . current = window . turnstile . render ( turnstileRef . current , {
48+ sitekey : TURNSTILE_SITE_KEY ,
49+ callback : ( token ) => setTurnstileToken ( token ) ,
50+ 'expired-callback' : ( ) => setTurnstileToken ( null ) ,
51+ 'error-callback' : ( ) => setTurnstileToken ( null ) ,
52+ theme : 'light' ,
53+ size : 'normal' ,
54+ } ) ;
55+ }
56+ } , [ ] ) ;
1857
1958 // Get collection config for available bands
2059 const collectionConfig = getCollection ( collection ) ;
@@ -44,6 +83,11 @@ function DownloadModal({ item, collection, onClose }) {
4483 return ;
4584 }
4685
86+ if ( ! turnstileToken ) {
87+ setError ( 'Please complete the security check' ) ;
88+ return ;
89+ }
90+
4791 setIsDownloading ( true ) ;
4892 setError ( null ) ;
4993
@@ -67,6 +111,7 @@ function DownloadModal({ item, collection, onClose }) {
67111 format : selectedFormat ,
68112 rescale : bandConfig . rescale || null ,
69113 colormap : bandConfig . colormap || null ,
114+ turnstileToken : turnstileToken ,
70115 } , filename , controller . signal ) ;
71116
72117 // Trigger download and close
@@ -82,6 +127,11 @@ function DownloadModal({ item, collection, onClose }) {
82127 }
83128 setIsDownloading ( false ) ;
84129 setAbortController ( null ) ;
130+ // Reset turnstile for retry
131+ setTurnstileToken ( null ) ;
132+ if ( turnstileWidgetId . current && window . turnstile ) {
133+ window . turnstile . reset ( turnstileWidgetId . current ) ;
134+ }
85135 }
86136 } ;
87137
@@ -164,7 +214,11 @@ function DownloadModal({ item, collection, onClose }) {
164214 { /* Backend status warning */ }
165215 { backendAvailable === false && (
166216 < div className = "backend-warning" >
167- Download backend is not available. Please try again later.
217+ < strong > Download Service Unavailable</ strong >
218+ < p style = { { margin : '8px 0 0 0' , fontSize : '12px' } } >
219+ The download backend is currently offline. You can still explore satellite imagery through the map viewer.
220+ This may be due to maintenance or budget limits.
221+ </ p >
168222 </ div >
169223 ) }
170224
@@ -227,6 +281,14 @@ function DownloadModal({ item, collection, onClose }) {
227281 </ div >
228282 ) }
229283
284+ { /* Turnstile widget */ }
285+ < div className = "download-section turnstile-section" >
286+ < div ref = { turnstileRef } > </ div >
287+ { ! turnstileToken && ! isDownloading && (
288+ < div className = "turnstile-hint" > Please complete the security check above</ div >
289+ ) }
290+ </ div >
291+
230292 { /* Error display */ }
231293 { error && (
232294 < div className = "download-error" >
@@ -246,7 +308,7 @@ function DownloadModal({ item, collection, onClose }) {
246308 < button
247309 className = "download-button-primary"
248310 onClick = { handleDownload }
249- disabled = { isDownloading || backendAvailable === false || ! selectedAsset || downloadsDisabled }
311+ disabled = { isDownloading || backendAvailable === false || ! selectedAsset || downloadsDisabled || ! turnstileToken }
250312 >
251313 { isDownloading ? (
252314 < >
0 commit comments