aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2024-05-09 14:51:39 -0700
committerAnton Tananaev <anton@traccar.org>2024-05-09 14:51:39 -0700
commita47064edf0e27d6c1fd3876d1cfd3a6c0188cf3c (patch)
tree9c0a753b136d55f178868a959f8f2e1eb768c0fd /src
parentc4754d0befac1a0bfb85f56419dade8378b98b32 (diff)
downloadtrackermap-web-a47064edf0e27d6c1fd3876d1cfd3a6c0188cf3c.tar.gz
trackermap-web-a47064edf0e27d6c1fd3876d1cfd3a6c0188cf3c.tar.bz2
trackermap-web-a47064edf0e27d6c1fd3876d1cfd3a6c0188cf3c.zip
Terms and privacy policy support (fix #1052)
Diffstat (limited to 'src')
-rw-r--r--src/App.jsx31
-rw-r--r--src/common/attributes/useServerAttributes.js8
-rw-r--r--src/common/attributes/useUserAttributes.js4
-rw-r--r--src/common/components/TermsDialog.jsx34
-rw-r--r--src/resources/l10n/en.json5
5 files changed, 77 insertions, 5 deletions
diff --git a/src/App.jsx b/src/App.jsx
index 4fe34f64..b6e864b2 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -6,9 +6,10 @@ import makeStyles from '@mui/styles/makeStyles';
import BottomMenu from './common/components/BottomMenu';
import SocketController from './SocketController';
import CachingController from './CachingController';
-import { useEffectAsync } from './reactHelper';
+import { useCatch, useEffectAsync } from './reactHelper';
import { sessionActions } from './store';
import UpdateController from './UpdateController';
+import TermsDialog from './common/components/TermsDialog';
const useStyles = makeStyles(() => ({
page: {
@@ -29,10 +30,24 @@ const App = () => {
const desktop = useMediaQuery(theme.breakpoints.up('md'));
const newServer = useSelector((state) => state.session.server.newServer);
- const initialized = useSelector((state) => !!state.session.user);
+ const termsUrl = useSelector((state) => state.session.server.attributes.termsUrl);
+ const user = useSelector((state) => state.session.user);
+
+ const acceptTerms = useCatch(async () => {
+ const response = await fetch(`/api/users/${user.id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ...user, attributes: { ...user.attributes, termsAccepted: true } }),
+ });
+ if (response.ok) {
+ dispatch(sessionActions.updateUser(await response.json()));
+ } else {
+ throw Error(await response.text());
+ }
+ });
useEffectAsync(async () => {
- if (!initialized) {
+ if (!user) {
const response = await fetch('/api/session');
if (response.ok) {
dispatch(sessionActions.updateUser(await response.json()));
@@ -43,9 +58,15 @@ const App = () => {
}
}
return null;
- }, [initialized]);
+ }, [user]);
- return !initialized ? (<LinearProgress />) : (
+ if (user == null) {
+ return (<LinearProgress />);
+ }
+ if (termsUrl && !user.attributes.termsAccepted) {
+ return (<TermsDialog open onCancel={() => navigate('/login')} onAccept={() => acceptTerms()} />);
+ }
+ return (
<>
<SocketController />
<CachingController />
diff --git a/src/common/attributes/useServerAttributes.js b/src/common/attributes/useServerAttributes.js
index 80ac3c7d..f46f715c 100644
--- a/src/common/attributes/useServerAttributes.js
+++ b/src/common/attributes/useServerAttributes.js
@@ -39,6 +39,14 @@ export default (t) => useMemo(() => ({
name: t('settingsDarkMode'),
type: 'boolean',
},
+ termsUrl: {
+ name: t('userTerms'),
+ type: 'string',
+ },
+ privacyUrl: {
+ name: t('userPrivacy'),
+ type: 'string',
+ },
totpEnable: {
name: t('settingsTotpEnable'),
type: 'boolean',
diff --git a/src/common/attributes/useUserAttributes.js b/src/common/attributes/useUserAttributes.js
index 81230884..e819412c 100644
--- a/src/common/attributes/useUserAttributes.js
+++ b/src/common/attributes/useUserAttributes.js
@@ -57,4 +57,8 @@ export default (t) => useMemo(() => ({
name: t('attributeMailSmtpPassword'),
type: 'string',
},
+ termsAccepted: {
+ name: t('userTermsAccepted'),
+ type: 'boolean',
+ },
}), [t]);
diff --git a/src/common/components/TermsDialog.jsx b/src/common/components/TermsDialog.jsx
new file mode 100644
index 00000000..4b909415
--- /dev/null
+++ b/src/common/components/TermsDialog.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { Button, Dialog, DialogActions, DialogContent, DialogContentText, Link } from '@mui/material';
+import { useTranslation } from './LocalizationProvider';
+
+const TermsDialog = ({ open, onCancel, onAccept }) => {
+ const t = useTranslation();
+
+ const termsUrl = useSelector((state) => state.session.server.attributes.termsUrl);
+ const privacyUrl = useSelector((state) => state.session.server.attributes.privacyUrl);
+
+ return (
+ <Dialog
+ open={open}
+ onClose={onCancel}
+ >
+ <DialogContent>
+ <DialogContentText>
+ {t('userTermsPrompt')}
+ <ul>
+ <li><Link href={termsUrl} target="_blank">{t('userTerms')}</Link></li>
+ <li><Link href={privacyUrl} target="_blank">{t('userPrivacy')}</Link></li>
+ </ul>
+ </DialogContentText>
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={onCancel}>{t('sharedCancel')}</Button>
+ <Button onClick={onAccept}>{t('sharedAccept')}</Button>
+ </DialogActions>
+ </Dialog>
+ );
+};
+
+export default TermsDialog;
diff --git a/src/resources/l10n/en.json b/src/resources/l10n/en.json
index a9303f63..945dfe4a 100644
--- a/src/resources/l10n/en.json
+++ b/src/resources/l10n/en.json
@@ -4,6 +4,7 @@
"sharedSave": "Save",
"sharedUpload": "Upload",
"sharedSet": "Set",
+ "sharedAccept": "Accept",
"sharedCancel": "Cancel",
"sharedCopy": "Copy",
"sharedAdd": "Add",
@@ -179,6 +180,10 @@
"userToken": "Token",
"userDeleteAccount": "Delete Account",
"userTemporary": "Temporary",
+ "userTerms": "Terms of Service",
+ "userPrivacy": "Privacy Policy",
+ "userTermsPrompt": "By clicking Accept, you agree to our Terms of Service and confirm that you have read our Privacy Policy.",
+ "userTermsAccepted": "Terms Accepted",
"loginTitle": "Login",
"loginLanguage": "Language",
"loginReset": "Reset Password",