diff --git a/code/websites/pokedex.online/src/views/ChallongeTest.vue b/code/websites/pokedex.online/src/views/ChallongeTest.vue index 9b382cb..c01902d 100644 --- a/code/websites/pokedex.online/src/views/ChallongeTest.vue +++ b/code/websites/pokedex.online/src/views/ChallongeTest.vue @@ -250,6 +250,13 @@ import TournamentDetail from '../components/challonge/TournamentDetail.vue'; import { ScopeType } from '../services/challonge.service.js'; const { getApiKey } = useChallongeApiKey(); +const apiKey = computed(() => getApiKey()); +const maskedApiKey = computed(() => { + if (!apiKey.value) return ''; + return apiKey.value.slice(0, 4) + '•••••••' + apiKey.value.slice(-4); +}); + +// OAuth Management const { isAuthenticated, accessToken, @@ -257,36 +264,19 @@ const { logout: oauthLogout, loading: oauthLoading } = useChallongeOAuth(); + +// Client Credentials Management const { isAuthenticated: isClientCredsAuthenticated, accessToken: clientCredsToken } = useChallongeClientCredentials(); // API Configuration -const apiVersion = ref('v2.1'); // 'v1' or 'v2.1' - -// State management with useAsyncState -const tournamentListState = useAsyncState(); -const loadMoreState = useAsyncState(); -const tournamentDetailsState = useAsyncState(); - -// Destructure for template usage -const { data: tournaments, isLoading: loading, error } = tournamentListState; -const { isLoading: loadingMore } = loadMoreState; - -const searchQuery = ref(''); -const expandedTournamentId = ref(null); - -// Pagination -const currentPage = ref(1); -const perPage = ref(10); -const totalTournaments = ref(0); -const hasNextPage = ref(false); - -// v2.1 Tournament Scope +const apiVersion = ref('v2.1'); const tournamentScope = ref(ScopeType.USER); +const perPage = ref(100); -// Debug mode - enabled to see what's happening +// Debug mode const debugMode = ref(true); // Collapsible section states @@ -294,249 +284,56 @@ const apiKeyCollapsed = ref(false); const oauthCollapsed = ref(false); const clientCredsCollapsed = ref(false); -// Make apiKey reactive -const apiKey = computed(() => getApiKey()); +// Initialize Challonge Client (replaces ~100 lines of inline client creation) +const { + client, + apiVersion: clientApiVersion, + tournamentScope: clientTournamentScope, + maskedApiKey: clientMaskedApiKey, + authType +} = useChallongeClient( + apiKey, + apiVersion, + tournamentScope, + { + oauthToken: accessToken, + oauthAuthenticated: isAuthenticated, + clientCredsToken: clientCredsToken, + clientCredsAuthenticated: isClientCredsAuthenticated + }, + debugMode +); -const maskedApiKey = computed(() => { - if (!apiKey.value) return ''; - return apiKey.value.slice(0, 4) + '•••••••' + apiKey.value.slice(-4); +// Initialize Tournament Tests (replaces ~200 lines of tournament logic) +const { + tournaments, + loading, + loadingMore, + error, + searchQuery, + expandedTournamentId, + currentPage, + hasNextPage, + tournamentDetails, + paginationInfo, + filteredTournaments, + testListTournaments, + loadMoreTournaments, + changePerPage, + toggleTournamentDetails, + resetState, + tournamentDetailsState +} = useChallongeTests(client, apiVersion, tournamentScope); + +// Update perPage when selector changes +watch(perPage, (newValue) => { + changePerPage(newValue); }); -// Tournament details computed from async state -const tournamentDetails = computed(() => tournamentDetailsState.data.value); - -// Create API client reactively based on version, key, and OAuth status -const client = computed(() => { - if (apiVersion.value === 'v1') { - // v1 only supports API key - if (!apiKey.value) return null; - return createChallongeV1Client(apiKey.value); - } else { - // v2.1 supports OAuth, client credentials, and API key - // Smart priority based on scope selection: - // - APPLICATION scope: prefer client credentials (they're designed for this) - // - USER scope: prefer OAuth user tokens or API key (client creds don't work well here) - - if (tournamentScope.value === ScopeType.APPLICATION) { - // APPLICATION scope - prefer client credentials - if (isClientCredsAuthenticated.value && clientCredsToken.value) { - console.log('🔐 Using Client Credentials token for APPLICATION scope'); - return createChallongeV2Client( - { token: clientCredsToken.value, type: AuthType.OAUTH }, - { debug: debugMode.value } - ); - } else if (isAuthenticated.value && accessToken.value) { - console.log('🔐 Using OAuth user token for APPLICATION scope'); - return createChallongeV2Client( - { token: accessToken.value, type: AuthType.OAUTH }, - { debug: debugMode.value } - ); - } - } else { - // USER scope - prefer OAuth user tokens or API key (client creds may not work) - if (isAuthenticated.value && accessToken.value) { - console.log('🔐 Using OAuth user token for USER scope'); - return createChallongeV2Client( - { token: accessToken.value, type: AuthType.OAUTH }, - { debug: debugMode.value } - ); - } else if (apiKey.value) { - console.log('🔑 Using API Key for USER scope'); - return createChallongeV2Client( - { token: apiKey.value, type: AuthType.API_KEY }, - { debug: debugMode.value } - ); - } - } - - // Fallback: try API key - if (apiKey.value) { - console.log('🔑 Using API Key (fallback)'); - return createChallongeV2Client( - { token: apiKey.value, type: AuthType.API_KEY }, - { debug: debugMode.value } - ); - } - - return null; - } -}); - -// Pagination info -const paginationInfo = computed(() => { - if (!tournaments.value) return ''; - const start = (currentPage.value - 1) * perPage.value + 1; - const end = Math.min( - start + tournaments.value.length - 1, - totalTournaments.value || tournaments.value.length - ); - const total = totalTournaments.value || tournaments.value.length; - return `Showing ${start}-${end} of ${total}`; -}); - -// Filter tournaments (client-side for now, can be moved to server-side) -const filteredTournaments = computed(() => { - if (!tournaments.value) return null; - if (!searchQuery.value.trim()) return tournaments.value; - - const query = searchQuery.value.toLowerCase(); - return tournaments.value.filter(t => { - const name = getTournamentName(t).toLowerCase(); - return name.includes(query); - }); -}); - -// Helper to get tournament name (handles both v1 and v2.1 response structures) -function getTournamentName(tournament) { - return tournament.tournament?.name || tournament.name || ''; -} - -// Helper to get tournament ID -function getTournamentId(tournament) { - return tournament.tournament?.id || tournament.id; -} - -// Helper to get tournament property -function getTournamentProp(tournament, prop) { - return tournament.tournament?.[prop] || tournament[prop]; -} - -async function testListTournaments(resetPagination = true) { - if (resetPagination) { - currentPage.value = 1; - searchQuery.value = ''; - expandedTournamentId.value = null; - tournamentDetailsState.reset(); - } - - await tournamentListState.execute(async () => { - if (apiVersion.value === 'v1') { - // v1 doesn't support pagination - const result = await client.value.tournaments.list(); - totalTournaments.value = result.length; - hasNextPage.value = false; - return result; - } else { - // v2.1 - Query all tournament states (pending, in_progress, ended) in parallel - // USER scope returns tournaments you have access to: - // - Tournaments you created - // - Tournaments where you're added as an admin - const result = await queryAllTournaments(client.value, { - page: currentPage.value, - per_page: 100, - scopeType: tournamentScope.value - }); - - console.log('📊 Tournament API Response (All States):', { - page: currentPage.value, - perPage: 100, - scope: tournamentScope.value, - scopeRaw: tournamentScope.value, - ScopeType_USER: ScopeType.USER, - ScopeType_APPLICATION: ScopeType.APPLICATION, - states: [ - 'pending', - 'checking_in', - 'checked_in', - 'accepting_predictions', - 'group_stages_underway', - 'group_stages_finalized', - 'underway', - 'awaiting_review', - 'complete' - ], - resultsCount: result.length, - isOAuthAuthenticated: isAuthenticated.value, - isClientCredsAuthenticated: isClientCredsAuthenticated.value, - authType: isClientCredsAuthenticated.value - ? 'Client Credentials' - : isAuthenticated.value - ? 'OAuth' - : 'API Key', - results: result - }); - - totalTournaments.value = result.length; - hasNextPage.value = result.length >= 100; - return result; - } - }); -} - -async function loadMoreTournaments() { - if (apiVersion.value === 'v1') return; // v1 doesn't support pagination - - currentPage.value++; - - const result = await loadMoreState.execute(async () => { - const newResults = await queryAllTournaments(client.value, { - page: currentPage.value, - per_page: 100, - scopeType: tournamentScope.value - }); - - hasNextPage.value = newResults.length === perPage.value; - return newResults; - }); - - if (result) { - // Append new results to existing tournaments - tournaments.value = [...tournaments.value, ...result]; - } else { - // Revert page increment on error - currentPage.value--; - } -} - -async function changePerPage(newLimit) { - perPage.value = newLimit; - await testListTournaments(true); -} - -async function toggleTournamentDetails(tournamentId) { - if (expandedTournamentId.value === tournamentId) { - expandedTournamentId.value = null; - tournamentDetailsState.reset(); - return; - } - - expandedTournamentId.value = tournamentId; - - await tournamentDetailsState.execute(async () => { - if (apiVersion.value === 'v1') { - return await client.value.tournaments.get(tournamentId, { - includeParticipants: true, - includeMatches: true - }); - } else { - // v2.1 get tournament - return await client.value.tournaments.get(tournamentId); - } - }); - - // Reset expanded state if there was an error - if (tournamentDetailsState.error.value) { - expandedTournamentId.value = null; - } -} - -async function switchApiVersion() { - // Clear state when switching versions - tournamentListState.reset(); - loadMoreState.reset(); - tournamentDetailsState.reset(); - searchQuery.value = ''; - expandedTournamentId.value = null; - currentPage.value = 1; -} - -function formatDate(dateString) { - if (!dateString) return ''; - return new Date(dateString).toLocaleString(); -} - // Watch for API version changes -watch(apiVersion, switchApiVersion); +watch(apiVersion, () => { + resetState(); +}); // Check for debug mode on mount onMounted(() => {