refactor(card-editor): useCardEditor + CardEditorView (Brief 3) (#94)
Extract admin editor state into useCardEditor and UI into CardEditorView; keep dynamic(ssr: false) on the page export. Page is ~40 lines. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d10bf1614b
commit
036e303b9e
3 changed files with 298 additions and 249 deletions
100
components/CardEditorView.js
Normal file
100
components/CardEditorView.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import CardEditorForm from './CardEditorForm';
|
||||||
|
import CardEditorPreview from './CardEditorPreview';
|
||||||
|
import CardEditorSearchPanel from './CardEditorSearchPanel';
|
||||||
|
|
||||||
|
export default function CardEditorView({
|
||||||
|
card,
|
||||||
|
formData,
|
||||||
|
handleColorChange,
|
||||||
|
handleInputChange,
|
||||||
|
handleSave,
|
||||||
|
message,
|
||||||
|
onSearchQueryChange,
|
||||||
|
router,
|
||||||
|
saving,
|
||||||
|
searchLoading,
|
||||||
|
searchQuery,
|
||||||
|
searchResults,
|
||||||
|
selectCard,
|
||||||
|
showEmptyState,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-6 py-8">
|
||||||
|
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
Admin Tools
|
||||||
|
</h1>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => router.push('/admin/card-editor')}
|
||||||
|
className="px-4 py-2 rounded-lg font-medium gradient-bg-purple text-white"
|
||||||
|
>
|
||||||
|
🖊️ Card Editor
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => router.push('/admin/card-import')}
|
||||||
|
className="px-4 py-2 rounded-lg font-medium transition-all duration-200 border"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--bg-primary)',
|
||||||
|
borderColor: 'var(--border)',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📥 Card Import
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-3xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
Admin Card Editor
|
||||||
|
</h2>
|
||||||
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
Search for a card to edit its details, or continue editing the selected card.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardEditorSearchPanel
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
onSearchQueryChange={onSearchQueryChange}
|
||||||
|
searchLoading={searchLoading}
|
||||||
|
searchResults={searchResults}
|
||||||
|
onSelectCard={selectCard}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{card && (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<CardEditorPreview formData={formData} />
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<CardEditorForm
|
||||||
|
formData={formData}
|
||||||
|
onInputChange={handleInputChange}
|
||||||
|
onColorChange={handleColorChange}
|
||||||
|
onSave={handleSave}
|
||||||
|
saving={saving}
|
||||||
|
message={message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showEmptyState && (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="text-6xl mb-4">🔍</div>
|
||||||
|
<h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
Search for a Card to Edit
|
||||||
|
</h2>
|
||||||
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
Use the search box above to find a card you want to edit.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
185
lib/use-card-editor.js
Normal file
185
lib/use-card-editor.js
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const EMPTY_FORM = {
|
||||||
|
name: '',
|
||||||
|
set_name: '',
|
||||||
|
set_code: '',
|
||||||
|
card_number: '',
|
||||||
|
rarity: '',
|
||||||
|
game: '',
|
||||||
|
mana_cost: '',
|
||||||
|
cmc: '',
|
||||||
|
card_type: '',
|
||||||
|
colors: [],
|
||||||
|
oracle_text: '',
|
||||||
|
power: '',
|
||||||
|
toughness: '',
|
||||||
|
image_url: '',
|
||||||
|
stock_image_url: '',
|
||||||
|
current_price: '',
|
||||||
|
market_price: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin card editor state and handlers (god-component split).
|
||||||
|
*/
|
||||||
|
export function useCardEditor(router) {
|
||||||
|
const { id } = router.query;
|
||||||
|
|
||||||
|
const [card, setCard] = useState(null);
|
||||||
|
const [fetchLoading, setFetchLoading] = useState(false);
|
||||||
|
const loading = id ? fetchLoading : false;
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
|
const [searchLoading, setSearchLoading] = useState(false);
|
||||||
|
const [formData, setFormData] = useState(EMPTY_FORM);
|
||||||
|
|
||||||
|
const fetchCard = async (cardId) => {
|
||||||
|
setFetchLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/cards/${cardId}`);
|
||||||
|
if (response.ok) {
|
||||||
|
const cardData = await response.json();
|
||||||
|
setCard(cardData);
|
||||||
|
setFormData({
|
||||||
|
name: cardData.name || '',
|
||||||
|
set_name: cardData.set_name || '',
|
||||||
|
set_code: cardData.set_code || '',
|
||||||
|
card_number: cardData.card_number || '',
|
||||||
|
rarity: cardData.rarity || '',
|
||||||
|
game: cardData.game || '',
|
||||||
|
mana_cost: cardData.mana_cost || '',
|
||||||
|
cmc: cardData.cmc || '',
|
||||||
|
card_type: cardData.card_type || '',
|
||||||
|
colors: Array.isArray(cardData.colors) ? cardData.colors : [],
|
||||||
|
oracle_text: cardData.oracle_text || '',
|
||||||
|
power: cardData.power || '',
|
||||||
|
toughness: cardData.toughness || '',
|
||||||
|
image_url: cardData.image_url || '',
|
||||||
|
stock_image_url: cardData.stock_image_url || '',
|
||||||
|
current_price: cardData.current_price || '',
|
||||||
|
market_price: cardData.market_price || '',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setMessage('Card not found');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setMessage('Error loading card: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
setFetchLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- load card when route id is set
|
||||||
|
fetchCard(id);
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const searchCards = async (query) => {
|
||||||
|
if (!query.trim()) {
|
||||||
|
setSearchResults([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/cards/search?query=${encodeURIComponent(query)}&limit=20`
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setSearchResults(data.cards || []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Search error:', error);
|
||||||
|
} finally {
|
||||||
|
setSearchLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (field, value) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleColorChange = (color, checked) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
colors: checked
|
||||||
|
? [...prev.colors, color]
|
||||||
|
: prev.colors.filter((c) => c !== color),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!card?.id) {
|
||||||
|
setMessage('No card selected to edit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSaving(true);
|
||||||
|
setMessage('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/cards/${card.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...formData,
|
||||||
|
cmc: formData.cmc ? parseInt(formData.cmc, 10) : null,
|
||||||
|
current_price: formData.current_price ? parseFloat(formData.current_price) : null,
|
||||||
|
market_price: formData.market_price ? parseFloat(formData.market_price) : null,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const updatedCard = await response.json();
|
||||||
|
setCard(updatedCard);
|
||||||
|
setMessage('Card updated successfully!');
|
||||||
|
setTimeout(() => setMessage(''), 3000);
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
setMessage('Error updating card: ' + (errorData.error || 'Unknown error'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setMessage('Error updating card: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectCard = (selectedCard) => {
|
||||||
|
router.push(`/admin/card-editor?id=${selectedCard.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearchQueryChange = (value) => {
|
||||||
|
setSearchQuery(value);
|
||||||
|
searchCards(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
card,
|
||||||
|
formData,
|
||||||
|
handleColorChange,
|
||||||
|
handleInputChange,
|
||||||
|
handleSave,
|
||||||
|
loading,
|
||||||
|
message,
|
||||||
|
onSearchQueryChange,
|
||||||
|
router,
|
||||||
|
saving,
|
||||||
|
searchLoading,
|
||||||
|
searchQuery,
|
||||||
|
searchResults,
|
||||||
|
selectCard,
|
||||||
|
showEmptyState: !card && !loading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,180 +1,24 @@
|
||||||
/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Layout from '../../components/Layout';
|
import Layout from '../../components/Layout';
|
||||||
import AdminProtected from '../../components/AdminProtected';
|
import AdminProtected from '../../components/AdminProtected';
|
||||||
import CardEditorForm from '../../components/CardEditorForm';
|
import CardEditorView from '../../components/CardEditorView';
|
||||||
import CardEditorPreview from '../../components/CardEditorPreview';
|
import { useCardEditor } from '../../lib/use-card-editor.js';
|
||||||
import CardEditorSearchPanel from '../../components/CardEditorSearchPanel';
|
|
||||||
|
|
||||||
// Make this component client-side only to avoid SSR issues
|
function CardEditorPage() {
|
||||||
const CardEditor = () => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { id } = router.query;
|
const editor = useCardEditor(router);
|
||||||
|
|
||||||
const [card, setCard] = useState(null);
|
if (editor.loading) {
|
||||||
const [fetchLoading, setFetchLoading] = useState(false);
|
|
||||||
const loading = id ? fetchLoading : false;
|
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
const [message, setMessage] = useState('');
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const [searchResults, setSearchResults] = useState([]);
|
|
||||||
const [searchLoading, setSearchLoading] = useState(false);
|
|
||||||
|
|
||||||
// Form state
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: '',
|
|
||||||
set_name: '',
|
|
||||||
set_code: '',
|
|
||||||
card_number: '',
|
|
||||||
rarity: '',
|
|
||||||
game: '',
|
|
||||||
mana_cost: '',
|
|
||||||
cmc: '',
|
|
||||||
card_type: '',
|
|
||||||
colors: [],
|
|
||||||
oracle_text: '',
|
|
||||||
power: '',
|
|
||||||
toughness: '',
|
|
||||||
image_url: '',
|
|
||||||
stock_image_url: '',
|
|
||||||
current_price: '',
|
|
||||||
market_price: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchCard = async (cardId) => {
|
|
||||||
setFetchLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/cards/${cardId}`);
|
|
||||||
if (response.ok) {
|
|
||||||
const cardData = await response.json();
|
|
||||||
setCard(cardData);
|
|
||||||
setFormData({
|
|
||||||
name: cardData.name || '',
|
|
||||||
set_name: cardData.set_name || '',
|
|
||||||
set_code: cardData.set_code || '',
|
|
||||||
card_number: cardData.card_number || '',
|
|
||||||
rarity: cardData.rarity || '',
|
|
||||||
game: cardData.game || '',
|
|
||||||
mana_cost: cardData.mana_cost || '',
|
|
||||||
cmc: cardData.cmc || '',
|
|
||||||
card_type: cardData.card_type || '',
|
|
||||||
colors: Array.isArray(cardData.colors) ? cardData.colors : [],
|
|
||||||
oracle_text: cardData.oracle_text || '',
|
|
||||||
power: cardData.power || '',
|
|
||||||
toughness: cardData.toughness || '',
|
|
||||||
image_url: cardData.image_url || '',
|
|
||||||
stock_image_url: cardData.stock_image_url || '',
|
|
||||||
current_price: cardData.current_price || '',
|
|
||||||
market_price: cardData.market_price || ''
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setMessage('Card not found');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setMessage('Error loading card: ' + error.message);
|
|
||||||
} finally {
|
|
||||||
setFetchLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load card data when editing an existing card
|
|
||||||
useEffect(() => {
|
|
||||||
if (id) {
|
|
||||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- load card when route id is set
|
|
||||||
fetchCard(id);
|
|
||||||
}
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
const searchCards = async (query) => {
|
|
||||||
if (!query.trim()) {
|
|
||||||
setSearchResults([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/cards/search?query=${encodeURIComponent(query)}&limit=20`);
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
setSearchResults(data.cards || []);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Search error:', error);
|
|
||||||
} finally {
|
|
||||||
setSearchLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (field, value) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
[field]: value
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleColorChange = (color, checked) => {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
colors: checked
|
|
||||||
? [...prev.colors, color]
|
|
||||||
: prev.colors.filter(c => c !== color)
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
if (!card?.id) {
|
|
||||||
setMessage('No card selected to edit');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSaving(true);
|
|
||||||
setMessage('');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/cards/${card.id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...formData,
|
|
||||||
cmc: formData.cmc ? parseInt(formData.cmc) : null,
|
|
||||||
current_price: formData.current_price ? parseFloat(formData.current_price) : null,
|
|
||||||
market_price: formData.market_price ? parseFloat(formData.market_price) : null
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const updatedCard = await response.json();
|
|
||||||
setCard(updatedCard);
|
|
||||||
setMessage('Card updated successfully!');
|
|
||||||
|
|
||||||
// Clear message after 3 seconds
|
|
||||||
setTimeout(() => setMessage(''), 3000);
|
|
||||||
} else {
|
|
||||||
const errorData = await response.json();
|
|
||||||
setMessage('Error updating card: ' + (errorData.error || 'Unknown error'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setMessage('Error updating card: ' + error.message);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectCard = (selectedCard) => {
|
|
||||||
router.push(`/admin/card-editor?id=${selectedCard.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
return (
|
||||||
<AdminProtected>
|
<AdminProtected>
|
||||||
{(user) => (
|
{(user) => (
|
||||||
<Layout user={user}>
|
<Layout user={user}>
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen">
|
||||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent)' }}></div>
|
<div
|
||||||
|
className="animate-spin rounded-full h-32 w-32 border-b-2"
|
||||||
|
style={{ borderColor: 'var(--text-accent)' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
)}
|
)}
|
||||||
|
|
@ -186,91 +30,11 @@ const CardEditor = () => {
|
||||||
<AdminProtected>
|
<AdminProtected>
|
||||||
{(user) => (
|
{(user) => (
|
||||||
<Layout user={user}>
|
<Layout user={user}>
|
||||||
<div className="container mx-auto px-6 py-8">
|
<CardEditorView {...editor} />
|
||||||
{/* Admin Navigation */}
|
</Layout>
|
||||||
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h1 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
Admin Tools
|
|
||||||
</h1>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<button
|
|
||||||
onClick={() => router.push('/admin/card-editor')}
|
|
||||||
className="px-4 py-2 rounded-lg font-medium gradient-bg-purple text-white"
|
|
||||||
>
|
|
||||||
🖊️ Card Editor
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => router.push('/admin/card-import')}
|
|
||||||
className="px-4 py-2 rounded-lg font-medium transition-all duration-200 border"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
📥 Card Import
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-8">
|
|
||||||
<h2 className="text-3xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
Admin Card Editor
|
|
||||||
</h2>
|
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
Search for a card to edit its details, or continue editing the selected card.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CardEditorSearchPanel
|
|
||||||
searchQuery={searchQuery}
|
|
||||||
onSearchQueryChange={(value) => {
|
|
||||||
setSearchQuery(value);
|
|
||||||
searchCards(value);
|
|
||||||
}}
|
|
||||||
searchLoading={searchLoading}
|
|
||||||
searchResults={searchResults}
|
|
||||||
onSelectCard={selectCard}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Card Editor Form */}
|
|
||||||
{card && (
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
||||||
<div className="lg:col-span-1">
|
|
||||||
<CardEditorPreview formData={formData} />
|
|
||||||
</div>
|
|
||||||
<div className="lg:col-span-2">
|
|
||||||
<CardEditorForm
|
|
||||||
formData={formData}
|
|
||||||
onInputChange={handleInputChange}
|
|
||||||
onColorChange={handleColorChange}
|
|
||||||
onSave={handleSave}
|
|
||||||
saving={saving}
|
|
||||||
message={message}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!card && !loading && (
|
|
||||||
<div className="text-center py-12">
|
|
||||||
<div className="text-6xl mb-4">🔍</div>
|
|
||||||
<h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
|
||||||
Search for a Card to Edit
|
|
||||||
</h2>
|
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
Use the search box above to find a card you want to edit.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
)}
|
)}
|
||||||
</AdminProtected>
|
</AdminProtected>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Export with dynamic import to disable SSR
|
export default dynamic(() => Promise.resolve(CardEditorPage), { ssr: false });
|
||||||
export default dynamic(() => Promise.resolve(CardEditor), { ssr: false });
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue