Skip to main content
Glama
page.tsx11.3 kB
'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { LogOut, Key, Copy, Trash2, Plus, Eye, EyeOff } from 'lucide-react'; import { useAuth } from '@/hooks/useAuth'; import { useApiKeys } from '@/hooks/useApiKeys'; import { ApiKeyModal } from '@/components/dashboard/ApiKeyModal'; export default function DashboardPage() { const router = useRouter(); const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); const { keys, isLoading, error, fetchKeys, generateKey, revokeKey } = useApiKeys(); const [showNewKeyModal, setShowNewKeyModal] = useState(false); const [copiedKeyId, setCopiedKeyId] = useState<string | null>(null); const [visibleKeys, setVisibleKeys] = useState<Set<string>>(new Set()); // Redirect if not authenticated useEffect(() => { if (!authLoading && !isAuthenticated) { router.push('/signin'); } }, [isAuthenticated, authLoading, router]); // Fetch API keys on mount useEffect(() => { if (isAuthenticated) { fetchKeys(); } }, [isAuthenticated, fetchKeys]); const toggleKeyVisibility = (keyId: string) => { setVisibleKeys((prev) => { const newSet = new Set(prev); if (newSet.has(keyId)) { newSet.delete(keyId); } else { newSet.add(keyId); } return newSet; }); }; const copyToClipboard = (text: string, keyId: string) => { navigator.clipboard.writeText(text); setCopiedKeyId(keyId); setTimeout(() => setCopiedKeyId(null), 2000); }; const handleLogout = () => { logout(); router.push('/'); }; const handleKeyGenerated = async () => { setShowNewKeyModal(false); await fetchKeys(); }; if (authLoading) { return ( <main className="min-h-screen bg-gradient-to-b from-black via-purple-900/10 to-black flex items-center justify-center pt-20"> <div className="text-gray-400">Loading...</div> </main> ); } if (!isAuthenticated || !user) { return null; } return ( <main className="min-h-screen bg-gradient-to-b from-black via-purple-900/10 to-black pt-20 pb-12"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> {/* Header */} <div className="flex justify-between items-start mb-12"> <div> <h1 className="text-4xl font-bold text-white mb-2">Dashboard</h1> <p className="text-gray-400">Welcome back, {user.email}</p> </div> <button onClick={handleLogout} className="px-4 py-2 rounded-lg border border-gray-800 text-white hover:border-purple-600 hover:text-purple-300 transition-colors flex items-center gap-2" > <LogOut className="w-5 h-5" /> Sign Out </button> </div> <div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> {/* Sidebar */} <div className="lg:col-span-1"> {/* User Info Card */} <div className="card mb-6"> <h2 className="text-lg font-semibold text-white mb-4">Account</h2> <div className="space-y-4"> <div> <p className="text-sm text-gray-400 mb-1">Email</p> <p className="text-white font-medium">{user.email}</p> </div> <div> <p className="text-sm text-gray-400 mb-1">Plan</p> <div className="flex items-center gap-2"> <span className="px-3 py-1 rounded-full text-sm font-semibold bg-purple-600/20 text-purple-300 capitalize"> {user.tier} </span> {user.tier === 'free' && ( <Link href="/pricing" className="text-xs text-purple-400 hover:text-purple-300" > Upgrade </Link> )} </div> </div> <div> <p className="text-sm text-gray-400 mb-1">Member Since</p> <p className="text-white font-medium"> {new Date(user.created_at).toLocaleDateString()} </p> </div> </div> </div> {/* Quick Links */} <div className="card"> <h2 className="text-lg font-semibold text-white mb-4">Quick Links</h2> <div className="space-y-2"> <Link href="/documentation" className="block px-3 py-2 rounded-lg text-gray-300 hover:bg-purple-600/10 hover:text-white transition-colors" > Browse Documentation </Link> <Link href="/api-docs" className="block px-3 py-2 rounded-lg text-gray-300 hover:bg-purple-600/10 hover:text-white transition-colors" > API Documentation </Link> {user.tier !== 'free' && ( <Link href="/admin" className="block px-3 py-2 rounded-lg text-gray-300 hover:bg-purple-600/10 hover:text-white transition-colors" > Admin Panel </Link> )} </div> </div> </div> {/* Main Content */} <div className="lg:col-span-2"> {/* API Keys Section */} <div className="card"> <div className="flex justify-between items-center mb-6"> <h2 className="text-lg font-semibold text-white flex items-center gap-2"> <Key className="w-5 h-5 text-purple-400" /> API Keys </h2> <button onClick={() => setShowNewKeyModal(true)} className="px-4 py-2 rounded-lg bg-gradient-to-r from-purple-600 to-pink-600 text-white font-semibold hover:shadow-lg hover:shadow-purple-600/50 transition-all duration-200 flex items-center gap-2" > <Plus className="w-4 h-4" /> Create Key </button> </div> {error && ( <div className="p-3 rounded-lg bg-red-600/20 border border-red-600/50 mb-4"> <p className="text-sm text-red-300">{error}</p> </div> )} {keys.length === 0 ? ( <div className="text-center py-8"> <p className="text-gray-400 mb-4">No API keys yet</p> <button onClick={() => setShowNewKeyModal(true)} className="text-purple-400 hover:text-purple-300 text-sm font-medium" > Create your first API key </button> </div> ) : ( <div className="space-y-4"> {keys.map((key) => ( <div key={key.id} className="p-4 rounded-lg border border-gray-800 bg-gray-900/30 hover:border-purple-600/50 transition-colors" > <div className="flex items-start justify-between mb-3"> <div className="flex-1"> <h3 className="text-white font-semibold">{key.name}</h3> <p className="text-xs text-gray-500 mt-1"> Created {new Date(key.created_at).toLocaleDateString()} </p> </div> <span className="px-2 py-1 rounded text-xs font-semibold bg-green-600/20 text-green-300"> {key.is_active ? 'Active' : 'Inactive'} </span> </div> {/* Key Preview */} <div className="bg-black/30 rounded p-3 mb-4 font-mono text-sm"> <div className="flex items-center justify-between"> <span className="text-gray-400"> {visibleKeys.has(key.id) ? key.key_prefix + '_' + '•'.repeat(20) : key.key_prefix + '_••••••••••••••••••••'} </span> <div className="flex gap-2"> <button onClick={() => toggleKeyVisibility(key.id)} className="text-gray-500 hover:text-gray-300" > {visibleKeys.has(key.id) ? ( <EyeOff className="w-4 h-4" /> ) : ( <Eye className="w-4 h-4" /> )} </button> <button onClick={() => copyToClipboard(key.key_prefix + '_secret', key.id) } className="text-gray-500 hover:text-gray-300" > <Copy className="w-4 h-4" /> </button> </div> </div> </div> {/* Stats */} <div className="grid grid-cols-2 gap-4 mb-4 text-sm"> <div> <p className="text-gray-500 text-xs mb-1">Rate Limit</p> <p className="text-white font-medium"> {key.rate_limit_rpm} req/min </p> </div> <div> <p className="text-gray-500 text-xs mb-1">Daily Limit</p> <p className="text-white font-medium"> {key.rate_limit_rpd.toLocaleString()} req/day </p> </div> </div> {/* Revoke Button */} <button onClick={() => revokeKey(key.id)} disabled={isLoading} className="w-full px-3 py-2 rounded-lg border border-red-600/50 text-red-400 hover:bg-red-600/10 transition-colors disabled:opacity-50 flex items-center justify-center gap-2 text-sm" > <Trash2 className="w-4 h-4" /> Revoke Key </button> </div> ))} </div> )} </div> </div> </div> </div> {/* API Key Modal */} {showNewKeyModal && ( <ApiKeyModal isOpen={showNewKeyModal} onClose={() => setShowNewKeyModal(false)} onKeyGenerated={handleKeyGenerated} isLoading={isLoading} /> )} </main> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aiatamai/atamai-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server