Files
TREK/client/src/components/Admin/useGoogleQuota.ts
T
Maurice 7acf7f5655 feat(admin): a daily limit on Google API calls
Admin > Settings > Google options gets a daily limit with today's count beside it. Every call through the Places and Routes clients is counted per UTC day; once the limit is reached the Google key reads as absent until the next day, so search, details and photos fall back to OpenStreetMap the way a keyless install does, and the admin sees that the day is used up. (#1582)
2026-10-01 12:15:38 +02:00

59 lines
2.2 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react'
import type { GoogleQuotaStatus } from '@trek/shared'
import { adminApi } from '../../api/client'
import { useToast } from '../shared/Toast'
import { useTranslation } from '../../i18n'
import { getApiErrorMessage } from '../../utils/apiError'
/**
* The admin's daily ceiling on Google API calls and today's count (#1582).
* Admin configuration, online only like the rest of the settings tab.
*/
export function useGoogleQuota() {
const { t } = useTranslation()
const toast = useToast()
const [status, setStatus] = useState<GoogleQuotaStatus | null>(null)
const [draft, setDraft] = useState('')
const [saving, setSaving] = useState(false)
// Read through a ref: the load runs once when the row opens, and a new toast or
// t identity on a later render must not fetch again and reset what is typed.
const notify = useRef({ toast, t })
notify.current = { toast, t }
useEffect(() => {
let cancelled = false
adminApi.getGoogleQuota()
.then((s: GoogleQuotaStatus) => {
if (cancelled) return
setStatus(s)
setDraft(s.daily_limit == null ? '' : String(s.daily_limit))
})
.catch((err: unknown) => {
if (!cancelled) notify.current.toast.error(getApiErrorMessage(err, notify.current.t('common.error')))
})
return () => { cancelled = true }
}, [])
const stored = status?.daily_limit == null ? '' : String(status.daily_limit)
const dirty = status !== null && draft.trim() !== stored
const save = useCallback(async () => {
if (!dirty || saving) return
const parsed = Number.parseInt(draft.trim(), 10)
const limit = draft.trim() === '' || !Number.isFinite(parsed) || parsed <= 0 ? null : parsed
setSaving(true)
try {
const next: GoogleQuotaStatus = await adminApi.updateGoogleQuota(limit)
setStatus(next)
setDraft(next.daily_limit == null ? '' : String(next.daily_limit))
toast.success(t('admin.googleQuota.saved'))
} catch (err) {
toast.error(getApiErrorMessage(err, t('common.error')))
} finally {
setSaving(false)
}
}, [dirty, saving, draft, toast, t])
return { status, draft, setDraft, dirty, saving, save }
}