Skip to content

Commit c464f4f

Browse files
hh
1 parent 58864fe commit c464f4f

24 files changed

Lines changed: 1788 additions & 893 deletions

components/Navigation/Breadcrumb.js

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { useEffect, useState } from 'react';
1+
import { useEffect, useMemo, useState } from 'react';
22
import Link from 'next/link';
33
import { useRouter } from 'next/router';
44

5-
const Breadcrumb = () => {
5+
const Breadcrumb = ( { items = null } ) => {
66
const router = useRouter();
77
const { pathname, query, isReady } = router;
88
const [ hasMounted, setHasMounted ] = useState( false );
99
const [ cardName, setCardName ] = useState( null );
10+
const hasCustomItems = Array.isArray( items ) && items.length > 0;
1011

1112
// Ensure client-only rendering
1213
useEffect( () => {
@@ -15,6 +16,10 @@ const Breadcrumb = () => {
1516

1617
// Fetch the card's human-readable name if we're on a details page
1718
useEffect( () => {
19+
if ( hasCustomItems ) {
20+
return undefined;
21+
}
22+
1823
if ( query.card ) {
1924
fetch( `/api/Yugioh/card/${ encodeURIComponent( query.card ) }` )
2025
.then( ( res ) => res.json() )
@@ -23,78 +28,96 @@ const Breadcrumb = () => {
2328
} )
2429
.catch( ( err ) => console.error( 'Breadcrumb: failed to fetch card name', err ) );
2530
}
26-
}, [ query.card ] );
31+
return undefined;
32+
}, [ hasCustomItems, query.card ] );
2733

2834
// Fall back to the card name passed in the query if no id lookup is available
2935
useEffect( () => {
36+
if ( hasCustomItems ) {
37+
return;
38+
}
39+
3040
if ( !query.card && query.card_name ) {
3141
const nameValue = Array.isArray( query.card_name ) ? query.card_name[ 0 ] : query.card_name;
3242
setCardName( nameValue || null );
3343
}
34-
}, [ query.card, query.card_name ] );
35-
36-
if ( !isReady || !hasMounted ) return null;
37-
38-
const crumbs = [];
39-
40-
// 1. Home
41-
crumbs.push( { label: 'Home', href: '/yugioh' } );
44+
}, [ hasCustomItems, query.card, query.card_name ] );
45+
46+
const crumbs = useMemo( () => {
47+
if ( hasCustomItems ) {
48+
return items
49+
.filter( Boolean )
50+
.map( ( item ) => ( {
51+
label: item?.label ?? '',
52+
href: item?.href ?? null,
53+
} ) )
54+
.filter( ( item ) => item.label );
55+
}
4256

43-
// 2. Set Index
44-
crumbs.push( { label: 'Set Index', href: '/yugioh/sets/set-index' } );
57+
const autoCrumbs = [];
4558

46-
// 3. Sets Starting With: [LETTER]
47-
if ( query.letter && pathname.includes( '/yugioh/sets' ) ) {
48-
crumbs.push( {
49-
label: `Sets Starting With: ${ String( query.letter ).toUpperCase() }`,
50-
href: `/yugioh/sets/${ encodeURIComponent( query.letter ) }`,
51-
} );
52-
}
59+
// 1. Home
60+
autoCrumbs.push( { label: 'Home', href: '/yugioh' } );
5361

54-
// 4. Cards In Set: [SET NAME]
62+
// 2. Set Index
63+
autoCrumbs.push( { label: 'Set Index', href: '/yugioh/sets/set-index' } );
5564

56-
// 5. Context-specific parent
57-
if ( pathname.includes( '/card-details' ) ) {
58-
if ( query.source === 'set' ) {
59-
crumbs.push( {
60-
label: `Cards In Set: ${ query.set_name }`,
61-
href: `/yugioh/sets/${ encodeURIComponent( query.letter ) }/${ encodeURIComponent( query.set_name ) }`
62-
} );
63-
} else if ( query.source === 'collection' ) {
64-
crumbs.push( {
65-
label: `Cards In Set: ${ query.set_name }`,
66-
href: `/yugioh/sets/${ encodeURIComponent( query.letter ) }/${ encodeURIComponent( query.set_name ) }`
67-
} );
68-
crumbs.push( {
69-
label: 'My Collection',
70-
href: '/yugioh/my-collection'
65+
// 3. Sets Starting With: [LETTER]
66+
if ( query.letter && pathname.includes( '/yugioh/sets' ) ) {
67+
autoCrumbs.push( {
68+
label: `Sets Starting With: ${ String( query.letter ).toUpperCase() }`,
69+
href: `/yugioh/sets/${ encodeURIComponent( query.letter ) }`,
7170
} );
7271
}
7372

74-
// 6. Card Details
75-
const fallbackCardName =
76-
cardName ||
77-
( Array.isArray( query.card_name ) ? query.card_name[ 0 ] : query.card_name ) ||
78-
( Array.isArray( query.card ) ? query.card[ 0 ] : query.card );
73+
// 4. Cards In Set: [SET NAME]
74+
75+
// 5. Context-specific parent
76+
if ( pathname.includes( '/card-details' ) ) {
77+
if ( query.source === 'set' ) {
78+
autoCrumbs.push( {
79+
label: `Cards In Set: ${ query.set_name }`,
80+
href: `/yugioh/sets/${ encodeURIComponent( query.letter ) }/${ encodeURIComponent( query.set_name ) }`,
81+
} );
82+
} else if ( query.source === 'collection' ) {
83+
autoCrumbs.push( {
84+
label: `Cards In Set: ${ query.set_name }`,
85+
href: `/yugioh/sets/${ encodeURIComponent( query.letter ) }/${ encodeURIComponent( query.set_name ) }`,
86+
} );
87+
autoCrumbs.push( {
88+
label: 'My Collection',
89+
href: '/yugioh/my-collection',
90+
} );
91+
}
92+
93+
// 6. Card Details
94+
const fallbackCardName =
95+
cardName ||
96+
( Array.isArray( query.card_name ) ? query.card_name[ 0 ] : query.card_name ) ||
97+
( Array.isArray( query.card ) ? query.card[ 0 ] : query.card );
98+
99+
autoCrumbs.push( {
100+
label: `Card Details: ${ fallbackCardName || 'Unknown Card' }`,
101+
href: null,
102+
} );
103+
}
79104

80-
crumbs.push( {
81-
label: `Card Details: ${ fallbackCardName || 'Unknown Card' }`,
82-
href: null
83-
} );
84-
}
105+
return autoCrumbs;
106+
}, [ cardName, hasCustomItems, items, pathname, query.card, query.card_name, query.letter, query.set_name, query.source ] );
85107

108+
if ( !isReady || !hasMounted ) return null;
86109

87110
return (
88111
<nav
89-
className="flex flex-wrap whitespace-break-spaces bg-transparent backdrop glass text-shadow rounded-sm w-full"
112+
className="mx-auto w-full max-w-7xl px-4 pt-4 sm:px-6 lg:px-8"
90113
aria-label="Breadcrumb"
91114
>
92-
<ol className="mx-auto sm:mx-0 sm:float-start flex flex-wrap w-fit space-x-4 px-4 sm:px-6 lg:px-8">
115+
<ol className="flex flex-wrap items-center gap-2 rounded-2xl border border-white/10 bg-black/40 px-4 py-3 text-sm text-white/80 shadow-2xl backdrop-blur">
93116
{ crumbs.map( ( crumb, idx ) => (
94-
<li key={ idx } className="flex flex-wrap items-center">
117+
<li key={ `${ crumb.label }-${ idx }` } className="flex min-w-0 items-center gap-2">
95118
{ idx > 0 && (
96119
<svg
97-
className="h-5 w-5 flex-shrink-0 text-white text-shadow backdrop"
120+
className="h-4 w-4 flex-shrink-0 text-white/40"
98121
viewBox="0 0 24 44"
99122
fill="currentColor"
100123
aria-hidden="true"
@@ -103,13 +126,13 @@ const Breadcrumb = () => {
103126
</svg>
104127
) }
105128
{ crumb.href ? (
106-
<span className="ml-4 p-2 text-sm font-black text-white text-shadow hover:text-gray-300">
107-
<Link href={ crumb.href } passHref>
129+
<span className="min-w-0 text-sm font-semibold text-white transition hover:text-indigo-200">
130+
<Link href={ crumb.href }>
108131
{ crumb.label }
109132
</Link>
110133
</span>
111134
) : (
112-
<span className="ml-4 p-2 text-sm font-black text-white text-shadow">
135+
<span className="min-w-0 text-sm font-semibold text-white">
113136
{ crumb.label }
114137
</span>
115138
) }

components/Sports/CardSetSelector.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const CardSetSelector: React.FC<CardSetSelectorProps> = ({ value, onChange }) =>
1111
<select
1212
value={value}
1313
onChange={(e) => onChange(e.target.value)}
14-
className="w-full md:w-auto px-4 py-2 bg-white text-black font-semibold rounded-lg border border-gray-700 focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
14+
aria-label="Select a sports card set"
15+
className="w-full rounded-2xl border border-white/10 bg-black/60 px-4 py-3 text-base font-medium text-white shadow-sm transition hover:border-white/30 focus:border-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-400/40"
1516
>
1617
{CARD_SETS.map((set) => (
1718
<option key={set} value={set}>
@@ -22,4 +23,4 @@ const CardSetSelector: React.FC<CardSetSelectorProps> = ({ value, onChange }) =>
2223
);
2324
};
2425

25-
export default CardSetSelector;
26+
export default CardSetSelector;

components/Sports/SportsPagination.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ const SportsPagination = ( { totalPages, currentPage, onPageChange } ) => {
4646
};
4747

4848
return (
49-
<nav className="pagination-container mt-4 my-10 w-full glass backdrop">
50-
<ul className="pagination flex justify-center items-center">
49+
<nav className="mt-4 w-full" aria-label="Sports card pagination">
50+
<ul className="mx-auto flex w-fit flex-wrap items-center justify-center gap-2 rounded-full border border-white/10 bg-black/40 px-2 py-2 text-white shadow-2xl backdrop-blur">
5151
{/* Previous button */ }
5252
<li className={ `page-item ${ currentPage === 1 ? 'disabled' : '' }` }>
5353
<button
5454
onClick={ () => onPageChange( currentPage - 1 ) }
5555
disabled={ currentPage === 1 }
56-
className="border border-white page-link px-3 py-2 mx-1 text-black font-bold bg-white rounded hover:bg-black hover:text-white"
56+
className="rounded-full border border-white/20 bg-white/10 px-4 py-2 text-sm font-semibold text-white transition hover:border-white/40 hover:bg-white/15 disabled:cursor-not-allowed disabled:opacity-50"
5757
>
5858
Previous
5959
</button>
@@ -67,13 +67,13 @@ const SportsPagination = ( { totalPages, currentPage, onPageChange } ) => {
6767
onChange={ handleInputChange }
6868
onBlur={ handleInputBlur }
6969
onKeyUp={ handleInputKeyPress }
70-
className="page-input text-black px-3 py-2 mx-1 text-center border rounded w-16"
70+
className="w-16 rounded-full border border-white/20 bg-black/60 px-3 py-2 text-center text-sm font-semibold text-white focus:border-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-400/40"
7171
aria-label="Page number input"
7272
/>
7373
</li>
7474

7575
<li className="page-item">
76-
<span className="page-total px-3 py-2 mx-1 font-black text-shadow">
76+
<span className="px-2 py-2 text-sm font-semibold text-white/70">
7777
/ { totalPages }
7878
</span>
7979
</li>
@@ -83,7 +83,7 @@ const SportsPagination = ( { totalPages, currentPage, onPageChange } ) => {
8383
<button
8484
onClick={ () => onPageChange( currentPage + 1 ) }
8585
disabled={ currentPage === totalPages }
86-
className="border border-white page-link px-3 py-2 mx-1 text-black font-bold bg-white rounded hover:bg-black hover:text-white"
86+
className="rounded-full border border-white/20 bg-white/10 px-4 py-2 text-sm font-semibold text-white transition hover:border-white/40 hover:bg-white/15 disabled:cursor-not-allowed disabled:opacity-50"
8787
>
8888
Next
8989
</button>

components/Sports/SportsTable.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,28 +71,30 @@ const SportsTable: React.FC<SportsTableProps> = ( {
7171

7272
if ( isLoading ) {
7373
return (
74-
<div className="mx-auto w-fit py-8 text-center min-h-screen">
75-
<p className="text-white backdrop glass text-shadow text-2xl px-2 py-4">Loading sports card data...</p>
74+
<div className="rounded-3xl border border-white/10 bg-black/40 px-6 py-16 text-center text-white shadow-2xl backdrop-blur">
75+
<p className="text-xl font-semibold text-white">Loading sports card data...</p>
76+
<p className="mt-2 text-sm text-white/65">This can take a moment when switching sets.</p>
7677
</div>
7778
);
7879
}
7980

8081
if ( !isLoading && flatData.length === 0 ) {
8182
return (
82-
<div className="min-h-screen mx-auto w-full py-8 text-center">
83-
<p className="text-white">No cards available for this set.</p>
83+
<div className="rounded-3xl border border-dashed border-white/20 bg-black/30 px-6 py-16 text-center text-white/80">
84+
<p className="text-lg font-semibold text-white">No cards available for this set.</p>
85+
<p className="mt-2 text-sm text-white/65">Try a different card set or refresh the sports cache.</p>
8486
</div>
8587
);
8688
}
8789

8890
return (
89-
<div className="min-h-screen mx-auto w-full max-w-7xl space-y-6 px-2 sm:px-4">
91+
<div className="mx-auto w-full space-y-6">
9092
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
9193
<SportsCSVButton sportsData={sportsData} />
92-
{showPagination && <div className="w-fit mx-auto">{renderPagination()}</div>}
94+
{showPagination && <div className="w-full sm:w-auto">{renderPagination()}</div>}
9395
</div>
9496

95-
<div className="grid gap-4 md:hidden w-[85%] mx-auto">
97+
<div className="grid gap-4 md:hidden">
9698
{paginatedData.map( ( product, index ) => {
9799
const isSelected = isCardSelected( product.id );
98100

@@ -108,9 +110,9 @@ const SportsTable: React.FC<SportsTableProps> = ( {
108110
toggleCardSelection( product );
109111
}
110112
}}
111-
className={`backdrop rounded-2xl border p-4 shadow-lg transition focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-0 ${ isSelected
113+
className={`rounded-2xl border bg-black/45 p-4 shadow-lg backdrop-blur transition focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-0 ${ isSelected
112114
? 'border-indigo-500/60'
113-
: 'border-white/10'
115+
: 'border-white/10 hover:border-white/25'
114116
}`}
115117
>
116118
<div className="flex items-start justify-between gap-3">
@@ -147,25 +149,25 @@ const SportsTable: React.FC<SportsTableProps> = ( {
147149
</div>
148150

149151
<div className="hidden md:block">
150-
<div className="table-container h-[40%] overflow-hidden">
152+
<div className="overflow-hidden rounded-3xl border border-white/10 bg-black/40 shadow-2xl backdrop-blur">
151153
<div className="overflow-x-auto">
152-
<table className="glass text-shadow font-black min-w-full divide-y divide-white/10 text-left text-sm text-white">
153-
<thead className="bg-transparent backdrop text-xs uppercase tracking-wide text-white/60">
154+
<table className="min-w-full divide-y divide-white/10 text-left text-sm text-white">
155+
<thead className="bg-transparent text-xs uppercase tracking-wide text-white/60">
154156
<tr>
155157
<TableHeader title="Name" sortKey="productName" onSort={handleSort} getSortIcon={getSortIcon} />
156158
<TableHeader
157159
title="Set"
158160
sortKey="consoleUri"
159161
onSort={handleSort}
160162
getSortIcon={getSortIcon}
161-
className="lg:table-cell"
163+
className="table-cell"
162164
/>
163165
<TableHeader title="Ungraded" sortKey="price1" onSort={handleSort} getSortIcon={getSortIcon} />
164166
<TableHeader title="PSA 9" sortKey="price3" onSort={handleSort} getSortIcon={getSortIcon} />
165167
<TableHeader title="PSA 10" sortKey="price2" onSort={handleSort} getSortIcon={getSortIcon} />
166168
</tr>
167169
</thead>
168-
<tbody className="bg-slate-900/40 text-shadow glass divide-y divide-white/10">
170+
<tbody className="divide-y divide-white/10 bg-slate-950/55">
169171
{paginatedData.map( ( product, index ) => (
170172
<TableRow
171173
key={`${ product.id }-${ index }`}

0 commit comments

Comments
 (0)