summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx')
-rw-r--r--browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx408
1 files changed, 408 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx b/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx
new file mode 100644
index 0000000000..b9750e0765
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx
@@ -0,0 +1,408 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Button } from "../../components/Button/Button";
+import React from "react";
+import { RichText } from "../../components/RichText/RichText";
+import { safeURI } from "../../template-utils";
+import { SimpleSnippet } from "../SimpleSnippet/SimpleSnippet";
+import { SnippetBase } from "../../components/SnippetBase/SnippetBase";
+import ConditionalWrapper from "../../components/ConditionalWrapper/ConditionalWrapper";
+
+// Alt text placeholder in case the prop from the server isn't available
+const ICON_ALT_TEXT = "";
+
+export class SubmitFormSnippet extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.expandSnippet = this.expandSnippet.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleSubmitAttempt = this.handleSubmitAttempt.bind(this);
+ this.onInputChange = this.onInputChange.bind(this);
+ this.state = {
+ expanded: false,
+ submitAttempted: false,
+ signupSubmitted: false,
+ signupSuccess: false,
+ disableForm: false,
+ };
+ }
+
+ handleSubmitAttempt() {
+ if (!this.state.submitAttempted) {
+ this.setState({ submitAttempted: true });
+ }
+ }
+
+ async handleSubmit(event) {
+ let json;
+
+ if (this.state.disableForm) {
+ return;
+ }
+
+ event.preventDefault();
+ this.setState({ disableForm: true });
+ this.props.sendUserActionTelemetry({
+ event: "CLICK_BUTTON",
+ event_context: "conversion-subscribe-activation",
+ id: "NEWTAB_FOOTER_BAR_CONTENT",
+ });
+
+ if (this.props.form_method.toUpperCase() === "GET") {
+ this.props.onBlock({ preventDismiss: true });
+ this.refs.form.submit();
+ return;
+ }
+
+ const { url, formData } = this.props.processFormData
+ ? this.props.processFormData(this.refs.mainInput, this.props)
+ : { url: this.refs.form.action, formData: new FormData(this.refs.form) };
+
+ try {
+ const fetchRequest = new Request(url, {
+ body: formData,
+ method: "POST",
+ credentials: "omit",
+ });
+ const response = await fetch(fetchRequest); // eslint-disable-line fetch-options/no-fetch-credentials
+ json = await response.json();
+ } catch (err) {
+ console.error(err);
+ }
+
+ if (json && json.status === "ok") {
+ this.setState({ signupSuccess: true, signupSubmitted: true });
+ if (!this.props.content.do_not_autoblock) {
+ this.props.onBlock({ preventDismiss: true });
+ }
+ this.props.sendUserActionTelemetry({
+ event: "CLICK_BUTTON",
+ event_context: "subscribe-success",
+ id: "NEWTAB_FOOTER_BAR_CONTENT",
+ });
+ } else {
+ console.error(
+ "There was a problem submitting the form",
+ json || "[No JSON response]"
+ );
+ this.setState({ signupSuccess: false, signupSubmitted: true });
+ this.props.sendUserActionTelemetry({
+ event: "CLICK_BUTTON",
+ event_context: "subscribe-error",
+ id: "NEWTAB_FOOTER_BAR_CONTENT",
+ });
+ }
+
+ this.setState({ disableForm: false });
+ }
+
+ expandSnippet() {
+ this.props.sendUserActionTelemetry({
+ event: "CLICK_BUTTON",
+ event_context: "scene1-button-learn-more",
+ id: this.props.UISurface,
+ });
+
+ this.setState({
+ expanded: true,
+ signupSuccess: false,
+ signupSubmitted: false,
+ });
+ }
+
+ renderHiddenFormInputs() {
+ const { hidden_inputs } = this.props.content;
+
+ if (!hidden_inputs) {
+ return null;
+ }
+
+ return Object.keys(hidden_inputs).map((key, idx) => (
+ <input key={idx} type="hidden" name={key} value={hidden_inputs[key]} />
+ ));
+ }
+
+ renderDisclaimer() {
+ const { content } = this.props;
+ if (!content.scene2_disclaimer_html) {
+ return null;
+ }
+ return (
+ <p className="disclaimerText">
+ <RichText
+ text={content.scene2_disclaimer_html}
+ localization_id="disclaimer_html"
+ links={content.links}
+ doNotAutoBlock={true}
+ openNewWindow={true}
+ sendClick={this.props.sendClick}
+ />
+ </p>
+ );
+ }
+
+ renderFormPrivacyNotice() {
+ const { content } = this.props;
+ if (!content.scene2_privacy_html) {
+ return null;
+ }
+ return (
+ <p className="privacyNotice">
+ <input
+ type="checkbox"
+ id="id_privacy"
+ name="privacy"
+ required="required"
+ />
+ <label htmlFor="id_privacy">
+ <RichText
+ text={content.scene2_privacy_html}
+ localization_id="privacy_html"
+ links={content.links}
+ doNotAutoBlock={true}
+ openNewWindow={true}
+ sendClick={this.props.sendClick}
+ />
+ </label>
+ </p>
+ );
+ }
+
+ renderSignupSubmitted() {
+ const { content } = this.props;
+ const isSuccess = this.state.signupSuccess;
+ const successTitle = isSuccess && content.success_title;
+ const bodyText = isSuccess
+ ? { success_text: content.success_text }
+ : { error_text: content.error_text };
+ const retryButtonText = content.retry_button_label;
+ return (
+ <SnippetBase {...this.props}>
+ <div className="submissionStatus">
+ {successTitle ? (
+ <h2 className="submitStatusTitle">{successTitle}</h2>
+ ) : null}
+ <p>
+ <RichText
+ {...bodyText}
+ localization_id={isSuccess ? "success_text" : "error_text"}
+ />
+ {isSuccess ? null : (
+ <Button onClick={this.expandSnippet}>{retryButtonText}</Button>
+ )}
+ </p>
+ </div>
+ </SnippetBase>
+ );
+ }
+
+ onInputChange(event) {
+ if (!this.props.validateInput) {
+ return;
+ }
+ const hasError = this.props.validateInput(
+ event.target.value,
+ this.props.content
+ );
+ event.target.setCustomValidity(hasError);
+ }
+
+ wrapSectionHeader(url) {
+ return function (children) {
+ return <a href={url}>{children}</a>;
+ };
+ }
+
+ renderInput() {
+ const placholder =
+ this.props.content.scene2_email_placeholder_text ||
+ this.props.content.scene2_input_placeholder;
+ return (
+ <input
+ ref="mainInput"
+ type={this.props.inputType || "email"}
+ className={`mainInput${this.state.submitAttempted ? "" : " clean"}`}
+ name="email"
+ required={true}
+ placeholder={placholder}
+ onChange={this.props.validateInput ? this.onInputChange : null}
+ />
+ );
+ }
+
+ renderForm() {
+ return (
+ <form
+ action={this.props.form_action}
+ method={this.props.form_method}
+ onSubmit={this.handleSubmit}
+ ref="form"
+ >
+ {this.renderHiddenFormInputs()}
+ <div>
+ {this.renderInput()}
+ <button
+ type="submit"
+ className="ASRouterButton primary"
+ onClick={this.handleSubmitAttempt}
+ ref="formSubmitBtn"
+ >
+ {this.props.content.scene2_button_label}
+ </button>
+ </div>
+ {this.renderFormPrivacyNotice() || this.renderDisclaimer()}
+ </form>
+ );
+ }
+
+ renderScene2Icon() {
+ const { content } = this.props;
+ if (!content.scene2_icon) {
+ return null;
+ }
+
+ return (
+ <div className="scene2Icon">
+ <img
+ src={safeURI(content.scene2_icon)}
+ className="icon-light-theme"
+ alt={content.scene2_icon_alt_text || ICON_ALT_TEXT}
+ />
+ <img
+ src={safeURI(content.scene2_icon_dark_theme || content.scene2_icon)}
+ className="icon-dark-theme"
+ alt={content.scene2_icon_alt_text || ICON_ALT_TEXT}
+ />
+ </div>
+ );
+ }
+
+ renderSignupView() {
+ const { content } = this.props;
+ const containerClass = `SubmitFormSnippet ${this.props.className}`;
+ return (
+ <SnippetBase
+ {...this.props}
+ className={containerClass}
+ footerDismiss={true}
+ >
+ {this.renderScene2Icon()}
+ <div className="message">
+ <p>
+ {content.scene2_title && (
+ <h3 className="scene2Title">{content.scene2_title}</h3>
+ )}{" "}
+ {content.scene2_text && (
+ <RichText
+ scene2_text={content.scene2_text}
+ localization_id="scene2_text"
+ />
+ )}
+ </p>
+ </div>
+ {this.renderForm()}
+ </SnippetBase>
+ );
+ }
+
+ renderSectionHeader() {
+ const { props } = this;
+
+ // an icon and text must be specified to render the section header
+ if (props.content.section_title_icon && props.content.section_title_text) {
+ const sectionTitleIconLight = safeURI(props.content.section_title_icon);
+ const sectionTitleIconDark = safeURI(
+ props.content.section_title_icon_dark_theme ||
+ props.content.section_title_icon
+ );
+ const sectionTitleURL = props.content.section_title_url;
+
+ return (
+ <div className="section-header">
+ <h3 className="section-title">
+ <ConditionalWrapper
+ wrap={this.wrapSectionHeader(sectionTitleURL)}
+ condition={sectionTitleURL}
+ >
+ <span
+ className="icon icon-small-spacer icon-light-theme"
+ style={{ backgroundImage: `url("${sectionTitleIconLight}")` }}
+ />
+ <span
+ className="icon icon-small-spacer icon-dark-theme"
+ style={{ backgroundImage: `url("${sectionTitleIconDark}")` }}
+ />
+ <span className="section-title-text">
+ {props.content.section_title_text}
+ </span>
+ </ConditionalWrapper>
+ </h3>
+ </div>
+ );
+ }
+
+ return null;
+ }
+
+ renderSignupViewAlt() {
+ const { content } = this.props;
+ const containerClass = `SubmitFormSnippet ${this.props.className} scene2Alt`;
+ return (
+ <SnippetBase
+ {...this.props}
+ className={containerClass}
+ // Don't show bottom dismiss button
+ footerDismiss={false}
+ >
+ {this.renderSectionHeader()}
+ {this.renderScene2Icon()}
+ <div className="message">
+ <p>
+ {content.scene2_text && (
+ <RichText
+ scene2_text={content.scene2_text}
+ localization_id="scene2_text"
+ />
+ )}
+ </p>
+ {this.renderForm()}
+ </div>
+ </SnippetBase>
+ );
+ }
+
+ getFirstSceneContent() {
+ return Object.keys(this.props.content)
+ .filter(key => key.includes("scene1"))
+ .reduce((acc, key) => {
+ acc[key.substr(7)] = this.props.content[key];
+ return acc;
+ }, {});
+ }
+
+ render() {
+ const content = { ...this.props.content, ...this.getFirstSceneContent() };
+
+ if (this.state.signupSubmitted) {
+ return this.renderSignupSubmitted();
+ }
+ // Render only scene 2 (signup view). Must check before `renderSignupView`
+ // to catch the Failure/Try again scenario where we want to return and render
+ // the scene again.
+ if (this.props.expandedAlt) {
+ return this.renderSignupViewAlt();
+ }
+ if (this.state.expanded) {
+ return this.renderSignupView();
+ }
+ return (
+ <SimpleSnippet
+ {...this.props}
+ content={content}
+ onButtonClick={this.expandSnippet}
+ />
+ );
+ }
+}