From 3a267905b004e39d493b357272195f192303c281 Mon Sep 17 00:00:00 2001 From: elpatron Date: Sun, 31 May 2026 13:43:52 +0200 Subject: [PATCH] feat: Push-Hinweis nach Erstellen eines Crew-Einladungslinks Owner sieht einen Dialog zur Aktivierung von Crew-Push-Benachrichtigungen, sofern diese noch nicht aktiv sind. Co-authored-by: Cursor --- client/src/components/SettingsForm.tsx | 44 ++++++++++++++++++++++++ client/src/i18n/locales/de.json | 6 ++++ client/src/i18n/locales/en.json | 6 ++++ client/src/services/pushNotifications.ts | 12 +++++++ 4 files changed, 68 insertions(+) diff --git a/client/src/components/SettingsForm.tsx b/client/src/components/SettingsForm.tsx index 5606933..6405c79 100644 --- a/client/src/components/SettingsForm.tsx +++ b/client/src/components/SettingsForm.tsx @@ -6,6 +6,12 @@ import LogbookBackupPanel from './LogbookBackupPanel.tsx' import { useDialog } from './ModalDialog.tsx' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import { apiFetch } from '../services/api.js' +import { + enableCollaboratorChangePush, + isCollaboratorPushActive, + isPushSupported +} from '../services/pushNotifications.js' +import { isIosDevice, isRunningStandalone } from '../hooks/usePwaInstall.js' interface SettingsFormProps { logbookId?: string | null @@ -151,6 +157,43 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF } } + const promptPushAfterInviteCreated = async () => { + if (!isPushSupported()) return + if (await isCollaboratorPushActive()) return + + const iosNeedsInstall = isIosDevice() && !isRunningStandalone() + + if (iosNeedsInstall) { + await showAlert( + t('settings.invite_push_prompt_ios_message'), + t('settings.invite_push_prompt_title'), + t('settings.invite_push_prompt_later') + ) + return + } + + const enable = await showConfirm( + t('settings.invite_push_prompt_message'), + t('settings.invite_push_prompt_title'), + t('settings.invite_push_prompt_enable'), + t('settings.invite_push_prompt_later') + ) + + if (!enable) return + + try { + await enableCollaboratorChangePush() + await showAlert( + t('settings.invite_push_prompt_success'), + t('settings.invite_push_prompt_title') + ) + trackPlausibleEvent(PlausibleEvents.PUSH_ENABLED) + } catch (err: unknown) { + console.error('Failed to enable push after invite:', err) + showAlert(err instanceof Error ? err.message : t('profile.push_error')) + } + } + const handleGenerateInvite = async () => { if (!logbookId) return setGeneratingInvite(true) @@ -175,6 +218,7 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF setInviteLink(link) trackPlausibleEvent(PlausibleEvents.INVITE_GENERATED) + await promptPushAfterInviteCreated() } catch (err: unknown) { console.error('Failed to generate invite:', err) showAlert(err instanceof Error ? err.message : 'Failed to generate invite link.') diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json index 9fd63c1..5460a3a 100644 --- a/client/src/i18n/locales/de.json +++ b/client/src/i18n/locales/de.json @@ -482,6 +482,12 @@ "delete_account_failed": "Konto konnte nicht gelöscht werden. Bitte versuche es erneut.", "delete_backup_hint": "Tipp: Erstelle vor dem Löschen Backups deiner Logbücher (.daagbok.json) in den Einstellungen jedes Logbuchs.", "deleting_account": "Konto wird gelöscht…", + "invite_push_prompt_title": "Push-Benachrichtigungen aktivieren?", + "invite_push_prompt_message": "Sobald eingeladene Crewmitglieder Änderungen synchronisieren, kannst du per Push informiert werden. Es werden keine Logbuch-Inhalte im Klartext gesendet.", + "invite_push_prompt_ios_message": "Sobald Crewmitglieder Änderungen synchronisieren, kannst du per Push informiert werden. Auf dem iPhone/iPad: App zum Home-Bildschirm hinzufügen (iOS 16.4+), dann Push im Benutzerprofil aktivieren.", + "invite_push_prompt_enable": "Jetzt aktivieren", + "invite_push_prompt_later": "Später", + "invite_push_prompt_success": "Push-Benachrichtigungen sind auf diesem Gerät aktiv.", "backup_title": "Backup & Wiederherstellung", "backup_desc": "Vollständiges verschlüsseltes Backup dieses Logbuchs (Einträge, Fotos, GPS-Tracks, Crew, Schiff). Mit Backup-Passphrase geschützt — für Restore auf diesem oder einem neuen Account.", "backup_export_title": "Backup erstellen", diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index 677750a..54621fb 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -482,6 +482,12 @@ "delete_account_failed": "Failed to delete account. Please try again.", "delete_backup_hint": "Tip: Before deleting, create backups of your logbooks (.daagbok.json) in each logbook's settings.", "deleting_account": "Deleting account…", + "invite_push_prompt_title": "Enable push notifications?", + "invite_push_prompt_message": "When invited crew members sync changes, you can be notified via push. No logbook content is sent in plain text.", + "invite_push_prompt_ios_message": "When crew members sync changes, you can get push notifications. On iPhone/iPad: add the app to your Home Screen (iOS 16.4+), then enable push in your user profile.", + "invite_push_prompt_enable": "Enable now", + "invite_push_prompt_later": "Later", + "invite_push_prompt_success": "Push notifications are active on this device.", "backup_title": "Backup & restore", "backup_desc": "Full encrypted backup of this logbook (entries, photos, GPS tracks, crew, vessel). Protected with a backup passphrase — restore on this or a new account.", "backup_export_title": "Create backup", diff --git a/client/src/services/pushNotifications.ts b/client/src/services/pushNotifications.ts index 12bed45..3c274ff 100644 --- a/client/src/services/pushNotifications.ts +++ b/client/src/services/pushNotifications.ts @@ -43,6 +43,18 @@ async function fetchVapidPublicKey(): Promise { } } +/** True when crew-change push is enabled and notification permission is granted. */ +export async function isCollaboratorPushActive(): Promise { + if (!isPushSupported()) return false + if (getNotificationPermission() !== 'granted') return false + try { + const prefs = await fetchPushPrefs() + return prefs.collaboratorChangesEnabled + } catch { + return false + } +} + export async function fetchPushPrefs(): Promise<{ collaboratorChangesEnabled: boolean }> { if (!localStorage.getItem('active_userid')) { return { collaboratorChangesEnabled: false }