aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2023-11-25 14:19:35 -0800
committerAnton Tananaev <anton@traccar.org>2023-11-25 14:19:41 -0800
commit4a7ab9a9b45a43b285e5d649d8f25e821f376ac3 (patch)
tree98fbb592e56f404de9adb6b070c55531da0f3617
parenta1dce943fc3f7075b5d2fd887cb9b2f30402f73e (diff)
downloadtrackermap-web-4a7ab9a9b45a43b285e5d649d8f25e821f376ac3.tar.gz
trackermap-web-4a7ab9a9b45a43b285e5d649d8f25e821f376ac3.tar.bz2
trackermap-web-4a7ab9a9b45a43b285e5d649d8f25e821f376ac3.zip
Add device sharing (fix #1118)
-rw-r--r--modern/src/common/components/StatusCard.jsx20
-rw-r--r--modern/src/resources/l10n/en.json2
2 files changed, 22 insertions, 0 deletions
diff --git a/modern/src/common/components/StatusCard.jsx b/modern/src/common/components/StatusCard.jsx
index 04e1d172..2e936420 100644
--- a/modern/src/common/components/StatusCard.jsx
+++ b/modern/src/common/components/StatusCard.jsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
+import dayjs from 'dayjs';
import Draggable from 'react-draggable';
import {
Card,
@@ -15,6 +16,7 @@ import {
Menu,
MenuItem,
CardMedia,
+ Snackbar,
} from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import CloseIcon from '@mui/icons-material/Close';
@@ -130,6 +132,7 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin
const [anchorEl, setAnchorEl] = useState(null);
const [removing, setRemoving] = useState(false);
+ const [shared, setShared] = useState(false);
const handleRemove = useCatch(async (removed) => {
if (removed) {
@@ -169,6 +172,21 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin
}
}, [navigate, position]);
+ const handleShare = useCatchCallback(async () => {
+ const expiration = dayjs().add(1, 'week').toISOString();
+ const response = await fetch('/api/devices/share', {
+ method: 'POST',
+ body: new URLSearchParams(`deviceId=${deviceId}&expiration=${expiration}`),
+ });
+ if (response.ok) {
+ const token = await response.text();
+ navigator.clipboard.writeText(`${window.location.origin}?token=${token}`);
+ setShared(true);
+ } else {
+ throw Error(await response.text());
+ }
+ }, [deviceId, setShared]);
+
return (
<>
<div className={classes.root}>
@@ -270,6 +288,7 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin
<MenuItem component="a" target="_blank" href={`https://www.google.com/maps/search/?api=1&query=${position.latitude}%2C${position.longitude}`}>{t('linkGoogleMaps')}</MenuItem>
<MenuItem component="a" target="_blank" href={`http://maps.apple.com/?ll=${position.latitude},${position.longitude}`}>{t('linkAppleMaps')}</MenuItem>
<MenuItem component="a" target="_blank" href={`https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${position.latitude}%2C${position.longitude}&heading=${position.course}`}>{t('linkStreetView')}</MenuItem>
+ <MenuItem onClick={handleShare}>{t('deviceShare')}</MenuItem>
</Menu>
)}
<RemoveDialog
@@ -278,6 +297,7 @@ const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPaddin
itemId={deviceId}
onResult={(removed) => handleRemove(removed)}
/>
+ <Snackbar open={shared} onClose={() => setShared(false)} message={t('sharedLinkCopied')} />
</>
);
};
diff --git a/modern/src/resources/l10n/en.json b/modern/src/resources/l10n/en.json
index ccbfde6a..0f57b52a 100644
--- a/modern/src/resources/l10n/en.json
+++ b/modern/src/resources/l10n/en.json
@@ -99,6 +99,7 @@
"sharedImport": "Import",
"sharedColumns": "Columns",
"sharedDropzoneText": "Drag and drop a file here or click",
+ "sharedLinkCopied": "Link copied",
"calendarSimple": "Simple",
"calendarRecurrence": "Recurrence",
"calendarOnce": "Once",
@@ -206,6 +207,7 @@
"deviceStatusUnknown": "Unknown",
"deviceRegisterFirst": "Register your first device",
"deviceIdentifierHelp": "IMEI, serial number or other id. It has to match the identifier device reports to the server.",
+ "deviceShare": "Share Device",
"groupDialog": "Group",
"groupParent": "Group",
"groupNoGroup": "No Group",