diff --git a/.env.development b/.env.development index baaf32cd9..9d543351d 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,5 @@ REACT_APP_DOPPLER_LEGACY_URL=http://localhost:52191 +REACT_APP_DOPPLER_SITES_URL= https://www.fromdoppler.com REACT_APP_DATAHUB_URL=https://hubapisecint.fromdoppler.com REACT_APP_RECAPTCHA_PUBLIC_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI REACT_APP_USE_DOPPLER_LEGACY_LOGIN=false diff --git a/.env.int b/.env.int index d00aeb147..8cb09f54c 100644 --- a/.env.int +++ b/.env.int @@ -1,4 +1,5 @@ REACT_APP_DOPPLER_LEGACY_URL=https://appint.fromdoppler.net +REACT_APP_DOPPLER_SITES_URL= https://www.fromdoppler.com REACT_APP_DATAHUB_URL=https://hubapisecint.fromdoppler.com REACT_APP_RECAPTCHA_PUBLIC_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI REACT_APP_USE_DOPPLER_LEGACY_LOGIN=false diff --git a/.env.production b/.env.production index 3df81be30..0a7f93c2c 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,5 @@ REACT_APP_DOPPLER_LEGACY_URL=https://app2.fromdoppler.com +REACT_APP_DOPPLER_SITES_URL= https://www.fromdoppler.com REACT_APP_DATAHUB_URL=https://hubapisec.fromdoppler.com REACT_APP_RECAPTCHA_PUBLIC_KEY=6LddzZ8UAAAAAPSs09txKtTl9ewIyqYihfOC-dzf REACT_APP_USE_DOPPLER_LEGACY_LOGIN=false diff --git a/.env.qa b/.env.qa index d77a1e6d3..3b7a55c01 100644 --- a/.env.qa +++ b/.env.qa @@ -1,4 +1,5 @@ REACT_APP_DOPPLER_LEGACY_URL=https://appqa.fromdoppler.net +REACT_APP_DOPPLER_SITES_URL= https://www.fromdoppler.com REACT_APP_DATAHUB_URL=https://hubapisecqa.fromdoppler.com REACT_APP_RECAPTCHA_PUBLIC_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI REACT_APP_USE_DOPPLER_LEGACY_LOGIN=false diff --git a/src/components/ForgotPassword/ForgotPassword.js b/src/components/ForgotPassword/ForgotPassword.js index 038c230c7..5c605ba54 100644 --- a/src/components/ForgotPassword/ForgotPassword.js +++ b/src/components/ForgotPassword/ForgotPassword.js @@ -15,6 +15,7 @@ import './ForgotPassword.css'; import { FormattedMessageMarkdown } from '../../i18n/FormattedMessageMarkdown'; import { Helmet } from 'react-helmet'; import { connect } from 'formik'; +import Promotions from '../shared/Promotions/Promotions'; const fieldNames = { email: 'email', @@ -149,23 +150,7 @@ const ForgotPassword = ({ intl, location, dependencies: { dopplerLegacyClient }

-
-
-
-
{_('feature_panel.forms')}
-

{_('feature_panel.forms_description')}

-

{_('feature_panel.forms_remarks')}

