diff --git a/src/components/Challenge/AvailableChallengeDetailsView.tsx b/src/components/Challenge/AvailableChallengeDetailsView.tsx deleted file mode 100644 index 791bb3bf9126d4ab889cc6f3de2d0232b9f901ec..0000000000000000000000000000000000000000 --- a/src/components/Challenge/AvailableChallengeDetailsView.tsx +++ /dev/null @@ -1,279 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useClient } from 'cozy-client' -import { Redirect } from 'react-router-dom' -import { useRecoilState, useRecoilValue } from 'recoil' -import { DateTime } from 'luxon' -import { Location } from 'history' - -import { ScreenType } from 'enum/screen.enum' -import { ChallengeType, UserProfile } from 'models' -import { userProfileState } from 'atoms/userProfile.state' -import { screenTypeState } from 'atoms/screenType.state' -import { fluidTypeState } from 'atoms/fluidState.state' -import { currentChallengeState } from 'atoms/challenge.state' -import ChallengeService from 'services/challenge.service' -import ConsumptionService from 'services/consumption.service' -import UserProfileService from 'services/userProfile.service' -import { getLagDays } from 'utils/date' - -import CozyBar from 'components/Header/CozyBar' -import Header from 'components/Header/Header' -import Content from 'components/Content/Content' -import { history } from 'components/App' -import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner' -import StyledButtonValid from 'components/CommonKit/Button/StyledButtonValid' -import AvailableChallengeIcon from 'assets/png/badges/available-big.png' -import { FluidType } from 'enum/fluid.enum' -import warningWhite from 'assets/icons/ico/warning-white.svg' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' - -interface AvailableChallengeDetailsViewProps { - location: Location<ChallengeType> -} -interface LackOfFluidData { - fluidType: Array<number> - periode: { - endDate: DateTime - startDate: DateTime - } -} - -const AvailableChallengeDetailsView: React.FC<AvailableChallengeDetailsViewProps> = ({ - location, -}: AvailableChallengeDetailsViewProps) => { - const { t } = useI18n() - const client = useClient() - - const screenType = useRecoilValue(screenTypeState) - const fluidTypes = useRecoilValue(fluidTypeState) - const [userProfile, setUserProfile] = useRecoilState<UserProfile>( - userProfileState - ) - const [currentChallenge, setCurrentChallenge] = useRecoilState( - currentChallengeState - ) - - const challengeState = location.state - const [redirect, setRedirect] = useState(false) - const [challenge, setChallenge] = useState<ChallengeType | null>(null) - const [headerHeight, setHeaderHeight] = useState<number>(0) - const [lackOfDataForChallenge, setLackOfDataForChallenge] = useState<boolean>( - false - ) - const [noConnecteur, setNoConnecteur] = useState<boolean>(false) - const [lackOfDataFluidtype, setLackOfDataFluidtype] = useState< - LackOfFluidData - >({ - fluidType: [], - periode: {}, - }) - const [isLoaded, setIsLoaded] = useState<boolean>(false) - - const challengeService = new ChallengeService(client) - - const defineHeaderHeight = (height: number) => { - setHeaderHeight(height) - } - - const updateUserProfileNotification = useCallback( - async (ecogestureList: string[]) => { - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ notificationEcogesture: ecogestureList }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) - }) - }, - [setUserProfile] - ) - - async function startChallenge() { - if (challenge) { - const startedChallenge = await challengeService.startChallenge( - challenge, - fluidTypes, - challenge.availableEcogestures - ) - setCurrentChallenge(startedChallenge) - const updatedNotificationEcogestureList: string[] = userProfile.notificationEcogesture - ? [...userProfile.notificationEcogesture] - : [] - if (startedChallenge && updatedNotificationEcogestureList) { - updatedNotificationEcogestureList.push( - startedChallenge.selectedEcogestures[0].id - ) - updatedNotificationEcogestureList.push( - startedChallenge.selectedEcogestures[1].id - ) - await updateUserProfileNotification(updatedNotificationEcogestureList) - } - } - } - - async function handleStartClick() { - await startChallenge() - setRedirect(true) - } - - const renderRedirect = () => { - if (redirect) { - return ( - <Redirect - to={{ - pathname: '/challenges/ongoing', - state: currentChallenge, - }} - /> - ) - } - } - - useEffect(() => { - let subscribed = true - async function checkPreviousData() { - const lag = getLagDays(fluidTypes) - const firstDayOfPreviousPeriod = challengeState.duration.days - const timePeriod = { - startDate: DateTime.local() - .plus({ days: -firstDayOfPreviousPeriod - lag }) - .startOf('day'), - endDate: DateTime.local() - .plus({ days: -lag }) - .endOf('day'), - } - const consumptionService = new ConsumptionService(client) - const fetchedPerformanceIndicators = await consumptionService.getPerformanceIndicators( - timePeriod, - 20, - fluidTypes - ) - if (fetchedPerformanceIndicators.length !== 0 && subscribed) { - let i = 0 - const listFluid: Array<FluidType> = [] - fetchedPerformanceIndicators.forEach(element => { - if (!element.value) { - const idFluid = fluidTypes[i] - listFluid.push(idFluid) - //getFluidName - setLackOfDataForChallenge(true) - } - i += 1 - setLackOfDataFluidtype({ - fluidType: listFluid, - periode: timePeriod, - }) - }) - } else { - setLackOfDataForChallenge(true) - setNoConnecteur(true) - } - setIsLoaded(true) - } - if (challengeState && subscribed) { - checkPreviousData() - setChallenge(challengeState) - } - return () => { - subscribed = false - } - }, []) - return ( - <React.Fragment> - <CozyBar - titleKey={'COMMON.APP_NEW_CHALLENGE_TITLE'} - displayBackArrow={true} - /> - <Header - setHeaderHeight={defineHeaderHeight} - desktopTitleKey={'COMMON.APP_NEW_CHALLENGE_TITLE'} - displayBackArrow={true} - ></Header> - {!challengeState ? <Redirect to="/challenges" /> : null} - <Content height={headerHeight} background="var(--darkLight2)"> - {!challenge ? ( - <StyledSpinner /> - ) : ( - <> - <div className="cp-root --available"> - <div className="cp-content"> - <div className="cp-info --available"> - <div className="cp-title text-22-bold">{challenge.title}</div> - <img - className="cp-icon" - src={AvailableChallengeIcon} - width={screenType === ScreenType.MOBILE ? 180 : 300} - ></img> - <div className="cp-description text-16-bold"> - {challenge.description} - </div> - <div> - {lackOfDataForChallenge && isLoaded && !noConnecteur ? ( - <div className="lack-of-data-challenge"> - <div className="lack-of-data-content"> - <div className="warning-white"> - <StyledIcon - icon={warningWhite} - size={36} - className="warning-icon" - /> - <span> - {t('CHALLENGE.LACK_OF_DATA')}{' '} - {DateTime.fromObject( - lackOfDataFluidtype.periode.startDate.c - ).toLocaleString(DateTime.DATE_FULL)}{' '} - {t('CHALLENGE.LACK_OF_DATA_1')}{' '} - {DateTime.fromObject( - lackOfDataFluidtype.periode.endDate.c - ).toLocaleString(DateTime.DATE_FULL)} - {t('CHALLENGE.LACK_OF_DATA_2')} - </span> - </div> - {lackOfDataFluidtype.fluidType.map( - (idFLuid: number) => { - return ( - <div key={idFLuid} className="fluid-enum"> - {' '} - - {t( - 'FLUID.' + FluidType[idFLuid] + '.NAME' - )}{' '} - </div> - ) - } - )} - <span></span> - </div> - </div> - ) : null} - </div> - <div className="cp-valid"> - <div className="cp-left-button"> - <StyledButtonValid - color="secondary" - onClick={() => history.goBack()} - > - {t('CHALLENGE.NOT_NOW')} - </StyledButtonValid> - </div> - <div className="cp-right-button"> - <StyledButtonValid - disabled={lackOfDataForChallenge && isLoaded} - color="primary" - onClick={handleStartClick} - > - {t('CHALLENGE.START')} - {renderRedirect()} - </StyledButtonValid> - </div> - </div> - </div> - </div> - </div> - </> - )} - </Content> - </React.Fragment> - ) -} - -export default AvailableChallengeDetailsView diff --git a/src/components/Challenge/ChallengeCardLink.tsx b/src/components/Challenge/ChallengeCardLink.tsx deleted file mode 100644 index d023dd3c4806e42f30c20d4b7d21d0b44799b45c..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeCardLink.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useRecoilValue } from 'recoil' -import { currentChallengeState } from 'atoms/challenge.state' -import ChallengeCardLinkItem from 'components/Challenge/ChallengeCardLinkItem' - -const ChallengeCardLink: React.FC = () => { - const { t } = useI18n() - const currentChallenge = useRecoilValue(currentChallengeState) - return ( - <> - <div className="ccc-root"> - {currentChallenge && currentChallenge.challengeType && ( - <div className="ccc-content"> - <div className="ccc-header text-14-normal-uppercase"> - {t('COMMON.CHALLENGE_CARD_LABEL')} - </div> - <ChallengeCardLinkItem userChallenge={currentChallenge} /> - </div> - )} - </div> - </> - ) -} - -export default ChallengeCardLink diff --git a/src/components/Challenge/ChallengeCardLinkItem.tsx b/src/components/Challenge/ChallengeCardLinkItem.tsx deleted file mode 100644 index 650ab10de2098122e4d2b1aecc2f94e210d17ca9..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeCardLinkItem.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react' -import { NavLink } from 'react-router-dom' -import { useRecoilValue } from 'recoil' - -import { TypeChallenge } from 'enum/challenge.enum' -import { challengeNotificationState } from 'atoms/notification.state' -import { UserChallenge } from 'models' - -import StyledChallengeCard from 'components/CommonKit/Card/StyledChallengeCard' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import ChallengePile from 'components/Challenge/ChallengePile' -import ChallengeViewingDate from 'components/Challenge/ChallengeViewingDate' -import ChallengeTimeline from 'components/Challenge/ChallengeTimeline' -import BlackArrowIcon from 'assets/icons/ico/black-arrow.svg' - -interface ChallengeCardLinkItemProps { - userChallenge: UserChallenge -} - -const ChallengeCardLinkItem: React.FC<ChallengeCardLinkItemProps> = ({ - userChallenge, -}: ChallengeCardLinkItemProps) => { - const challengeNotification = useRecoilValue(challengeNotificationState) - const challengesPath = 'challenges' - const challengeDetailPath = { - pathname: `challenges/ongoing`, - state: userChallenge, - } - return ( - <NavLink - className="cc-link" - to={challengeNotification ? challengesPath : challengeDetailPath} - > - <div> - {userChallenge.challengeType && - userChallenge.challengeType.type === TypeChallenge.CHALLENGE ? ( - <StyledChallengeCard displayBorder={true}> - <div className="cc"> - <div className="cc-content-left"> - <div className="cc-content-title cc-content-title-padding text-18-normal"> - {userChallenge.challengeType.title} - </div> - <div className="cc-content-progress"> - <ChallengePile challenge={userChallenge} small={true} /> - </div> - <div className="cc-content-timeline"> - <ChallengeTimeline challenge={userChallenge} /> - </div> - <div className="cc-content-visu text-15-normal"> - <ChallengeViewingDate challenge={userChallenge} /> - </div> - </div> - <div className="cc-content-right"> - <StyledIcon icon={BlackArrowIcon} size={18} /> - </div> - </div> - </StyledChallengeCard> - ) : ( - <StyledChallengeCard displayBorder={challengeNotification}> - <div className="cc"> - <div className="cc-content-left"> - <div className="cc-content-title text-18-normal"> - {userChallenge.challengeType - ? userChallenge.challengeType.title - : null} - </div> - </div> - <div className="cc-content-right"> - {challengeNotification && ( - <div className="cc-notification text-16-bold">1</div> - )} - <StyledIcon icon={BlackArrowIcon} size={18} /> - </div> - </div> - </StyledChallengeCard> - )} - </div> - </NavLink> - ) -} - -export default ChallengeCardLinkItem diff --git a/src/components/Challenge/ChallengeList.tsx b/src/components/Challenge/ChallengeList.tsx deleted file mode 100644 index 03c0faf0832ba835b84695125058f3f77e620530..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeList.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useClient } from 'cozy-client' -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' - -import { ChallengeState } from 'enum/challenge.enum' -import { ScreenType } from 'enum/screen.enum' -import { fluidTypeState } from 'atoms/fluidState.state' -import { challengeNotificationState } from 'atoms/notification.state' -import { screenTypeState } from 'atoms/screenType.state' -import { currentChallengeState } from 'atoms/challenge.state' -import { userProfileState } from 'atoms/userProfile.state' -import { ChallengeType, UserChallenge } from 'models' -import ChallengeService from 'services/challenge.service' -import UserProfileService from 'services/userProfile.service' - -import StyledChallengeSpinner from 'components/CommonKit/Spinner/StyledChallengeSpinner' -import ChallengeListItem from 'components/Challenge/ChallengeListItem' -import ChallengeModal from 'components/Challenge/ChallengeModal' - -const ChallengeList: React.FC = () => { - const client = useClient() - const screenType = useRecoilValue(screenTypeState) - const fluidTypes = useRecoilValue(fluidTypeState) - const [userProfile, setUserProfile] = useRecoilState(userProfileState) - const [challengeNotification, setChallengeNotification] = useRecoilState( - challengeNotificationState - ) - const setCurrentChallenge = useSetRecoilState(currentChallengeState) - - const challengeService = new ChallengeService(client) - const userProfileService = new UserProfileService(client) - const [ - ongoingChallenge, - setOngoingChallenge, - ] = useState<UserChallenge | null>(null) - const [ - ongoingChallengeModal, - setOngoingChallengeModal, - ] = useState<UserChallenge | null>(null) - const [challengesType, setChallengesType] = useState<ChallengeType[] | null>( - null - ) - const [userChallenges, setUserChallenges] = useState<UserChallenge[] | null>( - null - ) - const [openChallengeModal, setOpenChallengeModal] = useState(false) - const [scroll, setScroll] = useState(0) - const [paddingBottom, setPaddingBottom] = useState(0) - - const handleCloseClick = () => { - setOngoingChallenge(null) - setOpenChallengeModal(false) - setOngoingChallengeModal(null) - } - - const setRightChallengeInTheMiddle = ( - _challengesType: ChallengeType[], - _userChallenges: UserChallenge[] - ) => { - const header = screenType === ScreenType.MOBILE ? 96 : 174 - const bottomNav = screenType === ScreenType.MOBILE ? 60 : 0 - const challengeCard = screenType === ScreenType.MOBILE ? 100 : 120 - const _paddingBottom = - (window.innerHeight - header - bottomNav) / 2 - - _userChallenges.length * challengeCard - const canFitInHalfScreen = - window.innerHeight / 2 - header > - (_challengesType.length - 1) * challengeCard - if (_userChallenges[0].state === 0) { - setPaddingBottom(_paddingBottom + bottomNav) - if (canFitInHalfScreen) { - setScroll(0) - } else { - setScroll( - (challengeCard / 2) * - _challengesType.length * - (1 + challengeCard / window.innerHeight) - ) - } - } else { - setPaddingBottom(_paddingBottom - challengeCard + bottomNav) - if (canFitInHalfScreen) { - setScroll(0) - } else { - setScroll( - (challengeCard / 2) * - (_challengesType.length - 1) * - (1 + challengeCard / window.innerHeight) - ) - } - } - } - - useEffect(() => { - let subscribed = true - async function loadAvailableChallenges() { - const dataAllCT = await challengeService.getAvailableChallenges( - fluidTypes - ) - const dataAllUC = await challengeService.getAllUserChallenges() - if (subscribed && dataAllCT && dataAllUC) { - const ongoingChallengeTmp = dataAllUC.filter( - challenge => challenge.state === ChallengeState.ONGOING - )[0] - setRightChallengeInTheMiddle(dataAllCT, dataAllUC) - setChallengesType(dataAllCT) - setUserChallenges(dataAllUC) - setOngoingChallenge(ongoingChallengeTmp) - setOngoingChallengeModal(ongoingChallengeTmp) - if (challengeNotification) { - setOpenChallengeModal(true) - await challengeService.endChallenge(ongoingChallengeTmp, fluidTypes) - await userProfileService.getUserProfile().then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) - }) - setCurrentChallenge(null) - setChallengeNotification(false) - setOngoingChallenge(null) - } - } - } - loadAvailableChallenges() - return () => { - subscribed = false - } - }, []) - - useEffect(() => { - let subscribed = true - async function loadNewChallengesType() { - const dataAllCT = await challengeService.getAvailableChallenges( - fluidTypes - ) - const dataAllUC = await challengeService.getAllUserChallenges() - if (subscribed && dataAllCT && dataAllUC) { - setChallengesType(dataAllCT) - setUserChallenges(dataAllUC) - setRightChallengeInTheMiddle(dataAllCT, dataAllUC) - } - } - loadNewChallengesType() - return () => { - subscribed = false - } - }, [ongoingChallenge]) - - window.scrollTo({ - top: scroll, - behavior: 'smooth', - }) - - return ( - <> - {!challengesType ? ( - <div className="content-view-loading"> - <StyledChallengeSpinner size="5em" /> - </div> - ) : ( - <div className="loc-root"> - <div className="loc-content"> - <div className="timeline-line"></div> - <div - className="list-of-challenge-cards" - style={{ paddingBottom: paddingBottom }} - > - {challengesType.map((challenge, index) => - index === challengesType.length - 1 && - !ongoingChallenge && - userProfile.level === - challengesType[challengesType.length - 1].level ? ( - <ChallengeListItem - key={index} - challenge={challenge} - challengeState="available" - /> - ) : ongoingChallenge && - challengesType.length - 1 === index ? null : ( - <ChallengeListItem - key={index} - challenge={challenge} - challengeState="locked" - /> - ) - )} - {userChallenges && - userChallenges.map((challenge, index) => ( - <ChallengeListItem - key={index} - challenge={ - challenge.challengeType - ? challenge.challengeType - : undefined - } - userChallenge={challenge} - challengeState={ - challenge.state === 0 ? 'ongoing' : 'finished' - } - /> - ))} - </div> - </div> - </div> - )} - {openChallengeModal && ongoingChallengeModal && ( - <ChallengeModal - opened={openChallengeModal} - challenge={ongoingChallengeModal} - handleCloseClick={handleCloseClick} - badgeStatus={ongoingChallengeModal && ongoingChallengeModal.badge} - /> - )} - </> - ) -} - -export default ChallengeList diff --git a/src/components/Challenge/ChallengeListItem.tsx b/src/components/Challenge/ChallengeListItem.tsx deleted file mode 100644 index adc5ef5332c442e36db4925cfd0cae26e2de1417..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeListItem.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { NavLink } from 'react-router-dom' - -import { ChallengeType, Ecogesture, UserChallenge } from 'models' - -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import ChevronGrey from 'assets/icons/ico/chevron-right-grey.svg' -import ChevronBlue from 'assets/icons/ico/chevron-right-blue.svg' -import ChevronDarkBlue from 'assets/icons/ico/chevron-right-darkblue.svg' -import LockedChallengeIcon from 'assets/png/badges/locked.png' -import AvailableChallengeIcon from 'assets/png/badges/available.png' -import OngoingChallengeIcon from 'assets/png/badges/ongoing.png' -import DefaultChallengeIcon from 'assets/png/badges/default.png' - -interface ChallengeListItemProps { - challenge?: ChallengeType - userChallenge?: UserChallenge - handleClick?: (challenge: ChallengeType) => void - challengeState?: string - ecogestures?: Ecogesture[] -} - -const ChallengeListItem: React.FC<ChallengeListItemProps> = ({ - challenge, - userChallenge, - challengeState, -}: ChallengeListItemProps) => { - const { t } = useI18n() - const [badgeIcon, setBadgeIcon] = useState<string | undefined>() - - async function importRightBadge(id: string, badgeStatus: number) { - if (badgeStatus !== -1) { - // Les png doivent être au format idchallenge-badgestate.png - const importedBadge = - id === 'CHA00000001' - ? await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-1.png` - ) - : await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-${badgeStatus}.png` - ) - setBadgeIcon(importedBadge.default) - } - } - - const defineBadge = () => { - return challengeState === 'locked' - ? LockedChallengeIcon - : challengeState === 'available' - ? AvailableChallengeIcon - : challengeState === 'ongoing' - ? OngoingChallengeIcon - : challengeState === 'finished' - ? badgeIcon - : DefaultChallengeIcon - } - - useEffect(() => { - if (challenge && userChallenge) { - importRightBadge( - challenge.id, - userChallenge.badge ? userChallenge.badge : 0 - ) - } - }, []) - - return ( - <NavLink - className="cli-link" - to={{ - pathname: `challenges/${challengeState}`, - state: userChallenge ? userChallenge : challenge, - }} - > - <div className={`cli cli-${challengeState}`}> - <div className="cli-left"> - <div className="cli-content"> - <img - className="cli-content-icon" - src={defineBadge() ? defineBadge() : DefaultChallengeIcon} - ></img> - <div className={`cli-content-title`}> - {challengeState === 'available' && ( - <div className={`subtitle-${challengeState} text-16-normal`}> - {t('CHALLENGE.NEW_CHALLENGE')} - </div> - )} - {challengeState === 'ongoing' && ( - <div className={`subtitle-${challengeState} text-16-normal`}> - {t('CHALLENGE.ONGOING_CHALLENGE')} - </div> - )} - {challengeState === 'finished' && ( - <div className={`subtitle-${challengeState} text-16-normal`}> - {userChallenge - ? userChallenge.endingDate - .setLocale('fr-FR') - .toLocaleString() - : t('CHALLENGE.ENDINGDATE_UNDEFINED')} - </div> - )} - <div className={`title-${challengeState} text-18-bold`}> - {challenge && challenge.title - ? challenge.title - : t('CHALLENGE.NO_CHALLENGE')} - </div> - </div> - </div> - </div> - <div className={`cli-right cli-right-${challengeState}`}> - {challengeState === 'available' ? ( - <StyledIcon icon={ChevronDarkBlue} size={18} /> - ) : challengeState === 'ongoing' ? ( - <StyledIcon icon={ChevronBlue} size={18} /> - ) : ( - <StyledIcon icon={ChevronGrey} size={18} /> - )} - </div> - </div> - </NavLink> - ) -} - -export default ChallengeListItem diff --git a/src/components/Challenge/ChallengeModal.tsx b/src/components/Challenge/ChallengeModal.tsx deleted file mode 100644 index 20e4149a62032a3964220a865c57f5ab536c9c11..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeModal.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useState } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useRecoilValue } from 'recoil' - -import { ScreenType } from 'enum/screen.enum' -import { screenTypeState } from 'atoms/screenType.state' -import { UserChallenge } from 'models' -import { formatNumberValues } from 'utils/utils' - -import Modal from 'components/CommonKit/Modal/Modal' -import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner' -import StarIcon from 'assets/icons/visu/challenge/star.svg' -import StyledButtonValid from 'components/CommonKit/Button/StyledButtonValid' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' - -interface ChallengeModalProps { - opened: boolean - challenge: UserChallenge - handleCloseClick: () => void - badgeStatus: number | null -} - -const ChallengeModal: React.FC<ChallengeModalProps> = ({ - opened, - challenge, - handleCloseClick, - badgeStatus, -}: ChallengeModalProps) => { - const { t } = useI18n() - const screenType = useRecoilValue(screenTypeState) - const [badgeIcon, setBadgeIcon] = useState<string | null>(null) - - async function importRightBadge(id: string, _badgeStatus: number) { - if (badgeStatus !== -1) { - // Les png doivent être au format idchallenge-badgestate.png - const importedBadge = - id === 'CHA00000001' - ? await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-1.png` - ) - : await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-${_badgeStatus}.png` - ) - setBadgeIcon(importedBadge.default) - } - } - - const showTheRightBadge = (_badgeStatus: number | null) => { - const result = - challenge && - formatNumberValues( - Math.abs(challenge.maxEnergy - challenge.currentEnergy) - ) - const challengeId = challenge.challengeType - ? challenge.challengeType.id - : '' - const badge = challenge.badge ? challenge.badge : 0 - importRightBadge(challengeId, badge) - if (badgeIcon) { - if (_badgeStatus === 1) { - return ( - <> - <div className="cm-title win text-24-bold "> - {' '} - {t('CHALLENGE.CONGRATULATION')} - </div> - <div className="cm-result text-18-bold"> - {challengeId !== 'CHA00000001' ? ( - <> - <div>{t('CHALLENGE.RESULT_POSITIF')}</div> - <div className="cm-result-positif text-18-normal">{`${result} €`}</div> - </> - ) : null} - </div> - <div - className={ - challengeId !== 'CHA00000001' - ? 'cm-win-badge-star' - : 'cm-win-badge-star --ecolyo-royal' - } - > - <img - className="cm-win-badge" - src={badgeIcon} - width={screenType === ScreenType.MOBILE ? 160 : 180} - ></img> - <StyledIcon - className="cm-win-star" - icon={StarIcon} - size={ScreenType.MOBILE ? 260 : 320} - /> - </div> - <div className="cm-txt"> - {' '} - {challengeId === 'CHA00000001' ? ( - <> - <div className="text-18-normal"> - {t('CHALLENGE.WIN_TEXT_ECOLYO')} - </div> - <div className="cm-text-new-available text-18-bold"> - {t('CHALLENGE.WIN_TEXT_ECOLYO_NEW_AVAILABLE')} - </div> - </> - ) : ( - <div className="text-18-bold"> - {t('CHALLENGE.WIN_TEXT')} - <div>{`"${challenge && - challenge.challengeType && - challenge.challengeType.title}"`}</div> - </div> - )} - </div> - <StyledButtonValid onClick={handleCloseClick}> - {t('CHALLENGE.OK')} - </StyledButtonValid> - </> - ) - } else { - return ( - <> - <div className="cm-title defeat text-24-bold "> - {t('CHALLENGE.DEFEAT')} - </div> - <div className="cm-result text-18-bold"> - <div>{t('CHALLENGE.DEFEAT_RESULT')}</div> - <div> - {t('CHALLENGE.DEFEAT_RESULT2')} - <span className="cm-result-negatif text-18-normal">{`${result} €`}</span> - </div> - </div> - <img - className="cm-badge" - src={badgeIcon} - width={screenType === ScreenType.MOBILE ? 160 : 180} - ></img> - <div className="cm-txt text-18-bold "> - {' '} - {t('CHALLENGE.CONSOLATION')} - {`"${challenge && - challenge.challengeType && - challenge.challengeType.title}"`} - {t('CHALLENGE.CONSOLATION2')} - </div> - <div className="cm-button-valid"> - <StyledButtonValid onClick={handleCloseClick}> - {t('CHALLENGE.OK')} - </StyledButtonValid> - </div> - </> - ) - } - } - } - return ( - <Modal open={opened} handleCloseClick={handleCloseClick} border> - {!challenge ? ( - <StyledSpinner /> - ) : ( - <div className="cm-content">{showTheRightBadge(badgeStatus)}</div> - )} - </Modal> - ) -} - -export default ChallengeModal diff --git a/src/components/Challenge/ChallengePile.tsx b/src/components/Challenge/ChallengePile.tsx deleted file mode 100644 index b29a1b3185123a435135d48215faa31ae64c2215..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengePile.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' - -import { UserChallenge } from 'models' -import { formatNumberValues } from 'utils/utils' - -import pile from 'assets/icons/visu/challenge/pile.svg' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' - -interface ChallengePileProps { - challenge: UserChallenge - small?: boolean -} - -const ChallengePile: React.FC<ChallengePileProps> = ({ - challenge, - small = false, -}: ChallengePileProps) => { - const { t } = useI18n() - const [pilePercent, setPilePercent] = useState<number>(0) - const sizePile = small ? 100 : 150 - const textFont = small ? 'text-17-bold' : 'text-20-bold' - - const getPileSectionPercent = (percent: number) => { - if (percent <= 100) { - return Math.floor(percent / 5) * 5 - } - return 100 - } - - useEffect(() => { - if (challenge) { - const percent = - challenge.maxEnergy > 0 - ? (challenge.currentEnergy * 100) / challenge.maxEnergy - : 0 - setPilePercent(getPileSectionPercent(percent)) - } - }, []) - - return ( - <React.Fragment> - <div className={`pile-energy-follow ${small ? '--home' : null}`}> - <div className="pile-section"> - <div - className="filter-pile" - style={{ - width: `${pilePercent}%`, - height: `${small ? '30px' : '46px'}`, - }} - ></div> - <StyledIcon className="pile-icon" icon={pile} size={sizePile} /> - </div> - {challenge && challenge.maxEnergy > 0 ? ( - <div className={`values-section ${textFont}`}> - <span> - {challenge.currentEnergy && challenge.currentEnergy !== -1 - ? formatNumberValues( - challenge.maxEnergy - challenge.currentEnergy - ) - : formatNumberValues(challenge.maxEnergy)}{' '} - € - </span> - <span className="max-energy"> - {' '} - / {formatNumberValues(challenge.maxEnergy)} € - </span> - </div> - ) : ( - <div className={`no-values-section text-18-bold`}> - {t('CHALLENGE.RESULT_NOT_AVAILABLE')} - </div> - )} - </div> - </React.Fragment> - ) -} - -export default ChallengePile diff --git a/src/components/Challenge/ChallengeTimeline.tsx b/src/components/Challenge/ChallengeTimeline.tsx deleted file mode 100644 index 5f81faf3b3905f4f01a26e23ff55493480805e99..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeTimeline.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react' -import { DateTime, Interval } from 'luxon' - -import { UserChallenge } from 'models' -import { getLagDays } from 'utils/date' - -interface ChallengeTimelineViewProps { - challenge: UserChallenge -} - -const ChallengeTimeline: React.FC<ChallengeTimelineViewProps> = ({ - challenge, -}: ChallengeTimelineViewProps) => { - const viewingDate = () => { - if (challenge && challenge.challengeType) { - const startingDate = challenge.startingDate - return startingDate.plus({ - days: getLagDays(challenge.fluidTypes), - }) - } else { - return DateTime.local() - } - } - - const getListOfDays = () => { - if (challenge && challenge.challengeType) { - const startingDate = DateTime.fromISO(challenge.startingDate.toString()) - const listOfChallengeDays = [] - let i = 0 - if (challenge.challengeType.duration.days === 7) { - while (challenge.challengeType.duration.days > i) { - listOfChallengeDays.push({ - letter: startingDate - .plus({ days: i }) - .weekdayShort[0].toUpperCase(), - date: startingDate.plus({ days: i }).day, - }) - i++ - } - } else { - while (challenge.challengeType.duration.days >= i) { - listOfChallengeDays.push({ - letter: '', - date: startingDate.plus({ days: i }).toFormat('dd/MM'), - }) - i += 7 - } - } - return listOfChallengeDays - } else { - return [] - } - } - - const getListOfWeeks = () => { - if (challenge && challenge.challengeType) { - const startingDate = DateTime.fromISO(challenge.startingDate.toString()) - const listOfChallengeDays = [] - let i = 0 - while (challenge.challengeType.duration.days >= i) { - listOfChallengeDays.push({ - letter: '', - date: startingDate.plus({ days: i }).toFormat('dd/MM'), - }) - i += 7 - } - return listOfChallengeDays - } else { - return [] - } - } - - const getListOfPastDays = () => { - if (Interval.fromDateTimes(viewingDate(), DateTime.local()).isValid) { - if (challenge && challenge.challengeType) { - return Interval.fromDateTimes(viewingDate(), DateTime.local()).count( - 'days' - ) - } else { - return 0 - } - } else { - if (challenge) { - return -Interval.fromDateTimes(DateTime.local(), viewingDate()).count( - 'days' - ) - } else { - return 0 - } - } - } - - const generateMonthTimelinePart = ( - index: number, - dayCount: number, - className: string - ) => { - return ( - <div - className={`${className} ${ - (index === 0 && dayCount > 0) || - (index === getListOfWeeks().length - 1 && dayCount < 0) - ? 'none' - : index * 7 < getListOfPastDays() + dayCount - ? 'past' - : 'futur' - }`} - ></div> - ) - } - - const generateWeekTimelinePart = (index: number, className: string) => { - return ( - <div - className={`${className} ${ - index < getListOfPastDays() ? 'past' : 'futur' - }`} - ></div> - ) - } - - const monthChallenge = () => { - return getListOfWeeks().map((day, index) => ( - <div key={index} className="day-solo"> - <div className="day-line-label"> - {generateMonthTimelinePart(index, 3, 'date-dash')} - {generateMonthTimelinePart(index, 2, 'date-dash')} - {generateMonthTimelinePart(index, 1, 'date-dash')} - {generateMonthTimelinePart(index, 0, 'date-label')} - {generateMonthTimelinePart(index, -1, 'date-dash')} - {generateMonthTimelinePart(index, -2, 'date-dash')} - {generateMonthTimelinePart(index, -3, 'date-dash')} - </div> - <div className="day-letter">{day.letter}</div> - <div className="day-date">{day.date}</div> - </div> - )) - } - - const weekChallenge = () => { - return getListOfDays().map((day, index) => ( - <div key={index} className="day-solo"> - <div className="day-line-label"> - {generateWeekTimelinePart(index, 'date-dash')} - {generateWeekTimelinePart(index, 'date-label')} - {generateWeekTimelinePart(index + 1, 'date-dash')} - </div> - <div className="day-letter">{day.letter}</div> - <div className="day-date">{day.date}</div> - </div> - )) - } - - return ( - <React.Fragment> - <div className="list-of-days-duration"> - {challenge && - challenge.challengeType && - challenge.challengeType.duration.days === 7 - ? weekChallenge() - : monthChallenge()} - </div> - </React.Fragment> - ) -} - -export default ChallengeTimeline diff --git a/src/components/Challenge/ChallengeView.tsx b/src/components/Challenge/ChallengeView.tsx deleted file mode 100644 index 9be9e28ea651c704a1231e683a84c6d50e70d30c..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeView.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { useState } from 'react' -import CozyBar from 'components/Header/CozyBar' -import Header from 'components/Header/Header' -import Content from 'components/Content/Content' -import ChallengesList from 'components/Challenge/ChallengeList' - -const ChallengeView: React.FC = () => { - const [headerHeight, setHeaderHeight] = useState<number>(0) - - const defineHeaderHeight = (height: number) => { - setHeaderHeight(height) - } - - return ( - <React.Fragment> - <CozyBar titleKey={'COMMON.APP_CHALLENGE_TITLE'} /> - <Header - setHeaderHeight={defineHeaderHeight} - desktopTitleKey={'COMMON.APP_CHALLENGE_TITLE'} - ></Header> - <Content height={headerHeight} background="var(--darkLight2)"> - <ChallengesList /> - </Content> - </React.Fragment> - ) -} - -export default ChallengeView diff --git a/src/components/Challenge/ChallengeViewingDate.tsx b/src/components/Challenge/ChallengeViewingDate.tsx deleted file mode 100644 index 4c96a94f971fca8fa03fbb4224277a1b78844663..0000000000000000000000000000000000000000 --- a/src/components/Challenge/ChallengeViewingDate.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { DateTime } from 'luxon' - -import { UserChallenge } from 'models' -import { getLagDays } from 'utils/date' - -interface ChallengeViewingDateProps { - challenge: UserChallenge -} - -const ChallengeViewingDate: React.FC<ChallengeViewingDateProps> = ({ - challenge, -}: ChallengeViewingDateProps) => { - const { t } = useI18n() - const [firstDateWithData, setFirstDateWithData] = useState<DateTime | null>( - null - ) - const [lastDateWithData, setLastDateWithData] = useState<DateTime | null>( - null - ) - const diffDays = - firstDateWithData && firstDateWithData.diffNow(['days']).toObject().days - const diffDaysCeil = Math.ceil(diffDays || 0) - - useEffect(() => { - if (challenge) { - const lag = getLagDays(challenge.fluidTypes) - setFirstDateWithData( - challenge.startingDate.plus({ - days: lag - 1, - }) - ) - setLastDateWithData( - challenge.endingDate.plus({ - days: lag - 1, - }) - ) - } - }, []) - /* eslint-disable @typescript-eslint/camelcase */ - return ( - <React.Fragment> - {firstDateWithData && firstDateWithData > DateTime.local() ? ( - <div className="view-start-date"> - {t('CHALLENGE.VIEW_START', { - diffDaysCeil, - smart_count: diffDaysCeil, - })} - </div> - ) : ( - <div className="view-start-date"> - {t('CHALLENGE.VIEW_RESULT')} - <span>{lastDateWithData && lastDateWithData.toLocaleString()}</span> - </div> - )} - </React.Fragment> - ) -} - -export default ChallengeViewingDate diff --git a/src/components/Challenge/FinishedChallengeDetailsView.tsx b/src/components/Challenge/FinishedChallengeDetailsView.tsx deleted file mode 100644 index 12208658fa355fb390d05c766f3dda26215ce15d..0000000000000000000000000000000000000000 --- a/src/components/Challenge/FinishedChallengeDetailsView.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { Redirect } from 'react-router-dom' -import { useRecoilValue } from 'recoil' -import { Location } from 'history' - -import { TypeChallenge, BadgeState } from 'enum/challenge.enum' -import { ScreenType } from 'enum/screen.enum' -import { UserChallenge } from 'models' -import { screenTypeState } from 'atoms/screenType.state' -import { formatNumberValues } from 'utils/utils' - -import CozyBar from 'components/Header/CozyBar' -import Header from 'components/Header/Header' -import Content from 'components/Content/Content' -import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner' -import StarIcon from 'assets/png/challenge/star.png' -import EcogestureModal from 'components/Ecogesture/EcogestureModal' -import EcogestureCard from 'components/Ecogesture/EcogestureCard' - -interface FinishedChallengeDetailsViewProps { - location: Location<UserChallenge> -} - -const FinishedChallengeDetailsView: React.FC<FinishedChallengeDetailsViewProps> = ({ - location, -}: FinishedChallengeDetailsViewProps) => { - const { t } = useI18n() - const userChallengeState = location.state - const screenType = useRecoilValue(screenTypeState) - const [challengeEcogesture, setChallengeEcogesture] = useState<number>(0) - const [openEcogestureModal, setOpenEcogestureModal] = useState(false) - const [challenge, setChallenge] = useState<UserChallenge | null>(null) - const [headerHeight, setHeaderHeight] = useState<number>(0) - const [badgeIcon, setBadgeIcon] = useState<string | undefined>() - - async function importRightBadge(id: string, badgeStatus: number) { - if (badgeStatus !== -1) { - // Les png doivent être au format idchallenge-badgestate.png - const importedBadge = - id === 'CHA00000001' - ? await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-1.png` - ) - : await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-${badgeStatus}.png` - ) - setBadgeIcon(importedBadge.default) - } - } - - const defineHeaderHeight = (height: number) => { - setHeaderHeight(height) - } - - const handleClick = (index: number) => { - setChallengeEcogesture(index) - setOpenEcogestureModal(true) - } - - const handleCloseClick = () => { - setOpenEcogestureModal(false) - } - - useEffect(() => { - if (userChallengeState) { - setChallenge(userChallengeState) - if ( - userChallengeState.challengeType && - userChallengeState.badge != null - ) { - importRightBadge( - userChallengeState.challengeType.id, - userChallengeState.badge - ) - } - } - }, []) - - const result = - challenge && - formatNumberValues(Math.abs(challenge.maxEnergy - challenge.currentEnergy)) - - return ( - <React.Fragment> - <CozyBar - titleKey={'COMMON.APP_FINISHED_CHALLENGE_TITLE'} - displayBackArrow={true} - /> - <Header - setHeaderHeight={defineHeaderHeight} - desktopTitleKey={'COMMON.APP_FINISHED_CHALLENGE_TITLE'} - displayBackArrow={true} - ></Header> - {!userChallengeState ? <Redirect to="/challenges" /> : null} - <Content height={headerHeight} background="var(--darkLight2)"> - {!challenge ? ( - <StyledSpinner /> - ) : ( - <> - <div className="cp-root"> - <div className="cp-content"> - <div className="cp-info"> - <div className="cp-date text-16-normal"> - {challenge.endingDate.setLocale('fr-FR').toLocaleString()} - </div> - <div className="cp-title text-22-bold"> - {challenge.challengeType && challenge.challengeType.title} - </div> - {challenge.challengeType && - challenge.challengeType.type === TypeChallenge.CHALLENGE ? ( - challenge.badge === BadgeState.SUCCESS ? ( - <div className="cp-result text-18-bold"> - <div>{t('CHALLENGE.RESULT_POSITIF')}</div> - <div className="cp-result-positif text-18-normal">{`${result} €`}</div> - </div> - ) : ( - <div className="cp-result text-18-bold"> - <div>{t('CHALLENGE.RESULT_NEGATIF')}</div> - <div className="cp-result-negatif text-18-normal">{`${result} €`}</div> - </div> - ) - ) : null} - {challenge.badge === BadgeState.SUCCESS ? ( - <div className="cp-win-badge-star"> - <img - className="cp-win-badge" - src={badgeIcon} - width={screenType === ScreenType.MOBILE ? 200 : 300} - ></img> - <img - className="cp-win-star" - src={StarIcon} - width={screenType === ScreenType.MOBILE ? 380 : 480} - ></img> - </div> - ) : ( - <img - className="cp-win-badge" - src={badgeIcon} - width={screenType === ScreenType.MOBILE ? 200 : 300} - ></img> - )} - <div className="cp-description text-16-bold"> - {challenge.challengeType && - challenge.challengeType.description} - </div> - </div> - <div className="cp-bottom"> - <div className="cp-eg-content text-14-normal-uppercase"> - {t('CHALLENGE.LINKED_ECOGESTURES')} - <div className="cp-ecogestures"> - {challenge.selectedEcogestures - .filter((eg, index) => index < 2) - .map((eg, index) => ( - <EcogestureCard - key={index} - ecogesture={eg} - handleClick={() => handleClick(index)} - challengeEcogesture={index + 1} - /> - ))} - </div> - </div> - </div> - </div> - </div> - {openEcogestureModal && ( - <EcogestureModal - open={openEcogestureModal} - ecogesture={challenge.selectedEcogestures[challengeEcogesture]} - handleCloseClick={handleCloseClick} - unlockedEcogesture={true} - /> - )} - </> - )} - </Content> - </React.Fragment> - ) -} - -export default FinishedChallengeDetailsView diff --git a/src/components/Challenge/LockedChallengeDetailsViewContainer.tsx b/src/components/Challenge/LockedChallengeDetailsViewContainer.tsx deleted file mode 100644 index 1c64e8c9f305690d2e5f4f36f9a11cfec60d7c12..0000000000000000000000000000000000000000 --- a/src/components/Challenge/LockedChallengeDetailsViewContainer.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { Redirect } from 'react-router-dom' -import { useRecoilValue } from 'recoil' -import { Location } from 'history' - -import { ScreenType } from 'enum/screen.enum' -import { screenTypeState } from 'atoms/screenType.state' -import { ChallengeType } from 'models' - -import CozyBar from 'components/Header/CozyBar' -import Header from 'components/Header/Header' -import Content from 'components/Content/Content' -import { history } from 'components/App' -import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner' -import StyledButtonValid from 'components/CommonKit/Button/StyledButtonValid' -import LockedChallengeIcon from 'assets/png/badges/locked-big.png' - -interface LockedChallengeDetailsViewProps { - location: Location<ChallengeType> -} - -const LockedChallengeDetailsViewContainer: React.FC<LockedChallengeDetailsViewProps> = ({ - location, -}: LockedChallengeDetailsViewProps) => { - const { t } = useI18n() - const screenType = useRecoilValue(screenTypeState) - const [challenge, setChallenge] = useState<ChallengeType | null>(null) - const [headerHeight, setHeaderHeight] = useState<number>(0) - const challengeState = location.state - - const defineHeaderHeight = (height: number) => { - setHeaderHeight(height) - } - - useEffect(() => { - if (challengeState) { - setChallenge(challengeState) - } - }, []) - - return ( - <React.Fragment> - <CozyBar - titleKey={'COMMON.APP_LOCKED_CHALLENGE_TITLE'} - displayBackArrow={true} - /> - <Header - setHeaderHeight={defineHeaderHeight} - desktopTitleKey={'COMMON.APP_LOCKED_CHALLENGE_TITLE'} - displayBackArrow={true} - ></Header> - {!challengeState ? <Redirect to="/challenges" /> : null} - <Content height={headerHeight} background="var(--darkLight2)"> - {!challenge ? ( - <StyledSpinner /> - ) : ( - <> - <div className="cp-root --locked"> - <div className="cp-content --locked"> - <div className="cp-info"> - <div className="cp-title text-22-bold">{challenge.title}</div> - <img - className="cp-icon" - src={LockedChallengeIcon} - width={screenType === ScreenType.MOBILE ? 180 : 220} - ></img> - <div className="cp-description text-16-bold"> - {challenge.level < 900 - ? t('CHALLENGE.LOCKED') - : t('CHALLENGE.FULLY_LOCKED')} - </div> - </div> - <div className="cp-valid-locked"> - <StyledButtonValid - color="primary" - onClick={() => history.goBack()} - > - {t('CHALLENGE.BACK')} - </StyledButtonValid> - </div> - </div> - </div> - </> - )} - </Content> - </React.Fragment> - ) -} - -export default LockedChallengeDetailsViewContainer diff --git a/src/components/Challenge/OngoingChallengeDetailsView.tsx b/src/components/Challenge/OngoingChallengeDetailsView.tsx deleted file mode 100644 index a8c9ba2253bab25cacbc11f5ef954f6a1957186e..0000000000000000000000000000000000000000 --- a/src/components/Challenge/OngoingChallengeDetailsView.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { Redirect } from 'react-router-dom' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useClient } from 'cozy-client' -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' -import { Location } from 'history' - -import { TypeChallenge } from 'enum/challenge.enum' -import { ScreenType } from 'enum/screen.enum' -import { UserChallenge, UserProfile } from 'models' -import { userProfileState } from 'atoms/userProfile.state' -import { fluidTypeState } from 'atoms/fluidState.state' -import { screenTypeState } from 'atoms/screenType.state' -import { currentChallengeState } from 'atoms/challenge.state' -import ChallengeService from 'services/challenge.service' -import UserProfileService from 'services/userProfile.service' -import { formatCompareChallengeDate } from 'utils/date' - -import CozyBar from 'components/Header/CozyBar' -import Header from 'components/Header/Header' -import Content from 'components/Content/Content' -import { history } from 'components/App' -import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner' -import EcogestureModal from 'components/Ecogesture/EcogestureModal' -import EcogestureCard from 'components/Ecogesture/EcogestureCard' -import StyledStopButton from 'components/CommonKit/Button/StyledStopButton' -import ChallengePile from 'components/Challenge/ChallengePile' -import ChallengeViewingDate from 'components/Challenge/ChallengeViewingDate' -import ChallengeTimeline from 'components/Challenge/ChallengeTimeline' -import AvailableChallengeIcon from 'assets/png/badges/available.png' - -interface OngoingChallengeDetailsViewProps { - location: Location<UserChallenge> -} - -const OngoingChallengeDetailsView: React.FC<OngoingChallengeDetailsViewProps> = ({ - location, -}: OngoingChallengeDetailsViewProps) => { - const { t } = useI18n() - const client = useClient() - const screenType = useRecoilValue(screenTypeState) - const fluidTypes = useRecoilValue(fluidTypeState) - const [userProfile, setUserProfile] = useRecoilState<UserProfile>( - userProfileState - ) - const setCurrentChallenge = useSetRecoilState(currentChallengeState) - - const userChallengeState = location.state - const [challengeEcogesture, setChallengeEcogesture] = useState<number>(0) - const [openEcogestureModal, setOpenEcogestureModal] = useState(false) - const [challenge, setChallenge] = useState<UserChallenge | null>(null) - const [headerHeight, setHeaderHeight] = useState<number>(0) - const [maxEnergy, setMaxEnergy] = useState<number | null>(0) - - const challengeService = new ChallengeService(client) - const userProfileService = new UserProfileService(client) - - const defineHeaderHeight = (height: number) => { - setHeaderHeight(height) - } - - const updateUserProfileNotification = useCallback( - async (ecogestureList: string[]) => { - await userProfileService - .updateUserProfile({ notificationEcogesture: ecogestureList }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) - }) - }, - [setUserProfile] - ) - - async function stopChallenge(_challenge: UserChallenge) { - if (_challenge && _challenge.id) { - await challengeService.cancelChallenge(_challenge.id) - const updatednotificationEcogesture = userProfile.notificationEcogesture.filter( - (x: string) => - x !== _challenge.selectedEcogestures[0].id && - x !== _challenge.selectedEcogestures[1].id - ) - await updateUserProfileNotification(updatednotificationEcogesture) - setCurrentChallenge(null) - history.goBack() - } - } - - const handleClick = (index: number) => { - setChallengeEcogesture(index) - setOpenEcogestureModal(true) - } - - const handleCloseClick = () => { - setOpenEcogestureModal(false) - } - - useEffect(() => { - if (userChallengeState) { - setChallenge(userChallengeState) - setMaxEnergy(userChallengeState.maxEnergy) - } - }, []) - - return ( - <React.Fragment> - <CozyBar - titleKey={'COMMON.APP_ONGOING_CHALLENGE_TITLE'} - displayBackArrow={true} - /> - <Header - setHeaderHeight={defineHeaderHeight} - desktopTitleKey={'COMMON.APP_ONGOING_CHALLENGE_TITLE'} - displayBackArrow={true} - ></Header> - {!location.state ? <Redirect to="/challenges" /> : null} - <Content height={headerHeight} background="var(--darkLight2)"> - {!challenge ? ( - <StyledSpinner /> - ) : ( - <> - <div className="cp-root ongoing-challenge"> - <div className="cp-content"> - <div className="cp-info"> - <div className="cp-title text-22-bold"> - {challenge.challengeType && challenge.challengeType.title} - </div> - {challenge.challengeType && - challenge.challengeType.type === TypeChallenge.ACHIEVEMENT ? ( - <img - className="cp-icon-available" - src={AvailableChallengeIcon} - width={screenType === ScreenType.MOBILE ? 180 : 250} - ></img> - ) : ( - <div className="cp-follow"> - {maxEnergy && maxEnergy === -1 && ( - <ChallengeViewingDate challenge={challenge} /> - )} - <ChallengePile challenge={challenge} /> - <ChallengeTimeline challenge={challenge} /> - </div> - )} - <div className="cp-description text-16-bold"> - {challenge.challengeType && - challenge.challengeType.description} - {challenge.challengeType && - challenge.challengeType.type === 1 - ? null - : formatCompareChallengeDate(challenge)} - </div> - {challenge.challengeType && - challenge.challengeType.type !== - TypeChallenge.ACHIEVEMENT && ( - <div className="cp-valid --ongoing"> - <StyledStopButton - color="secondary" - onClick={() => stopChallenge(challenge)} - > - {t('CHALLENGE.STOP')} - </StyledStopButton> - </div> - )} - </div> - <div className="cp-bottom"> - <div className="cp-eg-content"> - <span className="linked-ecogestures"> - {t('CHALLENGE.LINKED_ECOGESTURES')} - </span> - <div className="cp-ecogestures"> - {challenge.selectedEcogestures.map((eg, index) => ( - <EcogestureCard - key={index} - ecogesture={eg} - handleClick={() => handleClick(index)} - challengeEcogesture={index + 1} - /> - ))} - </div> - </div> - </div> - </div> - </div> - {openEcogestureModal && ( - <EcogestureModal - open={openEcogestureModal} - ecogesture={challenge.selectedEcogestures[challengeEcogesture]} - handleCloseClick={handleCloseClick} - unlockedEcogesture={true} - /> - )} - </> - )} - </Content> - </React.Fragment> - ) -} - -export default OngoingChallengeDetailsView diff --git a/src/components/Connection/ConnectionForm.tsx b/src/components/Connection/ConnectionForm.tsx index 36f9e62a732ed5f01ec92a48b28c093794951a97..31332ed049355f89f54fe948997a9ac79c398b9b 100644 --- a/src/components/Connection/ConnectionForm.tsx +++ b/src/components/Connection/ConnectionForm.tsx @@ -1,10 +1,10 @@ import React, { useCallback } from 'react' import { useClient } from 'cozy-client' -import { useSetRecoilState } from 'recoil' +import { useDispatch } from 'react-redux' +import { updateProfile } from 'store/profile/profile.actions' -import { userProfileState } from 'atoms/userProfile.state' import { Konnector, Trigger, FluidConfig } from 'models' -import UserProfileService from 'services/userProfile.service' +import ProfileService from 'services/profile.service' import ConnectionLoginForm from 'components/Connection/ConnectionLoginForm' import ConnectionOAuthForm from 'components/Connection/ConnectionOAuthForm' @@ -25,17 +25,19 @@ const ConnectionForm: React.FC<ConnectionFormProps> = ({ handleSuccessForm, }: ConnectionFormProps) => { const client = useClient() - const setUserProfile = useSetRecoilState(userProfileState) + const dispatch = useDispatch() const oAuth: boolean = fluidConfig.konnectorConfig.oauth const updateProfileHaveSeenOldFluidModal = useCallback(async () => { - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ haveSeenOldFluidModal: false }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) + const profileService = new ProfileService(client) + await profileService + .updateProfile({ haveSeenOldFluidModal: false }) + .then(updatedProfile => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } }) - }, [setUserProfile]) + }, []) const handleSuccess = async (_account: Account, _trigger: Trigger) => { await updateProfileHaveSeenOldFluidModal() diff --git a/src/components/Connection/ConnectionResult.tsx b/src/components/Connection/ConnectionResult.tsx index 5aec1475387eacc2489aeb3fa4951f97a8e8bb75..a691ac3ba5462fc1fa814ef0b468018bd10384b1 100644 --- a/src/components/Connection/ConnectionResult.tsx +++ b/src/components/Connection/ConnectionResult.tsx @@ -1,19 +1,18 @@ import React, { useState, useEffect, useCallback } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { useClient } from 'cozy-client' -import { useRecoilValue, useSetRecoilState } from 'recoil' +import { useSetRecoilState } from 'recoil' +import { useDispatch } from 'react-redux' +import { updateProfile } from 'store/profile/profile.actions' import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' import { getKonnectorUpdateError } from 'utils/utils' import { Account, Konnector, Trigger } from 'models' -import { userProfileState } from 'atoms/userProfile.state' -import { fluidStatusState, fluidTypeState } from 'atoms/fluidState.state' -import { challengeNotificationState } from 'atoms/notification.state' -import ChallengeService from 'services/challenge.service' +import { fluidStatusState } from 'atoms/fluidState.state' import TriggerService from 'services/triggers.service' import AccountService from 'services/account.service' import FluidService from 'services/fluid.service' -import UserProfileService from 'services/userProfile.service' +import ProfileService from 'services/profile.service' import ConnectionFlow, { ERROR_EVENT, @@ -38,6 +37,7 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ }: ConnectionResultProps) => { const { t } = useI18n() const client = useClient() + const dispatch = useDispatch() const [trigger, setTrigger] = useState<Trigger | null>(null) const [updating, setUpdating] = useState<boolean>(false) const [lastExecutionDate, setLastExecutionDate] = useState<string>('-') @@ -45,39 +45,25 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ const [status, setStatus] = useState<string>('') const setFluidStatusState = useSetRecoilState(fluidStatusState) - const fluidTypes = useRecoilValue(fluidTypeState) - const setChallengeNotificationState = useSetRecoilState( - challengeNotificationState - ) - const setUserProfile = useSetRecoilState(userProfileState) const refreshFluidState = async () => { const fluidService = new FluidService(client) const fluidStatus = await fluidService.getFluidStatus() if (fluidStatus) { setFluidStatusState(fluidStatus) - const challengeService = new ChallengeService(client) - const achievement = await challengeService.checkAchievement('CHA00000001') - if (achievement) { - const notificationChallenge = await challengeService.isCurrentChallengeOver( - achievement, - fluidTypes - ) - if (notificationChallenge) { - setChallengeNotificationState(notificationChallenge) - } - } } } const updateProfileHaveSeenOldFluidModal = useCallback(async () => { - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ haveSeenOldFluidModal: false }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) + const profileService = new ProfileService(client) + await profileService + .updateProfile({ haveSeenOldFluidModal: false }) + .then(updatedProfile => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } }) - }, [setUserProfile]) + }, []) const updateState = async (_trigger: Trigger) => { const triggerService = new TriggerService(client) diff --git a/src/components/Content/Content.tsx b/src/components/Content/Content.tsx index b5b0e5f4ce294871fc6a415aed239fef6a66b9e3..2cacb0c80798f411d080b9f123e7a76a757a2765 100644 --- a/src/components/Content/Content.tsx +++ b/src/components/Content/Content.tsx @@ -1,16 +1,15 @@ import React, { useCallback, useEffect } from 'react' import { useClient } from 'cozy-client' +import { history } from 'components/App' import { useSelector, useDispatch } from 'react-redux' import { EcolyoState } from 'store' +import { changeScreenType } from 'store/global/global.actions' import { updateModalIsFeedbacksOpen } from 'store/modal/modal.actions' -import { useRecoilState } from 'recoil' -import { history } from 'components/App' +import { updateProfile } from 'store/profile/profile.actions' + +import ProfileService from 'services/profile.service' import MailService from 'services/mail.service' import { ScreenType } from 'enum/screen.enum' -import { UserProfile } from 'models' -import { userProfileState } from 'atoms/userProfile.state' -import { screenTypeState } from 'atoms/screenType.state' -import UserProfileService from 'services/userProfile.service' import get from 'lodash/get' import WelcomeModal from 'components/Welcome/WelcomeModal' @@ -29,21 +28,18 @@ const Content: React.FC<ContentProps> = ({ background = 'inherit', }: ContentProps) => { const client = useClient() + const dispatch = useDispatch() + const { screenType } = useSelector((state: EcolyoState) => state.global) + const profile = useSelector((state: EcolyoState) => state.profile) + const modalState = useSelector((state: EcolyoState) => state.modal) + const mailService = new MailService() + const cozyBarHeight = 48 const cozyNavHeight = 56 - const mailService = new MailService() - const [screenType, setScreenType] = useRecoilState<ScreenType>( - screenTypeState - ) - const [userProfile, setUserProfile] = useRecoilState<UserProfile>( - userProfileState - ) - const modalState = useSelector((state: EcolyoState) => state.modal) - const dispatch = useDispatch() const setWelcomeModalViewed = useCallback(async () => { let username = '' - // Retrieve public name from the stack + // TODO Move this code to Mail Service const settings = await client .getStackClient() .fetchJSON('GET', '/settings/instance') @@ -66,23 +62,28 @@ const Content: React.FC<ContentProps> = ({ ], } mailService.SendMail(client, mailData) - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ + const profileService = new ProfileService(client) + await profileService + .updateProfile({ isFirstConnection: false, }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) + .then(updatedProfile => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } }) - }, [setUserProfile]) + }, []) + const setFavoriteModalViewed = useCallback(async () => { - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ haveSeenFavoriteModal: true }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) + const profileService = new ProfileService(client) + await profileService + .updateProfile({ haveSeenFavoriteModal: true }) + .then(updatedProfile => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } }) - }, [setUserProfile]) + }, []) const handleFeedbackModalClose = () => { dispatch(updateModalIsFeedbacksOpen(false)) @@ -100,11 +101,11 @@ const Content: React.FC<ContentProps> = ({ useEffect(() => { function handleResize() { if (innerWidth <= 768) { - setScreenType(ScreenType.MOBILE) + dispatch(changeScreenType(ScreenType.MOBILE)) } else if (innerWidth <= 1024) { - setScreenType(ScreenType.TABLET) + dispatch(changeScreenType(ScreenType.TABLET)) } else { - setScreenType(ScreenType.DESKTOP) + dispatch(changeScreenType(ScreenType.DESKTOP)) } } handleResize() @@ -117,7 +118,7 @@ const Content: React.FC<ContentProps> = ({ return ( <> <WelcomeModal - open={userProfile.isFirstConnection} + open={profile.isFirstConnection} handleCloseClick={setWelcomeModalViewed} /> <FeedbackModal @@ -125,9 +126,7 @@ const Content: React.FC<ContentProps> = ({ handleCloseClick={handleFeedbackModalClose} /> <FavoriteModal - open={ - !userProfile.isFirstConnection && !userProfile.haveSeenFavoriteModal - } + open={!profile.isFirstConnection && !profile.haveSeenFavoriteModal} handleCloseClick={setFavoriteModalViewed} /> <div diff --git a/src/components/Ecogesture/EcogestureCard.tsx b/src/components/Ecogesture/EcogestureCard.tsx index 9751661b006823bad4077d467b66b3210df70f41..bc58ae8cdaa9ca3291b7a82d2f8a457c973037dc 100644 --- a/src/components/Ecogesture/EcogestureCard.tsx +++ b/src/components/Ecogesture/EcogestureCard.tsx @@ -1,9 +1,5 @@ import React, { useState, useEffect } from 'react' -import { useRecoilValue } from 'recoil' - import { Ecogesture } from 'models' -import { userProfileState } from 'atoms/userProfile.state' - import StyledEcogestureCard from 'components/CommonKit/Card/StyledEcogestureCard' import def from 'assets/icons/visu/ecogesture/default.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' @@ -17,15 +13,11 @@ interface EcogestureCardProps { const EcogestureCard: React.FC<EcogestureCardProps> = ({ ecogesture, handleClick, - challengeEcogesture, }: EcogestureCardProps) => { const handleCardclick = () => { handleClick && ecogesture && handleClick(ecogesture) } const [ecogestureIcon, setEcogestureIcon] = useState(def) - const userProfile = useRecoilValue(userProfileState) - const newEcogestures: string[] | null = - userProfile && userProfile.notificationEcogesture async function importEcogestureIcon(id: string) { // Les svg doivent être au format id.svg let importedEcogesture @@ -47,65 +39,28 @@ const EcogestureCard: React.FC<EcogestureCardProps> = ({ return ( <> - {challengeEcogesture ? ( - <div className={`cp-eg-${challengeEcogesture}`}> - <StyledEcogestureCard - border={true} - onClick={handleCardclick} - newEcogesture={ - newEcogestures && newEcogestures.includes(ecogesture.id) - } + <StyledEcogestureCard unlocked={true} onClick={handleCardclick}> + <div className="ec"> + <div + className={`ec-content ${ + ecogesture.unlocked ? 'ec-content-unlocked' : '' + }`} > - <div className="ec"> - <div className="ec-content ec-content-challenge"> - <div> - <StyledIcon - className="Icon" - icon={ecogestureIcon} - size={64} - /> - </div> - <div className="ec-content-challenge-text"> - {ecogesture.shortName} - </div> - </div> + <div className="ec-content-icon"> + {ecogesture.unlocked && ( + <StyledIcon className="Icon" icon={ecogestureIcon} size={50} /> + )} </div> - </StyledEcogestureCard> - </div> - ) : ( - <StyledEcogestureCard - unlocked={ecogesture.unlocked ? ecogesture.unlocked : false} - onClick={handleCardclick} - newEcogesture={ - newEcogestures && newEcogestures.includes(ecogesture.id) - } - > - <div className="ec"> - <div - className={`ec-content ${ - ecogesture.unlocked ? 'ec-content-unlocked' : '' - }`} - > - <div className="ec-content-icon"> - {ecogesture.unlocked && ( - <StyledIcon - className="Icon" - icon={ecogestureIcon} - size={50} - /> - )} - </div> - <div className="ec-content-short-name text-16-bold"> - {ecogesture.shortName} - </div> - <div className="ec-content-nwh"> - <span className="text-16-bold">{ecogesture.nwh}</span> - <span className="text-16-normal"> nWh</span> - </div> + <div className="ec-content-short-name text-16-bold"> + {ecogesture.shortName} + </div> + <div className="ec-content-nwh"> + <span className="text-16-bold">{ecogesture.nwh}</span> + <span className="text-16-normal"> nWh</span> </div> </div> - </StyledEcogestureCard> - )} + </div> + </StyledEcogestureCard> </> ) } diff --git a/src/components/Ecogesture/EcogestureList.tsx b/src/components/Ecogesture/EcogestureList.tsx index 0fb4ef3d0f2c9f0ca5bd1b8ecf754a8147cfc529..7d221c38d6fbfd24bc4ea5cdac9ce57bccbb5e45 100644 --- a/src/components/Ecogesture/EcogestureList.tsx +++ b/src/components/Ecogesture/EcogestureList.tsx @@ -4,7 +4,7 @@ import { useClient } from 'cozy-client' import { Ecogesture } from 'models' import { FluidType } from 'enum/fluid.enum' -import ChallengeService from 'services/challenge.service' +import EcogestureService from 'services/ecogesture.service' import GasIcon from 'assets/icons/ico/gas.svg' import ElecIcon from 'assets/icons/ico/elec.svg' @@ -33,7 +33,7 @@ const EcogesturesList: React.FC = () => { const [filterbyFluid, setfilterbyFluid] = useState<FluidType | null>(null) const [ascendingSort, setascendingSort] = useState(false) const [openDropDown, setopenDropDown] = useState(false) - const _challengeService = new ChallengeService(client) + const ecogestureService = new EcogestureService(client) const handleClick = (ecogesture: Ecogesture) => { setSelectedEcogesture(ecogesture) @@ -72,21 +72,12 @@ const EcogesturesList: React.FC = () => { useEffect(() => { let subscribed = true async function loadEcogestures() { - const dataAll = await _challengeService.getAllEcogestures() - const dataUnlocked = await _challengeService.getUnlockedEcogestures() + const dataAll = await ecogestureService.getAllEcogestures() if (subscribed && dataAll) { - if (dataUnlocked) { - dataAll.forEach(element => { - if (dataUnlocked.includes(element.id)) { - element.unlocked = true - } - }) - } setEcogestures(dataAll) } } loadEcogestures() - return () => { subscribed = false } @@ -278,7 +269,7 @@ const EcogesturesList: React.FC = () => { </div> </> )} - {openEcogestureModal && ( + {openEcogestureModal && selectedEcogesture && ( <EcogestureModal open={openEcogestureModal} ecogesture={selectedEcogesture} diff --git a/src/components/Ecogesture/EcogestureModal.tsx b/src/components/Ecogesture/EcogestureModal.tsx index 125436bf6ef38176e493e508fe0bf21f718af0d9..5d3ae99f97c0fd00cfd2d03080d1db61fad6eaee 100644 --- a/src/components/Ecogesture/EcogestureModal.tsx +++ b/src/components/Ecogesture/EcogestureModal.tsx @@ -1,34 +1,26 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { useClient } from 'cozy-client' +import React, { useState, useEffect } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useRecoilState } from 'recoil' import { getPicto } from 'utils/picto' import { Ecogesture } from 'models' -import { userProfileState } from 'atoms/userProfile.state' import Modal from 'components/CommonKit/Modal/Modal' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import def from 'assets/icons/visu/ecogesture/default.svg' -import UserProfileService from 'services/userProfile.service' interface EcogestureModalProps { open: boolean ecogesture: Ecogesture - unlockedEcogesture: boolean handleCloseClick: () => void } const EcogestureModal: React.FC<EcogestureModalProps> = ({ open, ecogesture, - unlockedEcogesture, handleCloseClick, }: EcogestureModalProps) => { const { t } = useI18n() const [ecogestureIcon, setEcogestureIcon] = useState(def) - const [userProfile, setUserProfile] = useRecoilState(userProfileState) - const client = useClient() async function importEcogestureIcon(id: string) { // Les svg doivent être au format id.svg @@ -43,32 +35,6 @@ const EcogestureModal: React.FC<EcogestureModalProps> = ({ } } - const updateUserProfileNotification = useCallback( - async (ecogestureList: string[]) => { - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ notificationEcogesture: ecogestureList }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) - }) - }, - [setUserProfile] - ) - - useEffect(() => { - let index = -1 - if (userProfile && userProfile.notificationEcogesture != undefined) { - index = userProfile.notificationEcogesture.indexOf(ecogesture.id) - } - if (index > -1) { - const updatedNotificationEcogestureList = [ - ...userProfile.notificationEcogesture, - ] - updatedNotificationEcogestureList.splice(index, 1) - updateUserProfileNotification(updatedNotificationEcogestureList) - } - }, []) - useEffect(() => { if (ecogesture) { importEcogestureIcon(ecogesture.id) @@ -78,25 +44,20 @@ const EcogestureModal: React.FC<EcogestureModalProps> = ({ return ( <> {ecogesture && ( - <Modal - open={open} - border={ecogesture.unlocked || unlockedEcogesture} - handleCloseClick={handleCloseClick} - > + <Modal open={open} border={true} handleCloseClick={handleCloseClick}> <div className="em-header text-14-normal-uppercase"> {t('ECOGESTURE.TITLE_ECOGESTURE')} </div> <div className="em-root"> <div className="em-content"> <div className="em-content-box-img"> - {(ecogesture.unlocked || unlockedEcogesture) && - ecogestureIcon && ( - <StyledIcon - className="icon" - icon={ecogestureIcon} - size={140} - /> - )} + {ecogestureIcon && ( + <StyledIcon + className="icon" + icon={ecogestureIcon} + size={140} + /> + )} </div> <div className="em-title text-24-bold "> {ecogesture.shortName} diff --git a/src/components/Header/CozyBar.tsx b/src/components/Header/CozyBar.tsx index d05ef4dd1a92223c613b01b7af76efca09769b66..4e907f4a00b54ddf2eed1ce989fc277ae2321c8d 100644 --- a/src/components/Header/CozyBar.tsx +++ b/src/components/Header/CozyBar.tsx @@ -1,12 +1,11 @@ import React from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useDispatch } from 'react-redux' +import { useSelector, useDispatch } from 'react-redux' +import { EcolyoState } from 'store' import { updateModalIsFeedbacksOpen } from 'store/modal/modal.actions' -import { useRecoilValue } from 'recoil' import { history } from 'components/App' import { ScreenType } from 'enum/screen.enum' -import { screenTypeState } from 'atoms/screenType.state' import BackArrowIcon from 'assets/icons/ico/back-arrow.svg' import FeedbacksIcon from 'assets/icons/ico/feedbacks.svg' @@ -22,9 +21,9 @@ const CozyBar = ({ displayBackArrow = false, }: CozyBarProps) => { const { t } = useI18n() - const { BarLeft, BarCenter, BarRight } = cozy.bar - const screenType = useRecoilValue(screenTypeState) const dispatch = useDispatch() + const { BarLeft, BarCenter, BarRight } = cozy.bar + const { screenType } = useSelector((state: EcolyoState) => state.global) const handleClickBack = () => { history.goBack() diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index a5f72a78ddeff93ebac76875d8ecd2107125866e..5e4ecd59b4f00e6a50879909fe6c418379fbf80d 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,12 +1,11 @@ import React, { useEffect, useRef } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useRecoilValue, useSetRecoilState } from 'recoil' +import { useSelector, useDispatch } from 'react-redux' +import { EcolyoState } from 'store' +import { updateModalIsFeedbacksOpen } from 'store/modal/modal.actions' import { history } from 'components/App' import { ScreenType } from 'enum/screen.enum' -import { ModalState } from 'models' -import { modalState } from 'atoms/modal.state' -import { screenTypeState } from 'atoms/screenType.state' import BackArrowIcon from 'assets/icons/ico/back-arrow.svg' import FeedbacksIcon from 'assets/icons/ico/feedbacks.svg' @@ -29,8 +28,8 @@ const Header: React.FC<HeaderProps> = ({ }: HeaderProps) => { const { t } = useI18n() const header = useRef(null) - const screenType = useRecoilValue(screenTypeState) - const setModalState = useSetRecoilState(modalState) + const dispatch = useDispatch() + const { screenType } = useSelector((state: EcolyoState) => state.global) const cozyBarHeight = 48 const headerBottomHeight = 8 @@ -40,7 +39,7 @@ const Header: React.FC<HeaderProps> = ({ } const handleClickFeedbacks = () => { - setModalState((prev: ModalState) => ({ ...prev, feedbackModal: true })) + dispatch(updateModalIsFeedbacksOpen(true)) } useEffect(() => { diff --git a/src/components/Home/HomeView.tsx b/src/components/Home/HomeView.tsx index e0d49a8380132cae380063e47aa8db5b5d0a809b..a34641b503d9938089c781445911cdc3d61e2dfa 100644 --- a/src/components/Home/HomeView.tsx +++ b/src/components/Home/HomeView.tsx @@ -1,6 +1,9 @@ import React, { useState, useEffect, useCallback } from 'react' import { useClient } from 'cozy-client' import { useRecoilState, useRecoilValue } from 'recoil' +import { useSelector, useDispatch } from 'react-redux' +import { EcolyoState } from 'store' +import { updateProfile } from 'store/profile/profile.actions' import { TimeStep } from 'enum/timeStep.enum' import { fluidStatusState, fluidTypeState } from 'atoms/fluidState.state' @@ -13,22 +16,22 @@ import Content from 'components/Content/Content' import FluidChart from 'components/FluidChart/FluidChart' import ConsumptionNavigator from 'components/ConsumptionNavigator/ConsumptionNavigator' import HomeIndicators from 'components/Home/HomeIndicators' -import ChallengeCardLink from 'components/Challenge/ChallengeCardLink' import KonnectorViewerList from 'components/Konnector/KonnectorViewerList' import OldFluidDataModal from 'components/Home/OldFluidDataModal' import { DateTime } from 'luxon' -import { userProfileState } from 'atoms/userProfile.state' -import UserProfileService from 'services/userProfile.service' + +import ProfileService from 'services/profile.service' import { FluidType } from 'enum/fluid.enum' const HomeView: React.FC = () => { const client = useClient() + const dispatch = useDispatch() + const profile = useSelector((state: EcolyoState) => state.profile) const fluidTypes = useRecoilValue(fluidTypeState) const fluidStatus = useRecoilValue(fluidStatusState) const [previousTimeStep, setPreviousTimeStep] = useRecoilState( previousTimeStepState ) - const [userProfile, setUserProfile] = useRecoilState(userProfileState) const [timeStep, setTimeStep] = useState<TimeStep>( previousTimeStep !== TimeStep.HALF_AN_HOUR ? previousTimeStep : TimeStep.DAY @@ -41,13 +44,15 @@ const HomeView: React.FC = () => { const [fluidOldData] = useState<FluidType[]>([]) const updateProfileHaveSeenOldFluidModal = useCallback(async () => { - const userProfileService = new UserProfileService(client) - await userProfileService - .updateUserProfile({ haveSeenOldFluidModal: DateTime.local() }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) + const profileService = new ProfileService(client) + await profileService + .updateProfile({ haveSeenOldFluidModal: DateTime.local() }) + .then(updatedProfile => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } }) - }, [setUserProfile]) + }, []) const setChartLoaded = () => { setChartLoading(false) @@ -71,6 +76,7 @@ const HomeView: React.FC = () => { const defineHeaderHeight = (height: number) => { setHeaderHeight(height) } + const checkFluidDataPeriod = async () => { for (const fluid of fluidStatus) { let diffInDays = 0 @@ -84,14 +90,17 @@ const HomeView: React.FC = () => { } } if (fluidOldData.length > 0) { - if (userProfile.haveSeenOldFluidModal === false) { + if (profile.haveSeenOldFluidModal === false) { //if user never has never seen the modal await updateProfileHaveSeenOldFluidModal() return setopenOldFluidDataModal(true) - } else if (userProfile.haveSeenOldFluidModal) { - const dateToCompare = userProfile.haveSeenOldFluidModal + } else if (profile.haveSeenOldFluidModal) { + const dateToCompare = profile.haveSeenOldFluidModal //if user has seen the modal since more than a day - const diff = dateToCompare.diffNow('hours').toObject().hours + const diff = + typeof dateToCompare === 'boolean' + ? 0 + : dateToCompare.diffNow('hours').toObject().hours if (diff && diff < -23) { updateProfileHaveSeenOldFluidModal() return setopenOldFluidDataModal(true) @@ -152,7 +161,6 @@ const HomeView: React.FC = () => { timeStep={timeStep} setIndicatorsLoaded={setIndicatorsLoaded} /> - <ChallengeCardLink /> </div> </Content> {fluidStatus && ( diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index fbccff671ac123609b6a0a629ddd8a05efd0777f..922c3c1923402140a1248a0de2400082ba58a49b 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,6 +1,7 @@ import React from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { NavLink } from 'react-router-dom' +import { useSelector } from 'react-redux' import ConsoIconOn from 'assets/icons/tabbar/conso/conso-on.svg' import ConsoIconOff from 'assets/icons/tabbar/conso/conso-off.svg' @@ -15,16 +16,13 @@ import ReportIconOff from 'assets/icons/tabbar/bilan/bilan-off.svg' import logoGrandLyon from 'assets/icons/tabbar/grand-lyon.svg' import logoTiga from 'assets/png/logo-TIGA.png' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import { useRecoilValue } from 'recoil' -import { - challengeNotificationState, - reportNotificationState, -} from 'atoms/notification.state' +import { EcolyoState } from 'store' export const Navbar: React.FC = () => { const { t } = useI18n() - const challengeNotification = useRecoilValue(challengeNotificationState) - const reportNotification = useRecoilValue(reportNotificationState) + const { seasonNotification, reportNotification } = useSelector( + (state: EcolyoState) => state.global + ) return ( <aside className="o-sidebar"> @@ -47,7 +45,7 @@ export const Navbar: React.FC = () => { className="c-nav-link" activeClassName="is-active" > - {challengeNotification && <div className="nb-notif">1</div>} + {seasonNotification && <div className="nb-notif">1</div>} <StyledIcon className="c-nav-icon off" icon={ChallengeIconOff} /> <StyledIcon className="c-nav-icon on" icon={ChallengeIconOn} /> {t('Nav.challenges')} diff --git a/src/components/Options/ReportOptions.tsx b/src/components/Options/ReportOptions.tsx index e4851d2fbd67bbcdf085f242dc05bbb32602ccbb..c4ba11a3c2ec73c70dbc673916682ee050e0548d 100644 --- a/src/components/Options/ReportOptions.tsx +++ b/src/components/Options/ReportOptions.tsx @@ -1,43 +1,32 @@ import React, { useCallback } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { useClient } from 'cozy-client' -import { useRecoilState } from 'recoil' -import UserProfileService from 'services/userProfile.service' -import { userProfileState } from 'atoms/userProfile.state' -import { UserProfile } from 'models' +import { useSelector, useDispatch } from 'react-redux' +import { EcolyoState } from 'store' +import { updateProfile } from 'store/profile/profile.actions' +import ProfileService from 'services/profile.service' const ReportOptions: React.FC = () => { const { t } = useI18n() const client = useClient() - const [userProfile, setUserProfile] = useRecoilState<UserProfile>( - userProfileState - ) + const dispatch = useDispatch() + const profile = useSelector((state: EcolyoState) => state.profile) - const updateUserProfileReport = useCallback( - async (value: boolean) => { - const userProfileService = new UserProfileService(client) - const reportAttributes = { - sendReportNotification: value, - haveSeenLastReport: userProfile.report.haveSeenLastReport, - monthlyReportDate: userProfile.report.monthlyReportDate, - } - try { - await userProfileService - .updateUserProfile({ report: reportAttributes }) - .then(updatedUserProfile => { - updatedUserProfile && setUserProfile(updatedUserProfile) - }) - } catch (err) { - console.log(err) - } - }, - [setUserProfile] - ) + const updateProfileReport = useCallback(async (value: boolean) => { + const profileService = new ProfileService(client) + await profileService + .updateProfile({ sendReportNotification: value }) + .then(updatedProfile => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } + }) + }, []) const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { e.target.value === 'true' - ? updateUserProfileReport(true) - : updateUserProfileReport(false) + ? updateProfileReport(true) + : updateProfileReport(false) } return ( @@ -55,8 +44,7 @@ const ReportOptions: React.FC = () => { value="true" onChange={handleChange} checked={ - userProfile && - userProfile.report.sendReportNotification === true + profile && profile.sendReportNotification === true ? true : false } @@ -71,8 +59,7 @@ const ReportOptions: React.FC = () => { value="false" onChange={handleChange} checked={ - userProfile && - userProfile.report.sendReportNotification === false + profile && profile.sendReportNotification === false ? true : false } diff --git a/src/components/Report/ChallengeItemReport.tsx b/src/components/Report/ChallengeItemReport.tsx deleted file mode 100644 index dbdad5ba65c54c9ed56f1892bf07e4770f9d5373..0000000000000000000000000000000000000000 --- a/src/components/Report/ChallengeItemReport.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useEffect, useState } from 'react' -import './challengeitemreport.scss' -import { NavLink } from 'react-router-dom' - -import { UserChallenge } from 'models' - -import StyledChallengeCard from 'components/CommonKit/Card/StyledChallengeCard' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import BlackArrowIcon from 'assets/icons/ico/black-arrow.svg' - -interface ChallengeItemReportProps { - userChallenge: UserChallenge -} - -const ChallengeItemReport: React.FC<ChallengeItemReportProps> = ({ - userChallenge, -}: ChallengeItemReportProps) => { - const [badgeSource, setBadgeSource] = useState<string>([]) - - async function importRightBadge( - id: string, - badgeStatus: number - ): Promise<string | undefined> { - if (badgeStatus !== -1) { - // Les png doivent être au format idchallenge-badgestate.png - const importedBadge = - id === 'CHA00000001' - ? await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-1.png` - ) - : await import( - /* webpackMode: "eager" */ `assets/png/badges/${id}-${badgeStatus}.png` - ) - return importedBadge.default - } - } - - useEffect(() => { - let subscribed = true - async function getBadge() { - if (userChallenge && userChallenge.challengeType) { - const badge = await importRightBadge( - userChallenge.challengeType.id, - userChallenge.badge != null ? userChallenge.badge : 0 - ) - if (subscribed && badge) { - setBadgeSource(badge) - } - } - } - getBadge() - return () => { - subscribed = false - } - }, [userChallenge.id]) - - return ( - <NavLink - className="challenge-card-link" - to={{ - pathname: `challenges/finished`, - state: userChallenge, - }} - > - <StyledChallengeCard displayBorder={true}> - <div className="challenge-card"> - <div className="challenge-card-content-left"> - <div className="challenge-card-content-badge"> - {badgeSource && ( - <img className="cp-win-badge" src={badgeSource} width={72} /> - )} - </div> - <div className="challenge-card-content-title text-18-normal"> - {userChallenge.challengeType - ? userChallenge.challengeType.title - : null} - </div> - </div> - <div className="challenge-card-content-right"> - <StyledIcon icon={BlackArrowIcon} size={18} /> - </div> - </div> - </StyledChallengeCard> - </NavLink> - ) -} - -export default ChallengeItemReport diff --git a/src/components/Report/ChallengeReport.tsx b/src/components/Report/ChallengeReport.tsx deleted file mode 100644 index 35ceaf275e19cf8d0bb7139a90b1bec53b36a533..0000000000000000000000000000000000000000 --- a/src/components/Report/ChallengeReport.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import './challengereport.scss' - -import { UserChallenge } from 'models' -import ChallengeItemReport from 'components/Report/ChallengeItemReport' - -interface ChallengeReportProps { - userChallenges: UserChallenge[] -} - -const ChallengeReport: React.FC<ChallengeReportProps> = ({ - userChallenges, -}: ChallengeReportProps) => { - return ( - <div className="challenge-root"> - {userChallenges.map((userChallenge, index) => { - return <ChallengeItemReport key={index} userChallenge={userChallenge} /> - })} - </div> - ) -} - -export default ChallengeReport diff --git a/src/components/Report/MonthlyReport.tsx b/src/components/Report/MonthlyReport.tsx index e73bfdfccf019b3257b3698023ac2e1cb8f99251..139c3388f43937997ef8252c41a6e1a7fc20f4fc 100644 --- a/src/components/Report/MonthlyReport.tsx +++ b/src/components/Report/MonthlyReport.tsx @@ -8,18 +8,16 @@ import './monthlyreport.scss' import { TimeStep } from 'enum/timeStep.enum' import { fluidTypeState } from 'atoms/fluidState.state' import { userProfileState } from 'atoms/userProfile.state' -import { PerformanceIndicator, UserChallenge } from 'models' +import { PerformanceIndicator } from 'models' import ConsumptionService from 'services/consumption.service' import PerformanceIndicatorService from 'services/performanceIndicator.service' import ConfigService from 'services/fluidConfig.service' -import ChallengeService from 'services/challenge.service' import { convertDateToMonthString } from 'utils/date' import PerformanceIndicatorContent from 'components/PerformanceIndicator/PerformanceIndicatorContent' import FluidPerformanceIndicator from 'components/PerformanceIndicator/FluidPerformanceIndicator' import DateNavigator from 'components/DateNavigator/DateNavigator' import ChartReport from 'components/Report/ChartReport' -import ChallengeReport from 'components/Report/ChallengeReport' import { FluidType } from 'enum/fluid.enum' const MonthlyReport: React.FC = () => { @@ -41,15 +39,11 @@ const MonthlyReport: React.FC = () => { compareValue: 0, percentageVariation: 0, }) - const [userChallenges, setUserChallenges] = useState<UserChallenge[] | null>( - null - ) const [isLoaded, setIsLoaded] = useState<boolean>(false) const [fluidLackOfData, setFluidLackOfData] = useState<Array<FluidType>>([]) const consumptionService = new ConsumptionService(client) const performanceIndicatorService = new PerformanceIndicatorService() - const challengeService = new ChallengeService(client) const configService = new ConfigService() const fluidConfig = configService.getFluidConfig() const timeStep = TimeStep.MONTH @@ -78,9 +72,6 @@ const MonthlyReport: React.FC = () => { fluidTypes, periods.comparisonTimePeriod ) - const userChallengesDone = await challengeService.getFinishedUserChallengesOnMonth( - reportDate.minus({ month: 1 }) - ) if (subscribed) { if (fetchedPerformanceIndicators) { const fluidLackOfDataIndicators: Array<FluidType> = [] @@ -97,9 +88,6 @@ const MonthlyReport: React.FC = () => { ) ) } - if (userChallengesDone) { - setUserChallenges(userChallengesDone) - } setIsLoaded(true) } } @@ -170,20 +158,6 @@ const MonthlyReport: React.FC = () => { ) : null })} </div> - <div> - {userChallenges && userChallenges.length > 0 ? ( - <div className="report-header text-16-normal-uppercase "> - {t('report.challenge') + - ' ' + - reportDate - .plus({ month: -1 }) - .toLocaleString({ locale: 'fr-FR', month: 'long' })} - </div> - ) : null} - {userChallenges && ( - <ChallengeReport userChallenges={userChallenges} /> - )} - </div> </div> </div> </div> diff --git a/src/components/Report/ReportView.tsx b/src/components/Report/ReportView.tsx index 30c7b154d734ede145ffadb8876d746baf0774c5..d88bea4e0d7784ca9abb68d90da940927882a9e2 100644 --- a/src/components/Report/ReportView.tsx +++ b/src/components/Report/ReportView.tsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from 'react' import { useClient } from 'cozy-client' -import { useRecoilState } from 'recoil' - -import { ReportAttributes, UserProfile } from 'models' -import { userProfileState } from 'atoms/userProfile.state' -import UserProfileService from 'services/userProfile.service' +import { useSelector, useDispatch } from 'react-redux' +import { updateProfile } from 'store/profile/profile.actions' +import { toogleReportNotification } from 'store/global/global.actions' +import { EcolyoState } from 'store' +import ProfileService from 'services/profile.service' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' @@ -13,10 +13,11 @@ import MonthlyReport from 'components/Report/MonthlyReport' const ReportView: React.FC = () => { const client = useClient() - const [userProfile, setUserProfile] = useRecoilState<UserProfile>( - userProfileState - ) const [headerHeight, setHeaderHeight] = useState<number>(0) + const { reportNotification } = useSelector( + (state: EcolyoState) => state.global + ) + const dispatch = useDispatch() const defineHeaderHeight = (height: number) => { setHeaderHeight(height) } @@ -24,20 +25,16 @@ const ReportView: React.FC = () => { useEffect(() => { let subscribed = true const updateUserProfileReport = async () => { - if (!userProfile.report.haveSeenLastReport) { - const reportAttributes: ReportAttributes = { - sendReportNotification: userProfile.report.sendReportNotification, - haveSeenLastReport: true, - monthlyReportDate: userProfile.report.monthlyReportDate, - } - const userProfileService = new UserProfileService(client) + if (reportNotification) { + const profileService = new ProfileService(client) try { - await userProfileService - .updateUserProfile({ report: reportAttributes }) - .then(updatedUserProfile => { - subscribed && - updatedUserProfile && - setUserProfile(updatedUserProfile) + await profileService + .updateProfile({ haveSeenLastReport: true }) + .then(updatedProfile => { + if (subscribed && updatedProfile) { + dispatch(updateProfile(updatedProfile)) + dispatch(toogleReportNotification(true)) + } }) } catch (err) { console.log(err) diff --git a/src/components/Report/challengeitemreport.scss b/src/components/Report/challengeitemreport.scss deleted file mode 100644 index 515b667acdd5da97c8cfe34de98032a6149b9827..0000000000000000000000000000000000000000 --- a/src/components/Report/challengeitemreport.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; - -.challenge-card-link { - color: black; - text-decoration: none; -} -.challenge-card { - cursor: pointer; - display: flex; - flex-direction: row; - width: 100%; - .challenge-card-content-left { - flex: 1; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - width: inherit; - .challenge-card-content-badge { - height: 4.5rem; - width: 4.5rem; - } - .challenge-card-content-title { - margin-left: 1rem; - color: $white; - } - } - .challenge-card-content-right { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - align-self: center; - } -} \ No newline at end of file diff --git a/src/components/Report/challengereport.scss b/src/components/Report/challengereport.scss deleted file mode 100644 index fe485d3f382f769b669a47e000ff8efc3a1568a3..0000000000000000000000000000000000000000 --- a/src/components/Report/challengereport.scss +++ /dev/null @@ -1,40 +0,0 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; - - -.challenge-root { - margin-bottom: 1rem; - .challenge-card-link { - color: black; - text-decoration: none; - } - .challenge-card { - cursor: pointer; - display: flex; - flex-direction: row; - width: 100%; - .challenge-card-content-left { - flex: 1; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - width: inherit; - .challenge-card-content-badge { - height: 4.5rem; - width: 4.5rem; - } - .challenge-card-content-title { - margin-left: 1rem; - color: $white; - } - } - .challenge-card-content-right { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - align-self: center; - } - } -} \ No newline at end of file diff --git a/src/components/Routes/Routes.tsx b/src/components/Routes/Routes.tsx index 386bdd32346af80c06d31330d0643980e9c224c0..ec47e91cc2a72d4cdc94e6481841d809fb5687b5 100644 --- a/src/components/Routes/Routes.tsx +++ b/src/components/Routes/Routes.tsx @@ -7,19 +7,7 @@ const HomeView = lazy(() => import('components/Home/HomeView')) const SingleFluidView = lazy(() => import('components/SingleFluid/SingleFluidView') ) -const ChallengeView = lazy(() => import('components/Challenge/ChallengeView')) -const LockedChallengeDetailsViewContainer = lazy(() => - import('components/Challenge/LockedChallengeDetailsViewContainer') -) -const AvailableChallengeDetailsView = lazy(() => - import('components/Challenge/AvailableChallengeDetailsView') -) -const OngoingChallengeDetailsView = lazy(() => - import('components/Challenge/OngoingChallengeDetailsView') -) -const FinishedChallengeDetailsView = lazy(() => - import('components/Challenge/FinishedChallengeDetailsView') -) + const EcogestureView = lazy(() => import('components/Ecogesture/EcogestureView') ) @@ -64,22 +52,6 @@ const Routes = () => { path="/challenges" render={({ match: { url } }) => ( <> - <Route - path={`${url}/locked`} - component={LockedChallengeDetailsViewContainer} - /> - <Route - path={`${url}/available`} - component={AvailableChallengeDetailsView} - /> - <Route - path={`${url}/ongoing`} - component={OngoingChallengeDetailsView} - /> - <Route - path={`${url}/finished`} - component={FinishedChallengeDetailsView} - /> <Route path={`${url}/`} component={SeasonView} exact /> </> )} diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index 54c2745be2b9b4fee29171f979601f444cf8e61e..b1e122f2d535cbfe70f2990d5d664a0db3a24c82 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -1,13 +1,13 @@ import React, { useState, useEffect, ComponentType, ReactNode } from 'react' import { useClient } from 'cozy-client' -import { useSetRecoilState } from 'recoil' import classNames from 'classnames' +import { useDispatch } from 'react-redux' +import { toogleReportNotification } from 'store/global/global.actions' +import { updateProfile } from 'store/profile/profile.actions' +import { useSetRecoilState } from 'recoil' import { fluidStatusState } from 'atoms/fluidState.state' import { userProfileState } from 'atoms/userProfile.state' -import { currentChallengeState } from 'atoms/challenge.state' -import { challengeNotificationState } from 'atoms/notification.state' import InitializationService from 'services/initialization.service' -import { getActualReportDate } from 'utils/date' interface SplashRootProps { fadeTimer?: number @@ -32,12 +32,8 @@ const SplashRoot = ({ splashStart: false, }) const [error, setError] = useState<Error | null>(null) - const setUserProfile = useSetRecoilState(userProfileState) + const dispatch = useDispatch() const setFluidStatusState = useSetRecoilState(fluidStatusState) - const setCurrentChallengeState = useSetRecoilState(currentChallengeState) - const setChallengeNotificationState = useSetRecoilState( - challengeNotificationState - ) const initializationService = new InitializationService(client) @@ -55,53 +51,37 @@ const SplashRoot = ({ let subscribed = true async function loadData() { try { - await initializationService.initIndex() - let profile = await initializationService.initUserProfile() + // Init index + // await initializationService.initIndex() + // Init profile and update ecogestures, seasons, report + let profile = await initializationService.initProfile() if (subscribed && profile) { - const resultChallengeType = await initializationService.initChallengeType( - profile.challengeTypeHash - ) - if ( - subscribed && - resultChallengeType.result && - resultChallengeType.userProfile - ) { - profile = resultChallengeType.userProfile - } const resultEcogesture = await initializationService.initEcogesture( profile.ecogestureHash ) if ( subscribed && resultEcogesture.result && - resultEcogesture.userProfile + resultEcogesture.profile ) { - profile = resultEcogesture.userProfile - } - - const resultReport = await initializationService.initReport( - profile.report - ) - if (subscribed && resultReport.result && resultReport.userProfile) { - profile = resultReport.userProfile + profile = resultEcogesture.profile } - setUserProfile(profile) - await initializationService.initUserChallenge() - const fluidStatus = await initializationService.initFluidStatus() - setFluidStatusState(fluidStatus) - const currentChallenge = await initializationService.initCurrentChallenge() - if (currentChallenge) { - setCurrentChallengeState(currentChallenge) - const isChallengeOver = await initializationService.isCurrentChallengeOver( - currentChallenge - ) - setChallengeNotificationState(isChallengeOver) + // TODO add initSeasons + const resultReport = await initializationService.initReport(profile) + if (subscribed && resultReport.result && resultReport.profile) { + profile = resultReport.profile } - setState(prev => ({ - ...prev, - splashStart: true, - })) + dispatch(updateProfile(profile)) + dispatch(toogleReportNotification(!profile.haveSeenLastReport)) } + // Init Fluid status + const fluidStatus = await initializationService.initFluidStatus() + setFluidStatusState(fluidStatus) + // Update state + setState(prev => ({ + ...prev, + splashStart: true, + })) } catch (err) { setError(err) } diff --git a/src/db/challengeTypeData.json b/src/db/challengeTypeData.json deleted file mode 100644 index 2be594a8afa18b8e2374bd1d4da99c5406969d21..0000000000000000000000000000000000000000 --- a/src/db/challengeTypeData.json +++ /dev/null @@ -1,135 +0,0 @@ -[ - { - "_id": "CHA00000001", - "type": 1, - "title": "Ecolyo Royal", - "description": "Connecter l'application Ecolyo à un distributeur d'énergie", - "level": 1, - "duration": { "days": 0 }, - "fluidTypes": [0, 1, 2], - "relationships": { - "availableEcogestures": { - "data": [ - { "_id": "0085", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0092", "_type": "com.grandlyon.ecolyo.ecogesture" } - ] - } - } - }, - { - "_id": "CHA00000002", - "type": 0, - "title": "Coques en stock", - "description": "Et si dans les 7 prochains jours vous réussissiez à consommer moins que dans les 7 derniers ?", - "level": 2, - "duration": { "days": 7 }, - "fluidTypes": [0, 1, 2], - "relationships": { - "availableEcogestures": { - "data": [ - { "_id": "0032", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0034", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0041", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0042", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0043", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0045", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0050", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0058", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0064", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0066", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0071", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0078", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0082", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0093", "_type": "com.grandlyon.ecolyo.ecogesture" } - ] - } - } - }, - { - "_id": "CHA00000003", - "type": 0, - "title": "Méga Coques en stock", - "description": "Et si dans les 4 prochaines semaines vous réussissiez à consommer moins que dans les 4 dernières ?", - "level": 3, - "duration": { "days": 28 }, - "fluidTypes": [0, 1, 2], - "relationships": { - "availableEcogestures": { - "data": [ - { "_id": "0032", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0034", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0041", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0042", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0043", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0045", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0050", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0058", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0064", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0066", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0071", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0078", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0082", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0093", "_type": "com.grandlyon.ecolyo.ecogesture" } - ] - } - } - }, - { - "_id": "CHA00000004", - "type": 0, - "title": "Winter is leaving", - "description": "Et si dans les 7 prochains jours vous réussissiez à consommer moins que dans les 7 derniers", - "level": 901, - "duration": { "days": 7 }, - "fluidTypes": [0, 1, 2], - "relationships": { - "availableEcogestures": { - "data": [ - { "_id": "0032", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0034", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0041", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0042", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0043", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0045", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0050", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0058", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0064", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0066", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0071", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0078", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0082", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0093", "_type": "com.grandlyon.ecolyo.ecogesture" } - ] - } - } - }, - { - "_id": "CHA00000005", - "type": 0, - "title": "Méga Winter is leaving", - "description": "Et si dans les 7 prochains jours vous réussissiez à consommer moins que dans les 7 derniers", - "level": 902, - "duration": { "days": 7 }, - "fluidTypes": [0, 1, 2], - "relationships": { - "availableEcogestures": { - "data": [ - { "_id": "0032", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0034", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0041", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0042", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0043", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0045", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0050", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0058", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0064", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0066", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0071", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0078", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0082", "_type": "com.grandlyon.ecolyo.ecogesture" }, - { "_id": "0093", "_type": "com.grandlyon.ecolyo.ecogesture" } - ] - } - } - } -] diff --git a/src/db/profileData.json b/src/db/profileData.json new file mode 100644 index 0000000000000000000000000000000000000000..2c1f724cba15bf6da77ccdca13077ef754ffac45 --- /dev/null +++ b/src/db/profileData.json @@ -0,0 +1,12 @@ +[ + { + "challengeTypeHash": "", + "ecogestureHash": "", + "isFirstConnection": true, + "haveSeenFavoriteModal": false, + "haveSeenOldFluidModal": false, + "haveSeenLastReport": true, + "sendReportNotification": false, + "monthlyReportDate": "0000-01-01T00:00:00.000Z" + } +] diff --git a/src/db/userChallengeData.json b/src/db/userChallengeData.json deleted file mode 100644 index 374af7e673198732d20fddba168f2cd51348681d..0000000000000000000000000000000000000000 --- a/src/db/userChallengeData.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "startingDate": "0001-01-01T00:00:00.000Z", - "endingDate": "9999-01-01T00:00:00.000Z", - "state": 0, - "maxEnergy": -1, - "currentEnergy": -1, - "relationships": { - "challengeType": { - "data": { - "_id": "CHA00000001", - "_type": "com.grandlyon.ecolyo.challengetype" - } - }, - "selectedEcogestures": { - "data": [ - { - "_id": "0085", - "_type": "com.grandlyon.ecolyo.ecogesture" - }, - { - "_id": "0092", - "_type": "com.grandlyon.ecolyo.ecogesture" - } - ] - } - } - } -] diff --git a/src/db/userProfileData.json b/src/db/userProfileData.json deleted file mode 100644 index 5ed06d03662c7c3d2882cb975258a3bde259f285..0000000000000000000000000000000000000000 --- a/src/db/userProfileData.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "level": 1, - "challengeTypeHash": "", - "ecogestureHash": "", - "haveSeenFavoriteModal": false, - "isFirstConnection": true, - "haveSeenOldFluidModal": false, - "notificationEcogesture": ["0085", "0092"], - "report": { - "sendReportNotification": false, - "haveSeenLastReport": true, - "monthlyReportDate": "0000-01-01T00:00:00.000Z" - } - } -] diff --git a/src/doctypes/com-grandlyon-ecolyo-challengetype.ts b/src/doctypes/com-grandlyon-ecolyo-challengetype.ts deleted file mode 100644 index fb4644f51a9e754d159f5b8e708d721ab3e942bf..0000000000000000000000000000000000000000 --- a/src/doctypes/com-grandlyon-ecolyo-challengetype.ts +++ /dev/null @@ -1 +0,0 @@ -export const CHALLENGETYPE_DOCTYPE = 'com.grandlyon.ecolyo.challengetype' diff --git a/src/doctypes/com-grandlyon-ecolyo-profile.ts b/src/doctypes/com-grandlyon-ecolyo-profile.ts new file mode 100644 index 0000000000000000000000000000000000000000..b68bb827fc5d49daea0890dd3a097bbde7fd9f0c --- /dev/null +++ b/src/doctypes/com-grandlyon-ecolyo-profile.ts @@ -0,0 +1 @@ +export const PROFILE_DOCTYPE = 'com.grandlyon.ecolyo.profile' diff --git a/src/doctypes/com-grandlyon-ecolyo-userchallenge.ts b/src/doctypes/com-grandlyon-ecolyo-userchallenge.ts deleted file mode 100644 index d9e45fded19d3dc3cd60cd8cb949f292346679dd..0000000000000000000000000000000000000000 --- a/src/doctypes/com-grandlyon-ecolyo-userchallenge.ts +++ /dev/null @@ -1 +0,0 @@ -export const USERCHALLENGE_DOCTYPE = 'com.grandlyon.ecolyo.userchallenge' diff --git a/src/doctypes/com-grandlyon-ecolyo-userprofile.ts b/src/doctypes/com-grandlyon-ecolyo-userprofile.ts deleted file mode 100644 index 4f24412b255f074cffc2adf29933874f5fa1ed81..0000000000000000000000000000000000000000 --- a/src/doctypes/com-grandlyon-ecolyo-userprofile.ts +++ /dev/null @@ -1 +0,0 @@ -export const USERPROFILE_DOCTYPE = 'com.grandlyon.ecolyo.userprofile' diff --git a/src/doctypes/index.ts b/src/doctypes/index.ts index 07e0993b95e2278f74dfb24acb326dcfd6c35282..b144eb622874e4f5141b80383cc3113f335eae63 100644 --- a/src/doctypes/index.ts +++ b/src/doctypes/index.ts @@ -6,10 +6,8 @@ import { KONNECTORS_DOCTYPE } from './io-cozy-konnectors' import { ACCOUNTS_DOCTYPE } from './io-cozy-accounts' import { JOBS_DOCTYPE } from './io-cozy-jobs' -import { CHALLENGETYPE_DOCTYPE } from './com-grandlyon-ecolyo-challengetype' -import { USERCHALLENGE_DOCTYPE } from './com-grandlyon-ecolyo-userchallenge' import { ECOGESTURE_DOCTYPE } from './com-grandlyon-ecolyo-ecogesture' -import { USERPROFILE_DOCTYPE } from './com-grandlyon-ecolyo-userprofile' +import { PROFILE_DOCTYPE } from './com-grandlyon-ecolyo-profile' // the documents schema, necessary for CozyClient const doctypes = { @@ -43,37 +41,13 @@ const doctypes = { attributes: {}, relationships: {}, }, - challengetype: { - doctype: CHALLENGETYPE_DOCTYPE, - attributes: {}, - relationships: { - availableEcogestures: { - doctype: ECOGESTURE_DOCTYPE, - type: 'has-many', - }, - }, - }, - userchallenge: { - doctype: USERCHALLENGE_DOCTYPE, - attributes: {}, - relationships: { - selectedEcogestures: { - doctype: ECOGESTURE_DOCTYPE, - type: 'has-many', - }, - challengeType: { - doctype: CHALLENGETYPE_DOCTYPE, - type: 'has-one', - }, - }, - }, ecogesture: { doctype: ECOGESTURE_DOCTYPE, attributes: {}, relationships: {}, }, - userprofile: { - doctype: USERPROFILE_DOCTYPE, + profile: { + doctype: PROFILE_DOCTYPE, attributes: {}, relationships: {}, }, @@ -102,7 +76,5 @@ export * from './io-cozy-accounts' export * from './io-cozy-triggers' export * from './io-cozy-jobs' -export * from './com-grandlyon-ecolyo-challengetype' -export * from './com-grandlyon-ecolyo-userchallenge' export * from './com-grandlyon-ecolyo-ecogesture' -export * from './com-grandlyon-ecolyo-userprofile' +export * from './com-grandlyon-ecolyo-profile' diff --git a/src/models/challenge.model.ts b/src/models/challenge.model.ts deleted file mode 100644 index 7306de6fb21d528d2e29e188e4e16ba40a52e460..0000000000000000000000000000000000000000 --- a/src/models/challenge.model.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { DateTime, Duration } from 'luxon' -import { FluidType } from 'enum/fluid.enum' -import { BadgeState, ChallengeState, TypeChallenge } from 'enum/challenge.enum' -import { Ecogesture } from './ecogesture.model' - -export interface ChallengeType { - id: string - type: TypeChallenge - title: string - description: string - level: number - duration: Duration - fluidTypes: FluidType[] - availableEcogestures: Ecogesture[] -} - -export interface UserChallenge { - id?: string - startingDate: DateTime - endingDate: DateTime - state: ChallengeState - selectedEcogestures: Ecogesture[] - challengeType: ChallengeType | null - maxEnergy: number - currentEnergy: number - badge: BadgeState | null - fluidTypes: FluidType[] -} diff --git a/src/models/global.model.ts b/src/models/global.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..0dddf2c336b2720d60476c1c66fecea2233320ca --- /dev/null +++ b/src/models/global.model.ts @@ -0,0 +1,7 @@ +import { ScreenType } from 'enum/screen.enum' + +export interface GlobalState { + screenType: ScreenType + seasonNotification: boolean + reportNotification: boolean +} diff --git a/src/models/index.ts b/src/models/index.ts index 5cd40965db4009822d828c0e2e4c5159c1658f7a..9260f60bd223e30a149c6a20f1d2907b4285dcff 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -5,9 +5,11 @@ export * from './datachart.model' export * from './dataload.model' export * from './ecogesture.model' export * from './fluid.model' +export * from './global.model' export * from './indicator.model' export * from './konnector.model' export * from './modal.model' +export * from './profile.model' export * from './report.model' export * from './timePeriod.model' export * from './trigger.model' diff --git a/src/models/userProfile.model.ts b/src/models/profile.model.ts similarity index 56% rename from src/models/userProfile.model.ts rename to src/models/profile.model.ts index 237cc7541c3c5263d40e74f01ffc79b6b5ed4b7a..5a02cd63311a652b2945724727cae01817450104 100644 --- a/src/models/userProfile.model.ts +++ b/src/models/profile.model.ts @@ -1,14 +1,15 @@ import { DateTime } from 'luxon' -import { ReportAttributes } from './report.model' -export interface UserProfile { +export interface Profile { id: string - level: number challengeTypeHash: string ecogestureHash: string - haveSeenFavoriteModal: boolean isFirstConnection: boolean + haveSeenFavoriteModal: boolean + haveSeenLastReport: boolean haveSeenOldFluidModal: DateTime | boolean - notificationEcogesture: string[] - report: ReportAttributes + sendReportNotification: boolean + monthlyReportDate: DateTime + _id?: string + _rev?: string } diff --git a/src/services/challenge.service.ts b/src/services/challenge.service.ts deleted file mode 100644 index 16bb0f4d4cf40eb68cae8bc30707e24965bf2547..0000000000000000000000000000000000000000 --- a/src/services/challenge.service.ts +++ /dev/null @@ -1,761 +0,0 @@ -import { DateTime } from 'luxon' -import { Client } from 'cozy-client' - -import { - CHALLENGETYPE_DOCTYPE, - ECOGESTURE_DOCTYPE, - USERCHALLENGE_DOCTYPE, -} from 'doctypes' - -import UserProfileService from 'services/userProfile.service' -import ChallengeMapperService, { - UserChallengeEntity, - ChallengeTypeEntity, -} from 'services/challengeMapper.service' -import { FluidType } from 'enum/fluid.enum' -import { ChallengeState, TypeChallenge, BadgeState } from 'enum/challenge.enum' -import { TimeStep } from 'enum/timeStep.enum' -import { Ecogesture, UserProfile, UserChallenge, ChallengeType } from 'models' -import ConsumptionService from 'services/consumption.service' -import PerformanceIndicatorService from 'services/performanceIndicator.service' -import { getCompareChallengePeriod, getLagDays } from 'utils/date' - -export default class ChallengeService { - private readonly _client: Client - private readonly _challengeMapper: ChallengeMapperService - - constructor(_client: Client) { - this._client = _client - this._challengeMapper = new ChallengeMapperService() - } - - public async updateChallenge( - id: string | undefined, - attributes: { - [key: string]: string | boolean | number | ChallengeState - } - ): Promise<UserChallenge | null> { - try { - const { data: userchallenge } = await this._client - .query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .where({ _id: id }) - .limitBy(1) - ) - .then(async ({ data }: any) => { - const doc = data[0] - return await this._client.save({ - ...doc, - ...attributes, - }) - }) - return userchallenge ? userchallenge : null - } catch (err) { - console.log(err) - throw err - } - } - - public async getMaxEnergy( - challenge: UserChallenge, - challengeFluidTypes: FluidType[] - ): Promise<number> { - const period = getCompareChallengePeriod(challenge, challengeFluidTypes) - try { - if (challenge && challenge.challengeType) { - const consumptionService = new ConsumptionService(this._client) - const fetchedPerformanceIndicators = await consumptionService.getPerformanceIndicators( - period, - TimeStep.DAY, - challengeFluidTypes - ) - const performanceIndicatorService = new PerformanceIndicatorService() - const maxEnergy = performanceIndicatorService.aggregatePerformanceIndicators( - fetchedPerformanceIndicators - ) - if (maxEnergy && maxEnergy.value && maxEnergy.value > 0) { - return maxEnergy.value - } - } - return -1 - } catch (error) { - console.log(error) - return -1 - } - } - - public async setMaxEnergy( - challenge: UserChallenge, - challengeFluidTypes: FluidType[] - ): Promise<number> { - const maxEnergy = await this.getMaxEnergy(challenge, challengeFluidTypes) - if (maxEnergy && maxEnergy > 0) { - try { - await this.updateChallenge(challenge.id, { - maxEnergy: maxEnergy, - }) - return maxEnergy - } catch (error) { - console.log(error) - return -1 - } - } - return -1 - } - - public async getSpentEnergy( - challenge: UserChallenge, - challengeFluidTypes: FluidType[] - ): Promise<number> { - const lagDays = getLagDays(challengeFluidTypes) - if (DateTime.local() > challenge.startingDate.plus({ days: lagDays })) { - const consumptionService = new ConsumptionService(this._client) - const startDate = challenge.startingDate - let endDate = DateTime.local() - .plus({ days: -lagDays }) - .endOf('day') - if (await this.isChallengeOver(challenge, challengeFluidTypes)) { - endDate = challenge.endingDate.plus({ days: -1 }).endOf('day') - } - const period = { startDate, endDate } - try { - if (challenge && challenge.challengeType) { - const fetchedPerformanceIndicators = await consumptionService.getPerformanceIndicators( - period, - TimeStep.DAY, - challengeFluidTypes - ) - const performanceIndicatorService = new PerformanceIndicatorService() - const spentEnergy = performanceIndicatorService.aggregatePerformanceIndicators( - fetchedPerformanceIndicators - ) - if (spentEnergy && spentEnergy.value && spentEnergy.value >= 0) { - return spentEnergy.value - } - } - } catch (error) { - console.log(error) - return 0 - } - } - return 0 - } - - public async setSpentEnergy( - challenge: UserChallenge, - challengeFluidTypes: FluidType[] - ): Promise<number> { - const spentEnergy = await this.getSpentEnergy( - challenge, - challengeFluidTypes - ) - if (spentEnergy && spentEnergy >= 0) { - try { - await this.updateChallenge(challenge.id, { - currentEnergy: spentEnergy, - }) - - return spentEnergy - } catch (error) { - console.log(error) - return 0 - } - } - return 0 - } - - public async isChallengeOver( - challenge: UserChallenge, - fluidTypes: FluidType[] - ) { - if (challenge) { - const typeChallenge = challenge.challengeType - ? challenge.challengeType.type - : 0 - return this.isChallengeOverByDate( - challenge.endingDate, - fluidTypes, - typeChallenge - ) - } else { - return false - } - } - - public async isChallengeOverByDate( - endingDate: DateTime, - fluidTypes: FluidType[], - typeChallenge: TypeChallenge - ) { - const lagDays = getLagDays(fluidTypes) - const endDate = - typeChallenge === TypeChallenge.CHALLENGE - ? endingDate.plus({ days: lagDays - 1 }).startOf('day') - : endingDate - if (DateTime.local() > endDate) { - return true - } else { - return false - } - } - - public getTheRightBadge( - spentEnergy: number | null, - maxEnergy: number | null - ) { - if (spentEnergy && maxEnergy) { - if (spentEnergy < maxEnergy) { - return 1 - } else { - return 0 - } - } - } - - public async setTheRightBadge(challenge: UserChallenge): Promise<boolean> { - let badge: BadgeState | null = null - if (challenge.challengeType) { - if (challenge.challengeType.type === 0) { - badge = - challenge.currentEnergy < challenge.maxEnergy - ? BadgeState.SUCCESS - : BadgeState.FAILED - } else if (challenge.challengeType.type === 1) { - badge = BadgeState.SUCCESS - } - try { - await this._client - .query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .where({ _id: challenge.id }) - .limitBy(1) - ) - .then(async ({ data }) => { - const doc = data[0] - await this._client.save({ - ...doc, - badge: badge, - }) - }) - return true - } catch (error) { - return false - } - } - return false - } - - public async endChallenge( - challenge: UserChallenge, - fluidTypes: FluidType[] - ): Promise<boolean> { - try { - if (challenge && challenge.challengeType) { - if (await this.isChallengeOver(challenge, fluidTypes)) { - if (challenge.challengeType.type === TypeChallenge.ACHIEVEMENT) { - await this.updateChallenge(challenge.id, { - state: ChallengeState.FINISHED, - }) - const userProfileService = new UserProfileService(this._client) - await userProfileService.updateUserProfile({ - level: challenge.challengeType.level + 1, - }) - } else { - if ( - this.getTheRightBadge( - challenge.currentEnergy, - challenge.maxEnergy - ) - ) { - const userProfileService = new UserProfileService(this._client) - await userProfileService.updateUserProfile({ - level: challenge.challengeType.level + 1, - }) - } - await this.updateChallenge(challenge.id, { - state: ChallengeState.FINISHED, - }) - } - } - } - return true - } catch (err) { - console.log(err) - throw err - } - } - - /* - * Return the date of the first day for which - * we can calculate the data in function of configured fluidTypes - */ - public getViewingDate = (challenge: UserChallenge): DateTime => { - const startingDate = challenge.startingDate - if (challenge && challenge.fluidTypes && challenge.fluidTypes.length > 0) { - const lagDays = getLagDays(challenge.fluidTypes) - return startingDate.plus({ days: lagDays }) - } else { - return startingDate - } - } - - public async getAllChallengeTypeEntities(): Promise< - ChallengeTypeEntity[] | null - > { - let challengeTypeEntities: ChallengeTypeEntity[] | null = null - - const challengeTypesresult = await this._client.query( - this._client.find(CHALLENGETYPE_DOCTYPE) - ) - challengeTypeEntities = challengeTypesresult.data - ? challengeTypesresult.data - : null - - if (!challengeTypeEntities) return null - - return challengeTypeEntities - } - - public async deleteAllChallengeTypeEntities(): Promise<boolean> { - try { - const challengeType = await this.getAllChallengeTypeEntities() - if (!challengeType) return true - for (let index = 0; index < challengeType.length; index++) { - await this._client.destroy(challengeType[index]) - } - return true - } catch (err) { - console.log(err) - throw err - } - } - - public async getAvailableChallenges( - fluidTypes?: FluidType[] - ): Promise<ChallengeType[] | null> { - const userProfileService = new UserProfileService(this._client) - const userProfile: UserProfile | null = await userProfileService.getUserProfile() - - if (fluidTypes && fluidTypes.length === 0) { - fluidTypes = [0, 1, 2] - } - - if (!userProfile) return null - const predicate = this.getAvailableChallengesPredicate( - fluidTypes, - userProfile && userProfile.level - ) - - const challengeTypesresult = await this._client.query( - this._client - .find(CHALLENGETYPE_DOCTYPE) - .where(predicate) - .include(['availableEcogestures']) - .sortBy([{ level: 'desc' }]) - ) - - const challengeTypeEntities: - | ChallengeTypeEntity[] - | null = challengeTypesresult.data ? challengeTypesresult.data : null - - const challengeTypeEntityRelationships: - | object - | null = challengeTypesresult.included - ? challengeTypesresult.included - : null - - if (!challengeTypeEntities || challengeTypeEntities.length === 0) - return null - const unlockedEcogestures = await this.getUnlockedEcogestures() - const challengeTypes = this._challengeMapper.mapToChallengeTypes( - challengeTypeEntities, - challengeTypeEntityRelationships, - unlockedEcogestures || undefined - // fluidTypes - ) - return challengeTypes - } - - public async updateCurrentChallengeProgress( - challenge: UserChallenge - ): Promise<UserChallenge | null> { - try { - if ( - challenge && - challenge.challengeType && - challenge.challengeType.type === TypeChallenge.CHALLENGE - ) { - if (challenge.maxEnergy === -1 && challenge.fluidTypes) { - const maxEnergyResult = await this.setMaxEnergy( - challenge, - challenge.fluidTypes - ) - if (maxEnergyResult > 0) { - challenge.maxEnergy = maxEnergyResult - } - } - // Check if we are in the viewing timezone for current challenge - const viewingDate = this.getViewingDate(challenge) - if (DateTime.local() >= viewingDate && challenge.fluidTypes) { - const currentEnergyResult = await this.setSpentEnergy( - challenge, - challenge.fluidTypes - ) - if (currentEnergyResult) { - challenge.currentEnergy = currentEnergyResult - } - } - } - return challenge - } catch (error) { - console.log('Context error: ', error) - throw error - } - } - - public async startChallenge( - challenge: ChallengeType, - fluidTypes: FluidType[], - selectedEcogestes: Ecogesture[] - ): Promise<UserChallenge | null> { - try { - // Ensure that none challenge is already on going - const ongoingChallenge = await this.getCurrentChallenge() - if (!ongoingChallenge) { - const startDate = DateTime.utc().startOf('day') - - const userChallenge: UserChallenge = { - startingDate: startDate, - endingDate: startDate.plus(challenge.duration), - state: ChallengeState.ONGOING, - selectedEcogestures: selectedEcogestes, - challengeType: challenge, - maxEnergy: -1, - currentEnergy: -1, - badge: -1, - fluidTypes: fluidTypes, - } - - await this._client.create( - USERCHALLENGE_DOCTYPE, - this._challengeMapper.mapFromUserChallenge(userChallenge) - ) - - // Retrive the challenge from base to have the userChallengeEntityRelationships - let createdUserChallenge = await this.getCurrentChallenge() - if (createdUserChallenge) { - // Update challenge progess - createdUserChallenge = await this.updateCurrentChallengeProgress( - createdUserChallenge - ) - } - return createdUserChallenge - } else { - return null - } - } catch (err) { - console.log(err) - throw err - } - } - - public async getAllUserChallenges( - withEcogestures = true - ): Promise<UserChallenge[] | null> { - const relationShipsToInclude = ['challengeType'] - if (withEcogestures) relationShipsToInclude.push('selectedEcogestures') - - const resultUserChallenge = await this._client.query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .include(relationShipsToInclude) - .where({ - state: { - $gte: ChallengeState.ONGOING, - $lt: ChallengeState.ABANDONED, - }, - }) - .sortBy([{ endingDate: 'desc' }]) - ) - - if (resultUserChallenge && !resultUserChallenge.data[0]) { - return [] - } - - const userChallengeEntitites: - | UserChallengeEntity[] - | null = resultUserChallenge.data ? resultUserChallenge.data : null - - const userChallengeEntityRelationships: - | object - | null = resultUserChallenge.included - ? resultUserChallenge.included - : null - - if (!userChallengeEntitites || userChallengeEntitites.length === 0) - return null - const userChallenges: UserChallenge[] = [] - - userChallengeEntitites.forEach(userChallengeEntitity => { - userChallenges.push( - this._challengeMapper.mapToUserChallenge( - userChallengeEntitity, - userChallengeEntityRelationships - ) - ) - }) - - return userChallenges - } - - public async getFinishedUserChallengesOnMonth( - date: DateTime - ): Promise<UserChallenge[]> { - try { - const startDate = date.startOf('month') - const endDate = date.endOf('month') - const resultUserChallenge = await this._client.query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .include(['challengeType', 'selectedEcogestures']) - .where({ - state: { - $eq: ChallengeState.FINISHED, - }, - endingDate: { - $gte: startDate.toISO(), - $lte: endDate.toISO(), - }, - }) - .sortBy([{ endingDate: 'desc' }]) - ) - - if (resultUserChallenge && !resultUserChallenge.data[0]) { - return [] - } - - const userChallengeEntitites: - | UserChallengeEntity[] - | null = resultUserChallenge.data ? resultUserChallenge.data : null - - const userChallengeEntityRelationships: - | object - | null = resultUserChallenge.included - ? resultUserChallenge.included - : null - - if (!userChallengeEntitites || userChallengeEntitites.length === 0) - return [] - const userChallenges: UserChallenge[] = [] - - userChallengeEntitites.forEach(userChallengeEntitity => { - userChallenges.push( - this._challengeMapper.mapToUserChallenge( - userChallengeEntitity, - userChallengeEntityRelationships - ) - ) - }) - return userChallenges - } catch (error) { - console.log(error) - throw error - } - } - - public async getCurrentChallenge( - withEcogestures = true - ): Promise<UserChallenge | null> { - const relationShipsToInclude = ['challengeType'] - if (withEcogestures) relationShipsToInclude.push('selectedEcogestures') - - const resultUserChallenge = await this._client.query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .where({ - state: { - $eq: ChallengeState.ONGOING, - }, - }) - .include(relationShipsToInclude) - .limitBy(1) - ) - - const userChallengeEntitites: - | UserChallengeEntity[] - | null = resultUserChallenge.data ? resultUserChallenge.data : null - - const userChallengeEntityRelationships: - | object - | null = resultUserChallenge.included - ? resultUserChallenge.included - : null - - if (!userChallengeEntitites || userChallengeEntitites.length === 0) - return null - - const userChallenge = this._challengeMapper.mapToUserChallenge( - userChallengeEntitites[0], - userChallengeEntityRelationships - ) - return userChallenge - } - - public async cancelChallenge(id: string): Promise<boolean> { - try { - await this.updateChallenge(id, { - state: ChallengeState.ABANDONED, - }) - return true - } catch (err) { - console.log(err) - throw err - } - } - - public async getUnlockedEcogestures(): Promise<string[] | null> { - const relationShipsToInclude = ['selectedEcogestures'] - const ecogestures = await this._client.query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .where({ - state: { - $lte: ChallengeState.FINISHED, - }, - }) - .include(relationShipsToInclude) - ) - if (ecogestures.data.length === 0) return null - const unlocked = ecogestures.data.map( - x => x.relationships.selectedEcogestures.data - ) - - return unlocked.flat().map(eg => eg._id) - } - - public async getAllEcogestures(): Promise<Ecogesture[] | null> { - const ecogestures = await this._client.query( - this._client.find(ECOGESTURE_DOCTYPE) - ) - if (!ecogestures) return null - - return ecogestures.data - } - - public getAvailableChallengesPredicate( - fluidTypes?: FluidType[], - level?: number - ) { - let predicate = {} - - let fluidTypePredicate = {} - // the predicate changes if the fluidType Array has more than 1 element - if (fluidTypes) { - if (fluidTypes.length >= 2) { - const orClauses: any = [] - fluidTypes.forEach(fluidType => - orClauses.push({ - $elemMatch: { - $eq: fluidType, - }, - }) - ) - fluidTypePredicate = { $or: [...orClauses] } - } else if (fluidTypes.length == 1) { - fluidTypePredicate = { - $elemMatch: { - $eq: fluidTypes[0], - }, - } - } - } - - let levelPredicate = {} - if (level) { - levelPredicate = { - $gte: level, - } - } else { - levelPredicate = { - $gt: 0, - } - } - - if (fluidTypePredicate) - predicate = { - ...predicate, - fluidTypes: fluidTypePredicate, - } - - if (levelPredicate) predicate = { ...predicate, level: levelPredicate } - - return predicate - } - - public async checkAchievement(id: string): Promise<UserChallenge | null> { - const challenge = await this.getCurrentChallenge() - if ( - challenge && - challenge.challengeType && - challenge.challengeType.type === TypeChallenge.ACHIEVEMENT && - challenge.challengeType.id === id - ) { - // Achievement is on going, we should update the endingDate - try { - const endDate = DateTime.local().startOf('day') - await this._client - .query( - this._client - .find(USERCHALLENGE_DOCTYPE) - .where({ _id: challenge.id }) - .limitBy(1) - ) - .then(async ({ data }) => { - const doc = data[0] - await this._client.save({ - ...doc, - endingDate: endDate, - }) - }) - challenge.endingDate = endDate - return challenge - } catch (error) { - console.log(error) - return null - } - } - return null - } - - /* - * If the challenge is over - * Set the rigth badge and return true - * else return false - */ - public async isCurrentChallengeOver( - challenge: UserChallenge, - fluidTypes: FluidType[] - ): Promise<boolean> { - try { - const typeChallenge = challenge.challengeType - ? challenge.challengeType.type - : 0 - const isOver = await this.isChallengeOverByDate( - challenge.endingDate, - fluidTypes, - typeChallenge - ) - if (isOver) { - await this.setTheRightBadge(challenge) - return true - } else { - return false - } - } catch (error) { - console.log(error) - throw error - } - } -} diff --git a/src/services/challengeMapper.service.ts b/src/services/challengeMapper.service.ts deleted file mode 100644 index 56f93678412d9e74cbc25189bfb065363dac8b37..0000000000000000000000000000000000000000 --- a/src/services/challengeMapper.service.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { DateTime, Duration } from 'luxon' -import { ChallengeState, BadgeState, TypeChallenge } from 'enum/challenge.enum' -import { UserChallenge, ChallengeType, Ecogesture } from 'models' -import { FluidType } from 'enum/fluid.enum' -import { CHALLENGETYPE_DOCTYPE, ECOGESTURE_DOCTYPE } from 'doctypes' - -export class UserChallengeEntity { - _id?: string - startingDate: string - endingDate: string - state: ChallengeState - maxEnergy: number - currentEnergy: number - badge: BadgeState | null - relationships: object - fluidTypes: FluidType[] - - constructor( - startingDate: string, - endingDate: string, - state: ChallengeState, - maxEnergy: number, - currentEnergy: number, - badge: BadgeState | null, - relationships: object, - fluidTypes: FluidType[], - _id?: string - ) { - this.startingDate = startingDate - this.endingDate = endingDate - this.state = state - this.maxEnergy = maxEnergy - this.currentEnergy = currentEnergy - this.badge = badge - this._id = _id - this.relationships = relationships - this.fluidTypes = fluidTypes - } -} - -export class ChallengeTypeEntity { - _id: string - type: TypeChallenge - title: string - description: string - level: number - duration: Duration - fluidTypes: FluidType[] - relationships: object - - constructor( - _id: string, - type: TypeChallenge, - title: string, - description: string, - level: number, - duration: Duration, - fluidTypes: FluidType[], - relationships: object - ) { - this._id = _id - this.type = type - this.title = title - this.description = description - this.level = level - this.duration = duration - this.fluidTypes = fluidTypes - this.relationships = relationships - } -} - -export default class ChallengeMapperService { - public mapFromUserChallenge( - userChallenge: UserChallenge - ): UserChallengeEntity { - const mappedEcogestures = userChallenge.selectedEcogestures.map( - ecogesture => ({ - _id: ecogesture.id, - _type: ECOGESTURE_DOCTYPE, - }) - ) - - const mappedUserChallenge: UserChallengeEntity = { - startingDate: userChallenge.startingDate.toISO(), - endingDate: userChallenge.endingDate.toISO(), - state: userChallenge.state, - maxEnergy: userChallenge.maxEnergy, - currentEnergy: userChallenge.currentEnergy, - badge: userChallenge.badge, - fluidTypes: userChallenge.fluidTypes, - relationships: { - selectedEcogestures: { - data: mappedEcogestures, - }, - challengeType: { - data: { - _id: userChallenge.challengeType - ? userChallenge.challengeType.id - : '', - _type: CHALLENGETYPE_DOCTYPE, - }, - }, - }, - } - return mappedUserChallenge - } - - public mapToUserChallenge( - userChallengeEntity: UserChallengeEntity, - userChallengeEntityRelationships?: object | null - ): UserChallenge { - let selectedEcogestures: Ecogesture[] = [] - let challengeType: ChallengeType | null = null - - if (userChallengeEntityRelationships) { - const idOfEcogestures = userChallengeEntity.relationships.selectedEcogestures.data.map( - eg => eg._id - ) - const challengeTypes = userChallengeEntityRelationships.filter( - challengeType => - challengeType._type === CHALLENGETYPE_DOCTYPE && - challengeType._id === - userChallengeEntity.relationships.challengeType.data._id - ) - const EcogesturesRelationships = userChallengeEntityRelationships.filter( - eg => idOfEcogestures.includes(eg._id) - ) - selectedEcogestures = EcogesturesRelationships.filter( - relationship => relationship._type === ECOGESTURE_DOCTYPE - ) - challengeType = challengeTypes ? challengeTypes[0] : null - } - - const mappedUserChallenge: UserChallenge = { - id: userChallengeEntity._id, - startingDate: DateTime.fromISO(userChallengeEntity.startingDate).startOf( - 'day' - ), - endingDate: DateTime.fromISO(userChallengeEntity.endingDate).startOf( - 'day' - ), - state: userChallengeEntity.state, - selectedEcogestures: selectedEcogestures, - challengeType: challengeType, - maxEnergy: userChallengeEntity.maxEnergy, - currentEnergy: userChallengeEntity.currentEnergy, - badge: userChallengeEntity.badge, - fluidTypes: userChallengeEntity.fluidTypes, - } - - return mappedUserChallenge - } - - public mapToChallengeType( - challengeEntity: ChallengeTypeEntity, - challengeTypeEntityRelationships?: object | null, - unlockedEcogestures?: string[] - // fluidTypes?: FluidType[] - ): ChallengeType { - const completeAvailableEcogestures: object[] = [] - - if (challengeTypeEntityRelationships) { - for (const ecogestureType of challengeEntity.relationships - .availableEcogestures.data) { - const unfilteredEcogestures = challengeTypeEntityRelationships.filter( - relationship => - relationship._type === ECOGESTURE_DOCTYPE && - relationship._id === ecogestureType._id - ) - if (unfilteredEcogestures && unfilteredEcogestures.length === 1) - completeAvailableEcogestures.push(unfilteredEcogestures[0]) - } - } - // --> fluid dependancy for challenge - - // let filteredAvailableEcogestures = completeAvailableEcogestures.filter( - // eg => - // fluidTypes && - // (fluidTypes.includes(eg.fluidTypes[0]) || - // fluidTypes.includes(eg.fluidTypes[1]) || - // fluidTypes.includes(eg.fluidTypes[2])) - // ) - - let filteredAvailableEcogestures = completeAvailableEcogestures - - const fluidFilteredEcogestures = [...new Set(filteredAvailableEcogestures)] - - fluidFilteredEcogestures.forEach(eg => { - if (unlockedEcogestures && !unlockedEcogestures.includes(eg._id)) { - filteredAvailableEcogestures.push(eg) - } - }) - - filteredAvailableEcogestures = [...new Set(filteredAvailableEcogestures)] - - if (filteredAvailableEcogestures.length < 2) { - filteredAvailableEcogestures = completeAvailableEcogestures - } - - const randomEcogesture = - filteredAvailableEcogestures[ - Math.floor(Math.random() * filteredAvailableEcogestures.length) - ] - const randomEcogestures: Ecogesture[] = filteredAvailableEcogestures.filter( - eg => eg.pack === randomEcogesture.pack && eg.id !== randomEcogesture.id - ) - - randomEcogestures.push(randomEcogesture) - - const mappedChallengeType: ChallengeType = { - id: challengeEntity._id, - type: challengeEntity.type, - title: challengeEntity.title, - description: challengeEntity.description, - level: challengeEntity.level, - duration: challengeEntity.duration, - fluidTypes: challengeEntity.fluidTypes, - availableEcogestures: randomEcogestures, - } - - return mappedChallengeType - } - - public mapToChallengeTypes( - challengeEntities: ChallengeTypeEntity[], - challengeTypeEntityRelationships?: object | null, - unlockedEcogestures?: string[] - // fluidTypes?: FluidType[] - ): ChallengeType[] { - return challengeEntities.map(challengeEntity => - this.mapToChallengeType( - challengeEntity, - challengeTypeEntityRelationships, - unlockedEcogestures - // fluidTypes - ) - ) - } -} diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index 4fb015737a44a45de140cfcfd59816be246eeaa0..ad8f26bb6e58b4ba4ee06bff3e8c1e271ec3bf04 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -1,12 +1,11 @@ import { QueryResult } from 'cozy-client' import { DateTime } from 'luxon' -import { FluidStatus, UserChallenge, UserProfile } from 'models' +import { FluidStatus, Profile } from 'models' import InitializationService from './initialization.service' import mockClient from '../../test/__mocks__/client' import { ecogesturesData } from '../../test/__mocks__/ecogesturesData.mock' import { challengesTypeData } from '../../test/__mocks__/challengesTypeData.mock' -import { userChallengeData } from '../../test/__mocks__/userChallengeData.mock' -import { userProfileData } from '../../test/__mocks__/userProfile.mock' +import { profileData } from '../../test/__mocks__/profile.mock' import { fluidStatusData } from '../../test/__mocks__/fluidStatusData.mock' import challengeTypeData from 'db/challengeTypeData.json' import ecogestureData from 'db/ecogestureData.json' @@ -33,13 +32,13 @@ jest.mock('./account.service', () => { }) }) -const mockGetUserProfile = jest.fn() -const mockUpdateUserProfile = jest.fn() -jest.mock('./userProfile.service', () => { +const mockGetProfile = jest.fn() +const mockUpdateProfile = jest.fn() +jest.mock('./profile.service', () => { return jest.fn(() => { return { - getUserProfile: mockGetUserProfile, - updateUserProfile: mockUpdateUserProfile, + getProfile: mockGetProfile, + updateProfile: mockUpdateProfile, } }) }) @@ -132,65 +131,63 @@ describe('Initialization service', () => { }) }) - describe('initUserProfile method', () => { - it('shoud return the userProfil when existing', async () => { - mockGetUserProfile.mockResolvedValue(userProfileData) - const result: UserProfile | null = await initializationService.initUserProfile() - expect(result).toEqual(userProfileData) + describe('initProfile method', () => { + it('shoud return the profil when existing', async () => { + mockGetProfile.mockResolvedValue(profileData) + const result: Profile | null = await initializationService.initProfile() + expect(result).toEqual(profileData) }) - it('shoud create and return the userProfil when no existing', async () => { + it('shoud create and return the profil when no existing', async () => { const mockQueryResult: QueryResult<boolean> = { data: true, bookmark: '', next: false, skip: 0, } - mockGetUserProfile + mockGetProfile .mockResolvedValueOnce(null) - .mockResolvedValueOnce(userProfileData) + .mockResolvedValueOnce(profileData) mockClient.create.mockResolvedValue(mockQueryResult) - const result: UserProfile | null = await initializationService.initUserProfile() - expect(result).toEqual(userProfileData) + const result: Profile | null = await initializationService.initProfile() + expect(result).toEqual(profileData) }) - it('shoud throw error when the userProfile is not created', async () => { + it('shoud throw error when the profile is not created', async () => { const mockQueryResult: QueryResult<boolean> = { data: true, bookmark: '', next: false, skip: 0, } - mockGetUserProfile.mockResolvedValueOnce(null).mockResolvedValueOnce(null) + mockGetProfile.mockResolvedValueOnce(null).mockResolvedValueOnce(null) mockClient.create.mockResolvedValue(mockQueryResult) let error try { - await initializationService.initUserProfile() + await initializationService.initProfile() } catch (err) { error = err } - expect(error).toEqual( - new Error('initUserProfile: UserProfile not created') - ) + expect(error).toEqual(new Error('initProfile: Profile not created')) }) - it('shoud throw error when the userProfile could not be fetched', async () => { - mockGetUserProfile.mockRejectedValueOnce(new Error()) + it('shoud throw error when the profile could not be fetched', async () => { + mockGetProfile.mockRejectedValueOnce(new Error()) let error try { - await initializationService.initUserProfile() + await initializationService.initProfile() } catch (err) { error = err } expect(error).toEqual(new Error()) }) - it('shoud throw error when the userProfile failed to be created', async () => { - mockGetUserProfile.mockResolvedValueOnce(null) + it('shoud throw error when the profile failed to be created', async () => { + mockGetProfile.mockResolvedValueOnce(null) mockClient.create.mockRejectedValueOnce(new Error()) let error try { - await initializationService.initUserProfile() + await initializationService.initProfile() } catch (err) { error = err } @@ -199,19 +196,18 @@ describe('Initialization service', () => { }) describe('initChallengeType method', () => { - //Ok - it('shoud return true and userProfile when challengeType is already up to date', async () => { + it('shoud return true and profile when challengeType is already up to date', async () => { mockGetAllChallengeTypeEntities.mockResolvedValue(challengeTypeData) const result: { result: boolean - userProfile: UserProfile | null + profile: Profile | null } = await initializationService.initChallengeType( hashFile(challengeTypeData) ) - expect(result).toEqual({ result: true, userProfile: null }) + expect(result).toEqual({ result: true, profile: null }) }) - it('shoud return true and userProfil when challengeType are created', async () => { + it('shoud return true and profil when challengeType are created', async () => { mockGetAllChallengeTypeEntities .mockResolvedValueOnce(null) .mockResolvedValueOnce(challengeTypeData) @@ -222,18 +218,18 @@ describe('Initialization service', () => { skip: 0, } mockClient.create.mockResolvedValue(mockQueryResult) - const mockUserProfil = { - ...userProfileData, + const mockProfile = { + ...profileData, challengeTypeHash: hashFile(challengeTypeData), } - mockUpdateUserProfile.mockResolvedValueOnce(mockUserProfil) + mockUpdateProfile.mockResolvedValueOnce(mockProfile) const result: { result: boolean - userProfile: UserProfile | null + profile: Profile | null } = await initializationService.initChallengeType( hashFile(challengeTypeData) ) - expect(result).toEqual({ result: true, userProfile: mockUserProfil }) + expect(result).toEqual({ result: true, profile: mockProfile }) }) it('shoud throw an error when challengeType should be created and created challenge number does not match', async () => { @@ -262,11 +258,11 @@ describe('Initialization service', () => { ) }) - it('shoud throw an error when challengeType should be created and updateUserProfile failed', async () => { + it('shoud throw an error when challengeType should be created and updateProfile failed', async () => { mockGetAllChallengeTypeEntities .mockResolvedValueOnce(null) .mockResolvedValueOnce(challengeTypeData) - mockUpdateUserProfile.mockResolvedValueOnce(null) + mockUpdateProfile.mockResolvedValueOnce(null) let error try { await initializationService.initChallengeType( @@ -275,16 +271,14 @@ describe('Initialization service', () => { } catch (err) { error = err } - expect(error).toEqual( - new Error('initChallengeType: UserProfile not updated') - ) + expect(error).toEqual(new Error('initChallengeType: Profile not updated')) }) it('shoud throw an error when challengeType should be created and challenge creation failed', async () => { mockGetAllChallengeTypeEntities .mockResolvedValueOnce(null) .mockResolvedValueOnce(challengeTypeData) - mockUpdateUserProfile.mockRejectedValueOnce(new Error()) + mockUpdateProfile.mockRejectedValueOnce(new Error()) let error try { await initializationService.initChallengeType( @@ -296,7 +290,7 @@ describe('Initialization service', () => { expect(error).toEqual(new Error()) }) - it('shoud return true and userProfil when challengeType are updated', async () => { + it('shoud return true and profil when challengeType are updated', async () => { mockGetAllChallengeTypeEntities .mockResolvedValueOnce(challengeTypeData) .mockResolvedValueOnce(challengeTypeData) @@ -308,16 +302,16 @@ describe('Initialization service', () => { skip: 0, } mockClient.create.mockResolvedValue(mockQueryResult) - const mockUserProfil = { - ...userProfileData, + const mockProfile = { + ...profileData, challengeTypeHash: hashFile(challengeTypeData), } - mockUpdateUserProfile.mockResolvedValueOnce(mockUserProfil) + mockUpdateProfile.mockResolvedValueOnce(mockProfile) const result: { result: boolean - userProfile: UserProfile | null + profile: Profile | null } = await initializationService.initChallengeType('') - expect(result).toEqual({ result: true, userProfile: mockUserProfil }) + expect(result).toEqual({ result: true, profile: mockProfile }) }) it('shoud throw an error when challengeType should be updated and created challenge number does not match', async () => { @@ -345,21 +339,19 @@ describe('Initialization service', () => { ) }) - it('shoud throw an error when challengeType should be updated and updateUserProfile failed', async () => { + it('shoud throw an error when challengeType should be updated and updateProfile failed', async () => { mockGetAllChallengeTypeEntities .mockResolvedValueOnce(challengeTypeData) .mockResolvedValueOnce(challengeTypeData) mockDeleteChallengeTypeEntities.mockResolvedValue(true) - mockUpdateUserProfile.mockResolvedValueOnce(null) + mockUpdateProfile.mockResolvedValueOnce(null) let error try { await initializationService.initChallengeType('') } catch (err) { error = err } - expect(error).toEqual( - new Error('initChallengeType: UserProfile not updated') - ) + expect(error).toEqual(new Error('initChallengeType: Profile not updated')) }) it('shoud throw an error when challengeType should be updated and challenge creation failed', async () => { @@ -367,7 +359,7 @@ describe('Initialization service', () => { .mockResolvedValueOnce(challengeTypeData) .mockResolvedValueOnce(challengeTypeData) mockDeleteChallengeTypeEntities.mockResolvedValue(true) - mockUpdateUserProfile.mockRejectedValueOnce(new Error()) + mockUpdateProfile.mockRejectedValueOnce(new Error()) let error try { await initializationService.initChallengeType('') @@ -379,13 +371,13 @@ describe('Initialization service', () => { }) describe('initEcoGesture method', () => { - it('shoud return true and userProfile = null when ecogestures hash is already up to date', async () => { + it('shoud return true and profile = null when ecogestures hash is already up to date', async () => { mockGetAllEcogestures.mockResolvedValue(ecogestureData) const result: { result: boolean - userProfile: UserProfile | null + profile: Profile | null } = await initializationService.initEcogesture(hashFile(ecogestureData)) - expect(result).toEqual({ result: true, userProfile: null }) + expect(result).toEqual({ result: true, profile: null }) }) it('shoud return true and ecogestures when ecogestures are created', async () => { @@ -399,16 +391,16 @@ describe('Initialization service', () => { skip: 0, } mockClient.create.mockResolvedValue(mockQueryResult) - const mockUserProfil = { - ...userProfileData, + const mockProfile = { + ...profileData, ecogestureHash: hashFile(ecogestureData), } - mockUpdateUserProfile.mockResolvedValueOnce(mockUserProfil) + mockUpdateProfile.mockResolvedValueOnce(mockProfile) const result: { result: boolean - userProfile: UserProfile | null + profile: Profile | null } = await initializationService.initEcogesture(hashFile(ecogestureData)) - expect(result).toEqual({ result: true, userProfile: mockUserProfil }) + expect(result).toEqual({ result: true, profile: mockProfile }) }) it('shoud throw an error when ecogestures should be created and created ecogestures number does not match', async () => { @@ -435,27 +427,25 @@ describe('Initialization service', () => { ) }) - it('shoud throw an error when ecogestures should be created and updateUserProfile failed', async () => { + it('shoud throw an error when ecogestures should be created and updateProfile failed', async () => { mockGetAllEcogestures .mockResolvedValueOnce(null) .mockResolvedValueOnce(ecogestureData) - mockUpdateUserProfile.mockResolvedValueOnce(null) + mockUpdateProfile.mockResolvedValueOnce(null) let error try { await initializationService.initEcogesture(hashFile(ecogestureData)) } catch (err) { error = err } - expect(error).toEqual( - new Error('initEcogesture: UserProfile not updated') - ) + expect(error).toEqual(new Error('initEcogesture: Profile not updated')) }) it('shoud throw an error when ecogestures should be created and challenge creation failed', async () => { mockGetAllEcogestures .mockResolvedValueOnce(null) .mockResolvedValueOnce(ecogestureData) - mockUpdateUserProfile.mockRejectedValueOnce(new Error()) + mockUpdateProfile.mockRejectedValueOnce(new Error()) let error try { await initializationService.initEcogesture(hashFile(ecogestureData)) @@ -465,7 +455,7 @@ describe('Initialization service', () => { expect(error).toEqual(new Error()) }) - it('shoud return true and userProfil when ecogestures are updated', async () => { + it('shoud return true and profil when ecogestures are updated', async () => { mockGetAllEcogestures .mockResolvedValueOnce(ecogestureData) .mockResolvedValueOnce(ecogestureData) @@ -477,16 +467,16 @@ describe('Initialization service', () => { skip: 0, } mockClient.create.mockResolvedValue(mockQueryResult) - const mockUserProfil = { - ...userProfileData, + const mockProfile = { + ...profileData, ecogestureHash: hashFile(ecogestureData), } - mockUpdateUserProfile.mockResolvedValueOnce(mockUserProfil) + mockUpdateProfile.mockResolvedValueOnce(mockProfile) const result: { result: boolean - userProfile: UserProfile | null + profile: Profile | null } = await initializationService.initEcogesture('') - expect(result).toEqual({ result: true, userProfile: mockUserProfil }) + expect(result).toEqual({ result: true, profile: mockProfile }) }) it('shoud throw an error when ecogestures should be updated and created ecogestures number does not match', async () => { @@ -514,21 +504,19 @@ describe('Initialization service', () => { ) }) - it('shoud throw an error when ecogestures should be updated and updateUserProfile failed', async () => { + it('shoud throw an error when ecogestures should be updated and updateProfile failed', async () => { mockGetAllEcogestures .mockResolvedValueOnce(ecogestureData) .mockResolvedValueOnce(ecogestureData) mockDeleteAllEcogestures.mockResolvedValue(true) - mockUpdateUserProfile.mockResolvedValueOnce(null) + mockUpdateProfile.mockResolvedValueOnce(null) let error try { await initializationService.initEcogesture('') } catch (err) { error = err } - expect(error).toEqual( - new Error('initEcogesture: UserProfile not updated') - ) + expect(error).toEqual(new Error('initEcogesture: Profile not updated')) }) it('shoud throw an error when ecogestures should be updated and ecogestures creation failed', async () => { @@ -536,7 +524,7 @@ describe('Initialization service', () => { .mockResolvedValueOnce(ecogestureData) .mockResolvedValueOnce(ecogestureData) mockDeleteAllEcogestures.mockResolvedValue(true) - mockUpdateUserProfile.mockRejectedValueOnce(new Error()) + mockUpdateProfile.mockRejectedValueOnce(new Error()) let error try { await initializationService.initEcogesture('') @@ -548,131 +536,65 @@ describe('Initialization service', () => { }) describe('initReport method', () => { - it('should return true and userProfile = null when report is up to date', async () => { - const mockUserReport = { + it('should return true and profile when report is up to date', async () => { + const mockProfile = { + ...profileData, monthlyReportDate: getActualReportDate(), - haveSeenLastReport: false, - sendReportNotification: true, } + console.log(mockProfile) const result: { result: boolean - userProfile: UserProfile | null - } = await initializationService.initReport(mockUserReport) - expect(result).toEqual({ result: true, userProfile: null }) - }) - - it('should return true and userProfile when no report', async () => { - mockUpdateUserProfile.mockResolvedValueOnce(userProfileData) - const result: { - result: boolean - userProfile: UserProfile | null - } = await initializationService.initReport(null) - expect(result).toEqual({ - result: true, - userProfile: userProfileData, - }) + profile: Profile | null + } = await initializationService.initReport(mockProfile) + expect(result).toEqual({ result: true, profile: mockProfile }) }) - it('should return true and userProfile when report is not up to date', async () => { - const mockUserReport = { + it('should return true and updated profile when report is not up to date', async () => { + const mockProfile = { + ...profileData, monthlyReportDate: DateTime.fromISO('2000-10-02T00:00:00.000'), + haveSeenLastReport: true, + } + const updatedProfile = { + ...profileData, + monthlyReportDate: getActualReportDate(), haveSeenLastReport: false, - sendReportNotification: true, } - mockUpdateUserProfile.mockResolvedValueOnce(userProfileData) + mockUpdateProfile.mockResolvedValueOnce(updatedProfile) const result: { result: boolean - userProfile: UserProfile | null - } = await initializationService.initReport(mockUserReport) + profile: Profile | null + } = await initializationService.initReport(mockProfile) expect(result).toEqual({ result: true, - userProfile: userProfileData, + profile: updatedProfile, }) }) it('should throw error when report is not up to date and profile is not updated', async () => { - const mockUserReport = { + const mockProfile = { + ...profileData, monthlyReportDate: DateTime.fromISO('2000-10-02T00:00:00.000'), - haveSeenLastReport: false, - sendReportNotification: true, } - mockUpdateUserProfile.mockResolvedValueOnce(null) + mockUpdateProfile.mockResolvedValueOnce(null) let error try { - await initializationService.initReport(mockUserReport) + await initializationService.initReport(mockProfile) } catch (err) { error = err } - expect(error).toEqual(new Error('initReport: UserProfile not updated')) + expect(error).toEqual(new Error('initReport: Profile not updated')) }) it('should throw error when report is not up to date and update profile failed', async () => { - const mockUserReport = { + const mockProfile = { + ...profileData, monthlyReportDate: DateTime.fromISO('2000-10-02T00:00:00.000'), - haveSeenLastReport: false, - sendReportNotification: true, } - mockUpdateUserProfile.mockRejectedValueOnce(new Error()) + mockUpdateProfile.mockRejectedValueOnce(new Error()) let error try { - await initializationService.initReport(mockUserReport) - } catch (err) { - error = err - } - expect(error).toEqual(new Error()) - }) - }) - - describe('initUserChallenge method', () => { - it('shoud return userchallenges when userchallenges already existing', async () => { - mockGetAllUserChallenges.mockResolvedValueOnce(userChallengeData) - const result: UserChallenge[] = await initializationService.initUserChallenge() - expect(result).toEqual(userChallengeData) - }) - - it('shoud return userchallenges when userchallenge should be created', async () => { - mockGetAllUserChallenges - .mockResolvedValueOnce([]) - .mockResolvedValueOnce(userChallengeData) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - const result: UserChallenge[] = await initializationService.initUserChallenge() - expect(result).toEqual(userChallengeData) - }) - - it('shoud throw error when userchallenge should be created and userchallenge is not updated', async () => { - mockGetAllUserChallenges - .mockResolvedValueOnce([]) - .mockResolvedValueOnce(null) - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.create.mockResolvedValue(mockQueryResult) - let error - try { - await initializationService.initUserChallenge() - } catch (err) { - error = err - } - expect(error).toEqual( - new Error('initUserChallenge: UserChallenge not updated') - ) - }) - - it('shoud throw error when userchallenge failed to be created', async () => { - mockGetAllUserChallenges.mockResolvedValueOnce([]) - mockClient.create.mockRejectedValueOnce(new Error()) - let error - try { - await initializationService.initUserChallenge() + await initializationService.initReport(mockProfile) } catch (err) { error = err } @@ -747,76 +669,4 @@ describe('Initialization service', () => { expect(error).toEqual(new Error()) }) }) - - describe('initCurrentChallenge method', () => { - it('should return updated current Challenge', async () => { - mockGetCurrentChallenge.mockResolvedValueOnce(userChallengeData) - const mockUpdatedUserChallenge = { - ...userChallengeData, - maxmaxEnergy: 100, - } - mockUpdateCurrentChallengeProgress.mockResolvedValueOnce( - mockUpdatedUserChallenge - ) - const result: UserChallenge | null = await initializationService.initCurrentChallenge() - expect(result).toBe(mockUpdatedUserChallenge) - }) - - it('should return null if no current Challenge', async () => { - mockGetCurrentChallenge.mockResolvedValueOnce(null) - const result: UserChallenge | null = await initializationService.initCurrentChallenge() - expect(result).toBeNull() - }) - - it('should throw error if current challenge failed to be retrieve', async () => { - mockGetCurrentChallenge.mockRejectedValueOnce(new Error()) - let error - try { - await initializationService.initCurrentChallenge() - } catch (err) { - error = err - } - expect(error).toEqual(new Error()) - }) - - it('should throw error if current challenge failed to be updated', async () => { - mockGetCurrentChallenge.mockResolvedValueOnce(userChallengeData) - mockUpdateCurrentChallengeProgress.mockRejectedValueOnce(new Error()) - let error - try { - await initializationService.initCurrentChallenge() - } catch (err) { - error = err - } - expect(error).toEqual(new Error()) - }) - }) - - describe('isCurrentChallengeOver method', () => { - it('should return true if isCurrentChallengeOver return true', async () => { - mockIsCurrentChallengeOver.mockResolvedValueOnce(true) - const result: boolean = await initializationService.isCurrentChallengeOver( - userChallengeData - ) - expect(result).toBe(true) - }) - - it('should return false if isCurrentChallengeOver return false', async () => { - mockIsCurrentChallengeOver.mockResolvedValueOnce(false) - const result: boolean = await initializationService.isCurrentChallengeOver( - userChallengeData - ) - expect(result).toBe(false) - }) - }) - - describe('checkAchievement method', () => { - it('should return UserChallenge from the id', async () => { - mockCheckAchievement.mockResolvedValueOnce(userChallengeData) - const result: UserChallenge | null = await initializationService.checkAchievement( - '4d9403218ef13e65b2e3a8ad1700c9f0' - ) - expect(result).toEqual(userChallengeData) - }) - }) }) diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index c788df7891bb1cbafd6fc55f605fa03c155c0c95..db2148024e7be276a4c6ae84057353243ef564de 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -1,9 +1,7 @@ import { Client, Q, QueryDefinition } from 'cozy-client' import { - CHALLENGETYPE_DOCTYPE, ECOGESTURE_DOCTYPE, - USERCHALLENGE_DOCTYPE, - USERPROFILE_DOCTYPE, + PROFILE_DOCTYPE, EGL_DAY_DOCTYPE, EGL_MONTH_DOCTYPE, EGL_YEAR_DOCTYPE, @@ -15,23 +13,16 @@ import { GRDF_HOUR_DOCTYPE, GRDF_MONTH_DOCTYPE, GRDF_YEAR_DOCTYPE, + ENEDIS_HOUR_DOCTYPE, } from 'doctypes' import { FluidType } from 'enum/fluid.enum' -import { - FluidStatus, - ReportAttributes, - UserChallenge, - UserProfile, -} from 'models' +import { FluidStatus, Profile } from 'models' -import ChallengeService from 'services/challenge.service' -import challengeTypeData from 'db/challengeTypeData.json' -import userChallengeData from 'db/userChallengeData.json' import EcogestureService from 'services/ecogesture.service' import ecogestureData from 'db/ecogestureData.json' -import UserProfileService from 'services/userProfile.service' -import userProfileData from 'db/userProfileData.json' +import ProfileService from 'services/profile.service' +import profileData from 'db/profileData.json' import KonnectorStatusService from 'services/konnectorStatus.service' import KonnectorService from 'services/konnector.service' import AccountService from 'services/account.service' @@ -39,6 +30,7 @@ import FluidService from 'services/fluid.service' import { hashFile } from 'utils/hash' import { getActualReportDate } from 'utils/date' +import { TimeStep } from 'enum/timeStep.enum' export default class InitializationService { private readonly _client: Client @@ -50,19 +42,78 @@ export default class InitializationService { /* * Call a query with where clause to create the index if not exist */ - private async createIndex(doctype: string): Promise<object> { + private async createIndex( + doctype: string, + timestep: TimeStep + ): Promise<object> { + const getMongoSelector = () => { + switch (timestep) { + case TimeStep.YEAR: + return { + year: { + $lte: 9999, + }, + } + case TimeStep.MONTH: + return { + year: { + $lte: 9999, + }, + month: { + $lte: 12, + }, + } + case TimeStep.DAY: + return { + year: { + $lte: 9999, + }, + month: { + $lte: 12, + }, + day: { + $lte: 31, + }, + } + case TimeStep.HOUR: + return { + year: { + $lte: 9999, + }, + month: { + $lte: 12, + }, + day: { + $lte: 31, + }, + hour: { + $lte: 23, + }, + } + case TimeStep.HALF_AN_HOUR: + return { + year: { + $lte: 9999, + }, + month: { + $lte: 12, + }, + day: { + $lte: 31, + }, + hour: { + $lte: 23, + }, + minute: { + $let: 30, + }, + } + default: + return {} + } + } const query: QueryDefinition = Q(doctype) - .where({ - year: { - $lte: 9999, - }, - month: { - $lte: 12, - }, - day: { - $lte: 31, - }, - }) + .where(getMongoSelector()) .limitBy(1) return await this._client.query(query) } @@ -77,17 +128,18 @@ export default class InitializationService { const accountService = new AccountService(this._client) const konnectorService = new KonnectorService(this._client) await Promise.all([ - this.createIndex(EGL_DAY_DOCTYPE), - this.createIndex(EGL_MONTH_DOCTYPE), - this.createIndex(EGL_YEAR_DOCTYPE), - this.createIndex(ENEDIS_DAY_DOCTYPE), - this.createIndex(ENEDIS_MINUTE_DOCTYPE), - this.createIndex(ENEDIS_MONTH_DOCTYPE), - this.createIndex(ENEDIS_YEAR_DOCTYPE), - this.createIndex(GRDF_DAY_DOCTYPE), - this.createIndex(GRDF_HOUR_DOCTYPE), - this.createIndex(GRDF_MONTH_DOCTYPE), - this.createIndex(GRDF_YEAR_DOCTYPE), + this.createIndex(EGL_YEAR_DOCTYPE, TimeStep.YEAR), + this.createIndex(EGL_MONTH_DOCTYPE, TimeStep.MONTH), + this.createIndex(EGL_DAY_DOCTYPE, TimeStep.DAY), + this.createIndex(ENEDIS_YEAR_DOCTYPE, TimeStep.YEAR), + this.createIndex(ENEDIS_MONTH_DOCTYPE, TimeStep.MONTH), + this.createIndex(ENEDIS_DAY_DOCTYPE, TimeStep.DAY), + this.createIndex(ENEDIS_HOUR_DOCTYPE, TimeStep.HOUR), + this.createIndex(ENEDIS_MINUTE_DOCTYPE, TimeStep.HALF_AN_HOUR), + this.createIndex(GRDF_YEAR_DOCTYPE, TimeStep.YEAR), + this.createIndex(GRDF_MONTH_DOCTYPE, TimeStep.MONTH), + this.createIndex(GRDF_DAY_DOCTYPE, TimeStep.DAY), + this.createIndex(GRDF_HOUR_DOCTYPE, TimeStep.HOUR), konnectorService.createIndexKonnector(), accountService.createIndexAccount(), ]) @@ -103,35 +155,36 @@ export default class InitializationService { } /* - * Check if UserProfil exist - * If not, the UserProfil is created - * sucess return: userProfil + * Check if profil exist + * If not, the profil is created + * sucess return: profil * failure return: null */ - public async initUserProfile(): Promise<UserProfile | null> { - const userProfileService = new UserProfileService(this._client) + public async initProfile(): Promise<Profile | null> { + const profileService = new ProfileService(this._client) try { - const loadedUserProfile = await userProfileService.getUserProfile() - if (!loadedUserProfile) { + const loadedProfile = await profileService.getProfile() + if (!loadedProfile) { // Population with the data - await this._client.create(USERPROFILE_DOCTYPE, userProfileData[0]) + await this._client.create(PROFILE_DOCTYPE, profileData[0]) // Check of created document - const checkUserProfile = await userProfileService.getUserProfile() - if (checkUserProfile) { + const checkProfile = await profileService.getProfile() + if (checkProfile) { console.log( - '%c Initialization: User Profile created', + '%c Initialization: Profile created', 'background: #222; color: white' ) - return checkUserProfile + return checkProfile } else { - throw new Error('initUserProfile: UserProfile not created') + throw new Error('initProfile: Profile not created') } } else { console.log( - '%c Initialization: User Profile loaded', + '%c Initialization: Profile loaded', 'background: #222; color: white' ) - return loadedUserProfile + // TODO ensure that actual profile has all needed attributes + return loadedProfile } } catch (error) { console.log('Initialization error: ', error) @@ -139,108 +192,12 @@ export default class InitializationService { } } - public async initChallengeType( - hash: string - ): Promise<{ result: boolean; userProfile: UserProfile | null }> { - const hashChanllengeType = hashFile(challengeTypeData) - const challengeService = new ChallengeService(this._client) - const userProfileService = new UserProfileService(this._client) - - // Populate data if none challengeType exists - const loadedChallengeTypes = await challengeService.getAllChallengeTypeEntities() - if ( - !loadedChallengeTypes || - (loadedChallengeTypes && loadedChallengeTypes.length === 0) - ) { - // Populate the doctype with data - try { - for (let i = 0; i <= challengeTypeData.length - 1; i++) { - await this._client.create(CHALLENGETYPE_DOCTYPE, challengeTypeData[i]) - } - // Check of created document - const checkCount = await challengeService.getAllChallengeTypeEntities() - if ( - !checkCount || - (checkCount && checkCount.length !== challengeTypeData.length) - ) { - throw new Error( - 'initChallengeType: Created challenge type entities does not macht' - ) - } - // Update userProfil with the hash - const updatedUserProfile = await userProfileService.updateUserProfile({ - challengeTypeHash: hashChanllengeType, - }) - if (updatedUserProfile) { - console.log( - '%c Initialization: Challenge Type created', - 'background: #222; color: white' - ) - return { result: true, userProfile: updatedUserProfile } - } else { - throw new Error('initChallengeType: UserProfile not updated') - } - } catch (error) { - console.log('Initialization error: ', error) - throw error - } - } - - // Update if the hash is not the same as the one from userprofile - if (hash !== hashChanllengeType) { - // Update the doctype - try { - // Deletion of all documents - await challengeService.deleteAllChallengeTypeEntities() - // Population with the data - await Promise.all( - challengeTypeData.map(async challengeType => { - await this._client.create(CHALLENGETYPE_DOCTYPE, challengeType) - }) - ) - // Check of created document - const checkCount = await challengeService.getAllChallengeTypeEntities() - if ( - !checkCount || - (checkCount && checkCount.length !== challengeTypeData.length) - ) { - throw new Error( - 'initChallengeType: Created challenge type entities does not macht' - ) - } - // Update userProfil with the hash - const updatedUserProfile = await userProfileService.updateUserProfile({ - challengeTypeHash: hashChanllengeType, - }) - if (updatedUserProfile) { - console.log( - '%c Initialization: Challenge Type updated', - 'background: #222; color: white' - ) - return { result: true, userProfile: updatedUserProfile } - } else { - throw new Error('initChallengeType: UserProfile not updated') - } - } catch (error) { - console.log('Initialization error: ', error) - throw error - } - } else { - // Doctype already up to date - console.log( - '%c Initialization: Challenge Type loaded', - 'background: #222; color: white' - ) - return { result: true, userProfile: null } - } - } - public async initEcogesture( hash: string - ): Promise<{ result: boolean; userProfile: UserProfile | null }> { + ): Promise<{ result: boolean; profile: Profile | null }> { const hashEcogestureType = hashFile(ecogestureData) const ecogestureService = new EcogestureService(this._client) - const userProfileService = new UserProfileService(this._client) + const profileService = new ProfileService(this._client) // Populate data if none ecogesture exists const loadedEcogestures = await ecogestureService.getAllEcogestures() @@ -263,18 +220,18 @@ export default class InitializationService { 'initEcogesture: Created ecogesture type entities does not macht' ) } - // Update userProfil with the hash - const updatedUserProfile = await userProfileService.updateUserProfile({ + // Update profil with the hash + const updatedProfile = await profileService.updateProfile({ ecogestureHash: hashEcogestureType, }) - if (updatedUserProfile) { + if (updatedProfile) { console.log( '%c Initialization: Ecogesture created', 'background: #222; color: white' ) - return { result: true, userProfile: updatedUserProfile } + return { result: true, profile: updatedProfile } } else { - throw new Error('initEcogesture: UserProfile not updated') + throw new Error('initEcogesture: Profile not updated') } } catch (error) { console.log('Initialization error: ', error) @@ -282,7 +239,7 @@ export default class InitializationService { } } - // Update if the hash is not the same as the one from userprofile + // Update if the hash is not the same as the one from profile if (hash !== hashEcogestureType) { // Update the doctype try { @@ -304,18 +261,18 @@ export default class InitializationService { 'initEcogesture: Created ecogesture type entities does not macht' ) } - // Update userProfil with the hash - const updatedUserProfile = await userProfileService.updateUserProfile({ + // Update profil with the hash + const updatedProfile = await profileService.updateProfile({ ecogestureHash: hashEcogestureType, }) - if (updatedUserProfile) { + if (updatedProfile) { console.log( '%c Initialization: Ecogesture updated', 'background: #222; color: white' ) - return { result: true, userProfile: updatedUserProfile } + return { result: true, profile: updatedProfile } } else { - throw new Error('initEcogesture: UserProfile not updated') + throw new Error('initEcogesture: Profile not updated') } } catch (error) { console.log('Initialization error: ', error) @@ -327,96 +284,151 @@ export default class InitializationService { '%c Initialization: Ecogesture loaded', 'background: #222; color: white' ) - return { result: true, userProfile: null } + return { result: true, profile: null } } } + // TODO Transform to initSeasons + // public async initChallengeType( + // hash: string + // ): Promise<{ + // result: boolean + // profile: Profile | null + // }> { + // const hashChanllengeType = hashFile(challengeTypeData) + // const challengeService = new ChallengeService(this._client) + // const profileService = new ProfileService(this._client) + + // // Populate data if none challengeType exists + // const loadedChallengeTypes = await challengeService.getAllChallengeTypeEntities() + // if ( + // !loadedChallengeTypes || + // (loadedChallengeTypes && loadedChallengeTypes.length === 0) + // ) { + // // Populate the doctype with data + // try { + // for (let i = 0; i <= challengeTypeData.length - 1; i++) { + // await this._client.create(CHALLENGETYPE_DOCTYPE, challengeTypeData[i]) + // } + // // Check of created document + // const checkCount = await challengeService.getAllChallengeTypeEntities() + // if ( + // !checkCount || + // (checkCount && checkCount.length !== challengeTypeData.length) + // ) { + // throw new Error( + // 'initChallengeType: Created challenge type entities does not macht' + // ) + // } + // // Update profil with the hash + // const updatedProfile = await profileService.updateProfile({ + // challengeTypeHash: hashChanllengeType, + // }) + // if (updatedProfile) { + // console.log( + // '%c Initialization: Challenge Type created', + // 'background: #222; color: white' + // ) + // return { + // result: true, + // profile: updatedProfile, + // } + // } else { + // throw new Error('initChallengeType: Profile not updated') + // } + // } catch (error) { + // console.log('Initialization error: ', error) + // throw error + // } + // } + + // // Update if the hash is not the same as the one from profile + // if (hash !== hashChanllengeType) { + // // Update the doctype + // try { + // // Deletion of all documents + // await challengeService.deleteAllChallengeTypeEntities() + // // Population with the data + // await Promise.all( + // challengeTypeData.map(async challengeType => { + // await this._client.create(CHALLENGETYPE_DOCTYPE, challengeType) + // }) + // ) + // // Check of created document + // const checkCount = await challengeService.getAllChallengeTypeEntities() + // if ( + // !checkCount || + // (checkCount && checkCount.length !== challengeTypeData.length) + // ) { + // throw new Error( + // 'initChallengeType: Created challenge type entities does not macht' + // ) + // } + // // Update profil with the hash + // const updatedProfile = await profileService.updateProfile({ + // challengeTypeHash: hashChanllengeType, + // }) + // if (updatedProfile) { + // console.log( + // '%c Initialization: Challenge Type updated', + // 'background: #222; color: white' + // ) + // return { + // result: true, + // profile: updatedProfile, + // } + // } else { + // throw new Error('initChallengeType: Profile not updated') + // } + // } catch (error) { + // console.log('Initialization error: ', error) + // throw error + // } + // } else { + // // Doctype already up to date + // console.log( + // '%c Initialization: Challenge Type loaded', + // 'background: #222; color: white' + // ) + // return { result: true, profile: null } + // } + // } + public async initReport( - userReport: ReportAttributes | null + profile: Profile ): Promise<{ result: boolean - userProfile: UserProfile | null + profile: Profile | null }> { try { const actualReportDate = getActualReportDate() - let isReport = false - if (!userReport) { - //INIT REPORT OBJECT - isReport = true - userReport = { - monthlyReportDate: actualReportDate, - haveSeenLastReport: false, - sendReportNotification: true, - } - } - - if (isReport || actualReportDate > userReport.monthlyReportDate) { - const reportAttributes = { + if ( + profile.monthlyReportDate && + actualReportDate <= profile.monthlyReportDate + ) { + return { result: true, profile: profile } + } else { + const profileService = new ProfileService(this._client) + const updatedProfile = await profileService.updateProfile({ monthlyReportDate: actualReportDate, haveSeenLastReport: false, - sendReportNotification: userReport.sendReportNotification, - } - const userProfileService = new UserProfileService(this._client) - const updatedUserProfile = await userProfileService.updateUserProfile({ - report: reportAttributes, }) - if (updatedUserProfile) { + if (updatedProfile) { console.log( - '%c Initialization: Report from userProfile updated', + '%c Initialization: Report information from profile updated', 'background: #222; color: white' ) - return { result: true, userProfile: updatedUserProfile } + return { result: true, profile: updatedProfile } } else { - throw new Error('initReport: UserProfile not updated') + throw new Error('initReport: Profile not updated') } } - return { result: true, userProfile: null } } catch (error) { console.log('Initialization error: ', error) throw error } } - /* - * Check if userChallenge exist - * If not, the userChallenge is created - * sucess return: UserChallenge[] - * failure throw Error - */ - public async initUserChallenge(): Promise<UserChallenge[]> { - const challengeService = new ChallengeService(this._client) - const loadedUserChallenge = await challengeService.getAllUserChallenges() - if ( - !loadedUserChallenge || - (loadedUserChallenge && loadedUserChallenge.length === 0) - ) { - try { - // Population with the data - await this._client.create(USERCHALLENGE_DOCTYPE, userChallengeData[0]) - // Check of created document - const checkUserChallenge = await challengeService.getAllUserChallenges() - if (checkUserChallenge) { - console.log( - '%c Initialization: User Challenge created', - 'background: #222; color: white' - ) - return checkUserChallenge - } else { - throw new Error('initUserChallenge: UserChallenge not updated') - } - } catch (error) { - console.log('Initialization error: ', error) - throw error - } - } else { - console.log( - '%c Initialization: User Challenge loaded', - 'background: #222; color: white' - ) - return loadedUserChallenge - } - } - /* * Check if FluidTypes exist * sucess return: FluidType[] @@ -464,49 +476,4 @@ export default class InitializationService { throw error } } - - /* - * Search for a current challenge - * and update it if found - */ - public async initCurrentChallenge(): Promise<UserChallenge | null> { - try { - // get the current challenge - const challengeService = new ChallengeService(this._client) - const currentChallenge = await challengeService.getCurrentChallenge() - if (!currentChallenge) { - // No current challenge - return null - } else { - const updatedChallenge = await challengeService.updateCurrentChallengeProgress( - currentChallenge - ) - return updatedChallenge - } - } catch (error) { - console.log('Initialization error: ', error) - throw error - } - } - - /* - * If the challenge is over - * Set the rigth badge and return true - * else return false - */ - public async isCurrentChallengeOver( - challenge: UserChallenge - ): Promise<boolean> { - const challengeService = new ChallengeService(this._client) - const isOver = await challengeService.isCurrentChallengeOver( - challenge, - challenge.fluidTypes - ) - return isOver - } - - public async checkAchievement(id: string): Promise<UserChallenge | null> { - const challengeService = new ChallengeService(this._client) - return await challengeService.checkAchievement(id) - } } diff --git a/src/services/userProfile.service.spec.ts b/src/services/profile.service.spec.ts similarity index 62% rename from src/services/userProfile.service.spec.ts rename to src/services/profile.service.spec.ts index 1e944097d181c69fb50d7d3310055f56900346f3..98c23baeed45d2ce7152128a479adb7b62cd33fd 100644 --- a/src/services/userProfile.service.spec.ts +++ b/src/services/profile.service.spec.ts @@ -1,41 +1,31 @@ -import UserProfileService from './userProfile.service' -import { Client, QueryResult } from 'cozy-client' -import { UserProfile } from 'models' -import { userProfileData } from '../../test/__mocks__/userProfile.mock' +import ProfileService from './profile.service' +import { QueryResult } from 'cozy-client' import { DateTime } from 'luxon' - -const mockClient = ({ - find: jest.fn(), - query: jest.fn(), - create: jest.fn(), - destroy: jest.fn(), - save: jest.fn(), -} as unknown) as jest.Mocked<Client> +import { Profile } from 'models' +import { profileData } from '../../test/__mocks__/profile.mock' +import mockClient from '../../test/__mocks__/client' describe('UserProfile service', () => { - const userProfileService = new UserProfileService(mockClient) + const profileService = new ProfileService(mockClient) describe('getUserProfile', () => { it('shoud return the user profile', async () => { - const mockQueryResult: QueryResult<UserProfile[]> = { - data: [userProfileData], + const mockQueryResult: QueryResult<Profile[]> = { + data: [profileData], bookmark: '', next: false, skip: 0, } mockClient.query.mockResolvedValueOnce(mockQueryResult) - const result = await userProfileService.getUserProfile() - expect(result).toEqual(userProfileData) + const result = await profileService.getProfile() + expect(result).toEqual(profileData) }) - it('shoud return the user profile from string haveSeenOldFluidModal', async () => { + it('shoud return the user profile from string haveSeenOldFluidModal & monthlyReportDate', async () => { const userProfile = { - ...userProfileData, + ...profileData, haveSeenOldFluidModal: '2020-11-09T11:27:30.073+01:00', - report: { - ...userProfileData.report, - monthlyReportDate: '2020-11-03T00:00:00.000+01:00', - }, + monthlyReportDate: '2020-11-09T11:27:30.073+01:00', } // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockQueryResult: QueryResult<any[]> = { @@ -46,28 +36,25 @@ describe('UserProfile service', () => { } mockClient.query.mockResolvedValueOnce(mockQueryResult) const resultUserProfile = { - ...userProfileData, + ...profileData, haveSeenOldFluidModal: DateTime.fromISO( '2020-11-09T11:27:30.073+01:00' ), - report: { - ...userProfileData.report, - monthlyReportDate: DateTime.fromISO('2020-11-03T00:00:00.000+01:00'), - }, + monthlyReportDate: DateTime.fromISO('2020-11-09T11:27:30.073+01:00'), } - const result = await userProfileService.getUserProfile() + const result = await profileService.getProfile() expect(result).toEqual(resultUserProfile) }) it('shoud return null if no user profile found', async () => { - const mockQueryResult: QueryResult<UserProfile[]> = { + const mockQueryResult: QueryResult<Profile[]> = { data: [], bookmark: '', next: false, skip: 0, } mockClient.query.mockResolvedValueOnce(mockQueryResult) - const result = await userProfileService.getUserProfile() + const result = await profileService.getProfile() expect(result).toBeNull() }) }) @@ -75,12 +62,9 @@ describe('UserProfile service', () => { describe('updateUserProfile', () => { it('shoud return an updated user profile', async () => { const userProfile = { - ...userProfileData, + ...profileData, haveSeenOldFluidModal: '2020-11-09T11:27:30.073+01:00', - report: { - ...userProfileData.report, - monthlyReportDate: '2020-11-03T00:00:00.000+01:00', - }, + monthlyReportDate: '2020-11-03T00:00:00.000+01:00', } // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockQueryResult: QueryResult<any[]> = { @@ -103,17 +87,14 @@ describe('UserProfile service', () => { } mockClient.save.mockResolvedValueOnce(mockUpdatedQueryResult) const resultUserProfile = { - ...userProfileData, + ...profileData, haveSeenOldFluidModal: DateTime.fromISO( '2020-11-09T11:27:30.073+01:00' ), - report: { - ...userProfileData.report, - monthlyReportDate: DateTime.fromISO('2020-11-03T00:00:00.000+01:00'), - }, + monthlyReportDate: DateTime.fromISO('2020-11-03T00:00:00.000+01:00'), haveSeenFavoriteModal: false, } - const result = await userProfileService.updateUserProfile({ + const result = await profileService.updateProfile({ haveSeenFavoriteModal: false, }) @@ -121,8 +102,8 @@ describe('UserProfile service', () => { }) it('shoud return null if no user profile found', async () => { - const mockQueryResult: QueryResult<UserProfile[]> = { - data: [userProfileData], + const mockQueryResult: QueryResult<Profile[]> = { + data: [profileData], bookmark: '', next: false, skip: 0, @@ -135,7 +116,7 @@ describe('UserProfile service', () => { skip: 0, } mockClient.save.mockResolvedValueOnce(mockUpdatedQueryResult) - const result = await userProfileService.updateUserProfile({ + const result = await profileService.updateProfile({ haveSeenFavoriteModal: false, }) expect(result).toBeNull() diff --git a/src/services/profile.service.ts b/src/services/profile.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d1b3dd22a0b7205b47b62bd9a8e3012d4c07332 --- /dev/null +++ b/src/services/profile.service.ts @@ -0,0 +1,53 @@ +import { Client, Q, QueryDefinition } from 'cozy-client' +import { Profile } from 'models' +import { PROFILE_DOCTYPE } from 'doctypes' +import { DateTime } from 'luxon' + +export default class ProfileService { + private readonly _client: Client + + constructor(_client: Client) { + this._client = _client + } + + public async getProfile(): Promise<Profile | null> { + const query: QueryDefinition = Q(PROFILE_DOCTYPE) + const { data } = await this._client.query(query.limitBy(1)) + const profile: Profile | null = data[0] ? data[0] : null + if (profile && typeof profile.haveSeenOldFluidModal === 'string') { + profile.haveSeenOldFluidModal = DateTime.fromISO( + profile.haveSeenOldFluidModal + ) + } + if (profile && typeof profile.monthlyReportDate === 'string') { + profile.monthlyReportDate = DateTime.fromISO(profile.monthlyReportDate) + } + return profile + } + + public async updateProfile(attributes: { + [key: string]: string | string[] | boolean | number | DateTime + }): Promise<Profile | null> { + const query: QueryDefinition = Q(PROFILE_DOCTYPE) + const { data: profile } = await this._client + .query(query.limitBy(1)) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .then(async ({ data }: any) => { + const doc = data[0] + const saveResult = await this._client.save({ + ...doc, + ...attributes, + }) + return saveResult + }) + if (profile && typeof profile.haveSeenOldFluidModal === 'string') { + profile.haveSeenOldFluidModal = DateTime.fromISO( + profile.haveSeenOldFluidModal + ) + } + if (profile && typeof profile.monthlyReportDate === 'string') { + profile.monthlyReportDate = DateTime.fromISO(profile.monthlyReportDate) + } + return profile ? profile : null + } +} diff --git a/src/services/userProfile.service.ts b/src/services/userProfile.service.ts deleted file mode 100644 index 33f6d712d048331fab1319f4332326f8799fcccf..0000000000000000000000000000000000000000 --- a/src/services/userProfile.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Client, Q, QueryDefinition } from 'cozy-client' -import { UserProfile } from 'models' -import { USERPROFILE_DOCTYPE } from 'doctypes' -import { DateTime } from 'luxon' -import { ReportAttributes } from 'models' - -export default class UserProfileService { - private readonly _client: Client - - constructor(_client: Client) { - this._client = _client - } - - public async getUserProfile(): Promise<UserProfile | null> { - const query: QueryDefinition = Q(USERPROFILE_DOCTYPE) - const { data } = await this._client.query(query.limitBy(1)) - const userProfile: UserProfile | null = data[0] ? data[0] : null - if (userProfile && typeof userProfile.haveSeenOldFluidModal === 'string') { - userProfile.haveSeenOldFluidModal = DateTime.fromISO( - userProfile.haveSeenOldFluidModal - ) - } - //INIT REPORT OBJECT - if ( - userProfile && - userProfile.report && - typeof userProfile.report.monthlyReportDate === 'string' - ) { - userProfile.report.monthlyReportDate = DateTime.fromISO( - userProfile.report.monthlyReportDate - ) - } - return userProfile - } - - public async updateUserProfile(attributes: { - [key: string]: - | string - | string[] - | boolean - | number - | DateTime - | ReportAttributes - }): Promise<UserProfile | null> { - const query: QueryDefinition = Q(USERPROFILE_DOCTYPE) - const { data: userProfile } = await this._client - .query(query.limitBy(1)) - .then(async ({ data }: any) => { - const doc = data[0] - const saveResult = await this._client.save({ - ...doc, - ...attributes, - }) - return saveResult - }) - if (userProfile && typeof userProfile.haveSeenOldFluidModal === 'string') { - userProfile.haveSeenOldFluidModal = DateTime.fromISO( - userProfile.haveSeenOldFluidModal - ) - } - if ( - userProfile && - typeof userProfile.report.monthlyReportDate === 'string' - ) { - userProfile.report.monthlyReportDate = DateTime.fromISO( - userProfile.report.monthlyReportDate - ) - } - return userProfile ? userProfile : null - } -} diff --git a/src/store/global/global.actions.ts b/src/store/global/global.actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..13a9e227449cb5dc7f9e195862ec5771c315b2b0 --- /dev/null +++ b/src/store/global/global.actions.ts @@ -0,0 +1,46 @@ +import { ScreenType } from 'enum/screen.enum' + +export const CHANGE_SCREEN_TYPE = 'CHANGE_SCREEN_TYPE' +export const TOOGLE_SEASON_NOTIFICATION = 'TOOGLE_SEASON_NOTIFICATION' +export const TOOGLE_REPORT_NOTIFICATION = 'TOOGLE_REPORT_NOTIFICATION' + +interface ChangeScreenType { + type: typeof CHANGE_SCREEN_TYPE + payload?: ScreenType +} + +interface ToogleSeasonNotification { + type: typeof TOOGLE_SEASON_NOTIFICATION + payload?: boolean +} + +interface ToogleReportNotification { + type: typeof TOOGLE_REPORT_NOTIFICATION + payload?: boolean +} + +export type GlobalActionTypes = + | ChangeScreenType + | ToogleSeasonNotification + | ToogleReportNotification + +export function changeScreenType(screenType: ScreenType): GlobalActionTypes { + return { + type: CHANGE_SCREEN_TYPE, + payload: screenType, + } +} + +export function toogleSeasonNotification(notif: boolean): GlobalActionTypes { + return { + type: TOOGLE_SEASON_NOTIFICATION, + payload: notif, + } +} + +export function toogleReportNotification(notif: boolean): GlobalActionTypes { + return { + type: TOOGLE_REPORT_NOTIFICATION, + payload: notif, + } +} diff --git a/src/store/global/global.reducer.ts b/src/store/global/global.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..1feaaf5faab581444a30652c4d54642a27b91059 --- /dev/null +++ b/src/store/global/global.reducer.ts @@ -0,0 +1,46 @@ +import { Reducer } from 'redux' +import { + CHANGE_SCREEN_TYPE, + TOOGLE_SEASON_NOTIFICATION, + TOOGLE_REPORT_NOTIFICATION, + GlobalActionTypes, +} from 'store/global/global.actions' +import { GlobalState } from 'models' +import { ScreenType } from 'enum/screen.enum' + +const initialState: GlobalState = { + screenType: ScreenType.MOBILE, + seasonNotification: false, + reportNotification: false, +} + +export const globalReducer: Reducer<GlobalState> = ( + state = initialState, + action: GlobalActionTypes +): GlobalState => { + switch (action.type) { + case CHANGE_SCREEN_TYPE: + const screenType = + action.payload != undefined ? action.payload : state.screenType + return { + ...state, + screenType: screenType, + } + case TOOGLE_SEASON_NOTIFICATION: + const notifSeason = + action.payload != undefined ? action.payload : state.seasonNotification + return { + ...state, + seasonNotification: notifSeason, + } + case TOOGLE_REPORT_NOTIFICATION: + const notifReport = + action.payload != undefined ? action.payload : state.reportNotification + return { + ...state, + reportNotification: notifReport, + } + default: + return state + } +} diff --git a/src/store/index.ts b/src/store/index.ts index 625802664aeada972ea401dbce56b75eb5c581a2..2387666f9fa5386da02d42d965eb4636a9668d57 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,8 +1,16 @@ -import { ModalState } from 'models' +import { GlobalState, ModalState, Profile } from 'models' +import { globalReducer } from 'store/global/global.reducer' +import { profileReducer } from './profile/profile.reducer' import { modalReducer } from 'store/modal/modal.reducer' export interface EcolyoState { + global: GlobalState + profile: Profile modal: ModalState } -export const ecolyoReducer = { modal: modalReducer } +export const ecolyoReducer = { + global: globalReducer, + profile: profileReducer, + modal: modalReducer, +} diff --git a/src/store/profile/profile.actions.ts b/src/store/profile/profile.actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..35e431e42deff302b9130052156db564c4a58728 --- /dev/null +++ b/src/store/profile/profile.actions.ts @@ -0,0 +1,28 @@ +import { Profile } from 'models' + +export const UPDATE_PROFILE = 'UPDATE_PROFILE' +export const SET_FIRST_CONNECTION = 'SET_FIRST_CONNECTION' + +interface UpdateProfile { + type: typeof UPDATE_PROFILE + payload?: Profile +} + +interface SetFirstConnection { + type: typeof SET_FIRST_CONNECTION +} + +export type ProfileActionTypes = UpdateProfile | SetFirstConnection + +export function updateProfile(profile: Profile): ProfileActionTypes { + return { + type: UPDATE_PROFILE, + payload: profile, + } +} + +export function setFirstConnection(): ProfileActionTypes { + return { + type: SET_FIRST_CONNECTION, + } +} diff --git a/src/store/profile/profile.reducer.ts b/src/store/profile/profile.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0625cfd7444ca411f7554d640b2e68cb884027b --- /dev/null +++ b/src/store/profile/profile.reducer.ts @@ -0,0 +1,40 @@ +import { Reducer } from 'redux' +import { + UPDATE_PROFILE, + SET_FIRST_CONNECTION, + ProfileActionTypes, +} from 'store/profile/profile.actions' +import { Profile } from 'models' +import { DateTime } from 'luxon' + +const initialState: Profile = { + id: '', + challengeTypeHash: '', + ecogestureHash: '', + isFirstConnection: true, + haveSeenFavoriteModal: false, + haveSeenOldFluidModal: true, + haveSeenLastReport: true, + sendReportNotification: false, + monthlyReportDate: DateTime.fromISO('0000-01-01T00:00:00.000Z'), +} + +export const profileReducer: Reducer<Profile> = ( + state = initialState, + action: ProfileActionTypes +): Profile => { + switch (action.type) { + case UPDATE_PROFILE: + return { + ...state, + ...action.payload, + } + case SET_FIRST_CONNECTION: + return { + ...state, + isFirstConnection: false, + } + default: + return state + } +} diff --git a/src/utils/date.ts b/src/utils/date.ts index 12306c0d00e99e79329f2ee517f4dcfc8350143f..093c373173bdd5b36d79afc2a578c59ea7798de2 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -155,7 +155,7 @@ export const convertDateToMonthString = (date: DateTime): string => { } } -export function getActualReportDate(): DateTime { +export const getActualReportDate = (): DateTime => { const now = DateTime.local().startOf('day') if (now.day < 3) { return now.set({ day: 3, month: now.month - 1 }) diff --git a/src/utils/hash.ts b/src/utils/hash.ts index 8bd0a97296121f4e0e8c3b9aed0d3fd7f555e0f3..b444432ed33b5e7f25d30a852046a207d50504dd 100644 --- a/src/utils/hash.ts +++ b/src/utils/hash.ts @@ -3,6 +3,7 @@ import hash from 'object-hash' /*** * sha1 hex encoding (default) */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function hashFile(file: any) { return hash(file) } diff --git a/test/__mocks__/profile.mock.ts b/test/__mocks__/profile.mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f88910d0360059b3da11c91dd4b05dec7b5cd15 --- /dev/null +++ b/test/__mocks__/profile.mock.ts @@ -0,0 +1,15 @@ +import { DateTime } from 'luxon' + +export const profileData = { + _id: '4d9403218ef13e65b2e3a8ad1700bc41', + _rev: '16-57473da4fc26315247c217083175dfa0', + id: '4d9403218ef13e65b2e3a8ad1700bc41', + challengeTypeHash: '1136feb6185c7643e071d14180c0e95782aa4ba3', + ecogestureHash: '9798a0aaccb47cff906fc4931a2eff5f9371dd8b', + isFirstConnection: true, + haveSeenFavoriteModal: false, + haveSeenOldFluidModal: false, + haveSeenLastReport: true, + monthlyReportDate: DateTime.fromISO('2020-11-03T00:00:00.000+01:00'), + sendReportNotification: false, +}