From b4c9d98b05de152e5730d124bf2a7664db6e1899 Mon Sep 17 00:00:00 2001
From: Oisin Coveney <oisin@metabase.com>
Date: Fri, 19 Jan 2024 11:37:18 -0800
Subject: [PATCH] [Epic #35961] Milestone 4 - Add Legalese Step (#37006)

Co-authored-by: Noah Moss <32746338+noahmoss@users.noreply.github.com>
---
 e2e/support/helpers/e2e-embedding-helpers.js  |   6 +
 .../embedding/embedding-dashboard.cy.spec.js  |   4 +-
 .../embedding-downloads-questions.cy.spec.js  |   2 +-
 .../embedding/embedding-smoketests.cy.spec.js |   6 +-
 .../embedding/embedding-snippets.cy.spec.js   |   6 +-
 .../30535-session-sandboxing.cy.spec.js       |   1 +
 ...6988-embedding-dynamic-settings.cy.spec.js |   1 +
 .../serialization/v2/e2e_test.clj             |   1 +
 .../src/metabase-types/api/mocks/settings.ts  |   1 +
 frontend/src/metabase-types/api/settings.ts   |   1 +
 .../src/metabase/admin/settings/settings.js   |   2 +-
 .../components/ModalContent/ModalContent.tsx  |   2 +-
 .../DashboardSharingEmbeddingModal.tsx        |   4 +-
 .../EmbedModal/EmbedModal.styled.tsx          |  20 ++-
 .../components/EmbedModal/EmbedModal.tsx      |  88 +++++++++----
 .../EmbedModal/EmbedModal.unit.spec.tsx       | 117 ++++++++++++++++++
 .../EmbedModalContent/EmbedModalContent.tsx   |  17 ++-
 .../EmbedModalContent.unit.spec.tsx           |   6 +-
 .../SelectEmbedTypePane.tsx                   |   9 +-
 .../SelectEmbedTypePane.unit.spec.tsx         |  18 +--
 .../AppearanceSettings.tsx                    |   4 +-
 .../StaticEmbedSetupPane/OverviewSettings.tsx |   4 +-
 .../ParametersSettings.tsx                    |   4 +-
 .../public/components/EmbedModal/types.ts     |   2 +-
 .../LegaleseStep/LegaleseStep.styled.tsx      |  10 ++
 .../widgets/LegaleseStep/LegaleseStep.tsx     |  58 +++++++++
 .../LegaleseStep/LegaleseStep.unit.spec.tsx   |  67 ++++++++++
 .../QuestionEmbedWidget.tsx                   |   4 +-
 src/metabase/util/embed.clj                   |  16 ++-
 test/metabase/util/embed_test.clj             |  33 +++++
 30 files changed, 442 insertions(+), 72 deletions(-)
 create mode 100644 frontend/src/metabase/public/components/EmbedModal/EmbedModal.unit.spec.tsx
 create mode 100644 frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.styled.tsx
 create mode 100644 frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.tsx
 create mode 100644 frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.unit.spec.tsx

diff --git a/e2e/support/helpers/e2e-embedding-helpers.js b/e2e/support/helpers/e2e-embedding-helpers.js
index a30f9e8cca3..4dc1a544d88 100644
--- a/e2e/support/helpers/e2e-embedding-helpers.js
+++ b/e2e/support/helpers/e2e-embedding-helpers.js
@@ -155,10 +155,12 @@ export function openEmbedModalFromMenu() {
  * @param {object} params
  * @param {("overview"|"parameters"|"appearance")} [params.activeTab] - modal tab to open
  * @param {("code"|"preview")} [params.previewMode] - preview mode type to activate
+ * @param {boolean} [params.acceptTerms] - whether we need to go through the legalese step
  */
 export function openStaticEmbeddingModal({
   activeTab,
   previewMode,
+  acceptTerms = true,
   confirmSave,
 } = {}) {
   openEmbedModalFromMenu();
@@ -169,6 +171,10 @@ export function openStaticEmbeddingModal({
 
   cy.findByTestId("sharing-pane-static-embed-button").click();
 
+  if (acceptTerms) {
+    cy.findByTestId("accept-legalese-terms-button").click();
+  }
+
   modal().within(() => {
     if (activeTab) {
       const tabKeyToNameMap = {
diff --git a/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js b/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js
index d11694f881b..4f5cfa7f411 100644
--- a/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js
+++ b/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js
@@ -54,7 +54,7 @@ describe("scenarios > embedding > dashboard parameters", () => {
         visitDashboard(dashboardId);
       });
 
-      openStaticEmbeddingModal({ activeTab: "parameters" });
+      openStaticEmbeddingModal({ activeTab: "parameters", acceptTerms: true });
 
       cy.findByLabelText("Enable or lock parameters").as("allParameters");
 
@@ -137,7 +137,7 @@ describe("scenarios > embedding > dashboard parameters", () => {
         visitDashboard(dashboardId);
       });
 
-      openStaticEmbeddingModal({ activeTab: "parameters" });
+      openStaticEmbeddingModal({ activeTab: "parameters", acceptTerms: false });
 
       cy.get("@allParameters").findByText("Locked").click();
       popover().contains("Disabled").click();
diff --git a/e2e/test/scenarios/embedding/embedding-downloads-questions.cy.spec.js b/e2e/test/scenarios/embedding/embedding-downloads-questions.cy.spec.js
index 7ac88490ef1..40fd5f0b550 100644
--- a/e2e/test/scenarios/embedding/embedding-downloads-questions.cy.spec.js
+++ b/e2e/test/scenarios/embedding/embedding-downloads-questions.cy.spec.js
@@ -103,7 +103,7 @@ describeEE("scenarios > embedding > questions > downloads", () => {
       cy.get("@questionId").then(questionId => {
         visitQuestion(questionId);
 
-        openStaticEmbeddingModal({ activeTab: "appearance" });
+        openStaticEmbeddingModal({ activeTab: "appearance", acceptTerms: false });
 
         cy.log("Disable downloads");
         cy.findByLabelText("Enable users to download data from this embed?")
diff --git a/e2e/test/scenarios/embedding/embedding-smoketests.cy.spec.js b/e2e/test/scenarios/embedding/embedding-smoketests.cy.spec.js
index 95f06d1dee7..2e5b7964666 100644
--- a/e2e/test/scenarios/embedding/embedding-smoketests.cy.spec.js
+++ b/e2e/test/scenarios/embedding/embedding-smoketests.cy.spec.js
@@ -211,7 +211,7 @@ describe("scenarios > embedding > smoke tests", { tags: "@OSS" }, () => {
           .and("contain", objectName);
 
         cy.log(`Unpublish ${object}`);
-        visitAndEnableSharing(object);
+        visitAndEnableSharing(object, false);
 
         modal().within(() => {
           cy.findByText(
@@ -331,7 +331,7 @@ function ensureEmbeddingIsDisabled() {
   });
 }
 
-function visitAndEnableSharing(object) {
+function visitAndEnableSharing(object, acceptTerms = true) {
   if (object === "question") {
     visitQuestion(ORDERS_QUESTION_ID);
   }
@@ -340,7 +340,7 @@ function visitAndEnableSharing(object) {
     visitDashboard(ORDERS_DASHBOARD_ID);
   }
 
-  openStaticEmbeddingModal();
+  openStaticEmbeddingModal({acceptTerms});
 }
 
 function sidebar() {
diff --git a/e2e/test/scenarios/embedding/embedding-snippets.cy.spec.js b/e2e/test/scenarios/embedding/embedding-snippets.cy.spec.js
index b0fbe75b647..6662719cbc2 100644
--- a/e2e/test/scenarios/embedding/embedding-snippets.cy.spec.js
+++ b/e2e/test/scenarios/embedding/embedding-snippets.cy.spec.js
@@ -26,7 +26,8 @@ features.forEach(feature => {
 
     it("dashboard should have the correct embed snippet", () => {
       visitDashboard(ORDERS_DASHBOARD_ID);
-      openStaticEmbeddingModal();
+      console.log("feature", feature);
+      openStaticEmbeddingModal({ acceptTerms: false });
 
       modal().within(() => {
         cy.findByText(
@@ -91,7 +92,8 @@ features.forEach(feature => {
 
     it("question should have the correct embed snippet", () => {
       visitQuestion(ORDERS_QUESTION_ID);
-      openStaticEmbeddingModal();
+      console.log("feature", feature);
+      openStaticEmbeddingModal({ acceptTerms: false });
 
       modal().within(() => {
         cy.findByText(
diff --git a/e2e/test/scenarios/embedding/reproductions/30535-session-sandboxing.cy.spec.js b/e2e/test/scenarios/embedding/reproductions/30535-session-sandboxing.cy.spec.js
index 624a5093a64..2067a9e94a1 100644
--- a/e2e/test/scenarios/embedding/reproductions/30535-session-sandboxing.cy.spec.js
+++ b/e2e/test/scenarios/embedding/reproductions/30535-session-sandboxing.cy.spec.js
@@ -41,6 +41,7 @@ describeEE("issue 30535", () => {
     openStaticEmbeddingModal({
       activeTab: "parameters",
       previewMode: "preview",
+      acceptTerms: false,
     });
 
     cy.document().then(doc => {
diff --git a/e2e/test/scenarios/sharing/reproductions/26988-embedding-dynamic-settings.cy.spec.js b/e2e/test/scenarios/sharing/reproductions/26988-embedding-dynamic-settings.cy.spec.js
index 56adc7a5cfe..aa768c7049e 100644
--- a/e2e/test/scenarios/sharing/reproductions/26988-embedding-dynamic-settings.cy.spec.js
+++ b/e2e/test/scenarios/sharing/reproductions/26988-embedding-dynamic-settings.cy.spec.js
@@ -37,6 +37,7 @@ describeEE("issue 26988", () => {
     openStaticEmbeddingModal({
       activeTab: "appearance",
       previewMode: "preview",
+      acceptTerms: false
     });
 
     cy.wait("@dashboard");
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj
index 909a1ccf869..b14cda6902c 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/e2e_test.clj
@@ -897,6 +897,7 @@
            show-homepage-xrays
            show-lighthouse-illustration
            show-metabot
+           show-static-embed-terms
            site-locale
            site-name
            source-address-header
diff --git a/frontend/src/metabase-types/api/mocks/settings.ts b/frontend/src/metabase-types/api/mocks/settings.ts
index ecd5b75012c..23d3067bfd2 100644
--- a/frontend/src/metabase-types/api/mocks/settings.ts
+++ b/frontend/src/metabase-types/api/mocks/settings.ts
@@ -164,6 +164,7 @@ export const createMockSettings = (
   engines: createMockEngines(),
   "has-user-setup": true,
   "hide-embed-branding?": true,
+  "show-static-embed-terms": true,
   "ga-enabled": false,
   "google-auth-auto-create-accounts-domain": null,
   "google-auth-client-id": null,
diff --git a/frontend/src/metabase-types/api/settings.ts b/frontend/src/metabase-types/api/settings.ts
index a358cf69002..2881cb4dcdb 100644
--- a/frontend/src/metabase-types/api/settings.ts
+++ b/frontend/src/metabase-types/api/settings.ts
@@ -281,6 +281,7 @@ export interface Settings {
   "uploads-table-prefix": string | null;
   "user-visibility": string | null;
   "last-acknowledged-version": string | null;
+  "show-static-embed-terms": boolean | null;
 }
 
 export type SettingKey = keyof Settings;
diff --git a/frontend/src/metabase/admin/settings/settings.js b/frontend/src/metabase/admin/settings/settings.js
index dbcca23184f..6cec320dd2d 100644
--- a/frontend/src/metabase/admin/settings/settings.js
+++ b/frontend/src/metabase/admin/settings/settings.js
@@ -53,7 +53,7 @@ export const UPDATE_SETTING = "metabase/admin/settings/UPDATE_SETTING";
 export const updateSetting = createThunkAction(
   UPDATE_SETTING,
   function (setting) {
-    return async function (dispatch, getState) {
+    return async function (dispatch) {
       try {
         await SettingsApi.put(setting);
       } catch (error) {
diff --git a/frontend/src/metabase/components/ModalContent/ModalContent.tsx b/frontend/src/metabase/components/ModalContent/ModalContent.tsx
index c64b0a7948e..ec05f468cc0 100644
--- a/frontend/src/metabase/components/ModalContent/ModalContent.tsx
+++ b/frontend/src/metabase/components/ModalContent/ModalContent.tsx
@@ -35,7 +35,7 @@ export default class ModalContent extends Component<ModalContentProps> {
   static propTypes = {
     "data-testid": PropTypes.string,
     id: PropTypes.string,
-    title: PropTypes.string,
+    title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
     centeredTitle: PropTypes.bool,
     onClose: PropTypes.func,
     // takes over the entire screen
diff --git a/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal/DashboardSharingEmbeddingModal.tsx b/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal/DashboardSharingEmbeddingModal.tsx
index 273051fcd7f..5f60406796c 100644
--- a/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal/DashboardSharingEmbeddingModal.tsx
+++ b/frontend/src/metabase/dashboard/containers/DashboardSharingEmbeddingModal/DashboardSharingEmbeddingModal.tsx
@@ -43,10 +43,10 @@ export const DashboardSharingEmbeddingModal = (
 
   return (
     <EmbedModal isOpen={isOpen} onClose={onClose}>
-      {({ embedType, setEmbedType }) => (
+      {({ embedType, goToNextStep }) => (
         <EmbedModalContent
           embedType={embedType}
-          setEmbedType={setEmbedType}
+          goToNextStep={goToNextStep}
           className={className}
           resource={dashboard}
           resourceParameters={parameters}
diff --git a/frontend/src/metabase/public/components/EmbedModal/EmbedModal.styled.tsx b/frontend/src/metabase/public/components/EmbedModal/EmbedModal.styled.tsx
index f9f7c044b3d..7df65a86e75 100644
--- a/frontend/src/metabase/public/components/EmbedModal/EmbedModal.styled.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/EmbedModal.styled.tsx
@@ -1,6 +1,24 @@
+import { css } from "@emotion/react";
 import styled from "@emotion/styled";
+import { Group } from "metabase/ui";
 import { ModalContentActionIcon } from "metabase/components/ModalContent";
 
+export const EmbedTitleContainer = styled(Group)<{
+  onClick?: () => void;
+}>`
+  ${({ onClick, theme }) => {
+    return (
+      onClick &&
+      css`
+        &:hover * {
+          color: ${theme.colors.brand[1]};
+          cursor: pointer;
+        }
+      `
+    );
+  }}
+`;
+
 export const EmbedModalHeaderBackIcon = styled(ModalContentActionIcon)`
-  margin: -0.5rem 0 -0.5rem -0.5rem;
+  padding: 0;
 `;
diff --git a/frontend/src/metabase/public/components/EmbedModal/EmbedModal.tsx b/frontend/src/metabase/public/components/EmbedModal/EmbedModal.tsx
index 2318cc829ea..7904c41a9a0 100644
--- a/frontend/src/metabase/public/components/EmbedModal/EmbedModal.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/EmbedModal.tsx
@@ -1,31 +1,68 @@
 import { useState } from "react";
 import { t } from "ttag";
+import {
+  EmbedModalHeaderBackIcon,
+  EmbedTitleContainer,
+} from "metabase/public/components/EmbedModal/EmbedModal.styled";
+import { getSetting } from "metabase/selectors/settings";
 import { useSelector } from "metabase/lib/redux";
 import { getApplicationName } from "metabase/selectors/whitelabel";
 import Modal from "metabase/components/Modal";
-import { Divider } from "metabase/ui";
+import { Box, Divider, Text } from "metabase/ui";
 import * as MetabaseAnalytics from "metabase/lib/analytics";
+import type { EmbedModalStep } from "./types";
 
-import { EmbedModalHeaderBackIcon } from "./EmbedModal.styled";
-import type { EmbedType } from "./types";
-
-interface EmbedModalProps {
+type EmbedModalProps = {
   isOpen?: boolean;
   onClose: () => void;
   children: ({
     embedType,
-    setEmbedType,
+    goToNextStep,
+    goBackToEmbedModal,
   }: {
-    embedType: EmbedType;
-    setEmbedType: (type: EmbedType) => void;
+    embedType: EmbedModalStep;
+    goToNextStep: () => void;
+    goBackToEmbedModal: () => void;
   }) => JSX.Element;
-}
+};
+
+const EmbedTitle = ({
+  onClick,
+  label,
+}: {
+  label: string;
+  onClick?: () => void;
+}) => {
+  return (
+    <EmbedTitleContainer onClick={onClick}>
+      {onClick && (
+        <EmbedModalHeaderBackIcon name="chevronleft" onClick={onClick} />
+      )}
+      <Text fz="xl" c="text.1">
+        {label}
+      </Text>
+    </EmbedTitleContainer>
+  );
+};
 
 export const EmbedModal = ({ children, isOpen, onClose }: EmbedModalProps) => {
-  const [embedType, setEmbedType] = useState<EmbedType>(null);
+  const shouldShowEmbedTerms = useSelector(state =>
+    getSetting(state, "show-static-embed-terms"),
+  );
+  const [embedType, setEmbedType] = useState<EmbedModalStep>(null);
   const applicationName = useSelector(getApplicationName);
 
-  const isEmbeddingStage = embedType === "application";
+  const goToNextStep = () => {
+    if (embedType === null && shouldShowEmbedTerms) {
+      setEmbedType("legalese");
+    } else {
+      setEmbedType("application");
+    }
+  };
+
+  const goBackToEmbedModal = () => {
+    setEmbedType(null);
+  };
 
   const onEmbedClose = () => {
     MetabaseAnalytics.trackStructEvent("Sharing Modal", "Modal Closed");
@@ -33,28 +70,29 @@ export const EmbedModal = ({ children, isOpen, onClose }: EmbedModalProps) => {
     setEmbedType(null);
   };
 
+  const modalHeaderProps =
+    embedType === null
+      ? {
+          label: t`Embed ${applicationName}`,
+          onClick: undefined,
+        }
+      : {
+          label: t`Static embedding`,
+          onClick: goBackToEmbedModal,
+        };
+
   return (
     <Modal
       isOpen={isOpen}
       onClose={onEmbedClose}
-      title={
-        isEmbeddingStage ? (
-          <>
-            <EmbedModalHeaderBackIcon
-              name="chevronleft"
-              onClick={() => setEmbedType(null)}
-            />
-            {t`Static embedding`}
-          </>
-        ) : (
-          t`Embed ${applicationName}`
-        )
-      }
+      title={<EmbedTitle {...modalHeaderProps} />}
       fit
       formModal={false}
     >
       <Divider />
-      {children({ embedType, setEmbedType })}
+      <Box bg="bg.0" h="100%">
+        {children({ embedType, goToNextStep, goBackToEmbedModal })}
+      </Box>
     </Modal>
   );
 };
diff --git a/frontend/src/metabase/public/components/EmbedModal/EmbedModal.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/EmbedModal.unit.spec.tsx
new file mode 100644
index 00000000000..2c4fe7a8e83
--- /dev/null
+++ b/frontend/src/metabase/public/components/EmbedModal/EmbedModal.unit.spec.tsx
@@ -0,0 +1,117 @@
+import userEvent from "@testing-library/user-event";
+import { Route } from "react-router";
+import { mockSettings } from "__support__/settings";
+import { renderWithProviders, screen, within } from "__support__/ui";
+import { createMockState } from "metabase-types/store/mocks";
+import { Button, Group, Text } from "metabase/ui";
+import { EmbedModal } from "./EmbedModal";
+
+const TestEmbedModal = ({
+  onClose,
+  isOpen,
+}: {
+  onClose: () => void;
+  isOpen?: boolean;
+}): JSX.Element => {
+  return (
+    <EmbedModal isOpen={isOpen} onClose={onClose}>
+      {({ embedType, goToNextStep, goBackToEmbedModal }) => (
+        <Group data-testid="test-embed-modal-content">
+          <Button onClick={goBackToEmbedModal}>Previous</Button>
+          <Text data-testid="test-embed-step">
+            {embedType ?? "Embed Landing"}
+          </Text>
+          <Button onClick={goToNextStep}>Next</Button>
+        </Group>
+      )}
+    </EmbedModal>
+  );
+};
+
+const setup = ({ isOpen = true, showStaticEmbedTerms = true } = {}) => {
+  const onClose = jest.fn();
+
+  renderWithProviders(
+    <Route
+      path="*"
+      component={() => <TestEmbedModal isOpen={isOpen} onClose={onClose} />}
+    ></Route>,
+    {
+      storeInitialState: createMockState({
+        settings: mockSettings({
+          "application-name": "Embed Metabase",
+          "show-static-embed-terms": showStaticEmbedTerms,
+        }),
+      }),
+      initialRoute: "*",
+      withRouter: true,
+    },
+  );
+
+  return { onClose };
+};
+
+describe("EmbedModal", () => {
+  it("should render header and content", () => {
+    setup();
+
+    expect(
+      within(screen.getByTestId("modal-header")).getByText("Embed Metabase"),
+    ).toBeInTheDocument();
+
+    expect(screen.getByTestId("test-embed-modal-content")).toBeInTheDocument();
+
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent(
+      "Embed Landing",
+    );
+  });
+
+  it("should go to the legalese step when `Next` is clicked", () => {
+    setup();
+
+    userEvent.click(screen.getByText("Next"));
+
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent("legalese");
+  });
+
+  it("should go to the legalese step then the static embedding step if the user has not accepted the embedding terms", () => {
+    setup({ showStaticEmbedTerms: true });
+    userEvent.click(screen.getByText("Next"));
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent("legalese");
+
+    userEvent.click(screen.getByText("Next"));
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent(
+      "application",
+    );
+  });
+
+  it("should immediately go to the static embedding step if the user has accepted the terms", async () => {
+    setup({ showStaticEmbedTerms: false });
+
+    userEvent.click(screen.getByText("Next"));
+    expect(
+      within(screen.getByTestId("modal-header")).getByText("Static embedding"),
+    ).toBeInTheDocument();
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent(
+      "application",
+    );
+  });
+
+  it("returns to the initial embed modal landing when the user clicks the modal title", () => {
+    setup();
+
+    userEvent.click(screen.getByText("Next"));
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent("legalese");
+
+    userEvent.click(screen.getByText("Static embedding"));
+    expect(screen.getByTestId("test-embed-step")).toHaveTextContent(
+      "Embed Landing",
+    );
+  });
+
+  it("calls onClose when the modal is closed", () => {
+    const { onClose } = setup();
+    userEvent.click(screen.getByLabelText("close icon"));
+    expect(onClose).toHaveBeenCalled();
+  });
+});
diff --git a/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.tsx b/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.tsx
index 5e590c38a6d..bb51d168a86 100644
--- a/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.tsx
@@ -1,5 +1,6 @@
 import { useState } from "react";
 import _ from "underscore";
+import { LegaleseStep } from "metabase/public/components/widgets/LegaleseStep/LegaleseStep";
 import { getSignedPreviewUrl, getSignedToken } from "metabase/public/lib/embed";
 import { getSetting } from "metabase/selectors/settings";
 import { useSelector } from "metabase/lib/redux";
@@ -15,14 +16,14 @@ import type {
   EmbedResource,
   EmbedResourceParameter,
   EmbedResourceType,
-  EmbedType,
+  EmbedModalStep,
 } from "../types";
 import { SelectEmbedTypePane } from "../SelectEmbedTypePane";
 import { EmbedModalContentStatusBar } from "./EmbedModalContentStatusBar";
 
 export interface EmbedModalContentProps {
-  embedType: EmbedType;
-  setEmbedType: (type: EmbedType) => void;
+  embedType: EmbedModalStep;
+  goToNextStep: () => void;
 
   resource: EmbedResource;
   resourceType: EmbedResourceType;
@@ -43,7 +44,7 @@ export const EmbedModalContent = (
 ): JSX.Element => {
   const {
     embedType,
-    setEmbedType,
+    goToNextStep,
     resource,
     resourceType,
     resourceParameters,
@@ -125,7 +126,7 @@ export const EmbedModalContent = (
     embeddingParams,
   );
 
-  if (!embedType) {
+  if (embedType == null) {
     return (
       <SelectEmbedTypePane
         resource={resource}
@@ -133,11 +134,15 @@ export const EmbedModalContent = (
         onCreatePublicLink={onCreatePublicLink}
         onDeletePublicLink={onDeletePublicLink}
         getPublicUrl={getPublicUrl}
-        onChangeEmbedType={setEmbedType}
+        goToNextStep={goToNextStep}
       />
     );
   }
 
+  if (embedType === "legalese") {
+    return <LegaleseStep goToNextStep={goToNextStep} />;
+  }
+
   const hasSettingsChanges = !_.isEqual(
     resource.embedding_params,
     embeddingParams,
diff --git a/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.unit.spec.tsx
index 608b7f0cd9a..2a92cb272fe 100644
--- a/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.unit.spec.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/EmbedModalContent/EmbedModalContent.unit.spec.tsx
@@ -149,7 +149,7 @@ function setup({
   resource = {} as EmbedResource,
   resourceType = "dashboard",
   resourceParameters = [],
-  setEmbedType = jest.fn(),
+  goToNextStep = jest.fn(),
   getPublicUrl = jest.fn(_resource => "some URL"),
   onUpdateEmbeddingParams = jest.fn(),
   onUpdateEnableEmbedding = jest.fn(),
@@ -159,7 +159,7 @@ function setup({
   const view = renderWithProviders(
     <EmbedModalContent
       embedType={embedType}
-      setEmbedType={setEmbedType}
+      goToNextStep={goToNextStep}
       resource={resource}
       resourceType={resourceType}
       resourceParameters={resourceParameters}
@@ -182,7 +182,7 @@ function setup({
 
   return {
     ...view,
-    setEmbedType,
+    goToNextStep,
     getPublicUrl,
     onUpdateEmbeddingParams,
     onUpdateEnableEmbedding,
diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.tsx
index 70f8acccdfc..768c7143a46 100644
--- a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.tsx
@@ -10,7 +10,7 @@ import * as MetabaseAnalytics from "metabase/lib/analytics";
 import Link from "metabase/core/components/Link";
 import type { ExportFormatType } from "metabase/dashboard/components/PublicLinkPopover/types";
 
-import type { EmbedResource, EmbedResourceType, EmbedType } from "../types";
+import type { EmbedResource, EmbedResourceType } from "../types";
 import { SharingPaneActionButton } from "./SharingPaneButton/SharingPaneButton.styled";
 import { SharingPaneButton } from "./SharingPaneButton/SharingPaneButton";
 import { PublicEmbedIcon, StaticEmbedIcon } from "./icons";
@@ -22,7 +22,7 @@ interface SelectEmbedTypePaneProps {
   onCreatePublicLink: () => void;
   onDeletePublicLink: () => void;
   getPublicUrl: (publicUuid: string, extension?: ExportFormatType) => string;
-  onChangeEmbedType: (embedType: EmbedType) => void;
+  goToNextStep: () => void;
 }
 
 export function SelectEmbedTypePane({
@@ -31,7 +31,7 @@ export function SelectEmbedTypePane({
   onCreatePublicLink,
   onDeletePublicLink,
   getPublicUrl,
-  onChangeEmbedType,
+  goToNextStep,
 }: SelectEmbedTypePaneProps) {
   const hasPublicLink = resource.public_uuid != null;
 
@@ -115,12 +115,11 @@ export function SelectEmbedTypePane({
           header={t`Static embed`}
           description={t`Securely embed this dashboard in your own application’s server code.`}
           illustration={<StaticEmbedIcon />}
-          onClick={() => onChangeEmbedType("application")}
+          onClick={goToNextStep}
         >
           <SharingPaneActionButton
             data-testid="sharing-pane-static-embed-button"
             fullWidth
-            onClick={() => onChangeEmbedType("application")}
           >
             {resource.enable_embedding ? t`Edit settings` : t`Set this up`}
           </SharingPaneActionButton>
diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.unit.spec.tsx
index 77ae7ab085b..938f2576df9 100644
--- a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.unit.spec.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/SelectEmbedTypePane.unit.spec.tsx
@@ -29,7 +29,7 @@ const setup = ({
   const onCreatePublicLink = jest.fn();
   const onDeletePublicLink = jest.fn();
   const getPublicUrl = jest.fn(uuid => uuid);
-  const onChangeEmbedType = jest.fn();
+  const goToNextStep = jest.fn();
 
   const { history } = renderWithProviders(
     <Route
@@ -41,7 +41,7 @@ const setup = ({
           onCreatePublicLink={onCreatePublicLink}
           onDeletePublicLink={onDeletePublicLink}
           getPublicUrl={getPublicUrl}
-          onChangeEmbedType={onChangeEmbedType}
+          goToNextStep={goToNextStep}
         />
       )}
     ></Route>,
@@ -58,7 +58,7 @@ const setup = ({
   );
 
   return {
-    onChangeEmbedType,
+    goToNextStep,
     onCreatePublicLink,
     onDeletePublicLink,
     getPublicUrl,
@@ -77,12 +77,12 @@ describe("SelectEmbedTypePane", () => {
         ).toBeInTheDocument();
       });
 
-      it("should call `onChangeEmbedType` with `application` when `Edit settings` is clicked", () => {
-        const { onChangeEmbedType } = setup({ isResourcePublished: true });
+      it("should call `goToNextStep` with `application` when `Edit settings` is clicked", () => {
+        const { goToNextStep } = setup({ isResourcePublished: true });
 
         userEvent.click(screen.getByRole("button", { name: "Edit settings" }));
 
-        expect(onChangeEmbedType).toHaveBeenCalledWith("application");
+        expect(goToNextStep).toHaveBeenCalled();
       });
     });
 
@@ -95,12 +95,12 @@ describe("SelectEmbedTypePane", () => {
         ).toBeInTheDocument();
       });
 
-      it("should call `onChangeEmbedType` with `application` when `Set this up` is clicked", () => {
-        const { onChangeEmbedType } = setup({ isResourcePublished: false });
+      it("should call `goToNextStep` with `application` when `Set this up` is clicked", () => {
+        const { goToNextStep } = setup({ isResourcePublished: false });
 
         userEvent.click(screen.getByRole("button", { name: "Set this up" }));
 
-        expect(onChangeEmbedType).toHaveBeenCalledWith("application");
+        expect(goToNextStep).toHaveBeenCalled();
       });
     });
   });
diff --git a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/AppearanceSettings.tsx b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/AppearanceSettings.tsx
index db1318fba19..bc7c3e46491 100644
--- a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/AppearanceSettings.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/AppearanceSettings.tsx
@@ -15,7 +15,7 @@ import type {
   EmbeddingParameters,
   EmbedResource,
   EmbedResourceType,
-  EmbedType,
+  EmbedModalStep,
 } from "../types";
 import EmbedCodePane from "./EmbedCodePane";
 import PreviewPane from "./PreviewPane";
@@ -35,7 +35,7 @@ const DEFAULT_THEME = THEME_OPTIONS[0].value;
 export interface AppearanceSettingsProps {
   activePane: ActivePreviewPane;
 
-  embedType: EmbedType;
+  embedType: EmbedModalStep;
   resource: EmbedResource;
   resourceType: EmbedResourceType;
   iframeUrl: string;
diff --git a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/OverviewSettings.tsx b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/OverviewSettings.tsx
index 529dd31e53d..06c8afa3ebf 100644
--- a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/OverviewSettings.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/OverviewSettings.tsx
@@ -8,7 +8,7 @@ import type {
   EmbeddingParameters,
   EmbedResource,
   EmbedResourceType,
-  EmbedType,
+  EmbedModalStep,
   EmbeddingDisplayOptions,
 } from "../types";
 import EmbedCodePane from "./EmbedCodePane";
@@ -16,7 +16,7 @@ import { SettingsTabLayout } from "./StaticEmbedSetupPane.styled";
 import { StaticEmbedSetupPaneSettingsContentSection } from "./StaticEmbedSetupPaneSettingsContentSection";
 
 export interface OverviewSettingsProps {
-  embedType: EmbedType;
+  embedType: EmbedModalStep;
   resource: EmbedResource;
   resourceType: EmbedResourceType;
   iframeUrl: string;
diff --git a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/ParametersSettings.tsx b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/ParametersSettings.tsx
index 0a28be27eae..41b9d1f3d08 100644
--- a/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/ParametersSettings.tsx
+++ b/frontend/src/metabase/public/components/EmbedModal/StaticEmbedSetupPane/ParametersSettings.tsx
@@ -16,7 +16,7 @@ import type {
   EmbeddingParametersValues,
   EmbeddingDisplayOptions,
   EmbedResource,
-  EmbedType,
+  EmbedModalStep,
   EmbedResourceParameterWithValue,
 } from "../types";
 import EmbedCodePane from "./EmbedCodePane";
@@ -35,7 +35,7 @@ export interface ParametersSettingsProps {
   lockedParameters: EmbedResourceParameter[];
   parameterValues: EmbeddingParametersValues;
 
-  embedType: EmbedType;
+  embedType: EmbedModalStep;
 
   iframeUrl: string;
   token: string;
diff --git a/frontend/src/metabase/public/components/EmbedModal/types.ts b/frontend/src/metabase/public/components/EmbedModal/types.ts
index 8e40ae20cfd..98c6dece3ba 100644
--- a/frontend/src/metabase/public/components/EmbedModal/types.ts
+++ b/frontend/src/metabase/public/components/EmbedModal/types.ts
@@ -2,7 +2,7 @@ import type { Card, Dashboard } from "metabase-types/api";
 
 export type ActivePreviewPane = "preview" | "code";
 
-export type EmbedType = "application" | null;
+export type EmbedModalStep = "application" | "legalese" | null;
 
 export type EmbedResource = (Card | Dashboard) & {
   embedding_params?: EmbeddingParameters | null;
diff --git a/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.styled.tsx b/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.styled.tsx
new file mode 100644
index 00000000000..0465b5c76e4
--- /dev/null
+++ b/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.styled.tsx
@@ -0,0 +1,10 @@
+import { css } from "@emotion/react";
+import styled from "@emotion/styled";
+import { Stack } from "metabase/ui";
+
+export const LegaleseStepDetailsContainer = styled(Stack)`
+  ${({ theme }) => css`
+    border: 1px solid ${theme.colors.border[0]};
+    border-radius: ${theme.radius.md};
+  `}
+`;
diff --git a/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.tsx b/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.tsx
new file mode 100644
index 00000000000..ce01436ed15
--- /dev/null
+++ b/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.tsx
@@ -0,0 +1,58 @@
+import { jt, t } from "ttag";
+import { updateSetting } from "metabase/admin/settings/settings";
+import { useDispatch } from "metabase/lib/redux";
+import ExternalLink from "metabase/core/components/ExternalLink";
+import { LegaleseStepDetailsContainer } from "metabase/public/components/widgets/LegaleseStep/LegaleseStep.styled";
+import { Text, Button, Center, Stack, Title } from "metabase/ui";
+
+export const LegaleseStep = ({
+  goToNextStep,
+}: {
+  goToNextStep: () => void;
+}) => {
+  const dispatch = useDispatch();
+
+  const onAcceptTerms = () => {
+    dispatch(
+      updateSetting({
+        key: "show-static-embed-terms",
+        value: false,
+      }),
+    );
+    goToNextStep();
+  };
+
+  return (
+    <Center bg="white" px="18rem" pt="6.25rem" pb="11.75rem">
+      <Stack align="center" spacing="3rem">
+        <Title order={2} fz="1.25rem">{t`First, some legalese`}</Title>
+
+        <LegaleseStepDetailsContainer p="lg" w="40rem">
+          <Text fw={700}>
+            {jt`By clicking "Agree and continue" you're agreeing to ${(
+              <ExternalLink
+                key="embed-license-link"
+                href="https://metabase.com/license/embedding"
+                target="_blank"
+              >
+                {t`our embedding license.`}
+              </ExternalLink>
+            )}`}
+          </Text>
+          <Text>
+            {t`When you embed charts or dashboards from Metabase in your own application that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the "Powered by Metabase" visible on those embeds.`}
+          </Text>
+          <Text>
+            {t`You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature.`}
+          </Text>
+        </LegaleseStepDetailsContainer>
+
+        <Button
+          variant="filled"
+          onClick={onAcceptTerms}
+          data-testid="accept-legalese-terms-button"
+        >{t`Agree and continue`}</Button>
+      </Stack>
+    </Center>
+  );
+};
diff --git a/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.unit.spec.tsx b/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.unit.spec.tsx
new file mode 100644
index 00000000000..c6b30048a57
--- /dev/null
+++ b/frontend/src/metabase/public/components/widgets/LegaleseStep/LegaleseStep.unit.spec.tsx
@@ -0,0 +1,67 @@
+import userEvent from "@testing-library/user-event";
+import fetchMock from "fetch-mock";
+import {
+  setupPropertiesEndpoints,
+  setupSettingsEndpoints,
+} from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import {
+  createMockSettingDefinition,
+  createMockSettings,
+} from "metabase-types/api/mocks";
+import { LegaleseStep } from "./LegaleseStep";
+
+const setup = () => {
+  const goToNextStep = jest.fn();
+
+  setupSettingsEndpoints([
+    createMockSettingDefinition({
+      key: "show-static-embed-terms",
+      value: true,
+    }),
+  ]);
+
+  setupPropertiesEndpoints(createMockSettings());
+
+  renderWithProviders(<LegaleseStep goToNextStep={goToNextStep} />);
+
+  return { goToNextStep };
+};
+
+describe("LegaleseStep", () => {
+  it("should render", () => {
+    setup();
+
+    expect(screen.getByText("First, some legalese")).toBeInTheDocument();
+    expect(
+      screen.getByText('By clicking "Agree and continue" you\'re agreeing to'),
+    ).toBeInTheDocument();
+    expect(
+      screen.getByText(
+        `When you embed charts or dashboards from Metabase in your own application that application isn't subject to the Affero General Public License that covers the rest of Metabase, provided you keep the Metabase logo and the "Powered by Metabase" visible on those embeds.`,
+      ),
+    ).toBeInTheDocument();
+    expect(
+      screen.getByText(
+        "You should, however, read the license text linked above as that is the actual license that you will be agreeing to by enabling this feature.",
+      ),
+    ).toBeInTheDocument();
+    expect(screen.getByText("Agree and continue")).toBeInTheDocument();
+  });
+
+  it("calls goToNextStep and updates setting on clicking 'Agree and continue'", async () => {
+    const settingPutCalls = fetchMock.put(
+      "path:/api/setting/show-static-embed-terms",
+      {},
+    );
+
+    const { goToNextStep } = setup();
+    userEvent.click(screen.getByText("Agree and continue"));
+
+    expect(settingPutCalls.calls().length).toBe(1);
+    expect(await settingPutCalls.lastCall()?.request?.json()).toEqual({
+      value: false,
+    });
+    expect(goToNextStep).toHaveBeenCalled();
+  });
+});
diff --git a/frontend/src/metabase/query_builder/components/QuestionEmbedWidget/QuestionEmbedWidget.tsx b/frontend/src/metabase/query_builder/components/QuestionEmbedWidget/QuestionEmbedWidget.tsx
index 9d7406be4cf..e4c23a50ef4 100644
--- a/frontend/src/metabase/query_builder/components/QuestionEmbedWidget/QuestionEmbedWidget.tsx
+++ b/frontend/src/metabase/query_builder/components/QuestionEmbedWidget/QuestionEmbedWidget.tsx
@@ -42,10 +42,10 @@ export const QuestionEmbedWidget = (props: QuestionEmbedWidgetProps) => {
 
   return (
     <EmbedModal onClose={onClose}>
-      {({ embedType, setEmbedType }) => (
+      {({ embedType, goToNextStep }) => (
         <EmbedModalContent
           embedType={embedType}
-          setEmbedType={setEmbedType}
+          goToNextStep={goToNextStep}
           className={className}
           resource={card}
           resourceType="question"
diff --git a/src/metabase/util/embed.clj b/src/metabase/util/embed.clj
index 701c7f02d3c..75dd068fd1a 100644
--- a/src/metabase/util/embed.clj
+++ b/src/metabase/util/embed.clj
@@ -6,8 +6,10 @@
    [cheshire.core :as json]
    [clojure.string :as str]
    [hiccup.core :refer [html]]
-   [metabase.models.setting :as setting]
+   [metabase.config :as config]
+   [metabase.models.setting :as setting :refer [defsetting]]
    [metabase.public-settings :as public-settings]
+   [metabase.public-settings.premium-features :as premium-features]
    [metabase.util :as u]
    [metabase.util.i18n :refer [deferred-tru trs tru]]
    [ring.util.codec :as codec]))
@@ -55,7 +57,7 @@
 
 ;;; ----------------------------------------------- EMBEDDING UTIL FNS -----------------------------------------------
 
-(setting/defsetting embedding-secret-key
+(defsetting embedding-secret-key
   (deferred-tru "Secret key used to sign JSON Web Tokens for requests to `/api/embed` endpoints.")
   :visibility :admin
   :audit :no-value
@@ -105,3 +107,13 @@
   [unsigned-token keyseq]
   (or (get-in unsigned-token keyseq)
       (throw (ex-info (tru "Token is missing value for keypath {0}" keyseq) {:status-code 400}))))
+
+(defsetting show-static-embed-terms
+  (deferred-tru "Check if the static embedding licensing should be hidden in the static embedding flow")
+  :type    :boolean
+  :default true
+  :export? true
+  :getter  (fn []
+              (if-not (and config/ee-available? (:valid (premium-features/token-status)))
+                (setting/get-value-of-type :boolean :show-static-embed-terms)
+                false)))
diff --git a/test/metabase/util/embed_test.clj b/test/metabase/util/embed_test.clj
index 5e26ada1bc6..19a5648cbc3 100644
--- a/test/metabase/util/embed_test.clj
+++ b/test/metabase/util/embed_test.clj
@@ -3,6 +3,10 @@
    [buddy.sign.jwt :as jwt]
    [clojure.test :refer :all]
    [crypto.random :as crypto-random]
+   [metabase.config :as config]
+   [metabase.public-settings.premium-features :as premium-features]
+   [metabase.public-settings.premium-features-test
+    :as premium-features-test]
    [metabase.test :as mt]
    [metabase.util.embed :as embed]))
 
@@ -21,3 +25,32 @@
            clojure.lang.ExceptionInfo
            #"JWT `alg` cannot be `none`"
            (embed/unsign token-with-alg-none))))))
+
+(deftest show-static-embed-terms-test
+  (mt/with-test-user :crowberto
+    (mt/with-temporary-setting-values [show-static-embed-terms nil]
+      (testing "Check if the user needs to accept the embedding licensing terms before static embedding"
+        (when-not config/ee-available?
+          (testing "should return true when user is OSS and has not accepted licensing terms"
+            (is (= (embed/show-static-embed-terms) true)))
+          (testing "should return false when user is OSS and has already accepted licensing terms"
+            (embed/show-static-embed-terms! false)
+            (is (= (embed/show-static-embed-terms) false))))
+        (when config/ee-available?
+          (testing "should return false when an EE user has a valid token"
+           (with-redefs [premium-features/fetch-token-status (fn [_x]
+                                                               {:valid    true
+                                                                :status   "fake"
+                                                                :features ["test" "fixture"]
+                                                                :trial    false})]
+             (mt/with-temporary-setting-values [premium-embedding-token premium-features-test/random-fake-token]
+              (is (= (embed/show-static-embed-terms) false))
+              (embed/show-static-embed-terms! false)
+              (is (= (embed/show-static-embed-terms) false)))))
+          (testing "when an EE user doesn't have a valid token"
+            (mt/with-temporary-setting-values [premium-embedding-token nil show-static-embed-terms nil]
+              (testing "should return true when the user has not accepted licensing terms"
+                (is (= (embed/show-static-embed-terms) true)))
+              (testing "should return false when the user has already accepted licensing terms"
+                (embed/show-static-embed-terms! false)
+                (is (= (embed/show-static-embed-terms) false))))))))))
-- 
GitLab