Checkbox for analytics opt in & settings redesign (#934)

This commit is contained in:
Timo 2023-03-01 13:47:36 +01:00 committed by GitHub
commit 0423a494c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 154 additions and 65 deletions

View file

@ -36,7 +36,10 @@ import {
fallbackICEServerAllowed,
} from "./matrix-utils";
import { widget } from "./widget";
import { PosthogAnalytics, RegistrationType } from "./PosthogAnalytics";
import {
PosthogAnalytics,
RegistrationType,
} from "./analytics/PosthogAnalytics";
import { translatedError } from "./TranslatedError";
import { useEventTarget } from "./useEvents";
import { Config } from "./config/Config";

View file

@ -0,0 +1,20 @@
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 "
)}
</>
<Link color="primary" href="https://element.io/privacy">
<>{t("Privacy Policy")}</>
</Link>
.
</>
);
};

View file

@ -19,8 +19,8 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from "matrix-js-sdk";
import { Buffer } from "buffer";
import { widget } from "./widget";
import { getSetting, setSetting, settingsBus } from "./settings/useSetting";
import { widget } from "../widget";
import { getSetting, setSetting, settingsBus } from "../settings/useSetting";
import {
CallEndedTracker,
CallStartedTracker,
@ -30,8 +30,8 @@ import {
MuteMicrophoneTracker,
UndecryptableToDeviceEventTracker,
} from "./PosthogEvents";
import { Config } from "./config/Config";
import { getUrlParams } from "./UrlParams";
import { Config } from "../config/Config";
import { getUrlParams } from "../UrlParams";
/* Posthog analytics tracking.
*

View file

@ -25,7 +25,7 @@ import { Button } from "../button";
import styles from "./LoginPage.module.css";
import { useInteractiveLogin } from "./useInteractiveLogin";
import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config";
export const LoginPage: FC = () => {

View file

@ -38,7 +38,7 @@ import { LoadingView } from "../FullScreenView";
import { useRecaptcha } from "./useRecaptcha";
import { Caption, Link } from "../typography/Typography";
import { usePageTitle } from "../usePageTitle";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { Config } from "../config/Config";
export const RegisterPage: FC = () => {

View file

@ -42,6 +42,8 @@ import { JoinExistingCallModal } from "./JoinExistingCallModal";
import { Title } from "../typography/Typography";
import { Form } from "../form/Form";
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
import { useOptInAnalytics } from "../settings/useSetting";
import { optInDescription } from "../analytics/AnalyticsOptInDescription";
interface Props {
client: MatrixClient;
@ -52,6 +54,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
const [callType, setCallType] = useState(CallType.Video);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error>();
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
const history = useHistory();
const { t } = useTranslation();
const { modalState, modalProps } = useModalTriggerState();
@ -141,6 +144,15 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
{loading ? t("Loading…") : t("Go")}
</Button>
</FieldRow>
<InputField
id="optInAnalytics"
type="checkbox"
checked={optInAnalytics}
description={optInDescription()}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setOptInAnalytics(event.target.checked)
}
/>
{error && (
<FieldRow className={styles.fieldRow}>
<ErrorMessage error={error} />

View file

@ -39,12 +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 { 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<Error>();
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
const [privacyPolicyUrl, recaptchaKey, register] =
useInteractiveRegistration();
const { execute, reset, recaptchaId } = useRecaptcha(recaptchaKey);
@ -152,6 +155,15 @@ export const UnauthenticatedView: FC = () => {
autoComplete="off"
/>
</FieldRow>
<InputField
id="optInAnalytics"
type="checkbox"
checked={optInAnalytics}
description={optInDescription()}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setOptInAnalytics(event.target.checked)
}
/>
<Caption>
<Trans>
By clicking "Go", you agree to our{" "}

View file

@ -209,3 +209,7 @@ limitations under the License.
margin-left: 26px;
width: 100%; /* Ensure that it breaks onto the next row */
}
.description.noLabel {
margin-top: -20px; /* Ensures that there is no weired spacing if the checkbox doesn't have a label */
}

View file

