Commit 7e6bdb77 authored by Andrei Oprea's avatar Andrei Oprea
Browse files

Bug 1649538 - Add new Snippets template for Send to Device r=k88hudson

parent 05bded91
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -70,3 +70,7 @@ export const SendToDeviceSnippet = props => {
    />
  );
};

export const SendToDeviceScene2Snippet = props => {
  return <SendToDeviceSnippet expandedAlt={true} {...props} />;
};
+163 −0
Original line number Diff line number Diff line
{
  "title": "SubmitFormSnippet",
  "description": "A template with two states: a SimpleSnippet and another that contains a form",
  "version": "1.2.0",
  "type": "object",
  "definitions": {
    "plainText": {
      "description": "Plain text (no HTML allowed)",
      "type": "string"
    },
    "richText": {
      "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
      "type": "string"
    },
    "link_url": {
      "description": "Target for links or buttons",
      "type": "string",
      "format": "uri"
    }
  },
  "properties": {
    "locale": {
      "type": "string",
      "description": "Two to five character string for the locale code"
    },
    "country": {
      "type": "string",
      "description": "Two character string for the country code (used for SMS)"
    },
    "section_title_icon": {
      "type": "string",
      "description": "Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
    },
    "section_title_icon_dark_theme": {
      "type": "string",
      "description": "Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
    },
    "section_title_text": {
      "type": "string",
      "description": "Section title text. section_title_icon must also be specified to display."
    },
    "scene2_title": {
      "allOf": [
        {"$ref": "#/definitions/plainText"},
        {"description": "Title displayed before text in scene 2. Should be plain text."}
      ]
    },
    "scene2_text": {
      "allOf": [
        {"$ref": "#/definitions/richText"},
        {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
      ]
    },
    "form_action": {
      "type": "string",
      "description": "Endpoint to submit form data."
    },
    "success_title": {
      "type": "string",
      "description": "(send to device) Title shown before text on successful registration."
    },
    "success_text": {
      "type": "string",
      "description": "Message shown on successful registration."
    },
    "error_text": {
      "type": "string",
      "description": "Message shown if registration failed."
    },
    "scene2_email_placeholder_text": {
      "type": "string",
      "description": "Value to show while input is empty."
    },
    "scene2_input_placeholder": {
      "type": "string",
      "description": "(send to device) Value to show while input is empty."
    },
    "scene2_button_label": {
      "type": "string",
      "description": "Label for form submit button"
    },
    "scene2_privacy_html": {
      "type": "string",
      "description": "Information about how the form data is used."
    },
    "scene2_disclaimer_html": {
      "type": "string",
      "description": "(send to device) Html for disclaimer and link underneath input box."
    },
    "scene2_icon": {
      "type": "string",
      "description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
    },
    "scene2_icon_dark_theme": {
      "type": "string",
      "description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
    },
    "scene2_icon_alt_text": {
      "type": "string",
      "description": "Alt text describing scene2 icon for screen readers",
      "default": ""
    },
    "scene2_newsletter": {
      "type": "string",
      "description": "Newsletter/basket id user is subscribing to. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/. Default 'mozilla-foundation'."
    },
    "hidden_inputs": {
      "type": "object",
      "description": "Each entry represents a hidden input, key is used as value for the name property."
    },
    "retry_button_label": {
      "allOf": [
        {"$ref": "#/definitions/plainText"},
        {"description": "Text for the button in the event of a submission error/failure."}
      ],
      "default": "Try again"
    },
    "do_not_autoblock": {
      "type": "boolean",
      "description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked"
    },
    "include_sms": {
      "type": "boolean",
      "description": "(send to device) Allow users to send an SMS message with the form?"
    },
    "message_id_sms": {
      "type": "string",
      "description": "(send to device) Newsletter/basket id representing the SMS message to be sent."
    },
    "message_id_email": {
      "type": "string",
      "description": "(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."
    },
    "utm_campaign": {
      "type": "string",
      "description": "(fxa) Value to pass through to GA as utm_campaign."
    },
    "utm_term": {
      "type": "string",
      "description": "(fxa) Value to pass through to GA as utm_term."
    },
    "links": {
      "additionalProperties": {
        "url": {
          "allOf": [
            {"$ref": "#/definitions/link_url"},
            {"description": "The url where the link points to."}
          ]
        },
        "metric": {
          "type": "string",
          "description": "Custom event name sent with telemetry event."
        }
      }
    }
  },
  "additionalProperties": false,
  "required": ["scene2_text"],
  "dependencies": {
    "section_title_icon": ["section_title_text"],
    "section_title_icon_dark_theme": ["section_title_text"]
  }
}
+117 −36
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ 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 = "";
@@ -226,16 +227,38 @@ export class SubmitFormSnippet extends React.PureComponent {
    );
  }

  renderSignupView() {
    const { content } = this.props;
    const containerClass = `SubmitFormSnippet ${this.props.className}`;
  renderForm() {
    return (
      <SnippetBase
        {...this.props}
        className={containerClass}
        footerDismiss={true}
      <form
        action={this.props.form_action}
        method={this.props.form_method}
        onSubmit={this.handleSubmit}
        ref="form"
      >
        {content.scene2_icon ? (
        {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)}
@@ -243,14 +266,24 @@ export class SubmitFormSnippet extends React.PureComponent {
          alt={content.scene2_icon_alt_text || ICON_ALT_TEXT}
        />
        <img
              src={safeURI(
                content.scene2_icon_dark_theme || content.scene2_icon
              )}
          src={safeURI(content.scene2_icon_dark_theme || content.scene2_icon)}
          className="icon-dark-theme"
          alt={content.scene2_icon_alt_text || ICON_ALT_TEXT}
        />
      </div>
        ) : null}
    );
  }

  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 && (
@@ -264,26 +297,70 @@ export class SubmitFormSnippet extends React.PureComponent {
            )}
          </p>
        </div>
        <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.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 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}
      >
              {content.scene2_button_label}
            </button>
        {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>
          {this.renderFormPrivacyNotice() || this.renderDisclaimer()}
        </form>
      </SnippetBase>
    );
  }
