diff --git a/config.json b/config.json index 68fd0241db2ed57d426280ba1b56a374435f1e69..dba7af10a2ab8143cafeab7484aa8988e6268b9c 100644 --- a/config.json +++ b/config.json @@ -8,8 +8,9 @@ "konnectorConfig": { "name": "enedis", "type": "ELECTRICITY", - "slug": "enedis-konnector", - "id": "io.cozy.konnectors/enedis-konnector" + "oauth": true, + "slug": "enedis", + "id": "io.cozy.konnectors/enedis" } }, { @@ -20,8 +21,9 @@ "konnectorConfig": { "name": "eau du grand lyon", "type": "WATER", - "slug": "egl-konnector", - "id": "io.cozy.konnectors/egl-konnector" + "oauth": false, + "slug": "egl", + "id": "io.cozy.konnectors/egl" } }, { @@ -32,8 +34,9 @@ "konnectorConfig": { "name": "grdf", "type": "GAS", - "slug": "grdf-konnector", - "id": "io.cozy.konnectors/grdf-konnector" + "oauth": false, + "slug": "grdf", + "id": "io.cozy.konnectors/grdf" } } ] diff --git a/src/assets/icons/visu/enedis-logo.svg b/src/assets/icons/visu/enedis-logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..0e07225ab4de19103fb97e4747d61039ecd4d08a --- /dev/null +++ b/src/assets/icons/visu/enedis-logo.svg @@ -0,0 +1,4 @@ +<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M0 10C0 4.47715 4.47715 0 10 0H38C43.5229 0 48 4.47715 48 10V38C48 43.5229 43.5228 48 38 48H0V10Z" fill="black"/> +<path d="M0 21V28H9V30C9 36.6274 14.3726 42 21 42H47.1679C47.7031 40.775 48 39.4222 48 38V35H21C18.2386 35 16 32.7615 16 30V28H33C36.3137 28 39 25.3137 39 22V12C39 8.68628 36.3137 6 33 6H15C11.6863 6 9 8.68628 9 12V19H16V16C16 14.3431 17.3431 13 19 13H29C30.6569 13 32 14.3431 32 16V18C32 19.6569 30.6569 21 29 21H0Z" fill="#F1C017"/> +</svg> diff --git a/src/components/CommonKit/Button/StyledOauthButton.tsx b/src/components/CommonKit/Button/StyledOauthButton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9e3e6ebec877d7a900ecaf5aa700404893b6f067 --- /dev/null +++ b/src/components/CommonKit/Button/StyledOauthButton.tsx @@ -0,0 +1,88 @@ +import React from 'react' +import MuiButton, { ButtonProps } from '@material-ui/core/Button' +import { + withStyles, + MuiThemeProvider, + createMuiTheme, +} from '@material-ui/core/styles' + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, + shape: { + borderRadius: 2, + }, + palette: { + primary: { + main: '#E3B82A', + }, + secondary: { + main: '#7B7B7B', + }, + }, +}) + +const BaseButton = withStyles({ + root: { + height: '5rem', + margin: '1.5rem 0 0 0', + padding: '0px 1px', + }, + label: { + textTransform: 'none', + fontFamily: 'Lato, sans-serif', + fontStyle: 'normal', + fontSize: '1rem', + lineHeight: '120%', + }, +})(MuiButton) + +const PrimaryButton = withStyles({ + root: { + background: 'var(--multiColorRadialGradient)', + }, + label: { + color: '#000000', + fontWeight: 'bold', + }, +})(BaseButton) + +const SecondaryButton = withStyles({ + root: { + border: '1px solid #121212', + }, + label: { + color: '#E0E0E0', + fontWeight: 'normal', + }, +})(BaseButton) + +function MyButton(props: ButtonProps) { + return props.color === 'secondary' ? ( + <SecondaryButton + fullWidth + color="secondary" + variant="outlined" + {...props} + /> + ) : ( + <PrimaryButton fullWidth color="primary" variant="contained" {...props} /> + ) +} + +const StyledOauthButton: React.ComponentType<ButtonProps> = props => { + return ( + <> + <MuiThemeProvider theme={theme}> + <MyButton {...props}>{props.children}</MyButton> + </MuiThemeProvider> + </> + ) +} + +StyledOauthButton.defaultProps = { + color: 'primary', +} + +export default StyledOauthButton diff --git a/src/components/CommonKit/Spinner/StyledBlackSpinner.tsx b/src/components/CommonKit/Spinner/StyledBlackSpinner.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3895685d0115b60dad99ffcc680ebf2a9449b38c --- /dev/null +++ b/src/components/CommonKit/Spinner/StyledBlackSpinner.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import MuiCircularProgress, { + CircularProgressProps, +} from '@material-ui/core/CircularProgress' + +const SpinnerBase = withStyles({ + root: { + color: 'var(--black)', + }, +})(MuiCircularProgress) + +type StyledBlackSpinnerProps = CircularProgressProps + +const StyledBlackSpinner: React.ComponentType<StyledBlackSpinnerProps> = ({ + ...props +}: StyledBlackSpinnerProps) => { + return <SpinnerBase {...props} /> +} + +export default StyledBlackSpinner diff --git a/src/components/ContentComponents/Konnector/KonnectorForm.tsx b/src/components/ContentComponents/Konnector/KonnectorForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ac3455632e6aecd4f5035c6c33802d00a4f6dcf1 --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorForm.tsx @@ -0,0 +1,101 @@ +import React from 'react' +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' + +interface KonnectorFormProps { + fluidConfig: IFluidConfig + login: string + setLogin: Function + password: string + setPassword: Function + loading: boolean + error: string + handleSubmit: Function + t: Function +} + +const KonnectorForm: React.FC<KonnectorFormProps> = ({ + fluidConfig, + login, + setLogin, + password, + setPassword, + loading, + error, + handleSubmit, + t, +}: KonnectorFormProps) => { + const konnectorName: string = fluidConfig.konnectorConfig.name + const konnectorType: string = fluidConfig.konnectorConfig.type + + 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') + } + } + } + + 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} + /> + <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}> + {t('KONNECTORCONFIG.BTN_NOACCOUNT')} + </StyledButton> + </form> + ) +} + +export default translate()(KonnectorForm) diff --git a/src/components/ContentComponents/Konnector/KonnectorLoading.tsx b/src/components/ContentComponents/Konnector/KonnectorLoading.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4df37acf2141d6a2d1d9fcbead68cf9b811d6eb9 --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorLoading.tsx @@ -0,0 +1,36 @@ +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 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/KonnectorOAuthForm.tsx b/src/components/ContentComponents/Konnector/KonnectorOAuthForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..598d7a0a559fd3f9d43501dbbe39bb4f2a2ad7dc --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorOAuthForm.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { translate } from 'cozy-ui/react/I18n' + +import { Konnector } from 'doctypes' +import OAuthForm from 'components/ContentComponents/OAuth/OAuthForm' +import StyledButton from 'components/CommonKit/Button/StyledButton' + +interface KonnectorOAuthFormProps { + konnector: Konnector + onSuccess: Function + loading: boolean + t: Function +} + +const KonnectorOAuthForm: React.FC<KonnectorOAuthFormProps> = ({ + konnector, + onSuccess, + loading, + t, +}: KonnectorOAuthFormProps) => { + 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} /> + </div> + <div className="koauthform-text text-16-bold"> + <div className="text-16-bold"> + {t('oauth.no_account.' + konnector.slug + '.title')} + </div> + <div className="text-16-normal"> + {t('oauth.no_account.' + konnector.slug + '.text')} + </div> + </div> + <div className="koauthform-button"> + <StyledButton type="button" color="secondary" disabled={loading}> + {t('oauth.create_account.' + konnector.slug)} + </StyledButton> + </div> + </div> + ) +} + +export default translate()(KonnectorOAuthForm) diff --git a/src/components/ContentComponents/Konnector/KonnectorResult.tsx b/src/components/ContentComponents/Konnector/KonnectorResult.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ffccef5334009ccc69c87e052f15f58932c5926d --- /dev/null +++ b/src/components/ContentComponents/Konnector/KonnectorResult.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import { translate } from 'cozy-ui/react/I18n' + +import StyledButton from 'components/CommonKit/Button/StyledButton' +import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' + +interface KonnectorResultProps { + date: string + updating: boolean + errored: boolean + updateKonnector: (event: any) => void + deleteAccount: (event: any) => void + t: Function +} + +const KonnectorResult: React.FC<KonnectorResultProps> = ({ + date, + updating, + errored, + updateKonnector, + deleteAccount, + t, +}: KonnectorResultProps) => { + return ( + <div> + <div className="accordion-update"> + <div + className={ + errored + ? 'accordion-caption-red text-16-normal' + : 'accordion-caption text-16-normal' + } + > + {t('KONNECTORCONFIG.LABEL_UPDATEDAT')} + </div> + <div>{date}</div> + </div> + <div className="inline-buttons"> + <StyledButton type="button" color="primary" onClick={updateKonnector}> + {updating ? ( + <StyledBlackSpinner size="2em" /> + ) : ( + <div>{t('KONNECTORCONFIG.BTN_UPDATE')}</div> + )} + </StyledButton> + <StyledButton + type="button" + color="secondary" + onClick={deleteAccount} + disabled={updating} + > + {t('KONNECTORCONFIG.BTN_DELETE')} + </StyledButton> + </div> + </div> + ) +} + +export default translate()(KonnectorResult) diff --git a/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx b/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx index 1d6089592de2141a4bc9013e14d77877d114e35b..25c75055bcbecfff530f6a0b8af9e192599e245b 100644 --- a/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewer.tsx @@ -1,6 +1,10 @@ import React, { useState, useEffect } from 'react' 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' @@ -15,55 +19,61 @@ const KonnectorViewer: React.FC<KonnectorViewerProps> = ({ client, isParam = false, }: KonnectorViewerProps) => { - const [Konnectors, setKonnectors] = useState(null) - const [Triggers, setTriggers] = useState(null) + const [konnector, setKonnector] = useState<Konnector | null>(null) + const [lastTrigger, setLastTrigger] = useState<any>(null) - const setAllKonnectors = async () => { + useEffect(() => { + let subscribed = true + const konnectorService = new KonnectorService( + client, + fluidConfig.konnectorConfig.slug + ) const kss = new KonnectorStatusService(client) - const allKonnectors = await kss.getAllKonnectors() - setKonnectors(allKonnectors) - //console.log('All Konnectors',allKonnectors); - } - const setAllTriggers = async () => { - const kss = new KonnectorStatusService(client) - const allTriggers = await kss.getAllTriggers() - setTriggers(allTriggers) - //console.log('All Triggers',allTriggers) - } + 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 fetchKonnectorFromTrigger = trigger => { - let konnectorName = '' - try { - konnectorName = trigger.message.konnector - } catch (error) { - return konnectorName + 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) + } + } } - return konnectorName - } - const getLastTrigger = konnector => { - if (!Triggers) return null - const lastTrigger = Triggers.filter( - trigger => - fetchKonnectorFromTrigger(trigger) === konnector.id.split('/').pop() - ) - .sort((a, b) => (new Date(a) > new Date(b) ? 1 : -1)) - .shift() - return lastTrigger - } - - useEffect(() => { - setAllKonnectors() - setAllTriggers() + getData() + return () => { + subscribed = false + } }, []) return ( - <KonnectorViewerCard - fluidConfig={fluidConfig} - trigger={getLastTrigger(fluidConfig.konnectorConfig)} - isParam={isParam} - /> + <> + {!konnector ? 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 98d6a138ce14b7f782d6e030079790733a8820d9..707c4c27ee7a936fcff69781cb949238fed64f76 100644 --- a/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx +++ b/src/components/ContentComponents/KonnectorViewer/KonnectorViewerCard.tsx @@ -14,41 +14,33 @@ import { translate } from 'cozy-ui/react/I18n' import { getPicto, getAddPicto, getFuildType, getParamPicto } from 'utils/utils' import chevronDown from 'assets/icons/ico/chevron-down.svg' import chevronUp from 'assets/icons/ico/chevron-up.svg' -import TrailingIcon from 'assets/icons/ico/trailing-icon.svg' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton' -import StyledButton from 'components/CommonKit/Button/StyledButton' -import failurePicto from '../KonnectorViewer/picto-failure.png' -import successPicto from '../KonnectorViewer/picto-success.png' -import pendingPicto from '../KonnectorViewer/picto-pending.png' -import KonnectorService from 'services/konnectorService' +import failurePicto from 'components/ContentComponents/KonnectorViewer/picto-failure.png' import KonnectorStatusService from 'services/konnectorStatusService' -import Spinner from 'components/CommonKit/Spinner/Spinner' import IFluidConfig from 'services/IFluidConfig' +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 { Konnector, Trigger } from 'doctypes' + interface KonnectorViewerCardProps { fluidConfig: IFluidConfig + konnector: Konnector trigger: any | null client: Client isParam: boolean t: Function } -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 KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ fluidConfig, + konnector, trigger, client, isParam, @@ -57,6 +49,7 @@ const KonnectorViewerCard: React.FC<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 [setActive, setActiveState] = useState('') @@ -102,7 +95,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ setJobState(JobState.Running) const connectionService = new ConnectionService( client, - fluidConfig.konnectorConfig.id, + fluidConfig.konnectorConfig.slug, login, password ) @@ -124,7 +117,6 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ setAccountId(data.message.account) setError('Connected') setAccount(true) - await context.refreshFluidTypes() return data } catch (error) { setLoading(false) @@ -152,7 +144,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ toggleAccordion() trigger = null setAccount(false) - context.refreshFluidTypes() + await context.refreshFluidTypes() } catch (error) { setLoading(false) setError(error.message) @@ -162,17 +154,12 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ const updateKonnector = async () => { setError('') setJobState('') - setLoading(true) + setUpdating(true) try { const account = await AccountService.getAccount( client, konnectorAccountId ) - const konnectorService = new KonnectorService( - client, - fluidConfig.konnectorConfig.id - ) - const konnector = await konnectorService.fetchKonnector() const triggersServices = new TriggerService(client, account, konnector) if (updateTrigger !== null) { triggersServices.setTrigger(updateTrigger) @@ -184,8 +171,35 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ throw new Error(`Error during trigger launching`) } setlaunchedJob(job) - setLoading(false) setUpdateDate(new Date(job.queued_at).toDateString()) + } catch (error) { + setUpdating(false) + setError(error.message) + } + } + + 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) @@ -211,15 +225,14 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ return <img className="state-icon" src={iconSrc}></img> } - // TODO: To be removed. This code slows the job state rendering so It can be visible - const setJobStateAfterTimer = (state: string) => { - setTimeout(() => { - setJobState(state) - }, 1000) - } - const jobStateCallBack = async (state: string) => { setJobState(state) + if (state !== JobState.Running) { + setLoading(false) + setUpdating(false) + setJobState(state) + context.refreshFluidTypes() + } } useEffect(() => { @@ -230,13 +243,12 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ let runningJob = null if (trigger) { - let triggerState = '' + let triggerState = null triggerState = TriggerService.fetchStateFromTrigger(trigger) const konnectorAcc = TriggerService.fetchKonnectorAccountFromTrigger( trigger ) - const konnectorId = fluidConfig.konnectorConfig.id.split('/') - if (subscribed && konnectorAcc.konnector === konnectorId[1]) { + if (subscribed && konnectorAcc.konnector === konnector.slug) { setAccountId(konnectorAcc.account) setAccount(true) } @@ -245,7 +257,7 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ runningJob = jobService.fetchJob(triggerState.last_executed_job_id) } if (launchedJob) runningJob = launchedJob - if (runningJob) jobService.watch(runningJob, setJobStateAfterTimer) + if (runningJob) jobService.watch(runningJob, jobStateCallBack) } } getTriggerData() @@ -287,105 +299,38 @@ const KonnectorViewerCard: React.FC<KonnectorViewerCardProps> = ({ className={`accordion-content ${setActive}`} > {!isKonnectorAcc ? ( - <form - className="form" - onSubmit={(e: React.FormEvent<HTMLFormElement>) => - handleSubmit(e) - } - > - {t('KONNECTORCONFIG.LABEL_FILLIN')}{' '} - {fluidConfig.konnectorConfig.name} - <div className="form-group"> - <div className="form-message">{error}</div> - <input - id={'idFieldLogin' + type} - 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' + type} - 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' + type)} - /> - </span> - </div> - <StyledButton type="submit" color="primary" disabled={loading}> - {t('KONNECTORCONFIG.BTN_CONNECTION')} - </StyledButton> - <StyledButton - type="button" - color="secondary" - disabled={loading} - > - {t('KONNECTORCONFIG.BTN_NOACCOUNT')} - </StyledButton> - </form> + !fluidConfig.konnectorConfig.oauth ? ( + <KonnectorForm + fluidConfig={fluidConfig} + login={login} + setLogin={setLogin} + password={password} + setPassword={setPassword} + loading={loading} + error={error} + handleSubmit={handleSubmit} + /> + ) : ( + <KonnectorOAuthForm + konnector={konnector} + onSuccess={initOauthAccount} + loading={loading} + /> + ) + ) : loading ? ( + <KonnectorLoading /> ) : ( - <div> - <div> - <div className="accordion-update"> - <div - className={ - JobState.Errored - ? 'accordion-caption-red' - : 'accordion-caption' - } - > - {t('KONNECTORCONFIG.LABEL_UPDATEDAT')} - </div> - {trigger ? ( - <div>{TriggerService.fetchDateFromTrigger(trigger)}</div> - ) : ( - <div>{updateDate}</div> - )} - </div> - <div className="inline-buttons"> - <StyledButton - type="button" - color="primary" - onClick={updateKonnector} - > - {loading ? ( - <Spinner size="2em" /> - ) : ( - <div>{t('KONNECTORCONFIG.BTN_UPDATE')}</div> - )} - </StyledButton> - <StyledButton - type="button" - color="secondary" - onClick={deleteAccount} - disabled={loading} - > - {t('KONNECTORCONFIG.BTN_DELETE')} - </StyledButton> - </div> - </div> - </div> + <KonnectorResult + date={ + trigger + ? TriggerService.fetchDateFromTrigger(trigger) + : updateDate + } + updating={updating} + errored={jobState === JobState.Errored} + updateKonnector={updateKonnector} + deleteAccount={deleteAccount} + /> )} </div> </div> diff --git a/src/components/ContentComponents/KonnectorViewer/picto-pending.png b/src/components/ContentComponents/KonnectorViewer/picto-pending.png deleted file mode 100644 index ed0000cc26ba09494a399dc48b20d02e1710fcbe..0000000000000000000000000000000000000000 Binary files a/src/components/ContentComponents/KonnectorViewer/picto-pending.png and /dev/null differ diff --git a/src/components/ContentComponents/KonnectorViewer/picto-success.png b/src/components/ContentComponents/KonnectorViewer/picto-success.png deleted file mode 100644 index 7cc8e017b34afc4e706f1a8153483f040e75da0f..0000000000000000000000000000000000000000 Binary files a/src/components/ContentComponents/KonnectorViewer/picto-success.png and /dev/null differ diff --git a/src/components/ContentComponents/OAuth/OAuthForm.tsx b/src/components/ContentComponents/OAuth/OAuthForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fb8b91c0fde9a58722dd04abcb34d7277793ab24 --- /dev/null +++ b/src/components/ContentComponents/OAuth/OAuthForm.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react' +import { Client, withClient } from 'cozy-client' +import { translate } from 'cozy-ui/react/I18n' + +import { Konnector } from 'doctypes' +import { OAuthWindow } from 'cozy-harvest-lib/dist/components/OAuthWindow' + +import iconEnedisLogo from 'assets/icons/visu/enedis-logo.svg' +import StyledOauthButton from 'components/CommonKit/Button/StyledOauthButton' +import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import StyledBlackSpinner from 'components/CommonKit/Spinner/StyledBlackSpinner' + +interface OAuthFormProps { + konnector: Konnector + onSuccess: Function + client: Client + t: Function +} + +const OAuthForm: React.FC<OAuthFormProps> = ({ + konnector, + onSuccess, + client, + t, +}: OAuthFormProps) => { + const IDLE = 'idle' + const WAITING = 'waiting' + + const [status, setStatus] = useState<string>(IDLE) + + function endOAuth() { + setStatus(IDLE) + } + function startOAuth() { + setStatus(WAITING) + } + function handleAccountId(accountId: string) { + endOAuth() + onSuccess(accountId) + } + function handleSubmit() { + startOAuth() + } + function handleOAuthCancel() { + endOAuth() + } + + const isWaiting = status === WAITING + return !konnector ? null : ( + <> + <StyledOauthButton + type="button" + color="primary" + onClick={handleSubmit} + disabled={isWaiting} + > + <div className="oauthform-button-content"> + <div className="oauthform-button-content-icon"> + {isWaiting ? ( + <StyledBlackSpinner size={48} /> + ) : ( + <StyledIcon icon={iconEnedisLogo} size={48} /> + )} + </div> + <div className="oauthform-button-text text-18-bold"> + <div>{t('oauth.connect.' + konnector.slug + '.label1')}</div> + <div>{t('oauth.connect.' + konnector.slug + '.label2')}</div> + </div> + </div> + </StyledOauthButton> + {isWaiting && ( + <OAuthWindow + client={client} + konnector={konnector} + onSuccess={handleAccountId} + onCancel={handleOAuthCancel} + t={t} + /> + )} + </> + ) +} + +export default translate()(withClient(OAuthForm)) diff --git a/src/locales/en.json b/src/locales/en.json index eef1f870d1cf69daabef174c011661872d67c4e5..85fbb46a6ec07c9d11717813ec1fd8332709014b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -36,7 +36,7 @@ "APP_ONGOING_CHALLENGE_TITLE": "Défi en cours", "APP_FINISHED_CHALLENGE_TITLE": "Défi terminé" }, - "LOADING":{ + "LOADING": { "INDEX": "Vérification des données", "DATA": "Initialisation des données", "FLUIDTYPES": "Récupération de votre configuration", @@ -115,7 +115,9 @@ "BTN_INSTALL": "Installer", "BTN_CONFIGURE": "Configurer", "BTN_UPDATE": "Mettre à jour", - "BTN_DELETE": "Supprimer" + "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..." }, "INDICATOR": { "DISPLAY_OTHER_FLUID": "Voir", @@ -137,7 +139,7 @@ "START": "Allons-y !", "NOT_NOW": "Pas maintenant !", "STOP": "Arrêter le défi", - "BACK":"I'll be back", + "BACK": "I'll be back", "ECOGESTURE": "Voir l'écogeste", "LINKED_ECOGESTURES": "Écogestes associés", "VIEW_START": "Visualisation à partir du ", @@ -161,13 +163,34 @@ "NO_ECOGESTURE": "Pas d'ecogeste" }, "NEGAWATT": { - "TITLE_NEGAWATT" : "NégaWatt", - "QUESTION" : "Que sont les nWh ? (néga Watt heure)", - "ANSWER" : { - "BASE" : "Le néga Watt heure (ou nWh) sert d'unité de mesure de vos économies d'énergie.", - "STRONG" : "10 nWh : économie importante", - "MEDIUM" : "3 nWh : économie moyenne", - "WEAK" : "1 nWh : économie faible" + "TITLE_NEGAWATT": "NégaWatt", + "QUESTION": "Que sont les nWh ? (néga Watt heure)", + "ANSWER": { + "BASE": "Le néga Watt heure (ou nWh) sert d'unité de mesure de vos économies d'énergie.", + "STRONG": "10 nWh : économie importante", + "MEDIUM": "3 nWh : économie moyenne", + "WEAK": "1 nWh : économie faible" + } + }, + "oauth": { + "connect": { + "enedis": { + "info" : "En cliquant sur ce bouton, vous allez accéder à votre compte personnel Enedis où vous pourrez donner votre accord pour qu’Enedis nous transmette vos données.", + "label1": "J'accède à mon", + "label2": "espace client Enedis" + } + }, + "no_account" : { + "enedis" : { + "title": "Pas de compte Enedis ?", + "text": "Vous pouvez le créer en vous munissant d'une facture d'élétricité." + } + }, + "create_account": { + "enedis": "Je créé mon compte personnal Enedis" + }, + "window": { + "title": "OAuth" } } } diff --git a/src/services/IFluidConfig.ts b/src/services/IFluidConfig.ts index 6d8d08f784ed7f68ff34bd53854139333f90725a..473047d7441a35d61852ed91e542e9f6864d16c6 100644 --- a/src/services/IFluidConfig.ts +++ b/src/services/IFluidConfig.ts @@ -1,6 +1,7 @@ interface KonnectorConfig { name: string type: string + oauth: boolean slug: string id: string } diff --git a/src/services/accountService.ts b/src/services/accountService.ts index 569108698e0b46f6173899184325316a47d6708d..8dad62c3d5c803113603908f8acc787aa6409b23 100644 --- a/src/services/accountService.ts +++ b/src/services/accountService.ts @@ -104,7 +104,7 @@ export class AccountService { .find('io.cozy.accounts') // eslint-disable-next-line @typescript-eslint/camelcase .where({ account_type: type }) - .sortBy([{ 'cozyMetadata.updatedAt': 'desc' }]) + // .sortBy([{ 'cozyMetadata.updatedAt': 'desc' }]) .limitBy(1) return await client.query(query) } catch (error) { diff --git a/src/services/konnectorService.ts b/src/services/konnectorService.ts index 4f956e5fd05e1767a71e040b49733435a79fafdb..b2521df2e609aac2e7a7176becb204ced0021d12 100644 --- a/src/services/konnectorService.ts +++ b/src/services/konnectorService.ts @@ -23,7 +23,7 @@ export default class KonnectorService { try { const { data } = await this._client.query( this._client.find(KONNECTORS_DOCTYPE).where({ - _id: this._konnectorId, + _id: KONNECTORS_DOCTYPE + '/' + this._konnectorId, }) ) const konnector: Konnector = data && data[0] diff --git a/src/styles/components/_konnector.scss b/src/styles/components/_konnector.scss index efd3c1e77da3e75aa4bd79929dc2968433cce686..db808a98592bcdab12223f138552ab32b2635b5c 100644 --- a/src/styles/components/_konnector.scss +++ b/src/styles/components/_konnector.scss @@ -59,7 +59,6 @@ padding: 0 1rem; } .accordion-caption { - font-size: 0.8rem; color: $google-text-2; text-transform: lowercase; &::first-letter { @@ -68,6 +67,10 @@ } .accordion-caption-red { color: $red-error; + text-transform: lowercase; + &::first-letter { + text-transform: uppercase; + } } .accordion-content { overflow: hidden; @@ -102,6 +105,27 @@ } } +// KonnectorOAuthForm +.koauthform{ + .koauthform-text{ + color: $text-bright; + padding-top: 1rem; + } + .koauthform-button{ + margin-bottom: 1rem; + } +} + +// KonnectorLoading +.kload-content { + margin: 0.5rem 0; + .kload-content-text { + text-align: center; + color: $text-bright; + margin: 1rem 0; + } +} + .state-icon { height: 22px; width: 22px; diff --git a/src/styles/components/_oauth.scss b/src/styles/components/_oauth.scss new file mode 100644 index 0000000000000000000000000000000000000000..7e305641fd84873ee25f6c141bdeb0777d38b093 --- /dev/null +++ b/src/styles/components/_oauth.scss @@ -0,0 +1,16 @@ +@import '../base/color'; +@import '../base/breakpoint'; + +.oauthform-button-content{ + display: flex; + justify-content: center; + align-items: center; + .oauthform-button-content-icon{ + margin: 0 1.375rem; + } + .oauthform-button-text{ + display: flex; + flex-direction: column; + align-items: flex-start; + } +} \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css index 2a9a12baeb23acd28be0eab338ccdd2841fed2e7..92ba7db5ccc39fd66aeb06b02fc64b1edb25d582 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -42,11 +42,11 @@ body { /* line 30, src/styles/base/_layout.scss */ [role='banner'] .coz-bar-container { - padding: 0 3.25em 0 0; background-color: white; } @media only screen and (max-width: 768px) { /* line 30, src/styles/base/_layout.scss */ [role='banner'] .coz-bar-container { + padding: 0 3.25em 0 0; background-color: unset; } } /* line 38, src/styles/base/_layout.scss */ @@ -905,8 +905,10 @@ p { /* line 105, src/styles/components/_konnector.scss */ .state-icon { - height: 20px; - width: 20px; } + height: 22px; + width: 22px; + margin-left: 32px; + margin-bottom: 40px; } /** BLACK **/ /** TEXT COLOR **/ @@ -1968,51 +1970,67 @@ p { align-items: center; flex-direction: column; color: #e0e0e0; - height: 84vh; + min-height: 84vh; justify-content: space-between; } /* line 374, src/styles/components/_challenges.scss */ .cp-root .--locked.cp-content { justify-content: center; } - /* line 378, src/styles/components/_challenges.scss */ - .cp-root .cp-content .cp-info { + /* line 379, src/styles/components/_challenges.scss */ + .cp-root .cp-content .cp-info, .cp-root .cp-content .cp-info.--available { display: flex; flex-direction: column; align-items: center; justify-content: space-between; background-color: #121212; width: 100%; - height: 60%; - padding-top: 2rem; + padding-top: 1rem; padding-bottom: 0.5rem; } /* line 388, src/styles/components/_challenges.scss */ + .cp-root .cp-content .--available.cp-info { + height: 74vh; } + /* line 392, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-icon { margin-bottom: 1rem; } - /* line 391, src/styles/components/_challenges.scss */ + /* line 395, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-icon-available { margin: 2rem; } - /* line 394, src/styles/components/_challenges.scss */ + /* line 398, src/styles/components/_challenges.scss */ + .cp-root .cp-content .cp-info .cp-win-badge-star { + display: grid; + align-items: center; + justify-items: center; } + /* line 402, src/styles/components/_challenges.scss */ + .cp-root .cp-content .cp-info .cp-win-badge-star .cp-win-badge { + grid-column: 1; + grid-row: 1; + z-index: 1; } + /* line 407, src/styles/components/_challenges.scss */ + .cp-root .cp-content .cp-info .cp-win-badge-star .cp-win-star { + grid-column: 1; + grid-row: 1; } + /* line 412, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-date { color: #a0a0a0; text-align: center; margin-top: 1rem; } - /* line 399, src/styles/components/_challenges.scss */ + /* line 417, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-title { color: #e0e0e0; text-align: center; margin-top: 0.5rem; } - /* line 404, src/styles/components/_challenges.scss */ + /* line 422, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-result { color: #e0e0e0; text-align: center; margin-top: 1.5rem; margin-bottom: 0.5rem; } - /* line 409, src/styles/components/_challenges.scss */ + /* line 427, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-result .cp-result-positif { color: #7fd771; } - /* line 412, src/styles/components/_challenges.scss */ + /* line 430, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-result .cp-result-negatif { color: #d24444; } - /* line 416, src/styles/components/_challenges.scss */ + /* line 434, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-description { text-align: center; margin-top: 1rem; @@ -2020,7 +2038,7 @@ p { margin-left: 1.25rem; margin-right: 1.25rem; max-width: 53rem; } - /* line 424, src/styles/components/_challenges.scss */ + /* line 442, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-valid { justify-content: center; display: flex; @@ -2028,17 +2046,17 @@ p { width: 90%; margin-top: 0.75rem; max-width: 53rem; } - /* line 431, src/styles/components/_challenges.scss */ + /* line 449, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-valid .cp-left-button { margin-right: 0.25rem; margin-left: 0; width: 100%; } - /* line 436, src/styles/components/_challenges.scss */ + /* line 454, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-info .cp-valid .cp-right-button { margin-left: 0.25rem; margin-right: 0; width: 100%; } - /* line 443, src/styles/components/_challenges.scss */ + /* line 461, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-follow { width: 90%; display: flex; @@ -2046,12 +2064,12 @@ p { align-items: center; margin-bottom: 1rem; max-width: 53rem; } - /* line 451, src/styles/components/_challenges.scss */ + /* line 469, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-valid-locked { margin-top: 2rem; width: 80%; max-width: 53rem; } - /* line 456, src/styles/components/_challenges.scss */ + /* line 474, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-bottom { padding: 1.25rem 1.25rem; width: 90%; @@ -2061,16 +2079,16 @@ p { max-width: 53rem; margin-bottom: 2rem; } @media only screen and (max-width: 768px) { - /* line 456, src/styles/components/_challenges.scss */ + /* line 474, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-bottom { margin-bottom: 0; } } - /* line 467, src/styles/components/_challenges.scss */ + /* line 485, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-bottom .cp-eg-content { width: 100%; } - /* line 469, src/styles/components/_challenges.scss */ + /* line 487, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-bottom .cp-eg-content .linked-ecogestures { text-transform: uppercase; } - /* line 472, src/styles/components/_challenges.scss */ + /* line 490, src/styles/components/_challenges.scss */ .cp-root .cp-content .cp-bottom .cp-eg-content .cp-ecogestures { width: 100%; display: flex; @@ -2094,7 +2112,7 @@ p { flex-direction: column; align-items: center; justify-content: center; - padding: 1rem 1.5rem; } + padding: 1rem 1.5rem 2.5rem 1.5rem; } /* line 11, src/styles/components/_ecogesture.scss */ .ecogesture-root .negawatt-button-content { width: calc(53rem - 2%); @@ -2176,44 +2194,43 @@ p { .em-header { color: #e0e0e0; border-bottom: 1px solid rgba(163, 163, 163, 0.4); - margin-bottom: 1rem; padding-bottom: 1em; width: 100%; display: flex; justify-content: center; } -/* line 94, src/styles/components/_ecogesture.scss */ +/* line 93, src/styles/components/_ecogesture.scss */ .em-icon { margin-bottom: 1rem; } -/* line 97, src/styles/components/_ecogesture.scss */ +/* line 96, src/styles/components/_ecogesture.scss */ .em-title { margin-bottom: 0; } -/* line 100, src/styles/components/_ecogesture.scss */ +/* line 99, src/styles/components/_ecogesture.scss */ .em-detail { display: flex; flex-direction: row; margin: 0.5em 0; } - /* line 104, src/styles/components/_ecogesture.scss */ + /* line 103, src/styles/components/_ecogesture.scss */ .em-detail .em-detail-nwh { display: flex; flex: 1; align-self: flex-start; margin-top: 0.65rem; color: var(--textDark); } - /* line 110, src/styles/components/_ecogesture.scss */ + /* line 109, src/styles/components/_ecogesture.scss */ .em-detail .em-detail-nwh .em-detail-nwh-unit { margin-left: 0.2rem; } - /* line 114, src/styles/components/_ecogesture.scss */ + /* line 113, src/styles/components/_ecogesture.scss */ .em-detail .em-picto-flow { display: flex; align-self: flex-end; } - /* line 117, src/styles/components/_ecogesture.scss */ + /* line 116, src/styles/components/_ecogesture.scss */ .em-detail .em-picto-flow .em-pic-content { margin: 0.3em; } -/* line 123, src/styles/components/_ecogesture.scss */ +/* line 122, src/styles/components/_ecogesture.scss */ .em-content-box { max-height: 25rem; overflow: auto; @@ -2223,25 +2240,28 @@ p { /* width */ /* Track */ /* Handle */ } - /* line 130, src/styles/components/_ecogesture.scss */ + /* line 129, src/styles/components/_ecogesture.scss */ .em-content-box::-webkit-scrollbar { width: 10px; } - /* line 134, src/styles/components/_ecogesture.scss */ + /* line 133, src/styles/components/_ecogesture.scss */ .em-content-box::-webkit-scrollbar-track { background: #3e4045; } - /* line 138, src/styles/components/_ecogesture.scss */ + /* line 137, src/styles/components/_ecogesture.scss */ .em-content-box::-webkit-scrollbar-thumb { background: #6f7074; } - /* line 141, src/styles/components/_ecogesture.scss */ + /* line 140, src/styles/components/_ecogesture.scss */ .em-content-box .em-content-box-text { display: flex; flex-direction: column; - padding: 0.5rem 1.5rem; + padding: 1.5rem 1.5rem; width: 22.125rem; } @media only screen and (max-width: 768px) { - /* line 141, src/styles/components/_ecogesture.scss */ + /* line 140, src/styles/components/_ecogesture.scss */ .em-content-box .em-content-box-text { width: 100%; } } + /* line 148, src/styles/components/_ecogesture.scss */ + .em-content-box .em-content-box-text .em-description { + padding-bottom: 2.5rem; } /** BLACK **/ /** TEXT COLOR **/ @@ -2280,7 +2300,7 @@ p { max-width: 100%; max-height: 30rem; transform: translate(-50%, -50%); - padding: 1rem 0; + padding: 1rem 0 0 0; box-sizing: border-box; box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.55); border-radius: 4px; } @@ -2339,37 +2359,34 @@ p { margin-top: 2.5rem; margin-bottom: 1.25rem; color: #e0e0e0; } - /* line 22, src/styles/components/_faq.scss */ - .faq-root .faq-content .faq-content-detail { - padding-bottom: 1rem; } -/* line 28, src/styles/components/_faq.scss */ +/* line 25, src/styles/components/_faq.scss */ .faq-card-link { color: black; } -/* line 31, src/styles/components/_faq.scss */ +/* line 28, src/styles/components/_faq.scss */ .faq-card { display: flex; flex-direction: row; margin: -0.75rem 0; width: 100%; } @media only screen and (max-width: 768px) { - /* line 31, src/styles/components/_faq.scss */ + /* line 28, src/styles/components/_faq.scss */ .faq-card { width: 100%; } } - /* line 39, src/styles/components/_faq.scss */ + /* line 36, src/styles/components/_faq.scss */ .faq-card .faq-card-content { display: flex; flex-direction: row; } - /* line 42, src/styles/components/_faq.scss */ + /* line 39, src/styles/components/_faq.scss */ .faq-card .faq-card-content .faq-card-content-icon { margin: 0.5rem 0; } - /* line 45, src/styles/components/_faq.scss */ + /* line 42, src/styles/components/_faq.scss */ .faq-card .faq-card-content .faq-card-content-title { margin: 0 1rem; align-self: center; } -/* line 53, src/styles/components/_faq.scss */ +/* line 50, src/styles/components/_faq.scss */ .faq-view-root { display: flex; flex-direction: column; @@ -2377,13 +2394,26 @@ p { justify-content: center; padding: 1rem 0; margin-top: 1.5rem; } - /* line 60, src/styles/components/_faq.scss */ + /* line 57, src/styles/components/_faq.scss */ .faq-view-root .faq-view-content { width: 45.75rem; } @media only screen and (max-width: 768px) { - /* line 60, src/styles/components/_faq.scss */ + /* line 57, src/styles/components/_faq.scss */ .faq-view-root .faq-view-content { width: 100%; } } + /* line 62, src/styles/components/_faq.scss */ + .faq-view-root .faq-view-content .faq-content-detail { + padding-bottom: 0.6rem; } + /* line 64, src/styles/components/_faq.scss */ + .faq-view-root .faq-view-content .faq-content-detail .text-bold { + font-weight: bold; } + /* line 67, src/styles/components/_faq.scss */ + .faq-view-root .faq-view-content .faq-content-detail .text-underline { + text-decoration: underline; } + /* line 70, src/styles/components/_faq.scss */ + .faq-view-root .faq-view-content .faq-content-detail .spaceline { + height: 0.6rem; + display: block; } /** BLACK **/ /** TEXT COLOR **/ diff --git a/src/styles/index.scss b/src/styles/index.scss index bbd950cdac0a009795d4eb78a34319fdefac1333..840c8d99c5335c425aec2a8b469b6b8318629034 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -22,6 +22,7 @@ @import 'components/modal'; @import 'components/faq'; @import 'components/splash'; +@import 'components/oauth'; :root { --blue: #{$blue};