diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86119b95a..70ad78cfc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,7 @@ jobs: PRODUCTION_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2 STAGING_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2 REVIEW_CLOUDFRONT_DISTRIBUTION_ID: E3267W09ZJHQG9 + REACT_APP_FOUNDATION_BUILD: ${{ github.repository_owner == 'microbit-foundation' }} steps: # Note: This workflow disables deployment steps and micro:bit branding installation on forks. @@ -35,7 +36,7 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: npm install --no-save @microbit-foundation/python-editor-v3-microbit@0.1.0-dev.198 @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.7.1 @microbit-foundation/circleci-npm-package-versioner@1 + - run: npm install --no-save @microbit-foundation/python-editor-v3-microbit@0.2.0-dev.18 @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.7.1 @microbit-foundation/circleci-npm-package-versioner@1 if: github.repository_owner == 'microbit-foundation' env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/lang/ui.ca.json b/lang/ui.ca.json index a94acbb42..d53287236 100644 --- a/lang/ui.ca.json +++ b/lang/ui.ca.json @@ -195,6 +195,10 @@ "defaultMessage": "Alguna cosa ha anat malament. Baixa el teu fitxer hexadecimal per mantenir-lo segur i, a continuació, actualitza la pàgina per tornar-la a carregar.", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "Copiat", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.en.json b/lang/ui.en.json index be2d13265..248c9d3b6 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -195,6 +195,10 @@ "defaultMessage": "Something went wrong. Download your hex file for safe keeping, then refresh the page to reload.", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "Copied", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.es-es.json b/lang/ui.es-es.json index e2f8c2907..0c702f70a 100644 --- a/lang/ui.es-es.json +++ b/lang/ui.es-es.json @@ -195,6 +195,10 @@ "defaultMessage": "Algo salió mal. Por seguridad, descarga tu archivo HEX y actualiza la página para recargar.", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "Copiado", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.fr.json b/lang/ui.fr.json index c0eb1d903..543c7abd9 100644 --- a/lang/ui.fr.json +++ b/lang/ui.fr.json @@ -195,6 +195,10 @@ "defaultMessage": "Une erreur est survenue. Téléchargez votre fichier hex pour ne pas le perdre, puis actualisez la page.", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "Copié", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.ja.json b/lang/ui.ja.json index acb27089f..774f88ef4 100644 --- a/lang/ui.ja.json +++ b/lang/ui.ja.json @@ -195,6 +195,10 @@ "defaultMessage": "問題が発生しました。hex ファイルをダウンロードして、ページをリロードしてください。", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "コピーしました", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.ko.json b/lang/ui.ko.json index cd45f9838..0f148421f 100644 --- a/lang/ui.ko.json +++ b/lang/ui.ko.json @@ -195,6 +195,10 @@ "defaultMessage": "오류가 발생했습니다. hex 파일을 다운로드해 작업물을 보호하고 새로 고침으로 페이지를 다시 불러오세요.", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "복사됨", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.nl.json b/lang/ui.nl.json index 61f866d70..52555630e 100644 --- a/lang/ui.nl.json +++ b/lang/ui.nl.json @@ -195,6 +195,10 @@ "defaultMessage": "Er is iets fout gegaan. Download je hexadecimale bestand voor een veilige bewaring, ververs daarna de pagina om te herladen.", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "Gekopieerd", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.zh-cn.json b/lang/ui.zh-cn.json index 43cc76358..8bd9b2c88 100644 --- a/lang/ui.zh-cn.json +++ b/lang/ui.zh-cn.json @@ -195,6 +195,10 @@ "defaultMessage": "出错了。下载您的 hex 文件以便安全保存,然后刷新页面来重新加载。", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "已复制", "description": "Text shown after copy to clipboard" diff --git a/lang/ui.zh-tw.json b/lang/ui.zh-tw.json index 840ab3e6d..8cb18f409 100644 --- a/lang/ui.zh-tw.json +++ b/lang/ui.zh-tw.json @@ -195,6 +195,10 @@ "defaultMessage": "發生錯誤。下載您的 HEX 檔案以便安全儲存,然後重新整理頁面來重新載入。", "description": "Text displayed when content fails to load" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, "copied": { "defaultMessage": "複製的", "description": "Text shown after copy to clipboard" diff --git a/package-lock.json b/package-lock.json index 7fc54aff8..c0d33676b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "mobile-drag-drop": "^2.3.0-rc.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-icons": "^4.2.0", + "react-icons": "^4.8.0", "react-intl": "^5.20.10", "vscode-jsonrpc": "^6.0.0", "vscode-languageserver-protocol": "^3.16.0", @@ -19743,9 +19743,9 @@ } }, "node_modules/react-icons": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz", - "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", "peerDependencies": { "react": "*" } @@ -38516,9 +38516,9 @@ } }, "react-icons": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz", - "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", "requires": {} }, "react-intl": { diff --git a/package.json b/package.json index cf8450668..d1b8b5f3e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "mobile-drag-drop": "^2.3.0-rc.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-icons": "^4.2.0", + "react-icons": "^4.8.0", "react-intl": "^5.20.10", "vscode-jsonrpc": "^6.0.0", "vscode-languageserver-protocol": "^3.16.0", diff --git a/public/index.html b/public/index.html index 24c52dba1..00ae47ae9 100644 --- a/public/index.html +++ b/public/index.html @@ -20,25 +20,15 @@ name="twitter:description" content="A Python Editor for the BBC micro:bit, built by the Micro:bit Educational Foundation and the global Python Community." /> - <% if (process.env.REACT_APP_GA_MEASUREMENT_ID && - (process.env.REACT_APP_STAGE === 'PRODUCTION' || process.env.REACT_APP_STAGE - === "STAGING")) { %> - + <% if (process.env.REACT_APP_FOUNDATION_BUILD === 'true') { %> + + <% } %> diff --git a/src/App.tsx b/src/App.tsx index a025e1d98..1e2187d5a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -62,6 +62,7 @@ const App = () => { }); const deployment = useDeployment(); + const { ConsentProvider } = deployment.compliance; return ( <> @@ -79,11 +80,13 @@ const App = () => { - - - - - + + + + + + + diff --git a/src/deployment/default/index.tsx b/src/deployment/default/index.tsx index 884a5df09..352736847 100644 --- a/src/deployment/default/index.tsx +++ b/src/deployment/default/index.tsx @@ -3,13 +3,31 @@ * * SPDX-License-Identifier: MIT */ -import { DeploymentConfig } from ".."; +import { createContext } from "react"; +import { CookieConsent, DeploymentConfig } from ".."; import { NullLogging } from "./logging"; import theme from "./theme"; +const stubConsentValue: CookieConsent = { + analytics: false, + functional: true, +}; +const stubConsentContext = createContext( + stubConsentValue +); + const defaultDeployment: DeploymentConfig = { chakraTheme: theme, logging: new NullLogging(), + compliance: { + ConsentProvider: ({ children }) => ( + + {children} + + ), + consentContext: stubConsentContext, + manageCookies: undefined, + }, }; export default defaultDeployment; diff --git a/src/deployment/index.ts b/src/deployment/index.ts index 61ba1edf0..03f8c7c06 100644 --- a/src/deployment/index.ts +++ b/src/deployment/index.ts @@ -3,24 +3,36 @@ * * SPDX-License-Identifier: MIT */ -import { ReactNode } from "react"; -import { IconType } from "react-icons/lib"; +import { ReactNode, useContext } from "react"; import { Logging } from "../logging/logging"; // This is configured via a webpack alias, defaulting to ./default import { default as d } from "theme-package"; export const deployment: DeploymentConfig = d; +export interface CookieConsent { + analytics: boolean; + functional: boolean; +} + export interface DeploymentConfig { squareLogo?: ReactNode; horizontalLogo?: ReactNode; - Compliance?: ({ - zIndex, - externalLinkIcon, - }: { - zIndex: number; - externalLinkIcon: IconType; - }) => JSX.Element; + compliance: { + /** + * A provider that will be used to wrap the app UI. + */ + ConsentProvider: (props: { children: ReactNode }) => JSX.Element; + /** + * Context that will be used to read the current consent value. + * The provider is not used directly. + */ + consentContext: React.Context; + /** + * Optional hook for the user to revisit cookie settings. + */ + manageCookies: (() => void) | undefined; + }; chakraTheme: any; @@ -35,3 +47,8 @@ export interface DeploymentConfig { export const useDeployment = (): DeploymentConfig => { return deployment; }; + +export const useCookieConsent = (): CookieConsent | undefined => { + const { compliance } = useDeployment(); + return useContext(compliance.consentContext); +}; diff --git a/src/e2e/app.ts b/src/e2e/app.ts index 5f0212b2b..02ba453df 100644 --- a/src/e2e/app.ts +++ b/src/e2e/app.ts @@ -126,10 +126,16 @@ export class App { value: "1", url: this.url, }); - // Don't show compliance notice. + // Don't show compliance notice for Foundation builds await page.setCookie({ name: "MBCC", - value: "1", + value: encodeURIComponent( + JSON.stringify({ + version: 1, + analytics: false, + functional: true, + }) + ), url: this.url, }); diff --git a/src/messages/ui.ca.json b/src/messages/ui.ca.json index 4144f03bd..6fcf67da5 100644 --- a/src/messages/ui.ca.json +++ b/src/messages/ui.ca.json @@ -411,6 +411,12 @@ "value": "Alguna cosa ha anat malament. Baixa el teu fitxer hexadecimal per mantenir-lo segur i, a continuació, actualitza la pàgina per tornar-la a carregar." } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index f855d5841..311634217 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -407,6 +407,12 @@ "value": "Something went wrong. Download your hex file for safe keeping, then refresh the page to reload." } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.es-es.json b/src/messages/ui.es-es.json index 8eafbb0b0..1588fa397 100644 --- a/src/messages/ui.es-es.json +++ b/src/messages/ui.es-es.json @@ -411,6 +411,12 @@ "value": "Algo salió mal. Por seguridad, descarga tu archivo HEX y actualiza la página para recargar." } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.fr.json b/src/messages/ui.fr.json index 9bd5b7145..df5a2e339 100644 --- a/src/messages/ui.fr.json +++ b/src/messages/ui.fr.json @@ -407,6 +407,12 @@ "value": "Une erreur est survenue. Téléchargez votre fichier hex pour ne pas le perdre, puis actualisez la page." } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.ja.json b/src/messages/ui.ja.json index 1b2138d94..536b66b2a 100644 --- a/src/messages/ui.ja.json +++ b/src/messages/ui.ja.json @@ -431,6 +431,12 @@ "value": "問題が発生しました。hex ファイルをダウンロードして、ページをリロードしてください。" } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.ko.json b/src/messages/ui.ko.json index 94077d95c..ade0f3fb2 100644 --- a/src/messages/ui.ko.json +++ b/src/messages/ui.ko.json @@ -415,6 +415,12 @@ "value": "오류가 발생했습니다. hex 파일을 다운로드해 작업물을 보호하고 새로 고침으로 페이지를 다시 불러오세요." } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.nl.json b/src/messages/ui.nl.json index 559feb68f..934206e71 100644 --- a/src/messages/ui.nl.json +++ b/src/messages/ui.nl.json @@ -419,6 +419,12 @@ "value": "Er is iets fout gegaan. Download je hexadecimale bestand voor een veilige bewaring, ververs daarna de pagina om te herladen." } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.zh-cn.json b/src/messages/ui.zh-cn.json index 82efc1329..b831daba9 100644 --- a/src/messages/ui.zh-cn.json +++ b/src/messages/ui.zh-cn.json @@ -407,6 +407,12 @@ "value": "出错了。下载您的 hex 文件以便安全保存,然后刷新页面来重新加载。" } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/messages/ui.zh-tw.json b/src/messages/ui.zh-tw.json index 1bca34cca..d622df00c 100644 --- a/src/messages/ui.zh-tw.json +++ b/src/messages/ui.zh-tw.json @@ -403,6 +403,12 @@ "value": "發生錯誤。下載您的 HEX 檔案以便安全儲存,然後重新整理頁面來重新載入。" } ], + "cookies-action": [ + { + "type": 0, + "value": "Cookies" + } + ], "copied": [ { "type": 0, diff --git a/src/workbench/HelpMenu.tsx b/src/workbench/HelpMenu.tsx index d1ce33fc7..521809c88 100644 --- a/src/workbench/HelpMenu.tsx +++ b/src/workbench/HelpMenu.tsx @@ -16,6 +16,7 @@ import { useDisclosure, } from "@chakra-ui/react"; import { useCallback, useRef } from "react"; +import { MdOutlineCookie } from "react-icons/md"; import { RiExternalLinkLine, RiFeedbackLine, @@ -25,7 +26,7 @@ import { import { FormattedMessage, useIntl } from "react-intl"; import { useDialogs } from "../common/use-dialogs"; import { zIndexAboveTerminal } from "../common/zIndex"; -import { deployment } from "../deployment"; +import { deployment, useDeployment } from "../deployment"; import AboutDialog from "./AboutDialog/AboutDialog"; import FeedbackForm from "./FeedbackForm"; @@ -49,6 +50,11 @@ const HelpMenu = ({ size, ...props }: HelpMenuProps) => { /> )); }, [dialogs]); + const { compliance } = useDeployment(); + const handleCookies = useCallback(() => { + // Only called if defined: + compliance.manageCookies!(); + }, [compliance]); const menuButtonRef = useRef(null); return ( <> @@ -106,7 +112,13 @@ const HelpMenu = ({ size, ...props }: HelpMenuProps) => { )} - + {deployment.compliance.manageCookies && ( + } onClick={handleCookies}> + + + )} + {(deployment.termsOfUseLink || + deployment.compliance.manageCookies) && } } onClick={aboutDialogDisclosure.onOpen} diff --git a/src/workbench/PreReleaseNotice.tsx b/src/workbench/PreReleaseNotice.tsx index 61a49b767..116209132 100644 --- a/src/workbench/PreReleaseNotice.tsx +++ b/src/workbench/PreReleaseNotice.tsx @@ -8,6 +8,7 @@ import { Flex, HStack, Text } from "@chakra-ui/layout"; import { useCallback, useEffect, useState } from "react"; import { RiFeedbackFill, RiInformationFill } from "react-icons/ri"; import { useStorage } from "../common/use-storage"; +import { useCookieConsent } from "../deployment"; import { flags } from "../flags"; export type ReleaseNoticeState = "info" | "feedback" | "closed"; @@ -39,13 +40,18 @@ export const useReleaseDialogState = (): [ ); const [releaseDialog, setReleaseDialog] = useState("closed"); - // Show the dialog on start-up once per user. + // Show the dialog on start-up once per user once we have cookie consent. + const cookieConsent = useCookieConsent(); useEffect(() => { - if (!flags.noWelcome && storedNotice.version < currentVersion) { + if ( + cookieConsent && + !flags.noWelcome && + storedNotice.version < currentVersion + ) { setReleaseDialog("info"); setStoredNotice({ version: currentVersion }); } - }, [storedNotice, setStoredNotice, setReleaseDialog]); + }, [cookieConsent, storedNotice, setStoredNotice, setReleaseDialog]); return [releaseDialog, setReleaseDialog]; }; diff --git a/src/workbench/Workbench.tsx b/src/workbench/Workbench.tsx index e3cc425c8..bd797d132 100644 --- a/src/workbench/Workbench.tsx +++ b/src/workbench/Workbench.tsx @@ -6,7 +6,6 @@ import { Box, Flex } from "@chakra-ui/layout"; import { useMediaQuery } from "@chakra-ui/react"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; -import { RiExternalLinkLine } from "react-icons/ri"; import { useIntl } from "react-intl"; import { hideSidebarMediaQuery, @@ -21,8 +20,6 @@ import { SplitViewSized, } from "../common/SplitView"; import { SizedMode } from "../common/SplitView/SplitView"; -import { zIndexAboveDialogs } from "../common/zIndex"; -import { useDeployment } from "../deployment"; import { ConnectionStatus } from "../device/device"; import { useConnectionStatus } from "../device/device-hooks"; import EditorArea from "../editor/EditorArea"; @@ -120,23 +117,9 @@ const Workbench = () => { )} ); - const inIframe = () => { - try { - return window.self !== window.top; - } catch (e) { - return true; - } - }; - const deployment = useDeployment(); - const Compliance = deployment.Compliance ?? (() => null); + return ( - {!inIframe() && ( - - )}