Skip to content

Commit 3fb27de

Browse files
hh
1 parent d5b3e06 commit 3fb27de

14 files changed

Lines changed: 300 additions & 251 deletions

File tree

components/Navigation/SideNav.js

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
1-
import Link from 'next/link';
2-
import { useEffect, useState } from 'react';
3-
import { useRouter } from 'next/router';
4-
1+
import Link from "next/link";
2+
import { useEffect, useState } from "react";
3+
import { useRouter } from "next/router";
54

65
export default function SideNav() {
76
const [ isAuthenticated, setIsAuthenticated ] = useState( false );
87
const router = useRouter();
98

109
useEffect( () => {
11-
const token = localStorage.getItem( "token" );
12-
setIsAuthenticated( !!token );
13-
14-
const tokenPollInterval = setInterval( () => {
15-
const updatedToken = localStorage.getItem( "token" );
16-
setIsAuthenticated( !!updatedToken );
17-
}, 500 );
10+
const checkAuth = async () => {
11+
try {
12+
const res = await fetch( "/api/auth/validate", {
13+
method: "GET",
14+
credentials: "include",
15+
} );
16+
setIsAuthenticated( res.ok );
17+
} catch {
18+
setIsAuthenticated( false );
19+
}
20+
};
1821

19-
return () => clearInterval( tokenPollInterval );
20-
}, [] );
22+
checkAuth();
23+
}, [ router ] );
2124

