ui improvement + fix search card in deck manager that leaded to a crash when card was found

This commit is contained in:
2025-11-25 18:38:09 +01:00
parent 7e1cd5f9bd
commit 4a28f5f1ec
13 changed files with 107 additions and 79 deletions

View File

@@ -201,7 +201,7 @@ const CardSearch = () => {
};
return (
<div className="bg-gray-900 text-white p-3 sm:p-6">
<div className="relative bg-gray-900 text-white p-3 sm:p-6 md:min-h-screen">
<div className="max-w-7xl mx-auto">
<h1 className="text-2xl md:text-3xl font-bold mb-4 md:mb-6">Card Search</h1>
<form onSubmit={handleSearch} className="mb-8 space-y-4">
@@ -774,7 +774,7 @@ const CardSearch = () => {
<div
className={`fixed bottom-4 right-4 p-4 rounded-lg shadow-lg transition-all duration-300 ${
snackbar.type === 'success' ? 'bg-green-500' : 'bg-red-500'
} text-white z-50`}
} text-white z-[140]`}
>
<div className="flex items-center justify-between">
<div className="flex items-center">

View File

@@ -189,7 +189,7 @@ export default function Collection() {
};
return (
<div className="bg-gray-900 text-white p-3 sm:p-6">
<div className="relative bg-gray-900 text-white p-3 sm:p-6 md:min-h-screen">
<div className="max-w-7xl mx-auto">
<h1 className="text-2xl md:text-3xl font-bold mb-4 md:mb-6">My Collection</h1>
@@ -295,7 +295,7 @@ export default function Collection() {
const displayOracleText = currentFace?.oracle_text || hoveredCard.oracle_text;
return (
<div className="hidden lg:block fixed top-1/2 right-8 transform -translate-y-1/2 z-40 pointer-events-none">
<div className="hidden lg:block fixed top-1/2 right-8 transform -translate-y-1/2 z-30 pointer-events-none">
<div className="bg-gray-800 rounded-lg shadow-2xl p-4 max-w-md">
<div className="relative">
<img
@@ -344,16 +344,16 @@ export default function Collection() {
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 transition-opacity duration-300"
className="fixed inset-0 bg-black bg-opacity-50 z-[110] transition-opacity duration-300"
onClick={() => setSelectedCard(null)}
/>
{/* Sliding Panel */}
<div className="fixed top-0 right-0 h-full w-full md:w-96 bg-gray-800 shadow-2xl z-50 overflow-y-auto animate-slide-in-right">
<div className="fixed top-0 right-0 h-full w-full md:w-96 bg-gray-800 shadow-2xl z-[120] overflow-y-auto animate-slide-in-right">
{/* Close button - fixed position, stays visible when scrolling */}
<button
onClick={() => setSelectedCard(null)}
className="fixed top-4 right-4 bg-gray-700 hover:bg-gray-600 text-white p-2 md:p-1.5 rounded-full transition-colors z-[60] shadow-lg"
className="fixed top-4 right-4 bg-gray-700 hover:bg-gray-600 text-white p-2 md:p-1.5 rounded-full transition-colors z-[130] shadow-lg"
aria-label="Close"
>
<X size={24} className="md:w-5 md:h-5" />
@@ -478,7 +478,7 @@ export default function Collection() {
<div
className={`fixed bottom-4 right-4 p-4 rounded-lg shadow-lg transition-all duration-300 ${
snackbar.type === 'success' ? 'bg-green-500' : 'bg-red-500'
} text-white z-50`}
} text-white z-[140]`}
>
<div className="flex items-center justify-between">
<div className="flex items-center">

View File

