From 9306dfea6ca96eab726ee46e0c4b7b5db045d304 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 18:08:10 +0100 Subject: [PATCH 01/40] Add mockup for favorites --- js/components/FavoriteBar.js | 52 ++++++++++++++++++++++++++++++++++++ js/redux/tournamentApi.js | 3 ++- pages/tournament.js | 3 ++- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 js/components/FavoriteBar.js diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js new file mode 100644 index 0000000..c3c5371 --- /dev/null +++ b/js/components/FavoriteBar.js @@ -0,0 +1,52 @@ +import React, {useState, useEffect} from 'react'; +import {Button} from 'reactstrap'; + +export function FavoriteBar({teams}) { + const [favorite, setFavorite] = useState(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const savedFavorite = localStorage.getItem('favoriteTeam'); + if (savedFavorite) { + const team = teams.find(team => team.id === parseInt(savedFavorite, 10)); + if (team) { + setFavorite(team); + } + } + }, [teams]); + + const toggleFavorite = team => { + if (favorite && favorite.id === team.id) { + setFavorite(null); + localStorage.removeItem('favoriteTeam'); + } else { + setFavorite(team); + localStorage.setItem('favoriteTeam', team.id); + } + }; + + return ( +
+ + {isVisible && ( +
+

Favorite Team

+ {favorite ?

{favorite.name}

:

No favorite team selected

} +

All Teams

+
    + {teams.map(team => ( +
  • + {team.name} {favorite && favorite.id === team.id && (Favorite)} + +
  • + ))} +
+
+ )} +
+ ); +} diff --git a/js/redux/tournamentApi.js b/js/redux/tournamentApi.js index 7d9576d..2bca5cb 100644 --- a/js/redux/tournamentApi.js +++ b/js/redux/tournamentApi.js @@ -67,7 +67,8 @@ function convertTournament(apiTournament) { isPublic: apiTournament.public, ownerUsername: apiTournament.owner_username, groupStage: groupStage, - playoffStages: playoffStages + playoffStages: playoffStages, + teams: apiTournament.teams }; } diff --git a/pages/tournament.js b/pages/tournament.js index b5b656c..a686e21 100644 --- a/pages/tournament.js +++ b/pages/tournament.js @@ -15,6 +15,7 @@ import {EditButton, TournamentStatusBar} from '../js/components/TournamentStatus import {LinkButton} from '../js/components/LinkButton'; import {LoadingPage} from '../js/components/LoadingPage'; import {getTournament} from '../js/redux/tournamentApi'; +import {FavoriteBar} from '../js/components/FavoriteBar'; class PrivateTournamentPage extends React.Component { render() { @@ -25,6 +26,7 @@ class PrivateTournamentPage extends React.Component { return (
+
{groupStage != null &&
- Vollbild-Ansicht Gruppen ); } From b44d7cb6734a8705249fd131e244c36557beba07 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 19:35:10 +0100 Subject: [PATCH 02/40] Add icons --- js/components/FavoriteBar.js | 48 ++++++++++++++++++++++++------------ package.json | 4 +++ yarn.lock | 33 +++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index c3c5371..f2f50d3 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -1,9 +1,14 @@ -import React, {useState, useEffect} from 'react'; -import {Button} from 'reactstrap'; +import React, { useState, useEffect } from 'react'; +import { Button } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faStar as filledStar } from '@fortawesome/free-solid-svg-icons'; +import { faStar as emptyStar } from '@fortawesome/free-regular-svg-icons'; +import { faHeartCirclePlus } from '@fortawesome/free-solid-svg-icons'; -export function FavoriteBar({teams}) { +export function FavoriteBar({ teams }) { const [favorite, setFavorite] = useState(null); const [isVisible, setIsVisible] = useState(false); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { const savedFavorite = localStorage.getItem('favoriteTeam'); @@ -13,6 +18,7 @@ export function FavoriteBar({teams}) { setFavorite(team); } } + setIsLoading(false); }, [teams]); const toggleFavorite = team => { @@ -25,28 +31,38 @@ export function FavoriteBar({teams}) { } }; + if (isLoading) { + return
Loading...
; + } + return (
- +
+

Favorit:

+

{favorite ? favorite.name : ''}

+ +
{isVisible && (
-

Favorite Team

- {favorite ?

{favorite.name}

:

No favorite team selected

}

All Teams

-
    +
    {teams.map(team => ( -
  • - {team.name} {favorite && favorite.id === team.id && (Favorite)} - -
  • + + {team.name} {favorite && favorite.id === team.id && (Favorite)} + +
    ))} -
+
)}
); -} +} \ No newline at end of file diff --git a/package.json b/package.json index 2c46b03..9423776 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,10 @@ "author": "", "license": "ISC", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@zeit/next-css": "^1.0.1", "axios": "^0.27.2", "bootstrap": "^5.1.3", diff --git a/yarn.lock b/yarn.lock index 2a8a372..81916e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,6 +43,39 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@fortawesome/fontawesome-common-types@6.7.2": + version "6.7.2" + resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470" + integrity sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg== + +"@fortawesome/fontawesome-svg-core@^6.7.2": + version "6.7.2" + resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz#0ac6013724d5cc327c1eb81335b91300a4fce2f2" + integrity sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/free-regular-svg-icons@^6.7.2": + version "6.7.2" + resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz#f1651e55e6651a15589b0569516208f9c65f96db" + integrity sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/free-solid-svg-icons@^6.7.2": + version "6.7.2" + resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz#fe25883b5eb8464a82918599950d283c465b57f6" + integrity sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.2" + +"@fortawesome/react-fontawesome@^0.2.2": + version "0.2.2" + resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4" + integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g== + dependencies: + prop-types "^15.8.1" + "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" From 416a4fbc86e18496eee025b57286276342bb758b Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:13:28 +0100 Subject: [PATCH 03/40] Add basic scroll to favorite behaviour --- js/components/FavoriteBar.js | 44 ++++++++++++++++++++++++------------ js/components/GroupStage.js | 17 ++++++++------ 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index f2f50d3..e0cd9c8 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -1,11 +1,11 @@ -import React, { useState, useEffect } from 'react'; -import { Button } from 'reactstrap'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faStar as filledStar } from '@fortawesome/free-solid-svg-icons'; -import { faStar as emptyStar } from '@fortawesome/free-regular-svg-icons'; -import { faHeartCirclePlus } from '@fortawesome/free-solid-svg-icons'; +import React, {useState, useEffect} from 'react'; +import {Button} from 'reactstrap'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faStar as filledStar} from '@fortawesome/free-solid-svg-icons'; +import {faStar as emptyStar} from '@fortawesome/free-regular-svg-icons'; +import {faHeartCirclePlus} from '@fortawesome/free-solid-svg-icons'; -export function FavoriteBar({ teams }) { +export function FavoriteBar({teams}) { const [favorite, setFavorite] = useState(null); const [isVisible, setIsVisible] = useState(false); const [isLoading, setIsLoading] = useState(true); @@ -31,32 +31,46 @@ export function FavoriteBar({ teams }) { } }; + const scrollToFavorite = () => { + if (favorite) { + const el = document.getElementById(`favorite-team-groupstage-${favorite.id}`); + if (el) { + el.scrollIntoView({behavior: 'smooth', block: 'end'}); + } + } + }; + if (isLoading) { return
Loading...
; } return (
-
-

Favorit:

-

{favorite ? favorite.name : ''}

+
+

Favorit:

+

{favorite ? favorite.name : ''}

+ {favorite && ( + + )}
{isVisible && (

All Teams

{teams.map(team => ( -
- - {team.name} {favorite && favorite.id === team.id && (Favorite)} + {team.name}
))} @@ -65,4 +79,4 @@ export function FavoriteBar({ teams }) { )}
); -} \ No newline at end of file +} diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index 17b45ed..6cc4f19 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -94,11 +94,14 @@ function GroupScoresTable(props) { function GroupScoresTableRow(props) { - return ( - {props.score.position} - {props.score.team.name} - {props.score.group_points} - {props.score.difference_in_points} - {props.score.scored_points} - ); + const teamId = `favorite-team-groupstage-${props.score.team.id}`; + return ( + + {props.score.position} + {props.score.team.name} + {props.score.group_points} + {props.score.difference_in_points} + {props.score.scored_points} + + ); } From 05000a3243a68e1c2c0d5707c7912b1a2898732d Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:15:50 +0100 Subject: [PATCH 04/40] Sort team names in favorites --- js/components/FavoriteBar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index e0cd9c8..34b7cfe 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -44,6 +44,8 @@ export function FavoriteBar({teams}) { return
Loading...
; } + const sortedTeams = [...teams].sort((a, b) => a.name.localeCompare(b.name)); + return (
@@ -62,7 +64,7 @@ export function FavoriteBar({teams}) {

All Teams

- {teams.map(team => ( + {sortedTeams.map(team => (
- {favorite && ( - - )} + {favorite && ( + + )} +
{isVisible && (
-

All Teams

{sortedTeams.map(team => (
@@ -81,4 +82,4 @@ export function FavoriteBar({teams}) { )}
); -} +} \ No newline at end of file diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index 5855f3f..9c1f40d 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -10,6 +10,10 @@ background-color: #f8f8f8; } +.favorites { + background-color: #f8f8f8; +} + .minw-25 { min-width: 350px; } From 31b0687c32b0999f1308e26bf5d6542d4af25004 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:30:21 +0100 Subject: [PATCH 06/40] On favorite set; pulse scroll button --- js/components/FavoriteBar.js | 16 ++++++++++++---- public/static/css/tournament.css | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 47d62ea..cafe85f 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -9,6 +9,7 @@ export function FavoriteBar({teams}) { const [favorite, setFavorite] = useState(null); const [isVisible, setIsVisible] = useState(false); const [isLoading, setIsLoading] = useState(true); + const [isPulsing, setIsPulsing] = useState(false); useEffect(() => { const savedFavorite = localStorage.getItem('favoriteTeam'); @@ -28,7 +29,9 @@ export function FavoriteBar({teams}) { } else { setFavorite(team); localStorage.setItem('favoriteTeam', team.id); + setIsPulsing(true); } + setIsVisible(false); // Close the favorite menu }; const scrollToFavorite = () => { @@ -37,6 +40,7 @@ export function FavoriteBar({teams}) { if (el) { el.scrollIntoView({behavior: 'smooth', block: 'end'}); } + setIsPulsing(false); // Stop pulsing when the button is clicked } }; @@ -47,16 +51,20 @@ export function FavoriteBar({teams}) { const sortedTeams = [...teams].sort((a, b) => a.name.localeCompare(b.name)); return ( -
+
-

Favorit:

+

Favorit:

{favorite ? favorite.name : ''}

- {favorite && ( - )} diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index 9c1f40d..572a1de 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -30,3 +30,19 @@ .scoreInput { width: 11rem; } + +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.2); + } + 100% { + transform: scale(1); + } +} + +.pulse-animation { + animation: pulse 1s infinite; +} \ No newline at end of file From 5c2481ab9d9fd6cb56d7d277cd35b44eaf90adf3 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:33:19 +0100 Subject: [PATCH 07/40] Scroll to fav button when chosen --- js/components/FavoriteBar.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index cafe85f..9be9735 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react'; +import React, {useState, useEffect, useRef} from 'react'; import {Button, ButtonGroup} from 'reactstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faStar as filledStar} from '@fortawesome/free-solid-svg-icons'; @@ -10,6 +10,7 @@ export function FavoriteBar({teams}) { const [isVisible, setIsVisible] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isPulsing, setIsPulsing] = useState(false); + const headingRef = useRef(null); useEffect(() => { const savedFavorite = localStorage.getItem('favoriteTeam'); @@ -30,6 +31,7 @@ export function FavoriteBar({teams}) { setFavorite(team); localStorage.setItem('favoriteTeam', team.id); setIsPulsing(true); + headingRef.current.scrollIntoView({behavior: 'smooth', block: 'center'}); } setIsVisible(false); // Close the favorite menu }; @@ -53,7 +55,7 @@ export function FavoriteBar({teams}) { return (
-

Favorit:

+

Favorit:

{favorite ? favorite.name : ''}

- {isVisible && ( -
-
- {sortedTeams.map(team => ( -
- - - {team.name} - -
- ))} -
+
+
+ {sortedTeams.map(team => ( +
+ + + {team.name} + +
+ ))}
- )} +
); -} \ No newline at end of file +} diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index 572a1de..6728a6f 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -19,7 +19,7 @@ } .match:hover { - box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important; + box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !important; } .match:hover > div { @@ -45,4 +45,14 @@ .pulse-animation { animation: pulse 1s infinite; -} \ No newline at end of file +} + +.favorite-bar { + max-height: 0; + overflow: hidden; + transition: max-height 0.7s ease-in-out; +} + +.favorite-bar.visible { + max-height: none; +} From 12bfa2eafe9f89a9b9849ae1040450a84e308323 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:40:09 +0100 Subject: [PATCH 09/40] Margin makes a huge difference --- js/components/FavoriteBar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 48e5816..725dfcb 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -65,8 +65,8 @@ export function FavoriteBar({teams}) {

Favorit:

-

{favorite ? favorite.name : ''}

- +

{favorite ? favorite.name : ''}

+ From 3b05195434535ca1e747bd6888c3ffeca587a873 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:45:39 +0100 Subject: [PATCH 10/40] Basic scroll to top button --- js/components/ScrollToTopButton.js | 45 ++++++++++++++++++++++++++++++ pages/tournament.js | 26 +++++++++-------- 2 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 js/components/ScrollToTopButton.js diff --git a/js/components/ScrollToTopButton.js b/js/components/ScrollToTopButton.js new file mode 100644 index 0000000..4548ae4 --- /dev/null +++ b/js/components/ScrollToTopButton.js @@ -0,0 +1,45 @@ +import React, {useState, useEffect} from 'react'; +import {Button} from 'reactstrap'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faArrowUp} from '@fortawesome/free-solid-svg-icons'; + +export function ScrollToTopButton() { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 2 * window.innerHeight) { + setIsVisible(true); + } else { + setIsVisible(false); + } + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const scrollToTop = () => { + window.scrollTo({top: 0, behavior: 'smooth'}); + }; + + return ( + <> + {isVisible && ( + + )} + + ); +} diff --git a/pages/tournament.js b/pages/tournament.js index a686e21..6c323b9 100644 --- a/pages/tournament.js +++ b/pages/tournament.js @@ -16,6 +16,7 @@ import {LinkButton} from '../js/components/LinkButton'; import {LoadingPage} from '../js/components/LoadingPage'; import {getTournament} from '../js/redux/tournamentApi'; import {FavoriteBar} from '../js/components/FavoriteBar'; +import {ScrollToTopButton} from '../js/components/ScrollToTopButton'; class PrivateTournamentPage extends React.Component { render() { @@ -23,18 +24,21 @@ class PrivateTournamentPage extends React.Component { const {isSignedIn, username} = this.props; const isOwner = username === ownerUsername; - return (
- - - -
- {groupStage != null && -
} - + return ( +
+ + + +
+ {groupStage != null && +
} + +
+
-
); + ); } } From 6ecb27ffa790edc1d970cc3efd1e605bd6766de3 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 22:47:37 +0100 Subject: [PATCH 11/40] Finetuning scroll to top button --- js/components/ScrollToTopButton.js | 39 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/js/components/ScrollToTopButton.js b/js/components/ScrollToTopButton.js index 4548ae4..14f42ec 100644 --- a/js/components/ScrollToTopButton.js +++ b/js/components/ScrollToTopButton.js @@ -8,7 +8,7 @@ export function ScrollToTopButton() { useEffect(() => { const handleScroll = () => { - if (window.scrollY > 2 * window.innerHeight) { + if (window.scrollY > 1.5 * window.innerHeight) { setIsVisible(true); } else { setIsVisible(false); @@ -23,23 +23,22 @@ export function ScrollToTopButton() { }; return ( - <> - {isVisible && ( - - )} - + ); -} +} \ No newline at end of file From 70655cb1d7bd2430a5f0f18328de804bf0b4b2d4 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 23:12:56 +0100 Subject: [PATCH 12/40] Add ids for all matches with level and team id --- js/components/Match.js | 2 +- js/components/MatchTable.js | 7 +++++-- js/components/PlayoffStages.js | 2 +- js/components/Stage.js | 13 ++++++++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/js/components/Match.js b/js/components/Match.js index 7396122..fcdeaee 100644 --- a/js/components/Match.js +++ b/js/components/Match.js @@ -127,7 +127,7 @@ export class Match extends React.Component {
- + {groupInformation} diff --git a/js/components/MatchTable.js b/js/components/MatchTable.js index 5fa509a..dc21bb1 100644 --- a/js/components/MatchTable.js +++ b/js/components/MatchTable.js @@ -41,13 +41,16 @@ export function MatchTable(props) { ); } else { + const team1Id = `favorite-team-level-${props.stageLevel}-${props.match.team1.id}`; + const team2Id = `favorite-team-level-${props.stageLevel}-${props.match.team1.id}`; + return ( - + - + )))} + + + )))} ); From b790a103c41e440320fae50529e6bf7e9440c763 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 23:34:23 +0100 Subject: [PATCH 13/40] Scroll to lowest stage of team or group stage if none are available --- js/components/FavoriteBar.js | 33 +++++++++++++++++++++++++++------ js/components/MatchTable.js | 4 ++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 725dfcb..dfe8bd5 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -46,13 +46,34 @@ export function FavoriteBar({teams}) { }; const scrollToFavorite = () => { - if (favorite) { - const el = document.getElementById(`favorite-team-groupstage-${favorite.id}`); - if (el) { - el.scrollIntoView({behavior: 'smooth', block: 'end'}); - } - setIsPulsing(false); // Stop pulsing when the button is clicked + if (!favorite) { + return; } + + const stageElements = document.querySelectorAll(`[id^='favorite-team-level-'][id$='-${favorite.id}']`); + let lowestStageEl = null; + let lowestStageNum = Infinity; + + stageElements.forEach(el => { + const match = el.id.match(/^favorite-team-level-(\d+)-(\d+)$/); + if (match) { + const stage = parseInt(match[1]); + if (stage < lowestStageNum) { + lowestStageNum = stage; + lowestStageEl = el; + } + } + }); + + if (lowestStageEl) { + lowestStageEl.scrollIntoView({behavior: 'smooth', block: 'end'}); + } else { + const groupEl = document.getElementById(`favorite-team-groupstage-${favorite.id}`); + if (groupEl) { + groupEl.scrollIntoView({behavior: 'smooth', block: 'end'}); + } + } + setIsPulsing(false); }; if (isLoading) { diff --git a/js/components/MatchTable.js b/js/components/MatchTable.js index dc21bb1..4b4a9a0 100644 --- a/js/components/MatchTable.js +++ b/js/components/MatchTable.js @@ -41,8 +41,8 @@ export function MatchTable(props) {
{props.match.team1.score} {props.match.team1.name}
{props.match.team2.score} {props.match.team2.name} diff --git a/js/components/PlayoffStages.js b/js/components/PlayoffStages.js index 79fe6b6..ddc3f13 100644 --- a/js/components/PlayoffStages.js +++ b/js/components/PlayoffStages.js @@ -50,7 +50,7 @@ export class PlayoffStages extends Component { return (
{this.props.playoffStages.map(stage => this.updateNextStage(stage.id)} - level={getLevelName(stage.level)} matches={stage.matches} + level={getLevelName(stage.level)} matches={stage.matches} stageLevel={stage.level} key={stage.level}/>)}
); } diff --git a/js/components/Stage.js b/js/components/Stage.js index a91a328..97f6cfd 100644 --- a/js/components/Stage.js +++ b/js/components/Stage.js @@ -3,15 +3,22 @@ import {Match} from './Match'; import React from 'react'; export function Stage(props) { - const {isSignedIn, isOwner, updateNextStage} = props; + const {isSignedIn, isOwner, updateNextStage, stageLevel} = props; return (

{props.level}

{props.matches.map((match => ( -
); } else { - const team1Id = `favorite-team-level-${props.stageLevel}-${props.match.team1.id}`; - const team2Id = `favorite-team-level-${props.stageLevel}-${props.match.team1.id}`; + const team1Id = props.stageLevel !== undefined ? `favorite-team-level-${props.stageLevel}-${props.match.team1.id}` : undefined; + const team2Id = props.stageLevel !== undefined ? `favorite-team-level-${props.stageLevel}-${props.match.team2.id}` : undefined; return ( From a132a57a245261c056a82b17ff041b72576f50f1 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 23:47:35 +0100 Subject: [PATCH 14/40] Fix a js error --- js/components/LinkButton.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/js/components/LinkButton.js b/js/components/LinkButton.js index 619551c..36a5ba6 100644 --- a/js/components/LinkButton.js +++ b/js/components/LinkButton.js @@ -3,13 +3,10 @@ import {useRouter} from 'next/router'; import React from 'react'; class LinkButtonComponent extends React.Component { - constructor(props) { - super(props); - this.defaultProps = { - outline: true, - color: 'secondary' - }; - } + static defaultProps = { + outline: true, + color: 'secondary' + }; handleClick(e) { e.preventDefault(); @@ -25,14 +22,7 @@ class LinkButtonComponent extends React.Component { } } -LinkButtonComponent.defaultProps = { - outline: true, - color: 'secondary' -}; - -// export default withRouter(LinkButton); - export function LinkButton(props) { const router = useRouter(); - return (); -} + return (); +} \ No newline at end of file From 2f793fecdbefd89e54366317f5c812fffb9fb4c3 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 23:48:14 +0100 Subject: [PATCH 15/40] Import fortawesome fontawesome config --- pages/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pages/index.js b/pages/index.js index 605a7c0..2a96586 100644 --- a/pages/index.js +++ b/pages/index.js @@ -8,6 +8,10 @@ import { import {TurniereNavigation} from '../js/components/Navigation'; import {BigImage} from '../js/components/BigImage'; import {Footer} from '../js/components/Footer'; +import '@fortawesome/fontawesome-svg-core/styles.css'; +import {config} from '@fortawesome/fontawesome-svg-core'; + +config.autoAddCss = false; function Main() { return (
@@ -72,7 +76,7 @@ function MainBottomSummary() {

Ich habe einen Turniercode bekommen. Was nun?

Der Turniercode führt dich direkt zu einem Turnier. Gebe dafür den Code oben ein, + href="#turniercode-form">oben ein, dann wirst du sofort weitergeleitet.

From 2800cefdd98d395e30e0b39d7a7b1be3a53a26c3 Mon Sep 17 00:00:00 2001 From: Malaber Date: Sat, 15 Mar 2025 13:40:50 +0100 Subject: [PATCH 16/40] Fix icons by switching to react-icons --- js/components/FavoriteBar.js | 13 ++++------ js/components/ScrollToTopButton.js | 7 +++--- package.json | 5 +--- pages/index.js | 2 -- yarn.lock | 38 ++++-------------------------- 5 files changed, 13 insertions(+), 52 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index dfe8bd5..940ca9c 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -1,9 +1,6 @@ import React, {useState, useEffect, useRef} from 'react'; import {Button, ButtonGroup} from 'reactstrap'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faStar as filledStar} from '@fortawesome/free-solid-svg-icons'; -import {faStar as emptyStar} from '@fortawesome/free-regular-svg-icons'; -import {faHeartCirclePlus, faArrowCircleRight} from '@fortawesome/free-solid-svg-icons'; +import {FaHeartCirclePlus, FaRegHeart, FaHeart, FaArrowTurnDown} from 'react-icons/fa6'; export function FavoriteBar({teams}) { const [favorite, setFavorite] = useState(null); @@ -89,7 +86,7 @@ export function FavoriteBar({teams}) {

{favorite ? favorite.name : ''}

{favorite && ( )} @@ -107,9 +104,7 @@ export function FavoriteBar({teams}) { {sortedTeams.map(team => (
{team.name} diff --git a/js/components/ScrollToTopButton.js b/js/components/ScrollToTopButton.js index 14f42ec..3e9bf7d 100644 --- a/js/components/ScrollToTopButton.js +++ b/js/components/ScrollToTopButton.js @@ -1,7 +1,6 @@ import React, {useState, useEffect} from 'react'; import {Button} from 'reactstrap'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faArrowUp} from '@fortawesome/free-solid-svg-icons'; +import {FaCircleArrowUp} from 'react-icons/fa6'; export function ScrollToTopButton() { const [isVisible, setIsVisible] = useState(false); @@ -38,7 +37,7 @@ export function ScrollToTopButton() { pointerEvents: isVisible ? 'auto' : 'none' }} > - + ); -} \ No newline at end of file +} diff --git a/package.json b/package.json index 9423776..8ad63aa 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,6 @@ "author": "", "license": "ISC", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-regular-svg-icons": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@fortawesome/react-fontawesome": "^0.2.2", "@zeit/next-css": "^1.0.1", "axios": "^0.27.2", "bootstrap": "^5.1.3", @@ -26,6 +22,7 @@ "qrcode.react": "^3.1.0", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-icons": "^5.5.0", "react-notify-toast": "^0.5.1", "react-pose": "^4.0.10", "react-redux": "^8.0.2", diff --git a/pages/index.js b/pages/index.js index 2a96586..3a7f6f2 100644 --- a/pages/index.js +++ b/pages/index.js @@ -8,8 +8,6 @@ import { import {TurniereNavigation} from '../js/components/Navigation'; import {BigImage} from '../js/components/BigImage'; import {Footer} from '../js/components/Footer'; -import '@fortawesome/fontawesome-svg-core/styles.css'; -import {config} from '@fortawesome/fontawesome-svg-core'; config.autoAddCss = false; diff --git a/yarn.lock b/yarn.lock index 81916e0..2ae0342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,39 +43,6 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@fortawesome/fontawesome-common-types@6.7.2": - version "6.7.2" - resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470" - integrity sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg== - -"@fortawesome/fontawesome-svg-core@^6.7.2": - version "6.7.2" - resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz#0ac6013724d5cc327c1eb81335b91300a4fce2f2" - integrity sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/free-regular-svg-icons@^6.7.2": - version "6.7.2" - resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz#f1651e55e6651a15589b0569516208f9c65f96db" - integrity sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/free-solid-svg-icons@^6.7.2": - version "6.7.2" - resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz#fe25883b5eb8464a82918599950d283c465b57f6" - integrity sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA== - dependencies: - "@fortawesome/fontawesome-common-types" "6.7.2" - -"@fortawesome/react-fontawesome@^0.2.2": - version "0.2.2" - resolved "https://artifactory.1and1.org/artifactory/api/npm/bit-npm-virtual/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4" - integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g== - dependencies: - prop-types "^15.8.1" - "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" @@ -5861,6 +5828,11 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +react-icons@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.5.0.tgz#8aa25d3543ff84231685d3331164c00299cdfaf2" + integrity sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" From 12028999bf4bcffbb7149c1441aa9fba52f26db8 Mon Sep 17 00:00:00 2001 From: Malaber Date: Sat, 15 Mar 2025 13:43:31 +0100 Subject: [PATCH 17/40] Style fixes for scroll to top button after icon switch --- js/components/ScrollToTopButton.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/components/ScrollToTopButton.js b/js/components/ScrollToTopButton.js index 3e9bf7d..c2277a2 100644 --- a/js/components/ScrollToTopButton.js +++ b/js/components/ScrollToTopButton.js @@ -34,10 +34,13 @@ export function ScrollToTopButton() { zIndex: 999, transition: 'opacity 0.5s ease-in-out', opacity: isVisible ? 0.7 : 0, - pointerEvents: isVisible ? 'auto' : 'none' + pointerEvents: isVisible ? 'auto' : 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' }} > - + ); -} +} \ No newline at end of file From c23ae0cf8ca92425ca6e7f339f6133f383c889c2 Mon Sep 17 00:00:00 2001 From: Malaber Date: Sat, 15 Mar 2025 13:47:03 +0100 Subject: [PATCH 18/40] Add rough search for teams --- js/components/FavoriteBar.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 940ca9c..6f3b4e2 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -1,5 +1,5 @@ import React, {useState, useEffect, useRef} from 'react'; -import {Button, ButtonGroup} from 'reactstrap'; +import {Button, ButtonGroup, Input} from 'reactstrap'; import {FaHeartCirclePlus, FaRegHeart, FaHeart, FaArrowTurnDown} from 'react-icons/fa6'; export function FavoriteBar({teams}) { @@ -7,6 +7,7 @@ export function FavoriteBar({teams}) { const [isVisible, setIsVisible] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isPulsing, setIsPulsing] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); const headingRef = useRef(null); const favoriteBarRef = useRef(null); @@ -78,6 +79,7 @@ export function FavoriteBar({teams}) { } const sortedTeams = [...teams].sort((a, b) => a.name.localeCompare(b.name)); + const filteredTeams = sortedTeams.filter(team => team.name.toLowerCase().includes(searchQuery.toLowerCase())); return (
@@ -100,8 +102,17 @@ export function FavoriteBar({teams}) {
+ {sortedTeams.length > 5 && ( + setSearchQuery(e.target.value)} + className="mb-2" + /> + )}
- {sortedTeams.map(team => ( + {filteredTeams.map(team => (
); } else { - const team1Id = props.stageLevel !== undefined ? `favorite-team-level-${props.stageLevel}-${props.match.team1.id}` : undefined; - const team2Id = props.stageLevel !== undefined ? `favorite-team-level-${props.stageLevel}-${props.match.team2.id}` : undefined; - + let team1Id; let team2Id; + if (props.stageLevel !== undefined) { + team1Id = `favorite-team-level-${props.stageLevel}-${props.match.team1.id}`; + team2Id = `favorite-team-level-${props.stageLevel}-${props.match.team2.id}`; + } else { + team1Id = undefined; + team2Id = undefined; + } return ( diff --git a/js/components/ScrollToTopButton.js b/js/components/ScrollToTopButton.js index c2277a2..8e16f7a 100644 --- a/js/components/ScrollToTopButton.js +++ b/js/components/ScrollToTopButton.js @@ -43,4 +43,4 @@ export function ScrollToTopButton() { ); -} \ No newline at end of file +} diff --git a/js/redux/tournamentApi.js b/js/redux/tournamentApi.js index 2bca5cb..e1972a3 100644 --- a/js/redux/tournamentApi.js +++ b/js/redux/tournamentApi.js @@ -41,7 +41,13 @@ export function getTournamentMatches(tournamentId, successCallback, errorCallbac } getRequest(getState(), '/tournaments/' + tournamentId + '/matches' + matchFilter) .then(response => { - successCallback(response.status, response.data.sort((a, b) => a.position > b.position).map(match => convertMatch(match))); + successCallback( + response.status, response.data.sort( + (a, b) => a.position > b.position + ).map( + match => convertMatch(match) + ) + ); }) .catch(errorCallback); } diff --git a/pages/create.js b/pages/create.js index bace474..1adb14f 100644 --- a/pages/create.js +++ b/pages/create.js @@ -141,8 +141,8 @@ class CreateTournamentForm extends React.Component { diff --git a/pages/index.js b/pages/index.js index 3a7f6f2..9b0e3c0 100644 --- a/pages/index.js +++ b/pages/index.js @@ -9,8 +9,6 @@ import {TurniereNavigation} from '../js/components/Navigation'; import {BigImage} from '../js/components/BigImage'; import {Footer} from '../js/components/Footer'; -config.autoAddCss = false; - function Main() { return (
@@ -73,8 +71,8 @@ function MainBottomSummary() {

Ich habe einen Turniercode bekommen. Was nun?

- Der Turniercode führt dich direkt zu einem Turnier. Gebe dafür den Code oben ein, + Der Turniercode führt dich direkt zu einem Turnier. Gebe dafür den Code + oben ein, dann wirst du sofort weitergeleitet.

diff --git a/pages/private.js b/pages/private.js index b019592..4f76721 100644 --- a/pages/private.js +++ b/pages/private.js @@ -9,11 +9,11 @@ import {Footer} from '../js/components/Footer'; import TournamentList from '../js/components/TournamentList'; import RequireLogin from '../js/components/RequireLogin'; -import { LinkButton } from '../js/components/LinkButton'; +import {LinkButton} from '../js/components/LinkButton'; class PrivateTournamentsPage extends React.Component { render() { - return ( + return (
Private Turniere: turnie.re @@ -42,10 +42,10 @@ function PrivateTournamentsPageContent(props) { - zu den öffentlichen Turnieren + zu den öffentlichen Turnieren { props.isSignedIn && - neues Turnier erstellen + neues Turnier erstellen } @@ -57,7 +57,7 @@ class PrivateTournamentsCard extends React.Component { return (

Private Turniere

- +
); } diff --git a/pages/tournament-fullscreen-groups.js b/pages/tournament-fullscreen-groups.js index 4799756..808b939 100644 --- a/pages/tournament-fullscreen-groups.js +++ b/pages/tournament-fullscreen-groups.js @@ -4,7 +4,7 @@ import {ErrorPageComponent} from '../js/components/ErrorComponents'; import {getTournament} from '../js/redux/tournamentApi'; import { Col, - Container, Navbar, NavbarBrand, NavItem, Row, Spinner + Container, Row, Spinner } from 'reactstrap'; import {QRCodeSVG} from 'qrcode.react'; import {Group} from '../js/components/GroupStage'; @@ -14,22 +14,22 @@ function FullscreenPage(props) { let logo; if (props.showLogo) { logo =
-
- -
- ; +
+ +
+ ; } else { - logo =
; + logo =
; } return (
- - - {props.groups.map(group =>
)} + + + {props.groups.map(group => )}
@@ -40,13 +40,6 @@ function FullscreenPage(props) { ); } -function FullscreenPageHeader(props) { - return ( - {props.title} - {props.page}/{props.maxPage} - ); -} - class Main extends React.Component { static async getInitialProps({query}) { @@ -121,9 +114,9 @@ class Main extends React.Component { Vollbild-Ansicht: turnie.re - - - lade Vollbild-Ansicht + + + lade Vollbild-Ansicht ); } diff --git a/pages/tournament-fullscreen.js b/pages/tournament-fullscreen.js index 5819389..2f69473 100644 --- a/pages/tournament-fullscreen.js +++ b/pages/tournament-fullscreen.js @@ -3,7 +3,7 @@ import React from 'react'; import {ErrorPageComponent} from '../js/components/ErrorComponents'; import {getTournamentMatches, getTournamentMeta} from '../js/redux/tournamentApi'; import { - Col, Container, DropdownItem, DropdownMenu, DropdownToggle, Navbar, NavbarBrand, NavItem, Row, UncontrolledDropdown, + Col, Container, DropdownItem, DropdownMenu, DropdownToggle, Navbar, NavbarBrand, Row, UncontrolledDropdown, Spinner } from 'reactstrap'; import {Match} from '../js/components/Match'; From dcf516020be34617ff529b7df59c4e956c681cc0 Mon Sep 17 00:00:00 2001 From: Malaber Date: Sun, 16 Mar 2025 14:25:39 +0100 Subject: [PATCH 21/40] Run eslint tests in test of gitlabci --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d19a63..5af8d13 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,3 +9,9 @@ variables: include: - project: 'turniere/turniere-infra' file: '/ci/pipeline.yaml' + +eslint: + stage: test + script: + - yarn install + - yarn eslint . From 49a2a27e4c03e030db5c78da9b88359c07bb7b26 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:26:47 +0100 Subject: [PATCH 22/40] Replace "Spiele Ausblenden" with Chevron --- js/components/GroupStage.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index 6cc4f19..cdbdc29 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -4,6 +4,7 @@ import React, {Component} from 'react'; import {getGroup} from '../redux/tournamentApi'; import {notify} from 'react-notify-toast'; import {sortMatchesByPositionAscending} from '../utils/sorting'; +import {FaChevronDown, FaChevronUp} from 'react-icons/fa6'; export default class GroupStage extends Component { constructor(props) { @@ -20,23 +21,27 @@ export default class GroupStage extends Component { return (

Gruppenphase -

- {this.props.groups.map(group => )} + {this.props.groups.map(group => )}
); } } -function ShowMatchesToggleButton(props) { - return (); } - export class Group extends Component { constructor(props) { super(props); @@ -61,8 +66,11 @@ export class Group extends Component { render() { return (
- -

Gruppe {this.state.number}

+ +

+ Gruppe {this.state.number} +

+ {this.state.matches.sort(sortMatchesByPositionAscending()).map((match => ( Date: Mon, 17 Mar 2025 15:31:23 +0100 Subject: [PATCH 23/40] Animation for chevron turning --- js/components/GroupStage.js | 13 ++++++++++--- public/static/css/tournament.css | 7 +++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index cdbdc29..51c5614 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -37,9 +37,16 @@ export default class GroupStage extends Component { } function ShowMatchesToggleChevron(props) { - return (); + const toggleClass = props.show ? 'rotate' : ''; + return ( + + ); } export class Group extends Component { diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index 6728a6f..b377cdc 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -56,3 +56,10 @@ .favorite-bar.visible { max-height: none; } + +.my-chevron { + transition: transform 0.3s ease; +} +.my-chevron.rotate { + transform: rotate(180deg); +} From dfd7525eeced02d68043478e3e5b7b70eaa3ca1c Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:34:03 +0100 Subject: [PATCH 24/40] Button only focussable with keyboard --- js/components/GroupStage.js | 4 ++-- public/static/css/tournament.css | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index 51c5614..26b37b5 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -4,7 +4,7 @@ import React, {Component} from 'react'; import {getGroup} from '../redux/tournamentApi'; import {notify} from 'react-notify-toast'; import {sortMatchesByPositionAscending} from '../utils/sorting'; -import {FaChevronDown, FaChevronUp} from 'react-icons/fa6'; +import {FaChevronDown} from 'react-icons/fa6'; export default class GroupStage extends Component { constructor(props) { @@ -42,7 +42,7 @@ function ShowMatchesToggleChevron(props) { diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index b377cdc..b145445 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -59,7 +59,13 @@ .my-chevron { transition: transform 0.3s ease; + color: gray; } .my-chevron.rotate { transform: rotate(180deg); } +.button-no-focus:focus:not(:focus-visible), +.button-no-focus:active { + outline: none !important; + box-shadow: none !important; +} \ No newline at end of file From 859804a649bd08b53d7897b26141c2d630527673 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:36:34 +0100 Subject: [PATCH 25/40] Move chevron up a bit --- js/components/GroupStage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index 26b37b5..530949b 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -42,7 +42,7 @@ function ShowMatchesToggleChevron(props) { From 5a00e2a10045b115f72ad9f2f969f65dbdf84951 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:42:00 +0100 Subject: [PATCH 26/40] Make name clickable --- js/components/FavoriteBar.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 26e1fb1..cdd6023 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -94,7 +94,7 @@ export function FavoriteBar({teams}) {

{favorite ? favorite.name : ''}

{favorite && ( )} @@ -119,13 +119,21 @@ export function FavoriteBar({teams}) { )}
{filteredTeams.map(team => ( -
- - - {team.name} - + {team.name}
))}
From 7a15106d01f568849505708dae2d7e14b7444c5b Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:44:47 +0100 Subject: [PATCH 27/40] Bootstrap and text overhaul --- js/components/FavoriteBar.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index cdd6023..2fbffca 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -89,20 +89,23 @@ export function FavoriteBar({teams}) { return (
-
+

Favorit:

{favorite ? favorite.name : ''}

- {favorite && ( )} From 2386675c52ca97063f5ad378ad21c2e3442d3ef7 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:45:09 +0100 Subject: [PATCH 28/40] Deutschland --- js/components/FavoriteBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 2fbffca..f29fa75 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -114,7 +114,7 @@ export function FavoriteBar({teams}) { {sortedTeams.length > 5 && ( setSearchQuery(e.target.value)} className="mb-2" From ae7af1998d24e96386ebfff143bf34c52e88ce73 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:48:26 +0100 Subject: [PATCH 29/40] Focus baby, Focus! --- js/components/FavoriteBar.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index f29fa75..1312bd2 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -10,6 +10,7 @@ export function FavoriteBar({teams}) { const [searchQuery, setSearchQuery] = useState(''); const headingRef = useRef(null); const favoriteBarRef = useRef(null); + const scrollButtonRef = useRef(null); useEffect(() => { const savedFavorite = localStorage.getItem('favoriteTeam'); @@ -45,6 +46,9 @@ export function FavoriteBar({teams}) { localStorage.setItem('favoriteTeam', team.id); setIsPulsing(true); headingRef.current.scrollIntoView({behavior: 'smooth', block: 'center'}); + if (scrollButtonRef.current) { + scrollButtonRef.current.focus(); + } } setIsVisible(false); // Close the favorite menu }; @@ -104,6 +108,7 @@ export function FavoriteBar({teams}) { title="Zum aktuellen Spiel des Favoriten springen" onClick={scrollToFavorite} className={isPulsing ? 'pulse-animation' : ''} + innerRef={scrollButtonRef} > From 01f8a8e34f3d09151fb78355e1167885fdf6836a Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 15:52:58 +0100 Subject: [PATCH 30/40] Padding and Margining --- js/components/FavoriteBar.js | 2 +- js/components/GroupStage.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 1312bd2..92e2817 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -94,7 +94,7 @@ export function FavoriteBar({teams}) { return (
-

Favorit:

+

Favorit:

{favorite ? favorite.name : ''}

); } @@ -54,10 +63,18 @@ export class Group extends Component { super(props); this.state = props.group; this.reload = this.reload.bind(this); + this.handleToggle = this.handleToggle.bind(this); this.onReloadSuccess = this.onReloadSuccess.bind(this); this.onReloadError = this.onReloadError.bind(this); } + handleToggle() { + this.props.showMatchesToggle(); + if (this.props.groupRef.current) { + this.props.groupRef.current.scrollIntoView({behavior: 'smooth', block: 'center'}); + } + } + reload() { getGroup(this.state.id, this.onReloadSuccess, this.onReloadError); } @@ -71,43 +88,55 @@ export class Group extends Component { } render() { - return (
- - -

- Gruppe {this.state.number} -

- - - {this.state.matches.sort(sortMatchesByPositionAscending()).map((match => ( - )))} - - -
-
- ); + return ( + + + +

+ Gruppe {this.state.number} +

+ + + {this.state.matches.sort(sortMatchesByPositionAscending()).map(match => ( + + ))} + + +
+
+ + ); } } function GroupScoresTable(props) { - return (
- - - - - - - - - - - {props.scores.map(groupScore => )} - -
#TeamPkt.Dif.Gew.
); + return ( + + + + + + + + + + + + {props.scores.map(groupScore => )} + +
#TeamPkt.Dif.Gew.
+ ); } - function GroupScoresTableRow(props) { const teamId = `favorite-team-groupstage-${props.score.team.id}`; return ( From 8a9dc704c090ac2e58cc2b2debb6b961d71e20f4 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 16:12:16 +0100 Subject: [PATCH 32/40] Only open single groups --- js/components/GroupStage.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index 81cb898..f930ba4 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -9,18 +9,12 @@ import {FaChevronDown} from 'react-icons/fa6'; export default class GroupStage extends Component { constructor(props) { super(props); - this.state = {showMatches: this.props.showMatches}; - this.toggleShowMatches = this.toggleShowMatches.bind(this); this.groupRefs = this.props.groups.reduce((acc, group) => { acc[group.id] = React.createRef(); return acc; }, {}); } - toggleShowMatches() { - this.setState({showMatches: !this.state.showMatches}); - } - render() { return (
@@ -34,8 +28,6 @@ export default class GroupStage extends Component { key={group.id} isSignedIn={this.props.isSignedIn} isOwner={this.props.isOwner} - showMatches={this.state.showMatches} - showMatchesToggle={this.toggleShowMatches} groupRef={this.groupRefs[group.id]} /> ))} @@ -61,7 +53,10 @@ function ShowMatchesToggleChevron(props) { export class Group extends Component { constructor(props) { super(props); - this.state = props.group; + this.state = { + ...props.group, + showMatches: false + }; this.reload = this.reload.bind(this); this.handleToggle = this.handleToggle.bind(this); this.onReloadSuccess = this.onReloadSuccess.bind(this); @@ -69,7 +64,7 @@ export class Group extends Component { } handleToggle() { - this.props.showMatchesToggle(); + this.setState(prevState => ({ showMatches: !prevState.showMatches })); if (this.props.groupRef.current) { this.props.groupRef.current.scrollIntoView({behavior: 'smooth', block: 'center'}); } @@ -96,10 +91,10 @@ export class Group extends Component { Gruppe {this.state.number} - + {this.state.matches.sort(sortMatchesByPositionAscending()).map(match => ( Date: Mon, 17 Mar 2025 16:14:53 +0100 Subject: [PATCH 33/40] Title for chevron --- js/components/GroupStage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index f930ba4..1135e15 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -44,6 +44,7 @@ function ShowMatchesToggleChevron(props) { color="link" onClick={props.toggle} className="position-absolute top-0 end-0 m-2 mt-1 button-no-focus" + title={props.show ? 'Matches ausblenden' : 'Matches einblenden'} > From 10d3ba59dd4c71e4f2e973b891f6d5381715682d Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 22:38:46 +0100 Subject: [PATCH 34/40] On group scroll highlight group --- js/components/FavoriteBar.js | 19 +++++++++++++------ js/components/GroupStage.js | 28 +++++++++++++++++----------- public/static/css/tournament.css | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 92e2817..dc09e88 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -76,10 +76,17 @@ export function FavoriteBar({teams}) { if (lowestStageEl) { lowestStageEl.scrollIntoView({behavior: 'smooth', block: 'end'}); } else { - const groupEl = document.getElementById(`favorite-team-groupstage-${favorite.id}`); - if (groupEl) { - groupEl.scrollIntoView({behavior: 'smooth', block: 'end'}); - } + const groupElements = document.querySelectorAll('[data-teams]'); + groupElements.forEach(groupEl => { + const teamIds = groupEl.getAttribute('data-teams').split(',').map(id => parseInt(id, 10)); + if (teamIds.includes(favorite.id)) { + groupEl.scrollIntoView({behavior: 'smooth', block: 'center'}); + groupEl.classList.add('highlight'); + setTimeout(() => { + groupEl.classList.remove('highlight'); + }, 2000); + } + }); } setIsPulsing(false); }; @@ -101,7 +108,7 @@ export function FavoriteBar({teams}) { title="{isVisible ? 'Favoriten schließen' : 'Favoriten öffnen'}" onClick={() => setIsVisible(!isVisible)} > - + {favorite && ( )} diff --git a/js/components/GroupStage.js b/js/components/GroupStage.js index 1135e15..10487a2 100644 --- a/js/components/GroupStage.js +++ b/js/components/GroupStage.js @@ -65,7 +65,7 @@ export class Group extends Component { } handleToggle() { - this.setState(prevState => ({ showMatches: !prevState.showMatches })); + this.setState(prevState => ({showMatches: !prevState.showMatches})); if (this.props.groupRef.current) { this.props.groupRef.current.scrollIntoView({behavior: 'smooth', block: 'center'}); } @@ -84,9 +84,15 @@ export class Group extends Component { } render() { + const teamIds = new Set(); + this.state.matches.forEach(match => { + teamIds.add(match.team1.id); + teamIds.add(match.team2.id); + }); + const teamIdsString = Array.from(teamIds).join(','); return ( - - + +

Gruppe {this.state.number} @@ -118,16 +124,16 @@ function GroupScoresTable(props) { return ( - - - - - - - + + + + + + + - {props.scores.map(groupScore => )} + {props.scores.map(groupScore => )}
#TeamPkt.Dif.Gew.
#TeamPkt.Dif.Gew.
); diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index b145445..66dbbc0 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -68,4 +68,19 @@ .button-no-focus:active { outline: none !important; box-shadow: none !important; +} +@keyframes blink { + 0% { + background-color: transparent; + } + 50% { + background-color: yellow; + } + 100% { + background-color: transparent; + } +} + +.highlight { + animation: blink 1s ease-in-out; } \ No newline at end of file From 5dd9c1155fb362e67bb3b42dfe23f404a5ff369f Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 22:50:54 +0100 Subject: [PATCH 35/40] Scroll to wait and highlight playoffs as well --- js/components/FavoriteBar.js | 23 ++++++++++++++++------- public/static/css/tournament.css | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index dc09e88..9fdaf9b 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -73,22 +73,31 @@ export function FavoriteBar({teams}) { } }); + let scrollTo; if (lowestStageEl) { - lowestStageEl.scrollIntoView({behavior: 'smooth', block: 'end'}); + scrollTo = lowestStageEl; } else { const groupElements = document.querySelectorAll('[data-teams]'); groupElements.forEach(groupEl => { const teamIds = groupEl.getAttribute('data-teams').split(',').map(id => parseInt(id, 10)); if (teamIds.includes(favorite.id)) { - groupEl.scrollIntoView({behavior: 'smooth', block: 'center'}); - groupEl.classList.add('highlight'); - setTimeout(() => { - groupEl.classList.remove('highlight'); - }, 2000); + scrollTo = groupEl; } }); } - setIsPulsing(false); + scrollTo.scrollIntoView({behavior: 'smooth', block: 'center'}); + + let scrollTimeout; + window.addEventListener('scroll', function() { + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(function() { + setIsPulsing(false); + scrollTo.classList.add('scroll-to-highlight'); + setTimeout(() => { + scrollTo.classList.remove('scroll-to-highlight'); + }, 2000); + }, 100); + }, {once: true}); }; if (isLoading) { diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index 66dbbc0..9031447 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -81,6 +81,6 @@ } } -.highlight { +.scroll-to-highlight { animation: blink 1s ease-in-out; } \ No newline at end of file From cdb1bc52dd42ad5e9c1aca96b150ab51a3025a8b Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 23:02:28 +0100 Subject: [PATCH 36/40] Scroll to Match instead of team and animate with enlargement --- js/components/FavoriteBar.js | 22 +++++++++++----------- js/components/Match.js | 15 +++++++++++++-- js/components/MatchTable.js | 12 ++---------- public/static/css/tournament.css | 19 ++++--------------- 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index 9fdaf9b..f072634 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -58,24 +58,24 @@ export function FavoriteBar({teams}) { return; } - const stageElements = document.querySelectorAll(`[id^='favorite-team-level-'][id$='-${favorite.id}']`); - let lowestStageEl = null; + const matchesWithFavoriteParticipation = document.querySelectorAll(`[data-team-level-ids*='-${favorite.id}']`); + let lowestMatch = null; // lowest means lowest stage number > latest game of the favorite let lowestStageNum = Infinity; - stageElements.forEach(el => { - const match = el.id.match(/^favorite-team-level-(\d+)-(\d+)$/); - if (match) { - const stage = parseInt(match[1]); - if (stage < lowestStageNum) { + matchesWithFavoriteParticipation.forEach(el => { + const dataTeamLevelIds = el.getAttribute('data-team-level-ids').split(','); + dataTeamLevelIds.forEach(pair => { + const [stage, teamId] = pair.split('-').map(Number); + if (teamId === favorite.id && stage < lowestStageNum) { lowestStageNum = stage; - lowestStageEl = el; + lowestMatch = el; } - } + }); }); let scrollTo; - if (lowestStageEl) { - scrollTo = lowestStageEl; + if (lowestMatch) { + scrollTo = lowestMatch; } else { const groupElements = document.querySelectorAll('[data-teams]'); groupElements.forEach(groupEl => { diff --git a/js/components/Match.js b/js/components/Match.js index 55e6df8..104957e 100644 --- a/js/components/Match.js +++ b/js/components/Match.js @@ -122,9 +122,20 @@ export class Match extends React.Component { const groupInformation = this.state.match.group ?
Gr. {this.state.match.group.number}
: ''; - + let team1Id; let team2Id; + if (this.props.stageLevel !== undefined) { + team1Id = `${this.props.stageLevel}-${this.props.match.team1.id}`; + team2Id = `${this.props.stageLevel}-${this.props.match.team2.id}`; + } else { + team1Id = undefined; + team2Id = undefined; + } return (
- +
); } else { - let team1Id; let team2Id; - if (props.stageLevel !== undefined) { - team1Id = `favorite-team-level-${props.stageLevel}-${props.match.team1.id}`; - team2Id = `favorite-team-level-${props.stageLevel}-${props.match.team2.id}`; - } else { - team1Id = undefined; - team2Id = undefined; - } return ( - + - +
{props.match.team1.score} {props.match.team1.name}
{props.match.team2.score} {props.match.team2.name} diff --git a/public/static/css/tournament.css b/public/static/css/tournament.css index 9031447..4280300 100644 --- a/public/static/css/tournament.css +++ b/public/static/css/tournament.css @@ -47,6 +47,10 @@ animation: pulse 1s infinite; } +.scroll-to-highlight { + animation: pulse 1s ease-in-out; +} + .favorite-bar { max-height: 0; overflow: hidden; @@ -69,18 +73,3 @@ outline: none !important; box-shadow: none !important; } -@keyframes blink { - 0% { - background-color: transparent; - } - 50% { - background-color: yellow; - } - 100% { - background-color: transparent; - } -} - -.scroll-to-highlight { - animation: blink 1s ease-in-out; -} \ No newline at end of file From 4edcfac209128a9080d03a6cdc2818fba673959c Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 23:04:47 +0100 Subject: [PATCH 37/40] Some comments --- js/components/FavoriteBar.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index f072634..ad934d1 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -55,13 +55,14 @@ export function FavoriteBar({teams}) { const scrollToFavorite = () => { if (!favorite) { - return; + return; // Exit if there is no favorite team selected } const matchesWithFavoriteParticipation = document.querySelectorAll(`[data-team-level-ids*='-${favorite.id}']`); let lowestMatch = null; // lowest means lowest stage number > latest game of the favorite let lowestStageNum = Infinity; + // Iterate over each element to find the match with the lowest stage number matchesWithFavoriteParticipation.forEach(el => { const dataTeamLevelIds = el.getAttribute('data-team-level-ids').split(','); dataTeamLevelIds.forEach(pair => { @@ -77,17 +78,19 @@ export function FavoriteBar({teams}) { if (lowestMatch) { scrollTo = lowestMatch; } else { + // If no match is found, look for group elements that contain the favorite team's ID const groupElements = document.querySelectorAll('[data-teams]'); groupElements.forEach(groupEl => { const teamIds = groupEl.getAttribute('data-teams').split(',').map(id => parseInt(id, 10)); if (teamIds.includes(favorite.id)) { - scrollTo = groupEl; + scrollTo = groupEl; // Update the scroll target to the group element } }); } - scrollTo.scrollIntoView({behavior: 'smooth', block: 'center'}); + scrollTo.scrollIntoView({behavior: 'smooth', block: 'center'}); // Smoothly scroll to the target element let scrollTimeout; + // Add a scroll event listener to start the highlighting after scrolling only window.addEventListener('scroll', function() { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(function() { From 7837b40d562b70de99e62f064d48c44f503b372a Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 17 Mar 2025 23:20:33 +0100 Subject: [PATCH 38/40] Add detection for scroll is over https://stackoverflow.com/a/51142522/10559526 --- js/components/FavoriteBar.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index ad934d1..fa5ac10 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -87,20 +87,24 @@ export function FavoriteBar({teams}) { } }); } - scrollTo.scrollIntoView({behavior: 'smooth', block: 'center'}); // Smoothly scroll to the target element let scrollTimeout; - // Add a scroll event listener to start the highlighting after scrolling only - window.addEventListener('scroll', function() { + const handleScroll = () => { clearTimeout(scrollTimeout); - scrollTimeout = setTimeout(function() { + scrollTimeout = setTimeout(() => { setIsPulsing(false); scrollTo.classList.add('scroll-to-highlight'); setTimeout(() => { scrollTo.classList.remove('scroll-to-highlight'); }, 2000); + window.removeEventListener('scroll', handleScroll); }, 100); - }, {once: true}); + }; + + scrollTo.scrollIntoView({behavior: 'smooth', block: 'center'}); // Smoothly scroll to the target element + + // Add a scroll event listener to start the highlighting after scrolling only + window.addEventListener('scroll', handleScroll); }; if (isLoading) { From 7a0bbccc19346e23ab51a5afc936df46de6041c8 Mon Sep 17 00:00:00 2001 From: Malaber Date: Wed, 19 Mar 2025 09:17:34 +0100 Subject: [PATCH 39/40] Extract favorite finding in playoffs to separate method --- js/components/FavoriteBar.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index fa5ac10..b75a7af 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -53,12 +53,8 @@ export function FavoriteBar({teams}) { setIsVisible(false); // Close the favorite menu }; - const scrollToFavorite = () => { - if (!favorite) { - return; // Exit if there is no favorite team selected - } - - const matchesWithFavoriteParticipation = document.querySelectorAll(`[data-team-level-ids*='-${favorite.id}']`); + function findLowestPlayoffParticipation(favoriteId) { + const matchesWithFavoriteParticipation = document.querySelectorAll(`[data-team-level-ids*='-${favoriteId}']`); let lowestMatch = null; // lowest means lowest stage number > latest game of the favorite let lowestStageNum = Infinity; @@ -73,6 +69,15 @@ export function FavoriteBar({teams}) { } }); }); + return lowestMatch; + } + + const scrollToFavorite = () => { + if (!favorite) { + return; // Exit if there is no favorite team selected + } + + const lowestMatch = findLowestPlayoffParticipation(favorite.id); let scrollTo; if (lowestMatch) { From e1cadf91089ef8f888adc8574ee2ed70a69d8463 Mon Sep 17 00:00:00 2001 From: Malaber Date: Wed, 19 Mar 2025 09:25:41 +0100 Subject: [PATCH 40/40] Extract group finding to method --- js/components/FavoriteBar.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/js/components/FavoriteBar.js b/js/components/FavoriteBar.js index b75a7af..86fd731 100644 --- a/js/components/FavoriteBar.js +++ b/js/components/FavoriteBar.js @@ -72,6 +72,21 @@ export function FavoriteBar({teams}) { return lowestMatch; } + function findScrollToGroup(favoriteId) { + // Look for group elements that contain the favorite team's ID + const groupElements = document.querySelectorAll('[data-teams]'); + const scrollToNotFound = null; + + groupElements.forEach(groupEl => { + const teamIds = groupEl.getAttribute('data-teams').split(',').map(id => parseInt(id, 10)); + if (teamIds.includes(favoriteId)) { + return groupEl; + } + }); + + return scrollToNotFound; + } + const scrollToFavorite = () => { if (!favorite) { return; // Exit if there is no favorite team selected @@ -83,14 +98,12 @@ export function FavoriteBar({teams}) { if (lowestMatch) { scrollTo = lowestMatch; } else { - // If no match is found, look for group elements that contain the favorite team's ID - const groupElements = document.querySelectorAll('[data-teams]'); - groupElements.forEach(groupEl => { - const teamIds = groupEl.getAttribute('data-teams').split(',').map(id => parseInt(id, 10)); - if (teamIds.includes(favorite.id)) { - scrollTo = groupEl; // Update the scroll target to the group element - } - }); + scrollTo = findScrollToGroup(favorite.id); + } + + if (!scrollTo) { + console.error('No match or group found for the favorite team'); + return; } let scrollTimeout;