refactor(decks): extract edit modal and deck grid (Brief 2) (#142)

Move the deck list grid (empty state + cards) into DecksGrid and the
edit form into DecksEditModal to continue shrinking pages/decks.js.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
varutasu 2026-06-13 01:11:47 -05:00 committed by GitHub
parent 31da384a9d
commit bce62c3716
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 192 additions and 166 deletions

View file

@ -0,0 +1,80 @@
import { Modal, Input, Button } from './ui';
import { DECK_INPUT_FIELD_CLASS } from '../lib/deck-format-utils.js';
export default function DecksEditModal({
editingDeck,
setEditingDeck,
onClose,
onEdit,
}) {
return (
<Modal open={!!editingDeck} onClose={onClose} title="Edit Deck" size="md">
{editingDeck && (
<form onSubmit={onEdit} className="space-y-4">
<Input
label="Deck Name *"
required
value={editingDeck.name}
onChange={(e) => setEditingDeck({ ...editingDeck, name: e.target.value })}
/>
<div>
<label
className="block text-sm font-medium mb-2"
style={{ color: 'var(--text-primary)' }}
htmlFor="edit-deck-format"
>
Format
</label>
<select
id="edit-deck-format"
value={editingDeck.format}
onChange={(e) => setEditingDeck({ ...editingDeck, format: e.target.value })}
className={DECK_INPUT_FIELD_CLASS}
>
<option value="Commander">Commander</option>
<option value="Standard">Standard</option>
<option value="Modern">Modern</option>
<option value="Legacy">Legacy</option>
</select>
</div>
<div>
<label
className="block text-sm font-medium mb-2"
style={{ color: 'var(--text-primary)' }}
htmlFor="edit-deck-description"
>
Description
</label>
<textarea
id="edit-deck-description"
value={editingDeck.description || ''}
onChange={(e) => setEditingDeck({ ...editingDeck, description: e.target.value })}
className={DECK_INPUT_FIELD_CLASS}
rows="3"
/>
</div>
<label className="flex items-center">
<input
type="checkbox"
checked={editingDeck.is_public}
onChange={(e) => setEditingDeck({ ...editingDeck, is_public: e.target.checked })}
className="mr-2"
style={{ accentColor: 'var(--accent-ember)' }}
/>
<span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Make deck public
</span>
</label>
<div className="flex space-x-3 pt-2">
<Button type="button" variant="secondary" onClick={onClose} className="flex-1">
Cancel
</Button>
<Button type="submit" variant="primary" className="flex-1">
Save Changes
</Button>
</div>
</form>
)}
</Modal>
);
}

98
components/DecksGrid.js Normal file
View file

@ -0,0 +1,98 @@
import Link from 'next/link';
import { Button } from './ui';
import { DECK_FORMAT_BADGE_STYLE, getDeckFormatIcon } from '../lib/deck-format-utils.js';
export default function DecksGrid({ decks, onEditDeck, onDeleteDeck, onCreateDeck }) {
if (decks.length === 0) {
return (
<div className="text-center py-12">
<div className="text-6xl mb-4">🃏</div>
<h3 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
No decks yet
</h3>
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
Create your first deck to get started
</p>
<Button variant="primary" onClick={onCreateDeck}>
Create Your First Deck
</Button>
</div>
);
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{decks.map((deck) => (
<div key={deck.id} className="glass-panel rounded-2xl p-6 transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="flex items-center space-x-2">
<span className="text-2xl">{getDeckFormatIcon(deck.format)}</span>
<span
className="px-2 py-1 rounded-full text-xs font-medium"
style={DECK_FORMAT_BADGE_STYLE}
>
{deck.format}
</span>
</div>
<div className="flex space-x-2">
<button
type="button"
onClick={() => onEditDeck({ ...deck })}
className="transition-colors"
style={{ color: 'var(--text-secondary)' }}
aria-label="Edit deck"
>
</button>
<button
type="button"
onClick={() => onDeleteDeck(deck.id)}
className="transition-colors"
style={{ color: 'var(--text-secondary)' }}
aria-label="Delete deck"
>
🗑
</button>
</div>
</div>
<h3 className="text-xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
{deck.name}
</h3>
{deck.description && (
<p
className="text-sm mb-4 line-clamp-2"
style={{ color: 'var(--text-secondary)' }}
>
{deck.description}
</p>
)}
<div
className="flex justify-between items-center text-sm mb-4"
style={{ color: 'var(--text-secondary)' }}
>
<span>{deck.card_count || 0} cards</span>
{deck.is_public && (
<span style={{ color: 'var(--accent-ember)' }}>Public</span>
)}
</div>
<div className="flex space-x-2">
<Link href={`/deck-builder?deck=${deck.id}`} className="flex-1">
<Button variant="primary" size="md" className="w-full">
Edit Deck
</Button>
</Link>
<Link href={`/deck/${deck.id}`} className="flex-1">
<Button variant="secondary" size="md" className="w-full">
View
</Button>
</Link>
</div>
</div>
))}
</div>
);
}

View file

@ -3,8 +3,9 @@ import { useRouter } from 'next/router';
import Link from 'next/link'; import Link from 'next/link';
import Layout from '../components/Layout'; import Layout from '../components/Layout';
import DecksCreateModal from '../components/DecksCreateModal'; import DecksCreateModal from '../components/DecksCreateModal';
import { Modal, Input, Button } from '../components/ui'; import DecksEditModal from '../components/DecksEditModal';
import { DECK_FORMAT_BADGE_STYLE, DECK_INPUT_FIELD_CLASS, getDeckFormatIcon } from '../lib/deck-format-utils.js'; import DecksGrid from '../components/DecksGrid';
import { Button } from '../components/ui';
import { useAuth } from '../lib/use-auth'; import { useAuth } from '../lib/use-auth';
/* 2026-06-04 design-sweep pass: the Tailwind token classes that this /* 2026-06-04 design-sweep pass: the Tailwind token classes that this
@ -210,93 +211,12 @@ export default function Decks() {
</div> </div>
</div> </div>
{/* Decks Grid */} <DecksGrid
{decks.length === 0 ? ( decks={decks}
<div className="text-center py-12"> onEditDeck={setEditingDeck}
<div className="text-6xl mb-4">🃏</div> onDeleteDeck={handleDeleteDeck}
<h3 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}> onCreateDeck={() => setShowCreateModal(true)}
No decks yet />
</h3>
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
Create your first deck to get started
</p>
<Button variant="primary" onClick={() => setShowCreateModal(true)}>
Create Your First Deck
</Button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{decks.map((deck) => (
<div key={deck.id} className="glass-panel rounded-2xl p-6 transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="flex items-center space-x-2">
<span className="text-2xl">{getDeckFormatIcon(deck.format)}</span>
<span
className="px-2 py-1 rounded-full text-xs font-medium"
style={DECK_FORMAT_BADGE_STYLE}
>
{deck.format}
</span>
</div>
<div className="flex space-x-2">
<button
onClick={() => setEditingDeck({ ...deck })}
className="transition-colors"
style={{ color: 'var(--text-secondary)' }}
aria-label="Edit deck"
>
</button>
<button
onClick={() => handleDeleteDeck(deck.id)}
className="transition-colors"
style={{ color: 'var(--text-secondary)' }}
aria-label="Delete deck"
>
🗑
</button>
</div>
</div>
<h3 className="text-xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
{deck.name}
</h3>
{deck.description && (
<p
className="text-sm mb-4 line-clamp-2"
style={{ color: 'var(--text-secondary)' }}
>
{deck.description}
</p>
)}
<div
className="flex justify-between items-center text-sm mb-4"
style={{ color: 'var(--text-secondary)' }}
>
<span>{deck.card_count || 0} cards</span>
{deck.is_public && (
<span style={{ color: 'var(--accent-ember)' }}>Public</span>
)}
</div>
<div className="flex space-x-2">
<Link href={`/deck-builder?deck=${deck.id}`} className="flex-1">
<Button variant="primary" size="md" className="w-full">
Edit Deck
</Button>
</Link>
<Link href={`/deck/${deck.id}`} className="flex-1">
<Button variant="secondary" size="md" className="w-full">
View
</Button>
</Link>
</div>
</div>
))}
</div>
)}
<DecksCreateModal <DecksCreateModal
isOpen={showCreateModal} isOpen={showCreateModal}
@ -306,84 +226,12 @@ export default function Decks() {
onCreate={handleCreateDeck} onCreate={handleCreateDeck}
/> />
<Modal <DecksEditModal
open={!!editingDeck} editingDeck={editingDeck}
setEditingDeck={setEditingDeck}
onClose={() => setEditingDeck(null)} onClose={() => setEditingDeck(null)}
title="Edit Deck" onEdit={handleEditDeck}
size="md"
>
{editingDeck && (
<form onSubmit={handleEditDeck} className="space-y-4">
<Input
label="Deck Name *"
required
value={editingDeck.name}
onChange={(e) => setEditingDeck({ ...editingDeck, name: e.target.value })}
/> />
<div>
<label
className="block text-sm font-medium mb-2"
style={{ color: 'var(--text-primary)' }}
htmlFor="edit-deck-format"
>
Format
</label>
<select
id="edit-deck-format"
value={editingDeck.format}
onChange={(e) => setEditingDeck({ ...editingDeck, format: e.target.value })}
className={DECK_INPUT_FIELD_CLASS}
>
<option value="Commander">Commander</option>
<option value="Standard">Standard</option>
<option value="Modern">Modern</option>
<option value="Legacy">Legacy</option>
</select>
</div>
<div>
<label
className="block text-sm font-medium mb-2"
style={{ color: 'var(--text-primary)' }}
htmlFor="edit-deck-description"
>
Description
</label>
<textarea
id="edit-deck-description"
value={editingDeck.description || ''}
onChange={(e) => setEditingDeck({ ...editingDeck, description: e.target.value })}
className={DECK_INPUT_FIELD_CLASS}
rows="3"
/>
</div>
<label className="flex items-center">
<input
type="checkbox"
checked={editingDeck.is_public}
onChange={(e) => setEditingDeck({ ...editingDeck, is_public: e.target.checked })}
className="mr-2"
style={{ accentColor: 'var(--accent-ember)' }}
/>
<span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Make deck public
</span>
</label>
<div className="flex space-x-3 pt-2">
<Button
type="button"
variant="secondary"
onClick={() => setEditingDeck(null)}
className="flex-1"
>
Cancel
</Button>
<Button type="submit" variant="primary" className="flex-1">
Save Changes
</Button>
</div>
</form>
)}
</Modal>
</div> </div>
</Layout> </Layout>
); );