@@ -480,7 +480,7 @@ export default function Community() {
// ============ MAIN VIEW ============
return (
<div className="bg-gray-900 text-white min-h-screen">
<div className="relative bg-gray-900 text-white md:min-h-screen">
{/* Header */}
<div className="sticky top-0 bg-gray-900/95 backdrop-blur border-b border-gray-800 p-3 z-10">
<h1 className="text-xl font-bold mb-3">Community</h1>

View File

@@ -31,7 +31,7 @@ export default function DeckBuilder({
initial={{ x: "100%" }}
animate={{ x: isOpen ? "0%" : "100%" }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
className="fixed top-0 right-0 w-4/5 h-full bg-gray-800 p-6 shadow-lg md:static md:w-full md:h-auto md:p-6 md:shadow-none z-50"
className="fixed top-0 right-0 w-4/5 h-full bg-gray-800 p-6 shadow-lg md:static md:w-full md:h-auto md:p-6 md:shadow-none z-[110]"
>
{/* Bouton de fermeture */}
<button
@@ -117,4 +117,4 @@ export default function DeckBuilder({
</motion.div>
</div>
);
}
}

View File

@@ -62,7 +62,7 @@ export default function DeckEditor({ deckId, onClose }: DeckEditorProps) {
if (loading) {
return (
<div className="min-h-screen bg-gray-900 text-white p-6 flex items-center justify-center">
<div className="relative md:min-h-screen bg-gray-900 text-white p-6 flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-blue-500"></div>
</div>
);
@@ -70,7 +70,7 @@ export default function DeckEditor({ deckId, onClose }: DeckEditorProps) {
if (!deck) {
return (
<div className="min-h-screen bg-gray-900 text-white p-6">
<div className="relative md:min-h-screen bg-gray-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<div className="bg-red-500/10 border border-red-500 rounded-lg p-4">
<h2 className="text-xl font-bold text-red-500">Error</h2>
@@ -82,4 +82,4 @@ export default function DeckEditor({ deckId, onClose }: DeckEditorProps) {
}
return <DeckManager initialDeck={deck} onSave={onClose} />;
}
}

View File

@@ -125,6 +125,7 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
const [currentDeckId, setCurrentDeckId] = useState<string | null>(initialDeck?.id || null);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState<Card[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [selectedCards, setSelectedCards] = useState<{
card: Card;
quantity: number;
@@ -296,11 +297,16 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
e.preventDefault();
if (!searchQuery.trim()) return;
setIsSearching(true);
try {
const cards = await searchCards(searchQuery);
setSearchResults(cards);
setSearchResults(cards || []);
} catch (error) {
console.error('Failed to search cards:', error);
setSearchResults([]);
setSnackbar({ message: 'Failed to search cards', type: 'error' });
} finally {
setIsSearching(false);
}
};
@@ -538,7 +544,7 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
};
return (
<div className="bg-gray-900 text-white p-3 sm:p-6 pt-6 pb-20 md:pt-20 md:pb-6">
<div className="relative bg-gray-900 text-white p-3 sm:p-6 pt-6 pb-44 md:pt-20 md:pb-6 md:min-h-screen">
<div className="max-w-7xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6">
{/* Card Search Section */}
@@ -575,7 +581,17 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
{/* Vertical Card List for Mobile */}
<div className="space-y-2">
{searchResults.map(card => {
{isSearching ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="animate-spin text-blue-500" size={48} />
</div>
) : searchResults.length === 0 && searchQuery ? (
<div className="text-center py-12 text-gray-400">
<p className="text-lg mb-2">No cards found</p>
<p className="text-sm">Try a different search term</p>
</div>
) : (
searchResults.map(card => {
const currentFaceIndex = getCurrentFaceIndex(card.id);
const isMultiFaced = isDoubleFaced(card);
const inCollection = userCollection.get(card.id) || 0;
@@ -702,7 +718,8 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
</button>
</div>
);
})}
})
)}
</div>
</div>
@@ -876,10 +893,6 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
})}
</div>
<div className="font-bold text-xl">
Total Price: ${totalPrice.toFixed(2)}
</div>
{deckSize > 0 && suggestedLandCountValue > 0 && (
<div className="bg-gray-700 rounded-lg p-3">
<div className="flex items-center justify-between mb-2">
@@ -906,49 +919,62 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
</div>
)}
<div className="flex gap-2">
{!isLoadingCollection && getMissingCards().length > 0 && (
<button
onClick={handleAddAllMissingCards}
disabled={isAddingAll}
className="flex-1 px-4 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg flex items-center justify-center gap-2"
title="Add missing cards to collection"
>
{isAddingAll ? (
<Loader2 className="animate-spin" size={20} />
) : (
<>
<PackagePlus size={20} />
<span className="hidden sm:inline">Add Missing</span>
</>
)}
</button>
)}
<button
onClick={saveDeck}
disabled={
!deckName.trim() || selectedCards.length === 0 || isSaving
}
className="flex-1 px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg flex items-center justify-center gap-2 relative"
>
{isSaving ? (
<>
<Loader2 className="animate-spin text-white absolute left-2 top-1/2 -translate-y-1/2" size={20} />
<span className="opacity-0">Save Deck</span>
</>
) : (
<>
<Save size={20} />
<span>{initialDeck ? 'Update Deck' : 'Save Deck'}</span>
</>
)}
</button>
</div>
</div>
</div>
</div>
</div>
{/* Fixed Footer with Price and Actions - Mobile First */}
<div className="fixed bottom-16 left-0 right-0 md:left-auto md:right-4 md:bottom-4 md:w-80 z-20 bg-gray-800 border-t border-gray-700 md:border md:rounded-lg shadow-2xl">
<div className="p-3 space-y-3">
{/* Total Price */}
<div className="flex items-center justify-between">
<span className="text-sm font-semibold text-gray-300">Total Price</span>
<span className="text-xl font-bold text-green-400">${totalPrice.toFixed(2)}</span>
</div>
{/* Action Buttons */}
<div className="flex gap-2">
{!isLoadingCollection && getMissingCards().length > 0 && (
<button
onClick={handleAddAllMissingCards}
disabled={isAddingAll}
className="flex-1 px-3 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg flex items-center justify-center gap-2 text-sm font-medium transition-colors"
title="Add missing cards to collection"
>
{isAddingAll ? (
<Loader2 className="animate-spin" size={18} />
) : (
<>
<PackagePlus size={18} />
<span className="hidden sm:inline">Add Missing</span>
</>
)}
</button>
)}
<button
onClick={saveDeck}
disabled={
!deckName.trim() || selectedCards.length === 0 || isSaving
}
className="flex-1 px-3 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg flex items-center justify-center gap-2 text-sm font-medium relative transition-colors"
>
{isSaving ? (
<>
<Loader2 className="animate-spin text-white" size={18} />
<span>Saving...</span>
</>
) : (
<>
<Save size={18} />
<span>{initialDeck ? 'Update' : 'Save'}</span>
</>
)}
</button>
</div>
</div>
</div>
{/* Hover Card Preview - only show if no card is selected */}
{hoveredCard && !selectedCard && (() => {
const currentFaceIndex = getCurrentFaceIndex(hoveredCard.id);
@@ -962,7 +988,7 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
const displayOracleText = currentFace?.oracle_text || hoveredCard.oracle_text;
return (
<div className="hidden lg:block fixed top-1/2 right-8 transform -translate-y-1/2 z-40 pointer-events-none">
<div className="hidden lg:block fixed top-1/2 right-8 transform -translate-y-1/2 z-30 pointer-events-none">
<div className="bg-gray-800 rounded-lg shadow-2xl p-4 max-w-md">
<div className="relative">
<img
@@ -1011,16 +1037,16 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 transition-opacity duration-300"
className="fixed inset-0 bg-black bg-opacity-50 z-[110] transition-opacity duration-300"
onClick={() => setSelectedCard(null)}
/>
{/* Sliding Panel */}
<div className="fixed top-0 right-0 h-full w-full md:w-96 bg-gray-800 shadow-2xl z-50 overflow-y-auto animate-slide-in-right">
<div className="fixed top-0 right-0 h-full w-full md:w-96 bg-gray-800 shadow-2xl z-[120] overflow-y-auto animate-slide-in-right">
{/* Close button */}
<button
onClick={() => setSelectedCard(null)}
className="fixed top-4 right-4 bg-gray-700 hover:bg-gray-600 text-white p-2 md:p-1.5 rounded-full transition-colors z-[60] shadow-lg"
className="fixed top-4 right-4 bg-gray-700 hover:bg-gray-600 text-white p-2 md:p-1.5 rounded-full transition-colors z-[130] shadow-lg"
aria-label="Close"
>
<X size={24} className="md:w-5 md:h-5" />
@@ -1125,7 +1151,7 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
{snackbar && (
<div
className={`fixed bottom-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg transition-all duration-300 ${
className={`fixed bottom-4 right-4 text-white p-4 rounded-lg shadow-lg transition-all duration-300 z-[140] ${
snackbar.type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`}
>

View File

@@ -157,11 +157,11 @@ import React, { useState, useEffect } from 'react';
);
return (
<div className="min-h-screen bg-gray-900 text-white p-6">
<div className="relative md:min-h-screen bg-gray-900 text-white p-6">
<div className="max-w-7xl mx-auto">
<h1 className="text-3xl font-bold mb-6">Life Counter</h1>
{!setupComplete ? renderSetupForm() : renderLifeCounters()}
</div>
</div>
);
}
}

View File

@@ -53,12 +53,12 @@ export default function Modal({
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-50 transition-opacity duration-300 animate-fade-in"
className="fixed inset-0 bg-black bg-opacity-50 z-[110] transition-opacity duration-300 animate-fade-in"
onClick={onClose}
/>
{/* Modal */}
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
<div className="fixed inset-0 z-[120] flex items-center justify-center p-4 pointer-events-none">
<div
className={`${sizeClasses[size]} w-full bg-gray-800 rounded-lg shadow-2xl pointer-events-auto animate-scale-in`}
onClick={(e) => e.stopPropagation()}

View File

@@ -69,7 +69,7 @@ export default function Navigation({ currentPage, setCurrentPage }: NavigationPr
return (
<>
{/* Desktop Navigation - Top */}
<nav className="hidden md:block fixed top-0 left-0 right-0 bg-gray-800 border-b border-gray-700 z-50 animate-slide-in-left">
<nav className="hidden md:block fixed top-0 left-0 right-0 bg-gray-800 border-b border-gray-700 z-[100] animate-slide-in-left">
<div className="max-w-7xl mx-auto px-4">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-8">
@@ -107,7 +107,7 @@ export default function Navigation({ currentPage, setCurrentPage }: NavigationPr
</button>
{showDropdown && (
<div className="absolute right-0 mt-2 w-48 bg-gray-800 rounded-md shadow-lg py-1 border border-gray-700 animate-scale-in glass-effect">
<div className="absolute right-0 mt-2 w-48 bg-gray-800 rounded-md shadow-lg py-1 border border-gray-700 animate-scale-in glass-effect z-[110]">
<button
onClick={handleSignOut}
className="flex items-center space-x-2 w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 transition-smooth"

View File

@@ -85,7 +85,7 @@ export default function PWAInstallPrompt() {
}
return (
<div className="fixed bottom-20 md:bottom-4 left-4 right-4 md:left-auto md:right-4 md:max-w-sm z-50 animate-slide-in-bottom">
<div className="fixed bottom-20 md:bottom-4 left-4 right-4 md:left-auto md:right-4 md:max-w-sm z-[105] animate-slide-in-bottom">
<div className="bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg shadow-2xl p-4 text-white">
<button
onClick={handleDismiss}

View File

@@ -340,14 +340,14 @@ export default function TradeCreator({
if (loading) {
return (
<div className="fixed inset-0 bg-black/80 z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-black/80 z-[110] flex items-center justify-center">
<Loader2 className="animate-spin text-blue-500" size={48} />
</div>
);
}
return (
<div className="fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-0 md:p-4">
<div className="fixed inset-0 bg-black/80 z-[110] flex items-center justify-center p-0 md:p-4">
<div className="bg-gray-800 w-full h-full md:rounded-lg md:w-full md:max-w-6xl md:max-h-[90vh] overflow-hidden flex flex-col">
{/* ============ MOBILE VIEW ============ */}