mitai-jinkendo/frontend/src/components/TandoorSettings.jsx
Lars d6838abda7
Some checks failed
Deploy Development / deploy (push) Successful in 1m1s
Build Test / pytest-backend (push) Failing after 2s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 27s
fix: Offene Lebensmittel nach Listen-Import wieder anzeigen
Unmapped ohne Kreuzjoin, Listen-GET kurz, Rebuild im Hintergrund.
Tandoor-Zugang (URL/Token) in den Einstellungen mit ausliefern.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 10:36:08 +02:00

141 lines
4.7 KiB
JavaScript

import { useEffect, useState } from 'react'
import { api } from '../utils/api'
export default function TandoorSettings() {
const [baseUrl, setBaseUrl] = useState('')
const [token, setToken] = useState('')
const [tokenSet, setTokenSet] = useState(false)
const [tokenHint, setTokenHint] = useState(null)
const [lastOk, setLastOk] = useState(null)
const [lastError, setLastError] = useState(null)
const [busy, setBusy] = useState(false)
const [msg, setMsg] = useState(null)
const load = async () => {
const s = await api.getTandoorSettings()
setBaseUrl(s.base_url || '')
setTokenSet(Boolean(s.token_set))
setTokenHint(s.token_hint || null)
setLastOk(s.last_ok_at || null)
setLastError(s.last_error || null)
setToken('')
}
useEffect(() => {
load().catch((e) => setMsg({ type: 'err', text: e.message }))
}, [])
const save = async () => {
setBusy(true)
setMsg(null)
try {
const s = await api.putTandoorSettings({
base_url: baseUrl,
token: token.trim() || undefined,
})
setToken('')
setTokenSet(Boolean(s.token_set))
setTokenHint(s.token_hint || null)
setLastError(s.last_error || null)
setMsg({ type: 'ok', text: 'Gespeichert. Das Token bleibt nur auf dem Server.' })
} catch (e) {
setMsg({ type: 'err', text: e.message })
} finally {
setBusy(false)
}
}
const test = async () => {
setBusy(true)
setMsg(null)
try {
const res = await api.testTandoorConnection({
base_url: baseUrl || undefined,
token: token.trim() || undefined,
})
const who = res.user ? ` als ${res.user}` : ''
setMsg({ type: 'ok', text: `Verbindung ok${who}.` })
setLastError(null)
setLastOk(new Date().toISOString())
} catch (e) {
setMsg({ type: 'err', text: e.message })
} finally {
setBusy(false)
}
}
const clear = async () => {
if (!confirm('Tandoor-Zugang wirklich entfernen?')) return
setBusy(true)
setMsg(null)
try {
await api.putTandoorSettings({ base_url: '', clear_token: true })
setBaseUrl('')
setToken('')
setTokenSet(false)
setTokenHint(null)
setLastOk(null)
setLastError(null)
setMsg({ type: 'ok', text: 'Zugang entfernt.' })
} catch (e) {
setMsg({ type: 'err', text: e.message })
} finally {
setBusy(false)
}
}
return (
<div className="card section-gap">
<div className="card-title">Tandoor (Familienrezepte)</div>
<p style={{ fontSize: 13, color: 'var(--text2)', marginBottom: 14, lineHeight: 1.6 }}>
Persönlicher Zugang zu eurem Tandoor — nicht systemweit. Gerichte bleiben dort; Mitai rechnet später Nährwerte.
Token unter Tandoor → Einstellungen → API-Tokens anlegen.
</p>
<div className="settings-page__field">
<label className="settings-page__field-label" htmlFor="tandoor-url">Adresse</label>
<input
id="tandoor-url"
className="form-input"
type="url"
placeholder="https://tandoor.stommer.de"
value={baseUrl}
onChange={(e) => setBaseUrl(e.target.value)}
autoComplete="off"
/>
</div>
<div className="settings-page__field">
<label className="settings-page__field-label" htmlFor="tandoor-token">API-Token</label>
<input
id="tandoor-token"
className="form-input"
type="password"
placeholder={tokenSet ? `Gespeichert (${tokenHint || '…'}) — leer lassen zum Behalten` : 'Token einfügen'}
value={token}
onChange={(e) => setToken(e.target.value)}
autoComplete="new-password"
/>
</div>
{lastOk && (
<p style={{ fontSize: 12, color: 'var(--accent-dark)', margin: '0 0 8px' }}>
Zuletzt erfolgreich: {String(lastOk).slice(0, 16).replace('T', ' ')}
</p>
)}
{lastError && !msg && (
<p style={{ fontSize: 12, color: 'var(--danger)', margin: '0 0 8px' }}>Letzter Fehler: {lastError}</p>
)}
{msg && (
<div style={{ fontSize: 13, color: msg.type === 'err' ? 'var(--danger)' : 'var(--accent-dark)', marginBottom: 10 }}>
{msg.text}
</div>
)}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
<button type="button" className="btn btn-primary" disabled={busy} onClick={save}>Speichern</button>
<button type="button" className="btn btn-secondary" disabled={busy} onClick={test}>Verbindung testen</button>
{tokenSet && (
<button type="button" className="btn btn-secondary" disabled={busy} onClick={clear}>Zugang entfernen</button>
)}
</div>
</div>
)
}