fix(players): improve Challonge auth UX and manual token flow

This commit is contained in:
Pandipipas
2026-02-17 18:39:29 +01:00
parent 9a5b64b2c0
commit c718f43eba
+85 -3
View File
@@ -128,6 +128,8 @@ const oauthLoading = ref(false);
const challongeOauthLoading = ref(false); const challongeOauthLoading = ref(false);
const isManualTokenDialogOpen = ref(false); const isManualTokenDialogOpen = ref(false);
const manualTokenDraft = ref(''); const manualTokenDraft = ref('');
const isChallongeManualTokenDialogOpen = ref(false);
const challongeManualTokenDraft = ref('');
const oauthSessionId = ref(''); const oauthSessionId = ref('');
const challongeOauthSessionId = ref(''); const challongeOauthSessionId = ref('');
let oauthPollingTimer: ReturnType<typeof setInterval> | null = null; let oauthPollingTimer: ReturnType<typeof setInterval> | null = null;
@@ -143,6 +145,7 @@ const selectedChallongePlayerIds = ref<string[]>([]);
const challongeImportDialogOpen = ref(false); const challongeImportDialogOpen = ref(false);
const loadingChallongeTournamentPlayers = ref(false); const loadingChallongeTournamentPlayers = ref(false);
const importingChallongeTournament = ref<ChallongeTournament | null>(null); const importingChallongeTournament = ref<ChallongeTournament | null>(null);
const hasValidatedChallongeToken = ref(false);
interface OAuthSessionResponse { interface OAuthSessionResponse {
sessionId: string; sessionId: string;
@@ -161,6 +164,7 @@ watch(startGGToken, (value) => {
watch(challongeToken, (value) => { watch(challongeToken, (value) => {
localStorage.setItem(CHALLONGE_TOKEN_STORAGE_KEY, value); localStorage.setItem(CHALLONGE_TOKEN_STORAGE_KEY, value);
hasValidatedChallongeToken.value = false;
}); });
const persistTemporaryStartGGPlayers = () => { const persistTemporaryStartGGPlayers = () => {
@@ -463,6 +467,8 @@ const selectedChallongeTournamentOption = computed(() =>
const canImportSelectedChallongeTournament = computed(() => Boolean(selectedChallongeTournamentOption.value)); const canImportSelectedChallongeTournament = computed(() => Boolean(selectedChallongeTournamentOption.value));
const hasChallongeTokenConfigured = computed(() => Boolean(challongeToken.value.trim())); const hasChallongeTokenConfigured = computed(() => Boolean(challongeToken.value.trim()));
const challongeConnectionLabel = computed(() => (hasValidatedChallongeToken.value ? 'Connected' : 'Token set'));
const filterChallongeTournaments = (value: string, update: (callback: () => void) => void) => { const filterChallongeTournaments = (value: string, update: (callback: () => void) => void) => {
update(() => { update(() => {
const needle = value.toLowerCase().trim(); const needle = value.toLowerCase().trim();
@@ -491,18 +497,41 @@ const loadChallongeRecentTournaments = async () => {
const tournaments = await sendNodeCGMessage<ChallongeTournament[]>('challonge:fetchRecentTournaments', { const tournaments = await sendNodeCGMessage<ChallongeTournament[]>('challonge:fetchRecentTournaments', {
token, token,
}); });
hasValidatedChallongeToken.value = true;
challongeRecentTournaments.value = tournaments; challongeRecentTournaments.value = tournaments;
if (!tournaments.length) { if (!tournaments.length) {
challongeTournamentsError.value = 'There are no recent tournaments for this account.'; challongeTournamentsError.value = 'There are no recent tournaments for this account.';
} }
} catch (error) { } 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 = []; challongeRecentTournaments.value = [];
} finally { } finally {
challongeLoadingTournaments.value = false; 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) => { const openChallongeImportDialog = async (tournament: ChallongeTournament) => {
importingChallongeTournament.value = tournament; importingChallongeTournament.value = tournament;
challongeImportDialogOpen.value = true; challongeImportDialogOpen.value = true;
@@ -932,9 +961,19 @@ onBeforeUnmount(() => {
<QBtn <QBtn
v-else v-else
outline outline
color="positive" :color="hasValidatedChallongeToken ? 'positive' : 'warning'"
icon="check_circle" icon="check_circle"
label="Connected" :label="challongeConnectionLabel"
@click="openChallongeManualTokenDialog"
/>
</div>
<div class="col-auto">
<QBtn
outline
color="white"
icon="vpn_key"
label="Use personal API"
@click="openChallongeManualTokenDialog"
/> />
</div> </div>
</div> </div>
@@ -1147,6 +1186,49 @@ onBeforeUnmount(() => {
</QCard> </QCard>
</QDialog> </QDialog>
<QDialog v-model="isChallongeManualTokenDialogOpen">
<QCard class="players-dialog">
<QCardSection>
<div class="text-h6">
Personal Challonge API
</div>
</QCardSection>
<QSeparator />
<QCardSection>
<div class="text-body2 q-mb-sm">
If OAuth fails, paste a personal Challonge API token.
</div>
<QInput
v-model="challongeManualTokenDraft"
label="Paste your personal Challonge token"
dense
outlined
type="password"
/>
</QCardSection>
<QSeparator />
<QCardActions align="right">
<QBtn
flat
label="Cancel"
color="secondary"
@click="isChallongeManualTokenDialogOpen = false"
/>
<QBtn
flat
color="negative"
label="Delete token"
@click="challongeManualTokenDraft = ''; saveChallongeManualToken()"
/>
<QBtn
color="primary"
label="Save token"
@click="saveChallongeManualToken"
/>
</QCardActions>
</QCard>
</QDialog>
<QDialog v-model="isDialogOpen"> <QDialog v-model="isDialogOpen">
<QCard class="players-dialog"> <QCard class="players-dialog">
<QCardSection> <QCardSection>