|
1 | | -"use client"; |
| 1 | +import Image from "next/image"; |
2 | 2 | import Link from 'next/link'; |
3 | 3 | import { useRouter } from 'next/router'; |
4 | | -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; |
| 4 | +import { useCallback } from 'react'; |
5 | 5 |
|
6 | 6 | const Card = ( { cardData } ) => { |
7 | 7 | const router = useRouter(); |
8 | | - const { letter, setName } = router.query; // only these two from URL |
9 | | - |
10 | | - const cardNameParam = cardData?.productName || cardData?.name || null; |
11 | | - |
12 | | - const derivePrimaryImageId = useCallback( () => { |
13 | | - if ( cardData?.id ) return cardData.id; |
14 | | - if ( cardData?.fallbackId ) return cardData.fallbackId; |
15 | | - if ( cardData?.detailId ) return cardData.detailId; |
16 | | - return null; |
17 | | - }, [ cardData?.detailId, cardData?.fallbackId, cardData?.id ] ); |
18 | | - |
19 | | - const primaryImageId = derivePrimaryImageId(); |
| 8 | + const { letter, setName, rarity } = router.query; // only these two from URL |
20 | 9 |
|
21 | 10 | const getLocalImagePath = useCallback( |
22 | | - ( cardId ) => ( cardId ? `/images/yugiohImages/${ String( cardId ) }.jpg` : null ), |
23 | | - [] |
| 11 | + ( cardId ) => `/images/yugiohImages/${ String( cardId ) }.jpg`, [] |
24 | 12 | ); |
25 | 13 |
|
26 | | - const [ sourceIndex, setSourceIndex ] = useState( 0 ); |
27 | | - const [ remoteLookupSources, setRemoteLookupSources ] = useState( [] ); |
28 | | - const hasAttemptedLookupRef = useRef( false ); |
29 | | - |
30 | | - useEffect( () => { |
31 | | - setSourceIndex( 0 ); |
32 | | - setRemoteLookupSources( [] ); |
33 | | - hasAttemptedLookupRef.current = false; |
34 | | - }, [ primaryImageId, cardData?.fallbackId, cardData?.detailId, cardNameParam ] ); |
35 | | - |
36 | | - const imageCandidates = useMemo( () => { |
37 | | - const sources = []; |
38 | | - const seen = new Set(); |
39 | | - |
40 | | - const pushSource = ( value ) => { |
41 | | - if ( value && !seen.has( value ) ) { |
42 | | - seen.add( value ); |
43 | | - sources.push( value ); |
44 | | - } |
45 | | - }; |
46 | | - |
47 | | - const pushLocal = ( id ) => { |
48 | | - const path = getLocalImagePath( id ); |
49 | | - if ( path ) pushSource( path ); |
50 | | - }; |
51 | | - |
52 | | - pushLocal( primaryImageId ); |
53 | | - pushLocal( cardData?.fallbackId ); |
54 | | - pushLocal( cardData?.detailId ); |
55 | | - pushSource( cardData?.remoteUrl ); |
56 | | - pushSource( cardData?.fallbackRemoteUrl ); |
57 | | - pushSource( cardData?.detailRemoteUrl ); |
58 | | - remoteLookupSources.forEach( pushSource ); |
59 | | - |
60 | | - return sources; |
61 | | - }, [ cardData?.detailId, cardData?.detailRemoteUrl, cardData?.fallbackId, cardData?.fallbackRemoteUrl, cardData?.remoteUrl, getLocalImagePath, primaryImageId, remoteLookupSources ] ); |
62 | | - |
63 | | - useEffect( () => { |
64 | | - if ( imageCandidates.length === 0 ) { |
65 | | - setSourceIndex( 0 ); |
66 | | - return; |
67 | | - } |
68 | | - |
69 | | - if ( sourceIndex >= imageCandidates.length ) { |
70 | | - setSourceIndex( imageCandidates.length - 1 ); |
71 | | - } |
72 | | - }, [ imageCandidates.length, sourceIndex ] ); |
73 | | - |
74 | | - useEffect( () => { |
75 | | - if ( remoteLookupSources.length === 0 ) return; |
76 | | - |
77 | | - const remoteStartIndex = Math.max( imageCandidates.length - remoteLookupSources.length, 0 ); |
78 | | - setSourceIndex( ( current ) => ( current < remoteStartIndex ? remoteStartIndex : current ) ); |
79 | | - }, [ imageCandidates.length, remoteLookupSources.length ] ); |
80 | | - |
81 | | - const tryRemoteLookup = useCallback( async () => { |
82 | | - if ( hasAttemptedLookupRef.current ) return; |
83 | | - if ( !cardNameParam ) return; |
84 | | - |
85 | | - hasAttemptedLookupRef.current = true; |
86 | | - |
87 | | - try { |
88 | | - const response = await fetch( |
89 | | - `/api/Yugioh/card/lookup?name=${ encodeURIComponent( cardNameParam ) }` |
90 | | - ); |
91 | | - if ( !response.ok ) return; |
92 | | - const data = await response.json(); |
93 | | - const remoteImages = Array.isArray( data?.card_images ) |
94 | | - ? data.card_images.map( ( image ) => image?.image_url ).filter( Boolean ) |
95 | | - : []; |
96 | | - if ( remoteImages.length > 0 ) { |
97 | | - setRemoteLookupSources( remoteImages ); |
98 | | - } |
99 | | - } catch ( error ) { |
100 | | - console.warn( 'Card image lookup failed', error ); |
101 | | - } |
102 | | - }, [ cardNameParam ] ); |
103 | | - |
104 | | - useEffect( () => { |
105 | | - if ( imageCandidates.length === 0 ) { |
106 | | - tryRemoteLookup(); |
107 | | - } |
108 | | - }, [ imageCandidates.length, tryRemoteLookup ] ); |
109 | | - |
110 | | - const imageSource = imageCandidates[ sourceIndex ] || ''; |
111 | | - |
112 | | - const handleImageError = useCallback( () => { |
113 | | - setSourceIndex( ( current ) => { |
114 | | - const nextIndex = current + 1; |
115 | | - if ( nextIndex < imageCandidates.length ) { |
116 | | - return nextIndex; |
117 | | - } |
118 | | - tryRemoteLookup(); |
119 | | - return current; |
120 | | - } ); |
121 | | - }, [ imageCandidates.length, tryRemoteLookup ] ); |
122 | | - |
123 | | - const detailCardId = cardData?.detailId ?? cardData?.id ?? primaryImageId ?? null; |
124 | | - const resolvedCardId = detailCardId ?? 'lookup'; |
125 | | - |
126 | | - const linkQuery = { |
127 | | - card: resolvedCardId, |
128 | | - source: 'set', // flag so card-details knows this came from a set page |
129 | | - }; |
130 | | - |
131 | | - if ( letter ) { |
132 | | - linkQuery.letter = letter; |
133 | | - } |
134 | | - |
135 | | - if ( setName ) { |
136 | | - linkQuery.set_name = setName; |
137 | | - } |
138 | | - |
139 | | - if ( cardNameParam ) { |
140 | | - linkQuery.card_name = cardNameParam; |
141 | | - } |
142 | | - |
143 | 14 | return ( |
144 | 15 | <> |
145 | | - <div className="hover:scale-95 hover:duration-100 transition-transform -p-5 h-72 w-full object-scale-down object-center"> |
146 | | - <Link |
147 | | - href={ { |
148 | | - pathname: '/yugioh/sets/[letter]/cards/card-details', |
149 | | - query: linkQuery, |
150 | | - } } |
151 | | - passHref |
152 | | - > |
| 16 | + <Link |
| 17 | + href={ { |
| 18 | + pathname: "/yugioh/sets/[letter]/cards/card-details", |
| 19 | + query: { |
| 20 | + card: cardData.id, |
| 21 | + letter: letter, |
| 22 | + set_name: setName, |
| 23 | + set_rarity: rarity, |
| 24 | + source: "set", // flag so card-details knows this came from a set page |
| 25 | + }, |
| 26 | + } } |
| 27 | + passHref |
| 28 | + > |
| 29 | + <div className="hover:scale-95 hover:duration-100 transition-transform"> |
153 | 30 | <img |
154 | | - className=" max-w-full w-full h-96 max-h-96" |
155 | | - src={ imageSource } |
156 | | - onError={ handleImageError } |
157 | | - alt={ `Card Image - ${ cardData.productName || cardData.name || 'Unknown' }` } |
158 | | - loading="lazy" |
| 31 | + className="lg:object-cover object-scale-down object-top w-full h-72 aspect-video" |
| 32 | + as="image" |
| 33 | + priority="true" |
| 34 | + unoptimized="true" |
| 35 | + src={ getLocalImagePath( cardData.id ) } |
| 36 | + alt={ `Card Image - ${ cardData.productName }` } |
| 37 | + width={ "auto" } |
| 38 | + height={ "auto" } |
159 | 39 | /> |
160 | | - </Link> |
161 | | - </div> |
| 40 | + </div> |
| 41 | + </Link> |
162 | 42 | </> |
163 | 43 | ); |
164 | 44 | }; |
165 | 45 |
|
166 | 46 | export default Card; |
167 | | - |
168 | | - |
169 | | - |
0 commit comments