22-
const handleLogout = () => {
23-
localStorage.removeItem( "token" );
24-
setIsAuthenticated( false );
25-
router.push( "/login" );
25+
const handleLogout = async () => {
26+
try {
27+
await fetch( "/api/auth/logout", {
28+
method: "POST",
29+
credentials: "include",
30+
} );
31+
setIsAuthenticated( false );
32+
router.push( "/login" );
33+
} catch {
34+
console.error( "Logout failed" );
35+
}
2636
};
2737

2838
return (
29-
<nav className="p-4 min-h-max z-50">
39+
<nav className="p-4 min-h-max z-50">
3040
<ul className="inset-2 rounded-lg bg-opacity-10">
3141
<li className="navButton mb-2 rounded-lg`">
3242
<Link href="/">
33-
<span className="rounded-xs block w-full text-left p-2 text-white bg-clip-padding border border-zinc-600 font-semibold backdrop-opacity-90 backdrop-blur-md hover:bg-zinc-400 bg-gradient-to-tr to-neutral-400 from-purple-800 hover:text-white">
43+
<span className="rounded-xs block w-full text-left p-2 text-white bg-clip-padding border border-zinc-600 font-semibold backdrop-opacity-90 backdrop-blur-md hover:bg-zinc-400 bg-gradient-to-tr to-neutral-400 from-purple-800 hover:text-white">
3444
Yu-Gi-Oh! Card Prices
3545
</span>
3646
</Link>

components/Yugioh/Card.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useCallback } from 'react';
55

66
const Card = ( { cardData } ) => {
77
const router = useRouter();
8-
const { letter, setName, productName } = router.query; // only these two from URL
8+
const { letter, setName } = router.query; // only these two from URL
99

1010
const getLocalImagePath = useCallback(
1111
( cardId ) => `/images/yugiohImages/${ String( cardId ) }.jpg`, []
@@ -33,8 +33,8 @@ const Card = ( { cardData } ) => {
3333
unoptimized="true"
3434
src={ getLocalImagePath( cardData.id ) }
3535
alt={ `Card Image - ${ cardData.productName }` }
36-
width="auto"
37-
height="auto"
36+
width={ 1600 }
37+
height={ 1600 }
3838
/>
3939
</div>
4040
</Link>

components/Yugioh/YugiohCardDataTable.js

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,26 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
2525
const makeKey = ( { card } ) =>
2626
`${ card?.productName }|${ card?.setName }|${ card?.number }|${ card?.printing }`;
2727

28-
// 1. Precompute stable unique IDs including duplicate counts for all items in matchedCardData
28+
// Stable unique IDs including duplicate counts
2929
const itemUniqueIds = useMemo( () => {
3030
const counts = {};
3131
return matchedCardData.map( ( item ) => {
3232
const baseKey = makeKey( item );
3333
counts[ baseKey ] = ( counts[ baseKey ] || 0 ) + 1;
34-
return `${ baseKey }|${ counts[ baseKey ] }`; // e.g. "card|set|num|print|1", "card|set|num|print|2"
34+
return `${ baseKey }|${ counts[ baseKey ] }`;
3535
} );
3636
}, [ matchedCardData ] );
3737

38-
// 2. Sort matchedCardData with original index retained for referencing unique IDs
38+
// Sorted data with index
3939
const sortedDataWithIndex = useMemo( () => {
4040
if ( !Array.isArray( matchedCardData ) ) return [];
41-
4241
const sorted = [ ...matchedCardData ].map( ( item, index ) => ( { item, originalIndex: index } ) );
4342
sorted.sort( ( a, b ) => {
4443
const aValue = sortConfig.key === 'marketPrice'
45-
? a.item.data?.marketPrice || 0
44+
? parseFloat( a.item.data?.marketPrice ) || 0
4645
: a.item.card[ sortConfig.key ];
4746
const bValue = sortConfig.key === 'marketPrice'
48-
? b.item.data?.marketPrice || 0
47+
? parseFloat( b.item.data?.marketPrice ) || 0
4948
: b.item.card[ sortConfig.key ];
5049
if ( aValue < bValue ) return sortConfig.direction === 'ascending' ? -1 : 1;
5150
if ( aValue > bValue ) return sortConfig.direction === 'ascending' ? 1 : -1;
@@ -54,15 +53,14 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
5453
return sorted;
5554
}, [ matchedCardData, sortConfig ] );
5655

57-
// 3. Pagination slice on sorted data
56+
// Pagination slice
5857
const sortedAndPaginatedData = useMemo( () => {
5958
const indexOfLast = currentPage * itemsPerPage;
6059
const indexOfFirst = indexOfLast - itemsPerPage;
6160
const currentItems = sortedDataWithIndex.slice( indexOfFirst, indexOfLast );
6261
return { currentItems, totalCount: matchedCardData.length };
6362
}, [ sortedDataWithIndex, currentPage, matchedCardData.length ] );
6463

65-
// Unique IDs in sorted order (full dataset)
6664
const sortedUniqueIds = useMemo(
6765
() => sortedDataWithIndex.map( ( { originalIndex } ) => itemUniqueIds[ originalIndex ] ),
6866
[ sortedDataWithIndex, itemUniqueIds ]
@@ -77,7 +75,6 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
7775
if ( isShift && lastCheckedKey !== null && sortedUniqueIds.length ) {
7876
const start = sortedUniqueIds.indexOf( lastCheckedKey );
7977
const end = sortedUniqueIds.indexOf( uniqueId );
80-
8178
if ( start !== -1 && end !== -1 ) {
8279
const [ lo, hi ] = start < end ? [ start, end ] : [ end, start ];
8380
for ( let i = lo; i <= hi; i++ ) {
@@ -96,8 +93,6 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
9693
[ selectedKeys, lastCheckedKey, sortedUniqueIds ]
9794
);
9895

99-
100-
// Current page unique IDs
10196
const pagedUniqueIds = useMemo(
10297
() => sortedAndPaginatedData.currentItems.map( ( { originalIndex } ) => itemUniqueIds[ originalIndex ] ),
10398
[ sortedAndPaginatedData, itemUniqueIds ]
@@ -127,7 +122,6 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
127122
setSelectAllChecked( false );
128123
};
129124

130-
// Sort handler unchanged
131125
const handleSort = useCallback(
132126
( key ) => {
133127
setSortConfig( ( prev ) => {
@@ -142,19 +136,18 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
142136
[]
143137
);
144138

145-
// Add to collection logic unchanged except use uniqueId filtering
146139
const addToCollection = useCallback( async () => {
147140
if ( selectedKeys.size === 0 ) {
148-
return setNotification( { show: true, message: 'No cards were selected to add to the collection!' } );
149-
}
150-
const token = localStorage.getItem( "token" );
151-
if ( !token ) {
152-
setNotification( { show: true, message: "You must be logged in to add cards." } );
153-
return;
141+
return setNotification( {
142+
show: true,
143+
message: "No cards were selected to add to the collection!",
144+
} );
154145
}
146+
155147
const selectedData = matchedCardData.filter( ( _, index ) =>
156148
selectedKeys.has( itemUniqueIds[ index ] )
157149
);
150+
158151
const collectionArray = selectedData.map( ( { card, data } ) => ( {
159152
productName: card?.productName,
160153
setName: card?.setName,
@@ -166,23 +159,31 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
166159
lowPrice: data?.lowPrice,
167160
quantity: 1,
168161
} ) );
162+
169163
try {
170164
const response = await fetch( `/api/Yugioh/cards`, {
171-
method: 'POST',
165+
method: "POST",
166+
credentials: "include", // ✅ automatically sends the JWT cookie
172167
headers: {
173168
"Content-Type": "application/json",
174-
Authorization: `Bearer ${ token }`,
175169
},
176170
body: JSON.stringify( { cards: collectionArray } ),
177171
} );
172+
178173
if ( !response.ok ) throw new Error();
179-
setNotification( { show: true, message: 'Card(s) added to the collection!' } );
174+
setNotification( {
175+
show: true,
176+
message: "Card(s) added to the collection!",
177+
} );
180178
} catch {
181-
setNotification( { show: true, message: 'Card(s) failed to save!' } );
179+
setNotification( {
180+
show: true,
181+
message: "Card(s) failed to save!",
182+
} );
182183
}
183184
}, [ selectedKeys, matchedCardData, itemUniqueIds ] );
184185

185-
// Download CSV using uniqueIds filtering
186+
186187
const downloadCSV = useCallback( () => {
187188
if ( selectedKeys.size === 0 ) {
188189
setNotification( { show: true, message: 'No cards selected to download!' } );
@@ -212,12 +213,18 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
212213
link.click();
213214
document.body.removeChild( link );
214215
URL.revokeObjectURL( url );
215-
}, [ selectedKeys, matchedCardData, itemUniqueIds ] );
216+
}, [ selectedKeys, matchedCardData, setMatchedCardData, itemUniqueIds ] );
216217

217218
const handleGoToCollectionPage = useCallback( () => {
218219
router.push( '/yugioh/my-collection' );
219220
}, [ router ] );
220221

222+
// Format helper for prices
223+
const formatPrice = ( val ) => {
224+
const num = parseFloat( val );
225+
return isNaN( num ) ? "0.00" : `${ num.toFixed( 2 ) }`;
226+
};
227+
221228
return (
222229
<div className="mx-auto w-full mb-10 min-h-fit">
223230
<Notification
@@ -241,7 +248,6 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
241248

242249
{/* Table */ }
243250
<div className="w-full mx-auto overflow-x-auto">
244-
{/* Bulk Action Bar */ }
245251
{ selectedKeys.size > 0 && (
246252
<div className="my-5 py-2 h-fit bg-stone-500 bg-opacity-20 backdrop-blur backdrop-filter text-white p-2 mb-2 flex justify-between items-center">
247253
<div className="text-sm float-start">
@@ -324,10 +330,10 @@ const YugiohCardDataTable = ( { matchedCardData, setMatchedCardData } ) => {
324330
{ card?.condition || 'N/A' }
325331
</td>
326332
<td className="p-2 text-center border-t border-gray-100 text-xs lg:text-sm sm:text-left text-black hover:bg-black hover:text-white">
327-
{ data?.marketPrice ?? 'N/A' }
333+
{ formatPrice( data?.marketPrice ) }
328334
</td>
329335
<td className="p-2 text-center border-t border-gray-100 text-xs lg:text-sm sm:text-left text-black hover:bg-black hover:text-white">
330-
{ data?.lowPrice ?? 'N/A' }
336+
{ formatPrice( data?.lowPrice ) }
331337
</td>
332338
</tr>
333339
);

middleware/authenticate.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import jwt from "jsonwebtoken";
22
import clientPromise from "@/utils/mongo";
3+
import { ObjectId } from "mongodb";
34

45
export default async function authenticate( req, res, next ) {
5-
const token = req.headers.authorization?.split( " " )[ 1 ];
6+
const token = req.cookies?.token;
67

78
if ( !token ) {
89
return res.status( 401 ).json( { error: "Unauthorized" } );
@@ -14,7 +15,10 @@ export default async function authenticate( req, res, next ) {
1415

1516
const client = await clientPromise;
1617
const db = client.db( "cardPriceApp" );
17-
const user = await db.collection( "users" ).findOne( { _id: { $eq: decoded.userId } } );
18+
19+
const user = await db
20+
.collection( "users" )
21+
.findOne( { _id: new ObjectId( decoded.userId ) } );
1822

1923
if ( !user ) {
2024
return res.status( 401 ).json( { error: "Invalid token" } );

0 commit comments

Comments
 (0)