Files
fotospiel-app/resources/js/admin/mobile/hooks/useDevicePermissions.ts
Codex Agent b780d82d62 Added Phase‑1 continuation work across deep links, offline moderation queue, and admin push.
resources/js/admin/mobile/lib.
  - Admin push is end‑to‑end: new backend model/migration/service/job + API endpoints, admin runtime config, push‑aware
    service worker, and a settings toggle via useAdminPushSubscription. Notifications now auto‑refresh on push.
  - New PHP/JS tests: admin push API feature test and queue/haptics unit tests
  Added admin-specific PWA icon assets and wired them into the admin manifest, service worker, and admin shell, plus a
  new “Device & permissions” card in mobile Settings with a persistent storage action and translations.
  Details: public/manifest.json, public/admin-sw.js, resources/views/admin.blade.php, new icons in public/; new hook
  resources/js/admin/mobile/hooks/useDevicePermissions.ts, helpers/tests in resources/js/admin/mobile/lib/
  devicePermissions.ts + resources/js/admin/mobile/lib/devicePermissions.test.ts, and Settings UI updates in resources/
  js/admin/mobile/SettingsPage.tsx with copy in resources/js/admin/i18n/locales/en/management.json and resources/js/
admin/i18n/locales/de/management.json.
2025-12-28 15:00:47 +01:00

98 lines
2.5 KiB
TypeScript

import React from 'react';
import {
normalizePermissionState,
resolveStorageStatus,
type PermissionStatus,
type StorageStatus,
} from '../lib/devicePermissions';
type DevicePermissionsState = {
notifications: PermissionStatus;
camera: PermissionStatus;
storage: StorageStatus;
};
type DevicePermissionsHook = DevicePermissionsState & {
loading: boolean;
refresh: () => Promise<void>;
requestPersistentStorage: () => Promise<boolean>;
};
export function useDevicePermissions(): DevicePermissionsHook {
const [permissions, setPermissions] = React.useState<DevicePermissionsState>({
notifications: 'unsupported',
camera: 'unsupported',
storage: 'unsupported',
});
const [loading, setLoading] = React.useState(true);
const refresh = React.useCallback(async () => {
setLoading(true);
try {
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
return;
}
let notificationState: PermissionStatus = 'unsupported';
if ('Notification' in window) {
notificationState = normalizePermissionState(Notification.permission);
}
let cameraState: PermissionStatus = 'unsupported';
if (navigator.permissions?.query) {
try {
const cameraPermission = await navigator.permissions.query({
name: 'camera' as PermissionName,
});
cameraState = normalizePermissionState(cameraPermission.state);
} catch {
cameraState = 'unsupported';
}
}
const storageSupported = Boolean(navigator.storage?.persisted);
let persisted: boolean | null = null;
if (storageSupported) {
persisted = await navigator.storage.persisted();
}
setPermissions({
notifications: notificationState,
camera: cameraState,
storage: resolveStorageStatus(persisted, storageSupported),
});
} finally {
setLoading(false);
}
}, []);
const requestPersistentStorage = React.useCallback(async () => {
if (typeof navigator === 'undefined' || !navigator.storage?.persist) {
return false;
}
try {
const granted = await navigator.storage.persist();
setPermissions((prev) => ({
...prev,
storage: granted ? 'persisted' : 'available',
}));
return granted;
} catch {
return false;
}
}, []);
React.useEffect(() => {
void refresh();
}, [refresh]);
return {
...permissions,
loading,
refresh,
requestPersistentStorage,
};
}