From 3cc26765300ae00f25b8d99a76fa2c726403a673 Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:17:01 +0200 Subject: [PATCH 1/7] Add comprehensive CSS animations and effects --- src/index.css | 311 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 296 insertions(+), 15 deletions(-) diff --git a/src/index.css b/src/index.css index 547fe27..1e05960 100644 --- a/src/index.css +++ b/src/index.css @@ -1,16 +1,297 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@keyframes slide { - 0% { - transform: translateX(0); - } - 100% { - transform: translateX(-50%); - } -} - -.animate-slide { - animation: slide 60s linear infinite; +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Existing slide animation */ +@keyframes slide { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-50%); + } +} + +.animate-slide { + animation: slide 60s linear infinite; +} + +/* Fade In Animation */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fadeIn 0.6s ease-out forwards; +} + +/* Fade In Delayed - for staggered animations */ +.animate-fade-in-delay-1 { + animation: fadeIn 0.6s ease-out 0.1s forwards; + opacity: 0; +} + +.animate-fade-in-delay-2 { + animation: fadeIn 0.6s ease-out 0.2s forwards; + opacity: 0; +} + +.animate-fade-in-delay-3 { + animation: fadeIn 0.6s ease-out 0.3s forwards; + opacity: 0; +} + +/* Slide In From Left */ +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-50px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.animate-slide-in-left { + animation: slideInLeft 0.5s ease-out forwards; +} + +/* Slide In From Right */ +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(50px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.animate-slide-in-right { + animation: slideInRight 0.5s ease-out forwards; +} + +/* Scale In Animation */ +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.animate-scale-in { + animation: scaleIn 0.4s ease-out forwards; +} + +/* Pulse Glow Animation */ +@keyframes pulseGlow { + 0%, 100% { + box-shadow: 0 0 5px rgba(59, 130, 246, 0.5); + } + 50% { + box-shadow: 0 0 20px rgba(59, 130, 246, 0.8), 0 0 30px rgba(59, 130, 246, 0.4); + } +} + +.animate-pulse-glow { + animation: pulseGlow 2s ease-in-out infinite; +} + +/* Shimmer Effect */ +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +.animate-shimmer { + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.2) 50%, + rgba(255, 255, 255, 0) 100% + ); + background-size: 1000px 100%; + animation: shimmer 2s infinite; +} + +/* Bounce In Animation */ +@keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(0.3); + } + 50% { + opacity: 1; + transform: scale(1.05); + } + 70% { + transform: scale(0.9); + } + 100% { + transform: scale(1); + } +} + +.animate-bounce-in { + animation: bounceIn 0.6s ease-out forwards; +} + +/* Float Animation */ +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } +} + +.animate-float { + animation: float 3s ease-in-out infinite; +} + +/* Gradient Animation */ +@keyframes gradientShift { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +.animate-gradient { + background: linear-gradient( + -45deg, + #ee7752, + #e73c7e, + #23a6d5, + #23d5ab + ); + background-size: 400% 400%; + animation: gradientShift 15s ease infinite; +} + +/* Card Flip Animation */ +@keyframes flipCard { + 0% { + transform: perspective(1000px) rotateY(0); + } + 100% { + transform: perspective(1000px) rotateY(180deg); + } +} + +.animate-flip { + animation: flipCard 0.6s ease-in-out; +} + +/* Rotate Animation */ +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.animate-rotate { + animation: rotate 2s linear infinite; +} + +/* Enhanced Hover Effects */ +.card-hover { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.card-hover:hover { + transform: translateY(-8px) scale(1.02); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), + 0 0 20px rgba(59, 130, 246, 0.3); +} + +/* Button Ripple Effect */ +.btn-ripple { + position: relative; + overflow: hidden; +} + +.btn-ripple::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.5); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.btn-ripple:active::after { + width: 300px; + height: 300px; +} + +/* Smooth Transitions */ +.transition-smooth { + transition: all 0.3s ease-in-out; +} + +/* Glass Morphism Effect */ +.glass-effect { + background: rgba(31, 41, 55, 0.8); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +/* Glow on Hover */ +.glow-on-hover { + transition: all 0.3s ease; +} + +.glow-on-hover:hover { + box-shadow: 0 0 15px rgba(59, 130, 246, 0.6), + 0 0 30px rgba(59, 130, 246, 0.4), + 0 0 45px rgba(59, 130, 246, 0.2); + transform: scale(1.05); +} + +/* Loading Animation */ +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.loading-spinner { + border: 3px solid rgba(255, 255, 255, 0.3); + border-top-color: #3b82f6; + border-radius: 50%; + animation: loading 1s linear infinite; } From c3cc4e59f6b5ffd8b7b17460fe6128f428c11f5e Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:17:58 +0200 Subject: [PATCH 2/7] Update components with CSS animations --- src/App.tsx | 180 ++++++++++++++++++++++++++-------------------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c009edc..7013917 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,91 +1,91 @@ -import React, { useState } from 'react'; - import DeckManager from './components/DeckManager'; - import DeckList from './components/DeckList'; - import LoginForm from './components/LoginForm'; - import Navigation from './components/Navigation'; - import Collection from './components/Collection'; - import DeckEditor from './components/DeckEditor'; - import Profile from './components/Profile'; - import CardSearch from './components/CardSearch'; - import LifeCounter from './components/LifeCounter'; - import { AuthProvider, useAuth } from './contexts/AuthContext'; - - type Page = 'home' | 'deck' | 'login' | 'collection' | 'edit-deck' | 'profile' | 'search' | 'life-counter'; - - function AppContent() { - const [currentPage, setCurrentPage] = useState('home'); - const [selectedDeckId, setSelectedDeckId] = useState(null); - const { user, loading } = useAuth(); - - if (loading) { - return ( -
-
-
- ); - } - - if (!user && currentPage !== 'login') { - return ; - } - - const handleDeckEdit = (deckId: string) => { - setSelectedDeckId(deckId); - setCurrentPage('edit-deck'); - }; - - const renderPage = () => { - switch (currentPage) { - case 'home': - return ( -
-
-

My Decks

- -
-
- ); - case 'deck': - return ; - case 'edit-deck': - return selectedDeckId ? ( - { - setSelectedDeckId(null); - setCurrentPage('home'); - }} - /> - ) : null; - case 'collection': - return ; - case 'profile': - return ; - case 'search': - return ; - case 'life-counter': - return ; - case 'login': - return ; - default: - return null; - } - }; - - return ( -
- - {renderPage()} -
- ); - } - - function App() { - return ( - - - - ); - } - +import React, { useState } from 'react'; + import DeckManager from './components/DeckManager'; + import DeckList from './components/DeckList'; + import LoginForm from './components/LoginForm'; + import Navigation from './components/Navigation'; + import Collection from './components/Collection'; + import DeckEditor from './components/DeckEditor'; + import Profile from './components/Profile'; + import CardSearch from './components/CardSearch'; + import LifeCounter from './components/LifeCounter'; + import { AuthProvider, useAuth } from './contexts/AuthContext'; + + type Page = 'home' | 'deck' | 'login' | 'collection' | 'edit-deck' | 'profile' | 'search' | 'life-counter'; + + function AppContent() { + const [currentPage, setCurrentPage] = useState('home'); + const [selectedDeckId, setSelectedDeckId] = useState(null); + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+
+
+ ); + } + + if (!user && currentPage !== 'login') { + return ; + } + + const handleDeckEdit = (deckId: string) => { + setSelectedDeckId(deckId); + setCurrentPage('edit-deck'); + }; + + const renderPage = () => { + switch (currentPage) { + case 'home': + return ( +
+
+

My Decks

+ +
+
+ ); + case 'deck': + return ; + case 'edit-deck': + return selectedDeckId ? ( + { + setSelectedDeckId(null); + setCurrentPage('home'); + }} + /> + ) : null; + case 'collection': + return ; + case 'profile': + return ; + case 'search': + return ; + case 'life-counter': + return ; + case 'login': + return ; + default: + return null; + } + }; + + return ( +
+ + {renderPage()} +
+ ); + } + + function App() { + return ( + + + + ); + } + export default App; From 80216ae4783096d52bad057e0c8e1f77b17abf4a Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:18:22 +0200 Subject: [PATCH 3/7] Update components with CSS animations --- src/components/DeckCard.tsx | 152 ++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/components/DeckCard.tsx b/src/components/DeckCard.tsx index 8632fa8..00cf7c7 100644 --- a/src/components/DeckCard.tsx +++ b/src/components/DeckCard.tsx @@ -1,77 +1,77 @@ -import React from 'react'; -import { AlertTriangle, Check, Edit } from 'lucide-react'; -import { Deck } from '../types'; -import { validateDeck } from '../utils/deckValidation'; - -interface DeckCardProps { - deck: Deck; - onEdit?: (deckId: string) => void; -} - -export default function DeckCard({ deck, onEdit }: DeckCardProps) { - - if(deck.id === "410ed539-a8f4-4bc4-91f1-6c113b9b7e25"){ - console.log("deck", deck.name); - console.log("cardEntities", deck.cards); - } - - const validation = validateDeck(deck); - const commander = deck.format === 'commander' ? deck.cards.find(card => - card.is_commander - )?.card : null; - - return ( -
onEdit?.(deck.id)} - > -
- {commander?.name -
-
- -
-
-

{deck.name}

- {validation.isValid ? ( -
- - Legal -
- ) : ( -
- - Issues -
- )} -
- -
- {deck.format} - {deck.cards.reduce((acc, curr) => acc + curr.quantity, 0)} cards -
- - {commander && ( -
- Commander: {commander.name} -
- )} - - -
-
- ); +import React from 'react'; +import { AlertTriangle, Check, Edit } from 'lucide-react'; +import { Deck } from '../types'; +import { validateDeck } from '../utils/deckValidation'; + +interface DeckCardProps { + deck: Deck; + onEdit?: (deckId: string) => void; +} + +export default function DeckCard({ deck, onEdit }: DeckCardProps) { + + if(deck.id === "410ed539-a8f4-4bc4-91f1-6c113b9b7e25"){ + console.log("deck", deck.name); + console.log("cardEntities", deck.cards); + } + + const validation = validateDeck(deck); + const commander = deck.format === 'commander' ? deck.cards.find(card => + card.is_commander + )?.card : null; + + return ( +
onEdit?.(deck.id)} + > +
+ {commander?.name +
+
+ +
+
+

{deck.name}

+ {validation.isValid ? ( +
+ + Legal +
+ ) : ( +
+ + Issues +
+ )} +
+ +
+ {deck.format} + {deck.cards.reduce((acc, curr) => acc + curr.quantity, 0)} cards +
+ + {commander && ( +
+ Commander: {commander.name} +
+ )} + + +
+
+ ); } From 91c237824a994eac07545984cc54642a39cb8c6d Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:18:45 +0200 Subject: [PATCH 4/7] Update components with CSS animations --- src/components/DeckList.tsx | 196 ++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/src/components/DeckList.tsx b/src/components/DeckList.tsx index e1569e6..3cbdb28 100644 --- a/src/components/DeckList.tsx +++ b/src/components/DeckList.tsx @@ -1,99 +1,99 @@ -import React, { useEffect, useState } from 'react'; -import { getCardById, getCardsByIds } from '../services/api'; -import { Deck } from '../types'; -import { supabase } from "../lib/supabase"; -import DeckCard from "./DeckCard"; - -interface DeckListProps { - onDeckEdit?: (deckId: string) => void; -} - -const DeckList = ({ onDeckEdit }: DeckListProps) => { - const [decks, setDecks] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchDecks = async () => { - const { data: decksData, error: decksError } = await supabase.from('decks').select('*'); - if (decksError) { - console.error('Error fetching decks:', decksError); - setLoading(false); - return; - } - - const decksWithCards = await Promise.all(decksData.map(async (deck) => { - const { data: cardEntities, error: cardsError } = await supabase - .from('deck_cards') - .select('*') - .eq('deck_id', deck.id); - - - - if (cardsError) { - console.error(`Error fetching cards for deck ${deck.id}:`, cardsError); - return { ...deck, cards: [] }; - } - - const cardIds = cardEntities.map((entity) => entity.card_id); - const uniqueCardIds = [...new Set(cardIds)]; - - if(deck.id === "410ed539-a8f4-4bc4-91f1-6c113b9b7e25"){ - console.log("uniqueCardIds", uniqueCardIds); - } - - - - try { - const scryfallCards = await getCardsByIds(uniqueCardIds); - - if (!scryfallCards) { - console.error("scryfallCards is undefined after getCardsByIds"); - return { ...deck, cards: [] }; - } - - const cards = cardEntities.map((entity) => { - const card = scryfallCards.find((c) => c.id === entity.card_id); - return { - card, - quantity: entity.quantity, - is_commander: entity.is_commander, - }; - }); - - return { - ...deck, - cards, - createdAt: new Date(deck.created_at), - updatedAt: new Date(deck.updated_at), - }; - } catch (error) { - console.error("Error fetching cards from Scryfall:", error); - return { ...deck, cards: [] }; - } - })); - - setDecks(decksWithCards); - setLoading(false); - }; - - fetchDecks(); - }, []); - - if (loading) { - return ( -
-
-
- ); - } - - return ( -
- {decks.map((deck) => ( - - ))} -
- ); -}; - +import React, { useEffect, useState } from 'react'; +import { getCardById, getCardsByIds } from '../services/api'; +import { Deck } from '../types'; +import { supabase } from "../lib/supabase"; +import DeckCard from "./DeckCard"; + +interface DeckListProps { + onDeckEdit?: (deckId: string) => void; +} + +const DeckList = ({ onDeckEdit }: DeckListProps) => { + const [decks, setDecks] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchDecks = async () => { + const { data: decksData, error: decksError } = await supabase.from('decks').select('*'); + if (decksError) { + console.error('Error fetching decks:', decksError); + setLoading(false); + return; + } + + const decksWithCards = await Promise.all(decksData.map(async (deck) => { + const { data: cardEntities, error: cardsError } = await supabase + .from('deck_cards') + .select('*') + .eq('deck_id', deck.id); + + + + if (cardsError) { + console.error(`Error fetching cards for deck ${deck.id}:`, cardsError); + return { ...deck, cards: [] }; + } + + const cardIds = cardEntities.map((entity) => entity.card_id); + const uniqueCardIds = [...new Set(cardIds)]; + + if(deck.id === "410ed539-a8f4-4bc4-91f1-6c113b9b7e25"){ + console.log("uniqueCardIds", uniqueCardIds); + } + + + + try { + const scryfallCards = await getCardsByIds(uniqueCardIds); + + if (!scryfallCards) { + console.error("scryfallCards is undefined after getCardsByIds"); + return { ...deck, cards: [] }; + } + + const cards = cardEntities.map((entity) => { + const card = scryfallCards.find((c) => c.id === entity.card_id); + return { + card, + quantity: entity.quantity, + is_commander: entity.is_commander, + }; + }); + + return { + ...deck, + cards, + createdAt: new Date(deck.created_at), + updatedAt: new Date(deck.updated_at), + }; + } catch (error) { + console.error("Error fetching cards from Scryfall:", error); + return { ...deck, cards: [] }; + } + })); + + setDecks(decksWithCards); + setLoading(false); + }; + + fetchDecks(); + }, []); + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+ {decks.map((deck) => ( + + ))} +
+ ); +}; + export default DeckList; From a76f8176f62adc04ee19c083b54e0b8a0156186f Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:19:15 +0200 Subject: [PATCH 5/7] Update components with CSS animations --- src/components/LoginForm.tsx | 292 +++++++++++++++++------------------ 1 file changed, 146 insertions(+), 146 deletions(-) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index fac6c86..abf542f 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -1,147 +1,147 @@ -import { useState, useEffect } from 'react'; - import { Mail, Lock, LogIn } from 'lucide-react'; - import { useAuth } from '../contexts/AuthContext'; - import { Card } from '../types'; - import { getRandomCards } from '../services/api'; - - export default function LoginForm() { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [isSignUp, setIsSignUp] = useState(false); - const [error, setError] = useState(null); - const { signIn, signUp } = useAuth(); - const [cards, setCards] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const loadCards = async () => { - try { - const randomCards = await getRandomCards(6); - setCards(randomCards); - } catch (error) { - console.error('Failed to load cards:', error); - } finally { - setLoading(false); - } - }; - - loadCards(); - }, []); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - - try { - if (isSignUp) { - await signUp(email, password); - } else { - await signIn(email, password); - } - window.location.href = '/'; // Redirect to home after successful login - } catch (error) { - setError(error instanceof Error ? error.message : 'An error occurred'); - } - }; - - if (loading) { - return
; - } - - return ( -
- {/* Animated Background */} -
-
- {[...cards, ...cards].map((card, index) => ( -
-
-
- ))} -
-
- - {/* Login Form */} -
-

- Deckerr -

- - {error && ( -
- {error} -
- )} - -
-
- -
- - setEmail(e.target.value)} - className="w-full pl-10 pr-4 py-2 bg-gray-800/50 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Enter your email" - required - /> -
-
-
- -
- - setPassword(e.target.value)} - className="w-full pl-10 pr-4 py-2 bg-gray-800/50 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" - placeholder="Enter your password" - required - /> -
-
- -
- -
- -
-
-
- ); +import { useState, useEffect } from 'react'; + import { Mail, Lock, LogIn } from 'lucide-react'; + import { useAuth } from '../contexts/AuthContext'; + import { Card } from '../types'; + import { getRandomCards } from '../services/api'; + + export default function LoginForm() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isSignUp, setIsSignUp] = useState(false); + const [error, setError] = useState(null); + const { signIn, signUp } = useAuth(); + const [cards, setCards] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const loadCards = async () => { + try { + const randomCards = await getRandomCards(6); + setCards(randomCards); + } catch (error) { + console.error('Failed to load cards:', error); + } finally { + setLoading(false); + } + }; + + loadCards(); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + try { + if (isSignUp) { + await signUp(email, password); + } else { + await signIn(email, password); + } + window.location.href = '/'; // Redirect to home after successful login + } catch (error) { + setError(error instanceof Error ? error.message : 'An error occurred'); + } + }; + + if (loading) { + return
; + } + + return ( +
+ {/* Animated Background */} +
+
+ {[...cards, ...cards].map((card, index) => ( +
+
+
+ ))} +
+
+ + {/* Login Form */} +
+

+ Deckerr +

+ + {error && ( +
+ {error} +
+ )} + +
+
+ +
+ + setEmail(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-800/50 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white transition-smooth" + placeholder="Enter your email" + required + /> +
+
+
+ +
+ + setPassword(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-gray-800/50 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white transition-smooth" + placeholder="Enter your password" + required + /> +
+
+ +
+ +
+ +
+
+
+ ); } From 19898d9f5ea2bc7011ac6a3c86a0cb0846322fc8 Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:19:32 +0200 Subject: [PATCH 6/7] Update components with CSS animations --- src/components/MagicCard.tsx | 60 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/MagicCard.tsx b/src/components/MagicCard.tsx index 426cefe..876c2ef 100644 --- a/src/components/MagicCard.tsx +++ b/src/components/MagicCard.tsx @@ -1,31 +1,31 @@ -import React from 'react'; -import { Card } from '../types'; - -interface MagicCardProps { - card: Card; -} - -const MagicCard = ({ card }: MagicCardProps) => { - return ( -
- {card.image_uris?.normal ? ( - {card.name} - ) : ( -
- No Image Available -
- )} - {card.prices?.usd && ( -
- ${card.prices.usd} -
- )} -
- ); -}; - +import React from 'react'; +import { Card } from '../types'; + +interface MagicCardProps { + card: Card; +} + +const MagicCard = ({ card }: MagicCardProps) => { + return ( +
+ {card.image_uris?.normal ? ( + {card.name} + ) : ( +
+ No Image Available +
+ )} + {card.prices?.usd && ( +
+ ${card.prices.usd} +
+ )} +
+ ); +}; + export default MagicCard; From 0475131b371933ad0065d7a49386734d67bdcd21 Mon Sep 17 00:00:00 2001 From: matthieu Date: Thu, 23 Oct 2025 16:20:09 +0200 Subject: [PATCH 7/7] Update components with CSS animations --- src/components/Navigation.tsx | 396 +++++++++++++++++----------------- 1 file changed, 198 insertions(+), 198 deletions(-) diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index a6a215c..23f1151 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -1,199 +1,199 @@ -import React, { useState, useRef, useEffect } from 'react'; - import { Home, PlusSquare, Library, LogOut, Settings, ChevronDown, Search, Heart, Menu } from 'lucide-react'; - import { useAuth } from '../contexts/AuthContext'; - import { supabase } from '../lib/supabase'; - - type Page = 'home' | 'deck' | 'login' | 'collection' | 'profile' | 'search' | 'life-counter'; - - interface NavigationProps { - currentPage: Page; - setCurrentPage: (page: Page) => void; - } - - export default function Navigation({ currentPage, setCurrentPage }: NavigationProps) { - const { user, signOut } = useAuth(); - const [showDropdown, setShowDropdown] = useState(false); - const [showMobileMenu, setShowMobileMenu] = useState(false); - const dropdownRef = useRef(null); - const mobileMenuRef = useRef(null); - const [username, setUsername] = useState(null); - - useEffect(() => { - const fetchProfile = async () => { - if (user) { - const { data } = await supabase - .from('profiles') - .select('username') - .eq('id', user.id) - .single(); - - if (data) { - setUsername(data.username); - } - } - }; - - fetchProfile(); - }, [user]); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setShowDropdown(false); - } - if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) { - setShowMobileMenu(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - const navItems = [ - { id: 'home' as const, label: 'Home', icon: Home }, - { id: 'deck' as const, label: 'New Deck', icon: PlusSquare }, - { id: 'collection' as const, label: 'Collection', icon: Library }, - { id: 'search' as const, label: 'Search', icon: Search }, - { id: 'life-counter' as const, label: 'Life Counter', icon: Heart }, - ]; - - const handleSignOut = async () => { - try { - await signOut(); - setCurrentPage('login'); - } catch (error) { - console.error('Error signing out:', error); - } - }; - - const getAvatarUrl = (userId: string) => { - return `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId}&backgroundColor=b6e3f4,c0aede,d1d4f9`; - }; - - return ( - <> - {/* Desktop Navigation - Top */} - - - {/* Mobile Navigation - Bottom */} - - - {/* Content Padding */} -
- - ); +import React, { useState, useRef, useEffect } from 'react'; + import { Home, PlusSquare, Library, LogOut, Settings, ChevronDown, Search, Heart, Menu } from 'lucide-react'; + import { useAuth } from '../contexts/AuthContext'; + import { supabase } from '../lib/supabase'; + + type Page = 'home' | 'deck' | 'login' | 'collection' | 'profile' | 'search' | 'life-counter'; + + interface NavigationProps { + currentPage: Page; + setCurrentPage: (page: Page) => void; + } + + export default function Navigation({ currentPage, setCurrentPage }: NavigationProps) { + const { user, signOut } = useAuth(); + const [showDropdown, setShowDropdown] = useState(false); + const [showMobileMenu, setShowMobileMenu] = useState(false); + const dropdownRef = useRef(null); + const mobileMenuRef = useRef(null); + const [username, setUsername] = useState(null); + + useEffect(() => { + const fetchProfile = async () => { + if (user) { + const { data } = await supabase + .from('profiles') + .select('username') + .eq('id', user.id) + .single(); + + if (data) { + setUsername(data.username); + } + } + }; + + fetchProfile(); + }, [user]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setShowDropdown(false); + } + if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) { + setShowMobileMenu(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const navItems = [ + { id: 'home' as const, label: 'Home', icon: Home }, + { id: 'deck' as const, label: 'New Deck', icon: PlusSquare }, + { id: 'collection' as const, label: 'Collection', icon: Library }, + { id: 'search' as const, label: 'Search', icon: Search }, + { id: 'life-counter' as const, label: 'Life Counter', icon: Heart }, + ]; + + const handleSignOut = async () => { + try { + await signOut(); + setCurrentPage('login'); + } catch (error) { + console.error('Error signing out:', error); + } + }; + + const getAvatarUrl = (userId: string) => { + return `https://api.dicebear.com/7.x/avataaars/svg?seed=${userId}&backgroundColor=b6e3f4,c0aede,d1d4f9`; + }; + + return ( + <> + {/* Desktop Navigation - Top */} + + + {/* Mobile Navigation - Bottom */} + + + {/* Content Padding */} +
+ + ); }