Newer
Older
import useExploration from 'components/Hooks/useExploration'
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 { setAnalysisMonth } from 'store/analysis/analysis.slice'
import {
setChallengeConsumption,
setUserChallengeList,
updateUserChallengeList,
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>(
/** 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(
(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(
(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
Sentry.startSpan({ name: 'Initialize app' }, async () => {
const initializationService = new InitializationService(
client,
setInitStepErrors
const customPopupService = new CustomPopupService(client)
const partnersInfoService = new PartnersInfoService(client)
const ms = new MigrationService(client, setInitStepErrors)
try {
// Run init steps in parallel
setInitStep(InitSteps.PROFILE)
termsStatus,
,
profile,
profileType,
profileEcogesture,
fluidStatus,
initializationService.initConsent(),
initializationService.initFluidPrices(),
initializationService.initProfile(),
initializationService.initProfileType(),
initializationService.initProfileEcogesture(),
initializationService.initFluidStatus(),
if (subscribed) dispatch(updateTermsStatus(termsStatus))
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
setInitStep(InitSteps.MIGRATION)
const migrationsResult = await ms.runMigrations(migrations)
// Init last release notes when they exist
dispatch(
showReleaseNotes({
notes: migrationsResult.notes,
redirectLink: migrationsResult.redirectLink,
show: migrationsResult.show,
})
)
if (subscribed && profile) {
setValidExploration(UserExplorationID.EXPLORATION007)
setInitStep(InitSteps.CHALLENGES)
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) {
await loadProfileType(profileType)
}
if (profileEcogesture) {
dispatch(setProfileEcogesture(profileEcogesture))
}
dispatch(toggleAnalysisNotification(!profile.haveSeenLastAnalysis))
// Process fluids status
if (subscribed) {
dispatch(setFluidStatus(fluidStatus))
let lastDataDate = DateTime.fromISO('0001-01-01')
for (const fluid of fluidStatus) {
if (fluid.lastDataDate && fluid.lastDataDate > lastDataDate) {
lastDataDate = fluid.lastDataDate
}
}
// Init Challenge
const userChallengeList =
await initializationService.initUserChallenges(fluidStatus)
if (subscribed) {
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
) {
dispatch(toggleChallengeExplorationNotification(true))
// Set action to notification if action is accomplished
if (
filteredCurrentOngoingChallenge[0]?.action.state ===
UserActionState.ONGOING
) {
const actionService = new ActionService(client)
const updatedUserChallenge = await actionService.isActionDone(
filteredCurrentOngoingChallenge[0]
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
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
)
if (
filteredCurrentDuelChallenge[0]?.duel.state ===
UserDuelState.ONGOING
) {
const { updatedUserChallenge, dataloads } =
await initializationService.initDuelProgress(
filteredCurrentDuelChallenge[0]
)
if (subscribed) {
dispatch(
setChallengeConsumption({
userChallenge: updatedUserChallenge,
currentDataload: dataloads,
})
)
// Check is duel is done and display notification
const challengeService = new ChallengeService(client)
const { isDone } = challengeService.isChallengeDone(
updatedUserChallenge,
dataloads
)
dispatch(toggleChallengeDuelNotification(isDone))
}
/**
* Load custom popup and partners info synchronously so these treatments don't block the loading
*/
customPopupService.getCustomPopup().then(customPopup => {
processCustomPopup(profile, customPopup)
}
})
partnersInfoService.getPartnersInfo().then(async partnersInfo => {
if (profile && partnersInfo) {
await processFluidsStatus(profile, partnersInfo)
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)
logApp.error(`[Initialization] Error : ${error}`)
Sentry.captureException(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