Skip to content

Commit 273fb87

Browse files
hh
1 parent d6b9d61 commit 273fb87

16 files changed

Lines changed: 420 additions & 414 deletions

File tree

components/Yugioh/Card.js

Lines changed: 28 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,46 @@
1-
"use client";
1+
import Image from "next/image";
22
import Link from 'next/link';
33
import { useRouter } from 'next/router';
4-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4+
import { useCallback } from 'react';
55

66
const Card = ( { cardData } ) => {
77
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
209

2110
const getLocalImagePath = useCallback(
22-
( cardId ) => ( cardId ? `/images/yugiohImages/${ String( cardId ) }.jpg` : null ),
23-
[]
11+
( cardId ) => `/images/yugiohImages/${ String( cardId ) }.jpg`, []
2412
);
2513

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-
14314
return (
14415
<>
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">
15330
<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" }
15939
/>
160-
</Link>
161-
</div>
40+
</div>
41+
</Link>
16242
</>
16343
);
16444
};
16545

16646
export default Card;
167-
168-
169-

components/Yugioh/GridView.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const GridView = ( { aggregatedData, onDeleteCard, onUpdateCard } ) => {
131131
</button>
132132
</div>
133133

134-
<div className="container box-content glass sm:px-5 rounded-xl w-full mx-auto flex flex-wrap flex-row gap-5 sm:grid sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
134+
<div className="container box-content bg-black/30 border border-white/10 rounded-md divide-x-2 inset-1 max-w-7xl mx-auto flex flex-wrap flex-row gap-5 sm:grid sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
135135

136136

137137

middleware/authenticate.js

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,93 @@ import jwt from "jsonwebtoken";
22
import clientPromise from "@/utils/mongo";
33
import { ObjectId } from "mongodb";
44

5-
export default async function authenticate( req, res, next ) {
6-
const token = req.cookies?.token;
5+
function extractBearerToken( headerValue ) {
6+
if ( !headerValue || typeof headerValue !== "string" ) {
7+
return null;
8+
}
9+
10+
const [ scheme, value ] = headerValue.split( " " );
11+
if ( scheme?.toLowerCase() !== "bearer" || !value ) {
12+
return null;
13+
}
14+
15+
return value.trim();
16+
}
17+
18+
function getTokenFromCookies( req ) {
19+
const cookieToken = req.cookies?.token;
20+
return typeof cookieToken === "string" && cookieToken.length > 0
21+
? cookieToken
22+
: null;
23+
}
24+
25+
export function getTokenFromRequest( req ) {
26+
return (
27+
extractBearerToken( req.headers?.authorization ) ||
28+
getTokenFromCookies( req ) ||
29+
null
30+
);
31+
}
32+
33+
export async function requireUser(
34+
req,
35+
res,
36+
{ ensureDatabaseUser = false } = {}
37+
) {
38+
const token = getTokenFromRequest( req );
739

840
if ( !token ) {
9-
return;
41+
res.status( 401 ).json( { error: "Unauthorized: No token provided" } );
42+
return null;
1043
}
1144

45+
let decoded;
1246
try {
13-
const decoded = jwt.verify( token, process.env.JWT_SECRET );
14-
req.user = decoded;
47+
decoded = jwt.verify( token, process.env.JWT_SECRET );
48+
} catch ( error ) {
49+
console.error( "Invalid token:", error );
50+
res.status( 401 ).json( { error: "Unauthorized: Invalid token" } );
51+
return null;
52+
}
1553

54+
if ( !decoded?.userId || !decoded?.username ) {
55+
res.status( 401 ).json( { error: "Unauthorized: Invalid token payload" } );
56+
return null;
57+
}
58+
59+
req.user = decoded;
60+
61+
if ( !ensureDatabaseUser ) {
62+
return { token, decoded };
63+
}
64+
65+
try {
1666
const client = await clientPromise;
1767
const db = client.db( "cardPriceApp" );
18-
1968
const user = await db
2069
.collection( "users" )
2170
.findOne( { _id: new ObjectId( decoded.userId ) } );
2271

2372
if ( !user ) {
24-
return res.status( 401 ).json( { error: "Invalid token" } );
73+
res.status( 401 ).json( { error: "Unauthorized: User not found" } );
74+
return null;
2575
}
2676

27-
next();
77+
return { token, decoded, user };
2878
} catch ( error ) {
29-
console.error( "Authentication error:", error );
30-
res.status( 401 ).json( { error: "Unauthorized" } );
79+
console.error( "Authentication lookup error:", error );
80+
res.status( 500 ).json( { error: "Internal server error" } );
81+
return null;
82+
}
83+
}
84+
85+
export default async function authenticate( req, res, next ) {
86+
const authResult = await requireUser( req, res, { ensureDatabaseUser: true } );
87+
if ( !authResult ) {
88+
return;
89+
}
90+
91+
if ( typeof next === "function" ) {
92+
return next();
3193
}
3294
}

pages/api/Yugioh/card/[...cardId].js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default async function handler( req, res ) {
66
const response = await fetch( url );
77
const data = await response.json();
88

9-
if ( !data || data.data.length === 0 ) {
9+
if ( !data || data?.data[ 0 ]?.length === 0 ) {
1010
return res.status( 404 ).json( { error: "Card not found" } );
1111
}
1212

@@ -25,11 +25,16 @@ export default async function handler( req, res ) {
2525
set_name: set.set_name,
2626
set_code: set.set_code,
2727
set_rarity: set.set_rarity,
28+
rarity_code: set.set_rarity_code,
2829
set_edition: set.set_edition || "Unknown Edition",
2930
set_price: set.set_price
3031
} ) ) || [],
31-
card_prices: card?.card_prices?.[ 0 ] || {},
32-
ebay_price: card?.card_prices?.[ 0 ]?.ebay_price || {}
32+
card_prices: card?.card_prices?.map( price => ( {
33+
tcgplayer_price: price.tcgplayer_price || "0.00",
34+
ebay_price: price.ebay_price || "0.00",
35+
amazon_price: price.amazon_price || "0.00"
36+
}
37+
) ) || [],
3338
};
3439

3540
res.status( 200 ).json( formattedCard );

0 commit comments

Comments
 (0)