import { useState, useCallback } from 'react' import { useDropzone } from 'react-dropzone' import { useMutation } from '@tanstack/react-query' import api from '../utils/api' function UploadZone({ title, description, accept, endpoint, icon }) { const [tasks, setTasks] = useState([]) const upload = useMutation({ mutationFn: async (file) => { const form = new FormData() form.append('file', file) const { data } = await api.post(endpoint, form, { headers: { 'Content-Type': 'multipart/form-data' }, }) return { file: file.name, ...data } }, onSuccess: (data) => { setTasks(t => [...t, { ...data, status: 'queued' }]) }, }) const onDrop = useCallback((accepted) => { accepted.forEach(file => upload.mutate(file)) }, [upload]) const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept, multiple: true, }) return (
{description}
Drop files hereβ¦
) : (Drag & drop files here, or click to browse
{Object.values(accept).flat().join(', ')}
Uploadingβ¦
)} {tasks.length > 0 && (Import activities from Garmin or Strava. Large exports are processed in the background.
Automatically import new Garmin watch files
After each activity, sync your Garmin watch via USB or Garmin Express. New FIT files appear in:
GARMIN/Activity/*.fit
Upload individual FIT files above using the "Single activity" uploader, or set up a folder-watch script:
{`# Example: auto-upload new FIT files
inotifywait -m ~/Garmin/Activity/ -e create \\
--format '%f' | while read file; do
curl -X POST /api/upload/activity \\
-H "Authorization: Bearer TOKEN" \\
-F "file=@$file"
done`}