fix(gps): erreur explicite + saisie manuelle si GPS indisponible (HTTP/laptop)

navigator.geolocation est undefined sur HTTP hors localhost (contexte non sécurisé).
- Message d'erreur visible selon le cas (permission, HTTPS, timeout)
- Fallback : deux champs lat/lon s'affichent automatiquement
- TodoForm : bouton GPS toujours actif (plus disabled sur navigator.geolocation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 15:52:41 +02:00
parent be0c8bceb6
commit 9de8ad5f3e
2 changed files with 117 additions and 36 deletions
+55 -14
View File
@@ -30,6 +30,8 @@ export default function NoteForm({ initialValues, onSubmit, onCancel, submitLabe
const [gpsLat, setGpsLat] = useState<number | undefined>(initialValues?.gps_lat ?? undefined)
const [gpsLon, setGpsLon] = useState<number | undefined>(initialValues?.gps_lon ?? undefined)
const [gpsLoading, setGpsLoading] = useState(false)
const [gpsError, setGpsError] = useState<string | null>(null)
const [gpsManual, setGpsManual] = useState(false)
const [saving, setSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
const contentRef = useRef<HTMLTextAreaElement>(null)
@@ -39,19 +41,37 @@ export default function NoteForm({ initialValues, onSubmit, onCancel, submitLabe
}
function handleGps() {
if (!navigator.geolocation) return
setGpsError(null)
if (!navigator.geolocation) {
setGpsError('GPS non disponible (HTTPS requis hors localhost)')
setGpsManual(true)
return
}
setGpsLoading(true)
navigator.geolocation.getCurrentPosition(
pos => {
setGpsLat(pos.coords.latitude)
setGpsLon(pos.coords.longitude)
setGpsLoading(false)
setGpsManual(false)
},
err => {
setGpsLoading(false)
if (err.code === 1) setGpsError('Permission refusée')
else if (err.code === 2) setGpsError('Position indisponible — saisie manuelle ?')
else setGpsError('Délai dépassé — saisie manuelle ?')
setGpsManual(true)
},
() => setGpsLoading(false),
{ timeout: 8000 },
)
}
function handleManualCoord(field: 'lat' | 'lon', val: string) {
const n = parseFloat(val.replace(',', '.'))
if (field === 'lat') setGpsLat(isNaN(n) ? undefined : n)
else setGpsLon(isNaN(n) ? undefined : n)
}
async function handleSubmit() {
if (!content.trim()) return
setSaving(true)
@@ -112,39 +132,60 @@ export default function NoteForm({ initialValues, onSubmit, onCancel, submitLabe
</div>
{/* GPS */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<button
type="button"
onClick={handleGps}
disabled={gpsLoading}
style={{
padding: '6px 14px',
borderRadius: 8,
padding: '6px 14px', borderRadius: 8,
border: '1px solid var(--bg-5)',
background: gpsLat ? 'var(--info)' : 'var(--bg-4)',
color: gpsLat ? '#fff' : 'var(--ink-2)',
cursor: 'pointer',
fontFamily: 'var(--font-ui)',
fontSize: 13,
minHeight: 36,
background: gpsLat != null ? 'var(--info)' : 'var(--bg-4)',
color: gpsLat != null ? '#fff' : 'var(--ink-2)',
cursor: gpsLoading ? 'default' : 'pointer',
fontFamily: 'var(--font-ui)', fontSize: 13, minHeight: 36,
}}
>
{gpsLoading ? '…' : gpsLat ? '📍 GPS capturé' : '📍 Ajouter GPS'}
<i className={`fa-solid fa-location-dot`} style={{ marginRight: 6 }} />
{gpsLoading ? '…' : gpsLat != null ? 'GPS capturé' : 'Ajouter GPS'}
</button>
{gpsLat && (
{gpsLat != null && (
<>
<span style={{ color: 'var(--ink-3)', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
<span style={{ color: 'var(--ink-3)', fontSize: 11, fontFamily: 'var(--font-mono)', flex: 1 }}>
{gpsLat.toFixed(5)}, {gpsLon?.toFixed(5)}
</span>
<button
type="button"
onClick={() => { setGpsLat(undefined); setGpsLon(undefined) }}
onClick={() => { setGpsLat(undefined); setGpsLon(undefined); setGpsManual(false); setGpsError(null) }}
style={{ background: 'transparent', border: 'none', color: 'var(--err)', cursor: 'pointer', fontSize: 14 }}
></button>
</>
)}
</div>
{gpsError && (
<span style={{ color: 'var(--warn)', fontSize: 11, fontFamily: 'var(--font-ui)' }}>
{gpsError}
</span>
)}
{gpsManual && gpsLat == null && (
<div style={{ display: 'flex', gap: 6 }}>
<input
style={{ ...inputStyle, flex: 1, fontSize: 12 }}
placeholder="Latitude (ex: 48.8566)"
onChange={e => handleManualCoord('lat', e.target.value)}
/>
<input
style={{ ...inputStyle, flex: 1, fontSize: 12 }}
placeholder="Longitude (ex: 2.3522)"
onChange={e => handleManualCoord('lon', e.target.value)}
/>
</div>
)}
</div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}>
<button
onClick={onCancel}
+44 -4
View File
@@ -69,6 +69,8 @@ export default function TodoForm({ onSubmit, onCancel, initialValues, submitLabe
const [photoPath, setPhotoPath] = useState<string | undefined>(initialValues?.photo_path ?? undefined)
const [photoLoading, setPhotoLoading] = useState(false)
const [gpsLoading, setGpsLoading] = useState(false)
const [gpsError, setGpsError] = useState<string | null>(null)
const [gpsManual, setGpsManual] = useState(false)
const [loading, setLoading] = useState(false)
const fileRef = useRef<HTMLInputElement>(null)
@@ -109,18 +111,37 @@ export default function TodoForm({ onSubmit, onCancel, initialValues, submitLabe
}, [])
function handleGps() {
if (!navigator.geolocation) return
setGpsError(null)
if (!navigator.geolocation) {
setGpsError('GPS non disponible (HTTPS requis hors localhost)')
setGpsManual(true)
return
}
setGpsLoading(true)
navigator.geolocation.getCurrentPosition(
pos => {
setGpsLat(pos.coords.latitude)
setGpsLng(pos.coords.longitude)
setGpsLoading(false)
setGpsManual(false)
},
() => setGpsLoading(false)
err => {
setGpsLoading(false)
if (err.code === 1) setGpsError('Permission refusée')
else if (err.code === 2) setGpsError('Position indisponible — saisie manuelle ?')
else setGpsError('Délai dépassé — saisie manuelle ?')
setGpsManual(true)
},
{ timeout: 8000 },
)
}
function handleManualCoord(field: 'lat' | 'lng', val: string) {
const n = parseFloat(val.replace(',', '.'))
if (field === 'lat') setGpsLat(isNaN(n) ? undefined : n)
else setGpsLng(isNaN(n) ? undefined : n)
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!title.trim()) return
@@ -296,7 +317,7 @@ export default function TodoForm({ onSubmit, onCancel, initialValues, submitLabe
<button
type="button"
onClick={handleGps}
disabled={!navigator.geolocation}
disabled={gpsLoading}
style={{
flex: 1,
padding: '8px',
@@ -306,7 +327,7 @@ export default function TodoForm({ onSubmit, onCancel, initialValues, submitLabe
color: gpsLat != null ? '#fff' : 'var(--ink-3)',
fontFamily: 'var(--font-ui)',
fontSize: 13,
cursor: navigator.geolocation ? 'pointer' : 'default',
cursor: gpsLoading ? 'default' : 'pointer',
minHeight: 40,
}}
>
@@ -318,6 +339,25 @@ export default function TodoForm({ onSubmit, onCancel, initialValues, submitLabe
ou Ctrl+V pour coller une image
</span>
)}
{gpsError && (
<span style={{ color: 'var(--warn)', fontSize: 11, fontFamily: 'var(--font-ui)', paddingLeft: 4 }}>
{gpsError}
</span>
)}
{gpsManual && gpsLat == null && (
<div style={{ display: 'flex', gap: 6 }}>
<input
style={{ ...inputStyle, flex: 1, fontSize: 12 }}
placeholder="Latitude (ex: 48.8566)"
onChange={e => handleManualCoord('lat', e.target.value)}
/>
<input
style={{ ...inputStyle, flex: 1, fontSize: 12 }}
placeholder="Longitude (ex: 2.3522)"
onChange={e => handleManualCoord('lng', e.target.value)}
/>
</div>
)}
</div>
{/* Boutons */}