From c718f43eba63b89c1626890aa7d78037820a135b Mon Sep 17 00:00:00 2001 From: Pandipipas <62224708+Pandipipas@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:39:29 +0100 Subject: [PATCH] fix(players): improve Challonge auth UX and manual token flow --- src/dashboard/scoreko-dev/views/Players.vue | 88 ++++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/src/dashboard/scoreko-dev/views/Players.vue b/src/dashboard/scoreko-dev/views/Players.vue index 2839fda..be6c0da 100644 --- a/src/dashboard/scoreko-dev/views/Players.vue +++ b/src/dashboard/scoreko-dev/views/Players.vue @@ -128,6 +128,8 @@ const oauthLoading = ref(false); const challongeOauthLoading = ref(false); const isManualTokenDialogOpen = ref(false); const manualTokenDraft = ref(''); +const isChallongeManualTokenDialogOpen = ref(false); +const challongeManualTokenDraft = ref(''); const oauthSessionId = ref(''); const challongeOauthSessionId = ref(''); let oauthPollingTimer: ReturnType | null = null; @@ -143,6 +145,7 @@ const selectedChallongePlayerIds = ref([]); const challongeImportDialogOpen = ref(false); const loadingChallongeTournamentPlayers = ref(false); const importingChallongeTournament = ref(null); +const hasValidatedChallongeToken = ref(false); interface OAuthSessionResponse { sessionId: string; @@ -161,6 +164,7 @@ watch(startGGToken, (value) => { watch(challongeToken, (value) => { localStorage.setItem(CHALLONGE_TOKEN_STORAGE_KEY, value); + hasValidatedChallongeToken.value = false; }); const persistTemporaryStartGGPlayers = () => { @@ -463,6 +467,8 @@ const selectedChallongeTournamentOption = computed(() => const canImportSelectedChallongeTournament = computed(() => Boolean(selectedChallongeTournamentOption.value)); const hasChallongeTokenConfigured = computed(() => Boolean(challongeToken.value.trim())); +const challongeConnectionLabel = computed(() => (hasValidatedChallongeToken.value ? 'Connected' : 'Token set')); + const filterChallongeTournaments = (value: string, update: (callback: () => void) => void) => { update(() => { const needle = value.toLowerCase().trim(); @@ -491,18 +497,41 @@ const loadChallongeRecentTournaments = async () => { const tournaments = await sendNodeCGMessage('challonge:fetchRecentTournaments', { token, }); + hasValidatedChallongeToken.value = true; challongeRecentTournaments.value = tournaments; if (!tournaments.length) { challongeTournamentsError.value = 'There are no recent tournaments for this account.'; } } catch (error) { - challongeTournamentsError.value = error instanceof Error ? error.message : 'Could not load tournaments.'; + hasValidatedChallongeToken.value = false; + const message = error instanceof Error ? error.message : 'Could not load tournaments.'; + challongeTournamentsError.value = message.includes('401') + ? 'Challonge rejected the token (401 Unauthorized). Verify OAuth callback/client credentials or paste a valid personal API token.' + : message; challongeRecentTournaments.value = []; } finally { challongeLoadingTournaments.value = false; } }; +const openChallongeManualTokenDialog = () => { + challongeManualTokenDraft.value = challongeToken.value; + isChallongeManualTokenDialogOpen.value = true; +}; + +const saveChallongeManualToken = () => { + challongeToken.value = challongeManualTokenDraft.value.trim(); + + if (!challongeToken.value) { + challongeRecentTournaments.value = []; + selectedChallongeTournamentSlug.value = ''; + challongeTournamentInput.value = ''; + challongeTournamentsError.value = ''; + } + + isChallongeManualTokenDialogOpen.value = false; +}; + const openChallongeImportDialog = async (tournament: ChallongeTournament) => { importingChallongeTournament.value = tournament; challongeImportDialogOpen.value = true; @@ -932,9 +961,19 @@ onBeforeUnmount(() => { + +
+
@@ -1147,6 +1186,49 @@ onBeforeUnmount(() => { + + + +
+ Personal Challonge API +
+
+ + +
+ If OAuth fails, paste a personal Challonge API token. +
+ +
+ + + + + + +
+
+