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 isManualTokenDialogOpen = ref(false);
const manualTokenDraft = ref('');
const isChallongeManualTokenDialogOpen = ref(false);
const challongeManualTokenDraft = ref('');
const oauthSessionId = ref('');
const challongeOauthSessionId = ref('');
let oauthPollingTimer: ReturnType<typeof setInterval> | null = null;
@@ -143,6 +145,7 @@ const selectedChallongePlayerIds = ref<string[]>([]);
const challongeImportDialogOpen = ref(false);
const loadingChallongeTournamentPlayers = ref(false);
const importingChallongeTournament = ref<ChallongeTournament | null>(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<ChallongeTournament[]>('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(() => {
<QBtn
v-else
outline
color="positive"
:color="hasValidatedChallongeToken ? 'positive' : 'warning'"
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>
@@ -1147,6 +1186,49 @@ onBeforeUnmount(() => {
</QCard>
</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">
<QCard class="players-dialog">
<QCardSection>