-
-
- Subscription Forms -
-
-
+ ); }; diff --git a/src/components/Login/Login.js b/src/components/Login/Login.js index 58820684d..7ca8f16e0 100644 --- a/src/components/Login/Login.js +++ b/src/components/Login/Login.js @@ -17,6 +17,7 @@ import { InjectAppServices } from '../../services/pure-di'; import { LoginErrorAccountNotValidated } from './LoginErrorAccountNotValidated'; import { FormattedMessageMarkdown } from '../../i18n/FormattedMessageMarkdown'; import { connect } from 'formik'; +import Promotions from '../shared/Promotions/Promotions'; const fieldNames = { user: 'user', @@ -199,23 +200,7 @@ const Login = ({ intl, location, dependencies: { dopplerLegacyClient, sessionMan

-
-
-
-
{_('feature_panel.forms')}
-

{_('feature_panel.forms_description')}

-

{_('feature_panel.forms_remarks')}

-
-
- Subscription Forms -
-
-
+ ); }; diff --git a/src/components/Signup/Signup.js b/src/components/Signup/Signup.js index a0ebc3c14..8d4f5ebd6 100644 --- a/src/components/Signup/Signup.js +++ b/src/components/Signup/Signup.js @@ -17,6 +17,8 @@ import { import LanguageSelector from '../shared/LanguageSelector/LanguageSelector'; import SignupConfirmation from './SignupConfirmation'; import { FormattedMessageMarkdown } from '../../i18n/FormattedMessageMarkdown'; +import Promotions from '../shared/Promotions/Promotions'; +import queryString from 'query-string'; const fieldNames = { firstname: 'firstname', @@ -33,6 +35,12 @@ const minLength = { errorMessageKey: 'validation_messages.error_min_length_2', }; +/** Extract the page parameter from url*/ +function extractPage(location) { + const parsedQuery = location && location.search && queryString.parse(location.search); + return (parsedQuery && (parsedQuery['page'] || parsedQuery['Page'])) || null; +} + /** Prepare empty values for all fields * It is required because in another way, the fields are not marked as touched. */ @@ -48,7 +56,7 @@ const getFormInitialValues = () => * @param { import('react-intl').InjectedIntl } props.intl * @param { import('../../services/pure-di').AppServices } props.dependencies */ -const Signup = function({ intl, dependencies: { dopplerLegacyClient, originResolver } }) { +const Signup = function({ intl, location, dependencies: { dopplerLegacyClient, originResolver } }) { const _ = (id, values) => intl.formatMessage({ id: id }, values); const [registeredUser, setRegisteredUser] = useState(null); @@ -143,7 +151,9 @@ const Signup = function({ intl, dependencies: { dopplerLegacyClient, originResol
{_('signup.sign_up')}

{_('signup.sign_up_sub')} {_('signup.do_you_already_have_an_account')}{' '} - {_('signup.log_in')} + + {_('signup.log_in')} +

-
-
-
-
{_('feature_panel.email_automation')}
-

{_('feature_panel.email_automation_description')}

-

{_('feature_panel.email_automation_remarks')}

-
-
- Automation -
-
-
+ ); }; diff --git a/src/components/shared/Promotions/Promotions.js b/src/components/shared/Promotions/Promotions.js new file mode 100644 index 000000000..83e8b64b5 --- /dev/null +++ b/src/components/shared/Promotions/Promotions.js @@ -0,0 +1,79 @@ +import React, { useState, useEffect } from 'react'; +import { injectIntl } from 'react-intl'; +import { InjectAppServices } from '../../../services/pure-di'; +import Loading from '../../Loading/Loading'; + +const getDefaultBannerData = (intl) => { + const _ = (id, values) => intl.formatMessage({ id: id }, values); + + return { + title: _('default_banner_data.title'), + description: _('default_banner_data.description'), + backgroundUrl: _('default_banner_data.background_url'), + imageUrl: _('default_banner_data.image_url'), + functionality: _('default_banner_data.functionality'), + fontColor: '#000', + }; +}; + +/** + * Promotions + * @param { Object } props + * @param { import('react-intl').InjectedIntl } props.intl + * @param { import('../../services/pure-di').AppServices } props.dependencies + */ +const Promotions = function({ + intl, + type, + page, + disabledSitesContent, + dependencies: { dopplerSitesClient }, +}) { + const [bannerData, setBannerData] = useState({}); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (disabledSitesContent) { + setBannerData(getDefaultBannerData(intl)); + setIsLoading(false); + } else { + const fetchData = async () => { + setIsLoading(true); + const bannerData = await dopplerSitesClient.getBannerData(intl.locale, type, page || ''); + setBannerData( + bannerData.success && bannerData.value ? bannerData.value : getDefaultBannerData(intl), + ); + setIsLoading(false); + }; + + fetchData(); + } + }, [disabledSitesContent, dopplerSitesClient, page, intl, type]); + + return ( +
+ {isLoading ? ( + + ) : ( +
+
+
{bannerData.functionality}
+

{bannerData.title}

+

{bannerData.description}

+
+
+ +
+
+ )} +
+ ); +}; + +export default InjectAppServices(injectIntl(Promotions)); diff --git a/src/components/shared/Promotions/Promotions.test.js b/src/components/shared/Promotions/Promotions.test.js new file mode 100644 index 000000000..165c742b8 --- /dev/null +++ b/src/components/shared/Promotions/Promotions.test.js @@ -0,0 +1,84 @@ +import React from 'react'; +import { render, cleanup, waitForDomChange } from '@testing-library/react'; +import 'jest-dom/extend-expect'; +import Promotions from './Promotions'; +import DopplerIntlProvider from '../../../i18n/DopplerIntlProvider.double-with-ids-as-values'; +import { AppServicesProvider } from '../../../services/pure-di'; +import { async } from 'q'; + +const emptyResponse = { success: false, error: new Error('Dummy error') }; +const fullResponse = { + success: true, + value: { + title: 'default_banner_data.title', + description: 'default_banner_data.description', + backgroundUrl: 'default_banner_data.background_url', + imageUrl: 'default_banner_data.image_url', + functionality: 'default_banner_data.functionality', + color: '#fff', + }, +}; + +describe('Promotions Component', () => { + afterEach(cleanup); + it('should sites response fail', async () => { + const dopplerSitesClientDouble = { + getBannerData: async () => emptyResponse, + }; + + const { container, getByText } = render( + + + + + , + ); + expect(container.querySelector('.loading-box')).toBeInTheDocument(); + await waitForDomChange(); + expect(getByText('default_banner_data.title')); + }); + + it('should disabled sites content', () => { + const dopplerSitesClientDouble = { + getBannerData: async () => emptyResponse, + }; + + const { getByText } = render( + + + + + , + ); + expect(getByText('default_banner_data.title')); + }); + + it('should has full data from service', async () => { + const dopplerSitesClientDouble = { + getBannerData: async () => fullResponse, + }; + + const { container, getByText } = render( + + + + + , + ); + expect(container.querySelector('.loading-box')).toBeInTheDocument(); + await waitForDomChange(); + expect(getByText(fullResponse.value.title)); + }); +}); diff --git a/src/doppler-types.ts b/src/doppler-types.ts index 0cad7aa2d..6911a3373 100644 --- a/src/doppler-types.ts +++ b/src/doppler-types.ts @@ -1,6 +1,9 @@ export type UnexpectedError = { success?: false; message?: string | null; error?: any }; export type ErrorResult = { success?: false; expectedError: TError } | UnexpectedError; export type Result = { success: true; value: TResult } | ErrorResult; +export type ResultWithoutExpectedErrors = + | { success: true; value: TResult } + | UnexpectedError; export type EmptyResult = { success: true } | ErrorResult; // It does not work: // type EmptyResult = { success: true } | UnexpectedError; diff --git a/src/i18n/en.js b/src/i18n/en.js index eafe800da..4a0df7f40 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -35,6 +35,13 @@ export default { send: `Send`, show: `Show`, }, + default_banner_data: { + background_url: 'https://cdn.fromdoppler.com/doppler-ui-library/v2.5.0/img/violet-yellow.png', + description: 'Classics and pop-ups with Single or Double Opt-In subscription. You decide how you want them to look, what data to request and where to place them!', + functionality: 'subscription forms', + image_url: 'https://cdn.fromdoppler.com/doppler-ui-library/v2.5.0/img/login-en.png', + title: 'Add new contacts to your Lists using custom Forms', + }, empty_notification_text: `You don't have pending notifications.`, feature_panel: { email_automation: `Email Automation`, diff --git a/src/i18n/es.js b/src/i18n/es.js index aa36af488..3ff84aa9c 100644 --- a/src/i18n/es.js +++ b/src/i18n/es.js @@ -35,6 +35,13 @@ export default { send: `Enviar`, show: `Mostrar`, }, + default_banner_data: { + background_url: 'https://cdn.fromdoppler.com/doppler-ui-library/v2.5.0/img/violet-yellow.png', + description: 'Clásicos y pop-ups con suscripción Simple o Doble Opt-In. ¡Tú eliges cómo quieres que luzcan, qué datos solicitar y dónde ubicarlos!', + functionality: 'Formularios de suscripción', + image_url: 'https://cdn.fromdoppler.com/doppler-ui-library/v2.5.0/img/login-es.png', + title: 'Suma contactos a tus Listas con Formularios personalizados', + }, empty_notification_text: `No tienes notificaciones pendientes.`, feature_panel: { email_automation: `Email Automation`, diff --git a/src/index.js b/src/index.js index fe1e10b82..c9c8dc6a5 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ import ReactGA from 'react-ga'; // Only used in development environment, it does not affect production build import { HardcodedDopplerLegacyClient } from './services/doppler-legacy-client.doubles'; import { HardcodedDatahubClient } from './services/datahub-client.doubles'; +import { HardcodedDopplerSitesClient } from './services/doppler-sites-client.doubles'; import { polyfill } from 'es6-object-assign'; import 'polyfill-array-includes'; import 'promise-polyfill/src/polyfill'; @@ -29,6 +30,7 @@ const forcedServices = process.env.NODE_ENV === 'development' ? { dopplerLegacyClient: new HardcodedDopplerLegacyClient(), + dopplerSitesClient: new HardcodedDopplerSitesClient(), datahubClient: new HardcodedDatahubClient(), shopifyClient: new HardcodedShopifyClient(), } diff --git a/src/services/doppler-sites-client.doubles.ts b/src/services/doppler-sites-client.doubles.ts new file mode 100644 index 000000000..601df9a6d --- /dev/null +++ b/src/services/doppler-sites-client.doubles.ts @@ -0,0 +1,33 @@ +import { DopplerSitesClient, PromotionsResult } from './doppler-sites-client'; + +import { timeout } from '../utils'; + +export class HardcodedDopplerSitesClient implements DopplerSitesClient { + public async getBannerData(lang: string, type: string, page: string): Promise { + console.log('getBannerData'); + await timeout(1500); + const response: any = { + title: 'mi funcionalidad', + functionality: 'mi funcionalidad', + description: 'mi descripcion', + image_url: 'https://qa.fromdoppler.com/wp-content/uploads/2019/06/login-es.746bf048.png', + background_url: 'https://qa.fromdoppler.com/wp-content/uploads/2019/06/violet-yellow.png', + font_color: '#000', + }; + return { + success: true, + value: { + title: response.title, + functionality: response.functionality, + description: response.description, + imageUrl: response.image_url, + backgroundUrl: response.background_url, + fontColor: response.font_color, + }, + }; + //return { + // success: false, + // error: new Error('Dummy error'), + //}; + } +} diff --git a/src/services/doppler-sites-client.ts b/src/services/doppler-sites-client.ts new file mode 100644 index 000000000..9bf68eff2 --- /dev/null +++ b/src/services/doppler-sites-client.ts @@ -0,0 +1,63 @@ +import { AxiosInstance, AxiosStatic } from 'axios'; +import { ResultWithoutExpectedErrors } from '../doppler-types'; + +export interface DopplerSitesClient { + getBannerData(lang: string, type: string, page?: string | null): Promise; +} + +export interface Promotions { + title: string; + functionality: string; + description: string; + imageUrl: string; + backgroundUrl: string; + fontColor: string; +} + +export type PromotionsResult = ResultWithoutExpectedErrors; + +export class HttpDopplerSitesClient implements DopplerSitesClient { + private readonly axios: AxiosInstance; + private readonly baseUrl: string; + + constructor({ axiosStatic, baseUrl }: { axiosStatic: AxiosStatic; baseUrl: string }) { + this.baseUrl = baseUrl; + this.axios = axiosStatic.create({ + baseURL: baseUrl, + }); + } + + public async getBannerData( + lang: string, + type: string, + page?: string | null, + ): Promise { + try { + const response: any = await this.axios.get( + `${ + this.baseUrl + }/wp-json/doppler2019/v1/getbanner?filter[lang]=${lang}&filter[type]=${type}&filter[page]=${page || + ''}`, + ); + if (!response || !response.data) { + throw new Error('Empty Site response'); + } + return { + success: true, + value: { + title: response.title, + functionality: response.functionality, + description: response.description, + imageUrl: response.image_url, + backgroundUrl: response.background_url, + fontColor: response.font_color, + }, + }; + } catch (error) { + return { + success: false, + error: error, + }; + } + } +} diff --git a/src/services/pure-di.tsx b/src/services/pure-di.tsx index 6a5b4a29d..b49dbd9c4 100644 --- a/src/services/pure-di.tsx +++ b/src/services/pure-di.tsx @@ -6,10 +6,12 @@ import { DatahubClient, HttpDatahubClient } from './datahub-client'; import { AppSession, createAppSessionRef } from './app-session'; import { OriginResolver, LocalStorageOriginResolver } from './origin-management'; import { ShopifyClient } from './shopify-client'; +import { DopplerSitesClient, HttpDopplerSitesClient } from './doppler-sites-client'; import { HardcodedShopifyClient } from './shopify-client.doubles'; interface AppConfiguration { dopplerLegacyUrl: string; + dopplerSitesUrl: string; datahubUrl: string; dopplerLegacyKeepAliveMilliseconds: number; recaptchaPublicKey: string; @@ -34,6 +36,7 @@ export interface AppServices { localStorage: Storage; originResolver: OriginResolver; shopifyClient: ShopifyClient; + dopplerSitesClient: DopplerSitesClient; } /** @@ -67,6 +70,7 @@ export class AppCompositionRoot implements AppServices { get appConfiguration() { return this.singleton('appConfiguration', () => ({ dopplerLegacyUrl: process.env.REACT_APP_DOPPLER_LEGACY_URL as string, + dopplerSitesUrl: process.env.REACT_APP_DOPPLER_SITES_URL as string, datahubUrl: process.env.REACT_APP_DATAHUB_URL as string, recaptchaPublicKey: process.env.REACT_APP_RECAPTCHA_PUBLIC_KEY as string, dopplerLegacyKeepAliveMilliseconds: parseInt(process.env @@ -102,6 +106,17 @@ export class AppCompositionRoot implements AppServices { ); } + get dopplerSitesClient() { + return this.singleton( + 'dopplerSitesClient', + () => + new HttpDopplerSitesClient({ + axiosStatic: this.axiosStatic, + baseUrl: this.appConfiguration.dopplerSitesUrl, + }), + ); + } + get shopifyClient() { return this.singleton('shopifyClient', () => new HardcodedShopifyClient()); }