From 4b245adb556c5f2e49c975d14284b1ae305b0232 Mon Sep 17 00:00:00 2001 From: Raman Yasel Date: Fri, 21 Apr 2023 12:40:21 +0300 Subject: [PATCH] feat: add marketo form --- .env.example | 4 + .github/workflows/pr-deploy.yml | 3 + .github/workflows/production.yml | 3 + .github/workflows/staging.yml | 4 + package-lock.json | 110 +++++++++++++++++- package.json | 5 +- src/components/blocks/footer/footer.view.js | 3 + .../form-message/form-message.module.scss | 31 +++++ .../shared/form-message/form-message.view.js | 33 ++++++ src/components/shared/form-message/index.js | 3 + .../form-message/svg/success.inline.svg | 4 + src/components/shared/marketo-form/index.js | 3 + .../shared/marketo-form/marketo-form.view.js | 23 ++++ src/components/shared/subscribe-form/index.js | 3 + .../shared/subscribe-form/subscribe-form.js | 76 ++++++++++++ .../subscribe-form/subscribe-form.module.scss | 81 +++++++++++++ src/hooks/use-marketo-form.js | 69 +++++++++++ src/hooks/use-subscribe-form.js | 32 +++++ 18 files changed, 488 insertions(+), 2 deletions(-) create mode 100644 src/components/shared/form-message/form-message.module.scss create mode 100644 src/components/shared/form-message/form-message.view.js create mode 100644 src/components/shared/form-message/index.js create mode 100644 src/components/shared/form-message/svg/success.inline.svg create mode 100644 src/components/shared/marketo-form/index.js create mode 100644 src/components/shared/marketo-form/marketo-form.view.js create mode 100644 src/components/shared/subscribe-form/index.js create mode 100644 src/components/shared/subscribe-form/subscribe-form.js create mode 100644 src/components/shared/subscribe-form/subscribe-form.module.scss create mode 100644 src/hooks/use-marketo-form.js create mode 100644 src/hooks/use-subscribe-form.js diff --git a/.env.example b/.env.example index 4bf58b40b3..08baea0ba5 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,7 @@ GATSBY_ALGOLIA_APP_ID= GATSBY_ALGOLIA_SEARCH_ONLY_KEY= GATSBY_ALGOLIA_INDEX_NAME= ALGOLIA_ADMIN_KEY= + +GATSBY_NEWSLETTER_FORM_MUNCHKIN_ID= +GATSBY_NEWSLETTER_FORM_ID= +GATSBY_NEWSLETTER_FORM_URL= diff --git a/.github/workflows/pr-deploy.yml b/.github/workflows/pr-deploy.yml index f1defb01c6..270c5b2785 100644 --- a/.github/workflows/pr-deploy.yml +++ b/.github/workflows/pr-deploy.yml @@ -6,6 +6,9 @@ env: GATSBY_DEFAULT_DOC_URL: https://mdr-ci.staging.k6.io/docs/${{ github.ref }} GATSBY_DEFAULT_BLOG_URL: https://k6.io/blog GATSBY_DEFAULT_APP_URL: https://app.staging.k6.io + GATSBY_NEWSLETTER_FORM_URL: https://go2.grafana.com + GATSBY_NEWSLETTER_FORM_MUNCHKIN_ID: 356-YFG-389 + GATSBY_NEWSLETTER_FORM_ID: 1420 on: pull_request: diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index aaedd53b3d..e21456a2fd 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -21,6 +21,9 @@ env: GATSBY_DEFAULT_DOC_URL: https://${{ secrets.PROD_CI_MAIN_URL }}/docs GATSBY_DEFAULT_MAIN_URL: https://${{ secrets.PROD_CI_MAIN_URL }} GATSBY_GOOGLE_API_KEY: ${{ secrets.PROD_CI_GATSBY_GOOGLE_API_KEY }} + GATSBY_NEWSLETTER_FORM_URL: https://go2.grafana.com + GATSBY_NEWSLETTER_FORM_MUNCHKIN_ID: 356-YFG-389 + GATSBY_NEWSLETTER_FORM_ID: 1420 jobs: update-dependencies: diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 052979337a..ad61e51b28 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -21,6 +21,10 @@ env: GATSBY_DEFAULT_DOC_URL: https://staging.k6.io/docs GATSBY_DEFAULT_MAIN_URL: https://staging.k6.io GATSBY_GOOGLE_API_KEY: ${{ secrets.STAGING_CI_GATSBY_GOOGLE_API_KEY }} + GATSBY_NEWSLETTER_FORM_URL: https://go2.grafana.com + GATSBY_NEWSLETTER_FORM_MUNCHKIN_ID: 356-YFG-389 + GATSBY_NEWSLETTER_FORM_ID: 1420 + jobs: update-dependencies: name: Update node module dependencies diff --git a/package-lock.json b/package-lock.json index c94784b799..240a84b0e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "k6-docs", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^3.1.0", "@mdx-js/mdx": "^1.6.22", "@mdx-js/react": "^1.6.22", "@sentry/gatsby": "^7.21.1", @@ -46,10 +47,12 @@ "react-clipboard.js": "^2.0.16", "react-cookie-banner": "^4.1.0", "react-dom": "^17.0.2", + "react-hook-form": "^7.43.9", "react-instantsearch-dom": "^6.38.1", "react-tooltip": "^4.5.1", "runes": "^0.4.3", - "sass": "^1.56.1" + "sass": "^1.56.1", + "yup": "^1.1.1" }, "devDependencies": { "@babel/core": "^7.20.2", @@ -2634,6 +2637,14 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.1.0.tgz", + "integrity": "sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -20256,6 +20267,11 @@ "signal-exit": "^3.0.2" } }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -20795,6 +20811,21 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "node_modules/react-hook-form": { + "version": "7.43.9", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.9.tgz", + "integrity": "sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-instantsearch-core": { "version": "6.38.1", "resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.38.1.tgz", @@ -24386,6 +24417,11 @@ "next-tick": "1" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -24485,6 +24521,11 @@ } ] }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -26386,6 +26427,28 @@ "node": ">=8" } }, + "node_modules/yup": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.1.1.tgz", + "integrity": "sha512-KfCGHdAErqFZWA5tZf7upSUnGKuTOnsI3hUsLr7fgVtx+DK04NPV01A68/FslI4t3s/ZWpvXJmgXhd7q6ICnag==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yurnalist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/yurnalist/-/yurnalist-2.1.0.tgz", @@ -28478,6 +28541,12 @@ "@hapi/hoek": "^9.0.0" } }, + "@hookform/resolvers": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.1.0.tgz", + "integrity": "sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -41540,6 +41609,11 @@ "signal-exit": "^3.0.2" } }, + "property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -41948,6 +42022,12 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "react-hook-form": { + "version": "7.43.9", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.43.9.tgz", + "integrity": "sha512-AUDN3Pz2NSeoxQ7Hs6OhQhDr6gtF9YRuutGDwPQqhSUAHJSgGl2VeY3qN19MG0SucpjgDiuMJ4iC5T5uB+eaNQ==", + "requires": {} + }, "react-instantsearch-core": { "version": "6.38.1", "resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.38.1.tgz", @@ -44790,6 +44870,11 @@ "next-tick": "1" } }, + "tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -44857,6 +44942,11 @@ } } }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -46324,6 +46414,24 @@ "@types/yoga-layout": "1.9.2" } }, + "yup": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.1.1.tgz", + "integrity": "sha512-KfCGHdAErqFZWA5tZf7upSUnGKuTOnsI3hUsLr7fgVtx+DK04NPV01A68/FslI4t3s/ZWpvXJmgXhd7q6ICnag==", + "requires": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } + }, "yurnalist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/yurnalist/-/yurnalist-2.1.0.tgz", diff --git a/package.json b/package.json index ce92ac6d92..44407b0b14 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "prepare": "husky install" }, "dependencies": { + "@hookform/resolvers": "^3.1.0", "@mdx-js/mdx": "^1.6.22", "@mdx-js/react": "^1.6.22", "@sentry/gatsby": "^7.21.1", @@ -70,10 +71,12 @@ "react-clipboard.js": "^2.0.16", "react-cookie-banner": "^4.1.0", "react-dom": "^17.0.2", + "react-hook-form": "^7.43.9", "react-instantsearch-dom": "^6.38.1", "react-tooltip": "^4.5.1", "runes": "^0.4.3", - "sass": "^1.56.1" + "sass": "^1.56.1", + "yup": "^1.1.1" }, "devDependencies": { "@babel/core": "^7.20.2", diff --git a/src/components/blocks/footer/footer.view.js b/src/components/blocks/footer/footer.view.js index 211ced9fdc..55d278de37 100644 --- a/src/components/blocks/footer/footer.view.js +++ b/src/components/blocks/footer/footer.view.js @@ -1,3 +1,4 @@ +import SubscribeForm from 'components/shared/subscribe-form'; import { Link } from 'gatsby'; import React, { useState, useEffect } from 'react'; import Logo from 'svg/logo-with-grafana-labs.inline.svg'; @@ -318,6 +319,8 @@ export const Footer = () => { + +
diff --git a/src/components/shared/form-message/form-message.module.scss b/src/components/shared/form-message/form-message.module.scss new file mode 100644 index 0000000000..393ae3bee2 --- /dev/null +++ b/src/components/shared/form-message/form-message.module.scss @@ -0,0 +1,31 @@ +.wrapper { + text-align: center; + width: 100%; +} + +.success-icon { + margin-bottom: 10px; + svg { + display: block; + margin: 0 auto; + } +} + +.success-text { + font-size: $font-size-sm; +} + +.error-text { + font-size: $font-size-sm; + color: $color-error; + + a { + color: $color-error; + font-weight: bold; + text-decoration: none; + + @include hover-supported { + text-decoration: underline; + } + } +} diff --git a/src/components/shared/form-message/form-message.view.js b/src/components/shared/form-message/form-message.view.js new file mode 100644 index 0000000000..80e3fd19cb --- /dev/null +++ b/src/components/shared/form-message/form-message.view.js @@ -0,0 +1,33 @@ +import MarketoForm from 'components/shared/marketo-form'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import styles from './form-message.module.scss'; +import SuccessSVG from './svg/success.inline.svg'; + +const FormMessage = ({ type }) => ( +
+ {type === 'success' && ( + <> +
+ +
+ + Your message has been successfully sent + + + )} + {type === 'error' && ( + + Your message has not been sent. Please, if the problem persists, contact{' '} + support@k6.io. + + )} +
+); + +MarketoForm.propTypes = { + type: PropTypes.string.isRequired, +}; + +export default FormMessage; diff --git a/src/components/shared/form-message/index.js b/src/components/shared/form-message/index.js new file mode 100644 index 0000000000..5fd95b5768 --- /dev/null +++ b/src/components/shared/form-message/index.js @@ -0,0 +1,3 @@ +import FormMessage from './form-message.view'; + +export default FormMessage; diff --git a/src/components/shared/form-message/svg/success.inline.svg b/src/components/shared/form-message/svg/success.inline.svg new file mode 100644 index 0000000000..9ec17006ea --- /dev/null +++ b/src/components/shared/form-message/svg/success.inline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/shared/marketo-form/index.js b/src/components/shared/marketo-form/index.js new file mode 100644 index 0000000000..71ba91a3a5 --- /dev/null +++ b/src/components/shared/marketo-form/index.js @@ -0,0 +1,3 @@ +import MarketoForm from './marketo-form.view'; + +export default MarketoForm; diff --git a/src/components/shared/marketo-form/marketo-form.view.js b/src/components/shared/marketo-form/marketo-form.view.js new file mode 100644 index 0000000000..54dd5250e6 --- /dev/null +++ b/src/components/shared/marketo-form/marketo-form.view.js @@ -0,0 +1,23 @@ +import useMarketo from 'hooks/use-marketo-form'; +import PropTypes from 'prop-types'; +import React, { memo } from 'react'; + +const MarketoForm = memo(({ debug, formId }) => { + useMarketo({ + formId, + callback: () => {}, + }); + + return + ); +}; + +export default SubscribeForm; diff --git a/src/components/shared/subscribe-form/subscribe-form.module.scss b/src/components/shared/subscribe-form/subscribe-form.module.scss new file mode 100644 index 0000000000..b9fd666f19 --- /dev/null +++ b/src/components/shared/subscribe-form/subscribe-form.module.scss @@ -0,0 +1,81 @@ +.subscribe-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 40px; + padding: 30px 0; + gap: 10px; + border-top: 1px solid $color-additional-2; + border-bottom: 1px solid $color-additional-2; + + @media only screen and (max-width: $screen-lg-down) { + padding-top: 0; + border-top: none; + } + + @media only screen and (max-width: $screen-md-down) { + flex-direction: column; + align-items: start; + } +} + +.subscriptionText { + font-size: $font-size-base; + line-height: $line-height-base; + + color: $color-secondary; + + &-bold { + font-weight: 500; + } + + > p { + margin: 2px 0 0 0; + } + + @media only screen and (max-width: $screen-md-down) { + margin-bottom: 10px; + } +} + +.subscribe-form { + display: flex; + align-items: center; + + @media only screen and (max-width: $screen-sm-down) { + width: 100%; + } +} + +.subscribe-input { + width: 294px; + height: 45px; + padding: 15px; + + margin: auto 0; + border: 1px solid #d3d3de; + outline: none; + + font-size: 16px; + line-height: 1; + + @media only screen and (max-width: $screen-sm-down) { + flex-grow: 1; + width: 50%; + } +} + +.subscribeInputInvalid { + border-color: $color-error; + box-shadow: 0 0 5px rgba($color-error, 0.25); + + &:focus { + border-color: $color-error; + box-shadow: 0 0 5px rgba($color-error, 0.25); + } +} + +.subscribe-button { + margin-left: 8px; + white-space: nowrap; +} diff --git a/src/hooks/use-marketo-form.js b/src/hooks/use-marketo-form.js new file mode 100644 index 0000000000..746b251509 --- /dev/null +++ b/src/hooks/use-marketo-form.js @@ -0,0 +1,69 @@ +import { useState, useEffect } from 'react'; + +const useMarketo = ({ formId, callback }) => { + const [scriptAdded, setScriptAdded] = useState(false); + const [formLoaded, setFormLoaded] = useState(false); + + useEffect(() => { + if (scriptAdded) { + if (!formLoaded) { + // eslint-disable-next-line no-undef + MktoForms2.loadForm( + `${process.env.GATSBY_NEWSLETTER_FORM_URL}`, + process.env.GATSBY_NEWSLETTER_FORM_MUNCHKIN_ID, + formId, + ); + // eslint-disable-next-line no-undef + MktoForms2.whenRendered((form) => { + const formElement = form.getFormElem()[0]; + const formElementId = form.getFormElem()[0].id.split('_')[1]; + + /** Remove the style attribute and make for, and id attributes unique */ + Array.from(formElement.querySelectorAll('[style]')) + .concat(formElement) + .forEach((element) => { + element.removeAttribute('style'); + if (element.hasAttribute('id') && element.tagName !== 'FORM') { + element.setAttribute( + 'id', + `${element.getAttribute('id')}_${formElementId}`, + ); + } + + if (element.tagName === 'LABEL') { + element.setAttribute( + 'for', + `${element.getAttribute('for')}_${formElementId}`, + ); + } + }); + + /** Remove from DOM */ + Array.from(formElement.querySelectorAll('.mktoInstruction')).forEach( + (element) => { + element.remove(); + }, + ); + + /** Remove