summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet')
-rw-r--r--browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.jsx76
-rw-r--r--browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json243
-rw-r--r--browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/isEmailOrPhoneNumber.js39
3 files changed, 358 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.jsx b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.jsx
new file mode 100644
index 0000000000..0929b8f711
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.jsx
@@ -0,0 +1,76 @@
+/* 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 { isEmailOrPhoneNumber } from "./isEmailOrPhoneNumber";
+import React from "react";
+import { SubmitFormSnippet } from "../SubmitFormSnippet/SubmitFormSnippet.jsx";
+
+function validateInput(value, content) {
+ const type = isEmailOrPhoneNumber(value, content);
+ return type ? "" : "Must be an email or a phone number.";
+}
+
+function processFormData(input, message) {
+ const { content } = message;
+ const type = content.include_sms
+ ? isEmailOrPhoneNumber(input.value, content)
+ : "email";
+ const formData = new FormData();
+ let url;
+ if (type === "phone") {
+ url = "https://basket.mozilla.org/news/subscribe_sms/";
+ formData.append("mobile_number", input.value);
+ formData.append("msg_name", content.message_id_sms);
+ formData.append("country", content.country);
+ } else if (type === "email") {
+ url = "https://basket.mozilla.org/news/subscribe/";
+ formData.append("email", input.value);
+ formData.append("newsletters", content.message_id_email);
+ formData.append(
+ "source_url",
+ encodeURIComponent(`https://snippets.mozilla.com/show/${message.id}`)
+ );
+ }
+ formData.append("lang", content.locale);
+ return { formData, url };
+}
+
+function addDefaultValues(props) {
+ return {
+ ...props,
+ content: {
+ scene1_button_label: "Learn more",
+ retry_button_label: "Try again",
+ scene2_dismiss_button_text: "Dismiss",
+ scene2_button_label: "Send",
+ scene2_input_placeholder: "Your email here",
+ locale: "en-US",
+ country: "us",
+ message_id_email: "",
+ include_sms: false,
+ ...props.content,
+ },
+ };
+}
+
+export const SendToDeviceSnippet = props => {
+ const propsWithDefaults = addDefaultValues(props);
+
+ return (
+ <SubmitFormSnippet
+ {...propsWithDefaults}
+ form_method="POST"
+ className="send_to_device_snippet"
+ inputType={propsWithDefaults.content.include_sms ? "text" : "email"}
+ validateInput={
+ propsWithDefaults.content.include_sms ? validateInput : null
+ }
+ processFormData={processFormData}
+ />
+ );
+};
+
+export const SendToDeviceScene2Snippet = props => {
+ return <SendToDeviceSnippet expandedAlt={true} {...props} />;
+};
diff --git a/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json
new file mode 100644
index 0000000000..34567443f4
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json
@@ -0,0 +1,243 @@
+{
+ "title": "SendToDeviceSnippet",
+ "description": "A snippet template for send to device mobile download",
+ "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",
+ "default": "en-US"
+ },
+ "country": {
+ "type": "string",
+ "description": "Two character string for the country code (used for SMS)",
+ "default": "us"
+ },
+ "scene1_title": {
+ "allof": [
+ { "$ref": "#/definitions/plainText" },
+ { "description": "snippet title displayed before snippet text" }
+ ]
+ },
+ "scene1_text": {
+ "allOf": [
+ { "$ref": "#/definitions/richText" },
+ {
+ "description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
+ }
+ ]
+ },
+ "scene1_section_title_icon": {
+ "type": "string",
+ "description": "Section title icon for scene 1. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
+ },
+ "scene1_section_title_icon_dark_theme": {
+ "type": "string",
+ "description": "Section title icon for scene 1, dark theme variant. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
+ },
+ "scene1_section_title_text": {
+ "type": "string",
+ "description": "Section title text for scene 1. scene1_section_title_icon must also be specified to display."
+ },
+ "scene1_section_title_url": {
+ "allOf": [
+ { "$ref": "#/definitions/link_url" },
+ { "description": "A url, scene1_section_title_text links to this" }
+ ]
+ },
+ "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"
+ }
+ ]
+ },
+ "scene1_icon": {
+ "type": "string",
+ "description": "Snippet icon. 64x64px. SVG or PNG preferred."
+ },
+ "scene1_icon_dark_theme": {
+ "type": "string",
+ "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+ },
+ "scene2_icon": {
+ "type": "string",
+ "description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
+ },
+ "scene2_icon_dark_theme": {
+ "type": "string",
+ "description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
+ },
+ "scene1_title_icon": {
+ "type": "string",
+ "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
+ },
+ "scene1_title_icon_dark_theme": {
+ "type": "string",
+ "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+ },
+ "scene2_button_label": {
+ "type": "string",
+ "description": "Label for form submit button",
+ "default": "Send"
+ },
+ "scene2_input_placeholder": {
+ "type": "string",
+ "description": "(send to device) Value to show while input is empty.",
+ "default": "Your email here"
+ },
+ "scene2_disclaimer_html": {
+ "type": "string",
+ "description": "(send to device) Html for disclaimer and link underneath input box."
+ },
+ "scene2_dismiss_button_text": {
+ "type": "string",
+ "description": "Label for the dismiss button when the sign-up form is expanded.",
+ "default": "Dismiss"
+ },
+ "hidden_inputs": {
+ "type": "object",
+ "description": "Each entry represents a hidden input, key is used as value for the name property.",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": ["email"]
+ },
+ "context": {
+ "type": "string",
+ "enum": ["fx_desktop_v3"]
+ },
+ "entrypoint": {
+ "type": "string",
+ "enum": ["snippets"]
+ },
+ "utm_content": {
+ "type": "string",
+ "description": "Firefox version number"
+ },
+ "utm_source": {
+ "type": "string",
+ "enum": ["snippet"]
+ },
+ "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."
+ },
+ "additionalProperties": false
+ }
+ },
+ "scene1_button_label": {
+ "allOf": [
+ { "$ref": "#/definitions/plainText" },
+ {
+ "description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
+ }
+ ],
+ "default": "Learn more"
+ },
+ "scene1_button_color": {
+ "type": "string",
+ "description": "The text color of the button. Valid CSS color."
+ },
+ "scene1_button_background_color": {
+ "type": "string",
+ "description": "The background color of the button. Valid CSS color."
+ },
+ "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",
+ "default": false
+ },
+ "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."
+ },
+ "include_sms": {
+ "type": "boolean",
+ "description": "(send to device) Allow users to send an SMS message with the form?",
+ "default": false
+ },
+ "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": ["scene1_text", "scene2_text", "scene1_button_label"],
+ "dependencies": {
+ "scene1_button_color": ["scene1_button_label"],
+ "scene1_button_background_color": ["scene1_button_label"]
+ }
+}
diff --git a/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/isEmailOrPhoneNumber.js b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/isEmailOrPhoneNumber.js
new file mode 100644
index 0000000000..44ef622227
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/isEmailOrPhoneNumber.js
@@ -0,0 +1,39 @@
+/* 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/. */
+
+/**
+ * Checks if a given string is an email or phone number or neither
+ * @param {string} val The user input
+ * @param {ASRMessageContent} content .content property on ASR message
+ * @returns {"email"|"phone"|""} The type of the input
+ */
+export function isEmailOrPhoneNumber(val, content) {
+ const { locale } = content;
+ // http://emailregex.com/
+ const email_re =
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ const check_email = email_re.test(val);
+ let check_phone; // depends on locale
+ switch (locale) {
+ case "en-US":
+ case "en-CA":
+ // allow 10-11 digits in case user wants to enter country code
+ check_phone = val.length >= 10 && val.length <= 11 && !isNaN(val);
+ break;
+ case "de":
+ // allow between 2 and 12 digits for german phone numbers
+ check_phone = val.length >= 2 && val.length <= 12 && !isNaN(val);
+ break;
+ // this case should never be hit, but good to have a fallback just in case
+ default:
+ check_phone = !isNaN(val);
+ break;
+ }
+ if (check_email) {
+ return "email";
+ } else if (check_phone) {
+ return "phone";
+ }
+ return "";
+}