diff --git a/src/components/ContentComponents/KonnectorViewer/picto-failure.png b/src/assets/png/picto/picto-failure.png similarity index 100% rename from src/components/ContentComponents/KonnectorViewer/picto-failure.png rename to src/assets/png/picto/picto-failure.png diff --git a/src/components/ContainerComponents/Header/Header.tsx b/src/components/ContainerComponents/Header/Header.tsx index 4a480d809febb585e8ea728ba2f8a68477200f7c..e0d487f600c86ebdecc9bf87be06f7f6f2abc11f 100644 --- a/src/components/ContainerComponents/Header/Header.tsx +++ b/src/components/ContainerComponents/Header/Header.tsx @@ -4,7 +4,6 @@ import { history } from 'components/ContainerComponents/ViewContainer/ViewContai import { withClient, Client } from 'cozy-client' import { ScreenType } from 'enum/screen.enum' import { AppContext } from 'components/Contexts/AppContextProvider' -import useInstanceSettings from 'components/Hooks/userInstanceSettings' import BackArrowIcon from 'assets/icons/ico/back-arrow.svg' import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' @@ -29,7 +28,6 @@ const Header: React.FC<HeaderProps> = ({ }: HeaderProps) => { const header = useRef(null) const { screenType } = useContext(AppContext) - const { data: instanceSettings } = useInstanceSettings(client) const cozyBarHeight = 48 const headerBottomHeight = 8 diff --git a/src/components/ContainerComponents/WelcomeModalContainer/WelcomeModalContainer.tsx b/src/components/ContainerComponents/WelcomeModalContainer/WelcomeModalContainer.tsx index 9acb933822ab33313210e9efe5152d0e4adb1669..c96bd78edca5cfaec7529b1652eec5936b6df271 100644 --- a/src/components/ContainerComponents/WelcomeModalContainer/WelcomeModalContainer.tsx +++ b/src/components/ContainerComponents/WelcomeModalContainer/WelcomeModalContainer.tsx @@ -3,7 +3,7 @@ import { withClient, Client } from 'cozy-client' import Modal from 'components/CommonKit/Modal/Modal' import StyledButton from 'components/CommonKit/Button/StyledButton' import { translate } from 'cozy-ui/react/I18n' -import useInstanceSettings from 'components/Hooks/userInstanceSettings' +import userInstanceSettings from 'components/Hooks/userInstanceSettings' interface WelcomeModalContainerProps { handleClose: () => void @@ -16,7 +16,7 @@ const WelcomeModalContainer: React.FC<WelcomeModalContainerProps> = ({ t, client, }: WelcomeModalContainerProps) => { - const { data: instanceSettings } = useInstanceSettings(client) + const { data: instanceSettings } = userInstanceSettings(client) return ( <React.Fragment> diff --git a/src/components/ContentComponents/Konnector/KonnectorForm.tsx b/src/components/ContentComponents/Konnector/KonnectorForm.tsx index 9a36bc472d656e6586f03dc195583147f6204ff6..e02336b889dd2542fbf802cfea2a5e30e5a0dbe9 100644 --- a/src/components/ContentComponents/Konnector/KonnectorForm.tsx +++ b/src/components/ContentComponents/Konnector/KonnectorForm.tsx @@ -2,105 +2,49 @@ import React from 'react' import { translate } from 'cozy-ui/react/I18n' import IFluidConfig from 'services/IFluidConfig' +import { Konnector, Trigger } from 'doctypes' -import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' -import StyledButton from 'components/CommonKit/Button/StyledButton' -import TrailingIcon from 'assets/icons/ico/trailing-icon.svg' +import KonnectorLoginForm from 'components/ContentComponents/Konnector/KonnectorLoginForm' +import KonnectorOAuthForm from 'components/ContentComponents/Konnector/KonnectorOAuthForm' interface KonnectorFormProps { fluidConfig: IFluidConfig - login: string - setLogin: Function - password: string - setPassword: Function - loading: boolean - error: string - handleSubmit: Function - t: Function + konnector: Konnector + account: Account | null + trigger: Trigger | null + handleSuccessForm: Function } const KonnectorForm: React.FC<KonnectorFormProps> = ({ fluidConfig, - login, - setLogin, - password, - setPassword, - loading, - error, - handleSubmit, - t, + konnector, + account, + trigger, + handleSuccessForm, }: KonnectorFormProps) => { - const konnectorName: string = fluidConfig.konnectorConfig.name - const konnectorType: string = fluidConfig.konnectorConfig.type - const siteLink: string = fluidConfig.siteLink + const oAuth: boolean = fluidConfig.konnectorConfig.oauth - function revealPassword(idInput: string) { - const input = document.getElementById(idInput) - if (input) { - if (input.getAttribute('type') === 'password') { - input.setAttribute('type', 'text') - } else { - input.setAttribute('type', 'password') - } - } + const handleSuccess = (_account: Account, _trigger: Trigger) => { + handleSuccessForm(_account, _trigger) } return ( - <form - className="form" - onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleSubmit(e)} - > - {t('KONNECTORCONFIG.LABEL_FILLIN')} {konnectorName} - <div className="form-group"> - <div className="form-message">{error}</div> - <input - id={'idFieldLogin' + konnectorType} - type="text" - className="form-control form-input" - aria-describedby="emailHelp" - placeholder="Adresse e-mail" - name="login" - onChange={(e: React.ChangeEvent<HTMLInputElement>) => - setLogin(e.target.value) - } - value={login} - ></input> - </div> - <div className="form-group"> - <div className="form-message">{error}</div> - <input - id={'idFieldPassword' + konnectorType} - type="password" - className="form-control form-input" - aria-describedby="PasswordHelp" - placeholder="Mot de passe" - name="password" - onChange={(e: React.ChangeEvent<HTMLInputElement>) => - setPassword(e.target.value) - } - value={password} + <> + {!oAuth ? ( + <KonnectorLoginForm + fluidConfig={fluidConfig} + onSuccess={handleSuccess} + account={account} + trigger={trigger} /> - <span> - <StyledIconButton - icon={TrailingIcon} - className="form-trailing-icon" - size={22} - onClick={() => revealPassword('idFieldPassword' + konnectorType)} - /> - </span> - </div> - <StyledButton type="submit" color="primary" disabled={loading}> - {t('KONNECTORCONFIG.BTN_CONNECTION')} - </StyledButton> - <StyledButton - type="button" - color="secondary" - disabled={loading} - onClick={() => window.open(siteLink, '_blank')} - > - {t('KONNECTORCONFIG.BTN_NOACCOUNT')} - </StyledButton> - </form> + ) : ( + <KonnectorOAuthForm + konnector={konnector} + siteLink={fluidConfig.siteLink} + onSuccess={handleSuccess} + /> + )} + </> ) } diff --git a/src/components/ContentComponents/Konnector/KonnectorLaunch.tsx b/src/components/ContentComponents/Konnector/KonnectorLaunch.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8b0c24dea2d95ef6768c265a09c0e15898c8c297 --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorLaunch.tsx @@ -0,0 +1,101 @@ +import React, { useEffect, useState } from 'react' +import { withClient, Client } from 'cozy-client' +import { translate } from 'cozy-ui/react/I18n' + +import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' +import { + KonnectorJob, + ERROR_EVENT, + LOGIN_SUCCESS_EVENT, + SUCCESS_EVENT, +} from 'cozy-harvest-lib/dist/models/KonnectorJob' +import { Trigger } from 'doctypes' + +import Lottie from 'react-lottie' +import * as loadingData from 'assets/anims/bounceloading.json' +import StyledButton from 'components/CommonKit/Button/StyledButton' + +const loadingOptions = { + loop: true, + autoplay: true, + animationData: loadingData, + rendererSettings: { + preserveAspectRatio: 'xMidYMid slice', + }, +} + +interface KonnectorLaunchProps { + trigger: Trigger + handleKonnectorLaunch: Function + client: Client + t: Function +} + +const KonnectorLaunch: React.FC<KonnectorLaunchProps> = ({ + trigger, + handleKonnectorLaunch, + client, + t, +}: KonnectorLaunchProps) => { + const [state, setState] = useState<string | null>(null) + + const callbackResponse = (_state: string) => { + setState(_state) + } + + const handleClick = () => { + handleKonnectorLaunch() + } + + useEffect(() => { + let subscribed = true + async function getData() { + if (subscribed && !isKonnectorRunning(trigger)) { + const konnectorJob = new KonnectorJob(client, trigger) + await konnectorJob.launch() + konnectorJob.jobWatcher.on(ERROR_EVENT, () => { + callbackResponse(ERROR_EVENT) + }) + konnectorJob.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => { + callbackResponse(LOGIN_SUCCESS_EVENT) + }) + konnectorJob.jobWatcher.on(SUCCESS_EVENT, () => { + callbackResponse(SUCCESS_EVENT) + }) + } + } + getData() + return () => { + subscribed = false + } + }, []) + + return ( + <div className="klaunch-content"> + {!state ? ( + <> + <Lottie options={loadingOptions} height={50} width={50} /> + <div className="klaunch-content-text klaunch-content-text-center text-16-normal"> + <div>{t('KONNECTORCONFIG.PLZ_WAIT')}</div> + </div> + <div className="klaunch-content-text text-16-normal"> + <div>{t('KONNECTORCONFIG.LOADING_DATA')}</div> + </div> + </> + ) : ( + <> + <div className="klaunch-info-txt"> + {state === ERROR_EVENT + ? t('KONNECTORCONFIG.ERROR_DATA') + : t('KONNECTORCONFIG.SUCCESS')} + <StyledButton type="button" color="primary" onClick={handleClick}> + <div>{t('KONNECTORCONFIG.OK')}</div> + </StyledButton> + </div> + </> + )} + </div> + ) +} + +export default translate()(withClient(KonnectorLaunch)) diff --git a/src/components/ContentComponents/Konnector/KonnectorLoading.tsx b/src/components/ContentComponents/Konnector/KonnectorLoading.tsx deleted file mode 100644 index e502a959adf95d7626d29dad1a0c217b69925a6e..0000000000000000000000000000000000000000 --- a/src/components/ContentComponents/Konnector/KonnectorLoading.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' -import { translate } from 'cozy-ui/react/I18n' -import Lottie from 'react-lottie' - -import * as loadingData from 'assets/anims/bounceloading.json' - -const loadingOptions = { - loop: true, - autoplay: true, - animationData: loadingData, - rendererSettings: { - preserveAspectRatio: 'xMidYMid slice', - }, -} - -interface KonnectorLoadingProps { - t: Function -} - -const KonnectorLoading: React.FC<KonnectorLoadingProps> = ({ - t, -}: KonnectorLoadingProps) => { - return ( - <div className="kload-content"> - <Lottie options={loadingOptions} height={50} width={50} /> - <div className="kload-content-text kload-content-text-center text-16-normal"> - <div>{t('KONNECTORCONFIG.PLZ_WAIT')}</div> - </div> - <div className="kload-content-text text-16-normal"> - <div>{t('KONNECTORCONFIG.LOADING_DATA')}</div> - </div> - </div> - ) -} - -export default translate()(KonnectorLoading) diff --git a/src/components/ContentComponents/Konnector/KonnectorLoginForm.tsx b/src/components/ContentComponents/Konnector/KonnectorLoginForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6d8381931f37c371712fb7e0502cb203c4fd57a4 --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorLoginForm.tsx @@ -0,0 +1,170 @@ +import React, { useState, useEffect } from 'react' +import { withClient, Client } from 'cozy-client' +import { translate } from 'cozy-ui/react/I18n' + +import IFluidConfig from 'services/IFluidConfig' + +import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' +import StyledButton from 'components/CommonKit/Button/StyledButton' +import TrailingIcon from 'assets/icons/ico/trailing-icon.svg' +import { ConnectionService } from 'services/connectionService' +import { AccountService } from 'services/accountService' +import { Account, AuthLoginData, Trigger } from 'doctypes' + +interface KonnectorLoginFormProps { + fluidConfig: IFluidConfig + onSuccess: Function + account: Account + trigger: Trigger + client: Client + t: Function +} + +const KonnectorLoginForm: React.FC<KonnectorLoginFormProps> = ({ + fluidConfig, + onSuccess, + account, + trigger, + client, + t, +}: KonnectorLoginFormProps) => { + const konnectorName: string = fluidConfig.konnectorConfig.name + const konnectorType: string = fluidConfig.konnectorConfig.type + const siteLink: string = fluidConfig.siteLink + + const [login, setLogin] = useState<string>('') + const [password, setPassword] = useState<string>('') + const [error, setError] = useState<string>('') + const [loading, setLoading] = useState<boolean>(false) + + function revealPassword(idInput: string) { + const input = document.getElementById(idInput) + if (input) { + if (input.getAttribute('type') === 'password') { + input.setAttribute('type', 'text') + } else { + input.setAttribute('type', 'password') + } + } + } + + const connect = async () => { + const connectionService = new ConnectionService( + client, + fluidConfig.konnectorConfig.slug, + login, + password + ) + const { account, trigger } = await connectionService.connectNewUser() + if (!trigger) { + setError(t('KONNECTORCONFIG.ERROR_ACCOUNT_CREATION')) + setLoading(false) + return null + } + onSuccess(account, trigger) + } + + const update = async () => { + const auth = { + login: login, + password: password, + } + account.auth = auth + const updatedAccount = await AccountService.updateAccount(client, account) + onSuccess(updatedAccount, trigger) + } + + const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { + e.preventDefault() + try { + setError('') + setLoading(true) + if (!login || !password) { + setError(t('KONNECTORCONFIG.ERROR_NO_LOGIN_PASSWORD')) + setLoading(false) + return null + } + if (!account) { + await connect() + } else { + await update() + } + } catch (error) { + setLoading(false) + } + } + + useEffect(() => { + if (account) { + const auth: AuthLoginData = account.auth + if (auth.login) { + setLogin(auth.login) + } + setError(t('KONNECTORCONFIG.ERROR_LOGIN_FAILED')) + } + }, []) + + return ( + <form + className="form" + onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleSubmit(e)} + > + {t('KONNECTORCONFIG.LABEL_FILLIN')} {konnectorName} + <div className="form-group"> + <input + id={'idFieldLogin' + konnectorType} + type="text" + className="form-control form-input" + aria-describedby="emailHelp" + placeholder="Adresse e-mail" + name="login" + onChange={(e: React.ChangeEvent<HTMLInputElement>) => + setLogin(e.target.value) + } + value={login} + ></input> + </div> + <div className="form-group"> + <input + id={'idFieldPassword' + konnectorType} + type="password" + className="form-control form-input" + aria-describedby="PasswordHelp" + placeholder="Mot de passe" + name="password" + onChange={(e: React.ChangeEvent<HTMLInputElement>) => + setPassword(e.target.value) + } + value={password} + /> + <span> + <StyledIconButton + icon={TrailingIcon} + className="form-trailing-icon" + size={22} + onClick={() => revealPassword('idFieldPassword' + konnectorType)} + /> + </span> + <div className="form-message">{error}</div> + </div> + <StyledButton + className="form-button" + type="submit" + color="primary" + disabled={loading} + > + {t('KONNECTORCONFIG.BTN_CONNECTION')} + </StyledButton> + <StyledButton + type="button" + color="secondary" + disabled={loading} + onClick={() => window.open(siteLink, '_blank')} + > + {t('KONNECTORCONFIG.BTN_NOACCOUNT')} + </StyledButton> + </form> + ) +} + +export default translate()(withClient(KonnectorLoginForm)) diff --git a/src/components/ContentComponents/Konnector/KonnectorNotFound.tsx b/src/components/ContentComponents/Konnector/KonnectorNotFound.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cfe6ea899b5ee56d2abe54f461583039fd254c1c --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorNotFound.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import { translate } from 'cozy-ui/react/I18n' + +import StyledButton from 'components/CommonKit/Button/StyledButton' + +interface KonnectorNotFoundProps { + konnectorSlug: string + t: Function +} + +const KonnectorNotFound: React.FC<KonnectorNotFoundProps> = ({ + konnectorSlug, + t, +}: KonnectorNotFoundProps) => { + const openKonnectorURL = () => { + // TODO - Use getstoreinstallationurl from client - https://docs.cozy.io/en/cozy-client/api/cozy-client/#getstoreinstallationurl-string + const hostname = window.location.origin.replace('ecolyo', 'store') + const url = hostname + '/#/discover/' + konnectorSlug + window.open(url, '_blank') + } + + return ( + <div className="knotfound"> + <div className="knotfound-text"> + {' '} + {t('KONNECTORCONFIG.NOT_INSTALLED')} + </div> + <div className="knotfound-button"> + <StyledButton type="button" color="primary" onClick={openKonnectorURL}> + {t('KONNECTORCONFIG.BTN_INSTALL')} + </StyledButton> + </div> + </div> + ) +} + +export default translate()(KonnectorNotFound) diff --git a/src/components/ContentComponents/Konnector/KonnectorOAuthForm.tsx b/src/components/ContentComponents/Konnector/KonnectorOAuthForm.tsx index 19381bb90596d91d588578eb669002210338c0b9..6461ee961269b28aba5210dd6da9d8e5223d3f0d 100644 --- a/src/components/ContentComponents/Konnector/KonnectorOAuthForm.tsx +++ b/src/components/ContentComponents/Konnector/KonnectorOAuthForm.tsx @@ -1,15 +1,19 @@ import React from 'react' +import { Client, withClient } from 'cozy-client' import { translate } from 'cozy-ui/react/I18n' -import { Konnector } from 'doctypes' +import { Konnector, Trigger } from 'doctypes' import OAuthForm from 'components/ContentComponents/OAuth/OAuthForm' import StyledButton from 'components/CommonKit/Button/StyledButton' +import { AccountService } from 'services/accountService' +import { TriggerService } from 'services/triggersService' interface KonnectorOAuthFormProps { konnector: Konnector siteLink: string onSuccess: Function loading: boolean + client: Client t: Function } @@ -18,15 +22,25 @@ const KonnectorOAuthForm: React.FC<KonnectorOAuthFormProps> = ({ siteLink, onSuccess, loading, + client, t, }: KonnectorOAuthFormProps) => { + const handleSuccess = async (accountId: string) => { + const account = await AccountService.getAccount(client, accountId) + if (!account) { + onSuccess(null, null) + } + const triggersServices = new TriggerService(client, account, konnector) + const trigger: Trigger = await triggersServices.createTrigger() + onSuccess(account, trigger) + } return ( <div className="koauthform"> <div className="koauthform-text text-16-normal"> {t('oauth.connect.' + konnector.slug + '.info')} </div> <div className="koauthform-button"> - <OAuthForm konnector={konnector} onSuccess={onSuccess} /> + <OAuthForm konnector={konnector} onSuccess={handleSuccess} /> </div> <div className="koauthform-text text-16-bold"> <div className="text-16-bold"> @@ -50,4 +64,4 @@ const KonnectorOAuthForm: React.FC<KonnectorOAuthFormProps> = ({ ) } -export default translate()(KonnectorOAuthForm) +export default translate()(withClient(KonnectorOAuthForm)) diff --git a/src/components/ContentComponents/Konnector/KonnectorResult.tsx b/src/components/ContentComponents/Konnector/KonnectorResult.tsx index d6cf0f8708e150568496004999e21a9fac35de8c..c0b54a0a55371b89b2168b1ddac1bd80fc1f90c3 100644 --- a/src/components/ContentComponents/Konnector/KonnectorResult.tsx +++ b/src/components/ContentComponents/Konnector/KonnectorResult.tsx @@ -1,42 +1,135 @@ -import React from 'react' +import React, { useState, useContext, useEffect } from 'react' +import { withClient, Client } from 'cozy-client' import { translate } from 'cozy-ui/react/I18n' +import { AppContext } from 'components/Contexts/AppContextProvider' + import StyledButton from 'components/CommonKit/Button/StyledButton' import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' +import { Account, Trigger } from 'doctypes' +import { TriggerService } from 'services/triggersService' +import { AccountService } from 'services/accountService' + +import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers' +import { + KonnectorJob, + ERROR_EVENT, + LOGIN_SUCCESS_EVENT, + SUCCESS_EVENT, +} from 'cozy-harvest-lib/dist/models/KonnectorJob' + interface KonnectorResultProps { - date: string - updating: boolean - errored: boolean - updateKonnector: (event: any) => void - deleteAccount: (event: any) => void + account: Account + handleJobState: Function + client: Client t: Function } const KonnectorResult: React.FC<KonnectorResultProps> = ({ - date, - updating, - errored, - updateKonnector, - deleteAccount, + account, + handleJobState, + client, t, }: KonnectorResultProps) => { + const [trigger, setTrigger] = useState<Trigger | null>(null) + const [updating, setUpdating] = useState<boolean>(false) + const [lastExecutionDate, setLastExecutionDate] = useState<string>('-') + const [status, setStatus] = useState<string>('') + + const context = useContext(AppContext) + + const updateState = async (trigger: Trigger) => { + const triggerState = await TriggerService.fetchTriggerState(client, trigger) + if (triggerState) { + setLastExecutionDate( + new Date(triggerState.last_execution).toLocaleString() + ) + setStatus(triggerState.status) + handleJobState(triggerState.status) + await context.refreshFluidTypes() + } + } + + const callbackResponse = async () => { + if (trigger) { + await updateState(trigger) + } + setUpdating(false) + } + + const updateKonnector = async () => { + setUpdating(true) + setStatus('') + setLastExecutionDate('-') + handleJobState('') + + if (trigger && !isKonnectorRunning(trigger)) { + const konnectorJob = new KonnectorJob(client, trigger) + await konnectorJob.launch() + konnectorJob.jobWatcher.on(ERROR_EVENT, () => { + callbackResponse() + }) + konnectorJob.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => { + callbackResponse() + }) + konnectorJob.jobWatcher.on(SUCCESS_EVENT, () => { + callbackResponse() + }) + } + } + + const deleteAccount = async () => { + setUpdating(true) + try { + if (account) { + await AccountService.deleteAccount(client, account) + await context.refreshFluidTypes() + } + } catch (error) { + setUpdating(false) + } + } + + useEffect(() => { + let subscribed = true + async function getData() { + const _trigger = await TriggerService.fetchTriggerFromAccount( + client, + account + ) + if (subscribed && _trigger) { + setTrigger(_trigger) + await updateState(_trigger) + } + } + getData() + return () => { + subscribed = false + } + }, []) + return ( <div className="accordion-update-result"> <div className="accordion-update"> <div className={ - errored + status === 'errored' ? 'accordion-caption-red text-16-normal' : 'accordion-caption text-16-normal' } > {t('KONNECTORCONFIG.LABEL_UPDATEDAT')} </div> - <div>{date}</div> + <div>{lastExecutionDate}</div> </div> <div className="inline-buttons"> - <StyledButton type="button" color="primary" onClick={updateKonnector}> + <StyledButton + type="button" + color="primary" + onClick={updateKonnector} + disabled={updating} + > {updating ? ( <StyledBlackSpinner size="2em" /> ) : ( @@ -56,4 +149,4 @@ const KonnectorResult: React.FC<KonnectorResultProps> = ({ ) } -export default translate()(KonnectorResult) +export default translate()(withClient(KonnectorResult)) diff --git a/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx b/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx index 25c75055bcbecfff530f6a0b8af9e192599e245b..5f9c8ec19cfd61f79d1926303fa6fb8e3ca0e9a4 100644 --- a/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx @@ -3,7 +3,6 @@ import { withClient, Client } from 'cozy-client' import { Konnector } from 'doctypes' import KonnectorService from 'services/konnectorService' -import KonnectorStatusService from 'services/konnectorStatusService' import KonnectorViewerCard from 'components/ContentComponents/KonnectorViewer/KonnectorViewerCard' import IFluidConfig from 'services/IFluidConfig' @@ -20,7 +19,7 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ isParam = false, }: KonnectorViewerProps) => { const [konnector, setKonnector] = useState<Konnector | null>(null) - const [lastTrigger, setLastTrigger] = useState<any>(null) + const [loaded, setLoaded] = useState<boolean>(false) useEffect(() => { let subscribed = true @@ -28,35 +27,15 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ client, fluidConfig.konnectorConfig.slug ) - const kss = new KonnectorStatusService(client) - async function getData() { const _konnector: Konnector = await konnectorService.fetchKonnector() - if (!_konnector || !_konnector.slug) { - throw new Error( - `Could not find konnector for ${fluidConfig.konnectorConfig.slug}` - ) - } if (subscribed && _konnector) { setKonnector(_konnector) } - - const allTriggers = await kss.getAllTriggers() - if (allTriggers) { - const lastTrigger = allTriggers - .filter( - trigger => - trigger.worker === 'konnector' && - trigger.message.konnector === fluidConfig.konnectorConfig.slug - ) - .sort((a, b) => (new Date(a) > new Date(b) ? 1 : -1)) - .shift() - if (subscribed && lastTrigger) { - setLastTrigger(lastTrigger) - } + if (subscribed) { + setLoaded(true) } } - getData() return () => { subscribed = false @@ -65,11 +44,10 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ return ( <> - {!konnector ? null : ( + {!loaded ? null : ( <KonnectorViewerCard fluidConfig={fluidConfig} konnector={konnector} - trigger={lastTrigger} isParam={isParam} /> )} diff --git a/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx index 5a196ab0d35e78ab2f0507750c083b9d339e5dbf..8ea145a2b6b44152e929e640653828e89e62aebd 100644 --- a/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx @@ -1,11 +1,6 @@ -import React, { useState, useEffect, useRef, useContext } from 'react' -import { ConnectionService } from 'services/connectionService' -import { TriggerService } from 'services/triggersService' +import React, { useState, useEffect, useRef } from 'react' import { AccountService } from 'services/accountService' -import { AppContext } from 'components/Contexts/AppContextProvider' - -import { JobService, JobState } from 'services/jobsService' import { FluidType } from 'enum/fluid.enum' import { withClient, Client } from 'cozy-client' @@ -18,21 +13,21 @@ import chevronUp from 'assets/icons/ico/chevron-up.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' -import failurePicto from 'components/ContentComponents/KonnectorViewer/picto-failure.png' -import KonnectorStatusService from 'services/konnectorStatusService' +import failurePicto from 'assets/png/picto/picto-failure.png' import IFluidConfig from 'services/IFluidConfig' +import KonnectorNotFound from 'components/ContentComponents/Konnector/KonnectorNotFound' import KonnectorForm from 'components/ContentComponents/Konnector/KonnectorForm' -import KonnectorOAuthForm from 'components/ContentComponents/Konnector/KonnectorOAuthForm' -import KonnectorLoading from 'components/ContentComponents/Konnector/KonnectorLoading' import KonnectorResult from 'components/ContentComponents/Konnector/KonnectorResult' +import KonnectorLaunch from 'components/ContentComponents/Konnector/KonnectorLaunch' -import { Konnector, Trigger } from 'doctypes' +import { Konnector, Trigger, TriggerState } from 'doctypes' +import { JobState } from 'services/jobsService' +import { TriggerService } from 'services/triggersService' interface KonnectorViewerCardProps { fluidConfig: IFluidConfig konnector: Konnector - trigger: any | null client: Client isParam: boolean t: Function @@ -41,34 +36,32 @@ interface KonnectorViewerCardProps { const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ fluidConfig, konnector, - trigger, client, isParam, t, }: KonnectorViewerCardProps) => { - const [login, setLogin] = useState<string>('') - const [password, setPassword] = useState<string>('') - const [loading, setLoading] = useState<boolean>(false) - const [updating, setUpdating] = useState<boolean>(false) - const [konnectorAccountId, setAccountId] = useState<string>('') - const [isKonnectorAcc, setAccount] = useState<boolean>(false) + 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 [setActive, setActiveState] = useState('') const [setHeight, setHeightState] = useState('0px') - const [updateTrigger, setUpdateTrigger] = useState<any>(null) - const [updateDate, setUpdateDate] = useState<string>( - new Date('0001-01-01T00:00:00Z').toDateString() - ) - // const [frequency, setFrequency] = useState<string>('daily') - const [error, setError] = useState<string>('') + const [jobState, setJobState] = useState<string>('') - const [launchedJob, setlaunchedJob] = useState<any>(null) + + const [isLoading, setLoading] = useState<boolean>(true) + const content: React.MutableRefObject<null> = useRef<null>(null) const type: string = fluidConfig.konnectorConfig.type const fluid: FluidType = getFuildType(type) const iconType = getPicto(fluid) - const context = useContext(AppContext) const iconAddType = isParam ? getParamPicto(fluid) : getAddPicto(fluid) + const loginFailed: boolean = + triggerState != null && + triggerState.last_error != undefined && + triggerState.last_error === 'LOGIN_FAILED' const toggleAccordion = () => { setActiveState(setActive === '' ? 'active' : '') @@ -82,260 +75,126 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ ) } - const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { - try { - e.preventDefault() - setError('') - setLoading(true) - if (!login || !password) { - setError('Please enter a login and password') - setLoading(false) - return null - } - setJobState(JobState.Running) - const connectionService = new ConnectionService( - client, - fluidConfig.konnectorConfig.slug, - login, - password - ) - const data = await connectionService.connectNewUser() - setLoading(false) - if (!data) { - setError('Error during the user connection') - setLoading(false) - setJobState(JobState.Errored) - return null - } - trigger = await KonnectorStatusService.getSingleTrigger( - client, - data.trigger_id - ) - setUpdateTrigger(trigger) - setlaunchedJob(data) - setUpdateDate(new Date(data.queued_at).toDateString()) - setAccountId(data.message.account) - setError('Connected') - setAccount(true) - return data - } catch (error) { - setLoading(false) - setError(error.message) - } + const getKonnectorStateMarkup = () => { + return jobState === JobState.Errored ? ( + <img className="state-icon" src={failurePicto}></img> + ) : ( + '' + ) } - const deleteAccount = async () => { - try { - const account = await AccountService.getAccount( - client, - konnectorAccountId - ) - if (!account) { - setError('Error fetching account') - setLoading(false) - } - const delAccount = await AccountService.deleteAccount(client, account) - if (!delAccount) { - setError('Error during deletion account') - setLoading(false) - } - setError('') - setJobState('') - toggleAccordion() - trigger = null - setAccount(false) - await context.refreshFluidTypes() - } catch (error) { - setLoading(false) - setError(error.message) + const updateState = async (trigger: Trigger) => { + const triggerState = await TriggerService.fetchTriggerState(client, trigger) + if (triggerState) { + setTriggerState(triggerState) } } - const updateKonnector = async () => { - setError('') - setJobState('') - setUpdating(true) - try { - const account = await AccountService.getAccount( - client, - konnectorAccountId - ) - const triggersServices = new TriggerService(client, account, konnector) - if (updateTrigger !== null) { - triggersServices.setTrigger(updateTrigger) - } else { - triggersServices.setTrigger(trigger) - } - const job = await triggersServices.launchTrigger() - if (!job) { - throw new Error(`Error during trigger launching`) - } - setlaunchedJob(job) - setUpdateDate(new Date(job.queued_at).toDateString()) - } catch (error) { - setUpdating(false) - setError(error.message) - } + const handleSuccessForm = (_account: Account, _trigger: Trigger) => { + setAccount(_account) + setTrigger(_trigger) + setLaunch(true) } - const initOauthAccount = async (oauthAccountId: string) => { - setError('') - setJobState('') - setLoading(true) - try { - const account = await AccountService.getAccount(client, oauthAccountId) - const triggersServices = new TriggerService(client, account, konnector) - const trigger: Trigger = await triggersServices.createTrigger() - if (!trigger) { - throw new Error(`Error during trigger creation`) - } - //Launch the creation trigger - const job = await triggersServices.launchTrigger() - if (!job) { - throw new Error(`Error during trigger launching`) - } - setUpdateTrigger(trigger) - setlaunchedJob(job) - setUpdateDate(new Date(job.queued_at).toDateString()) - setAccountId(job.message.account) - setError('Connected') - setAccount(true) - } catch (error) { - setLoading(false) - setError(error.message) + const handleKonnectorLaunch = async () => { + if (trigger) { + await updateState(trigger) } + setLaunch(false) } - const getIcon = (jobState: string) => { - switch (jobState) { - case JobState.Errored: - return failurePicto - // case JobState.Running: - // return pendingPicto - // case JobState.Done: - // return successPicto - default: - return '' - } - } - - const getKonnectorStateMarkup = (jobState: string) => { - const iconSrc = getIcon(jobState) - if (iconSrc === '') return '' - return <img className="state-icon" src={iconSrc}></img> - } - - const jobStateCallBack = async (state: string) => { - setJobState(state) - if (state !== JobState.Running) { - setLoading(false) - setUpdating(false) - setJobState(state) - context.refreshFluidTypes() - } + const handleJobState = (_jobState: JobState) => { + setJobState(_jobState) } useEffect(() => { let subscribed = true - async function getTriggerData() { - if (trigger || launchedJob) { - const jobService = new JobService(client) - let runningJob = null - - if (trigger) { - let triggerState = null - triggerState = TriggerService.fetchStateFromTrigger(trigger) - const konnectorAcc = TriggerService.fetchKonnectorAccountFromTrigger( - trigger + async function getData() { + setLoading(true) + if (konnector) { + const _account = await AccountService.getAccountByType( + client, + konnector.slug + ) + if (subscribed && _account) { + setAccount(_account) + const _trigger = await TriggerService.fetchTriggerFromAccount( + client, + _account ) - if (subscribed && konnectorAcc.konnector === konnector.slug) { - setAccountId(konnectorAcc.account) - setAccount(true) + if (subscribed && _trigger) { + setTrigger(_trigger) + await updateState(_trigger) } - if (subscribed && !launchedJob) setJobState(triggerState.status) - if (triggerState.status === JobState.Running) - runningJob = jobService.fetchJob(triggerState.last_executed_job_id) } - if (launchedJob) runningJob = launchedJob - if (runningJob) jobService.watch(runningJob, jobStateCallBack) } + setLoading(false) } - getTriggerData() + getData() return () => { subscribed = false } - }, [trigger, launchedJob]) + }, []) return ( <> - <div className={`accordion ${setActive}`}> - <div> - <div - className={`accordion-header ${setActive}`} - onClick={toggleAccordion} - > - <div className="accordion-icon"> - {!isKonnectorAcc ? ( - <StyledIcon className="icon" icon={iconAddType} size={49} /> - ) : ( - <StyledIcon className="icon" icon={iconType} size={49} /> - )} - </div> - <div className="state-picto"> - {getKonnectorStateMarkup(jobState)} - </div> - <div className="accordion-info"> - <div className="accordion-title text-16-normal"> - {!isKonnectorAcc - ? t('KONNECTORCONFIG.LABEL_CONNECTTO_' + FluidType[fluid]) - : t('FLUID.' + FluidType[fluid] + '.LABEL')} + {isLoading ? null : ( + <div className={`accordion ${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> - <StyledIconButton icon={setActive ? chevronUp : chevronDown} /> - </div> - <div - ref={content} - style={{ maxHeight: `${setHeight}` }} - className={`accordion-content ${setActive}`} - > - {!isKonnectorAcc ? ( - !fluidConfig.konnectorConfig.oauth ? ( - <KonnectorForm - fluidConfig={fluidConfig} - login={login} - setLogin={setLogin} - password={password} - setPassword={setPassword} - loading={loading} - error={error} - handleSubmit={handleSubmit} + + <div + ref={content} + style={{ maxHeight: `${setHeight}` }} + className={`accordion-content ${setActive}`} + > + {!konnector ? ( + <KonnectorNotFound + konnectorSlug={fluidConfig.konnectorConfig.slug} + /> + ) : shouldLaunch && trigger ? ( + <KonnectorLaunch + trigger={trigger} + handleKonnectorLaunch={handleKonnectorLaunch} + /> + ) : account && !loginFailed ? ( + <KonnectorResult + account={account} + handleJobState={handleJobState} /> ) : ( - <KonnectorOAuthForm + <KonnectorForm + fluidConfig={fluidConfig} konnector={konnector} - siteLink={fluidConfig.siteLink} - onSuccess={initOauthAccount} - loading={loading} + account={account} + trigger={trigger} + handleSuccessForm={handleSuccessForm} /> - ) - ) : loading ? ( - <KonnectorLoading /> - ) : ( - <KonnectorResult - date={ - trigger - ? TriggerService.fetchDateFromTrigger(trigger) - : updateDate - } - updating={updating} - errored={jobState === JobState.Errored} - updateKonnector={updateKonnector} - deleteAccount={deleteAccount} - /> - )} + )} + </div> </div> </div> - </div> + )} </> ) } diff --git a/src/components/ContentComponents/OAuth/OAuthForm.tsx b/src/components/ContentComponents/OAuth/OAuthForm.tsx index fb8b91c0fde9a58722dd04abcb34d7277793ab24..b0d982ad9e90975a6ce47720f83fbf8551650ead 100644 --- a/src/components/ContentComponents/OAuth/OAuthForm.tsx +++ b/src/components/ContentComponents/OAuth/OAuthForm.tsx @@ -13,6 +13,7 @@ import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' interface OAuthFormProps { konnector: Konnector onSuccess: Function + loginFailed: boolean client: Client t: Function } diff --git a/src/components/Hooks/userInstanceSettings.tsx b/src/components/Hooks/userInstanceSettings.tsx index f261ad4a50efe8b950724c10dbc7d435da31d67d..eb05ff8b973ea275f12537a16fe28ea3c88122d1 100644 --- a/src/components/Hooks/userInstanceSettings.tsx +++ b/src/components/Hooks/userInstanceSettings.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import get from 'lodash/get' import { Client } from 'cozy-client' -const useInstanceSettings = (client: Client) => { +const userInstanceSettings = (client: Client) => { const [settings, setSettings] = useState({}) const [fetchStatus, setFetchStatus] = useState('idle') @@ -12,9 +12,6 @@ const useInstanceSettings = (client: Client) => { const response = await client .getStackClient() .fetchJSON('GET', '/settings/instance') - // const response = await client.query( - // client.all('io.cozy.settings').getById('instance') - // ) setSettings(get(response, 'data.attributes'), {}) setFetchStatus('loaded') } catch (error) { @@ -30,4 +27,4 @@ const useInstanceSettings = (client: Client) => { } } -export default useInstanceSettings +export default userInstanceSettings diff --git a/src/doctypes/io-cozy-accounts.ts b/src/doctypes/io-cozy-accounts.ts index a0fc3b34a016566b417392d7547ce50ca140a91f..67fb66e8c52a87246fcb39dbe6663e61129f1f20 100644 --- a/src/doctypes/io-cozy-accounts.ts +++ b/src/doctypes/io-cozy-accounts.ts @@ -1,12 +1,21 @@ export const ACCOUNTS_DOCTYPE = 'io.cozy.accounts' +type AuthLoginData = { + login: string + credentials_encrypted?: string + password?: string +} + +type OAuthData = { + access_token: string + refresh_token: string + scope: string | null +} + export type Account = { _id: string account_type: string - auth: { - credentials_encrypted: string - login: string - } + auth: AuthLoginData | OAuthData identifier: string state?: string | null } diff --git a/src/doctypes/io-cozy-triggers.ts b/src/doctypes/io-cozy-triggers.ts index 1366e7b53412a70a493b260d4ad9ad5fb5b1a5c6..60e696f4ac1906bd6518670c2e709f1dffac04d1 100644 --- a/src/doctypes/io-cozy-triggers.ts +++ b/src/doctypes/io-cozy-triggers.ts @@ -3,7 +3,7 @@ export const TRIGGERS_DOCTYPE = 'io.cozy.triggers' export type Trigger = { _id: string type: string - workker: string + worker: string arguments: string message: { account: string @@ -11,12 +11,24 @@ export type Trigger = { } } +export type TriggerState = { + trigger_id: string + status: string + last_error?: string + last_executed_job_id: string + last_execution: string + last_failed_job_id: string + last_failure: string + last_manual_execution: string + last_manual_job_id: string +} + export function isTrigger(trigger: any): trigger is Trigger { return ( trigger && '_id' in trigger && 'type' in trigger && - 'workker' in trigger && + 'worker' in trigger && 'arguments' in trigger && 'message' in trigger ) diff --git a/src/locales/en.json b/src/locales/en.json index f1427a73bdb6041402e4372689585d349ff5de07..0dae370fc74023a8f5963873503aab3693f1bb3a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -121,7 +121,14 @@ "BTN_UPDATE": "Mettre à jour", "BTN_DELETE": "Supprimer", "LOADING_DATA": "Vos premières données seront disponibles dans quelques minutes et les prochaines données seront chargées automatiquement.", - "PLZ_WAIT": "Veuillez patienter..." + "PLZ_WAIT": "Veuillez patienter...", + "NOT_INSTALLED": "Le connecteur n'est pas installé. Veuillez l'installer en cliquant sur le bouton ci-dessous.", + "ERROR_NO_LOGIN_PASSWORD": "Identifiant et mot de passe requis", + "ERROR_ACCOUNT_CREATION": "Une erreur est survenue, veuillez essayer de nouveau.", + "ERROR_LOGIN_FAILED": "Identifiants invalides", + "SUCCESS": "Félicitations, vos premières données sont disponibles et les prochaines seront chargées automatiquement.", + "ERROR_DATA": "Une erreur est survenue pendant le rapatriement des données.", + "OK": "Ok" }, "INDICATOR": { "DISPLAY_OTHER_FLUID": "Voir", diff --git a/src/services/accountService.ts b/src/services/accountService.ts index 3732790237cdc495153cdc32865f62d50418ced2..74ac09a72071c57115686d02c6627c998e3e36d1 100644 --- a/src/services/accountService.ts +++ b/src/services/accountService.ts @@ -98,15 +98,26 @@ export class AccountService { } } + static updateAccount = async (client: Client, account: Account) => { + try { + const updatedAccount: Account = await accountsMutations( + client + ).updateAccount(account) + return updatedAccount + } catch (error) { + throw error + } + } + static getAccountByType = async (client: Client, type: string) => { try { const query = client .find('io.cozy.accounts') // eslint-disable-next-line @typescript-eslint/camelcase .where({ account_type: type }) - // .sortBy([{ 'cozyMetadata.updatedAt': 'desc' }]) .limitBy(1) - return await client.query(query) + const result = await client.query(query) + return result.data[0] ? result.data[0] : null } catch (error) { throw error } diff --git a/src/services/connectionService.ts b/src/services/connectionService.ts index 7108fedf68945dbeea026025100b04b65be2f58c..6fb4a3044b20f441b356c810728f4ea5757f4bbc 100644 --- a/src/services/connectionService.ts +++ b/src/services/connectionService.ts @@ -48,11 +48,15 @@ export class ConnectionService { if (!trigger) { throw new Error(`Error during trigger creation`) } - //Launch the creation trigger - const job = await triggersServices.launchTrigger() - if (!job) { - throw new Error(`Error during trigger launching`) + return { + account: account, + trigger: trigger, } - return job + //Launch the creation trigger + // const job = await triggersServices.launchTrigger() + // if (!job) { + // throw new Error(`Error during trigger launching`) + // } + // return job } } diff --git a/src/services/konnectorStatusService.ts b/src/services/konnectorStatusService.ts index 0b241f9f9152794bc6bee4ea6f2a1812677bef6f..8ab8c3bde63422f10d14660e413ac12406e8f0be 100644 --- a/src/services/konnectorStatusService.ts +++ b/src/services/konnectorStatusService.ts @@ -16,6 +16,7 @@ export default class KonnectorStatusService { return 'konnector status' } + // TODO - move to triggerservices async getTriggerbyKonnector(konnector: Konnector) { const query = this._client .find('io.cozy.triggers') @@ -120,13 +121,13 @@ export default class KonnectorStatusService { ), ]) const data: FluidType[] = [] - if (elecData && elecData.data && elecData.data[0]) { + if (elecData) { data.push(fluidConfig[FluidType.ELECTRICITY].fluidTypeId) } - if (gasData && gasData.data && gasData.data[0]) { + if (gasData) { data.push(fluidConfig[FluidType.GAS].fluidTypeId) } - if (waterData && waterData.data && waterData.data[0]) { + if (waterData) { data.push(fluidConfig[FluidType.WATER].fluidTypeId) } return data diff --git a/src/services/triggersService.ts b/src/services/triggersService.ts index 790798be45796c6c72294ba2c48fca4dfc4e0c60..1356ad79fe67302a8f15e22621535ce7c0bdbc5d 100644 --- a/src/services/triggersService.ts +++ b/src/services/triggersService.ts @@ -24,7 +24,7 @@ export class TriggerService { this._trigger = { _id: '', type: '', - workker: '', + worker: '', arguments: '', message: { account: '', @@ -65,14 +65,10 @@ export class TriggerService { 'TriggersServices : createTrigger - _triggerAttributes or _client not found' ) } - try { - this._trigger = await triggersMutations(this._client).createTrigger( - this._triggerAttributes - ) - return this._trigger - } catch (error) { - throw error - } + this._trigger = await triggersMutations(this._client).createTrigger( + this._triggerAttributes + ) + return this._trigger } setTrigger = (trigger: Trigger) => { @@ -110,4 +106,25 @@ export class TriggerService { trigger.current_state.last_execution ).toLocaleString(DateTime.DATETIME_MED) } + + static async fetchTriggerFromAccount(client: Client, account: Account) { + if (account == null) return null + const query = client + .find('io.cozy.triggers') + .where({ 'message.account': account._id }) + .sortBy([{ 'cozyMetadata.updatedAt': 'desc' }]) + .limitBy(1) + const result = await client.query(query) + return result.data[0] ? result.data[0] : null + } + + static async fetchTriggerState(client: Client, trigger: Trigger) { + if (trigger == null) return null + const triggerState = await client + .getStackClient() + .fetchJSON('GET', `/jobs/triggers/${trigger._id}`) + return triggerState.data.attributes.current_state + ? triggerState.data.attributes.current_state + : null + } } diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index b783811df1eb23611db386fdd20d3a7eb792a389..09c504861eb784d6b31623b9210d03edb7ce7740 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -23,6 +23,7 @@ .form-group { display: flex; flex-direction: column; + margin: 1.5rem 0 0 0; .form-trailing-icon { float: right; position: relative; @@ -39,6 +40,10 @@ .form-message { color: $red-error; min-height: 1.25rem; + margin-top: 0.125rem; + } + .form-button { + margin-top: 0.125rem; } ::placeholder { color: $google-text-1; diff --git a/src/styles/components/_konnector.scss b/src/styles/components/_konnector.scss index f12600f8c40f89692e245945242f78f4f827865b..e5ac1303c3c4edd92c5a6daa1c9047403b84b1aa 100644 --- a/src/styles/components/_konnector.scss +++ b/src/styles/components/_konnector.scss @@ -44,6 +44,7 @@ } .state-picto { position: absolute; + display: flex; } } .accordion-update-result { @@ -111,39 +112,59 @@ } } +// KonnectorNotFound +.knotfound { + margin: 0 1.5rem; + @media #{$large-phone} { + margin: 0; + } + .knotfound-text { + color: $text-bright; + padding-top: 1rem; + } + .knotfound-button { + margin-bottom: 1rem; + } +} + // KonnectorOAuthForm -.koauthform{ +.koauthform { margin: 0 1.5rem; @media #{$large-phone} { margin: 0; } - .koauthform-text{ + .koauthform-text { color: $text-bright; padding-top: 1rem; } - .koauthform-button{ + .koauthform-button { margin-bottom: 1rem; } } // KonnectorLoading -.kload-content { +.klaunch-content { margin: 0.5rem 1.5rem; @media #{$large-phone} { margin: 0.5rem 0; } - .kload-content-text { + .klaunch-content-text { color: $text-bright; margin: 1rem 0; } - .kload-content-text-center { + .klaunch-content-text-center { text-align: center; } + .klaunch-info-txt { + margin-top: 1.5rem; + } } + .state-icon { height: 22px; width: 22px; - margin-left: 32px; - margin-bottom: 40px; + position: absolute; + bottom: 8px; + left: 30px; }