Newer
Older
import useExploration from 'components/Hooks/useExploration'
UsageEventType,
UserActionState,
UserChallengeState,
UserDuelState,
import { DateTime } from 'luxon'
import { migrations } from 'migrations/migration.data'
import { MigrationService } from 'migrations/migration.service'
import {
CustomPopup,
InitSteps,
InitStepsErrors,
PartnersInfo,
Profile,
import React, { ReactNode, useCallback, useEffect, useState } from 'react'
import ActionService from 'services/action.service'
import ChallengeService from 'services/challenge.service'
import CustomPopupService from 'services/customPopup.service'
import FluidService from 'services/fluid.service'
import InitializationService from 'services/initialization.service'
import PartnersInfoService from 'services/partnersInfo.service'
import ProfileTypeEntityService from 'services/profileTypeEntity.service'
import UsageEventService from 'services/usageEvent.service'
import { setAnalysisMonth } from 'store/analysis/analysis.slice'
import {
setChallengeConsumption,
setUserChallengeList,
updateUserChallengeList,
import { setSelectedDate } from 'store/chart/chart.slice'
toggleAnalysisNotification,
toggleChallengeActionNotification,
toggleChallengeDuelNotification,
updateTermsStatus,
} from 'store/global/global.slice'
import { useAppDispatch } from 'store/hooks'
import { openPartnersModal, setCustomPopup } from 'store/modal/modal.slice'
import { updateProfile } from 'store/profile/profile.slice'
import { setProfileEcogesture } from 'store/profileEcogesture/profileEcogesture.slice'
import { setProfileType } from 'store/profileType/profileType.slice'
import logApp from 'utils/logger'
import { getTodayDate } from 'utils/utils'
import SplashScreen from './SplashScreen'
import SplashScreenError from './SplashScreenError'
interface SplashRootProps {
fadeTimer?: number
children: ReactNode
}
/**
* Added splash screen if data is not ready
* @param params {{ fadeTimer, splashComponent, children }}
*/
const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => {
const [{ splashEnd, splashStart }, setState] = useState({
splashEnd: false,
splashStart: false,
})
const [initStep, setInitStep] = useState<InitSteps>(InitSteps.MIGRATION)
const [initStepErrors, setInitStepErrors] = useState<InitStepsErrors | null>(
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/** Return current status of partner if modal has not been seen today */
const getPartnerStatus = useCallback(
(currentStatus: boolean, lastSeenDate: DateTime) => {
if (today !== lastSeenDate.toISO()) {
return currentStatus
}
return false
},
[today]
)
/** For each fluid, if notification is activated, set FluidStatus.maintenance to true */
const processFluidsStatus = useCallback(
async (profile: Profile, partnersInfo: PartnersInfo) => {
if (partnersInfo.notification_activated && !profile?.isFirstConnection) {
const fluidService = new FluidService(client)
const _updatedFluidStatus = await fluidService.getFluidStatus(
partnersInfo
)
dispatch(setFluidStatus(_updatedFluidStatus))
}
},
[client, dispatch]
)
/** Process customPopup and enable it if activated */
const processCustomPopup = useCallback(
async (profile: Profile, customPopup: CustomPopup) => {
try {
if (
today !== profile?.customPopupDate.toISO() &&
!profile?.isFirstConnection
) {
dispatch(setCustomPopup(customPopup))
}
} catch (error) {
console.error('Error while checking customPopup informations')
}
},
[dispatch, today]
)
/**
* For each fluid, set partnersIssue to true if notification is activated and seenDate < today
*/
const processPartnersStatus = useCallback(
async (profile: Profile, partnersInfo: PartnersInfo) => {
try {
if (
partnersInfo.notification_activated &&
!profile?.isFirstConnection
) {
const partnersIssue = {
enedis: getPartnerStatus(
partnersInfo.enedis_failure,
profile.partnersIssueSeenDate.enedis
),
egl: getPartnerStatus(
partnersInfo.egl_failure,
profile.partnersIssueSeenDate.egl
),
grdf: getPartnerStatus(
partnersInfo.grdf_failure,
profile.partnersIssueSeenDate.grdf
),
}
if (Object.values(partnersIssue).some(issue => issue)) {
dispatch(openPartnersModal(partnersIssue))
}
}
} catch (error) {
console.error('Error while fetching partners informations')
}
},
[dispatch, getPartnerStatus]
)
const loadProfileType = useCallback(
async (profileType: ProfileType) => {
const profileTypeEntityService = new ProfileTypeEntityService(client)
const updatedProfileType = await profileTypeEntityService.saveProfileType(
profileType
)
if (updatedProfileType) {
dispatch(setProfileType(updatedProfileType))
}
},
[client, dispatch]
)
useEffect(() => {
let timeoutSplash: NodeJS.Timeout
if (splashStart) {
timeoutSplash = setTimeout(() => {
setState(prev => ({ ...prev, splashEnd: true }))
}, fadeTimer)
}
return () => timeoutSplash && clearTimeout(timeoutSplash)
}, [splashStart, fadeTimer])
const [, setValidExploration] = useExploration()
useEffect(() => {
let subscribed = true
async function loadData() {
const initializationService = new InitializationService(
client,
const customPopupService = new CustomPopupService(client)
const partnersInfoService = new PartnersInfoService(client)
const ms = new MigrationService(client, setInitStepErrors)
const startTime = performance.now()
const transaction = Sentry.startTransaction({ name: 'Initialize app' })
const termsStatus = await initializationService.initConsent()
if (subscribed) dispatch(updateTermsStatus(termsStatus))
// Init fluidPrices
await initializationService.initFluidPrices()
// Init profile and update ecogestures, challenges, analysis
const profile = await initializationService.initProfile()
const profileType = await initializationService.initProfileType()
const profileEcogesture =
await initializationService.initProfileEcogesture()
const migrationsResult = await ms.runMigrations(migrations)
// Init last release notes when they exist
dispatch(
showReleaseNotes({
notes: migrationsResult.notes,
redirectLink: migrationsResult.redirectLink,
show: migrationsResult.show,
})
setValidExploration(UserExplorationID.EXPLORATION007)
const [
duelHash,
quizHash,
challengeHash,
explorationHash,
analysisResult,
] = await Promise.all([
initializationService.initDuelEntity(profile.duelHash),
initializationService.initQuizEntity(profile.quizHash),
initializationService.initExplorationEntity(profile.challengeHash),
initializationService.initChallengeEntity(profile.explorationHash),
initializationService.initAnalysis(profile),
])
const updatedProfile: Partial<Profile> = {
duelHash,
quizHash,
challengeHash,
explorationHash,
monthlyAnalysisDate: analysisResult.monthlyAnalysisDate,
haveSeenLastAnalysis: analysisResult.haveSeenLastAnalysis,
}
dispatch(updateProfile(updatedProfile))
dispatch(setAnalysisMonth(analysisResult.monthlyAnalysisDate))
if (profileType) {
dispatch(setProfileEcogesture(profileEcogesture))
dispatch(toggleAnalysisNotification(!profile.haveSeenLastAnalysis))
// Init Fluid status && lastDate for the chart
const fluidStatus = await initializationService.initFluidStatus()
if (subscribed) {
dispatch(setFluidStatus(fluidStatus))
const refDate = DateTime.fromISO('0001-01-01')
let lastDataDate: DateTime | null = DateTime.fromISO('0001-01-01')
for (const fluid of fluidStatus) {
if (fluid.lastDataDate && fluid.lastDataDate > lastDataDate) {
lastDataDate = fluid.lastDataDate
}
}
if (lastDataDate > refDate) {
dispatch(setSelectedDate(lastDataDate))
}
const userChallengeList =
await initializationService.initUserChallenges(fluidStatus)
dispatch(setUserChallengeList(userChallengeList))
const filteredCurrentOngoingChallenge = userChallengeList.filter(
challenge => challenge.state === UserChallengeState.ONGOING
)
// Set Notification if exploration state is notification
if (
filteredCurrentOngoingChallenge[0]?.exploration.state ===
UserExplorationState.NOTIFICATION
// Set action to notification if action is accomplished
filteredCurrentOngoingChallenge[0]?.action.state ===
UserActionState.ONGOING
const actionService = new ActionService(client)
const updatedUserChallenge: UserChallenge | null =
await actionService.isActionDone(
filteredCurrentOngoingChallenge[0]
)
if (updatedUserChallenge) {
dispatch(updateUserChallengeList(updatedUserChallenge))
}
// Set Notification if action state is notification
if (
filteredCurrentOngoingChallenge[0]?.action.state ===
UserActionState.NOTIFICATION
) {
dispatch(toggleChallengeActionNotification(true))
}
const filteredCurrentDuelChallenge = userChallengeList.filter(
challenge => challenge.state === UserChallengeState.DUEL
filteredCurrentDuelChallenge[0]?.duel.state ===
UserDuelState.ONGOING
const { updatedUserChallenge, dataloads } =
await initializationService.initDuelProgress(
filteredCurrentDuelChallenge[0]
)
dispatch(
setChallengeConsumption({
userChallenge: updatedUserChallenge,
currentDataload: dataloads,
})
)
// Check is duel is done and display notification
const challengeService = new ChallengeService(client)
const { isDone } = await challengeService.isChallengeDone(
updatedUserChallenge,
dataloads
)
if (profile) {
await UsageEventService.addEvent(client, {
type: UsageEventType.CONNECTION_EVENT,
result: profile.isFirstConnection ? 'firstConnection' : undefined,
context:
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
? 'mobile'
: 'desktop',
/**
* Load custom popup and partners info synchronously so these treatments don't block the loading
*/
customPopupService.getCustomPopup().then(async customPopup => {
if (profile && customPopup) {
await processCustomPopup(profile, customPopup)
}
})
partnersInfoService.getPartnersInfo().then(async partnersInfo => {
if (profile && partnersInfo) {
await processFluidsStatus(profile, partnersInfo)
await processPartnersStatus(profile, partnersInfo)
}
})
if (subscribed) {
logDuration('[Initialization] Finished successfully !', startTime)
setState(prev => ({
...prev,
splashStart: true,
}))
}
} catch (error: any) {
if (error.message === 'Failed to fetch' && !initStepErrors) {
setInitStepErrors(InitStepsErrors.UNKNOWN_ERROR)
if (!initStepErrors) loadData()
return () => {
subscribed = false
}
}, [
client,
dispatch,
initStepErrors,
processCustomPopup,
processFluidsStatus,
processPartnersStatus,
setValidExploration,
])
return (
<>
{!splashEnd && (
<div
style={{ transitionDuration: `${fadeTimer / 1000}s` }}
className={classNames('splash-root', {
['splash-fade']: splashStart,
})}
>
{!initStepErrors ? (
<SplashScreen initStep={initStep} />
) : (
<SplashScreenError error={initStepErrors} />
)}
</div>
)}
{splashStart && children}
</>
)
}
export default SplashRoot