@@ -306,6 +383,10 @@ export class SubmitFormSnippet extends React.PureComponent {
    if (this.state.expanded) {
      return this.renderSignupView();
    }
    // Render only scene 2 (signup view)
    if (this.props.expandedAlt) {
      return this.renderSignupViewAlt();
    }
    return (
      <SimpleSnippet
        {...this.props}
+33 −2
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@
  width: 100%;

  .disclaimerText {
    margin: 20px 0 0;
    margin: 5px 0 0;
    font-size: 12px;
    color: var(--newtab-text-secondary-color);
  }
@@ -72,7 +72,8 @@
  }

  .innerWrapper {
    max-width: 670px;
    // https://github.com/mozmeao/snippets/blob/2054899350590adcb3c0b0a341c782b0e2f81d0b/activity-stream/newsletter-subscribe.html#L46
    max-width: 736px;
    flex-wrap: wrap;
    justify-items: center;
    padding-top: 40px;
@@ -121,6 +122,36 @@
      }
    }
  }

  &.scene2Alt {
    text-align: start;

    .scene2Icon {
      flex: 1;
      margin-bottom: 0;
    }

    .message {
      flex: 5;
      margin-bottom: 0;

      p {
        margin-bottom: 10px;
      }
    }

    .section-header {
      width: 100%;

      .icon {
        margin-inline-end: 0;
      }
    }

    .innerWrapper {
      padding: 0 0 16px;
    }
  }
}

.submissionStatus {
+5 −1
Original line number Diff line number Diff line
@@ -5,7 +5,10 @@
import { EOYSnippet } from "./EOYSnippet/EOYSnippet";
import { FXASignupSnippet } from "./FXASignupSnippet/FXASignupSnippet";
import { NewsletterSnippet } from "./NewsletterSnippet/NewsletterSnippet";
import { SendToDeviceSnippet } from "./SendToDeviceSnippet/SendToDeviceSnippet";
import {
  SendToDeviceSnippet,
  SendToDeviceScene2Snippet,
} from "./SendToDeviceSnippet/SendToDeviceSnippet";
import { SimpleBelowSearchSnippet } from "./SimpleBelowSearchSnippet/SimpleBelowSearchSnippet";
import { SimpleSnippet } from "./SimpleSnippet/SimpleSnippet";

@@ -15,6 +18,7 @@ export const SnippetsTemplates = {
  newsletter_snippet: NewsletterSnippet,
  fxa_signup_snippet: FXASignupSnippet,
  send_to_device_snippet: SendToDeviceSnippet,
  send_to_device_scene2_snippet: SendToDeviceScene2Snippet,
  eoy_snippet: EOYSnippet,
  simple_below_search_snippet: SimpleBelowSearchSnippet,
};
Loading