refactor(decks): extract format utils and create modal (Brief 1)

Add shared deck format helpers and DecksCreateModal; decks page keeps
the edit modal inline for Brief 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Randall Stillwell 2026-06-12 21:20:45 -05:00
parent e3b4b0e213
commit 31788ed4d1
3 changed files with 117 additions and 115 deletions

View file

@ -0,0 +1,81 @@
import { Modal, Input, Button } from './ui';
import { DECK_INPUT_FIELD_CLASS } from '../lib/deck-format-utils.js';
export default function DecksCreateModal({
isOpen,
onClose,
newDeck,
setNewDeck,
onCreate,
}) {
return (
<Modal open={isOpen} onClose={onClose} title="Create New Deck" size="md">
<form onSubmit={onCreate} className="space-y-4">
<Input
label="Deck Name *"
required
value={newDeck.name}
onChange={(e) => setNewDeck({ ...newDeck, name: e.target.value })}
placeholder="Enter deck name"
/>
<div>
<label
className="block text-sm font-medium mb-2"
style={{ color: 'var(--text-primary)' }}
htmlFor="create-deck-format"
>
Format
</label>
<select
id="create-deck-format"
value={newDeck.format}
onChange={(e) => setNewDeck({ ...newDeck, 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="create-deck-description"
>
Description
</label>
<textarea
id="create-deck-description"
value={newDeck.description}
onChange={(e) => setNewDeck({ ...newDeck, description: e.target.value })}
className={DECK_INPUT_FIELD_CLASS}
rows="3"
placeholder="Describe your deck strategy..."
/>
</div>
<label className="flex items-center">
<input
type="checkbox"
checked={newDeck.is_public}
onChange={(e) => setNewDeck({ ...newDeck, 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">
Create Deck
</Button>
</div>
</form>
</Modal>
);
}

23
lib/deck-format-utils.js Normal file
View file

@ -0,0 +1,23 @@
export const DECK_FORMAT_OPTIONS = ['Commander', 'Standard', 'Modern', 'Legacy'];
export const DECK_INPUT_FIELD_CLASS = 'input-field w-full';
export const DECK_FORMAT_BADGE_STYLE = {
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
};
export function getDeckFormatIcon(format) {
switch (format) {
case 'Commander':
return '⚔️';
case 'Standard':
return '🏆';
case 'Modern':
return '🔥';
case 'Legacy':
return '💎';
default:
return '🃏';
}
}

View file

@ -2,7 +2,9 @@ import { useState, useEffect } from 'react';
import { useRouter } from 'next/router'; 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 { Modal, Input, Button } from '../components/ui'; import { Modal, Input, Button } from '../components/ui';
import { DECK_FORMAT_BADGE_STYLE, DECK_INPUT_FIELD_CLASS, getDeckFormatIcon } from '../lib/deck-format-utils.js';
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
@ -132,39 +134,6 @@ export default function Decks() {
} }
}; };
const getFormatIcon = (format) => {
switch (format) {
case 'Commander': return '⚔️';
case 'Standard': return '🏆';
case 'Modern': return '🔥';
case 'Legacy': return '💎';
default: return '🃏';
}
};
// Single neutral chip style for format badges — the prior
// getFormatColor() returned 5 stale Tailwind colour pairs
// (bg-purple-100, bg-blue-100, etc.) that don't fit the
// Deck Hearth palette and don't render in dark theme anyway.
const formatBadgeStyle = {
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
};
// Reusable inline-style for the .card surfaces used as stat
// cards + deck cards. We can't use the .glass-panel class +
// rounded-xl directly because we want the same gradient-border
// catch-light effect that the chrome chips have, and inline
// styles for that pattern are verbose. Easiest path: opt into
// the existing .glass-panel class for the multi-layer bg, then
// override border-radius via className. (.glass-panel doesn't
// set border-radius so we control it via Tailwind.)
const chipBgStyle = {};
// textarea / select use .input-field which is the Deck-Hearth
// standard input class (rounded-2xl + ember focus ring).
const inputFieldClass = 'input-field w-full';
if (!user) { if (!user) {
return ( return (
<Layout user={user}> <Layout user={user}>
@ -215,7 +184,7 @@ export default function Decks() {
{/* Stats */} {/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8"> <div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="glass-panel rounded-2xl p-6" style={chipBgStyle}> <div className="glass-panel rounded-2xl p-6">
<div className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}> <div className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
{decks.length} {decks.length}
</div> </div>
@ -261,10 +230,10 @@ export default function Decks() {
<div key={deck.id} className="glass-panel rounded-2xl p-6 transition-shadow"> <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 justify-between items-start mb-4">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<span className="text-2xl">{getFormatIcon(deck.format)}</span> <span className="text-2xl">{getDeckFormatIcon(deck.format)}</span>
<span <span
className="px-2 py-1 rounded-full text-xs font-medium" className="px-2 py-1 rounded-full text-xs font-medium"
style={formatBadgeStyle} style={DECK_FORMAT_BADGE_STYLE}
> >
{deck.format} {deck.format}
</span> </span>
@ -329,84 +298,13 @@ export default function Decks() {
</div> </div>
)} )}
<Modal <DecksCreateModal
open={showCreateModal} isOpen={showCreateModal}
onClose={() => setShowCreateModal(false)} onClose={() => setShowCreateModal(false)}
title="Create New Deck" newDeck={newDeck}
size="md" setNewDeck={setNewDeck}
> onCreate={handleCreateDeck}
<form onSubmit={handleCreateDeck} className="space-y-4"> />
<Input
label="Deck Name *"
required
value={newDeck.name}
onChange={(e) => setNewDeck({ ...newDeck, name: e.target.value })}
placeholder="Enter deck name"
/>
<div>
<label
className="block text-sm font-medium mb-2"
style={{ color: 'var(--text-primary)' }}
htmlFor="create-deck-format"
>
Format
</label>
<select
id="create-deck-format"
value={newDeck.format}
onChange={(e) => setNewDeck({ ...newDeck, format: e.target.value })}
className={inputFieldClass}
>
<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="create-deck-description"
>
Description
</label>
<textarea
id="create-deck-description"
value={newDeck.description}
onChange={(e) => setNewDeck({ ...newDeck, description: e.target.value })}
className={inputFieldClass}
rows="3"
placeholder="Describe your deck strategy..."
/>
</div>
<label className="flex items-center">
<input
type="checkbox"
checked={newDeck.is_public}
onChange={(e) => setNewDeck({ ...newDeck, 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={() => setShowCreateModal(false)}
className="flex-1"
>
Cancel
</Button>
<Button type="submit" variant="primary" className="flex-1">
Create Deck
</Button>
</div>
</form>
</Modal>
<Modal <Modal
open={!!editingDeck} open={!!editingDeck}
@ -434,7 +332,7 @@ export default function Decks() {
id="edit-deck-format" id="edit-deck-format"
value={editingDeck.format} value={editingDeck.format}
onChange={(e) => setEditingDeck({ ...editingDeck, format: e.target.value })} onChange={(e) => setEditingDeck({ ...editingDeck, format: e.target.value })}
className={inputFieldClass} className={DECK_INPUT_FIELD_CLASS}
> >
<option value="Commander">Commander</option> <option value="Commander">Commander</option>
<option value="Standard">Standard</option> <option value="Standard">Standard</option>
@ -454,7 +352,7 @@ export default function Decks() {
id="edit-deck-description" id="edit-deck-description"
value={editingDeck.description || ''} value={editingDeck.description || ''}
onChange={(e) => setEditingDeck({ ...editingDeck, description: e.target.value })} onChange={(e) => setEditingDeck({ ...editingDeck, description: e.target.value })}
className={inputFieldClass} className={DECK_INPUT_FIELD_CLASS}
rows="3" rows="3"
/> />
</div> </div>