diff --git a/config.json b/config.json index 26c228945ddd7a59c627c254d1b3dda9d8a9dcb6..0d5f94578b4f66aec36fd653b6cf805efda395fd 100644 --- a/config.json +++ b/config.json @@ -7,11 +7,10 @@ "dataDelayOffset": 1, "konnectorConfig": { "name": "Enedis", - "type": "ELECTRICITY", "oauth": true, - "slug": "enedisgrandlyon" - }, - "siteLink": "https://mon-compte-particulier.enedis.fr/donnees/" + "slug": "enedisgrandlyon", + "siteLink": "https://mon-compte-particulier.enedis.fr/donnees/" + } }, { "fluidTypeId": 1, @@ -20,11 +19,10 @@ "dataDelayOffset": 3, "konnectorConfig": { "name": "Eau du Grand Lyon", - "type": "WATER", "oauth": false, - "slug": "eglgrandlyon" - }, - "siteLink": "https://agence.eaudugrandlyon.com/inscription.aspx" + "slug": "eglgrandlyon", + "siteLink": "https://agence.eaudugrandlyon.com/inscription.aspx" + } }, { "fluidTypeId": 2, @@ -33,11 +31,10 @@ "dataDelayOffset": 2, "konnectorConfig": { "name": "GRDF", - "type": "GAS", "oauth": true, - "slug": "grdfgrandlyon" - }, - "siteLink": "https://monespace.grdf.fr/monespace/connexion" + "slug": "grdfgrandlyon", + "siteLink": "https://monespace.grdf.fr/monespace/connexion" + } } ] } diff --git a/src/components/Connection/ConnectionForm.tsx b/src/components/Connection/ConnectionForm.tsx index 59afb731d9c193f74249125ee63817ecfd5fb6ac..12df801606b3759e21097f6acfa6ed2240be60de 100644 --- a/src/components/Connection/ConnectionForm.tsx +++ b/src/components/Connection/ConnectionForm.tsx @@ -1,83 +1,38 @@ import React, { useCallback } from 'react' -import { useClient } from 'cozy-client' import { useDispatch } from 'react-redux' -import { updateProfile } from 'store/profile/profile.actions' +import { updatedFluidConnection } from 'store/global/global.actions' +import { FluidStatus, FluidConnection } from 'models' -import { - Account, - Konnector, - Trigger, - FluidConfig, - Profile, - FluidStatus, -} from 'models' -import ProfileService from 'services/profile.service' - -import ConnectionLoginForm from 'components/Connection/ConnectionLoginForm' -import ConnectionOAuthForm from 'components/Connection/ConnectionOAuthForm' -import FluidService from 'services/fluid.service' -import { setFluidStatus } from 'store/global/global.actions' +import ConnectionFormLogin from 'components/Connection/ConnectionFormLogin' +import ConnectionFormOauth from 'components/Connection/ConnectionFormOAuth' interface ConnectionFormProps { - fluidConfig: FluidConfig - konnector: Konnector - account: Account | null - trigger: Trigger | null - handleSuccessForm: Function + fluidStatus: FluidStatus } const ConnectionForm: React.FC<ConnectionFormProps> = ({ - fluidConfig, - konnector, - account, - trigger, - handleSuccessForm, + fluidStatus, }: ConnectionFormProps) => { - const client = useClient() const dispatch = useDispatch() - const oAuth: boolean = fluidConfig.konnectorConfig.oauth - - const updateProfileHaveSeenOldFluidModal = useCallback(async () => { - const profileService = new ProfileService(client) - await profileService - .updateProfile({ haveSeenOldFluidModal: false }) - .then((updatedProfile: Profile | null) => { - if (updatedProfile) { - dispatch(updateProfile(updatedProfile)) - } - }) - }, [client, dispatch]) - const updateGlobalState = useCallback(async () => { - const fluidService = new FluidService(client) - await fluidService.getFluidStatus().then((fluidStatus: FluidStatus[]) => { - dispatch(setFluidStatus(fluidStatus)) - }) - }, [client, dispatch]) - - const handleSuccess = useCallback( - async (_account: Account, _trigger: Trigger) => { - await updateProfileHaveSeenOldFluidModal() - await updateGlobalState() - handleSuccessForm(_account, _trigger) - }, - [updateProfileHaveSeenOldFluidModal, updateGlobalState, handleSuccessForm] - ) + const handleSuccess = useCallback(async () => { + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + shouldLaunchKonnector: true, + } + dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) + }, [dispatch, fluidStatus.fluidType, fluidStatus.connection]) return ( <> - {!oAuth ? ( - <ConnectionLoginForm - fluidConfig={fluidConfig} + {!fluidStatus.connection.konnectorConfig.oauth ? ( + <ConnectionFormLogin + fluidStatus={fluidStatus} onSuccess={handleSuccess} - account={account} - trigger={trigger} /> ) : ( - <ConnectionOAuthForm - fluidConfig={fluidConfig} - konnector={konnector} - siteLink={fluidConfig.siteLink} + <ConnectionFormOauth + fluidStatus={fluidStatus} onSuccess={handleSuccess} /> )} diff --git a/src/components/Connection/ConnectionLoginForm.tsx b/src/components/Connection/ConnectionFormLogin.tsx similarity index 70% rename from src/components/Connection/ConnectionLoginForm.tsx rename to src/components/Connection/ConnectionFormLogin.tsx index 7e299d6ef0d02a162f6166218b9c25bf39ae86fb..11b642a4dc2bff2a0e745f27436d78a718290834 100644 --- a/src/components/Connection/ConnectionLoginForm.tsx +++ b/src/components/Connection/ConnectionFormLogin.tsx @@ -1,8 +1,15 @@ import React, { useState, useEffect } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { useClient } from 'cozy-client' - -import { Account, AccountAuthData, Trigger, FluidConfig } from 'models' +import { useDispatch } from 'react-redux' + +import { + Account, + AccountAuthData, + Trigger, + FluidStatus, + FluidConnection, +} from 'models' import AccountService from 'services/account.service' import ConnectionService from 'services/connection.service' @@ -15,44 +22,43 @@ import iconEGLLogo from 'assets/icons/visu/egl-logo.svg' import StyledAuthButton from 'components/CommonKit/Button/StyledAuthButton' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import './auth.scss' -import './connectionLoginForm.scss' +import './connectionFormLogin.scss' +import { FluidType } from 'enum/fluid.enum' +import { updatedFluidConnection } from 'store/global/global.actions' -interface ConnectionLoginFormProps { - fluidConfig: FluidConfig +interface ConnectionFormLoginProps { + fluidStatus: FluidStatus onSuccess: Function - account: Account | null - trigger: Trigger | null } -const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ - fluidConfig, +const ConnectionFormLogin: React.FC<ConnectionFormLoginProps> = ({ + fluidStatus, onSuccess, - account, - trigger, -}: ConnectionLoginFormProps) => { +}: ConnectionFormLoginProps) => { const { t } = useI18n() const client = useClient() - const konnectorSlug: string = fluidConfig.konnectorConfig.slug - const konnectorType: string = fluidConfig.konnectorConfig.type - const siteLink: string = fluidConfig.siteLink + const dispatch = useDispatch() + const konnectorSlug: string = fluidStatus.connection.konnectorConfig.slug + const fluidName: string = FluidType[fluidStatus.fluidType] + const siteLink: string = fluidStatus.connection.konnectorConfig.siteLink + const account: Account | null = fluidStatus.connection.account const [login, setLogin] = useState<string>('') const [password, setPassword] = useState<string>('') const [error, setError] = useState<string>('') const [loading, setLoading] = useState<boolean>(false) - const getIcon = (fluidName: string) => { - switch (fluidName) { - case 'grdf': + const getIcon = (fluidType: FluidType): string | null => { + switch (fluidType) { + case FluidType.GAS: return iconGRDFLogo - case 'egl': + case FluidType.WATER: return iconEGLLogo default: - return null + return '' } } - - const icon = getIcon(fluidConfig.name) + const icon = getIcon(fluidStatus.fluidType) const changeLogin = (value: string) => { setError('') @@ -81,17 +87,20 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ const { account: _account, trigger: _trigger, - } = await connectionService.connectNewUser( - fluidConfig.konnectorConfig.slug, - login, - password - ) + } = await connectionService.connectNewUser(konnectorSlug, login, password) if (!_trigger) { setError(t('KONNECTORCONFIG.ERROR_ACCOUNT_CREATION')) return null } + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + account: _account, + trigger: _trigger, + shouldLaunchKonnector: true, + } setLoading(false) - onSuccess(_account, _trigger) + dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) + onSuccess() } catch (err) { setLoading(false) console.log(err) @@ -99,15 +108,21 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ } const update = async () => { - if (account) { + if (fluidStatus.connection.account) { + const _account = fluidStatus.connection.account const auth = { login: login, password: password, } - account.auth = auth + _account.auth = auth const accountService = new AccountService(client) - const updatedAccount = await accountService.updateAccount(account) - onSuccess(updatedAccount, trigger) + const updatedAccount = await accountService.updateAccount(_account) + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + account: updatedAccount, + } + dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) + onSuccess() } } @@ -153,7 +168,7 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ <div className="form-group"> <input - id={'idFieldLogin' + konnectorType} + id={'idFieldLogin' + fluidName} type="text" className={ error === '' @@ -161,9 +176,7 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ : 'form-control form-input --error' } aria-describedby="emailHelp" - placeholder={t( - 'auth.' + fluidConfig.konnectorConfig.slug + '.connect.placeholder' - )} + placeholder={t('auth.' + konnectorSlug + '.connect.placeholder')} name="login" onChange={(e: React.ChangeEvent<HTMLInputElement>) => changeLogin(e.target.value) @@ -173,7 +186,7 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ </div> <div className="form-group"> <input - id={'idFieldPassword' + konnectorType} + id={'idFieldPassword' + fluidName} type="password" className={ error === '' @@ -193,7 +206,7 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ icon={TrailingIcon} className="form-trailing-icon" size={22} - onClick={() => revealPassword('idFieldPassword' + konnectorType)} + onClick={() => revealPassword('idFieldPassword' + fluidName)} /> </span> </div> @@ -211,32 +224,18 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ {icon && <StyledIcon icon={icon} size={48} />} </div> <div className="authform-button-text text-18-bold"> - <div> - {t( - 'auth.' + - fluidConfig.konnectorConfig.slug + - '.connect.label1' - )} - </div> - <div> - {t( - 'auth.' + - fluidConfig.konnectorConfig.slug + - '.connect.label2' - )} - </div> + <div>{t('auth.' + konnectorSlug + '.connect.label1')}</div> + <div>{t('auth.' + konnectorSlug + '.connect.label2')}</div> </div> </div> </StyledAuthButton> </div> <div className="kloginauthform-text text-16-bold"> <div className="text-16-bold"> - {t( - 'auth.' + fluidConfig.konnectorConfig.slug + '.no_account.title' - )} + {t('auth.' + konnectorSlug + '.no_account.title')} </div> <div className="text-16-normal"> - {t('auth.' + fluidConfig.konnectorConfig.slug + '.no_account.text')} + {t('auth.' + konnectorSlug + '.no_account.text')} </div> </div> <StyledButton @@ -246,11 +245,11 @@ const ConnectionLoginForm: React.FC<ConnectionLoginFormProps> = ({ disabled={loading} onClick={() => window.open(siteLink, '_blank')} > - {t('auth.' + fluidConfig.konnectorConfig.slug + '.create_account')} + {t('auth.' + konnectorSlug + '.create_account')} </StyledButton> </div> </form> ) } -export default ConnectionLoginForm +export default ConnectionFormLogin diff --git a/src/components/Connection/ConnectionFormOAuth.tsx b/src/components/Connection/ConnectionFormOAuth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ea84dd6101bf1842035dd44ea5dacdbcd8459744 --- /dev/null +++ b/src/components/Connection/ConnectionFormOAuth.tsx @@ -0,0 +1,103 @@ +import React, { useCallback } from 'react' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import { useClient } from 'cozy-client' +import { useDispatch } from 'react-redux' +import './connectionFormOAuth.scss' + +import { Konnector, Trigger, FluidStatus, FluidConnection } from 'models' +import OAuthForm from 'components/Connection/OAuthForm' +import StyledButton from 'components/CommonKit/Button/StyledButton' +import AccountService from 'services/account.service' +import TriggerService from 'services/triggers.service' +import { updatedFluidConnection } from 'store/global/global.actions' + +interface ConnectionFormOAuthProps { + fluidStatus: FluidStatus + onSuccess: Function +} + +const ConnectionFormOAuth: React.FC<ConnectionFormOAuthProps> = ({ + fluidStatus, + onSuccess, +}: ConnectionFormOAuthProps) => { + const { t } = useI18n() + const client = useClient() + const dispatch = useDispatch() + + const konnectorSlug: string = fluidStatus.connection.konnectorConfig.slug + const siteLink: string = fluidStatus.connection.konnectorConfig.siteLink + const konnector: Konnector | null = fluidStatus.connection.konnector + + const handleSuccess = useCallback( + async (accountId: string) => { + if (konnector) { + const accountService = new AccountService(client) + const account = await accountService.getAccount(accountId) + if (!account) { + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + account: null, + trigger: null, + } + dispatch( + updatedFluidConnection(fluidStatus.fluidType, updatedConnection) + ) + } else { + const triggersServices = new TriggerService(client) + const trigger: Trigger = await triggersServices.createTrigger( + account, + konnector + ) + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + account: account, + trigger: trigger, + } + dispatch( + updatedFluidConnection(fluidStatus.fluidType, updatedConnection) + ) + onSuccess() + } + } + }, + [ + client, + konnector, + dispatch, + fluidStatus.fluidType, + fluidStatus.connection, + onSuccess, + ] + ) + + return ( + <div className="koauthform"> + <p className="info-provider text-16-normal"> + {t('auth.' + `${konnectorSlug}` + '.connect.info')} + </p> + <div className="koauthform-button"> + <OAuthForm konnector={konnector} onSuccess={handleSuccess} /> + </div> + <div className="koauthform-text text-16-bold"> + <div className="text-16-bold"> + {t('auth.' + konnectorSlug + '.no_account.title')} + </div> + <div className="text-16-normal"> + {t('auth.' + konnectorSlug + '.no_account.text')} + </div> + </div> + <div className="koauthform-button"> + <StyledButton + type="button" + color="secondary" + className="create-account" + onClick={() => window.open(siteLink, '_blank')} + > + {t('auth.' + konnectorSlug + '.create_account')} + </StyledButton> + </div> + </div> + ) +} + +export default ConnectionFormOAuth diff --git a/src/components/Connection/ConnectionOAuthForm.scss b/src/components/Connection/ConnectionOAuthForm.scss deleted file mode 100644 index ba1d2923f2c352e4d24b34f47c539b0c4dcbcb41..0000000000000000000000000000000000000000 --- a/src/components/Connection/ConnectionOAuthForm.scss +++ /dev/null @@ -1,21 +0,0 @@ -@import 'src/styles/base/color'; -@import 'src/styles/base/breakpoint'; -// KonnectorOAuthForm -.koauthform { - margin: 0 1.5rem; - padding: 0 1rem; - .create-account { - color: $grey-bright; - } - @media #{$large-phone} { - margin: 0; - padding: 0 1rem; - } - .koauthform-text { - color: $grey-bright; - padding-top: 1rem; - } - .koauthform-button { - margin-bottom: 1rem; - } -} diff --git a/src/components/Connection/ConnectionOAuthForm.tsx b/src/components/Connection/ConnectionOAuthForm.tsx deleted file mode 100644 index 658bf13cb8fded59ad6ec19194b2e6dbce4f86c5..0000000000000000000000000000000000000000 --- a/src/components/Connection/ConnectionOAuthForm.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useCallback } from 'react' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { useClient } from 'cozy-client' - -import { Konnector, Trigger, FluidConfig } from 'models' -import OAuthForm from 'components/Connection/OAuthForm' -import StyledButton from 'components/CommonKit/Button/StyledButton' -import AccountService from 'services/account.service' -import TriggerService from 'services/triggers.service' -import './connectionOAuthForm.scss' -interface ConnectionOAuthFormProps { - fluidConfig: FluidConfig - konnector: Konnector - siteLink: string - onSuccess: Function -} - -const ConnectionOAuthForm: React.FC<ConnectionOAuthFormProps> = ({ - fluidConfig, - konnector, - siteLink, - onSuccess, -}: ConnectionOAuthFormProps) => { - const { t } = useI18n() - const client = useClient() - const konnectorSlug: string = fluidConfig.konnectorConfig.slug - - const handleSuccess = useCallback( - async (accountId: string) => { - const accountService = new AccountService(client) - const account = await accountService.getAccount(accountId) - if (!account) { - onSuccess(null, null) - } - const triggersServices = new TriggerService(client) - const trigger: Trigger = await triggersServices.createTrigger( - account, - konnector - ) - onSuccess(account, trigger) - }, - [client, konnector, onSuccess] - ) - - return ( - <div className="koauthform"> - <p className="info-provider text-16-normal"> - {t('auth.' + `${konnectorSlug}` + '.connect.info')} - </p> - <div className="koauthform-button"> - <OAuthForm konnector={konnector} onSuccess={handleSuccess} /> - </div> - <div className="koauthform-text text-16-bold"> - <div className="text-16-bold"> - {t('auth.' + konnector.slug + '.no_account.title')} - </div> - <div className="text-16-normal"> - {t('auth.' + konnector.slug + '.no_account.text')} - </div> - </div> - <div className="koauthform-button"> - <StyledButton - type="button" - color="secondary" - className="create-account" - onClick={() => window.open(siteLink, '_blank')} - > - {t('auth.' + konnector.slug + '.create_account')} - </StyledButton> - </div> - </div> - ) -} - -export default ConnectionOAuthForm diff --git a/src/components/Connection/ConnectionResult.tsx b/src/components/Connection/ConnectionResult.tsx index f56474828076e7771e4fcced3e7ee7e51671bffd..f685517b6e2c2b907a7d7306b81359636ce9020c 100644 --- a/src/components/Connection/ConnectionResult.tsx +++ b/src/components/Connection/ConnectionResult.tsx @@ -1,154 +1,50 @@ -import React, { useState, useEffect, useCallback } from 'react' +import React, { useState, useEffect } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { useClient } from 'cozy-client' -import { useDispatch, useSelector } from 'react-redux' -import { AppStore } from 'store' -import { setFluidStatus } from 'store/global/global.actions' -import { updateProfile } from 'store/profile/profile.actions' -import { toogleChallengeNotification } from 'store/global/global.actions' -import { setChallengeConsumption } from 'store/challenge/challenge.actions' -import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' -import { getKonnectorUpdateError } from 'utils/utils' +import { useDispatch } from 'react-redux' +import { updatedFluidConnection } from 'store/global/global.actions' -import { Account, FluidStatus, Konnector, Profile, Trigger } from 'models' -import TriggerService from 'services/triggers.service' +import { Account, FluidConnection, FluidStatus } from 'models' import AccountService from 'services/account.service' -import FluidService from 'services/fluid.service' -import ProfileService from 'services/profile.service' +import { getKonnectorUpdateError } from 'utils/utils' -import ConnectionFlow, { - ERROR_EVENT, - LOGIN_SUCCESS_EVENT, - SUCCESS_EVENT, -} from 'cozy-harvest-lib/dist/models/ConnectionFlow' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import warningWhite from 'assets/icons/ico/warning-white.svg' import StyledButton from 'components/CommonKit/Button/StyledButton' import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' -import InitializationService from 'services/initialization.service' -import ChallengeService from 'services/challenge.service' -import { UserChallengeState } from 'enum/userChallenge.enum' -import { UserDuelState } from 'enum/userDuel.enum' import './connectionResult.scss' interface ConnectionResultProps { - account: Account - konnector: Konnector - handleJobState: Function + fluidStatus: FluidStatus + handleAccountDeletion: Function } const ConnectionResult: React.FC<ConnectionResultProps> = ({ - account, - konnector, - handleJobState, + fluidStatus, + handleAccountDeletion, }: ConnectionResultProps) => { const { t } = useI18n() const client = useClient() - const { currentChallenge } = useSelector( - (state: AppStore) => state.ecolyo.challenge - ) - const dispatch = useDispatch() - const [trigger, setTrigger] = useState<Trigger | null>(null) + const account: Account | null = fluidStatus.connection.account + const [updating, setUpdating] = useState<boolean>(false) const [lastExecutionDate, setLastExecutionDate] = useState<string>('-') const [konnectorError, setKonnectorError] = useState<string>('') const [status, setStatus] = useState<string>('') - const refreshFluidState = useCallback(async () => { - const fluidService = new FluidService(client) - await fluidService.getFluidStatus().then((fluidStatus: FluidStatus[]) => { - dispatch(setFluidStatus(fluidStatus)) - }) - }, [client, dispatch]) - - const refreshChallengeState = useCallback(async () => { - if ( - currentChallenge && - currentChallenge.state === UserChallengeState.DUEL && - currentChallenge.duel.state === UserDuelState.ONGOING - ) { - const initializationService = new InitializationService(client) - const { - updatedUserChallenge, - dataloads, - } = await initializationService.initDuelProgress(currentChallenge) - dispatch(setChallengeConsumption(updatedUserChallenge, dataloads)) - // Check is duel is done and display notification - const challengeService = new ChallengeService(client) - const { isDone } = await challengeService.isChallengeDone( - updatedUserChallenge, - dataloads - ) - dispatch(toogleChallengeNotification(isDone)) - } - }, [client, dispatch, currentChallenge]) - - const updateProfileHaveSeenOldFluidModal = useCallback(async () => { - const profileService = new ProfileService(client) - await profileService - .updateProfile({ haveSeenOldFluidModal: false }) - .then((updatedProfile: Profile | null) => { - if (updatedProfile) { - dispatch(updateProfile(updatedProfile)) - } - }) - }, [client, dispatch]) - - const updateState = useCallback( - async (_trigger: Trigger) => { - const triggerService = new TriggerService(client) - const triggerState = await triggerService.fetchTriggerState(_trigger) - - if (triggerState) { - if (triggerState.last_success) { - setLastExecutionDate( - new Date(triggerState.last_success).toLocaleString() - ) - } else { - setLastExecutionDate('-') - } - if (triggerState.status === 'errored' && triggerState.last_error) { - setKonnectorError(getKonnectorUpdateError(triggerState.last_error)) - } - setStatus(triggerState.status) - handleJobState(triggerState.status) - await refreshFluidState() - await refreshChallengeState() - } - }, - [client, handleJobState, refreshFluidState, refreshChallengeState] - ) - - const callbackResponse = async () => { - if (trigger) { - await updateState(trigger) - } - setUpdating(false) - } - const updateKonnector = async () => { setUpdating(true) setStatus('') setLastExecutionDate('-') setKonnectorError('') - handleJobState('') - await updateProfileHaveSeenOldFluidModal() - if (trigger && !isKonnectorRunning(trigger)) { - const connectionFlow = new ConnectionFlow(client, trigger, konnector) - await connectionFlow.launch() - - connectionFlow.jobWatcher.on(ERROR_EVENT, () => { - callbackResponse() - }) - connectionFlow.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => { - callbackResponse() - }) - connectionFlow.jobWatcher.on(SUCCESS_EVENT, () => { - callbackResponse() - }) + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + shouldLaunchKonnector: true, } + dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) + setUpdating(false) } const deleteAccount = async () => { @@ -157,8 +53,7 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ if (account) { const accountService = new AccountService(client) await accountService.deleteAccount(account) - await updateProfileHaveSeenOldFluidModal() - await refreshFluidState() + await handleAccountDeletion() } } catch (error) { setUpdating(false) @@ -166,20 +61,28 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ } useEffect(() => { - let subscribed = true - async function getData() { - const triggerService = new TriggerService(client) - const _trigger = await triggerService.getTrigger(account, konnector) - if (subscribed && _trigger) { - setTrigger(_trigger) - await updateState(_trigger) - } + if ( + fluidStatus.connection.triggerState && + fluidStatus.connection.triggerState.last_success + ) { + setLastExecutionDate( + new Date( + fluidStatus.connection.triggerState.last_success + ).toLocaleString() + ) + } else { + setLastExecutionDate('-') } - getData() - return () => { - subscribed = false + if ( + fluidStatus.connection.triggerState && + fluidStatus.connection.triggerState.status === 'errored' && + fluidStatus.connection.triggerState.last_error + ) { + setKonnectorError( + getKonnectorUpdateError(fluidStatus.connection.triggerState.last_error) + ) } - }, [account, client, konnector, updateState]) + }, [fluidStatus.connection.triggerState]) return ( <div className="accordion-update-result"> diff --git a/src/components/Connection/OAuthForm.tsx b/src/components/Connection/OAuthForm.tsx index cfade01d8c08925fa7f3a1a21317d6673c69d9e8..4486748a6547a8b3e3cc3180b00d396897255426 100644 --- a/src/components/Connection/OAuthForm.tsx +++ b/src/components/Connection/OAuthForm.tsx @@ -16,7 +16,7 @@ import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' import ModalGRDF from './ModalGRDF' import './auth.scss' interface OAuthFormProps { - konnector: Konnector + konnector: Konnector | null onSuccess: Function } @@ -34,7 +34,7 @@ const OAuthForm: React.FC<OAuthFormProps> = ({ const toggleGRDFModal = useCallback(() => { setOpenGRDFModal(prev => !prev) - }, [openGRDFModal]) + }, []) const endOAuth = () => { setStatus(IDLE) @@ -57,14 +57,15 @@ const OAuthForm: React.FC<OAuthFormProps> = ({ }, []) const fluidconfig = new ConfigService().getFluidConfig() - const icon = - konnector.slug === fluidconfig[FluidType.ELECTRICITY].konnectorConfig.slug + const icon = konnector + ? konnector.slug === fluidconfig[FluidType.ELECTRICITY].konnectorConfig.slug ? iconEnedisLogo : konnector.slug === fluidconfig[FluidType.GAS].konnectorConfig.slug ? iconGrdfLogo : konnector.slug === fluidconfig[FluidType.WATER].konnectorConfig.slug ? iconEglLogo : '' + : '' const isWaiting = status === WAITING return !konnector ? null : ( <> diff --git a/src/components/Connection/ConnectionLoginForm.scss b/src/components/Connection/connectionFormLogin.scss similarity index 100% rename from src/components/Connection/ConnectionLoginForm.scss rename to src/components/Connection/connectionFormLogin.scss diff --git a/src/components/Connection/connectionOAuthForm.scss b/src/components/Connection/connectionFormOAuth.scss similarity index 94% rename from src/components/Connection/connectionOAuthForm.scss rename to src/components/Connection/connectionFormOAuth.scss index ba1d2923f2c352e4d24b34f47c539b0c4dcbcb41..09c2223ce4d54d0ccb2b43906f9787d0ad97b17c 100644 --- a/src/components/Connection/connectionOAuthForm.scss +++ b/src/components/Connection/connectionFormOAuth.scss @@ -1,16 +1,16 @@ @import 'src/styles/base/color'; @import 'src/styles/base/breakpoint'; -// KonnectorOAuthForm + .koauthform { margin: 0 1.5rem; padding: 0 1rem; - .create-account { - color: $grey-bright; - } @media #{$large-phone} { margin: 0; padding: 0 1rem; } + .create-account { + color: $grey-bright; + } .koauthform-text { color: $grey-bright; padding-top: 1rem; diff --git a/src/components/Connection/connectionLoginForm.scss b/src/components/Connection/connectionLoginForm.scss deleted file mode 100644 index 258cf9df8a7441e9ca934cbcc0615746a0640aa7..0000000000000000000000000000000000000000 --- a/src/components/Connection/connectionLoginForm.scss +++ /dev/null @@ -1,73 +0,0 @@ -@import 'src/styles/base/color'; -@import 'src/styles/base/breakpoint'; - -// KonnectorLoginForm -.kloginauthform { - .create-account { - color: $grey-bright; - } - .kloginauthform-text { - color: $grey-bright; - padding-top: 1rem; - } - .kloginauthform-button { - margin-bottom: 1rem; - } -} - -.form { - padding: 1rem; - @media #{$large-phone} { - padding: 1rem; - } - label { - font-style: normal; - font-weight: normal; - font-size: 1rem; - color: $grey-bright; - margin-top: 0rem; - } - .form-input { - background: rgba(0, 0, 0, 0.3); - border: 1px solid #7b7b7b; - border-radius: 4px; - color: $grey-bright; - padding: 0 5%; - outline-offset: -1px; - &.--error { - border: 1px solid $red-primary; - } - } - .form-input:focus { - outline: none; - border: 1px solid $gold-shadow; - } - .form-group { - display: flex; - flex-direction: column; - margin: 1.5rem 0 0 0; - .form-trailing-icon { - float: right; - position: relative; - margin-top: -48px; - margin-right: 15px; - } - } - .form-control { - height: 3rem; - @media #{$large-phone} { - max-width: 100vw; - } - } - .form-message { - color: $red-primary; - min-height: 1.25rem; - margin-top: 0.75rem; - } - .form-button { - margin-top: 0.75rem; - } - ::placeholder { - color: $grey-bright; - } -} diff --git a/src/components/Connection/connectionNotFound.scss b/src/components/Connection/connectionNotFound.scss index 180ec9800d9a9338742a2ce3bd015aebfbe20cd5..8a162b898e8c2aaf242d19f4a2a3ab2098edf3f5 100644 --- a/src/components/Connection/connectionNotFound.scss +++ b/src/components/Connection/connectionNotFound.scss @@ -1,10 +1,12 @@ @import 'src/styles/base/color'; @import 'src/styles/base/breakpoint'; -// KonnectorNotFound + .knotfound { margin: 0 1.5rem; + padding: 0 1rem; @media #{$large-phone} { margin: 0; + padding: 0 1rem; } .knotfound-text { color: $grey-bright; diff --git a/src/components/ConsumptionNavigator/ActivateHalfHourLoad.tsx b/src/components/ConsumptionNavigator/ActivateHalfHourLoad.tsx index ab44bc6452c140ed1dd3c7ee4dce62b8dde59e1c..4f4e60ee39b731be0b262ea3f3327cb9c850b785 100644 --- a/src/components/ConsumptionNavigator/ActivateHalfHourLoad.tsx +++ b/src/components/ConsumptionNavigator/ActivateHalfHourLoad.tsx @@ -20,7 +20,9 @@ const ActivateHalfHourLoad = () => { className="cta-box-button" type="button" color="primary" - onClick={() => window.open(fluidConfig[0].siteLink, '_blank')} + onClick={() => + window.open(fluidConfig[0].konnectorConfig.siteLink, '_blank') + } > <div className="oauthform-button-content"> <div className="oauthform-button-content-icon"> diff --git a/src/components/Home/HomeIndicators.tsx b/src/components/Home/HomeIndicators.tsx index ca44379b8b08026ec47274d88b07def9b0bcdd87..e2687dcff2103d0a0ecabadf9b0c1dc305464e39 100644 --- a/src/components/Home/HomeIndicators.tsx +++ b/src/components/Home/HomeIndicators.tsx @@ -15,7 +15,7 @@ import { convertDateToShortDateString } from 'utils/date' import PerformanceIndicatorContent from 'components/PerformanceIndicator/PerformanceIndicatorContent' import FluidPerformanceIndicator from 'components/PerformanceIndicator/FluidPerformanceIndicator' -import KonnectorViewerListItem from 'components/Konnector/KonnectorViewerListItem' +import KonnectorViewerCard from 'components/Konnector/KonnectorViewerCard' interface HomeIndicatorsProps { timeStep: TimeStep @@ -28,7 +28,9 @@ const HomeIndicators: React.FC<HomeIndicatorsProps> = ({ }: HomeIndicatorsProps) => { const { t } = useI18n() const client = useClient() - const { fluidTypes } = useSelector((state: AppStore) => state.ecolyo.global) + const { fluidStatus, fluidTypes } = useSelector( + (state: AppStore) => state.ecolyo.global + ) const [performanceIndicators, setPerformanceIndicators] = useState< PerformanceIndicator[] >([]) @@ -122,9 +124,9 @@ const HomeIndicators: React.FC<HomeIndicatorsProps> = ({ })} {fluidConfig.map((fluid, index) => { return fluidTypes.includes(fluid.fluidTypeId) ? null : ( - <KonnectorViewerListItem + <KonnectorViewerCard key={index} - fluidConfig={fluid} + fluidStatus={fluidStatus[index]} isParam={false} /> ) diff --git a/src/components/Home/HomeView.tsx b/src/components/Home/HomeView.tsx index e7f96544bf54fd67cfa109fc07bf5cec48164c4f..c80bb8901730a33f08624445b232aa18c4cfc01f 100644 --- a/src/components/Home/HomeView.tsx +++ b/src/components/Home/HomeView.tsx @@ -133,7 +133,7 @@ const HomeView: React.FC = () => { await checkFluidDataPeriod() } checkData() - }, [fluidStatus, checkFluidDataPeriod]) + }, [checkFluidDataPeriod]) return ( <React.Fragment> diff --git a/src/components/Home/OldFluidDataModal.tsx b/src/components/Home/OldFluidDataModal.tsx index 5f99a53e74d96f6a777b8d26ab20e960778493fe..f56608d357836acbdefca6ffcbc5bbfa43141ffb 100644 --- a/src/components/Home/OldFluidDataModal.tsx +++ b/src/components/Home/OldFluidDataModal.tsx @@ -32,7 +32,7 @@ const OldFluidDataModal: React.FC<OldFluidDataModalProps> = ({ fluidStatus && fluidStatus.length > 0 && fluidStatus.forEach(fluid => { - if (fluid.status === FluidState.ERRORED) { + if (fluid.status === FluidState.ERROR) { !erroredKonnectors.includes(fluid.fluidType) && erroredKonnectors.push(fluid.fluidType) } diff --git a/src/components/Konnector/KonnectorModal.tsx b/src/components/Konnector/KonnectorModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..804a40cb7ad5052d1526eeeca3c753e3432ed271 --- /dev/null +++ b/src/components/Konnector/KonnectorModal.tsx @@ -0,0 +1,94 @@ +import React from 'react' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import './konnectorModal.scss' + +import { ERROR_EVENT } from 'cozy-harvest-lib/dist/models/ConnectionFlow' + +import Lottie from 'react-lottie' +import * as loadingData from 'assets/anims/bounceloading.json' + +import StyledButton from 'components/CommonKit/Button/StyledButton' +import Modal from 'components/CommonKit/Modal/Modal' +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import errorIcon from 'assets/icons/visu/data-nok.svg' +import successIcon from 'assets/icons/visu/data-ok.svg' +import { FluidType } from 'enum/fluid.enum' + +const loadingOptions = { + loop: true, + autoplay: true, + animationData: loadingData, + rendererSettings: { + preserveAspectRatio: 'xMidYMid slice', + }, +} + +interface KonnectorModalProps { + open: boolean + state: string | null + fluidType: FluidType + handleCloseClick: Function +} + +const KonnectorModal: React.FC<KonnectorModalProps> = ({ + open, + state, + fluidType, + handleCloseClick, +}: KonnectorModalProps) => { + const { t } = useI18n() + const fluidName: string = FluidType[fluidType] + return ( + <Modal + open={open} + handleCloseClick={() => handleCloseClick()} + border={false} + > + <div className="kmodal-content"> + {!state ? ( + <> + <Lottie options={loadingOptions} height={50} width={50} speed={2} /> + <div className="kmodal-content-text kmodal-content-text-center text-16-normal"> + <div className="kc-wait">{t('KONNECTORCONFIG.PLZ_WAIT')}</div> + </div> + <div className="kmodal-content-text text-16-normal"> + <div>{t('KONNECTORCONFIG.LOADING_DATA')}</div> + </div> + </> + ) : ( + <> + <div className="kmodal-info"> + {state === ERROR_EVENT ? ( + <div className="konnector-config"> + <StyledIcon icon={errorIcon} size={48} /> + <div className="kce-picto-txt text-20-bold"> + {t('KONNECTORCONFIG.ERROR_TXT')} + </div> + <div>{t('KONNECTORCONFIG.ERROR_DATA_' + fluidName)}</div> + <div>{t('KONNECTORCONFIG.ERROR_DATA_2')}</div> + </div> + ) : ( + <div className="konnector-config"> + <StyledIcon icon={successIcon} size={48} /> + <div className="kcs-picto-txt text-20-bold"> + {t('KONNECTORCONFIG.SUCCESS_TXT')} + </div> + {t('KONNECTORCONFIG.SUCCESS_DATA_' + fluidName)} + </div> + )} + <StyledButton + type="button" + color="primary" + onClick={() => handleCloseClick()} + > + <div>{t('KONNECTORCONFIG.OK')}</div> + </StyledButton> + </div> + </> + )} + </div> + </Modal> + ) +} + +export default KonnectorModal diff --git a/src/components/Konnector/KonnectorViewerCard.tsx b/src/components/Konnector/KonnectorViewerCard.tsx index 69f98d2fda0bc98940c09d874d7551120207d776..e041b50887883f681a7feedb6afa4ffbae811c57 100644 --- a/src/components/Konnector/KonnectorViewerCard.tsx +++ b/src/components/Konnector/KonnectorViewerCard.tsx @@ -1,14 +1,34 @@ import React, { useState, useEffect, useRef, useCallback } from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { useClient } from 'cozy-client' +import { useDispatch, useSelector } from 'react-redux' +import { AppStore } from 'store' +import classNames from 'classnames' +import './konnectorViewerCard.scss' -import { FluidType } from 'enum/fluid.enum' -import { JobState } from 'enum/jobState.enum' -import { Account, Konnector, Trigger, TriggerState, FluidConfig } from 'models' -import AccountService from 'services/account.service' -import TriggerService from 'services/triggers.service' +import { FluidState, FluidType } from 'enum/fluid.enum' +import { UserChallengeState } from 'enum/userChallenge.enum' +import { UserDuelState } from 'enum/userDuel.enum' +import { + Account, + Konnector, + Trigger, + FluidStatus, + Profile, + FluidConnection, +} from 'models' import { getPicto, getAddPicto, getParamPicto } from 'utils/picto' -import { getFluidType } from 'utils/utils' +import { updateProfile } from 'store/profile/profile.actions' +import { setChallengeConsumption } from 'store/challenge/challenge.actions' +import { + setFluidStatus, + toogleChallengeNotification, + updatedFluidConnection, +} from 'store/global/global.actions' +import ProfileService from 'services/profile.service' +import FluidService from 'services/fluid.service' +import InitializationService from 'services/initialization.service' +import ChallengeService from 'services/challenge.service' import chevronDown from 'assets/icons/ico/chevron-down.svg' import chevronUp from 'assets/icons/ico/chevron-up.svg' @@ -18,188 +38,236 @@ import failurePicto from 'assets/png/picto/picto-failure.png' import ConnectionNotFound from 'components/Connection/ConnectionNotFound' import ConnectionForm from 'components/Connection/ConnectionForm' import ConnectionResult from 'components/Connection/ConnectionResult' -import ConnectionLaunch from 'components/Connection/ConnectionLaunch' -import './konnectorViewerCard.scss' +import KonnectorModal from 'components/Konnector/KonnectorModal' + +import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' +import ConnectionFlow, { + ERROR_EVENT, + LOGIN_SUCCESS_EVENT, + SUCCESS_EVENT, +} from 'cozy-harvest-lib/dist/models/ConnectionFlow' + interface KonnectorViewerCardProps { - fluidConfig: FluidConfig - konnector: Konnector | null + fluidStatus: FluidStatus isParam: boolean } const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ - fluidConfig, - konnector, + fluidStatus, isParam, }: KonnectorViewerCardProps) => { const { t } = useI18n() const client = useClient() - const [account, setAccount] = useState<Account | null>(null) - const [trigger, setTrigger] = useState<Trigger | null>(null) - const [triggerState, setTriggerState] = useState<TriggerState | null>(null) - const [shouldLaunch, setLaunch] = useState<boolean>(false) + const dispatch = useDispatch() - const [setActive, setActiveState] = useState('') - const [setHeight, setHeightState] = useState('0px') + const fluidSlug = fluidStatus.connection.konnectorConfig.slug + const fluidState = fluidStatus.status + const konnector: Konnector | null = fluidStatus.connection.konnector + const account: Account | null = fluidStatus.connection.account + const trigger: Trigger | null = fluidStatus.connection.trigger - const [jobState, setJobState] = useState<string>('') - - const [isLoading, setLoading] = useState<boolean>(true) + const [active, setActive] = useState<boolean>(false) + const [openModal, setOpenModal] = useState(false) + const [state, setState] = useState<string | null>(null) + const { currentChallenge } = useSelector( + (state: AppStore) => state.ecolyo.challenge + ) - const content: React.MutableRefObject<null> = useRef<null>(null) - const type: string = fluidConfig.konnectorConfig.type - const fluid: FluidType = getFluidType(type) - const iconType = getPicto(fluid) + const content = useRef<HTMLDivElement>(null) + const scrollHeight = + content.current && content.current.scrollHeight + ? content.current.scrollHeight + : 0 - const iconAddType = isParam ? getParamPicto(fluid) : getAddPicto(fluid) - const loginFailed: boolean = - triggerState != null && - triggerState.last_error != undefined && - triggerState.last_error === 'LOGIN_FAILED' && - triggerState.status != undefined && - triggerState.status === 'errored' + const iconType = getPicto(fluidStatus.fluidType) + const iconAddType = isParam + ? getParamPicto(fluidStatus.fluidType) + : getAddPicto(fluidStatus.fluidType) const toggleAccordion = () => { - setActiveState(setActive === '' ? 'active' : '') - setHeightState( - setActive === 'active' && - content && - content.current && - content.current.scrollHeight - ? '0px' - : `${content.current.scrollHeight}px` - ) + setActive(prev => !prev) } - const getKonnectorStateMarkup = () => { - return jobState === JobState.Errored ? ( - <img className="state-icon" src={failurePicto}></img> - ) : ( - '' - ) - } + const updateProfileHaveSeenOldFluidModal = useCallback(async () => { + const profileService = new ProfileService(client) + await profileService + .updateProfile({ haveSeenOldFluidModal: false }) + .then((updatedProfile: Profile | null) => { + if (updatedProfile) { + dispatch(updateProfile(updatedProfile)) + } + }) + }, [client, dispatch]) - const updateState = useCallback( - async (_trigger: Trigger) => { - const triggerService = new TriggerService(client) - const _triggerState = await triggerService.fetchTriggerState(_trigger) - if (_triggerState) { - setTriggerState(_triggerState) - } - }, - [client] - ) + const updateGlobalState = useCallback(async () => { + const fluidService = new FluidService(client) + await fluidService.getFluidStatus().then((fluidStatus: FluidStatus[]) => { + dispatch(setFluidStatus(fluidStatus)) + }) + }, [client, dispatch]) - const handleSuccessForm = useCallback( - (_account: Account, _trigger: Trigger) => { - setAccount(_account) - setTrigger(_trigger) - setLaunch(true) - }, - [] - ) + const refreshChallengeState = useCallback(async () => { + if ( + currentChallenge && + currentChallenge.state === UserChallengeState.DUEL && + currentChallenge.duel.state === UserDuelState.ONGOING + ) { + const initializationService = new InitializationService(client) + const { + updatedUserChallenge, + dataloads, + } = await initializationService.initDuelProgress(currentChallenge) + dispatch(setChallengeConsumption(updatedUserChallenge, dataloads)) + // Check is duel is done and display notification + const challengeService = new ChallengeService(client) + const { isDone } = await challengeService.isChallengeDone( + updatedUserChallenge, + dataloads + ) + dispatch(toogleChallengeNotification(isDone)) + } + }, [client, dispatch, currentChallenge]) + + const handleConnectionEnd = useCallback(async () => { + const updatedConnection: FluidConnection = { + ...fluidStatus.connection, + shouldLaunchKonnector: false, + } + dispatch(updatedFluidConnection(fluidStatus.fluidType, updatedConnection)) + await updateProfileHaveSeenOldFluidModal() + await refreshChallengeState() + await updateGlobalState + setOpenModal(false) + }, [ + dispatch, + fluidStatus.connection, + fluidStatus.fluidType, + updateProfileHaveSeenOldFluidModal, + updateGlobalState, + refreshChallengeState, + ]) + + const handleAccountDeletion = useCallback(async () => { + await updateProfileHaveSeenOldFluidModal() + await refreshChallengeState() + await updateGlobalState() + setActive(false) + }, [ + updateProfileHaveSeenOldFluidModal, + updateGlobalState, + refreshChallengeState, + ]) - const handleConnectionLaunch = useCallback(async () => { - if (trigger) { - await updateState(trigger) + const getConnectionCard = useCallback(() => { + if (fluidState === FluidState.KONNECTOR_NOT_FOUND) { + return <ConnectionNotFound konnectorSlug={fluidSlug} /> + } else if (account && fluidState !== FluidState.ERROR_LOGIN_FAILED) { + return ( + <ConnectionResult + fluidStatus={fluidStatus} + handleAccountDeletion={handleAccountDeletion} + /> + ) + } else { + return <ConnectionForm fluidStatus={fluidStatus} /> } - setLaunch(false) - }, [trigger, updateState]) + }, [fluidSlug, fluidState, account, fluidStatus, handleAccountDeletion]) - const handleJobState = useCallback((_jobState: JobState) => { - setJobState(_jobState) - }, []) + const callbackResponse = (_state: string) => { + setState(_state) + } useEffect(() => { let subscribed = true async function getData() { - setLoading(true) - if (konnector) { - const accountService = new AccountService(client) - const _account = await accountService.getAccountByType(konnector.slug) - if (subscribed && _account) { - setAccount(_account) - const triggerService = new TriggerService(client) - const _trigger = await triggerService.getTrigger(_account, konnector) - if (subscribed && _trigger) { - setTrigger(_trigger) - await updateState(_trigger) - } - } + if ( + subscribed && + fluidStatus.connection.shouldLaunchKonnector && + !isKonnectorRunning(trigger) + ) { + const connectionFlow = new ConnectionFlow(client, trigger, konnector) + await connectionFlow.launch() + connectionFlow.jobWatcher.on(ERROR_EVENT, () => { + callbackResponse(ERROR_EVENT) + }) + connectionFlow.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => { + callbackResponse(LOGIN_SUCCESS_EVENT) + }) + connectionFlow.jobWatcher.on(SUCCESS_EVENT, () => { + callbackResponse(SUCCESS_EVENT) + }) + setOpenModal(true) } - setLoading(false) } getData() return () => { subscribed = false } - }, [client, konnector, updateState]) + }, [client, konnector, trigger, fluidStatus.connection.shouldLaunchKonnector]) return ( <> - {isLoading ? null : ( - <div - className={`accordion ${ - jobState === JobState.Errored ? '--errored' : '' - } ${setActive}`} - > - <div> - <div - className={`accordion-header ${setActive}`} - onClick={toggleAccordion} - > - <div className="accordion-icon"> - {account && !loginFailed ? ( - <StyledIcon className="icon" icon={iconType} size={49} /> - ) : ( - <StyledIcon className="icon" icon={iconAddType} size={49} /> - )} - </div> - <div className="state-picto">{getKonnectorStateMarkup()}</div> - <div className="accordion-info"> - <div className="accordion-title text-16-normal"> - {account && !loginFailed - ? t('FLUID.' + FluidType[fluid] + '.LABEL') - : t('KONNECTORCONFIG.LABEL_CONNECTTO_' + FluidType[fluid])} - </div> - </div> - <StyledIconButton icon={setActive ? chevronUp : chevronDown} /> - </div> - - <div - ref={content} - style={{ maxHeight: `${setHeight}` }} - className={`accordion-content ${setActive}`} - > - {!konnector ? ( - <ConnectionNotFound - konnectorSlug={fluidConfig.konnectorConfig.slug} - /> - ) : shouldLaunch && trigger ? ( - <ConnectionLaunch - trigger={trigger} - konnector={konnector} - type={type} - handleConnectionLaunch={handleConnectionLaunch} - /> - ) : account && !loginFailed ? ( - <ConnectionResult - account={account} - konnector={konnector} - handleJobState={handleJobState} - /> + <div + className={classNames('accordion', { + ['--errored']: + fluidStatus.status === FluidState.ERROR || + fluidStatus.status === FluidState.ERROR_LOGIN_FAILED, + ['active']: active, + })} + > + <div> + <div + className={classNames('accordion-header', { + ['active']: active, + })} + onClick={toggleAccordion} + > + <div className="accordion-icon"> + {fluidStatus.connection.account && + fluidStatus.status !== FluidState.ERROR_LOGIN_FAILED ? ( + <StyledIcon className="icon" icon={iconType} size={49} /> ) : ( - <ConnectionForm - fluidConfig={fluidConfig} - konnector={konnector} - account={account} - trigger={trigger} - handleSuccessForm={handleSuccessForm} - /> + <StyledIcon className="icon" icon={iconAddType} size={49} /> )} </div> + <div className="state-picto"> + {fluidStatus.status === FluidState.ERROR ? ( + <img className="state-icon" src={failurePicto}></img> + ) : null} + </div> + <div className="accordion-info"> + <div className="accordion-title text-16-normal"> + {fluidStatus.connection.account && + fluidStatus.status !== FluidState.ERROR_LOGIN_FAILED + ? t('FLUID.' + FluidType[fluidStatus.fluidType] + '.LABEL') + : t( + 'KONNECTORCONFIG.LABEL_CONNECTTO_' + + FluidType[fluidStatus.fluidType] + )} + </div> + </div> + <StyledIconButton icon={active ? chevronUp : chevronDown} /> + </div> + + <div + ref={content} + style={{ + maxHeight: `${active ? scrollHeight : 0}px`, + }} + className={classNames('accordion-content', { + ['active']: active, + })} + > + {getConnectionCard()} </div> </div> - )} + </div> + <KonnectorModal + open={openModal} + state={state} + fluidType={fluidStatus.fluidType} + handleCloseClick={handleConnectionEnd} + /> </> ) } diff --git a/src/components/Konnector/KonnectorViewerList.tsx b/src/components/Konnector/KonnectorViewerList.tsx index b9553ab370afee71f6c532504a99e79be07051fe..e81a2b135e686c255a5cf1cf6b808381e93510d0 100644 --- a/src/components/Konnector/KonnectorViewerList.tsx +++ b/src/components/Konnector/KonnectorViewerList.tsx @@ -2,22 +2,34 @@ import React from 'react' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { useSelector } from 'react-redux' import { AppStore } from 'store' +import './konnectorViewerList.scss' -import { FluidConfig } from 'models' -import ConfigService from 'services/fluidConfig.service' +import { FluidStatus } from 'models' +import { FluidState } from 'enum/fluid.enum' +import KonnectorViewerCard from 'components/Konnector/KonnectorViewerCard' -import KonnectorViewerListItem from 'components/Konnector/KonnectorViewerListItem' -import './konnectorViewerList.scss' interface KonnectorViewerListProps { isParam: boolean } +const sortFluid = (a: FluidStatus, b: FluidStatus): number => { + if ( + (a.status === FluidState.KONNECTOR_NOT_FOUND || + a.status === FluidState.NOT_CONNECTED) && + b.status !== FluidState.KONNECTOR_NOT_FOUND && + b.status !== FluidState.NOT_CONNECTED + ) { + return 1 + } else { + return -1 + } +} + const KonnectorViewerList: React.FC<KonnectorViewerListProps> = ({ isParam = false, }: KonnectorViewerListProps) => { const { t } = useI18n() - const fluidConfigs: FluidConfig[] = new ConfigService().getFluidConfig() - const { fluidTypes } = useSelector((state: AppStore) => state.ecolyo.global) + const { fluidStatus } = useSelector((state: AppStore) => state.ecolyo.global) return ( <div className="kv-root"> <div className="kv-content"> @@ -25,24 +37,17 @@ const KonnectorViewerList: React.FC<KonnectorViewerListProps> = ({ {t('KONNECTORCONFIG.TITLE_CONNECTION')} </div> <div> - {fluidConfigs.map((item: FluidConfig, index: number) => { - return fluidTypes.includes(item.fluidTypeId) ? ( - <KonnectorViewerListItem - isParam={isParam} - key={index} - fluidConfig={item} - /> - ) : null - })} - {fluidConfigs.map((item: FluidConfig, index: number) => { - return fluidTypes.includes(item.fluidTypeId) ? null : ( - <KonnectorViewerListItem - isParam={isParam} - key={index} - fluidConfig={item} - /> - ) - })} + {fluidStatus + .sort(sortFluid) + .map((fluidStatusItem: FluidStatus, index: number) => { + return ( + <KonnectorViewerCard + key={index} + isParam={isParam} + fluidStatus={fluidStatusItem} + /> + ) + })} </div> </div> </div> diff --git a/src/components/Konnector/konnectorModal.scss b/src/components/Konnector/konnectorModal.scss new file mode 100644 index 0000000000000000000000000000000000000000..e300cd6bd709187fc9866f20cc2f64900f87fc14 --- /dev/null +++ b/src/components/Konnector/konnectorModal.scss @@ -0,0 +1,39 @@ +@import 'src/styles/base/color'; +@import 'src/styles/base/breakpoint'; + +.kmodal-content { + margin: 0.5rem 1.5rem; + @media #{$large-phone} { + margin: 0.5rem 0; + } + .kmodal-content-text { + color: $grey-bright; + margin: 1rem; + text-align: center; + .kc-wait { + color: $soft-grey; + margin-bottom: 2rem; + } + } + .kmodal-content-text-center { + text-align: center; + } + .kmodal-info { + margin: 1.5rem; + .konnector-config { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + .kce-picto-txt { + color: $red-primary; + margin: 1.25rem; + } + .kcs-picto-txt { + color: $multi-color; + margin: 1.25rem; + } + } + } +} diff --git a/src/components/Konnector/konnectorViewerCard.scss b/src/components/Konnector/konnectorViewerCard.scss index 6eb517cf0fb673e7c43af17f9fe3cbf9af3b2d7a..45218cbe34fcf9a99bb2700dab2919d115327b20 100644 --- a/src/components/Konnector/konnectorViewerCard.scss +++ b/src/components/Konnector/konnectorViewerCard.scss @@ -11,6 +11,7 @@ background: $grey-linear-gradient-background; box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.75); transition: background-color 0.6s ease; + overflow: hidden; &.active { background-color: $accordion-active-background; } diff --git a/src/cozy-client.d.ts b/src/cozy-client.d.ts index 1dda96e002e55086570cd51d70ac41438a803134..d47de75b0c4c6c6883f44a33985a96080f61fc3f 100644 --- a/src/cozy-client.d.ts +++ b/src/cozy-client.d.ts @@ -82,7 +82,7 @@ declare module 'cozy-client' { } export class Client { - appMetadata: { version: string } + appMetadata: { version: string; slug: string } options: ClientLogin idCounter: number isLogged: boolean diff --git a/src/enum/fluid.enum.ts b/src/enum/fluid.enum.ts index e485b1af0ad06a1556b68787ba207bc7360398c1..e62cd529ed496bf0b6c157c4d59c82b5ac04f3d4 100644 --- a/src/enum/fluid.enum.ts +++ b/src/enum/fluid.enum.ts @@ -8,7 +8,7 @@ export enum FluidType { export enum FluidState { KONNECTOR_NOT_FOUND = 0, NOT_CONNECTED = 1, - LOGIN_FAILED = 100, DONE = 200, - ERRORED = 300, + ERROR = 300, + ERROR_LOGIN_FAILED = 301, } diff --git a/src/models/config.model.ts b/src/models/config.model.ts index 95fa4101b9fdc1e5f7fcd86760126c4d870ca130..c291ff66cb47545ae3d6a368696287de4f33d78f 100644 --- a/src/models/config.model.ts +++ b/src/models/config.model.ts @@ -1,9 +1,9 @@ -interface KonnectorConfig { +export interface KonnectorConfig { name: string - type: string oauth: boolean slug: string cron?: string + siteLink: string } export interface FluidConfig { @@ -12,5 +12,4 @@ export interface FluidConfig { coefficient: number dataDelayOffset: number konnectorConfig: KonnectorConfig - siteLink: string } diff --git a/src/models/fluid.model.ts b/src/models/fluid.model.ts index ac34e80770e6621025f52a243ee316e16ced89bb..2e92b74e8cbf1f872652f5e92e1c60ad0eea940f 100644 --- a/src/models/fluid.model.ts +++ b/src/models/fluid.model.ts @@ -1,14 +1,15 @@ import { DateTime } from 'luxon' import { FluidState, FluidType } from 'enum/fluid.enum' -import { Konnector, Trigger, Account } from 'models' +import { Konnector, Trigger, Account, KonnectorConfig } from 'models' +import { TriggerState } from './trigger.model' -interface FluidConnection { - type: string - slug: string - isConnectionOnGoing: boolean +export interface FluidConnection { + shouldLaunchKonnector: boolean konnector: Konnector | null account: Account | null trigger: Trigger | null + triggerState: TriggerState | null + konnectorConfig: KonnectorConfig } export interface FluidStatus { fluidType: FluidType diff --git a/src/services/fluid.service.spec.ts b/src/services/fluid.service.spec.ts index f9cbfcccbf6ac9a67ded956058df2ff05889fdf0..383d94131a5606cf657251c7a43eaf1fe0b803ad 100644 --- a/src/services/fluid.service.spec.ts +++ b/src/services/fluid.service.spec.ts @@ -66,41 +66,56 @@ describe('FLuid service', () => { const mockResult: FluidStatus[] = [ { fluidType: FluidType.ELECTRICITY, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: mockLastDataDates[FluidType.ELECTRICITY], connection: { - type: 'ELECTRICITY', - slug: 'enedisgrandlyon', konnector: konnectorsData[0], account: accountsData[0], trigger: triggersData[0], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Enedis', + oauth: true, + slug: 'enedisgrandlyon', + siteLink: 'https://mon-compte-particulier.enedis.fr/donnees/', + }, }, }, { fluidType: FluidType.WATER, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: mockLastDataDates[FluidType.WATER], connection: { - type: 'WATER', - slug: 'eglgrandlyon', konnector: konnectorsData[1], account: accountsData[1], trigger: triggersData[1], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Eau du Grand Lyon', + oauth: false, + slug: 'eglgrandlyon', + siteLink: 'https://agence.eaudugrandlyon.com/inscription.aspx', + }, }, }, { fluidType: FluidType.GAS, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: mockLastDataDates[FluidType.GAS], connection: { - type: 'GAS', - slug: 'grdfgrandlyon', konnector: konnectorsData[2], account: accountsData[2], trigger: triggersData[2], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'GRDF', + oauth: true, + slug: 'grdfgrandlyon', + siteLink: 'https://monespace.grdf.fr/monespace/connexion', + }, }, }, ] @@ -134,12 +149,17 @@ describe('FLuid service', () => { status: FluidState.NOT_CONNECTED, lastDataDate: mockLastDataDates[FluidType.ELECTRICITY], connection: { - type: 'ELECTRICITY', - slug: 'enedisgrandlyon', konnector: konnectorsData[0], account: null, trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Enedis', + oauth: true, + slug: 'enedisgrandlyon', + siteLink: 'https://mon-compte-particulier.enedis.fr/donnees/', + }, }, }, { @@ -147,12 +167,17 @@ describe('FLuid service', () => { status: FluidState.NOT_CONNECTED, lastDataDate: mockLastDataDates[FluidType.WATER], connection: { - type: 'WATER', - slug: 'eglgrandlyon', konnector: konnectorsData[1], account: null, trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Eau du Grand Lyon', + oauth: false, + slug: 'eglgrandlyon', + siteLink: 'https://agence.eaudugrandlyon.com/inscription.aspx', + }, }, }, { @@ -160,12 +185,17 @@ describe('FLuid service', () => { status: FluidState.NOT_CONNECTED, lastDataDate: mockLastDataDates[FluidType.GAS], connection: { - type: 'GAS', - slug: 'grdfgrandlyon', konnector: konnectorsData[2], account: null, trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'GRDF', + oauth: true, + slug: 'grdfgrandlyon', + siteLink: 'https://monespace.grdf.fr/monespace/connexion', + }, }, }, ] @@ -199,12 +229,17 @@ describe('FLuid service', () => { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: mockLastDataDates[FluidType.ELECTRICITY], connection: { - type: 'ELECTRICITY', - slug: 'enedisgrandlyon', konnector: null, account: accountsData[0], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Enedis', + oauth: true, + slug: 'enedisgrandlyon', + siteLink: 'https://mon-compte-particulier.enedis.fr/donnees/', + }, }, }, { @@ -212,12 +247,17 @@ describe('FLuid service', () => { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: mockLastDataDates[FluidType.WATER], connection: { - type: 'WATER', - slug: 'eglgrandlyon', konnector: null, account: accountsData[1], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Eau du Grand Lyon', + oauth: false, + slug: 'eglgrandlyon', + siteLink: 'https://agence.eaudugrandlyon.com/inscription.aspx', + }, }, }, { @@ -225,12 +265,17 @@ describe('FLuid service', () => { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: mockLastDataDates[FluidType.GAS], connection: { - type: 'GAS', - slug: 'grdfgrandlyon', konnector: null, account: accountsData[2], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'GRDF', + oauth: true, + slug: 'grdfgrandlyon', + siteLink: 'https://monespace.grdf.fr/monespace/connexion', + }, }, }, ] @@ -264,12 +309,17 @@ describe('FLuid service', () => { status: FluidState.NOT_CONNECTED, lastDataDate: mockLastDataDates[FluidType.ELECTRICITY], connection: { - type: 'ELECTRICITY', - slug: 'enedisgrandlyon', konnector: konnectorsData[0], account: accountsData[0], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Enedis', + oauth: true, + slug: 'enedisgrandlyon', + siteLink: 'https://mon-compte-particulier.enedis.fr/donnees/', + }, }, }, { @@ -277,12 +327,17 @@ describe('FLuid service', () => { status: FluidState.NOT_CONNECTED, lastDataDate: mockLastDataDates[FluidType.WATER], connection: { - type: 'WATER', - slug: 'eglgrandlyon', konnector: konnectorsData[1], account: accountsData[1], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Eau du Grand Lyon', + oauth: false, + slug: 'eglgrandlyon', + siteLink: 'https://agence.eaudugrandlyon.com/inscription.aspx', + }, }, }, { @@ -290,12 +345,17 @@ describe('FLuid service', () => { status: FluidState.NOT_CONNECTED, lastDataDate: mockLastDataDates[FluidType.GAS], connection: { - type: 'GAS', - slug: 'grdfgrandlyon', konnector: konnectorsData[2], account: accountsData[2], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'GRDF', + oauth: true, + slug: 'grdfgrandlyon', + siteLink: 'https://monespace.grdf.fr/monespace/connexion', + }, }, }, ] @@ -326,41 +386,56 @@ describe('FLuid service', () => { const mockResult: FluidStatus[] = [ { fluidType: FluidType.ELECTRICITY, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: mockLastDataDates[FluidType.ELECTRICITY], connection: { - type: 'ELECTRICITY', - slug: 'enedisgrandlyon', konnector: konnectorsData[0], account: accountsData[0], trigger: triggersData[0], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Enedis', + oauth: true, + slug: 'enedisgrandlyon', + siteLink: 'https://mon-compte-particulier.enedis.fr/donnees/', + }, }, }, { fluidType: FluidType.WATER, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: null, connection: { - type: 'WATER', - slug: 'eglgrandlyon', konnector: konnectorsData[1], account: accountsData[1], trigger: triggersData[1], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Eau du Grand Lyon', + oauth: false, + slug: 'eglgrandlyon', + siteLink: 'https://agence.eaudugrandlyon.com/inscription.aspx', + }, }, }, { fluidType: FluidType.GAS, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: null, connection: { - type: 'GAS', - slug: 'grdfgrandlyon', konnector: konnectorsData[2], account: accountsData[2], trigger: triggersData[2], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'GRDF', + oauth: true, + slug: 'grdfgrandlyon', + siteLink: 'https://monespace.grdf.fr/monespace/connexion', + }, }, }, ] diff --git a/src/services/fluid.service.ts b/src/services/fluid.service.ts index 9e801507f3d04d25acd99d32ea0837656b403189..213933ea022afc845b0cd365ed2c375a85bede7b 100644 --- a/src/services/fluid.service.ts +++ b/src/services/fluid.service.ts @@ -26,7 +26,9 @@ export default class FluidService { case 'done': return FluidState.DONE case 'errored': - return FluidState.ERRORED + if (state.last_error && state.last_error === 'LOGIN_FAILED') + return FluidState.ERROR_LOGIN_FAILED + else return FluidState.ERROR default: return FluidState.NOT_CONNECTED } @@ -108,12 +110,12 @@ export default class FluidService { status: this.parseFluidStatus(elecKonnector, elecStatus), lastDataDate: lastDataDates[FluidType.ELECTRICITY], connection: { - type: fluidConfig[FluidType.ELECTRICITY].konnectorConfig.type, - slug: fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug, - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: elecKonnector, account: elecAccount, trigger: elecTrigger, + triggerState: elecStatus, + konnectorConfig: fluidConfig[FluidType.ELECTRICITY].konnectorConfig, }, }, { @@ -121,12 +123,12 @@ export default class FluidService { status: this.parseFluidStatus(waterKonnector, waterStatus), lastDataDate: lastDataDates[FluidType.WATER], connection: { - type: fluidConfig[FluidType.WATER].konnectorConfig.type, - slug: fluidConfig[FluidType.WATER].konnectorConfig.slug, - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: waterKonnector, account: waterAccount, trigger: waterTrigger, + triggerState: waterStatus, + konnectorConfig: fluidConfig[FluidType.WATER].konnectorConfig, }, }, { @@ -134,12 +136,12 @@ export default class FluidService { status: this.parseFluidStatus(gasKonnector, gasStatus), lastDataDate: lastDataDates[FluidType.GAS], connection: { - type: fluidConfig[FluidType.GAS].konnectorConfig.type, - slug: fluidConfig[FluidType.GAS].konnectorConfig.slug, - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: gasKonnector, account: gasAccount, trigger: gasTrigger, + triggerState: gasStatus, + konnectorConfig: fluidConfig[FluidType.GAS].konnectorConfig, }, }, ] diff --git a/src/store/global/global.actions.ts b/src/store/global/global.actions.ts index b0a16c593e40aba842cca9218cea6bac8f83d9e9..3a889a003c50d53cf2ffa489ab17168a0fcd80d0 100644 --- a/src/store/global/global.actions.ts +++ b/src/store/global/global.actions.ts @@ -1,10 +1,12 @@ +import { FluidType } from 'enum/fluid.enum' import { ScreenType } from 'enum/screen.enum' -import { FluidStatus } from 'models' +import { FluidConnection, FluidStatus } from 'models' export const CHANGE_SCREEN_TYPE = 'CHANGE_SCREEN_TYPE' export const TOOGLE_CHALLENGE_NOTIFICATION = 'TOOGLE_CHALLENGE_NOTIFICATION' export const TOOGLE_REPORT_NOTIFICATION = 'TOOGLE_REPORT_NOTIFICATION' export const SET_FLUID_STATUS = 'SET_FLUID_STATUS' +export const UPDATE_FLUID_CONNECTION = 'UPDATE_FLUID_CONNECTION' interface ChangeScreenType { type: typeof CHANGE_SCREEN_TYPE @@ -26,11 +28,17 @@ interface SetFluidStatus { payload?: FluidStatus[] } +interface UpdatedFluidConnection { + type: typeof UPDATE_FLUID_CONNECTION + payload?: { fluidType: FluidType; fluidConnection: FluidConnection } +} + export type GlobalActionTypes = | ChangeScreenType | ToogleChallengeNotification | ToogleReportNotification | SetFluidStatus + | UpdatedFluidConnection export function changeScreenType(screenType: ScreenType): GlobalActionTypes { return { @@ -59,3 +67,12 @@ export function setFluidStatus(fluidStatus: FluidStatus[]): GlobalActionTypes { payload: fluidStatus, } } +export function updatedFluidConnection( + fluidType: FluidType, + fluidConnection: FluidConnection +): GlobalActionTypes { + return { + type: UPDATE_FLUID_CONNECTION, + payload: { fluidType, fluidConnection }, + } +} diff --git a/src/store/global/global.reducer.spec.ts b/src/store/global/global.reducer.spec.ts index b6bb5b10c697d684c695830b865b1dd292b06e81..79458f9bbb03ec55e941dc00fca3f7daf993e461 100644 --- a/src/store/global/global.reducer.spec.ts +++ b/src/store/global/global.reducer.spec.ts @@ -25,12 +25,17 @@ const mockFluidStatus: FluidStatus[] = [ status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: null, connection: { - type: '', - slug: '', - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: null, account: null, trigger: null, + triggerState: null, + konnectorConfig: { + name: '', + oauth: false, + slug: '', + siteLink: '', + }, }, }, { @@ -38,12 +43,17 @@ const mockFluidStatus: FluidStatus[] = [ status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: null, connection: { - type: '', - slug: '', - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: null, account: null, trigger: null, + triggerState: null, + konnectorConfig: { + name: '', + oauth: false, + slug: '', + siteLink: '', + }, }, }, { @@ -51,12 +61,17 @@ const mockFluidStatus: FluidStatus[] = [ status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: null, connection: { - type: '', - slug: '', - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: null, account: null, trigger: null, + triggerState: null, + konnectorConfig: { + name: '', + oauth: false, + slug: '', + siteLink: '', + }, }, }, ] @@ -134,28 +149,38 @@ describe('global reducer', () => { const fluidStatus: FluidStatus[] = [ { fluidType: FluidType.ELECTRICITY, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: mockLastDataDates[FluidType.ELECTRICITY], connection: { - type: 'ELECTRICITY', - slug: 'enedisgrandlyon', konnector: konnectorsData[0], account: accountsData[0], trigger: triggersData[0], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Enedis', + oauth: true, + slug: 'enedisgrandlyon', + siteLink: 'https://mon-compte-particulier.enedis.fr/donnees/', + }, }, }, { fluidType: FluidType.WATER, - status: FluidState.ERRORED, + status: FluidState.ERROR, lastDataDate: mockLastDataDates[FluidType.WATER], connection: { - type: 'WATER', - slug: 'eglgrandlyon', konnector: konnectorsData[1], account: accountsData[1], trigger: triggersData[1], - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'Eau du Grand Lyon', + oauth: false, + slug: 'eglgrandlyon', + siteLink: 'https://agence.eaudugrandlyon.com/inscription.aspx', + }, }, }, { @@ -163,12 +188,17 @@ describe('global reducer', () => { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: mockLastDataDates[FluidType.GAS], connection: { - type: 'GAS', - slug: 'grdfgrandlyon', konnector: null, account: accountsData[2], trigger: null, - isConnectionOnGoing: false, + triggerState: null, + shouldLaunchKonnector: false, + konnectorConfig: { + name: 'GRDF', + oauth: true, + slug: 'grdfgrandlyon', + siteLink: 'https://monespace.grdf.fr/monespace/connexion', + }, }, }, ] diff --git a/src/store/global/global.reducer.ts b/src/store/global/global.reducer.ts index 711b9c10c6ad03e81629c99648f6350e4f1b5ea9..df371a5562c9ea4f6942b56a40f78ed62fa86945 100644 --- a/src/store/global/global.reducer.ts +++ b/src/store/global/global.reducer.ts @@ -5,6 +5,7 @@ import { TOOGLE_REPORT_NOTIFICATION, SET_FLUID_STATUS, GlobalActionTypes, + UPDATE_FLUID_CONNECTION, } from 'store/global/global.actions' import { FluidStatus, GlobalState } from 'models' import { ScreenType } from 'enum/screen.enum' @@ -20,12 +21,17 @@ const initialState: GlobalState = { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: null, connection: { - type: '', - slug: '', - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: null, account: null, trigger: null, + triggerState: null, + konnectorConfig: { + name: '', + oauth: false, + slug: '', + siteLink: '', + }, }, }, { @@ -33,12 +39,17 @@ const initialState: GlobalState = { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: null, connection: { - type: '', - slug: '', - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: null, account: null, trigger: null, + triggerState: null, + konnectorConfig: { + name: '', + oauth: false, + slug: '', + siteLink: '', + }, }, }, { @@ -46,12 +57,17 @@ const initialState: GlobalState = { status: FluidState.KONNECTOR_NOT_FOUND, lastDataDate: null, connection: { - type: '', - slug: '', - isConnectionOnGoing: false, + shouldLaunchKonnector: false, konnector: null, account: null, trigger: null, + triggerState: null, + konnectorConfig: { + name: '', + oauth: false, + slug: '', + siteLink: '', + }, }, }, ], @@ -105,6 +121,23 @@ export const globalReducer: Reducer<GlobalState> = ( fluidTypes: getFluidTypesFromStatus(action.payload), } : state + case UPDATE_FLUID_CONNECTION: + // TODO Update status in fonction of the connection + if (action.payload !== undefined) { + const updatedFluidStatus = [...state.fluidStatus] + const fluidType: FluidType = action.payload.fluidType + const findIndex = state.fluidStatus.findIndex( + fluid => fluid.fluidType === fluidType + ) + updatedFluidStatus[findIndex].connection = + action.payload.fluidConnection + return { + ...state, + fluidStatus: updatedFluidStatus, + } + } else { + return state + } default: return state }