All files / src/components/settings AIQuotaSection.tsx

75.86% Statements 22/29
66.66% Branches 8/12
55.55% Functions 5/9
82.6% Lines 19/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82          1x                             6x 6x     6x     6x 4x 2x 2x     6x         6x 1x 1x 1x 1x 1x       1x       6x   4x         24x                                              
import { useState, useEffect } from 'react'
import { Loader2, Save } from 'lucide-react'
import { toast } from 'sonner'
import { api, type QuotaResponse, type QuotaLimits } from '../../api/client'
 
const FEATURE_LABELS: { key: keyof QuotaLimits; label: string }[] = [
  { key: 'remix', label: 'Remixes' },
  { key: 'remix_suggestions', label: 'Remix Suggestions' },
  { key: 'scale', label: 'Scaling' },
  { key: 'tips', label: 'Tips' },
  { key: 'discover', label: 'Discover' },
  { key: 'timer', label: 'Timer Naming' },
]
 
interface AIQuotaSectionProps {
  quotaData: QuotaResponse | null
  onSave: (limits: QuotaLimits) => void
}
 
export default function AIQuotaSection({ quotaData, onSave }: AIQuotaSectionProps) {
  const defaultLimits: QuotaLimits = { remix: 0, remix_suggestions: 0, scale: 0, tips: 0, discover: 0, timer: 0 }
  const [limits, setLimits] = useState<QuotaLimits>(
    quotaData?.limits ?? defaultLimits,
  )
  const [saving, setSaving] = useState(false)
 
  // Sync limits when quotaData loads from parent
  useEffect(() => {
    if (!quotaData?.limits) return
    const id = requestAnimationFrame(() => setLimits(quotaData.limits))
    return () => cancelAnimationFrame(id)
  }, [quotaData?.limits])
 
  const handleChange = (key: keyof QuotaLimits, value: string) => {
    const num = parseInt(value, 10)
    if (!isNaN(num) && num >= 0) setLimits((prev) => ({ ...prev, [key]: num }))
  }
 
  const handleSave = async () => {
    setSaving(true)
    try {
      const updated = await api.ai.quotas.update(limits)
      onSave(updated.limits)
      toast.success('Quota limits saved')
    } catch {
      toast.error('Failed to save quota limits')
    } finally {
      setSaving(false)
    }
  }
 
  if (!quotaData) return null
 
  return (
    <div className="rounded-lg border border-border bg-card p-4">
      <h2 className="mb-4 text-lg font-medium text-foreground">AI Daily Limits</h2>
      <div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
        {FEATURE_LABELS.map(({ key, label }) => (
          <label key={key} className="block">
            <span className="mb-1 block text-sm font-medium text-foreground">{label}</span>
            <input
              type="number"
              min={0}
              value={limits[key]}
              onChange={(e) => handleChange(key, e.target.value)}
              className="w-full rounded-lg border border-border bg-input-background px-3 py-2 text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
            />
          </label>
        ))}
      </div>
      <button
        onClick={handleSave}
        disabled={saving}
        className="mt-4 flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
      >
        {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
        Save Limits
      </button>
    </div>
  )
}
 
← Back to Dashboard