@ -55,14 +55,14 @@ export function Field({ children, className }: FieldProps): JSX.Element {
}
interface InputFieldProps {
label: string;
label?: string;
type: string;
prefix?: string;
suffix?: string;
id?: string;
checked?: boolean;
className?: string;
description?: string;
description?: string | ReactNode;
disabled?: boolean;
required?: boolean;
// this is a hack. Those variables should be part of `HTMLAttributes<HTMLInputElement> | HTMLAttributes<HTMLTextAreaElement>`
@ -140,7 +140,14 @@ export const InputField = forwardRef<
</label>
{suffix && <span>{suffix}</span>}
{description && (
<p id={descriptionId} className={styles.description}>
<p
id={descriptionId}
className={
label
? styles.description
: classNames(styles.description, styles.noLabel)
}
>
{description}
</p>
)}

View file

@ -35,7 +35,7 @@ import { CallEvent } from "matrix-js-sdk/src/webrtc/call";
import styles from "./GroupCallInspector.module.css";
import { SelectInput } from "../input/SelectInput";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
interface InspectorContextState {
eventsByUserId?: { [userId: string]: SequenceDiagramMatrixEvent[] };

View file

@ -32,7 +32,7 @@ import { CallEndedView } from "./CallEndedView";
import { useRoomAvatar } from "./useRoomAvatar";
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
import { useLocationNavigation } from "../useLocationNavigation";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { useMediaHandler } from "../settings/useMediaHandler";
import { findDeviceByName, getDevices } from "../media-utils";

View file

@ -63,7 +63,7 @@ import {
import { useModalTriggerState } from "../Modal";
import { useAudioContext } from "../video-grid/useMediaStream";
import { useFullscreen } from "../video-grid/useFullscreen";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { widget, ElementWidgetActions } from "../widget";
import { useJoinRule } from "./useJoinRule";
import { useUrlParams } from "../UrlParams";

View file

@ -29,7 +29,7 @@ import { useTranslation } from "react-i18next";
import { IWidgetApiRequest } from "matrix-widget-api";
import { usePageUnload } from "./usePageUnload";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { TranslatedError, translatedError } from "../TranslatedError";
import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget";

View file

@ -26,3 +26,12 @@ limitations under the License.
.fieldRowText {
margin-bottom: 0;
}
/*
This style guarantees a fixed width of the tab bar in the settings window.
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;
}

View file

@ -34,11 +34,13 @@ import {
useOptInAnalytics,
canEnableSpatialAudio,
useNewGrid,
useDeveloperSettingsTab,
} from "./useSetting";
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";
interface Props {
isOpen: boolean;
@ -62,6 +64,8 @@ export const SettingsModal = (props: Props) => {
const [spatialAudio, setSpatialAudio] = useSpatialAudio();
const [showInspector, setShowInspector] = useShowInspector();
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
const [developerSettingsTab, setDeveloperSettingsTab] =
useDeveloperSettingsTab();
const [keyboardShortcuts, setKeyboardShortcuts] = useKeyboardShortcuts();
const [newGrid, setNewGrid] = useNewGrid();
@ -80,7 +84,7 @@ export const SettingsModal = (props: Props) => {
title={
<>
<AudioIcon width={16} height={16} />
<span>{t("Audio")}</span>
<span className={styles.tabLabel}>{t("Audio")}</span>
</>
}
>
@ -158,24 +162,11 @@ export const SettingsModal = (props: Props) => {
title={
<>
<OverflowIcon width={16} height={16} />
<span>{t("Advanced")}</span>
<span>{t("More")}</span>
</>
}
>
<FieldRow>
<InputField
id="optInAnalytics"
label={t("Allow analytics")}
type="checkbox"
checked={optInAnalytics}
description={t(
"This will send anonymised data (such as the duration of a call and the number of participants) to the Element Call team to help us optimise the application based on how it is used."
)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setOptInAnalytics(event.target.checked)
}
/>
</FieldRow>
<h4>Keyboard</h4>
<FieldRow>
<InputField
id="keyboardShortcuts"
@ -190,51 +181,79 @@ export const SettingsModal = (props: Props) => {
}
/>
</FieldRow>
</TabItem>
<TabItem
title={
<>
<DeveloperIcon width={16} height={16} />
<span>{t("Developer")}</span>
</>
}
>
<FieldRow>
<Body className={styles.fieldRowText}>
{t("Version: {{version}}", {
version: import.meta.env.VITE_APP_VERSION || "dev",
})}
</Body>
</FieldRow>
<h4>Analytics</h4>
<FieldRow>
<InputField
id="showInspector"
name="inspector"
label={t("Show call inspector")}
id="optInAnalytics"
type="checkbox"
checked={showInspector}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowInspector(e.target.checked)
checked={optInAnalytics}
description={optInDescription()}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setOptInAnalytics(event.target.checked)
}
/>
</FieldRow>
<FieldRow>
<InputField
id="newGrid"
label={t("Use the upcoming grid system")}
id="developerSettingsTab"
type="checkbox"
checked={newGrid}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNewGrid(e.target.checked)
checked={developerSettingsTab}
label={t("Developer Settings")}
description={t(
"Expose developer settings in the settings window."
)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setDeveloperSettingsTab(event.target.checked)
}
/>
</FieldRow>
<FieldRow>
<Button onPress={downloadDebugLog}>
{t("Download debug logs")}
</Button>
</FieldRow>
</TabItem>
{developerSettingsTab && (
<TabItem
title={
<>
<DeveloperIcon width={16} height={16} />
<span>{t("Developer")}</span>
</>
}
>
<FieldRow>
<Body className={styles.fieldRowText}>
{t("Version: {{version}}", {
version: import.meta.env.VITE_APP_VERSION || "dev",
})}
</Body>
</FieldRow>
<FieldRow>
<InputField
id="showInspector"
name="inspector"
label={t("Show call inspector")}
type="checkbox"
checked={showInspector}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowInspector(e.target.checked)
}
/>
</FieldRow>
<FieldRow>
<InputField
id="newGrid"
label={t("Use the upcoming grid system")}
type="checkbox"
checked={newGrid}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setNewGrid(e.target.checked)
}
/>
</FieldRow>
<FieldRow>
<Button onPress={downloadDebugLog}>
{t("Download debug logs")}
</Button>
</FieldRow>
</TabItem>
)}
</TabContainer>
</Modal>
);

View file

@ -91,3 +91,5 @@ export const useOptInAnalytics = () => useSetting("opt-in-analytics", false);
export const useKeyboardShortcuts = () =>
useSetting("keyboard-shortcuts", true);
export const useNewGrid = () => useSetting("new-grid", false);
export const useDeveloperSettingsTab = () =>
useSetting("developer-settings-tab", false);