@@ -54,6 +54,7 @@ export default function AddCragScreen() {
5454 const [ selectedClimbingTypes , setSelectedClimbingTypes ] = useState < string [ ] > ( [ ] ) ;
5555 const [ description , setDescription ] = useState ( "" ) ;
5656 const [ isSecret , setIsSecret ] = useState ( false ) ;
57+ const [ isLocationless , setIsLocationless ] = useState ( false ) ;
5758
5859 // Loading states
5960 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
@@ -119,7 +120,7 @@ export default function AddCragScreen() {
119120 Alert . alert ( t ( "addCragModal.errors.nameRequired" , "Name is required" ) ) ;
120121 return ;
121122 }
122- if ( ! position ) {
123+ if ( ! isLocationless && ! position ) {
123124 Alert . alert (
124125 t ( "addCragModal.errors.locationRequired" , "Location is required" ) ,
125126 isSecret
@@ -128,7 +129,7 @@ export default function AddCragScreen() {
128129 ) ;
129130 return ;
130131 }
131- if ( ! country . trim ( ) || country . trim ( ) . length < 2 ) {
132+ if ( ! isLocationless && ( ! country . trim ( ) || country . trim ( ) . length < 2 ) ) {
132133 Alert . alert (
133134 t ( "addCragModal.errors.countryRequired" , "Country is required" ) ,
134135 t ( "addCragModal.errors.countryRequiredDesc" , "Please enter a 2-letter country code (e.g., CH, US, FR)." )
@@ -145,9 +146,13 @@ export default function AddCragScreen() {
145146 const result = await submitCrag (
146147 {
147148 name : name . trim ( ) ,
148- lat : position . latitude ,
149- lon : position . longitude ,
150- country : ( ( ) => {
149+ lat : isLocationless ? undefined : position ! . latitude ,
150+ lon : isLocationless ? undefined : position ! . longitude ,
151+ country : isLocationless ? ( country . trim ( ) ? ( ( ) => {
152+ const code = country . trim ( ) . substring ( 0 , 2 ) . toUpperCase ( ) ;
153+ if ( / ^ [ A - Z ] { 2 } $ / . test ( code ) ) return code ;
154+ return undefined ;
155+ } ) ( ) : undefined ) : ( ( ) => {
151156 const code = country . trim ( ) . substring ( 0 , 2 ) . toUpperCase ( ) ;
152157 if ( / ^ [ A - Z ] { 2 } $ / . test ( code ) ) return code ;
153158 return "" ;
@@ -160,6 +165,7 @@ export default function AddCragScreen() {
160165 climbingTypes : selectedClimbingTypes . length > 0 ? selectedClimbingTypes : undefined ,
161166 description : description . trim ( ) || undefined ,
162167 isSecret : isSecret || undefined ,
168+ isLocationless : isLocationless || undefined ,
163169 } ,
164170 syncKeyHash
165171 ) ;
@@ -192,7 +198,7 @@ export default function AddCragScreen() {
192198 }
193199 }
194200
195- const canSubmit = name . trim ( ) && position && country . trim ( ) . length >= 2 && ! isSubmitting && ! isGeocoding ;
201+ const canSubmit = name . trim ( ) && ( isLocationless || ( position && country . trim ( ) . length >= 2 ) ) && ! isSubmitting && ! isGeocoding ;
196202
197203 return (
198204 < KeyboardAvoidingView
@@ -235,7 +241,7 @@ export default function AddCragScreen() {
235241 </ View >
236242 < View style = { styles . secretTextContainer } >
237243 < View style = { styles . secretLabelRow } >
238- < Text style = { [ styles . secretLabel , isSecret && { color : isDark ? "#fef3c7" : "#78350f" } ] } >
244+ < Text style = { [ styles . secretLabel , { color : isSecret ? ( isDark ? "#fef3c7" : "#78350f" ) : colors . text } ] } >
239245 { t ( "addCragModal.secretCrag.label" , "Secret Crag" ) }
240246 </ Text >
241247 { isSecret && (
@@ -250,7 +256,63 @@ export default function AddCragScreen() {
250256 </ View >
251257 </ TouchableOpacity >
252258
253- { /* Map Location Picker */ }
259+ { /* Locationless Crag Toggle */ }
260+ < TouchableOpacity
261+ style = { [
262+ styles . secretToggle ,
263+ {
264+ backgroundColor : isLocationless
265+ ? isDark ? "rgba(147,51,234,0.15)" : "#faf5ff"
266+ : colors . surface ,
267+ borderColor : isLocationless
268+ ? isDark ? "rgba(147,51,234,0.4)" : "#d8b4fe"
269+ : "transparent" ,
270+ } ,
271+ ] }
272+ onPress = { ( ) => {
273+ setIsLocationless ( ! isLocationless ) ;
274+ if ( ! isLocationless ) {
275+ // Switching to locationless: clear position data
276+ setIsSecret ( false ) ;
277+ }
278+ } }
279+ activeOpacity = { 0.7 }
280+ >
281+ < View
282+ style = { [
283+ styles . secretIconCircle ,
284+ {
285+ backgroundColor : isLocationless
286+ ? isDark ? "rgba(147,51,234,0.3)" : "#e9d5ff"
287+ : colors . border ,
288+ } ,
289+ ] }
290+ >
291+ < Ionicons
292+ name = "location-outline"
293+ size = { 20 }
294+ color = { isLocationless ? ( isDark ? "#d8b4fe" : "#6b21a8" ) : colors . muted }
295+ />
296+ </ View >
297+ < View style = { styles . secretTextContainer } >
298+ < View style = { styles . secretLabelRow } >
299+ < Text style = { [ styles . secretLabel , { color : isLocationless ? ( isDark ? "#f3e8ff" : "#581c87" ) : colors . text } ] } >
300+ { t ( "addCragModal.locationless.label" , "No Location" ) }
301+ </ Text >
302+ { isLocationless && (
303+ < View style = { [ styles . secretBadge , { backgroundColor : isDark ? "rgba(147,51,234,0.3)" : "#e9d5ff" } ] } >
304+ < Text style = { [ styles . secretBadgeText , { color : isDark ? "#d8b4fe" : "#6b21a8" } ] } > ON</ Text >
305+ </ View >
306+ ) }
307+ </ View >
308+ < Text style = { [ styles . secretHint , { color : colors . muted } ] } >
309+ { t ( "addCragModal.locationless.hint" , "For crags with sensitive access. No weather data — only community reports." ) }
310+ </ Text >
311+ </ View >
312+ </ TouchableOpacity >
313+
314+ { /* Map Location Picker (hidden for locationless crags) */ }
315+ { ! isLocationless && (
254316 < View style = { styles . fieldGroup } >
255317 < Text style = { [ styles . sectionLabel , { color : colors . textSecondary } ] } >
256318 { isSecret
@@ -266,9 +328,10 @@ export default function AddCragScreen() {
266328 isSecret = { isSecret }
267329 />
268330 </ View >
331+ ) }
269332
270- { /* Nearby Crags Warning */ }
271- { nearbyCrags . length > 0 && (
333+ { /* Nearby Crags Warning (hidden for locationless crags) */ }
334+ { ! isLocationless && nearbyCrags . length > 0 && (
272335 < View
273336 style = { [
274337 styles . warningBox ,
@@ -352,7 +415,7 @@ export default function AddCragScreen() {
352415 < View style = { styles . rowFields } >
353416 < View style = { [ styles . fieldGroup , { flex : 1 } ] } >
354417 < Text style = { [ styles . label , { color : colors . textSecondary } ] } >
355- { t ( "addCragModal.form.country" , "Country" ) } *
418+ { t ( "addCragModal.form.country" , "Country" ) } { isLocationless ? "" : " *" }
356419 </ Text >
357420 < TextInput
358421 style = { [ styles . input , { color : colors . text , backgroundColor : colors . surface , borderColor : colors . border } ] }
0 commit comments