mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 20:51:18 +00:00
fix push
This commit is contained in:
parent
3170779602
commit
16728da422
5 changed files with 127 additions and 64 deletions
|
|
@ -1,14 +1,20 @@
|
|||
import { Suspense } from "react";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { requireManager } from "@/src/lib/auth/getUser";
|
||||
import { isManager, requireAuthWithData } from "@/src/lib/auth/getUser";
|
||||
import { getNotificationSettings } from "@/src/modules/settings/actions/notification-settings-actions";
|
||||
import { resolveNotificationSettings } from "@/src/modules/notifications/lib/notification-types";
|
||||
import SettingsNotificationPageView from "@/src/modules/settings/ui/views/settings-notification-page-view";
|
||||
import SettingsLoading from "../loading";
|
||||
|
||||
export default async function SettingsNotificationPage() {
|
||||
const manager = await requireManager();
|
||||
const businesses = manager.businesses.map((b) => ({ id: b.id, name: b.name }));
|
||||
// requireAuthWithData() statt requireManager(): der Push-Teil dieser Seite
|
||||
// gilt für alle Rollen (siehe NotificationPermissionSettings unten), nur
|
||||
// die Business-Opt-out-Kategorien (In-App-Benachrichtigungstypen) bleiben
|
||||
// manager-/business-spezifisch.
|
||||
const userData = await requireAuthWithData();
|
||||
const businesses = isManager(userData)
|
||||
? userData.businesses.map((b) => ({ id: b.id, name: b.name }))
|
||||
: [];
|
||||
|
||||
const firstBusinessId = businesses[0]?.id;
|
||||
const result = firstBusinessId ? await getNotificationSettings(firstBusinessId) : null;
|
||||
|
|
@ -26,7 +32,11 @@ export default async function SettingsNotificationPage() {
|
|||
|
||||
return (
|
||||
<Suspense fallback={<SettingsLoading />}>
|
||||
<SettingsNotificationPageView businesses={businesses} initialSettings={initialSettings} />
|
||||
<SettingsNotificationPageView
|
||||
businesses={businesses}
|
||||
initialSettings={initialSettings}
|
||||
showBusinessSettings={isManager(userData)}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,27 @@ export function isPushBaseSupported(): boolean {
|
|||
|
||||
export type EnablePushResult = { success: true } | { success: false; error: string };
|
||||
|
||||
// navigator.serviceWorker.ready löst sich NIE auf, wenn die SW-Registrierung
|
||||
// fehlschlägt oder gar nicht erst versucht wird (z.B. kein sicherer Kontext -
|
||||
// http://<lan-ip>:3000 auf dem Handy statt https/localhost) - ohne Timeout
|
||||
// bleibt enablePushOnThisDevice() dann für immer pending, nachdem der Nutzer
|
||||
// die Permission bereits erteilt hat ("stuck"-Symptom).
|
||||
function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error(message)), ms);
|
||||
promise.then(
|
||||
(value) => {
|
||||
clearTimeout(timer);
|
||||
resolve(value);
|
||||
},
|
||||
(error) => {
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragt die Browser-Permission an (falls nötig), erstellt eine
|
||||
* PushSubscription und registriert sie serverseitig. Wirft nie - Fehler
|
||||
|
|
@ -50,7 +71,11 @@ export async function enablePushOnThisDevice(): Promise<EnablePushResult> {
|
|||
return { success: false, error: "Berechtigung wurde nicht erteilt." };
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const registration = await withTimeout(
|
||||
navigator.serviceWorker.ready,
|
||||
10_000,
|
||||
"Der Service Worker wurde nicht rechtzeitig aktiv. Läuft die Seite über HTTPS (oder localhost)?"
|
||||
);
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!),
|
||||
|
|
@ -82,12 +107,20 @@ export async function enablePushOnThisDevice(): Promise<EnablePushResult> {
|
|||
|
||||
export async function disablePushOnThisDevice(): Promise<void> {
|
||||
if (!isPushSupported()) return;
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
try {
|
||||
const registration = await withTimeout(
|
||||
navigator.serviceWorker.ready,
|
||||
10_000,
|
||||
"Service Worker nicht rechtzeitig aktiv."
|
||||
);
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
if (!subscription) return;
|
||||
|
||||
await unsubscribeFromPush(subscription.endpoint).catch(() => {});
|
||||
await subscription.unsubscribe().catch(() => {});
|
||||
} catch (error) {
|
||||
console.error("disablePushOnThisDevice fehlgeschlagen:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export type PushDeviceStatus = "unsupported" | "denied" | "disabled" | "enabled";
|
||||
|
|
@ -103,7 +136,16 @@ export async function getPushDeviceStatus(): Promise<PushDeviceStatus> {
|
|||
if (Notification.permission === "denied") return "denied";
|
||||
if (Notification.permission !== "granted") return "disabled";
|
||||
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
try {
|
||||
const registration = await withTimeout(
|
||||
navigator.serviceWorker.ready,
|
||||
10_000,
|
||||
"Service Worker nicht rechtzeitig aktiv."
|
||||
);
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
return subscription ? "enabled" : "disabled";
|
||||
} catch (error) {
|
||||
console.error("getPushDeviceStatus fehlgeschlagen:", error);
|
||||
return "disabled";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type NotificationType,
|
||||
} from "@/src/modules/notifications/lib/notification-types";
|
||||
import NotificationTypeIcon from "@/src/modules/notifications/ui/components/NotificationTypeIcon";
|
||||
import NotificationPermissionSettings from "../components/NotificationPermissionSettings";
|
||||
import {
|
||||
getNotificationSettings,
|
||||
saveNotificationSettings,
|
||||
|
|
@ -27,11 +28,14 @@ import {
|
|||
interface SettingsNotificationPageViewProps {
|
||||
businesses: { id: string; name: string }[];
|
||||
initialSettings: NotificationSettings;
|
||||
/** Business-Opt-out-Kategorien gelten nur für Manager. */
|
||||
showBusinessSettings: boolean;
|
||||
}
|
||||
|
||||
const SettingsNotificationPageView = ({
|
||||
businesses,
|
||||
initialSettings,
|
||||
showBusinessSettings,
|
||||
}: SettingsNotificationPageViewProps) => {
|
||||
const [selectedBusinessId, setSelectedBusinessId] = useState(businesses[0]?.id ?? "");
|
||||
const [settings, setSettings] = useState<NotificationSettings>(initialSettings);
|
||||
|
|
@ -75,7 +79,10 @@ const SettingsNotificationPageView = ({
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{businesses.length === 0 ? (
|
||||
<NotificationPermissionSettings />
|
||||
|
||||
{showBusinessSettings &&
|
||||
(businesses.length === 0 ? (
|
||||
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-xl p-6 text-center">
|
||||
<p className="text-sm text-gray-500">Du hast noch keine Businesses angelegt.</p>
|
||||
</div>
|
||||
|
|
@ -125,7 +132,7 @@ const SettingsNotificationPageView = ({
|
|||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@ const SettingsOverviewView = async () => {
|
|||
icon={User}
|
||||
href="/settings/personal"
|
||||
/>
|
||||
<SettingsMenuItem
|
||||
name="Benachrichtigungen"
|
||||
description="Push-Benachrichtigungen für dieses Gerät verwalten"
|
||||
icon={Bell}
|
||||
href="/settings/notifications"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { requireAuthWithData, isManager, isEmployee, isUser } from "@/src/lib/auth/getUser";
|
||||
import PersonalSettingsForm, { PersonalSettingsProps } from "../components/PersonalSettingsForm";
|
||||
import NotificationPermissionSettings from "../components/NotificationPermissionSettings";
|
||||
|
||||
const SettingsPersonalPageView = async () => {
|
||||
const userData = await requireAuthWithData();
|
||||
|
|
@ -52,7 +51,6 @@ const SettingsPersonalPageView = async () => {
|
|||
</p>
|
||||
</div>
|
||||
<PersonalSettingsForm {...props} />
|
||||
<NotificationPermissionSettings />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue