import React, { useEffect, useState } from 'react'; import { motion } from 'framer-motion'; import { toast } from 'react-toastify'; import { useStore } from '@nanostores/react'; import { classNames } from '~/utils/classNames'; import { Button } from '~/components/ui/Button'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '~/components/ui/Collapsible'; import { supabaseConnection, isConnecting, isFetchingStats, isFetchingApiKeys, updateSupabaseConnection, fetchSupabaseStats, fetchProjectApiKeys, initializeSupabaseConnection, type SupabaseProject, } from '~/lib/stores/supabase'; interface ConnectionTestResult { status: 'success' | 'error' | 'testing'; message: string; timestamp?: number; } interface ProjectAction { name: string; icon: string; action: (projectId: string) => Promise; requiresConfirmation?: boolean; variant?: 'default' | 'destructive' | 'outline'; } // Supabase logo SVG component const SupabaseLogo = () => ( ); export default function SupabaseTab() { const connection = useStore(supabaseConnection); const connecting = useStore(isConnecting); const fetchingStats = useStore(isFetchingStats); const fetchingApiKeys = useStore(isFetchingApiKeys); const [tokenInput, setTokenInput] = useState(''); const [isProjectsExpanded, setIsProjectsExpanded] = useState(false); const [connectionTest, setConnectionTest] = useState(null); const [isProjectActionLoading, setIsProjectActionLoading] = useState(false); const [selectedProjectId, setSelectedProjectId] = useState(''); // Connection testing function - uses server-side API to test environment token const testConnection = async () => { setConnectionTest({ status: 'testing', message: 'Testing connection...', }); try { const response = await fetch('/api/supabase-user', { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (response.ok) { const data = (await response.json()) as any; setConnectionTest({ status: 'success', message: `Connected successfully using environment token. Found ${data.projects?.length || 0} projects`, timestamp: Date.now(), }); } else { const errorData = (await response.json().catch(() => ({}))) as { error?: string }; setConnectionTest({ status: 'error', message: `Connection failed: ${errorData.error || `${response.status} ${response.statusText}`}`, timestamp: Date.now(), }); } } catch (error) { setConnectionTest({ status: 'error', message: `Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, timestamp: Date.now(), }); } }; // Project actions const projectActions: ProjectAction[] = [ { name: 'Get API Keys', icon: 'i-ph:key', action: async (projectId: string) => { try { await fetchProjectApiKeys(projectId, connection.token); toast.success('API keys fetched successfully'); } catch (err: unknown) { const error = err instanceof Error ? err.message : 'Unknown error'; toast.error(`Failed to fetch API keys: ${error}`); } }, }, { name: 'View Dashboard', icon: 'i-ph:layout', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}`, '_blank'); }, }, { name: 'View Database', icon: 'i-ph:database', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/editor`, '_blank'); }, }, { name: 'View Auth', icon: 'i-ph:user-circle', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/auth/users`, '_blank'); }, }, { name: 'View Storage', icon: 'i-ph:folder', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/storage/buckets`, '_blank'); }, }, { name: 'View Functions', icon: 'i-ph:code', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/functions`, '_blank'); }, }, { name: 'View Logs', icon: 'i-ph:scroll', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/logs`, '_blank'); }, }, { name: 'View Settings', icon: 'i-ph:gear', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/settings`, '_blank'); }, }, { name: 'View API Docs', icon: 'i-ph:book', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/api`, '_blank'); }, }, { name: 'View Realtime', icon: 'i-ph:radio', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/realtime`, '_blank'); }, }, { name: 'View Edge Functions', icon: 'i-ph:terminal', action: async (projectId: string) => { window.open(`https://supabase.com/dashboard/project/${projectId}/functions`, '_blank'); }, }, ]; // Initialize connection on component mount - check server-side token first useEffect(() => { const initializeConnection = async () => { try { // First try to initialize using server-side token await initializeSupabaseConnection(); // If no connection was established, the user will need to manually enter a token const currentState = supabaseConnection.get(); if (!currentState.user) { console.log('No server-side Supabase token available, manual connection required'); } } catch (error) { console.error('Failed to initialize Supabase connection:', error); } }; initializeConnection(); }, []); useEffect(() => { const fetchProjects = async () => { if (connection.user && connection.token && !connection.stats) { await fetchSupabaseStats(connection.token); } }; fetchProjects(); }, [connection.user, connection.token]); const handleConnect = async () => { if (!tokenInput) { toast.error('Please enter a Supabase access token'); return; } isConnecting.set(true); try { await fetchSupabaseStats(tokenInput); updateSupabaseConnection({ token: tokenInput, isConnected: true, }); toast.success('Successfully connected to Supabase'); setTokenInput(''); } catch (error) { console.error('Auth error:', error); toast.error('Failed to connect to Supabase'); updateSupabaseConnection({ user: null, token: '' }); } finally { isConnecting.set(false); } }; const handleDisconnect = () => { updateSupabaseConnection({ user: null, token: '', stats: undefined, selectedProjectId: undefined, isConnected: false, project: undefined, credentials: undefined, }); setConnectionTest(null); setSelectedProjectId(''); toast.success('Disconnected from Supabase'); }; const handleProjectAction = async (projectId: string, action: ProjectAction) => { if (action.requiresConfirmation) { if (!confirm(`Are you sure you want to ${action.name.toLowerCase()}?`)) { return; } } setIsProjectActionLoading(true); await action.action(projectId); setIsProjectActionLoading(false); }; const handleProjectSelect = async (projectId: string) => { setSelectedProjectId(projectId); updateSupabaseConnection({ selectedProjectId: projectId }); if (projectId && connection.token) { try { await fetchProjectApiKeys(projectId, connection.token); } catch (error) { console.error('Failed to fetch API keys:', error); } } }; const renderProjects = () => { if (fetchingStats) { return (
Fetching Supabase projects...
); } return (
Your Projects ({connection.stats?.totalProjects || 0})
{/* Supabase Overview Dashboard */} {connection.stats?.projects?.length ? (

Supabase Overview

{connection.stats.totalProjects}
Total Projects
{connection.stats.projects.filter((p: SupabaseProject) => p.status === 'ACTIVE_HEALTHY').length}
Active Projects
{new Set(connection.stats.projects.map((p: SupabaseProject) => p.region)).size}
Regions Used
{connection.stats.projects.filter((p: SupabaseProject) => p.status !== 'ACTIVE_HEALTHY').length}
Inactive Projects
) : null} {connection.stats?.projects?.length ? (
{connection.stats.projects.map((project: SupabaseProject) => (
handleProjectSelect(project.id)} >
{project.name}
{project.region}
{new Date(project.created_at).toLocaleDateString()}
{project.status.replace('_', ' ')}
{/* Project Details Grid */}
{project.stats?.database?.tables ?? '--'}
Tables
{project.stats?.storage?.buckets ?? '--'}
Buckets
{project.stats?.functions?.deployed ?? '--'}
Functions
{project.stats?.database?.size_mb ? `${project.stats.database.size_mb} MB` : '--'}
DB Size
{selectedProjectId === project.id && (
{projectActions.map((action) => ( ))}
{/* Project Details */}
Database Schema
Tables: {project.stats?.database?.tables ?? '--'}
Views: {project.stats?.database?.views ?? '--'}
Functions: {project.stats?.database?.functions ?? '--'}
Size: {project.stats?.database?.size_mb ? `${project.stats.database.size_mb} MB` : '--'}
Storage
Buckets: {project.stats?.storage?.buckets ?? '--'}
Files: {project.stats?.storage?.files ?? '--'}
Used: {project.stats?.storage?.used_gb ? `${project.stats.storage.used_gb} GB` : '--'}
Available: {project.stats?.storage?.available_gb ? `${project.stats.storage.available_gb} GB` : '--'}
{connection.credentials && (
Project Credentials
)}
)}
))}
) : (
No projects found in your Supabase account
)}
); }; return (
{/* Header */}

Supabase Integration

{connection.user && ( )}

Connect and manage your Supabase projects with database access, authentication, and storage controls

{/* Connection Test Results */} {connectionTest && (
{connectionTest.status === 'success' && (
)} {connectionTest.status === 'error' && (
)} {connectionTest.status === 'testing' && (
)} {connectionTest.message}
{connectionTest.timestamp && (

{new Date(connectionTest.timestamp).toLocaleString()}

)} )} {/* Main Connection Component */}
{!connection.user ? (

Tip: You can also set the{' '} VITE_SUPABASE_ACCESS_TOKEN {' '} environment variable to connect automatically.

setTokenInput(e.target.value)} disabled={connecting} placeholder="Enter your Supabase access token" className={classNames( 'w-full px-3 py-2 rounded-lg text-sm', 'bg-[#F8F8F8] dark:bg-[#1A1A1A]', 'border border-[#E5E5E5] dark:border-[#333333]', 'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary', 'focus:outline-none focus:ring-1 focus:ring-bolt-elements-borderColorActive', 'disabled:opacity-50', )} />
) : (
Connected to Supabase
{connection.user && (

{connection.user.email}

{connection.user.role} • Member since{' '} {new Date(connection.user.created_at).toLocaleDateString()}

{connection.stats?.totalProjects || 0} Projects
{new Set(connection.stats?.projects?.map((p: SupabaseProject) => p.region) || []).size}{' '} Regions
{connection.stats?.projects?.filter((p: SupabaseProject) => p.status === 'ACTIVE_HEALTHY') .length || 0}{' '} Active
{/* Advanced Analytics */}

Performance Analytics

Database Health
{(() => { const totalProjects = connection.stats?.totalProjects || 0; const activeProjects = connection.stats?.projects?.filter((p: SupabaseProject) => p.status === 'ACTIVE_HEALTHY') .length || 0; const healthRate = totalProjects > 0 ? Math.round((activeProjects / totalProjects) * 100) : 0; const avgTablesPerProject = totalProjects > 0 ? Math.round( (connection.stats?.projects?.reduce( (sum, p) => sum + (p.stats?.database?.tables || 0), 0, ) || 0) / totalProjects, ) : 0; return [ { label: 'Health Rate', value: `${healthRate}%` }, { label: 'Active Projects', value: activeProjects }, { label: 'Avg Tables/Project', value: avgTablesPerProject }, ]; })().map((item, idx) => (
{item.label}: {item.value}
))}
Auth & Security
{(() => { const totalProjects = connection.stats?.totalProjects || 0; const projectsWithAuth = connection.stats?.projects?.filter((p) => p.stats?.auth?.users !== undefined).length || 0; const authEnabledRate = totalProjects > 0 ? Math.round((projectsWithAuth / totalProjects) * 100) : 0; const totalUsers = connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.auth?.users || 0), 0) || 0; return [ { label: 'Auth Enabled', value: `${authEnabledRate}%` }, { label: 'Total Users', value: totalUsers }, { label: 'Avg Users/Project', value: totalProjects > 0 ? Math.round(totalUsers / totalProjects) : 0, }, ]; })().map((item, idx) => (
{item.label}: {item.value}
))}
Regional Distribution
{(() => { const regions = connection.stats?.projects?.reduce( (acc, p: SupabaseProject) => { acc[p.region] = (acc[p.region] || 0) + 1; return acc; }, {} as Record, ) || {}; return Object.entries(regions) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([region, count]) => ({ label: region.toUpperCase(), value: count })); })().map((item, idx) => (
{item.label}: {item.value}
))}
{/* Resource Utilization */}

Resource Overview

{(() => { const totalDatabase = connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.database?.size_mb || 0), 0) || 0; const totalStorage = connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.storage?.used_gb || 0), 0) || 0; const totalFunctions = connection.stats?.projects?.reduce( (sum, p) => sum + (p.stats?.functions?.deployed || 0), 0, ) || 0; const totalTables = connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.database?.tables || 0), 0) || 0; const totalBuckets = connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.storage?.buckets || 0), 0) || 0; return [ { label: 'Database', value: totalDatabase > 0 ? `${totalDatabase} MB` : '--', icon: 'i-ph:database', color: 'text-blue-500', bgColor: 'bg-blue-100 dark:bg-blue-900/20', textColor: 'text-blue-800 dark:text-blue-400', }, { label: 'Storage', value: totalStorage > 0 ? `${totalStorage} GB` : '--', icon: 'i-ph:folder', color: 'text-green-500', bgColor: 'bg-green-100 dark:bg-green-900/20', textColor: 'text-green-800 dark:text-green-400', }, { label: 'Functions', value: totalFunctions, icon: 'i-ph:code', color: 'text-purple-500', bgColor: 'bg-purple-100 dark:bg-purple-900/20', textColor: 'text-purple-800 dark:text-purple-400', }, { label: 'Tables', value: totalTables, icon: 'i-ph:table', color: 'text-orange-500', bgColor: 'bg-orange-100 dark:bg-orange-900/20', textColor: 'text-orange-800 dark:text-orange-400', }, { label: 'Buckets', value: totalBuckets, icon: 'i-ph:archive', color: 'text-teal-500', bgColor: 'bg-teal-100 dark:bg-teal-900/20', textColor: 'text-teal-800 dark:text-teal-400', }, ]; })().map((metric, index) => (
{metric.label}
{metric.value}
))}
{/* Usage Metrics */}
Database
Tables:{' '} {connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.database?.tables || 0), 0) || '--'}
Size:{' '} {(() => { const totalSize = connection.stats?.projects?.reduce( (sum, p) => sum + (p.stats?.database?.size_mb || 0), 0, ) || 0; return totalSize > 0 ? `${totalSize} MB` : '--'; })()}
Storage
Buckets:{' '} {connection.stats?.projects?.reduce((sum, p) => sum + (p.stats?.storage?.buckets || 0), 0) || '--'}
Used:{' '} {(() => { const totalUsed = connection.stats?.projects?.reduce( (sum, p) => sum + (p.stats?.storage?.used_gb || 0), 0, ) || 0; return totalUsed > 0 ? `${totalUsed} GB` : '--'; })()}
Functions
Deployed:{' '} {connection.stats?.projects?.reduce( (sum, p) => sum + (p.stats?.functions?.deployed || 0), 0, ) || '--'}
Invocations:{' '} {connection.stats?.projects?.reduce( (sum, p) => sum + (p.stats?.functions?.invocations || 0), 0, ) || '--'}
)} {renderProjects()}
)}
); }