Skip to content

Commit 9fb162f

Browse files
committed
new tournament bracket.
1 parent 666fdd0 commit 9fb162f

File tree

2 files changed

+59
-40
lines changed

2 files changed

+59
-40
lines changed

src/pages/AppPage.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@ import AppCard from '../components/AppCard';
55
import usePageTitle from '../utils/usePageTitle';
66

77
const apps = [
8-
{
9-
to: '/apps/word-counter',
10-
title: 'Word Counter',
11-
description: 'Count words, characters, lines and paragraphs in a text.',
12-
},
13-
{
14-
to: '/apps/ip',
15-
title: 'Show my IP',
16-
description: 'Display your public IP address.',
17-
},
188
{
199
to: '/apps/tournament-bracket',
2010
title: 'Tournament Bracket',
2111
description: 'Create and manage tournament brackets.',
2212
},
13+
{
14+
to: '/apps/word-counter',
15+
title: 'Word Counter',
16+
description: 'Count words, characters, lines and paragraphs in a text.',
17+
},
18+
// {
19+
// to: '/apps/ip',
20+
// title: 'Show my IP',
21+
// description: 'Display your public IP address.',
22+
// },
2323
];
2424

2525
function AppPage() {

src/pages/apps/TournamentBracketPage.js

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,32 @@ function TournamentBracketPage() {
2323

2424
const [competitors, setCompetitors] = useState([]);
2525
const [newCompetitor, setNewCompetitor] = useState('');
26+
const [newCompetitorCharLimitError, setNewCompetitorCharLimitError] = useState(false);
2627
const [editingIndex, setEditingIndex] = useState(null);
2728
const [editingText, setEditingText] = useState('');
2829
const [nameError, setNameError] = useState('');
2930
const newCompetitorInputRef = useRef(null);
3031

3132
const addCompetitor = () => {
3233
const trimmedCompetitor = newCompetitor.trim();
33-
if (trimmedCompetitor && competitors.length < 64) {
34+
if (trimmedCompetitor) {
35+
if (trimmedCompetitor.length > 5) {
36+
addToast({ title: 'Error', message: 'Competitor name cannot exceed 5 characters.', duration: 3000 });
37+
return;
38+
}
3439
if (competitors.includes(trimmedCompetitor)) {
3540
setNameError('Competitor names must be unique.');
3641
addToast({ title: 'Error', message: 'Competitor names must be unique.', duration: 3000 });
3742
return;
3843
}
39-
setCompetitors([...competitors, trimmedCompetitor]);
40-
setNewCompetitor('');
41-
setNameError('');
42-
newCompetitorInputRef.current.focus();
44+
if (competitors.length < 64) {
45+
setCompetitors([...competitors, trimmedCompetitor]);
46+
setNewCompetitor('');
47+
setNameError('');
48+
newCompetitorInputRef.current.focus();
49+
} else {
50+
addToast({ title: 'Error', message: 'Maximum of 64 competitors reached.', duration: 3000 });
51+
}
4352
}
4453
};
4554

@@ -60,6 +69,10 @@ function TournamentBracketPage() {
6069

6170
const saveEdit = (index) => {
6271
const trimmedName = editingText.trim();
72+
if (trimmedName.length > 5) {
73+
addToast({ title: 'Error', message: 'Competitor name cannot exceed 5 characters.', duration: 3000 });
74+
return;
75+
}
6376
if (competitors.includes(trimmedName) && competitors[index] !== trimmedName) {
6477
addToast({ title: 'Error', message: 'Competitor names must be unique.', duration: 3000 });
6578
return;
@@ -122,10 +135,10 @@ function TournamentBracketPage() {
122135

123136
const [champion, setChampion] = useState(null);
124137

125-
const advanceWinner = (winner, roundIndex, matchIndex) => {
138+
const advanceWinner = (winner, loser, roundIndex, matchIndex) => {
126139
if (!bracket || !winner) return;
127140

128-
setAdvancedMatches(prev => ({ ...prev, [`${roundIndex}-${matchIndex}`]: winner }));
141+
setAdvancedMatches(prev => ({ ...prev, [`${roundIndex}-${matchIndex}`]: { winner, loser } }));
129142

130143
if (roundIndex === bracket.length - 1) {
131144
setChampion(winner);
@@ -206,7 +219,7 @@ function TournamentBracketPage() {
206219
const [compactLayout, setCompactLayout] = useState(false);
207220

208221
return (
209-
<div className="py-16 sm:py-24">
222+
<div className="py-16 sm:py-24 min-h-screen">
210223
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
211224
<Link
212225
to="/apps"
@@ -222,9 +235,9 @@ function TournamentBracketPage() {
222235
<span className="single-app-color">tb</span>
223236
</h1>
224237
<hr className="border-gray-700" />
225-
<div className="flex justify-center items-center mt-8">
238+
<div className="flex flex-col justify-center mt-8">
226239
<div
227-
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full w-full max-w-7xl"
240+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-start relative w-full max-w-7xl flex-grow"
228241
style={cardStyle}
229242
>
230243
<div
@@ -239,20 +252,24 @@ function TournamentBracketPage() {
239252
{!tournamentStarted ? (
240253
<div>
241254
<div className="mb-8">
242-
<h2 className="text-2xl font-arvo font-normal mb-4">Add Competitors</h2>
255+
<h2 className="text-2xl font-arvo font-normal mb-4">Add Competitors (max 64 competitors, 5 char limit)</h2>
243256
<div className="flex gap-4">
244257
<input
245258
type="text"
246259
value={newCompetitor}
247-
onChange={(e) => setNewCompetitor(e.target.value)}
260+
onChange={(e) => {
261+
const value = e.target.value;
262+
setNewCompetitor(value);
263+
setNewCompetitorCharLimitError(value.length > 5);
264+
}}
248265
onKeyDown={handleNewCompetitorKeyDown}
249266
placeholder="Enter competitor name"
250-
className="bg-gray-800 text-white p-2 rounded-lg flex-grow"
267+
className={`bg-gray-800 text-white p-2 rounded-lg flex-grow ${newCompetitorCharLimitError ? 'border-red-500 border-2' : ''}`}
251268
ref={newCompetitorInputRef}
252269
/>
253270
<button
254271
onClick={addCompetitor}
255-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
272+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
256273
${competitors.length >= 64
257274
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
258275
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
@@ -263,11 +280,12 @@ function TournamentBracketPage() {
263280
Add
264281
</button>
265282
</div>
283+
{newCompetitorCharLimitError && <p className="text-red-500 mt-2">Competitor name cannot exceed 5 characters.</p>}
266284
{nameError && <p className="text-red-500 mt-2">{nameError}</p>}
267285
{competitors.length >= 64 && <p className="text-red-500 mt-2">Maximum of 64 competitors reached.</p>}
268286
</div>
269287

270-
<div>
288+
<div className="flex-grow">
271289
<h2 className="text-2xl font-arvo font-normal mb-4">Competitors ({competitors.length})</h2>
272290
<ul className="space-y-2">
273291
{competitors.map((competitor, index) => (
@@ -285,7 +303,7 @@ function TournamentBracketPage() {
285303
<div className="flex gap-2">
286304
{editingIndex === index ? (
287305
<button onClick={() => saveEdit(index)}
288-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
306+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
289307
${false // Save button is never disabled based on current logic
290308
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
291309
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
@@ -296,7 +314,7 @@ function TournamentBracketPage() {
296314
</button>
297315
) : (
298316
<button onClick={() => startEditing(index, competitor)}
299-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
317+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
300318
${false // Edit button is never disabled based on current logic
301319
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
302320
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
@@ -307,7 +325,7 @@ function TournamentBracketPage() {
307325
</button>
308326
)}
309327
<button onClick={() => deleteCompetitor(index)}
310-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
328+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
311329
${false // Delete button is never disabled based on current logic
312330
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
313331
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
@@ -325,7 +343,7 @@ function TournamentBracketPage() {
325343
<div className="mt-8">
326344
<button
327345
onClick={startTournament}
328-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
346+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
329347
${competitors.length < 2
330348
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
331349
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
@@ -361,7 +379,7 @@ function TournamentBracketPage() {
361379
<div key={roundIndex} className={`flex flex-col ${compactLayout ? 'space-y-2' : 'space-y-4'} min-w-max flex-shrink-0`}>
362380
<h3 className="text-xl font-bold">Round {roundIndex + 1}</h3>
363381
{round.map((match, matchIndex) => (
364-
<div key={matchIndex} className={`group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out overflow-hidden w-72 ${compactLayout ? '' : 'h-full'}`} style={cardStyle}>
382+
<div key={matchIndex} className={`group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out overflow-hidden w-80 ${compactLayout ? 'min-h-[100px]' : 'h-full'}`} style={cardStyle}>
365383
<div className="flex justify-between items-center mb-2">
366384
<div className="flex items-center gap-2">
367385
<input
@@ -371,11 +389,11 @@ function TournamentBracketPage() {
371389
onChange={(e) => handleScoreChange(roundIndex, matchIndex, 0, e.target.value)}
372390
disabled={!match[0] || !match[1]}
373391
/>
374-
<span>{match[0] || 'TBD'}</span>
392+
<span className={`break-words ${match[1] === null ? 'text-article font-bold' : advancedMatches[`${roundIndex}-${matchIndex}`]?.winner === match[0] ? 'text-article font-bold' : advancedMatches[`${roundIndex}-${matchIndex}`]?.loser === match[0] ? 'text-gray-500 line-through' : ''}`}>{match[0] || 'TBD'}</span>
375393
</div>
376394
<span>vs</span>
377395
<div className="flex items-center gap-2">
378-
<span>{match[1] || (roundIndex === 0 ? 'Bye' : 'TBD')}</span>
396+
<span className={`break-words ${match[1] === null ? 'text-gray-500 line-through' : advancedMatches[`${roundIndex}-${matchIndex}`]?.winner === match[1] ? 'text-article font-bold' : advancedMatches[`${roundIndex}-${matchIndex}`]?.loser === match[1] ? 'text-gray-500 line-through' : ''}`}>{match[1] || (roundIndex === 0 ? 'Bye' : 'TBD')}</span>
379397
<input
380398
type="number"
381399
className="bg-gray-700 text-white p-1 rounded-lg w-16"
@@ -392,17 +410,18 @@ function TournamentBracketPage() {
392410
const score2 = scores[roundIndex]?.[matchIndex]?.[1];
393411
if (score1 === undefined || score2 === undefined || score1 === '' || score2 === '' || score1 === score2) return;
394412
const winner = score1 > score2 ? match[0] : match[1];
395-
advanceWinner(winner, roundIndex, matchIndex);
413+
const loser = score1 > score2 ? match[1] : match[0];
414+
advanceWinner(winner, loser, roundIndex, matchIndex);
396415
}}
397-
className={`flex items-center justify-center gap-2 text-lg font-mono font-normal px-4 py-1 rounded-md border transition-colors duration-300 ease-in-out w-full mb-4
416+
className={`flex items-center justify-center gap-2 text-lg font-mono font-normal px-4 py-1 rounded-md border transition-colors duration-300 ease-in-out w-full mb-4
398417
${advancedMatches[`${roundIndex}-${matchIndex}`] || scores[roundIndex]?.[matchIndex]?.[0] === undefined || scores[roundIndex]?.[matchIndex]?.[1] === undefined || scores[roundIndex]?.[matchIndex]?.[0] === '' || scores[roundIndex]?.[matchIndex]?.[1] === '' || scores[roundIndex]?.[matchIndex]?.[0] === scores[roundIndex]?.[matchIndex]?.[1]
399418
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
400419
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
401420
}`}
402421
style={buttonStyle}
403422
disabled={advancedMatches[`${roundIndex}-${matchIndex}`] || scores[roundIndex]?.[matchIndex]?.[0] === undefined || scores[roundIndex]?.[matchIndex]?.[1] === undefined || scores[roundIndex]?.[matchIndex]?.[0] === '' || scores[roundIndex]?.[matchIndex]?.[1] === '' || scores[roundIndex]?.[matchIndex]?.[0] === scores[roundIndex]?.[matchIndex]?.[1]}
404423
>
405-
{advancedMatches[`${roundIndex}-${matchIndex}`] ? `${advancedMatches[`${roundIndex}-${matchIndex}`]} advanced!` : (roundIndex === bracket.length - 1 ? 'Set Champion' : 'Set Winner')}
424+
{advancedMatches[`${roundIndex}-${matchIndex}`] ? `${advancedMatches[`${roundIndex}-${matchIndex}`].winner} advanced!` : (roundIndex === bracket.length - 1 ? 'Set Champion' : 'Set Winner')}
406425
</button>
407426
)}
408427
</div>
@@ -412,25 +431,25 @@ function TournamentBracketPage() {
412431
</div>
413432
{champion && (
414433
<div className="mt-8 text-center">
415-
<h2 className="text-3xl font-bold">Champion!</h2>
434+
<h2 className="text-3xl font-bold">🎉 Champion! 🎉</h2>
416435
<p className="text-xl font-bold text-yellow-400">{champion}</p>
417436
</div>
418437
)}
419438
<div className="mt-8 flex gap-4 justify-center">
420439
<button
421440
onClick={resetScores}
422-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
441+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
423442
${false // Reset Scores button is never disabled based on current logic
424443
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
425444
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
426445
}`}
427446
style={buttonStyle}
428447
>
429-
Reset Scores
448+
Reset Matches
430449
</button>
431450
<button
432451
onClick={resetTournament}
433-
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
452+
className={`flex items-center gap-2 text-lg font-mono font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out
434453
${false // Reset Tournament button is never disabled based on current logic
435454
? 'bg-gray-600 text-gray-400 cursor-not-allowed'
436455
: `bg-[${colors.app}] text-white hover:bg-[${colors.app}]`
@@ -450,4 +469,4 @@ function TournamentBracketPage() {
450469
);
451470
}
452471

453-
export default TournamentBracketPage;
472+
export default TournamentBracketPage;

0 commit comments

Comments
 (0)