From 04f30e3ac2a6c432fc60d1be024f23199044beb6 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Thu, 12 Dec 2024 22:55:59 +0100 Subject: [PATCH] =?UTF-8?q?R=C3=A9cup=C3=A9ration=20de=20d=C3=A9fis=20et?= =?UTF-8?q?=20tirage=20d'un=20nouveau=20d=C3=A9fi=20via=20des=20boutons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/app/(tabs)/challenges.tsx | 32 ++++++++++--- client/components/GameProvider.tsx | 32 +++++++++++-- .../hooks/mutations/useChallengeMutation.ts | 46 +++++++++++++++++++ client/hooks/useChallengeActions.ts | 7 ++- client/hooks/useChallenges.ts | 7 ++- client/hooks/useGame.ts | 6 ++- client/package.json | 4 +- .../challengeActions/challengeActionsSlice.ts | 40 ++++++++++++++-- .../features/challenges/challengesSlice.ts | 24 +++++++++- client/utils/features/common.ts | 8 ++++ client/utils/features/game/gameSlice.ts | 9 ++-- client/utils/features/train/trainSlice.ts | 12 +---- 12 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 client/hooks/mutations/useChallengeMutation.ts create mode 100644 client/utils/features/common.ts diff --git a/client/app/(tabs)/challenges.tsx b/client/app/(tabs)/challenges.tsx index dafc085..647aea7 100644 --- a/client/app/(tabs)/challenges.tsx +++ b/client/app/(tabs)/challenges.tsx @@ -1,12 +1,14 @@ import ChallengeCard from '@/components/ChallengeCard' import PenaltyBanner from '@/components/PenalyBanner' +import { useDrawRandomChallengeMutation } from '@/hooks/mutations/useChallengeMutation' +import { useAuth } from '@/hooks/useAuth' import { useChallengeActions } from '@/hooks/useChallengeActions' import { useChallenges } from '@/hooks/useChallenges' import { useGame } from '@/hooks/useGame' import { FontAwesome6 } from '@expo/vector-icons' -import { useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' import { View } from 'react-native' -import { Appbar, Banner, FAB, MD3Colors, Surface, Text } from 'react-native-paper' +import { ActivityIndicator, Appbar, Banner, FAB, MD3Colors, Surface, Text, TouchableRipple } from 'react-native-paper' function ChallengeScreenHeader() { return <> @@ -19,31 +21,49 @@ function ChallengeScreenHeader() { } function ChallengeScreenBody() { + const auth = useAuth() const game = useGame() const challengeActions = useChallengeActions() const challenges = useChallenges() const currentChallengeAction = useMemo(() => { - if (!game.currentChallengeId) + if (!game.activeChallengeId) return null - return challengeActions.challengeActions.find((action) => action.id === game.currentChallengeId) + return challengeActions.challengeActions.find((action) => action.id === game.activeChallengeId) }, [game, challengeActions]) const currentChallenge = useMemo(() => { if (!currentChallengeAction) return null return challenges.challenges.find((challenge) => challenge.id === currentChallengeAction.challengeId) }, [currentChallengeAction, challenges]) + const [loading, setLoading] = useState(false) + const drawRandomChallengeMutation = useDrawRandomChallengeMutation({ + auth, + onPostSuccess: () => setLoading(true), + }) + useEffect(() => { + if (challengeActions) + setLoading(false) + }, [challengeActions]) return <> {currentChallenge && } {!currentChallenge && game.currentRunner && <> Aucun défi en cours. - + drawRandomChallengeMutation.mutate()} + variant='tertiary' + customSize={64} /> + {loading && } } { - if (playerQuery.isSuccess && playerQuery.data) + if (playerQuery.isSuccess && playerQuery.data) { updateMoney(playerQuery.data.money) + updateActiveChallengeId(playerQuery.data.activeChallengeId) + } }, [playerQuery.status, playerQuery.dataUpdatedAt]) const trainsQuery = useQuery({ @@ -47,10 +56,27 @@ export default function GameProvider({ children }: { children: ReactNode }) { refetchInterval: 5000, }) useEffect(() => { - if (trainsQuery.isSuccess && trainsQuery.data && trainsQuery) + if (trainsQuery.isSuccess && trainsQuery.data) downloadTrains(trainsQuery.data) }, [trainsQuery.status, trainsQuery.dataUpdatedAt]) + const challengesQuery = useQuery({ + queryKey: ['get-challenges', game.playerId, auth.token], + queryFn: () => fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/challenges/?size=10000`, { + headers: { "Authorization": `Bearer ${auth.token}` }} + ).then(resp => resp.json()), + enabled: isAuthValid(auth) && !!game.playerId, + refetchInterval: 5000, + }) + useEffect(() => { + if (challengesQuery.isSuccess && challengesQuery.data) { + downloadChallenges(challengesQuery.data) + const dataWithPlayerActions = challengesQuery.data.data.filter( + (challenge: (Challenge & {action: ChallengeActionPayload | null})) => challenge.action !== null && challenge.action.playerId === game.playerId) + downloadChallengeActions({ data: dataWithPlayerActions }) + } + }, [challengesQuery.status, challengesQuery.dataUpdatedAt]) + return <> {children} diff --git a/client/hooks/mutations/useChallengeMutation.ts b/client/hooks/mutations/useChallengeMutation.ts new file mode 100644 index 0000000..e4259c2 --- /dev/null +++ b/client/hooks/mutations/useChallengeMutation.ts @@ -0,0 +1,46 @@ +import { AuthState } from "@/utils/features/auth/authSlice" +import { useMutation } from "@tanstack/react-query" + +type ErrorResponse = { + error: string + message: string + statusCode: number +} + +type onPostSuccessFunc = () => void +type ErrorFuncProps = { response?: ErrorResponse, error?: Error } +type onErrorFunc = (props: ErrorFuncProps) => void + +type ChallengeActionProps = { + auth: AuthState + onPostSuccess?: onPostSuccessFunc + onError?: onErrorFunc +} + +export const useDrawRandomChallengeMutation = ({ auth, onPostSuccess, onError }: ChallengeActionProps) => { + return useMutation({ + mutationFn: async () => { + return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/challenges/draw-random/`, { + method: "POST", + headers: { + "Authorization": `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, + }).then(resp => resp.json()) + }, + onSuccess: async (data) => { + if (data.statusCode) { + if (onError) + onError({ response: data }) + return + } + console.log(data) + if (onPostSuccess) + onPostSuccess() + }, + onError: async (error: Error) => { + if (onError) + onError({ error: error }) + } + }) +} diff --git a/client/hooks/useChallengeActions.ts b/client/hooks/useChallengeActions.ts index 44da90d..7c4e821 100644 --- a/client/hooks/useChallengeActions.ts +++ b/client/hooks/useChallengeActions.ts @@ -1,3 +1,8 @@ -import { useAppSelector } from "./useStore" +import { ChallengeActionsPayload, downloadChallengeActions } from "@/utils/features/challengeActions/challengeActionsSlice" +import { useAppDispatch, useAppSelector } from "./useStore" export const useChallengeActions = () => useAppSelector((state) => state.challengeActions) +export const useDownloadChallengeActions = () => { + const dispath = useAppDispatch() + return (challengesData: ChallengeActionsPayload) => dispath(downloadChallengeActions(challengesData)) +} diff --git a/client/hooks/useChallenges.ts b/client/hooks/useChallenges.ts index c4b6605..97ec33d 100644 --- a/client/hooks/useChallenges.ts +++ b/client/hooks/useChallenges.ts @@ -1,3 +1,8 @@ -import { useAppSelector } from "./useStore" +import { ChallengesPayload, downloadChallenges } from "@/utils/features/challenges/challengesSlice" +import { useAppDispatch, useAppSelector } from "./useStore" export const useChallenges = () => useAppSelector((state) => state.challenges) +export const useDownloadChallenges = () => { + const dispath = useAppDispatch() + return (challengesData: ChallengesPayload) => dispath(downloadChallenges(challengesData)) +} diff --git a/client/hooks/useGame.ts b/client/hooks/useGame.ts index 8b2799f..2e746ea 100644 --- a/client/hooks/useGame.ts +++ b/client/hooks/useGame.ts @@ -1,5 +1,5 @@ import { useAppDispatch, useAppSelector } from "./useStore" -import { GamePayload, setPlayerId, updateGameState, updateMoney } from "@/utils/features/game/gameSlice" +import { GamePayload, setPlayerId, updateActiveChallengeId, updateGameState, updateMoney } from "@/utils/features/game/gameSlice" export const useGame = () => useAppSelector((state) => state.game) export const useSetPlayerId = () => { @@ -10,6 +10,10 @@ export const useUpdateMoney = () => { const dispatch = useAppDispatch() return (money: number) => dispatch(updateMoney(money)) } +export const useUpdateActiveChallengeId = () => { + const dispatch = useAppDispatch() + return (challengeActionId: number) => dispatch(updateActiveChallengeId(challengeActionId)) +} export const useUpdateGameState = () => { const dispatch = useAppDispatch() return (game: GamePayload) => dispatch(updateGameState(game)) diff --git a/client/package.json b/client/package.json index df46b4e..f0ddffa 100644 --- a/client/package.json +++ b/client/package.json @@ -19,6 +19,7 @@ "@expo/vector-icons": "^14.0.2", "@maplibre/maplibre-react-native": "^10.0.0-alpha.28", "@pchmn/expo-material3-theme": "github:pchmn/expo-material3-theme", + "@react-native-async-storage/async-storage": "1.23.1", "@react-navigation/bottom-tabs": "^7.0.0", "@react-navigation/native": "^7.0.0", "@reduxjs/toolkit": "^2.4.0", @@ -58,8 +59,7 @@ "react-native-screens": "~4.1.0", "react-native-web": "~0.19.13", "react-native-webview": "13.12.2", - "react-redux": "^9.1.2", - "@react-native-async-storage/async-storage": "1.23.1" + "react-redux": "^9.1.2" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/client/utils/features/challengeActions/challengeActionsSlice.ts b/client/utils/features/challengeActions/challengeActionsSlice.ts index 35fa4c8..de5261a 100644 --- a/client/utils/features/challengeActions/challengeActionsSlice.ts +++ b/client/utils/features/challengeActions/challengeActionsSlice.ts @@ -1,11 +1,9 @@ -import { createSlice } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { Challenge } from '../challenges/challengesSlice' export interface ChallengeAction { id: number challengeId: number - title: string, - description: string, - reward: number, success: boolean, start: number, // date end: number | null, // date @@ -21,13 +19,45 @@ const initialState: ActionsState = { challengeActions: [] } +export interface ChallengeActionPayload { + id: number + playerId: number + challengeId: number + success: boolean, + start: string, + end: string | null, + penaltyStart: string | null, + penaltyEnd: string | null, +} + +export interface ChallengeActionsPayload { + data: (Challenge & { action: ChallengeActionPayload })[] +} + export const challengeActionsSlice = createSlice({ name: 'challengeActions', initialState: initialState, reducers: { + + downloadChallengeActions(state, action: PayloadAction) { + if (state.challengeActions) + state.challengeActions = state.challengeActions.filter(challengeAction => action.payload.data.filter(dlChallenge => dlChallenge.action.id === challengeAction.id) === null) + for (const dlChallenge of action.payload.data) { + state.challengeActions.push({ + id: dlChallenge.action.id, + challengeId: dlChallenge.id, + success: dlChallenge.action.success, + start: new Date(dlChallenge.action.start).getTime(), + end: dlChallenge.action.end ? new Date(dlChallenge.action.end).getTime() : null, + penaltyStart: dlChallenge.action.penaltyStart ? new Date(dlChallenge.action.penaltyStart).getTime() : null, + penaltyEnd: dlChallenge.action.penaltyEnd ? new Date(dlChallenge.action.penaltyEnd).getTime() : null, + }) + } + state.challengeActions.sort((c1, c2) => c2.id - c1.id) + }, }, }) -export const { } = challengeActionsSlice.actions +export const { downloadChallengeActions } = challengeActionsSlice.actions export default challengeActionsSlice.reducer diff --git a/client/utils/features/challenges/challengesSlice.ts b/client/utils/features/challenges/challengesSlice.ts index 95a4e33..06e91a8 100644 --- a/client/utils/features/challenges/challengesSlice.ts +++ b/client/utils/features/challenges/challengesSlice.ts @@ -1,4 +1,6 @@ -import { createSlice } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { PaginationMeta } from '../common' +import { ChallengeAction } from '../challengeActions/challengeActionsSlice' export interface Challenge { id: number @@ -15,13 +17,31 @@ const initialState: ChallengesState = { challenges: [] } +export interface ChallengesPayload { + data: (Challenge & { action: ChallengeAction | null })[] + meta: PaginationMeta +} + export const challengesSlice = createSlice({ name: 'challenges', initialState: initialState, reducers: { + downloadChallenges(state, action: PayloadAction) { + if (state.challenges) + state.challenges = state.challenges.filter(challenge => action.payload.data.filter(dlChallenge => dlChallenge.id === challenge.id) === null) + for (const dlChallenge of action.payload.data) { + state.challenges.push({ + id: dlChallenge.id, + title: dlChallenge.title, + description: dlChallenge.description, + reward: dlChallenge.reward, + }) + } + state.challenges.sort((c1, c2) => c2.id - c1.id) + }, }, }) -export const { } = challengesSlice.actions +export const { downloadChallenges } = challengesSlice.actions export default challengesSlice.reducer diff --git a/client/utils/features/common.ts b/client/utils/features/common.ts new file mode 100644 index 0000000..d1c4805 --- /dev/null +++ b/client/utils/features/common.ts @@ -0,0 +1,8 @@ +export interface PaginationMeta { + currentPage: number + lastPage: number + nextPage: number + prevPage: number + total: number + totalPerPage: number +} diff --git a/client/utils/features/game/gameSlice.ts b/client/utils/features/game/gameSlice.ts index bd0531c..39711a6 100644 --- a/client/utils/features/game/gameSlice.ts +++ b/client/utils/features/game/gameSlice.ts @@ -21,7 +21,7 @@ export interface GameState { gameStarted: boolean money: number currentRunner: boolean - currentChallengeId: number | null + activeChallengeId: number | null chaseFreeTime: number | null // date penaltyStart: number | null // date penaltyEnd: number | null //date @@ -32,7 +32,7 @@ const initialState: GameState = { gameStarted: false, money: 0, currentRunner: false, - currentChallengeId: null, + activeChallengeId: null, chaseFreeTime: null, penaltyStart: null, penaltyEnd: null, @@ -48,6 +48,9 @@ export const gameSlice = createSlice({ updateMoney: (state, action: PayloadAction) => { state.money = action.payload }, + updateActiveChallengeId: (state, action: PayloadAction) => { + state.activeChallengeId = action.payload + }, updateGameState: (state, action: PayloadAction) => { const game: GamePayload = action.payload state.gameStarted = game.started @@ -60,6 +63,6 @@ export const gameSlice = createSlice({ }, }) -export const { setPlayerId, updateMoney, updateGameState } = gameSlice.actions +export const { setPlayerId, updateMoney, updateActiveChallengeId, updateGameState } = gameSlice.actions export default gameSlice.reducer diff --git a/client/utils/features/train/trainSlice.ts b/client/utils/features/train/trainSlice.ts index e29b11b..0a7f03d 100644 --- a/client/utils/features/train/trainSlice.ts +++ b/client/utils/features/train/trainSlice.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { PaginationMeta } from '../common' export interface InterrailTime { hours: number @@ -73,15 +74,6 @@ const initialState: TrainsState = { trains: [] } -export interface PaginationMeta { - currentPage: number - lastPage: number - nextPage: number - prevPage: number - total: number - totalPerPage: number -} - export interface TrainsPayload { data: TrainTrip[] meta: PaginationMeta @@ -105,8 +97,8 @@ export const trainSlice = createSlice({ arrivalTime: dlTrain.arrivalTime, info: info, }) - state.trains.sort((t1, t2) => t1.departureTime > t2.departureTime ? -1 : t1.departureTime == t2.arrivalTime ? 0 : 1) } + state.trains.sort((t1, t2) => t1.departureTime > t2.departureTime ? -1 : t1.departureTime == t2.arrivalTime ? 0 : 1) } }, })