From 971eca59ff9071fe8726fa1855283cf47e162a45 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 13 Mar 2023 18:40:16 -0400 Subject: [PATCH 1/3] Opt into analytics by default during the beta --- public/locales/en-GB/app.json | 4 ++-- src/analytics/AnalyticsNotice.tsx | 14 ++++++++++++++ src/analytics/AnalyticsOptInDescription.tsx | 20 -------------------- src/form/Form.tsx | 4 ++-- src/home/RegisteredView.module.css | 4 ++++ src/home/RegisteredView.tsx | 20 ++++++++------------ src/home/UnauthenticatedView.module.css | 4 ++++ src/home/UnauthenticatedView.tsx | 20 ++++++++------------ src/room/RoomPage.tsx | 7 +++++++ src/settings/SettingsModal.module.css | 4 ++-- src/settings/SettingsModal.tsx | 19 +++++++++++++++---- src/settings/useSetting.ts | 8 +++++++- src/tabs/Tabs.module.css | 4 +++- 13 files changed, 76 insertions(+), 56 deletions(-) create mode 100644 src/analytics/AnalyticsNotice.tsx delete mode 100644 src/analytics/AnalyticsOptInDescription.tsx diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index fe52c85f..389eeb70 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -8,6 +8,7 @@ "{{name}} is talking…": "{{name}} is talking…", "{{names}}, {{name}}": "{{names}}, {{name}}", "{{roomName}} - Walkie-talkie call": "{{roomName}} - Walkie-talkie call", + "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "<0>Already have an account?<1><0>Log in Or <2>Access as a guest": "<0>Already have an account?<1><0>Log in Or <2>Access as a guest", "<0>Create an account Or <2>Access as a guest": "<0>Create an account Or <2>Access as a guest", "<0>Join call now<1>Or<2>Copy call link and join later": "<0>Join call now<1>Or<2>Copy call link and join later", @@ -21,7 +22,7 @@ "Avatar": "Avatar", "By clicking \"Go\", you agree to our <2>Terms and conditions": "By clicking \"Go\", you agree to our <2>Terms and conditions", "By clicking \"Join call now\", you agree to our <2>Terms and conditions": "By clicking \"Join call now\", you agree to our <2>Terms and conditions", - "By ticking this box you consent to the collection of anonymous data, which we use to improve your experience. You can find more information about which data we track in our ": "By ticking this box you consent to the collection of anonymous data, which we use to improve your experience. You can find more information about which data we track in our ", + "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy and our <5>Cookie Policy.": "By participating in this beta, you consent to the collection of anonymous data, which we use to improve the product. You can find more information about which data we track in our <2>Privacy Policy and our <5>Cookie Policy.", "Call link copied": "Call link copied", "Call type menu": "Call type menu", "Camera": "Camera", @@ -85,7 +86,6 @@ "Press and hold spacebar to talk over {{name}}": "Press and hold spacebar to talk over {{name}}", "Press and hold to talk": "Press and hold to talk", "Press and hold to talk over {{name}}": "Press and hold to talk over {{name}}", - "Privacy Policy": "Privacy Policy", "Profile": "Profile", "Recaptcha dismissed": "Recaptcha dismissed", "Recaptcha not loaded": "Recaptcha not loaded", diff --git a/src/analytics/AnalyticsNotice.tsx b/src/analytics/AnalyticsNotice.tsx new file mode 100644 index 00000000..feceef76 --- /dev/null +++ b/src/analytics/AnalyticsNotice.tsx @@ -0,0 +1,14 @@ +import React, { FC } from "react"; +import { Trans } from "react-i18next"; + +import { Link } from "../typography/Typography"; + +export const AnalyticsNotice: FC = () => ( + + By participating in this beta, you consent to the collection of anonymous + data, which we use to improve the product. You can find more information + about which data we track in our{" "} + Privacy Policy and our{" "} + Cookie Policy. + +); diff --git a/src/analytics/AnalyticsOptInDescription.tsx b/src/analytics/AnalyticsOptInDescription.tsx deleted file mode 100644 index 46727f5f..00000000 --- a/src/analytics/AnalyticsOptInDescription.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { t } from "i18next"; -import React from "react"; - -import { Link } from "../typography/Typography"; - -export const optInDescription: () => JSX.Element = () => { - return ( - <> - <> - {t( - "By ticking this box you consent to the collection of anonymous data, which we use to improve your experience. You can find more information about which data we track in our " - )} - - - <>{t("Privacy Policy")} - - . - - ); -}; diff --git a/src/form/Form.tsx b/src/form/Form.tsx index beea4c86..fd98a62a 100644 --- a/src/form/Form.tsx +++ b/src/form/Form.tsx @@ -15,14 +15,14 @@ limitations under the License. */ import classNames from "classnames"; -import React, { FormEventHandler, forwardRef } from "react"; +import React, { FormEventHandler, forwardRef, ReactNode } from "react"; import styles from "./Form.module.css"; interface FormProps { className: string; onSubmit: FormEventHandler; - children: JSX.Element[]; + children: ReactNode[]; } export const Form = forwardRef( diff --git a/src/home/RegisteredView.module.css b/src/home/RegisteredView.module.css index ae43b5bc..a96bd53b 100644 --- a/src/home/RegisteredView.module.css +++ b/src/home/RegisteredView.module.css @@ -37,3 +37,7 @@ limitations under the License. .recentCallsTitle { margin-bottom: 32px; } + +.notice { + color: var(--secondary-content); +} diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index a6278c2e..06a6720c 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -39,11 +39,11 @@ import { CallList } from "./CallList"; import { UserMenuContainer } from "../UserMenuContainer"; import { useModalTriggerState } from "../Modal"; import { JoinExistingCallModal } from "./JoinExistingCallModal"; -import { Title } from "../typography/Typography"; +import { Caption, Title } from "../typography/Typography"; import { Form } from "../form/Form"; import { CallType, CallTypeDropdown } from "./CallTypeDropdown"; import { useOptInAnalytics } from "../settings/useSetting"; -import { optInDescription } from "../analytics/AnalyticsOptInDescription"; +import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; interface Props { client: MatrixClient; @@ -54,7 +54,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) { const [callType, setCallType] = useState(CallType.Video); const [loading, setLoading] = useState(false); const [error, setError] = useState(); - const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); + const [optInAnalytics] = useOptInAnalytics(); const history = useHistory(); const { t } = useTranslation(); const { modalState, modalProps } = useModalTriggerState(); @@ -144,15 +144,11 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) { {loading ? t("Loading…") : t("Go")} - ) => - setOptInAnalytics(event.target.checked) - } - /> + {optInAnalytics === null && ( + + + + )} {error && ( diff --git a/src/home/UnauthenticatedView.module.css b/src/home/UnauthenticatedView.module.css index bad173ea..49c272bf 100644 --- a/src/home/UnauthenticatedView.module.css +++ b/src/home/UnauthenticatedView.module.css @@ -45,3 +45,7 @@ limitations under the License. display: none; } } + +.notice { + color: var(--secondary-content); +} diff --git a/src/home/UnauthenticatedView.tsx b/src/home/UnauthenticatedView.tsx index 6339e42d..c31637b6 100644 --- a/src/home/UnauthenticatedView.tsx +++ b/src/home/UnauthenticatedView.tsx @@ -39,15 +39,15 @@ import { CallType, CallTypeDropdown } from "./CallTypeDropdown"; import styles from "./UnauthenticatedView.module.css"; import commonStyles from "./common.module.css"; import { generateRandomName } from "../auth/generateRandomName"; +import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { useOptInAnalytics } from "../settings/useSetting"; -import { optInDescription } from "../analytics/AnalyticsOptInDescription"; export const UnauthenticatedView: FC = () => { const { setClient } = useClient(); const [callType, setCallType] = useState(CallType.Video); const [loading, setLoading] = useState(false); const [error, setError] = useState(); - const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); + const [optInAnalytics] = useOptInAnalytics(); const [privacyPolicyUrl, recaptchaKey, register] = useInteractiveRegistration(); const { execute, reset, recaptchaId } = useRecaptcha(recaptchaKey); @@ -155,16 +155,12 @@ export const UnauthenticatedView: FC = () => { autoComplete="off" /> - ) => - setOptInAnalytics(event.target.checked) - } - /> - + {optInAnalytics === null && ( + + + + )} + By clicking "Go", you agree to our{" "} Terms and conditions diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index f4e460d7..fe918617 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -27,6 +27,7 @@ import { useUrlParams } from "../UrlParams"; import { MediaHandlerProvider } from "../settings/useMediaHandler"; import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser"; import { translatedError } from "../TranslatedError"; +import { useOptInAnalytics } from "../settings/useSetting"; export const RoomPage: FC = () => { const { t } = useTranslation(); @@ -46,9 +47,15 @@ export const RoomPage: FC = () => { const roomIdOrAlias = roomId ?? roomAlias; if (!roomIdOrAlias) throw translatedError("No room specified", t); + const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics(); const { registerPasswordlessUser } = useRegisterPasswordlessUser(); const [isRegistering, setIsRegistering] = useState(false); + useEffect(() => { + // During the beta, opt into analytics by default + if (optInAnalytics === null) setOptInAnalytics(true); + }, [optInAnalytics, setOptInAnalytics]); + useEffect(() => { // If we've finished loading, are not already authed and we've been given a display name as // a URL param, automatically register a passwordless user diff --git a/src/settings/SettingsModal.module.css b/src/settings/SettingsModal.module.css index 9b4951b4..1e44dad7 100644 --- a/src/settings/SettingsModal.module.css +++ b/src/settings/SettingsModal.module.css @@ -20,7 +20,7 @@ limitations under the License. } .tabContainer { - margin: 27px 16px; + padding: 27px 20px; } .fieldRowText { @@ -33,5 +33,5 @@ The "Developer" item in the tab bar can be toggled. Without a defined width activating the developer tab makes the tab container jump to the right. */ .tabLabel { - width: 80px; + min-width: 80px; } diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 90a1cb5f..28808315 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from "react"; import { Item } from "@react-stately/collections"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { Modal } from "../Modal"; import styles from "./SettingsModal.module.css"; @@ -39,8 +39,8 @@ import { import { FieldRow, InputField } from "../input/Input"; import { Button } from "../button"; import { useDownloadDebugLog } from "./submit-rageshake"; -import { Body } from "../typography/Typography"; -import { optInDescription } from "../analytics/AnalyticsOptInDescription"; +import { Body, Caption } from "../typography/Typography"; +import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; interface Props { isOpen: boolean; @@ -71,6 +71,17 @@ export const SettingsModal = (props: Props) => { const downloadDebugLog = useDownloadDebugLog(); + const optInDescription = ( + + + +
+ You may withdraw consent by unchecking this box. If you are currently in + a call, this setting will take effect at the end of the call. +
+ + ); + return ( { id="optInAnalytics" type="checkbox" checked={optInAnalytics} - description={optInDescription()} + description={optInDescription} onChange={(event: React.ChangeEvent) => setOptInAnalytics(event.target.checked) } diff --git a/src/settings/useSetting.ts b/src/settings/useSetting.ts index 756ac74b..0fe5fe24 100644 --- a/src/settings/useSetting.ts +++ b/src/settings/useSetting.ts @@ -87,9 +87,15 @@ export const useSpatialAudio = (): [boolean, (val: boolean) => void] => { }; export const useShowInspector = () => useSetting("show-inspector", false); -export const useOptInAnalytics = () => useSetting("opt-in-analytics", false); + +// null = undecided +export const useOptInAnalytics = () => + useSetting("opt-in-analytics", null); + export const useKeyboardShortcuts = () => useSetting("keyboard-shortcuts", true); + export const useNewGrid = () => useSetting("new-grid", false); + export const useDeveloperSettingsTab = () => useSetting("developer-settings-tab", false); diff --git a/src/tabs/Tabs.module.css b/src/tabs/Tabs.module.css index bad7a0e6..188747cc 100644 --- a/src/tabs/Tabs.module.css +++ b/src/tabs/Tabs.module.css @@ -88,7 +88,9 @@ limitations under the License. .tabContainer { width: 100%; flex-direction: row; - margin: 27px 16px; + padding: 27px 20px; + box-sizing: border-box; + overflow: hidden; } .tabList { From 5f41f9476bc1827664974c2207b7da664a0d8620 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 23 Mar 2023 13:07:34 -0400 Subject: [PATCH 2/3] Disable the opt in analytics setting if Posthog isn't configured --- src/settings/SettingsModal.tsx | 11 +++++------ src/settings/useSetting.ts | 23 ++++++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 28808315..6e38b5fa 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -32,7 +32,6 @@ import { useSpatialAudio, useShowInspector, useOptInAnalytics, - canEnableSpatialAudio, useNewGrid, useDeveloperSettingsTab, } from "./useSetting"; @@ -133,16 +132,16 @@ export const SettingsModal = (props: Props) => { label={t("Spatial audio")} type="checkbox" checked={spatialAudio} - disabled={!canEnableSpatialAudio()} + disabled={setSpatialAudio === null} description={ - canEnableSpatialAudio() - ? t( + setSpatialAudio === null + ? t("This feature is only supported on Firefox.") + : t( "This will make a speaker's audio seem as if it is coming from where their tile is positioned on screen. (Experimental feature: this may impact the stability of audio.)" ) - : t("This feature is only supported on Firefox.") } onChange={(event: React.ChangeEvent) => - setSpatialAudio(event.target.checked) + setSpatialAudio!(event.target.checked) } /> diff --git a/src/settings/useSetting.ts b/src/settings/useSetting.ts index 0fe5fe24..13288b64 100644 --- a/src/settings/useSetting.ts +++ b/src/settings/useSetting.ts @@ -16,6 +16,10 @@ limitations under the License. import { EventEmitter } from "events"; import { useMemo, useState, useEffect, useCallback } from "react"; +import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; + +type Setting = [T, (value: T) => void]; +type DisableableSetting = [T, ((value: T) => void) | null]; // Bus to notify other useSetting consumers when a setting is changed export const settingsBus = new EventEmitter(); @@ -24,10 +28,7 @@ const getSettingKey = (name: string): string => { return `matrix-setting-${name}`; }; // Like useState, but reads from and persists the value to localStorage -const useSetting = ( - name: string, - defaultValue: T -): [T, (value: T) => void] => { +const useSetting = (name: string, defaultValue: T): Setting => { const key = useMemo(() => getSettingKey(name), [name]); const [value, setValue] = useState(() => { @@ -65,7 +66,7 @@ export const setSetting = (name: string, newValue: T) => { settingsBus.emit(name, newValue); }; -export const canEnableSpatialAudio = () => { +const canEnableSpatialAudio = () => { const { userAgent } = navigator; // Spatial audio means routing audio through audio contexts. On Chrome, // this bypasses the AEC processor and so breaks echo cancellation. @@ -79,18 +80,22 @@ export const canEnableSpatialAudio = () => { return userAgent.includes("Firefox"); }; -export const useSpatialAudio = (): [boolean, (val: boolean) => void] => { +export const useSpatialAudio = (): DisableableSetting => { const settingVal = useSetting("spatial-audio", false); if (canEnableSpatialAudio()) return settingVal; - return [false, (_: boolean) => {}]; + return [false, null]; }; export const useShowInspector = () => useSetting("show-inspector", false); // null = undecided -export const useOptInAnalytics = () => - useSetting("opt-in-analytics", null); +export const useOptInAnalytics = (): DisableableSetting => { + const settingVal = useSetting("opt-in-analytics", null); + if (PosthogAnalytics.instance.isEnabled()) return settingVal; + + return [false, null]; +}; export const useKeyboardShortcuts = () => useSetting("keyboard-shortcuts", true); From c4f029ae4f831d389d74139adc30d84bece6ddba Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 27 Mar 2023 22:30:12 -0400 Subject: [PATCH 3/3] Fix lint error --- src/settings/useSetting.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings/useSetting.ts b/src/settings/useSetting.ts index 13288b64..f2a2bfcd 100644 --- a/src/settings/useSetting.ts +++ b/src/settings/useSetting.ts @@ -16,6 +16,7 @@ limitations under the License. import { EventEmitter } from "events"; import { useMemo, useState, useEffect, useCallback } from "react"; + import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; type Setting = [T, (value: T) => void];