MVP Version

This commit is contained in:
Stefan Hardegger
2025-07-23 12:28:48 +02:00
parent 59d29dceaf
commit d69bed00a2
22 changed files with 1781 additions and 153 deletions

View File

@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import AppLayout from '../../components/layout/AppLayout';
import { useTheme } from '../../lib/theme';
import Button from '../../components/ui/Button';
import { storyApi, authorApi } from '../../lib/api';
type FontFamily = 'serif' | 'sans' | 'mono';
type FontSize = 'small' | 'medium' | 'large' | 'extra-large';
@@ -27,6 +28,15 @@ export default function SettingsPage() {
const { theme, setTheme } = useTheme();
const [settings, setSettings] = useState<Settings>(defaultSettings);
const [saved, setSaved] = useState(false);
const [typesenseStatus, setTypesenseStatus] = useState<{
stories: { loading: boolean; message: string; success?: boolean };
authors: { loading: boolean; message: string; success?: boolean };
}>({
stories: { loading: false, message: '' },
authors: { loading: false, message: '' }
});
const [authorsSchema, setAuthorsSchema] = useState<any>(null);
const [showSchema, setShowSchema] = useState(false);
// Load settings from localStorage on mount
useEffect(() => {
@@ -85,6 +95,66 @@ export default function SettingsPage() {
setSettings(prev => ({ ...prev, [key]: value }));
};
const handleTypesenseOperation = async (
type: 'stories' | 'authors',
operation: 'reindex' | 'recreate',
apiCall: () => Promise<{ success: boolean; message: string; count?: number; error?: string }>
) => {
setTypesenseStatus(prev => ({
...prev,
[type]: { loading: true, message: 'Processing...', success: undefined }
}));
try {
const result = await apiCall();
setTypesenseStatus(prev => ({
...prev,
[type]: {
loading: false,
message: result.success ? result.message : result.error || 'Operation failed',
success: result.success
}
}));
// Clear message after 5 seconds
setTimeout(() => {
setTypesenseStatus(prev => ({
...prev,
[type]: { loading: false, message: '', success: undefined }
}));
}, 5000);
} catch (error) {
setTypesenseStatus(prev => ({
...prev,
[type]: {
loading: false,
message: 'Network error occurred',
success: false
}
}));
setTimeout(() => {
setTypesenseStatus(prev => ({
...prev,
[type]: { loading: false, message: '', success: undefined }
}));
}, 5000);
}
};
const fetchAuthorsSchema = async () => {
try {
const result = await authorApi.getTypesenseSchema();
if (result.success) {
setAuthorsSchema(result.schema);
} else {
setAuthorsSchema({ error: result.error });
}
} catch (error) {
setAuthorsSchema({ error: 'Failed to fetch schema' });
}
};
return (
<AppLayout>
<div className="max-w-2xl mx-auto space-y-8">
@@ -251,6 +321,119 @@ export default function SettingsPage() {
</div>
</div>
{/* Typesense Search Management */}
<div className="theme-card theme-shadow rounded-lg p-6">
<h2 className="text-xl font-semibold theme-header mb-4">Search Index Management</h2>
<p className="theme-text mb-6">
Manage the Typesense search indexes for stories and authors. Use these tools if search functionality isn't working properly.
</p>
<div className="space-y-6">
{/* Stories Section */}
<div className="border theme-border rounded-lg p-4">
<h3 className="text-lg font-semibold theme-header mb-3">Stories Index</h3>
<div className="flex flex-col sm:flex-row gap-3 mb-3">
<Button
onClick={() => handleTypesenseOperation('stories', 'reindex', storyApi.reindexTypesense)}
disabled={typesenseStatus.stories.loading}
loading={typesenseStatus.stories.loading}
variant="ghost"
className="flex-1"
>
{typesenseStatus.stories.loading ? 'Reindexing...' : 'Reindex Stories'}
</Button>
<Button
onClick={() => handleTypesenseOperation('stories', 'recreate', storyApi.recreateTypesenseCollection)}
disabled={typesenseStatus.stories.loading}
loading={typesenseStatus.stories.loading}
variant="secondary"
className="flex-1"
>
{typesenseStatus.stories.loading ? 'Recreating...' : 'Recreate Collection'}
</Button>
</div>
{typesenseStatus.stories.message && (
<div className={`text-sm p-2 rounded ${
typesenseStatus.stories.success
? 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200'
: 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200'
}`}>
{typesenseStatus.stories.message}
</div>
)}
</div>
{/* Authors Section */}
<div className="border theme-border rounded-lg p-4">
<h3 className="text-lg font-semibold theme-header mb-3">Authors Index</h3>
<div className="flex flex-col sm:flex-row gap-3 mb-3">
<Button
onClick={() => handleTypesenseOperation('authors', 'reindex', authorApi.reindexTypesense)}
disabled={typesenseStatus.authors.loading}
loading={typesenseStatus.authors.loading}
variant="ghost"
className="flex-1"
>
{typesenseStatus.authors.loading ? 'Reindexing...' : 'Reindex Authors'}
</Button>
<Button
onClick={() => handleTypesenseOperation('authors', 'recreate', authorApi.recreateTypesenseCollection)}
disabled={typesenseStatus.authors.loading}
loading={typesenseStatus.authors.loading}
variant="secondary"
className="flex-1"
>
{typesenseStatus.authors.loading ? 'Recreating...' : 'Recreate Collection'}
</Button>
</div>
{typesenseStatus.authors.message && (
<div className={`text-sm p-2 rounded ${
typesenseStatus.authors.success
? 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200'
: 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200'
}`}>
{typesenseStatus.authors.message}
</div>
)}
{/* Debug Schema Section */}
<div className="border-t theme-border pt-3">
<div className="flex items-center gap-2 mb-2">
<Button
onClick={fetchAuthorsSchema}
variant="ghost"
className="text-xs"
>
Inspect Schema
</Button>
<Button
onClick={() => setShowSchema(!showSchema)}
variant="ghost"
className="text-xs"
disabled={!authorsSchema}
>
{showSchema ? 'Hide' : 'Show'} Schema
</Button>
</div>
{showSchema && authorsSchema && (
<div className="text-xs theme-text bg-gray-50 dark:bg-gray-800 p-3 rounded border overflow-auto max-h-48">
<pre>{JSON.stringify(authorsSchema, null, 2)}</pre>
</div>
)}
</div>
</div>
<div className="text-sm theme-text bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg">
<p className="font-medium mb-1">When to use these tools:</p>
<ul className="text-xs space-y-1 ml-4">
<li>• <strong>Reindex:</strong> Refresh search data while keeping the existing schema</li>
<li>• <strong>Recreate Collection:</strong> Delete and rebuild the entire search index (fixes schema issues)</li>
</ul>
</div>
</div>
</div>
{/* Actions */}
<div className="flex justify-end gap-4">
<Button