import { useEffect, useRef, useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import PageHeader from '../components/PageHeader'
import Card from '../components/Card'
import Button from '../components/Button'
import BreedSelect from '../components/BreedSelect'
import { useAuth } from '../authContext'
import { getSettings, updateSettings, exportDatabaseUrl, importData } from '../api'
import { THEMES, applyTheme, DEFAULT_THEME } from '../lib/theme'
const WEIGHT_UNIT_OPTIONS = [
{ value: 'decimal_lbs', label: 'Decimal lbs' },
{ value: 'lbs_oz', label: 'lbs + oz' },
]
function Toast({ message }) {
if (!message) return null
return (
{message}
)
}
function Settings() {
const { mode, logout } = useAuth()
const queryClient = useQueryClient()
const { data: settings, isLoading } = useQuery({ queryKey: ['settings'], queryFn: getSettings })
const [form, setForm] = useState(null)
const [saving, setSaving] = useState(false)
const [toast, setToast] = useState(null)
const importInputRef = useRef(null)
const [pendingFile, setPendingFile] = useState(null)
const [importing, setImporting] = useState(false)
// Seed the form from settings exactly once (render-phase state adjustment):
// form is null only until settings first load, so a later settings refetch
// can't clobber the user's in-progress edits.
if (settings && form === null) {
setForm({
rabbitry_name: settings.rabbitry_name ?? '',
weight_unit: settings.weight_unit ?? 'decimal_lbs',
kit_prefix: settings.kit_prefix ?? '',
default_breed: settings.default_breed ?? '',
theme: settings.theme ?? DEFAULT_THEME,
})
}
useEffect(() => {
if (!toast) return
const timer = setTimeout(() => setToast(null), 2500)
return () => clearTimeout(timer)
}, [toast])
async function handleSave() {
setSaving(true)
try {
await updateSettings(form)
setToast('Settings saved')
} catch (err) {
setToast(err.message || 'Failed to save settings')
} finally {
setSaving(false)
}
}
function handleThemeSelect(themeId) {
setForm((prev) => ({ ...prev, theme: themeId }))
// Apply immediately for live preview; handleSave persists it to the backend.
applyTheme(themeId)
}
function handleExport() {
window.location.href = exportDatabaseUrl()
}
function handleFilePicked(e) {
const file = e.target.files?.[0]
// Reset the input so picking the same file again still fires onChange.
e.target.value = ''
if (!file) return
// Validate before uploading: an oversized file gets rejected by the server
// mid-stream, which the browser surfaces as an opaque "network error" rather
// than a readable message — so catch it (and obvious wrong file types) here.
const name = file.name.toLowerCase()
if (!name.endsWith('.csv') && !name.endsWith('.json')) {
setToast('Please choose a .csv or .json file')
return
}
if (file.size > 10 * 1024 * 1024) {
setToast('File is too large (max 10 MB)')
return
}
setPendingFile(file)
}
async function handleConfirmImport() {
const file = pendingFile
setImporting(true)
try {
const result = await importData(file)
// Everything derived from rabbits/litters is now stale.
queryClient.invalidateQueries()
setToast(`Imported ${result.rabbits_imported} rabbits`)
setPendingFile(null)
} catch (err) {
setToast(err.message || 'Import failed')
} finally {
setImporting(false)
}
}
if (isLoading || !form) {
return (
)
}
return (
setForm({ ...form, rabbitry_name: e.target.value })}
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
/>
{WEIGHT_UNIT_OPTIONS.map((opt) => (
))}
setForm({ ...form, default_breed: breed })}
/>
Pre-fills the breed field when adding a new rabbit.
Selecting a theme previews it instantly. Save to keep it across your devices.
{THEMES.map((theme) => (
))}
Export Database
Download a full backup of the SQLite database file.
Import Data
Load rabbits from a CSV or JSON file. This replaces your entire herd —{' '}
all current rabbits and litters are deleted first.
{mode === 'authed' && (
Account
End this session on this device.
)}
{pendingFile && (
Replace all data?
Importing {pendingFile.name} will
permanently delete all current rabbits and litters{' '}
before loading the file. This cannot be undone.
)}
)
}
export default Settings