Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ on:
pull_request:
branches: [develop, main]

# Caller workflow invokes reusable workflow (quality.yml).
# It must allow the permissions required by the called workflow checkout.
# Caller workflows that only invoke reusable jobs need an empty permissions block.
# The called workflow (quality.yml) declares its own token scope.
permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion locales/es.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ios": {
"CFBundleDisplayName": "jugaMUS",
"NSPhotoLibraryUsageDescription": "jugaMUS accede a tus fotos para que puedas elegir una imagen de perfil. La foto se sube a tu cuenta y la verán otros jugadores en las partidas y torneos en los que participes."
"NSPhotoLibraryUsageDescription": "jugaMUS accede a tus fotos para que puedas elegir una imagen de perfil. La foto se sube a tu cuenta y la verán otros jugadores en las partidas, torneos y ligas en los que participes."
}
}
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"react-hook-form": "^7.83.0",
"react-native": "0.81.5",
"react-native-chart-kit": "^6.12.2",
"react-native-confetti-cannon": "^1.5.2",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
Expand Down
4 changes: 4 additions & 0 deletions src/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function TabsLayout() {

return (
<Tabs
backBehavior="history"
screenOptions={{
headerShown: false,
lazy: true,
Expand Down Expand Up @@ -74,6 +75,9 @@ export default function TabsLayout() {
<Tabs.Screen name="tournaments/create" options={{ href: null }} />
<Tabs.Screen name="tournaments/[id]" options={{ href: null }} />
<Tabs.Screen name="tournaments/edit/[id]" options={{ href: null }} />
<Tabs.Screen name="leagues/create" options={{ href: null }} />
<Tabs.Screen name="leagues/[id]" options={{ href: null }} />
<Tabs.Screen name="leagues/edit/[id]" options={{ href: null }} />
<Tabs.Screen name="profile/index" options={{ title: 'Perfil', ...VISIBLE_TAB_OPTIONS }} />
<Tabs.Screen name="profile/[userId]" options={{ href: null }} />
<Tabs.Screen name="profile/edit" options={{ href: null }} />
Expand Down
142 changes: 122 additions & 20 deletions src/app/(tabs)/explore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,25 @@ import {
parseIsoToDate,
} from '@/components/ui/dateTimePickerUtils'
import { MunicipalityPicker } from '@/components/ui/MunicipalityPicker'
import { MATCH_STATUS, TOURNAMENT_STATUS, type ExploreContentType } from '@/constants'
import { useInfinitePublicMatches, usePublicTournamentsExplore } from '@/hooks/useMatches'
import {
LEAGUE_STATUS,
MATCH_STATUS,
TOURNAMENT_STATUS,
type ExploreContentType,
} from '@/constants'
import {
useInfinitePublicMatches,
usePublicLeaguesExplore,
usePublicTournamentsExplore,
} from '@/hooks/useMatches'
import type {
PublicMatchExplorerRow,
PublicMatchesListFilters,
VisibilityFilter,
} from '@/services/matches.service'
import type { LeagueRow, PublicLeaguesListFilters } from '@/services/leagues.service'
import type { PublicTournamentsListFilters, TournamentRow } from '@/services/tournaments.service'
import { leagueFormatDisplay, leagueStatusDisplay } from '@/utils/leagueDisplay'
import { Colors } from '@/theme/colors'
import { Fonts } from '@/theme/typography'
import { screenTopPadding } from '@/theme/layout'
Expand Down Expand Up @@ -97,6 +108,10 @@ function tournamentStatusTone(tournament: TournamentRow): StatusDotTone {
return 'upcoming'
}

function leagueStatusTone(league: LeagueRow): StatusDotTone {
return league.status === LEAGUE_STATUS.IN_PROGRESS ? 'active' : 'upcoming'
}

function ExploreMatchRow({ row, onPress }: { row: PublicMatchExplorerRow; onPress: () => void }) {
const tone = matchStatusTone(row.status)
return (
Expand Down Expand Up @@ -158,6 +173,37 @@ function ExploreTournamentRow({ row, onPress }: { row: TournamentRow; onPress: (
)
}

function ExploreLeagueRow({ row, onPress }: { row: LeagueRow; onPress: () => void }) {
const tone = leagueStatusTone(row)
return (
<Pressable
style={styles.row}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={`Liga: ${row.title}`}>
<StatusDot tone={tone} />
<View style={styles.rowBody}>
<Text style={styles.rowKind}>Liga · {leagueFormatDisplay(row.format)}</Text>
<Text style={styles.rowTitle} numberOfLines={2}>
{row.title}
</Text>
<Text style={styles.rowMeta}>
{formatCityAndPlace(row.city, row.place_defined, row.place_text)}
</Text>
</View>
<View style={styles.rowTrailing}>
<Text style={[styles.rowStatus, tone === 'active' && styles.rowStatusActive]}>
{leagueStatusDisplay(row).text}
</Text>
<Text style={styles.rowDate}>{formatDisplay(row.start_at)}</Text>
<Text style={styles.rowExtra}>
{row.status === LEAGUE_STATUS.REGISTRATION ? 'Sin iniciar' : 'Ver clasificación'}
</Text>
</View>
</Pressable>
)
}

export default function ExploreScreen() {
const router = useRouter()
const insets = useSafeAreaInsets()
Expand Down Expand Up @@ -213,10 +259,36 @@ export default function ExploreScreen() {
const {
data: tournaments,
isLoading: tournamentsLoading,
isError: tournamentsIsError,
error: tournamentsError,
isRefetching: tournamentsRefetching,
refetch: refetchTournaments,
} = usePublicTournamentsExplore(tournamentFilters)

const leagueFilters: PublicLeaguesListFilters = useMemo(
() => ({
search: filters.search,
city: filters.city,
status: filters.status,
hideCelebrated: filters.hideCelebrated,
startAfter: filters.startAfter,
startBefore: filters.startBefore,
minFreeSlots: filters.minFreeSlots,
contentType: filters.contentType,
visibility: filters.visibility ?? 'all',
}),
[filters]
)

const {
data: leagues,
isLoading: leaguesLoading,
isError: leaguesIsError,
error: leaguesError,
isRefetching: leaguesRefetching,
refetch: refetchLeagues,
} = usePublicLeaguesExplore(leagueFilters)

const matchRows = useMemo(() => data?.pages.flatMap((p) => p.rows) ?? [], [data?.pages])

const exploreItems = useMemo((): ExploreItem[] => {
Expand All @@ -232,16 +304,23 @@ export default function ExploreScreen() {
start_at: row.start_at,
row,
}))
const merged = [...matchItems, ...tournamentItems].sort(
const leagueItems: ExploreItem[] = (leagues ?? []).map((row) => ({
kind: 'league',
id: row.id,
start_at: row.start_at,
row,
}))
const merged = [...matchItems, ...tournamentItems, ...leagueItems].sort(
(a, b) => new Date(a.start_at).getTime() - new Date(b.start_at).getTime()
)
return filterExploreItemsForCelebrated(merged, filters.hideCelebrated)
}, [matchRows, tournaments, filters.hideCelebrated])
}, [matchRows, tournaments, leagues, filters.hideCelebrated])

const refetchAll = useCallback(() => {
void refetch()
void refetchTournaments()
}, [refetch, refetchTournaments])
void refetchLeagues()
}, [refetch, refetchTournaments, refetchLeagues])

const openFilterModal = useCallback(() => {
setDraftCity(filters.city)
Expand Down Expand Up @@ -323,7 +402,7 @@ export default function ExploreScreen() {

const listHeader = (
<View style={styles.listHeader}>
<ScreenHeader title="Descubrir" subtitle="Partidas y torneos" />
<ScreenHeader title="Descubrir" subtitle="Partidas, torneos y ligas" />

<TextInput
style={styles.search}
Expand Down Expand Up @@ -351,8 +430,9 @@ export default function ExploreScreen() {
</View>
)

const showMatches = filters.contentType !== 'tournaments'
const showTournaments = filters.contentType !== 'matches'
const showMatches = filters.contentType !== 'tournaments' && filters.contentType !== 'leagues'
const showTournaments = filters.contentType !== 'matches' && filters.contentType !== 'leagues'
const showLeagues = filters.contentType !== 'matches' && filters.contentType !== 'tournaments'

const listFooter = isFetchingNextPage ? (
<View style={styles.footerLoad}>
Expand All @@ -368,17 +448,27 @@ export default function ExploreScreen() {

if (
(showMatches && isLoading && !data) ||
(showTournaments && tournamentsLoading && !tournaments)
(showTournaments && tournamentsLoading && !tournaments) ||
(showLeagues && leaguesLoading && !leagues)
) {
return (
<View style={[styles.centered, { paddingTop: screenTopPadding(insets.top, 24) }]}>
<ActivityIndicator size="large" color={Colors.primary} />
<Text style={styles.loadingHint}>Cargando partidas y torneos…</Text>
<Text style={styles.loadingHint}>Cargando partidas, torneos y ligas…</Text>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</View>
)
}

if (isError) {
if (
(showMatches && isError) ||
(showTournaments && tournamentsIsError) ||
(showLeagues && leaguesIsError)
) {
const shownError =
(showMatches && isError ? error : null) ??
(showTournaments && tournamentsIsError ? tournamentsError : null) ??
(showLeagues && leaguesIsError ? leaguesError : null)

return (
<View
style={[
Expand All @@ -387,13 +477,15 @@ export default function ExploreScreen() {
]}>
<Text style={styles.errorTitle}>No se pudo cargar el listado</Text>
<Text style={styles.errorMsg}>
{error instanceof Error ? error.message : 'Error desconocido'}
{shownError instanceof Error ? shownError.message : 'Error desconocido'}
</Text>
<Text style={styles.errorHint}>
Si acabas de actualizar la app, aplica la migración Supabase `009_list_public_matches` en
tu proyecto.
</Text>
<Button title="Reintentar" onPress={() => void refetch()} style={{ marginTop: 16 }} />
{showMatches && isError ? (
<Text style={styles.errorHint}>
Si acabas de actualizar la app, aplica la migración Supabase `009_list_public_matches`
en tu proyecto.
</Text>
) : null}
<Button title="Reintentar" onPress={() => void refetchAll()} style={{ marginTop: 16 }} />
</View>
)
}
Expand All @@ -409,19 +501,26 @@ export default function ExploreScreen() {
row={item.row}
onPress={() => router.push(`/(tabs)/matches/${item.id}`)}
/>
) : (
) : item.kind === 'tournament' ? (
<ExploreTournamentRow
row={item.row}
onPress={() => router.push(`/(tabs)/tournaments/${item.id}`)}
/>
) : (
<ExploreLeagueRow
row={item.row}
onPress={() => router.push(`/(tabs)/leagues/${item.id}`)}
/>
)
}
ListHeaderComponent={listHeader}
ListFooterComponent={listFooter}
contentContainerStyle={[styles.listContent, { paddingBottom: insets.bottom + 88 }]}
onEndReached={onEndReached}
onEndReachedThreshold={0.35}
refreshing={(isRefetching && !isFetchingNextPage) || tournamentsRefetching}
refreshing={
(isRefetching && !isFetchingNextPage) || tournamentsRefetching || leaguesRefetching
}
onRefresh={refetchAll}
ListEmptyComponent={
<View style={styles.emptyWrap}>
Expand All @@ -431,7 +530,9 @@ export default function ExploreScreen() {
? 'No hay partidas que coincidan con tu búsqueda y filtros.'
: filters.contentType === 'tournaments'
? 'No hay torneos que coincidan con tu búsqueda y filtros.'
: 'No hay partidas ni torneos que coincidan con tu búsqueda y filtros.'}{' '}
: filters.contentType === 'leagues'
? 'No hay ligas que coincidan con tu búsqueda y filtros.'
: 'No hay partidas, torneos ni ligas que coincidan con tu búsqueda y filtros.'}{' '}
Prueba a ampliar fechas o quitar filtros.
</Text>
<Button
Expand Down Expand Up @@ -472,6 +573,7 @@ export default function ExploreScreen() {
{ id: 'all', label: 'Todo', value: 'all' as ExploreContentType },
{ id: 'm', label: 'Partidas', value: 'matches' as ExploreContentType },
{ id: 't', label: 'Torneos', value: 'tournaments' as ExploreContentType },
{ id: 'l', label: 'Ligas', value: 'leagues' as ExploreContentType },
] as const
).map((opt) => {
const selected = draftContentType === opt.value
Expand Down
12 changes: 10 additions & 2 deletions src/app/(tabs)/leaderboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
import { useRouter, type Href } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
Expand All @@ -15,11 +15,19 @@ export default function LeaderboardScreen() {
const [city, setCity] = useState('')
const { data, isPending, isError, refetch } = useLeaderboard(city || null)

const goBack = useCallback(() => {
if (router.canGoBack()) {
router.back()
return
}
router.replace('/(tabs)/profile' as Href)
}, [router])

return (
<View style={[styles.root, { paddingTop: screenTopPadding(insets.top) }]}>
<View style={styles.topBar}>
<Pressable
onPress={() => router.back()}
onPress={goBack}
accessibilityRole="button"
hitSlop={8}
style={styles.closeWrap}>
Expand Down
Loading
Loading