Compare commits
	
		
			4 Commits
		
	
	
		
			291e7ff8a7
			...
			04f30e3ac2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						04f30e3ac2
	
				 | 
					
					
						|||
| 
						
						
							
						
						9d0b5cb254
	
				 | 
					
					
						|||
| 
						
						
							
						
						db8a8b4b7b
	
				 | 
					
					
						|||
| 
						
						
							
						
						ac20baad23
	
				 | 
					
					
						
@@ -1,37 +1,85 @@
 | 
				
			|||||||
 | 
					import ChallengeCard from '@/components/ChallengeCard'
 | 
				
			||||||
import PenaltyBanner from '@/components/PenalyBanner'
 | 
					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 { FontAwesome6 } from '@expo/vector-icons'
 | 
				
			||||||
 | 
					import { useEffect, useMemo, useState } from 'react'
 | 
				
			||||||
import { View } from 'react-native'
 | 
					import { View } from 'react-native'
 | 
				
			||||||
import { Appbar, Button, Surface, Text } from 'react-native-paper'
 | 
					import { ActivityIndicator, Appbar, Banner, FAB, MD3Colors, Surface, Text, TouchableRipple } from 'react-native-paper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function ChallengesScreen() {
 | 
					function ChallengeScreenHeader() {
 | 
				
			||||||
  return (
 | 
					  return <>
 | 
				
			||||||
    <Surface style={{ flex: 1 }}>
 | 
					 | 
				
			||||||
    <Appbar.Header>
 | 
					    <Appbar.Header>
 | 
				
			||||||
      <Appbar.Content title={"Défis"} />
 | 
					      <Appbar.Content title={"Défis"} />
 | 
				
			||||||
      <Appbar.Action icon='format-list-bulleted' />
 | 
					      <Appbar.Action icon='format-list-bulleted' />
 | 
				
			||||||
    </Appbar.Header>
 | 
					    </Appbar.Header>
 | 
				
			||||||
    <PenaltyBanner />
 | 
					    <PenaltyBanner />
 | 
				
			||||||
      <Surface elevation={2} style={{ flex: 1, margin: 20, borderRadius: 20 }}>
 | 
					  </>
 | 
				
			||||||
        <View style={{ padding: 10 }}>
 | 
					}
 | 
				
			||||||
          <Text variant='headlineMedium' style={{ textAlign: 'center' }}>Titre</Text>
 | 
					
 | 
				
			||||||
 | 
					function ChallengeScreenBody() {
 | 
				
			||||||
 | 
					  const auth = useAuth()
 | 
				
			||||||
 | 
					  const game = useGame()
 | 
				
			||||||
 | 
					  const challengeActions = useChallengeActions()
 | 
				
			||||||
 | 
					  const challenges = useChallenges()
 | 
				
			||||||
 | 
					  const currentChallengeAction = useMemo(() => {
 | 
				
			||||||
 | 
					    if (!game.activeChallengeId)
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    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 && <ChallengeCard challenge={currentChallenge} />}
 | 
				
			||||||
 | 
					    {!currentChallenge && game.currentRunner && <>
 | 
				
			||||||
 | 
					      <Banner
 | 
				
			||||||
 | 
					          visible={!currentChallenge && game.currentRunner && !loading}
 | 
				
			||||||
 | 
					          icon='cancel'
 | 
				
			||||||
 | 
					          style={{ backgroundColor: MD3Colors.error40 }}>
 | 
				
			||||||
 | 
					        <Text variant='titleMedium' style={{ textAlign: 'center' }}>Aucun défi en cours.</Text>
 | 
				
			||||||
 | 
					      </Banner>
 | 
				
			||||||
 | 
					      <View style={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}>
 | 
				
			||||||
 | 
					        <FAB
 | 
				
			||||||
 | 
					            label='Tirer un défi'
 | 
				
			||||||
 | 
					            icon='cards'
 | 
				
			||||||
 | 
					            disabled={drawRandomChallengeMutation.isPending}
 | 
				
			||||||
 | 
					            visible={!currentChallenge && game.currentRunner && !loading}
 | 
				
			||||||
 | 
					            onPress={() => drawRandomChallengeMutation.mutate()}
 | 
				
			||||||
 | 
					            variant='tertiary'
 | 
				
			||||||
 | 
					            customSize={64} />
 | 
				
			||||||
 | 
					        {loading && <ActivityIndicator size={'large'} />}
 | 
				
			||||||
      </View>
 | 
					      </View>
 | 
				
			||||||
        <View style={{ flexGrow: 1 }}>
 | 
					    </>}
 | 
				
			||||||
          <Surface elevation={5} mode='flat' style={{ flexGrow: 1, padding: 15 }}>
 | 
					    <Banner
 | 
				
			||||||
            <Text variant='bodyLarge' style={{ flexGrow: 1 }}>Description</Text>
 | 
					        visible={game.gameStarted && !game.currentRunner}
 | 
				
			||||||
            <Text variant='titleMedium'>
 | 
					        icon={({ size }) => <FontAwesome6 name='cat' size={size} color={'pink'} />}
 | 
				
			||||||
              Récompense : 500 <FontAwesome6 name='coins' />
 | 
					        style={{ backgroundColor: MD3Colors.secondary30 }}>
 | 
				
			||||||
            </Text>
 | 
					      Vous êtes poursuiveuse, et n'avez donc pas de défi à accomplir.
 | 
				
			||||||
          </Surface>
 | 
					    </Banner>
 | 
				
			||||||
        </View>
 | 
					  </>
 | 
				
			||||||
        <View style={{ flexWrap: 'wrap', flexDirection: 'row', justifyContent: 'space-around', padding: 15 }}>
 | 
					}
 | 
				
			||||||
          <Button mode='outlined' icon='cancel'>
 | 
					
 | 
				
			||||||
            Passer
 | 
					export default function ChallengesScreen() {
 | 
				
			||||||
          </Button>
 | 
					  return (
 | 
				
			||||||
          <Button mode='contained' icon='check'>
 | 
					    <Surface style={{ flex: 1 }}>
 | 
				
			||||||
            Terminer
 | 
					      <ChallengeScreenHeader />
 | 
				
			||||||
          </Button>
 | 
					      <ChallengeScreenBody />
 | 
				
			||||||
        </View>
 | 
					 | 
				
			||||||
      </Surface>
 | 
					 | 
				
			||||||
    </Surface>
 | 
					    </Surface>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								client/components/ChallengeCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								client/components/ChallengeCard.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import { Challenge } from "@/utils/features/challenges/challengesSlice"
 | 
				
			||||||
 | 
					import { FontAwesome6 } from "@expo/vector-icons"
 | 
				
			||||||
 | 
					import { View } from "react-native"
 | 
				
			||||||
 | 
					import { Button, Surface, Text } from "react-native-paper"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function ChallengeCard({ challenge }: { challenge: Challenge }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Surface elevation={2} style={{ flex: 1, margin: 20, borderRadius: 20 }}>
 | 
				
			||||||
 | 
					    <View style={{ padding: 10 }}>
 | 
				
			||||||
 | 
					      <Text variant='headlineMedium' style={{ textAlign: 'center' }}>{challenge.title}</Text>
 | 
				
			||||||
 | 
					    </View>
 | 
				
			||||||
 | 
					    <View style={{ flexGrow: 1 }}>
 | 
				
			||||||
 | 
					      <Surface elevation={5} mode='flat' style={{ flexGrow: 1, padding: 15 }}>
 | 
				
			||||||
 | 
					        <Text variant='bodyLarge' style={{ flexGrow: 1 }}>{challenge.description}</Text>
 | 
				
			||||||
 | 
					        <Text variant='titleMedium'>
 | 
				
			||||||
 | 
					          Récompense : {challenge.reward} <FontAwesome6 name='coins' />
 | 
				
			||||||
 | 
					        </Text>
 | 
				
			||||||
 | 
					      </Surface>
 | 
				
			||||||
 | 
					    </View>
 | 
				
			||||||
 | 
					    <View style={{ flexWrap: 'wrap', flexDirection: 'row', justifyContent: 'space-around', padding: 15 }}>
 | 
				
			||||||
 | 
					      <Button mode='outlined' icon='cancel'>
 | 
				
			||||||
 | 
					        Passer
 | 
				
			||||||
 | 
					      </Button>
 | 
				
			||||||
 | 
					      <Button mode='contained' icon='check'>
 | 
				
			||||||
 | 
					        Terminer
 | 
				
			||||||
 | 
					      </Button>
 | 
				
			||||||
 | 
					    </View>
 | 
				
			||||||
 | 
					  </Surface>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,11 @@
 | 
				
			|||||||
import { useAuth } from '@/hooks/useAuth'
 | 
					import { useAuth } from '@/hooks/useAuth'
 | 
				
			||||||
import { useGame, useUpdateGameState, useUpdateMoney } from '@/hooks/useGame'
 | 
					import { useDownloadChallengeActions } from '@/hooks/useChallengeActions'
 | 
				
			||||||
 | 
					import { useDownloadChallenges } from '@/hooks/useChallenges'
 | 
				
			||||||
 | 
					import { useGame, useUpdateActiveChallengeId, useUpdateGameState, useUpdateMoney } from '@/hooks/useGame'
 | 
				
			||||||
import { useDownloadTrains } from '@/hooks/useTrain'
 | 
					import { useDownloadTrains } from '@/hooks/useTrain'
 | 
				
			||||||
import { isAuthValid } from '@/utils/features/auth/authSlice'
 | 
					import { isAuthValid } from '@/utils/features/auth/authSlice'
 | 
				
			||||||
 | 
					import { ChallengeActionPayload } from '@/utils/features/challengeActions/challengeActionsSlice'
 | 
				
			||||||
 | 
					import { Challenge } from '@/utils/features/challenges/challengesSlice'
 | 
				
			||||||
import { useQuery } from '@tanstack/react-query'
 | 
					import { useQuery } from '@tanstack/react-query'
 | 
				
			||||||
import { ReactNode, useEffect } from 'react'
 | 
					import { ReactNode, useEffect } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,7 +14,10 @@ export default function GameProvider({ children }: { children: ReactNode }) {
 | 
				
			|||||||
  const game = useGame()
 | 
					  const game = useGame()
 | 
				
			||||||
  const updateGameState = useUpdateGameState()
 | 
					  const updateGameState = useUpdateGameState()
 | 
				
			||||||
  const updateMoney = useUpdateMoney()
 | 
					  const updateMoney = useUpdateMoney()
 | 
				
			||||||
 | 
					  const updateActiveChallengeId = useUpdateActiveChallengeId()
 | 
				
			||||||
  const downloadTrains = useDownloadTrains()
 | 
					  const downloadTrains = useDownloadTrains()
 | 
				
			||||||
 | 
					  const downloadChallenges = useDownloadChallenges()
 | 
				
			||||||
 | 
					  const downloadChallengeActions = useDownloadChallengeActions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const gameQuery = useQuery({
 | 
					  const gameQuery = useQuery({
 | 
				
			||||||
    queryKey: ['get-game', auth.token],
 | 
					    queryKey: ['get-game', auth.token],
 | 
				
			||||||
@@ -34,8 +41,10 @@ export default function GameProvider({ children }: { children: ReactNode }) {
 | 
				
			|||||||
    refetchInterval: 5000,
 | 
					    refetchInterval: 5000,
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (playerQuery.isSuccess && playerQuery.data)
 | 
					    if (playerQuery.isSuccess && playerQuery.data) {
 | 
				
			||||||
      updateMoney(playerQuery.data.money)
 | 
					      updateMoney(playerQuery.data.money)
 | 
				
			||||||
 | 
					      updateActiveChallengeId(playerQuery.data.activeChallengeId)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }, [playerQuery.status, playerQuery.dataUpdatedAt])
 | 
					  }, [playerQuery.status, playerQuery.dataUpdatedAt])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const trainsQuery = useQuery({
 | 
					  const trainsQuery = useQuery({
 | 
				
			||||||
@@ -47,10 +56,27 @@ export default function GameProvider({ children }: { children: ReactNode }) {
 | 
				
			|||||||
    refetchInterval: 5000,
 | 
					    refetchInterval: 5000,
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (trainsQuery.isSuccess && trainsQuery.data && trainsQuery)
 | 
					    if (trainsQuery.isSuccess && trainsQuery.data)
 | 
				
			||||||
      downloadTrains(trainsQuery.data)
 | 
					      downloadTrains(trainsQuery.data)
 | 
				
			||||||
  }, [trainsQuery.status, trainsQuery.dataUpdatedAt])
 | 
					  }, [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 <>
 | 
					  return <>
 | 
				
			||||||
    {children}
 | 
					    {children}
 | 
				
			||||||
  </>
 | 
					  </>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										46
									
								
								client/hooks/mutations/useChallengeMutation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								client/hooks/mutations/useChallengeMutation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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 })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 useChallengeActions = () => useAppSelector((state) => state.challengeActions)
 | 
				
			||||||
 | 
					export const useDownloadChallengeActions = () => {
 | 
				
			||||||
 | 
					  const dispath = useAppDispatch()
 | 
				
			||||||
 | 
					  return (challengesData: ChallengeActionsPayload) => dispath(downloadChallengeActions(challengesData))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,8 @@
 | 
				
			|||||||
import { useAppSelector } from "./useStore"
 | 
					import { ChallengesPayload, downloadChallenges } from "@/utils/features/challenges/challengesSlice"
 | 
				
			||||||
 | 
					import { useAppDispatch, useAppSelector } from "./useStore"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useTrain = () => useAppSelector((state) => state.challenges)
 | 
					export const useChallenges = () => useAppSelector((state) => state.challenges)
 | 
				
			||||||
 | 
					export const useDownloadChallenges = () => {
 | 
				
			||||||
 | 
					  const dispath = useAppDispatch()
 | 
				
			||||||
 | 
					  return (challengesData: ChallengesPayload) => dispath(downloadChallenges(challengesData))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { useAppDispatch, useAppSelector } from "./useStore"
 | 
					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 useGame = () => useAppSelector((state) => state.game)
 | 
				
			||||||
export const useSetPlayerId = () => {
 | 
					export const useSetPlayerId = () => {
 | 
				
			||||||
@@ -10,6 +10,10 @@ export const useUpdateMoney = () => {
 | 
				
			|||||||
    const dispatch = useAppDispatch()
 | 
					    const dispatch = useAppDispatch()
 | 
				
			||||||
    return (money: number) => dispatch(updateMoney(money))
 | 
					    return (money: number) => dispatch(updateMoney(money))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export const useUpdateActiveChallengeId = () => {
 | 
				
			||||||
 | 
					    const dispatch = useAppDispatch()
 | 
				
			||||||
 | 
					    return (challengeActionId: number) => dispatch(updateActiveChallengeId(challengeActionId))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
export const useUpdateGameState = () => {
 | 
					export const useUpdateGameState = () => {
 | 
				
			||||||
    const dispatch = useAppDispatch()
 | 
					    const dispatch = useAppDispatch()
 | 
				
			||||||
    return (game: GamePayload) => dispatch(updateGameState(game))
 | 
					    return (game: GamePayload) => dispatch(updateGameState(game))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@
 | 
				
			|||||||
    "@expo/vector-icons": "^14.0.2",
 | 
					    "@expo/vector-icons": "^14.0.2",
 | 
				
			||||||
    "@maplibre/maplibre-react-native": "^10.0.0-alpha.28",
 | 
					    "@maplibre/maplibre-react-native": "^10.0.0-alpha.28",
 | 
				
			||||||
    "@pchmn/expo-material3-theme": "github:pchmn/expo-material3-theme",
 | 
					    "@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/bottom-tabs": "^7.0.0",
 | 
				
			||||||
    "@react-navigation/native": "^7.0.0",
 | 
					    "@react-navigation/native": "^7.0.0",
 | 
				
			||||||
    "@reduxjs/toolkit": "^2.4.0",
 | 
					    "@reduxjs/toolkit": "^2.4.0",
 | 
				
			||||||
@@ -58,8 +59,7 @@
 | 
				
			|||||||
    "react-native-screens": "~4.1.0",
 | 
					    "react-native-screens": "~4.1.0",
 | 
				
			||||||
    "react-native-web": "~0.19.13",
 | 
					    "react-native-web": "~0.19.13",
 | 
				
			||||||
    "react-native-webview": "13.12.2",
 | 
					    "react-native-webview": "13.12.2",
 | 
				
			||||||
    "react-redux": "^9.1.2",
 | 
					    "react-redux": "^9.1.2"
 | 
				
			||||||
    "@react-native-async-storage/async-storage": "1.23.1"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@babel/core": "^7.25.2",
 | 
					    "@babel/core": "^7.25.2",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,9 @@
 | 
				
			|||||||
import { createSlice } from '@reduxjs/toolkit'
 | 
					import { createSlice, PayloadAction } from '@reduxjs/toolkit'
 | 
				
			||||||
 | 
					import { Challenge } from '../challenges/challengesSlice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ChallengeAction {
 | 
					export interface ChallengeAction {
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
  challengeId: number
 | 
					  challengeId: number
 | 
				
			||||||
  title: string,
 | 
					 | 
				
			||||||
  description: string,
 | 
					 | 
				
			||||||
  reward: number,
 | 
					 | 
				
			||||||
  success: boolean,
 | 
					  success: boolean,
 | 
				
			||||||
  start: number,  // date
 | 
					  start: number,  // date
 | 
				
			||||||
  end: number | null,  // date
 | 
					  end: number | null,  // date
 | 
				
			||||||
@@ -21,13 +19,45 @@ const initialState: ActionsState = {
 | 
				
			|||||||
  challengeActions: []
 | 
					  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({
 | 
					export const challengeActionsSlice = createSlice({
 | 
				
			||||||
  name: 'challengeActions',
 | 
					  name: 'challengeActions',
 | 
				
			||||||
  initialState: initialState,
 | 
					  initialState: initialState,
 | 
				
			||||||
  reducers: {
 | 
					  reducers: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    downloadChallengeActions(state, action: PayloadAction<ChallengeActionsPayload>) {
 | 
				
			||||||
 | 
					      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
 | 
					export default challengeActionsSlice.reducer
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
					export interface Challenge {
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
@@ -15,13 +17,31 @@ const initialState: ChallengesState = {
 | 
				
			|||||||
  challenges: []
 | 
					  challenges: []
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ChallengesPayload {
 | 
				
			||||||
 | 
					  data: (Challenge & { action: ChallengeAction | null })[]
 | 
				
			||||||
 | 
					  meta: PaginationMeta
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const challengesSlice = createSlice({
 | 
					export const challengesSlice = createSlice({
 | 
				
			||||||
  name: 'challenges',
 | 
					  name: 'challenges',
 | 
				
			||||||
  initialState: initialState,
 | 
					  initialState: initialState,
 | 
				
			||||||
  reducers: {
 | 
					  reducers: {
 | 
				
			||||||
 | 
					    downloadChallenges(state, action: PayloadAction<ChallengesPayload>) {
 | 
				
			||||||
 | 
					      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
 | 
					export default challengesSlice.reducer
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								client/utils/features/common.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/utils/features/common.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					export interface PaginationMeta {
 | 
				
			||||||
 | 
					  currentPage: number
 | 
				
			||||||
 | 
					  lastPage: number
 | 
				
			||||||
 | 
					  nextPage: number
 | 
				
			||||||
 | 
					  prevPage: number
 | 
				
			||||||
 | 
					  total: number
 | 
				
			||||||
 | 
					  totalPerPage: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
 | 
					import { createSlice, PayloadAction } from '@reduxjs/toolkit'
 | 
				
			||||||
 | 
					import { ChallengeAction } from '../challengeActions/challengeActionsSlice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface RunPayload {
 | 
					export interface RunPayload {
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
@@ -20,9 +21,10 @@ export interface GameState {
 | 
				
			|||||||
  gameStarted: boolean
 | 
					  gameStarted: boolean
 | 
				
			||||||
  money: number
 | 
					  money: number
 | 
				
			||||||
  currentRunner: boolean
 | 
					  currentRunner: boolean
 | 
				
			||||||
 | 
					  activeChallengeId: number | null
 | 
				
			||||||
  chaseFreeTime: number | null  // date
 | 
					  chaseFreeTime: number | null  // date
 | 
				
			||||||
  penaltyStart: number | null // date
 | 
					  penaltyStart: number | null // date
 | 
				
			||||||
  penaltyEnd: number | null  // date
 | 
					  penaltyEnd: number | null  //date
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialState: GameState = {
 | 
					const initialState: GameState = {
 | 
				
			||||||
@@ -30,6 +32,7 @@ const initialState: GameState = {
 | 
				
			|||||||
  gameStarted: false,
 | 
					  gameStarted: false,
 | 
				
			||||||
  money: 0,
 | 
					  money: 0,
 | 
				
			||||||
  currentRunner: false,
 | 
					  currentRunner: false,
 | 
				
			||||||
 | 
					  activeChallengeId: null,
 | 
				
			||||||
  chaseFreeTime: null,
 | 
					  chaseFreeTime: null,
 | 
				
			||||||
  penaltyStart: null,
 | 
					  penaltyStart: null,
 | 
				
			||||||
  penaltyEnd: null,
 | 
					  penaltyEnd: null,
 | 
				
			||||||
@@ -45,6 +48,9 @@ export const gameSlice = createSlice({
 | 
				
			|||||||
    updateMoney: (state, action: PayloadAction<number>) => {
 | 
					    updateMoney: (state, action: PayloadAction<number>) => {
 | 
				
			||||||
      state.money = action.payload
 | 
					      state.money = action.payload
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    updateActiveChallengeId: (state, action: PayloadAction<number | null>) => {
 | 
				
			||||||
 | 
					      state.activeChallengeId = action.payload
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    updateGameState: (state, action: PayloadAction<GamePayload>) => {
 | 
					    updateGameState: (state, action: PayloadAction<GamePayload>) => {
 | 
				
			||||||
      const game: GamePayload = action.payload
 | 
					      const game: GamePayload = action.payload
 | 
				
			||||||
      state.gameStarted = game.started
 | 
					      state.gameStarted = game.started
 | 
				
			||||||
@@ -57,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
 | 
					export default gameSlice.reducer
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
 | 
					import { createSlice, PayloadAction } from '@reduxjs/toolkit'
 | 
				
			||||||
 | 
					import { PaginationMeta } from '../common'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface InterrailTime {
 | 
					export interface InterrailTime {
 | 
				
			||||||
  hours: number
 | 
					  hours: number
 | 
				
			||||||
@@ -73,15 +74,6 @@ const initialState: TrainsState = {
 | 
				
			|||||||
  trains: []
 | 
					  trains: []
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PaginationMeta {
 | 
					 | 
				
			||||||
  currentPage: number
 | 
					 | 
				
			||||||
  lastPage: number
 | 
					 | 
				
			||||||
  nextPage: number
 | 
					 | 
				
			||||||
  prevPage: number
 | 
					 | 
				
			||||||
  total: number
 | 
					 | 
				
			||||||
  totalPerPage: number
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface TrainsPayload {
 | 
					export interface TrainsPayload {
 | 
				
			||||||
  data: TrainTrip[]
 | 
					  data: TrainTrip[]
 | 
				
			||||||
  meta: PaginationMeta
 | 
					  meta: PaginationMeta
 | 
				
			||||||
@@ -105,8 +97,8 @@ export const trainSlice = createSlice({
 | 
				
			|||||||
          arrivalTime: dlTrain.arrivalTime,
 | 
					          arrivalTime: dlTrain.arrivalTime,
 | 
				
			||||||
          info: info,
 | 
					          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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					  Warnings:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - You are about to drop the column `active` on the `ChallengeAction` table. All the data in the column will be lost.
 | 
				
			||||||
 | 
					  - A unique constraint covering the columns `[activeChallengeId]` on the table `Player` will be added. If there are existing duplicate values, this will fail.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					-- AlterTable
 | 
				
			||||||
 | 
					ALTER TABLE "ChallengeAction" DROP COLUMN "active";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- AlterTable
 | 
				
			||||||
 | 
					ALTER TABLE "Player" ADD COLUMN     "activeChallengeId" INTEGER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- CreateIndex
 | 
				
			||||||
 | 
					CREATE UNIQUE INDEX "Player_activeChallengeId_key" ON "Player"("activeChallengeId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- AddForeignKey
 | 
				
			||||||
 | 
					ALTER TABLE "Player" ADD CONSTRAINT "Player_activeChallengeId_fkey" FOREIGN KEY ("activeChallengeId") REFERENCES "ChallengeAction"("id") ON DELETE SET NULL ON UPDATE CASCADE;
 | 
				
			||||||
@@ -12,6 +12,8 @@ model Player {
 | 
				
			|||||||
  name              String            @unique
 | 
					  name              String            @unique
 | 
				
			||||||
  password          String
 | 
					  password          String
 | 
				
			||||||
  money             Int               @default(0)
 | 
					  money             Int               @default(0)
 | 
				
			||||||
 | 
					  activeChallenge   ChallengeAction?  @relation("ActiveChallenge", fields: [activeChallengeId], references: [id])
 | 
				
			||||||
 | 
					  activeChallengeId Int?              @unique
 | 
				
			||||||
  actions           ChallengeAction[]
 | 
					  actions           ChallengeAction[]
 | 
				
			||||||
  geolocations      Geolocation[]
 | 
					  geolocations      Geolocation[]
 | 
				
			||||||
  moneyUpdates      MoneyUpdate[]
 | 
					  moneyUpdates      MoneyUpdate[]
 | 
				
			||||||
@@ -68,7 +70,6 @@ model ChallengeAction {
 | 
				
			|||||||
  playerId     Int
 | 
					  playerId     Int
 | 
				
			||||||
  challenge    Challenge    @relation(fields: [challengeId], references: [id])
 | 
					  challenge    Challenge    @relation(fields: [challengeId], references: [id])
 | 
				
			||||||
  challengeId  Int          @unique
 | 
					  challengeId  Int          @unique
 | 
				
			||||||
  active       Boolean      @default(false)
 | 
					 | 
				
			||||||
  success      Boolean      @default(false)
 | 
					  success      Boolean      @default(false)
 | 
				
			||||||
  start        DateTime     @default(now()) @db.Timestamptz(3)
 | 
					  start        DateTime     @default(now()) @db.Timestamptz(3)
 | 
				
			||||||
  end          DateTime?    @db.Timestamptz(3)
 | 
					  end          DateTime?    @db.Timestamptz(3)
 | 
				
			||||||
@@ -76,6 +77,7 @@ model ChallengeAction {
 | 
				
			|||||||
  penaltyEnd   DateTime?    @db.Timestamptz(3)
 | 
					  penaltyEnd   DateTime?    @db.Timestamptz(3)
 | 
				
			||||||
  run          PlayerRun    @relation(fields: [runId], references: [id])
 | 
					  run          PlayerRun    @relation(fields: [runId], references: [id])
 | 
				
			||||||
  runId        Int
 | 
					  runId        Int
 | 
				
			||||||
 | 
					  activePlayer Player?      @relation("ActiveChallenge")
 | 
				
			||||||
  moneyUpdate  MoneyUpdate?
 | 
					  moneyUpdate  MoneyUpdate?
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,11 +20,15 @@ export class ChallengeActionsService {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async findAll(queryPagination: QueryPaginationDto, filterChallengeActions: FilterChallengeActionsDto): Promise<[ChallengeAction[], number]> {
 | 
					  async findAll(queryPagination: QueryPaginationDto, { playerId, challengeId, success }: FilterChallengeActionsDto): Promise<[ChallengeAction[], number]> {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
      await this.prisma.challengeAction.findMany({
 | 
					      await this.prisma.challengeAction.findMany({
 | 
				
			||||||
        ...paginate(queryPagination),
 | 
					        ...paginate(queryPagination),
 | 
				
			||||||
        where: filterChallengeActions,
 | 
					        where: {
 | 
				
			||||||
 | 
					          playerId: playerId,
 | 
				
			||||||
 | 
					          challengeId: challengeId,
 | 
				
			||||||
 | 
					          success: success,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      await this.prisma.challengeAction.count(),
 | 
					      await this.prisma.challengeAction.count(),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
@@ -54,20 +58,14 @@ export class ChallengeActionsService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async endCurrentChallenge(player: Player, success: boolean): Promise<ChallengeAction> {
 | 
					  async endCurrentChallenge(player: Player, success: boolean): Promise<ChallengeAction> {
 | 
				
			||||||
    const challengeAction = await this.prisma.challengeAction.findFirst({
 | 
					    if (!player.activeChallengeId)
 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        playerId: player.id,
 | 
					 | 
				
			||||||
        active: true,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    if (!challengeAction)
 | 
					 | 
				
			||||||
      throw new BadRequestException("Aucun défi n'est en cours")
 | 
					      throw new BadRequestException("Aucun défi n'est en cours")
 | 
				
			||||||
 | 
					    const challengeAction = await this.prisma.challengeAction.findUnique({ where: { id: player.activeChallengeId } })
 | 
				
			||||||
    let data
 | 
					    let data
 | 
				
			||||||
    const now = new Date()
 | 
					    const now = new Date()
 | 
				
			||||||
    if  (success) {
 | 
					    if  (success) {
 | 
				
			||||||
      data = {
 | 
					      data = {
 | 
				
			||||||
        success: success,
 | 
					        success: success,
 | 
				
			||||||
        active: false,
 | 
					 | 
				
			||||||
        end: now,
 | 
					        end: now,
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,12 +83,15 @@ export class ChallengeActionsService {
 | 
				
			|||||||
    else {
 | 
					    else {
 | 
				
			||||||
      data = {
 | 
					      data = {
 | 
				
			||||||
        success: success,
 | 
					        success: success,
 | 
				
			||||||
        active: false,
 | 
					 | 
				
			||||||
        end: now,
 | 
					        end: now,
 | 
				
			||||||
        penaltyStart: now,
 | 
					        penaltyStart: now,
 | 
				
			||||||
        penaltyEnd: new Date(now.getTime() + Constants.PENALTY_TIME * 60 * 1000),
 | 
					        penaltyEnd: new Date(now.getTime() + Constants.PENALTY_TIME * 60 * 1000),
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    await this.prisma.player.update({
 | 
				
			||||||
 | 
					      where: { id: player.id },
 | 
				
			||||||
 | 
					      data: { activeChallengeId: null },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    return await this.prisma.challengeAction.update({
 | 
					    return await this.prisma.challengeAction.update({
 | 
				
			||||||
      where: {
 | 
					      where: {
 | 
				
			||||||
        id: challengeAction.id,
 | 
					        id: challengeAction.id,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,12 +9,6 @@ export class CreateChallengeActionDto {
 | 
				
			|||||||
  @ApiProperty({ description: "Identifiant du défi rattaché à l'action" })
 | 
					  @ApiProperty({ description: "Identifiant du défi rattaché à l'action" })
 | 
				
			||||||
  challengeId: number
 | 
					  challengeId: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @BooleanTransform()
 | 
					 | 
				
			||||||
  @ApiProperty({ description: "Est-ce que le défi est actuellement en train d'être réalisé", default: true })
 | 
					 | 
				
			||||||
  active: boolean = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  @IsBoolean()
 | 
					  @IsBoolean()
 | 
				
			||||||
  @BooleanTransform()
 | 
					  @BooleanTransform()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,12 +16,6 @@ export class FilterChallengeActionsDto {
 | 
				
			|||||||
  @ApiProperty({ description: "Identifiant du défi attaché à cette action", required: false })
 | 
					  @ApiProperty({ description: "Identifiant du défi attaché à cette action", required: false })
 | 
				
			||||||
  challengeId?: number
 | 
					  challengeId?: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsOptional()
 | 
					 | 
				
			||||||
  @IsBoolean()
 | 
					 | 
				
			||||||
  @BooleanTransform()
 | 
					 | 
				
			||||||
  @ApiProperty({ description: "Défi en train d'être accompli", required: false })
 | 
					 | 
				
			||||||
  active?: boolean
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  @IsBoolean()
 | 
					  @IsBoolean()
 | 
				
			||||||
  @BooleanTransform()
 | 
					  @BooleanTransform()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,11 +22,6 @@ export class ChallengeActionEntity implements ChallengeAction {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  challengeId: number
 | 
					  challengeId: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Est-ce que le défi est actuellement en train d'être réalisé
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  active: boolean
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Est-ce que le défi a été réussi
 | 
					   * Est-ce que le défi a été réussi
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,13 +64,7 @@ export class ChallengesService {
 | 
				
			|||||||
    const game = await this.prisma.game.findUnique({ where: { id: 1 }, include: { currentRun: true } })
 | 
					    const game = await this.prisma.game.findUnique({ where: { id: 1 }, include: { currentRun: true } })
 | 
				
			||||||
    if (game.currentRun?.runnerId !== player.id)
 | 
					    if (game.currentRun?.runnerId !== player.id)
 | 
				
			||||||
      throw new ConflictException("Vous n'êtes pas en course, ce n'est pas à vous de tirer un défi.")
 | 
					      throw new ConflictException("Vous n'êtes pas en course, ce n'est pas à vous de tirer un défi.")
 | 
				
			||||||
    const currentChallengeAction = await this.prisma.challengeAction.findFirst({
 | 
					    if (player.activeChallengeId)
 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        playerId: player.id,
 | 
					 | 
				
			||||||
        active: true,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    if (currentChallengeAction)
 | 
					 | 
				
			||||||
      throw new ConflictException("Un défi est déjà en cours d'accomplissement")
 | 
					      throw new ConflictException("Un défi est déjà en cours d'accomplissement")
 | 
				
			||||||
    const remaningChallenges = await this.prisma.challenge.count({
 | 
					    const remaningChallenges = await this.prisma.challenge.count({
 | 
				
			||||||
      where: {
 | 
					      where: {
 | 
				
			||||||
@@ -92,10 +86,13 @@ export class ChallengesService {
 | 
				
			|||||||
        playerId: player.id,
 | 
					        playerId: player.id,
 | 
				
			||||||
        challengeId: challenge.id,
 | 
					        challengeId: challenge.id,
 | 
				
			||||||
        runId: game.currentRunId,
 | 
					        runId: game.currentRunId,
 | 
				
			||||||
        active: true,
 | 
					 | 
				
			||||||
        success: false,
 | 
					        success: false,
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    await this.prisma.player.update({
 | 
				
			||||||
 | 
					      where: { id: player.id },
 | 
				
			||||||
 | 
					      data: { activeChallengeId: action.id },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
    challengeEntity.action = action
 | 
					    challengeEntity.action = action
 | 
				
			||||||
    return challengeEntity
 | 
					    return challengeEntity
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,17 +58,17 @@ export class GameService {
 | 
				
			|||||||
      throw new ConflictException("La partie n'a pas encore démarré.")
 | 
					      throw new ConflictException("La partie n'a pas encore démarré.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Clôture de l'éventuel défi en cours, qui n'a alors pas été réussi
 | 
					    // Clôture de l'éventuel défi en cours, qui n'a alors pas été réussi
 | 
				
			||||||
    await this.prisma.challengeAction.updateMany({
 | 
					    const currentRunner = await this.prisma.player.findUnique({ where: { id: game.currentRun.runnerId } })
 | 
				
			||||||
      where: {
 | 
					    if (currentRunner.activeChallengeId) {
 | 
				
			||||||
        playerId: game.currentRun.runnerId,
 | 
					      await this.prisma.challengeAction.update({
 | 
				
			||||||
        runId: game.currentRunId,
 | 
					        where: { id: currentRunner.activeChallengeId },
 | 
				
			||||||
        active: true,
 | 
					        data: { success: false },
 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      data: {
 | 
					 | 
				
			||||||
        active: false,
 | 
					 | 
				
			||||||
        success: false,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					      await this.prisma.player.update({
 | 
				
			||||||
 | 
					        where: { id: currentRunner.id },
 | 
				
			||||||
 | 
					        data: { activeChallengeId: null },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.prisma.playerRun.update({
 | 
					    await this.prisma.playerRun.update({
 | 
				
			||||||
      where: { id: game.currentRunId },
 | 
					      where: { id: game.currentRunId },
 | 
				
			||||||
@@ -173,7 +173,7 @@ export class GameService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const orpanChallengeMoneyUpdates = await this.prisma.moneyUpdate.findMany({ where: { reason: MoneyUpdateType.WIN_CHALLENGE, actionId: null } })
 | 
					    const orpanChallengeMoneyUpdates = await this.prisma.moneyUpdate.findMany({ where: { reason: MoneyUpdateType.WIN_CHALLENGE, actionId: null } })
 | 
				
			||||||
    await this.prisma.moneyUpdate.deleteMany({ where: { reason: MoneyUpdateType.WIN_CHALLENGE, actionId: null } })
 | 
					    await this.prisma.moneyUpdate.deleteMany({ where: { reason: MoneyUpdateType.WIN_CHALLENGE, actionId: null } })
 | 
				
			||||||
    deleted.push(...orpanTrainMoneyUpdates)
 | 
					    deleted.push(...orpanChallengeMoneyUpdates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return { added: added, deleted: deleted }
 | 
					    return { added: added, deleted: deleted }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,37 @@
 | 
				
			|||||||
import { ApiProperty } from "@nestjs/swagger"
 | 
					import { ApiProperty } from "@nestjs/swagger"
 | 
				
			||||||
import { Player } from "@prisma/client"
 | 
					import { Player } from "@prisma/client"
 | 
				
			||||||
import { Exclude } from 'class-transformer'
 | 
					import { Exclude } from 'class-transformer'
 | 
				
			||||||
 | 
					import { IsOptional } from "class-validator"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PlayerEntity implements Player {
 | 
					export class PlayerEntity implements Player {
 | 
				
			||||||
  constructor(partial: Partial<PlayerEntity>) {
 | 
					  constructor(partial: Partial<PlayerEntity>) {
 | 
				
			||||||
    Object.assign(this, partial)
 | 
					    Object.assign(this, partial)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ApiProperty({description: "Identifiant unique"})
 | 
					  /**
 | 
				
			||||||
 | 
					   * Identifiant unique
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ApiProperty({description: "Nom de læ joueur⋅se"})
 | 
					  /**
 | 
				
			||||||
 | 
					   * Nom de læ joueur⋅se
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  name: string
 | 
					  name: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Mot de passe hashé
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  @Exclude()
 | 
					  @Exclude()
 | 
				
			||||||
  password: string
 | 
					  password: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ApiProperty({description: "Nombre de jetons dont dispose actuellement læ joueur⋅se"})
 | 
					  /**
 | 
				
			||||||
 | 
					   * Nombre de jetons dont dispose actuellement læ joueur⋅se
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  money: number
 | 
					  money: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Identifiant du défi en cours d'accomplissement
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
					  activeChallengeId: number | null
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user