mirror of
https://github.com/xavidop/cardex.git
synced 2026-08-29 01:08:52 +00:00
feat: added warning for the api keys
This commit is contained in:
@@ -10,6 +10,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Loader2, Save } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { compressBase64Image, getBase64Size, formatBytes } from '@/utils/imageUtils';
|
||||
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
|
||||
|
||||
export default function GenerateFromPhotoPage() {
|
||||
const { user } = useAuth();
|
||||
@@ -99,6 +100,14 @@ export default function GenerateFromPhotoPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Keys Warning - only show OpenAI key warning for this page */}
|
||||
<div className="mb-6">
|
||||
<ApiKeysWarning
|
||||
requiredKeys={['openaiApiKey']}
|
||||
showDismiss={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PhotoCardGenerator onCardGenerated={handleCardGenerated} />
|
||||
|
||||
{generatedCard && (
|
||||
|
||||
@@ -8,6 +8,7 @@ import { addCardToCollection } from '@/lib/firestore';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { compressBase64Image, getBase64Size, formatBytes } from '@/utils/imageUtils';
|
||||
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
|
||||
|
||||
export default function GenerateCardPage() {
|
||||
const { user } = useAuth();
|
||||
@@ -93,6 +94,14 @@ export default function GenerateCardPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Keys Warning - only show Gemini key warning for this page */}
|
||||
<div className="mb-6">
|
||||
<ApiKeysWarning
|
||||
requiredKeys={['geminiApiKey']}
|
||||
showDismiss={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardGenerator onCardGenerated={handleCardGenerated} />
|
||||
|
||||
{generatedCard && (
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ScanLine, Sparkles, BookOpen, Camera } from 'lucide-react';
|
||||
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const router = useRouter();
|
||||
@@ -18,6 +19,11 @@ export default function DashboardPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Keys Warning */}
|
||||
<div className="mb-8">
|
||||
<ApiKeysWarning />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-6xl mx-auto">
|
||||
<Card className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CardScanner from '@/components/cards/CardScanner';
|
||||
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
|
||||
|
||||
export default function ScanCardPage() {
|
||||
return (
|
||||
@@ -9,6 +10,15 @@ export default function ScanCardPage() {
|
||||
Upload an image of your Pokémon card to automatically identify and add it to your collection
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Keys Warning - only show Gemini key warning for scanning */}
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<ApiKeysWarning
|
||||
requiredKeys={['geminiApiKey']}
|
||||
showDismiss={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardScanner />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useApiKeys } from '@/hooks/useApiKeys';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AlertTriangle, Settings, X } from 'lucide-react';
|
||||
|
||||
interface ApiKeysWarningProps {
|
||||
className?: string;
|
||||
showDismiss?: boolean;
|
||||
requiredKeys?: ('geminiApiKey' | 'openaiApiKey')[];
|
||||
}
|
||||
|
||||
export default function ApiKeysWarning({
|
||||
className = "",
|
||||
showDismiss = true,
|
||||
requiredKeys = ['geminiApiKey', 'openaiApiKey']
|
||||
}: ApiKeysWarningProps) {
|
||||
const { hasGeminiKey, hasOpenAIKey, loading } = useApiKeys();
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
if (loading || dismissed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check which keys are missing
|
||||
const missingKeys: string[] = [];
|
||||
if (requiredKeys.includes('geminiApiKey') && !hasGeminiKey) {
|
||||
missingKeys.push('Gemini API');
|
||||
}
|
||||
if (requiredKeys.includes('openaiApiKey') && !hasOpenAIKey) {
|
||||
missingKeys.push('OpenAI API');
|
||||
}
|
||||
|
||||
// If no keys are missing, don't show the warning
|
||||
if (missingKeys.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allKeysMissing = missingKeys.length === requiredKeys.length;
|
||||
|
||||
return (
|
||||
<Alert className={`border-yellow-200 bg-yellow-50 dark:border-yellow-800 dark:bg-yellow-950 ${className}`}>
|
||||
<AlertTriangle className="h-4 w-4 text-yellow-600 dark:text-yellow-400" />
|
||||
<AlertDescription className="flex items-center justify-between w-full">
|
||||
<div className="flex-1">
|
||||
<span className="font-medium text-yellow-800 dark:text-yellow-200">
|
||||
{allKeysMissing ? 'API Keys Required' : 'Some API Keys Missing'}
|
||||
</span>
|
||||
<p className="text-yellow-700 dark:text-yellow-300 mt-1">
|
||||
{allKeysMissing
|
||||
? `Configure your ${missingKeys.join(' and ')} key${missingKeys.length > 1 ? 's' : ''} to enable AI-powered features.`
|
||||
: `Missing ${missingKeys.join(' and ')} key${missingKeys.length > 1 ? 's' : ''}. Some features may be unavailable.`
|
||||
}
|
||||
</p>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<Button asChild size="sm" variant="default" className="bg-yellow-600 hover:bg-yellow-700 text-white">
|
||||
<Link href="/dashboard/settings">
|
||||
<Settings className="h-3 w-3 mr-1" />
|
||||
Configure Keys
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{showDismiss && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-yellow-600 hover:text-yellow-800 dark:text-yellow-400 dark:hover:text-yellow-200 h-8 w-8 p-0 ml-4"
|
||||
onClick={() => setDismissed(true)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Dismiss</span>
|
||||
</Button>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { Loader2, Download } from 'lucide-react';
|
||||
import { downloadCardFromBase64 } from '@/utils/downloadUtils';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useApiKeys } from '@/hooks/useApiKeys';
|
||||
|
||||
export interface GeneratePokemonCardInput {
|
||||
pokemonName: string;
|
||||
@@ -79,6 +80,7 @@ interface CardGeneratorProps {
|
||||
|
||||
export function CardGenerator({ onCardGenerated, initialValues }: CardGeneratorProps) {
|
||||
const { user } = useAuth();
|
||||
const { hasGeminiKey } = useApiKeys();
|
||||
const { toast } = useToast();
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [generatedCard, setGeneratedCard] = useState<{ imageBase64: string; prompt: string } | null>(null);
|
||||
@@ -138,6 +140,11 @@ export function CardGenerator({ onCardGenerated, initialValues }: CardGeneratorP
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasGeminiKey) {
|
||||
setError('Gemini API key is required to generate cards. Please configure it in Settings.');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGenerating(true);
|
||||
setError('');
|
||||
setGeneratedCard(null);
|
||||
@@ -394,17 +401,31 @@ export function CardGenerator({ onCardGenerated, initialValues }: CardGeneratorP
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={isGenerating}>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isGenerating || !hasGeminiKey}
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Generating Card...
|
||||
</>
|
||||
) : !hasGeminiKey ? (
|
||||
'Gemini API Key Required'
|
||||
) : (
|
||||
'Generate Pokemon Card'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{!hasGeminiKey && (
|
||||
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<p className="text-sm text-yellow-700">
|
||||
Please configure your Gemini API key in <strong>Settings</strong> to generate cards.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-md">
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
|
||||
@@ -5,13 +5,14 @@ import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Pencil, Trash2, Sparkles, Download, Play, Pause, Video, Clock, AlertCircle, RotateCcw, FileDown, Eye } from 'lucide-react';
|
||||
import { Pencil, Trash2, Sparkles, Download, Play, Pause, Video, Clock, AlertCircle, RotateCcw, FileDown, Eye, Settings } from 'lucide-react';
|
||||
import type { PokemonCard } from '@/types';
|
||||
import { downloadCardImage, downloadCardVideo } from '@/utils/downloadUtils';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import ShareButton from '@/components/ui/share-button';
|
||||
import { useState } from 'react';
|
||||
import { generateVideoForExistingCard } from '@/lib/firestore';
|
||||
import { useApiKeys } from '@/hooks/useApiKeys';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -33,6 +34,7 @@ interface CardItemProps {
|
||||
|
||||
export default function CardItem({ card, onDelete, onUpdate, isDeleting }: CardItemProps) {
|
||||
const { toast } = useToast();
|
||||
const { hasGeminiKey } = useApiKeys();
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [isGeneratingVideo, setIsGeneratingVideo] = useState(false);
|
||||
|
||||
@@ -288,29 +290,101 @@ export default function CardItem({ card, onDelete, onUpdate, isDeleting }: CardI
|
||||
|
||||
{/* Video Generation Controls */}
|
||||
{!card.videoUrl && card.videoGenerationStatus !== 'generating' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleGenerateVideo}
|
||||
disabled={isGeneratingVideo}
|
||||
className="w-full bg-gradient-to-r from-purple-50 to-pink-50 hover:from-purple-100 hover:to-pink-100 border-purple-200 text-purple-700"
|
||||
>
|
||||
<Video className="h-4 w-4 mr-2" />
|
||||
{isGeneratingVideo ? 'Starting...' : 'Make Card Live'}
|
||||
</Button>
|
||||
hasGeminiKey ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleGenerateVideo}
|
||||
disabled={isGeneratingVideo}
|
||||
className="w-full bg-gradient-to-r from-purple-50 to-pink-50 hover:from-purple-100 hover:to-pink-100 border-purple-200 text-purple-700"
|
||||
>
|
||||
<Video className="h-4 w-4 mr-2" />
|
||||
{isGeneratingVideo ? 'Starting...' : 'Make Card Live'}
|
||||
</Button>
|
||||
) : (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full bg-yellow-50 hover:bg-yellow-100 border-yellow-200 text-yellow-700"
|
||||
>
|
||||
<Video className="h-4 w-4 mr-2" />
|
||||
Make Card Live
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-yellow-600" />
|
||||
Gemini API Key Required
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
To generate videos for your cards, you need to configure your Gemini API key in the settings.
|
||||
This feature uses Google's Veo model to create animated videos from your card images.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction asChild>
|
||||
<Link href="/dashboard/settings">
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Configure API Keys
|
||||
</Link>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
)}
|
||||
|
||||
{card.videoGenerationStatus === 'failed' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleGenerateVideo}
|
||||
disabled={isGeneratingVideo}
|
||||
className="w-full border-orange-200 bg-orange-50 hover:bg-orange-100 text-orange-700"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
{isGeneratingVideo ? 'Starting...' : 'Retry Video Generation'}
|
||||
</Button>
|
||||
hasGeminiKey ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleGenerateVideo}
|
||||
disabled={isGeneratingVideo}
|
||||
className="w-full border-orange-200 bg-orange-50 hover:bg-orange-100 text-orange-700"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
{isGeneratingVideo ? 'Starting...' : 'Retry Video Generation'}
|
||||
</Button>
|
||||
) : (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full bg-yellow-50 hover:bg-yellow-100 border-yellow-200 text-yellow-700"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
Retry Video Generation
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-yellow-600" />
|
||||
Gemini API Key Required
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
To retry video generation for your card, you need to configure your Gemini API key in the settings.
|
||||
This feature uses Google's Veo model to create animated videos from your card images.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction asChild>
|
||||
<Link href="/dashboard/settings">
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Configure API Keys
|
||||
</Link>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Download Video Button - Show when video is ready */}
|
||||
|
||||
@@ -10,11 +10,13 @@ import { Loader2, Upload, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import CardForm, { type CardFormInputs } from './CardForm';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useApiKeys } from '@/hooks/useApiKeys';
|
||||
import { addCardToCollection } from '@/lib/firestore';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../ui/card';
|
||||
|
||||
export default function CardScanner() {
|
||||
const { hasGeminiKey } = useApiKeys();
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [scanResult, setScanResult] = useState<ScanPokemonCardOutput | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -53,6 +55,12 @@ export default function CardScanner() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasGeminiKey) {
|
||||
setError('Gemini API key is required to scan Pokemon cards. Please configure it in Settings.');
|
||||
toast({ title: 'API Key Required', description: 'Gemini API key is required to scan Pokemon cards. Please configure it in Settings.', variant: 'destructive' });
|
||||
return;
|
||||
}
|
||||
|
||||
setIsScanning(true);
|
||||
setError(null);
|
||||
setScanResult(null);
|
||||
@@ -179,24 +187,36 @@ export default function CardScanner() {
|
||||
</div>
|
||||
|
||||
{imageDataUrl && (
|
||||
<Button
|
||||
onClick={handleScan}
|
||||
disabled={isScanning || !imageDataUrl}
|
||||
className="w-full py-6 text-lg font-semibold"
|
||||
size="lg"
|
||||
>
|
||||
{isScanning ? (
|
||||
<>
|
||||
<Loader2 className="mr-3 h-5 w-5 animate-spin" />
|
||||
Analyzing Card...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="mr-3 h-5 w-5" />
|
||||
Scan Card
|
||||
</>
|
||||
<>
|
||||
<Button
|
||||
onClick={handleScan}
|
||||
disabled={isScanning || !imageDataUrl || !hasGeminiKey}
|
||||
className="w-full py-6 text-lg font-semibold"
|
||||
size="lg"
|
||||
>
|
||||
{isScanning ? (
|
||||
<>
|
||||
<Loader2 className="mr-3 h-5 w-5 animate-spin" />
|
||||
Analyzing Card...
|
||||
</>
|
||||
) : !hasGeminiKey ? (
|
||||
'Gemini API Key Required'
|
||||
) : (
|
||||
<>
|
||||
<Upload className="mr-3 h-5 w-5" />
|
||||
Scan Card
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{!hasGeminiKey && (
|
||||
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<p className="text-sm text-yellow-700">
|
||||
Please configure your Gemini API key in <strong>Settings</strong> to scan Pokemon cards.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,6 +16,7 @@ import Image from 'next/image';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { downloadCardFromBase64 } from '@/utils/downloadUtils';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useApiKeys } from '@/hooks/useApiKeys';
|
||||
|
||||
export interface GenerateFromPhotoInput {
|
||||
photoDataUri: string;
|
||||
@@ -75,6 +76,7 @@ interface PhotoCardGeneratorProps {
|
||||
|
||||
export function PhotoCardGenerator({ onCardGenerated, initialValues }: PhotoCardGeneratorProps) {
|
||||
const { user } = useAuth();
|
||||
const { hasOpenAIKey } = useApiKeys();
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [generatedCard, setGeneratedCard] = useState<{ imageBase64: string; prompt: string } | null>(null);
|
||||
const [error, setError] = useState<string>('');
|
||||
@@ -162,6 +164,11 @@ export function PhotoCardGenerator({ onCardGenerated, initialValues }: PhotoCard
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasOpenAIKey) {
|
||||
setError('OpenAI API key is required to generate cards from photos. Please configure it in Settings.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.photoDataUri) {
|
||||
toast({
|
||||
title: 'Photo Required',
|
||||
@@ -432,17 +439,31 @@ export function PhotoCardGenerator({ onCardGenerated, initialValues }: PhotoCard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={isGenerating || !imagePreview}>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isGenerating || !imagePreview || !hasOpenAIKey}
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Generating Card from Photo...
|
||||
</>
|
||||
) : !hasOpenAIKey ? (
|
||||
'OpenAI API Key Required'
|
||||
) : (
|
||||
'Generate Pokemon Card from Photo'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{!hasOpenAIKey && (
|
||||
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<p className="text-sm text-yellow-700">
|
||||
Please configure your OpenAI API key in <strong>Settings</strong> to generate cards from photos.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-md">
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { getUserApiKeys } from '@/lib/firestore';
|
||||
import type { UserApiKeys } from '@/types';
|
||||
|
||||
interface UseApiKeysResult {
|
||||
apiKeys: UserApiKeys | null;
|
||||
loading: boolean;
|
||||
hasGeminiKey: boolean;
|
||||
hasOpenAIKey: boolean;
|
||||
hasAnyKey: boolean;
|
||||
refreshApiKeys: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useApiKeys(): UseApiKeysResult {
|
||||
const { user } = useAuth();
|
||||
const [apiKeys, setApiKeys] = useState<UserApiKeys | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const loadApiKeys = async () => {
|
||||
if (!user) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const keys = await getUserApiKeys(user.uid);
|
||||
setApiKeys(keys);
|
||||
} catch (error) {
|
||||
console.error('Error loading API keys:', error);
|
||||
setApiKeys(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadApiKeys();
|
||||
}, [user]);
|
||||
|
||||
const refreshApiKeys = async () => {
|
||||
setLoading(true);
|
||||
await loadApiKeys();
|
||||
};
|
||||
|
||||
const hasGeminiKey = !!(apiKeys?.geminiApiKey);
|
||||
const hasOpenAIKey = !!(apiKeys?.openaiApiKey);
|
||||
const hasAnyKey = hasGeminiKey || hasOpenAIKey;
|
||||
|
||||
return {
|
||||
apiKeys,
|
||||
loading,
|
||||
hasGeminiKey,
|
||||
hasOpenAIKey,
|
||||
hasAnyKey,
|
||||
refreshApiKeys,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user