11import type { IconButtonProps } from "@chakra-ui/react" ;
22import {
3+ Box ,
34 CloseButton ,
45 Dialog ,
56 Field ,
7+ Flex ,
8+ HStack ,
69 IconButton ,
10+ Input ,
711 Portal ,
12+ Separator ,
813 Switch ,
914 Text ,
1015} from "@chakra-ui/react" ;
1116import * as React from "react" ;
12- import { LuSettings } from "react-icons/lu" ;
17+ import { LuPlus , LuSettings , LuTrash2 } from "react-icons/lu" ;
1318import { useStore } from "../../store" ;
19+ import { authConfig } from "./auth" ;
1420
1521interface SettingsButtonProps extends Omit < IconButtonProps , "aria-label" > { }
1622
@@ -79,6 +85,12 @@ export const SettingsButton = React.forwardRef<
7985 </ Text >
8086 </ Field . HelperText >
8187 </ Field . Root >
88+ { ! authConfig && (
89+ < >
90+ < Separator my = { 4 } />
91+ < TokensSection />
92+ </ >
93+ ) }
8294 </ Dialog . Body >
8395 < Dialog . CloseTrigger asChild >
8496 < CloseButton size = "sm" />
@@ -89,3 +101,86 @@ export const SettingsButton = React.forwardRef<
89101 </ Dialog . Root >
90102 ) ;
91103} ) ;
104+
105+ function TokensSection ( ) {
106+ const tokens = useStore ( ( store ) => store . tokens ) ;
107+ const setToken = useStore ( ( store ) => store . setToken ) ;
108+ const removeToken = useStore ( ( store ) => store . removeToken ) ;
109+ const href = useStore ( ( store ) => store . href ) ;
110+
111+ const defaultBaseUri = React . useMemo ( ( ) => {
112+ if ( ! href ) return "" ;
113+ try {
114+ return new URL ( href ) . origin ;
115+ } catch {
116+ return "" ;
117+ }
118+ } , [ href ] ) ;
119+
120+ const [ baseUri , setBaseUri ] = React . useState ( "" ) ;
121+ const [ tokenValue , setTokenValue ] = React . useState ( "" ) ;
122+
123+ const handleAdd = ( ) => {
124+ const uri = baseUri . trim ( ) || defaultBaseUri ;
125+ const val = tokenValue . trim ( ) ;
126+ if ( ! uri || ! val ) return ;
127+ setToken ( uri , val ) ;
128+ setBaseUri ( "" ) ;
129+ setTokenValue ( "" ) ;
130+ } ;
131+
132+ const entries = Object . entries ( tokens ) ;
133+
134+ return (
135+ < Box >
136+ < Text fontWeight = "medium" mb = { 2 } >
137+ Access tokens
138+ </ Text >
139+ < Text fontSize = "sm" color = "fg.muted" mb = { 3 } >
140+ Provide Bearer tokens for authenticated STAC APIs.
141+ </ Text >
142+ { entries . length > 0 && (
143+ < Box mb = { 3 } >
144+ { entries . map ( ( [ uri ] ) => (
145+ < Flex key = { uri } align = "center" justify = "space-between" py = { 1 } >
146+ < Text fontSize = "sm" truncate >
147+ { uri }
148+ </ Text >
149+ < IconButton
150+ aria-label = { `Remove token for ${ uri } ` }
151+ size = "xs"
152+ variant = "ghost"
153+ onClick = { ( ) => removeToken ( uri ) }
154+ >
155+ < LuTrash2 />
156+ </ IconButton >
157+ </ Flex >
158+ ) ) }
159+ </ Box >
160+ ) }
161+ < HStack >
162+ < Input
163+ placeholder = { defaultBaseUri || "https://api.example.com" }
164+ size = "sm"
165+ value = { baseUri }
166+ onChange = { ( e ) => setBaseUri ( e . target . value ) }
167+ />
168+ < Input
169+ placeholder = "Token"
170+ size = "sm"
171+ type = "password"
172+ value = { tokenValue }
173+ onChange = { ( e ) => setTokenValue ( e . target . value ) }
174+ />
175+ < IconButton
176+ aria-label = "Add token"
177+ size = "sm"
178+ variant = "outline"
179+ onClick = { handleAdd }
180+ >
181+ < LuPlus />
182+ </ IconButton >
183+ </ HStack >
184+ </ Box >
185+ ) ;
186+ }
0 commit comments