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:
@@ -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,36 +132,57 @@ export default function NoteForm({ initialValues, onSubmit, onCancel, submitLabe
|
||||
</div>
|
||||
|
||||
{/* GPS */}
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleGps}
|
||||
disabled={gpsLoading}
|
||||
style={{
|
||||
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,
|
||||
}}
|
||||
>
|
||||
{gpsLoading ? '…' : gpsLat ? '📍 GPS capturé' : '📍 Ajouter GPS'}
|
||||
</button>
|
||||
{gpsLat && (
|
||||
<>
|
||||
<span style={{ color: 'var(--ink-3)', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
|
||||
{gpsLat.toFixed(5)}, {gpsLon?.toFixed(5)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setGpsLat(undefined); setGpsLon(undefined) }}
|
||||
style={{ background: 'transparent', border: 'none', color: 'var(--err)', cursor: 'pointer', fontSize: 14 }}
|
||||
>✕</button>
|
||||
</>
|
||||
<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,
|
||||
border: '1px solid var(--bg-5)',
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<i className={`fa-solid fa-location-dot`} style={{ marginRight: 6 }} />
|
||||
{gpsLoading ? '…' : gpsLat != null ? 'GPS capturé' : 'Ajouter GPS'}
|
||||
</button>
|
||||
{gpsLat != null && (
|
||||
<>
|
||||
<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); 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>
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user