diff --git a/Backend/Models/User.cs b/Backend/Models/User.cs index b18a928732..10c12604cf 100644 --- a/Backend/Models/User.cs +++ b/Backend/Models/User.cs @@ -69,6 +69,9 @@ public class User [BsonElement("otelConsent")] public bool OtelConsent { get; set; } + [BsonElement("answeredConsent")] + public bool AnsweredConsent { get; set; } + [BsonElement("uiLang")] public string UILang { get; set; } @@ -101,6 +104,7 @@ public User() Password = ""; Username = ""; OtelConsent = false; + AnsweredConsent = false; UILang = ""; GlossSuggestion = AutocompleteSetting.On; Token = ""; @@ -124,6 +128,7 @@ public User Clone() Password = Password, Username = Username, OtelConsent = OtelConsent, + AnsweredConsent = AnsweredConsent, UILang = UILang, GlossSuggestion = GlossSuggestion, Token = Token, @@ -147,6 +152,7 @@ public bool ContentEquals(User other) other.Password.Equals(Password, StringComparison.Ordinal) && other.Username.Equals(Username, StringComparison.Ordinal) && other.OtelConsent == OtelConsent && + other.AnsweredConsent == AnsweredConsent && other.UILang.Equals(UILang, StringComparison.Ordinal) && other.GlossSuggestion.Equals(GlossSuggestion) && other.Token.Equals(Token, StringComparison.Ordinal) && @@ -185,6 +191,7 @@ public override int GetHashCode() hash.Add(Password); hash.Add(Username); hash.Add(OtelConsent); + hash.Add(AnsweredConsent); hash.Add(UILang); hash.Add(GlossSuggestion); hash.Add(Token); diff --git a/Backend/Otel/OtelKernel.cs b/Backend/Otel/OtelKernel.cs index 96b6a9900d..88725313df 100644 --- a/Backend/Otel/OtelKernel.cs +++ b/Backend/Otel/OtelKernel.cs @@ -108,7 +108,6 @@ public override async void OnEnd(Activity data) var consentString = data?.GetBaggageItem("otelConsentBaggage"); data?.AddTag("otelConsent", consentString); var consent = bool.TryParse(consentString, out bool value) ? value : false; - // Note: A bool value also would have worked for SetTag if (consent) { var uriPath = (string?)data?.GetTagItem("url.full"); diff --git a/Backend/Repositories/UserRepository.cs b/Backend/Repositories/UserRepository.cs index d48f311116..35e3d8e44f 100644 --- a/Backend/Repositories/UserRepository.cs +++ b/Backend/Repositories/UserRepository.cs @@ -197,6 +197,7 @@ public async Task Update(string userId, User user, bool updateIs .Set(x => x.Agreement, user.Agreement) .Set(x => x.Username, user.Username) .Set(x => x.OtelConsent, user.OtelConsent) + .Set(x => x.AnsweredConsent, user.AnsweredConsent) .Set(x => x.UILang, user.UILang) .Set(x => x.GlossSuggestion, user.GlossSuggestion); diff --git a/src/api/models/user.ts b/src/api/models/user.ts index 7fd261072e..7b3cc9a52d 100644 --- a/src/api/models/user.ts +++ b/src/api/models/user.ts @@ -98,6 +98,12 @@ export interface User { * @memberof User */ otelConsent?: boolean; + /** + * + * @type {boolean} + * @memberof User + */ + answeredConsent?: boolean; /** * * @type {string} diff --git a/src/components/AnalyticsConsent/AnalyticsConsent.tsx b/src/components/AnalyticsConsent/AnalyticsConsent.tsx new file mode 100644 index 0000000000..67090d44fb --- /dev/null +++ b/src/components/AnalyticsConsent/AnalyticsConsent.tsx @@ -0,0 +1,20 @@ +import { ReactElement } from "react"; + +interface ConsentProps { + onChangeConsent: (consentVal: boolean) => void; +} + +export function AnalyticsConsent(props: ConsentProps): ReactElement { + const acceptAnalytics = (): void => { + props.onChangeConsent(true); + }; + const rejectAnalytics = (): void => { + props.onChangeConsent(false); + }; + return ( +
+ + +
+ ); +} diff --git a/src/components/App/AppLoggedIn.tsx b/src/components/App/AppLoggedIn.tsx index 5fb0c49417..87a5b668ca 100644 --- a/src/components/App/AppLoggedIn.tsx +++ b/src/components/App/AppLoggedIn.tsx @@ -4,6 +4,9 @@ import { Theme, ThemeProvider, createTheme } from "@mui/material/styles"; import { ReactElement, useEffect, useMemo, useState } from "react"; import { Route, Routes } from "react-router-dom"; +import { updateUser } from "backend"; +import { getCurrentUser } from "backend/localStorage"; +import { AnalyticsConsent } from "components/AnalyticsConsent/AnalyticsConsent"; import DatePickersLocalizationProvider from "components/App/DatePickersLocalizationProvider"; import SignalRHub from "components/App/SignalRHub"; import AppBar from "components/AppBar/AppBarComponent"; @@ -47,6 +50,18 @@ export default function AppWithBar(): ReactElement { const projFonts = useMemo(() => new ProjectFonts(proj), [proj]); const [styleOverrides, setStyleOverrides] = useState(); + const [answeredConsent, setAnsweredConsent] = useState( + getCurrentUser()?.answeredConsent + ); + + async function handleConsentChange(otelConsent: boolean): Promise { + await updateUser({ + ...getCurrentUser()!, + otelConsent, + answeredConsent: true, + }); + setAnsweredConsent(true); + } useEffect(() => { updateLangFromUser(); @@ -83,6 +98,9 @@ export default function AppWithBar(): ReactElement { + {answeredConsent ? null : ( + + )} } /> } /> diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 7d906a381c..d66ae6d3d1 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -3,7 +3,6 @@ import { RouterProvider } from "react-router-dom"; import AnnouncementBanner from "components/AnnouncementBanner"; import UpperRightToastContainer from "components/Toast/UpperRightToastContainer"; -import CookieConsent from "cookies/CookieConsent"; import router from "router/browserRouter"; /** @@ -13,7 +12,6 @@ export default function App(): ReactElement { return (
}> - diff --git a/src/components/UserSettings/UserSettings.tsx b/src/components/UserSettings/UserSettings.tsx index bfde8a5935..e076cb591e 100644 --- a/src/components/UserSettings/UserSettings.tsx +++ b/src/components/UserSettings/UserSettings.tsx @@ -11,18 +11,17 @@ import { Typography, } from "@mui/material"; import { enqueueSnackbar } from "notistack"; -import { FormEvent, Fragment, ReactElement, useEffect, useState } from "react"; +import { FormEvent, Fragment, ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; -import { show } from "vanilla-cookieconsent"; import { AutocompleteSetting, User } from "api/models"; import { isEmailTaken, updateUser } from "backend"; import { getAvatar, getCurrentUser } from "backend/localStorage"; +import { AnalyticsConsent } from "components/AnalyticsConsent/AnalyticsConsent"; import { asyncLoadSemanticDomains } from "components/Project/ProjectActions"; import ClickableAvatar from "components/UserSettings/ClickableAvatar"; import { updateLangFromUser } from "i18n"; -import { useAppDispatch, useAppSelector } from "rootRedux/hooks"; -import { StoreState } from "rootRedux/types"; +import { useAppDispatch } from "rootRedux/hooks"; import theme from "types/theme"; import { uiWritingSystems } from "types/writingSystem"; @@ -58,14 +57,10 @@ export function UserSettings(props: { }): ReactElement { const dispatch = useAppDispatch(); - const analyticsConsent = useAppSelector( - (state: StoreState) => state.analyticsState.consent - ); - const [name, setName] = useState(props.user.name); const [phone, setPhone] = useState(props.user.phone); const [email, setEmail] = useState(props.user.email); - const [otelConsent, setOtelConsent] = useState(analyticsConsent); + const [otelConsent, setOtelConsent] = useState(props.user.otelConsent); const [uiLang, setUiLang] = useState(props.user.uiLang ?? ""); const [glossSuggestion, setGlossSuggestion] = useState( props.user.glossSuggestion @@ -81,9 +76,13 @@ export function UserSettings(props: { return unchanged || !(await isEmailTaken(unicodeEmail)); } - useEffect(() => { - setOtelConsent(analyticsConsent); - }, [analyticsConsent]); + const [displayConsent, setDisplayConsent] = useState(false); + const show = (): void => setDisplayConsent(true); + + const handleConsentChange = (consentVal: boolean): void => { + setOtelConsent(consentVal); + setDisplayConsent(false); + }; const disabled = name === props.user.name && @@ -293,7 +292,7 @@ export function UserSettings(props: { {t( - analyticsConsent + otelConsent ? "userSettings.analyticsConsent.consentYes" : "userSettings.analyticsConsent.consentNo" )} @@ -301,11 +300,16 @@ export function UserSettings(props: { + {displayConsent ? ( + + ) : null} diff --git a/src/types/user.ts b/src/types/user.ts index 29d6d0177c..55ad01aeb1 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -15,6 +15,7 @@ export function newUser(name = "", username = "", password = ""): User { glossSuggestion: AutocompleteSetting.On, token: "", isAdmin: false, + answeredConsent: false, }; }