Files
flyer-crawler.projectium.com/components/SystemCheck.tsx

178 lines
7.5 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import { supabase, invokeSystemCheckFunction } from '../services/supabaseClient';
import { ShieldCheckIcon } from './icons/ShieldCheckIcon';
import { LoadingSpinner } from './LoadingSpinner';
import { CheckCircleIcon } from './icons/CheckCircleIcon';
import { XCircleIcon } from './icons/XCircleIcon';
import { DatabaseSeeder } from './DatabaseSeeder';
type TestStatus = 'idle' | 'running' | 'pass' | 'fail';
interface Check {
id: string;
name: string;
status: TestStatus;
message: string;
}
const initialChecks: Check[] = [
{ id: 'schema', name: 'Database Schema', status: 'idle', message: 'Verifies required tables exist.' },
{ id: 'rls', name: 'RLS Policies', status: 'idle', message: 'Verifies key security policies are active.' },
{ id: 'trigger', name: 'User Creation Trigger', status: 'idle', message: 'Checks function security configuration.' },
{ id: 'storage', name: 'Storage Bucket', status: 'idle', message: "Checks 'flyers' bucket exists and is public." },
{ id: 'functions', name: 'Edge Functions', status: 'idle', message: "Verifies 'delete-user' and 'seed-database' are deployed." },
{ id: 'seed', name: 'Seeded Users', status: 'idle', message: 'Verifies default development users exist.' },
];
interface SystemCheckProps {
onReady: () => void;
}
export const SystemCheck: React.FC<SystemCheckProps> = ({ onReady }) => {
const [checks, setChecks] = useState<Check[]>(initialChecks);
const [isRunning, setIsRunning] = useState(false);
const [hasRunAutoTest, setHasRunAutoTest] = useState(false);
const [showSeeder, setShowSeeder] = useState(false);
const updateCheckStatus = (id: string, status: TestStatus, message: string) => {
setChecks(prev => prev.map(c => c.id === id ? { ...c, status, message } : c));
};
const runChecks = useCallback(async () => {
setIsRunning(true);
setShowSeeder(false);
setChecks(prev => prev.map(c => ({ ...c, status: 'running', message: 'Checking...' })));
let allTestsPassed = true;
// Step 1: Backend Schema, RLS, Trigger, Storage checks via Edge Function
try {
const results = await invokeSystemCheckFunction();
for (const key in results) {
const { pass, message } = results[key];
updateCheckStatus(key, pass ? 'pass' : 'fail', message);
if (!pass) allTestsPassed = false;
}
} catch (e: any) {
allTestsPassed = false;
const failedCheckIds = ['schema', 'rls', 'trigger', 'storage'];
failedCheckIds.forEach(id => updateCheckStatus(id, 'fail', e.message));
}
if (!allTestsPassed) {
setIsRunning(false);
return;
}
// Step 2: Edge Function Deployment
try {
// Test if functions are deployed by calling them. A "Not found" error is a failure.
// Any other error (like missing body) is a pass for this check's purpose.
const { error: seedError } = await supabase.functions.invoke('seed-database', {body: {}});
if (seedError && seedError.message.includes('Not found')) throw new Error("'seed-database' function not found.");
const { error: deleteError } = await supabase.functions.invoke('delete-user', {body: {}});
if (deleteError && deleteError.message.includes('Not found')) throw new Error("'delete-user' function not found.");
updateCheckStatus('functions', 'pass', 'All required Edge Functions are deployed.');
} catch (e: any) {
allTestsPassed = false;
updateCheckStatus('functions', 'fail', `${e.message} Please deploy it via the Supabase CLI.`);
}
if (!allTestsPassed) {
setIsRunning(false);
return;
}
// Step 3: Seeded User Login
try {
const { error } = await supabase.auth.signInWithPassword({
email: 'admin@example.com',
password: 'password123',
});
if (error) throw error;
await supabase.auth.signOut();
updateCheckStatus('seed', 'pass', 'Default admin user login verified.');
} catch (e: any) {
allTestsPassed = false;
const message = e.message.includes('Invalid login credentials')
? "Invalid login credentials. The seeded users are missing from your database."
: `Failed: ${e.message}`;
updateCheckStatus('seed', 'fail', message);
setShowSeeder(true);
}
setIsRunning(false);
if (allTestsPassed) {
onReady();
}
}, [onReady]);
useEffect(() => {
if (supabase && !hasRunAutoTest) {
setHasRunAutoTest(true);
runChecks();
}
}, [supabase, hasRunAutoTest, runChecks]);
const getStatusIndicator = (status: TestStatus) => {
switch (status) {
case 'running': return <div className="w-5 h-5 text-blue-500"><LoadingSpinner /></div>;
case 'pass': return <CheckCircleIcon className="w-5 h-5 text-green-500" />;
case 'fail': return <XCircleIcon className="w-5 h-5 text-red-500" />;
case 'idle': return <div className="w-5 h-5 rounded-full border-2 border-gray-400 dark:border-gray-600"></div>;
default: return null;
}
};
if (!supabase) return null;
return (
<div className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<h3 className="text-lg font-bold text-gray-800 dark:text-white flex items-center mb-3">
<ShieldCheckIcon className="w-6 h-6 mr-2 text-brand-primary" />
System Check
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
This checklist verifies your Supabase setup against the README instructions.
</p>
<ul className="space-y-3 mb-4">
{checks.map(check => (
<li key={check.id} className="flex items-start space-x-3">
<div className="flex-shrink-0 pt-0.5">{getStatusIndicator(check.status)}</div>
<div>
<p className="text-sm font-semibold text-gray-800 dark:text-gray-200">{check.name}</p>
<p className={`text-xs whitespace-pre-wrap ${check.status === 'fail' ? 'text-red-600 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`}>
{check.message}
</p>
</div>
</li>
))}
</ul>
{showSeeder && (
<div className="my-4">
<DatabaseSeeder onSuccess={runChecks} />
</div>
)}
<button
onClick={runChecks}
disabled={isRunning}
className="w-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-wait text-gray-800 dark:text-white font-bold py-2 px-4 rounded-lg transition-colors duration-300 flex items-center justify-center"
>
{isRunning ? (
<>
<div className="w-5 h-5 mr-2"><LoadingSpinner /></div>
Running Checks...
</>
) : (
'Re-run Checks'
)}
</button>
</div>
);
};