diff --git a/client/package-lock.json b/client/package-lock.json index d951186..b0dcfb2 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ "jspdf": "^4.2.1", "leaflet": "^1.9.4", "lucide-react": "^1.16.0", + "qrcode": "^1.5.4", "react": "^19.2.6", "react-dom": "^19.2.6", "react-i18next": "^17.0.8" @@ -25,6 +26,7 @@ "@eslint/js": "^10.0.1", "@types/leaflet": "^1.9.21", "@types/node": "^24.12.3", + "@types/qrcode": "^1.5.5", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^4.7.0", @@ -34,7 +36,6 @@ "globals": "^17.6.0", "happy-dom": "^20.9.0", "playwright": "^1.51.0", - "qrcode": "^1.5.4", "typescript": "~6.0.2", "typescript-eslint": "^8.59.2", "vite": "^6.3.5", @@ -2970,6 +2971,16 @@ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", "license": "MIT" }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/raf": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", @@ -3461,7 +3472,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3471,7 +3481,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3777,7 +3786,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3855,7 +3863,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -3867,7 +3874,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3880,7 +3886,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -4051,7 +4056,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4140,7 +4144,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", - "dev": true, "license": "MIT" }, "node_modules/dompurify": { @@ -4195,7 +4198,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/entities": { @@ -4948,7 +4950,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5498,7 +5499,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6208,7 +6208,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6231,7 +6230,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6376,7 +6374,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -6458,7 +6455,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", - "dev": true, "license": "MIT", "dependencies": { "dijkstrajs": "^1.0.1", @@ -6653,7 +6649,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6673,7 +6668,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, "license": "ISC" }, "node_modules/resolve": { @@ -6845,7 +6839,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, "license": "ISC" }, "node_modules/set-function-length": { @@ -7113,7 +7106,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -7230,7 +7222,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8067,7 +8058,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, "license": "ISC" }, "node_modules/which-typed-array": { @@ -8343,7 +8333,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -8380,7 +8369,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, "license": "ISC" }, "node_modules/yallist": { @@ -8394,7 +8382,6 @@ "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -8417,7 +8404,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -8431,7 +8417,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -8445,7 +8430,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -8458,7 +8442,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -8474,7 +8457,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" diff --git a/client/package.json b/client/package.json index a9feb6b..7831a10 100644 --- a/client/package.json +++ b/client/package.json @@ -29,12 +29,14 @@ "lucide-react": "^1.16.0", "react": "^19.2.6", "react-dom": "^19.2.6", - "react-i18next": "^17.0.8" + "react-i18next": "^17.0.8", + "qrcode": "^1.5.4" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/leaflet": "^1.9.21", "@types/node": "^24.12.3", + "@types/qrcode": "^1.5.5", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^4.7.0", @@ -44,7 +46,6 @@ "globals": "^17.6.0", "happy-dom": "^20.9.0", "playwright": "^1.51.0", - "qrcode": "^1.5.4", "typescript": "~6.0.2", "typescript-eslint": "^8.59.2", "vite": "^6.3.5", diff --git a/client/src/App.css b/client/src/App.css index 54c49e6..e334014 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1716,6 +1716,38 @@ html.scheme-dark .themed-select-option.is-selected { min-width: 0; } +.link-with-qr { + display: flex; + flex-direction: column; + gap: 16px; +} + +.link-qr-block { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px; + border-radius: var(--app-radius-card, 12px); + background: var(--app-surface-inset, rgba(0, 0, 0, 0.2)); + border: 1px solid var(--app-border-subtle); +} + +.link-qr-label { + margin: 0; + font-size: 13px; + color: var(--app-text-muted); + text-align: center; +} + +.link-qr-image { + display: block; + border-radius: 8px; + background: #fff; + padding: 8px; + box-sizing: content-box; +} + .form-actions--start { justify-content: flex-start; } diff --git a/client/src/components/LinkQrCode.tsx b/client/src/components/LinkQrCode.tsx new file mode 100644 index 0000000..62dcc3e --- /dev/null +++ b/client/src/components/LinkQrCode.tsx @@ -0,0 +1,54 @@ +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import QRCode from 'qrcode' + +interface LinkQrCodeProps { + value: string + size?: number +} + +export default function LinkQrCode({ value, size = 200 }: LinkQrCodeProps) { + const { t } = useTranslation() + const [dataUrl, setDataUrl] = useState(null) + + useEffect(() => { + if (!value.trim()) { + setDataUrl(null) + return + } + + let cancelled = false + void QRCode.toDataURL(value, { + width: size, + margin: 2, + errorCorrectionLevel: 'M', + color: { dark: '#0f172a', light: '#ffffff' } + }) + .then((url) => { + if (!cancelled) setDataUrl(url) + }) + .catch((err) => { + console.error('QR code generation failed:', err) + if (!cancelled) setDataUrl(null) + }) + + return () => { + cancelled = true + } + }, [value, size]) + + if (!value.trim() || !dataUrl) return null + + return ( +
+

{t('settings.link_qr_hint')}

+ {t('settings.link_qr_alt')} +
+ ) +} diff --git a/client/src/components/LiveLogView.tsx b/client/src/components/LiveLogView.tsx index 24c7f02..59dbac2 100644 --- a/client/src/components/LiveLogView.tsx +++ b/client/src/components/LiveLogView.tsx @@ -21,7 +21,6 @@ import { Zap } from 'lucide-react' import { db } from '../services/db.js' -import { getActiveMasterKey } from '../services/auth.js' import { getLogbookKey } from '../services/logbookKeys.js' import { decryptJson } from '../services/crypto.js' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' @@ -66,8 +65,6 @@ import CourseDialInput from './CourseDialInput.tsx' import LiveCameraCapture from './LiveCameraCapture.tsx' import { saveEntryPhoto, deleteEntryPhoto } from '../services/photoAttachments.js' import { blobToCompressedJpegDataUrl } from '../utils/imageCompress.js' -import i18n from '../i18n/index.js' - interface LiveLogViewProps { logbookId: string onOpenEditor: (entryId: string) => void diff --git a/client/src/components/SettingsForm.tsx b/client/src/components/SettingsForm.tsx index 3a23669..b3796a0 100644 --- a/client/src/components/SettingsForm.tsx +++ b/client/src/components/SettingsForm.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import { Settings as SettingsIcon, Check, Users, Trash2, Copy, Link as LinkIcon } from 'lucide-react' import { ensureLogbookKey } from '../services/logbookKeys.js' import LogbookBackupPanel from './LogbookBackupPanel.tsx' +import LinkQrCode from './LinkQrCode.tsx' import { useDialog } from './ModalDialog.tsx' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import { apiFetch } from '../services/api.js' @@ -314,23 +315,27 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF {shareEnabled && shareLink && ( -
- (e.target as HTMLInputElement).select()} - /> - +
+
+ (e.target as HTMLInputElement).select()} + /> + +
+
)}
@@ -367,23 +372,27 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF {inviteLink && ( -
- (e.target as HTMLInputElement).select()} - /> - +
+
+ (e.target as HTMLInputElement).select()} + /> + +
+
)} diff --git a/client/src/i18n/locales/da.json b/client/src/i18n/locales/da.json index 0c060da..06ca2ca 100644 --- a/client/src/i18n/locales/da.json +++ b/client/src/i18n/locales/da.json @@ -633,6 +633,8 @@ "share_enable": "Aktivér offentligt link", "share_copied": "Link kopieret!", "share_copy_btn": "Kopier link", + "link_qr_hint": "Scan QR-koden med din telefon", + "link_qr_alt": "QR-kode til linket", "danger_zone_title": "Farezone", "danger_zone_desc": "Når du sletter din konto, slettes alle dine Passkey'er, logbøger, skibsdata, besætningsprofiler, rejseindlæg og E2E-nøgler uigenkaldeligt. Denne handling kan ikke fortrydes.", "delete_account_btn": "Slet konto uigenkaldeligt", diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json index 679c3fc..8bd6ae2 100644 --- a/client/src/i18n/locales/de.json +++ b/client/src/i18n/locales/de.json @@ -633,6 +633,8 @@ "share_enable": "Öffentlichen Link aktivieren", "share_copied": "Link kopiert!", "share_copy_btn": "Link kopieren", + "link_qr_hint": "QR-Code zum Scannen mit dem Smartphone", + "link_qr_alt": "QR-Code für den Link", "danger_zone_title": "Gefahrenzone", "danger_zone_desc": "Durch das Löschen deines Kontos werden alle deine Passkeys, Logbücher, Schiffsdaten, Crew-Profile, Reiseeinträge und E2E-Schlüssel unwiderruflich gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "delete_account_btn": "Konto unwiderruflich löschen", diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index e977726..204a2cf 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -633,6 +633,8 @@ "share_enable": "Enable Public Link", "share_copied": "Link copied!", "share_copy_btn": "Copy Link", + "link_qr_hint": "Scan this QR code with your phone", + "link_qr_alt": "QR code for the link", "danger_zone_title": "Danger Zone", "danger_zone_desc": "Deleting your account will permanently delete all your passkeys, logbooks, vessel data, crew profiles, travel logs, and E2E keys. This action cannot be undone.", "delete_account_btn": "Permanently Delete Account", diff --git a/client/src/i18n/locales/nb.json b/client/src/i18n/locales/nb.json index 123661e..1394cc6 100644 --- a/client/src/i18n/locales/nb.json +++ b/client/src/i18n/locales/nb.json @@ -633,6 +633,8 @@ "share_enable": "Aktiver offentlig lenke", "share_copied": "Linken er kopiert!", "share_copy_btn": "Kopier lenke", + "link_qr_hint": "Skann QR-koden med telefonen", + "link_qr_alt": "QR-kode for lenken", "danger_zone_title": "Faresone", "danger_zone_desc": "Hvis du sletter kontoen din, slettes alle dine Passkeys, loggbøker, skipsdata, mannskapsprofiler, reiseoppføringer og E2E-nøkler ugjenkallelig. Denne handlingen kan ikke angres.", "delete_account_btn": "Slett konto ugjenkallelig", diff --git a/client/src/i18n/locales/sv.json b/client/src/i18n/locales/sv.json index 1a58910..07fee46 100644 --- a/client/src/i18n/locales/sv.json +++ b/client/src/i18n/locales/sv.json @@ -633,6 +633,8 @@ "share_enable": "Aktivera offentlig länk", "share_copied": "Länk kopierad!", "share_copy_btn": "Kopiera länk", + "link_qr_hint": "Skanna QR-koden med mobilen", + "link_qr_alt": "QR-kod för länken", "danger_zone_title": "Farlig zon", "danger_zone_desc": "Om du raderar ditt konto raderas oåterkalleligen alla dina Passkey, loggböcker, fartygsdata, besättningsprofiler, reseanteckningar och E2E-nycklar. Denna åtgärd kan inte ångras.", "delete_account_btn": "Ta bort konto oåterkalleligt",