2026-05-09 09:13:38 +02:00
|
|
|
import {
|
|
|
|
|
createContext,
|
|
|
|
|
useCallback,
|
|
|
|
|
useContext,
|
|
|
|
|
useEffect,
|
|
|
|
|
useMemo,
|
|
|
|
|
useState,
|
|
|
|
|
} from 'react'
|
|
|
|
|
import api from '../utils/api'
|
2026-05-09 10:42:56 +02:00
|
|
|
import { activeClubMemberships } from '../utils/activeClub'
|
2026-05-09 09:13:38 +02:00
|
|
|
|
|
|
|
|
const OrgInboxContext = createContext(null)
|
|
|
|
|
|
|
|
|
|
export function canAccessOrgInbox(user) {
|
|
|
|
|
if (!user?.id) return false
|
|
|
|
|
if (user.role === 'admin' || user.role === 'superadmin') return true
|
2026-05-09 10:42:56 +02:00
|
|
|
return activeClubMemberships(user.clubs).some((c) => (c.roles || []).includes('club_admin'))
|
2026-05-09 09:13:38 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-11 19:36:23 +02:00
|
|
|
function canSeeContentReports(user) {
|
|
|
|
|
if (user?.role === 'admin' || user?.role === 'superadmin') return true
|
|
|
|
|
return activeClubMemberships(user?.clubs || []).some((c) => (c.roles || []).includes('club_admin'))
|
2026-05-11 17:54:53 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 09:13:38 +02:00
|
|
|
/** Nach Annahme/Ablehnung: Posteingang-Badges & Widget aktualisieren */
|
|
|
|
|
export function notifyOrgInboxChanged() {
|
|
|
|
|
window.dispatchEvent(new Event('shinkan:inbox-changed'))
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 06:53:37 +02:00
|
|
|
/** Eine konsistente Ladepfad-Logik für Join-Requests + Content-Reports (ein Codepfad für Mount + refresh). */
|
|
|
|
|
async function fetchOrgInboxSnapshot(canAccess, canAccessReports) {
|
|
|
|
|
const out = { items: [], contentReports: [], contentReportsError: null }
|
|
|
|
|
if (canAccess) {
|
|
|
|
|
try {
|
|
|
|
|
const data = await api.getInboxJoinRequests()
|
|
|
|
|
out.items = Array.isArray(data) ? data : []
|
|
|
|
|
} catch {
|
|
|
|
|
out.items = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (canAccessReports) {
|
|
|
|
|
try {
|
|
|
|
|
const data = await api.getInboxContentReports()
|
|
|
|
|
out.contentReports = Array.isArray(data) ? data : []
|
|
|
|
|
} catch (err) {
|
|
|
|
|
out.contentReports = []
|
|
|
|
|
out.contentReportsError = err?.message || String(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 09:13:38 +02:00
|
|
|
export function OrgInboxProvider({ user, children }) {
|
|
|
|
|
const [items, setItems] = useState([])
|
2026-05-11 17:54:53 +02:00
|
|
|
const [contentReports, setContentReports] = useState([])
|
2026-05-11 21:32:44 +02:00
|
|
|
const [contentReportsError, setContentReportsError] = useState(null)
|
2026-05-09 09:13:38 +02:00
|
|
|
const canAccess = useMemo(() => canAccessOrgInbox(user), [user])
|
2026-05-11 19:36:23 +02:00
|
|
|
const canAccessReports = useMemo(() => canSeeContentReports(user), [user])
|
2026-05-09 09:13:38 +02:00
|
|
|
|
|
|
|
|
const refresh = useCallback(async () => {
|
2026-05-14 06:53:37 +02:00
|
|
|
if (!canAccess && !canAccessReports) {
|
2026-05-09 09:13:38 +02:00
|
|
|
setItems([])
|
2026-05-11 17:54:53 +02:00
|
|
|
setContentReports([])
|
2026-05-11 21:32:44 +02:00
|
|
|
setContentReportsError(null)
|
2026-05-14 06:53:37 +02:00
|
|
|
return
|
2026-05-09 09:13:38 +02:00
|
|
|
}
|
2026-05-14 06:53:37 +02:00
|
|
|
const snap = await fetchOrgInboxSnapshot(canAccess, canAccessReports)
|
|
|
|
|
setItems(snap.items)
|
|
|
|
|
setContentReports(snap.contentReports)
|
|
|
|
|
setContentReportsError(canAccessReports ? snap.contentReportsError : null)
|
2026-05-11 17:54:53 +02:00
|
|
|
}, [canAccess, canAccessReports])
|
2026-05-09 09:13:38 +02:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-05-11 17:54:53 +02:00
|
|
|
if (!canAccess && !canAccessReports) {
|
2026-05-09 09:13:38 +02:00
|
|
|
setItems([])
|
2026-05-11 17:54:53 +02:00
|
|
|
setContentReports([])
|
2026-05-11 21:32:44 +02:00
|
|
|
setContentReportsError(null)
|
2026-05-09 09:13:38 +02:00
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
let cancelled = false
|
2026-05-14 08:36:31 +02:00
|
|
|
let idleId = null
|
|
|
|
|
let timeoutId = null
|
|
|
|
|
|
|
|
|
|
const load = async () => {
|
2026-05-14 06:53:37 +02:00
|
|
|
const snap = await fetchOrgInboxSnapshot(canAccess, canAccessReports)
|
|
|
|
|
if (cancelled) return
|
|
|
|
|
setItems(snap.items)
|
|
|
|
|
setContentReports(snap.contentReports)
|
|
|
|
|
setContentReportsError(canAccessReports ? snap.contentReportsError : null)
|
2026-05-14 08:36:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const schedule = () => {
|
|
|
|
|
if (cancelled) return
|
|
|
|
|
if (typeof window.requestIdleCallback === 'function') {
|
|
|
|
|
idleId = window.requestIdleCallback(
|
|
|
|
|
() => {
|
|
|
|
|
idleId = null
|
|
|
|
|
void load()
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 1500 },
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
timeoutId = window.setTimeout(() => {
|
|
|
|
|
timeoutId = null
|
|
|
|
|
void load()
|
|
|
|
|
}, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
schedule()
|
|
|
|
|
|
2026-05-09 09:13:38 +02:00
|
|
|
return () => {
|
|
|
|
|
cancelled = true
|
2026-05-14 08:36:31 +02:00
|
|
|
if (idleId != null && typeof window.cancelIdleCallback === 'function') {
|
|
|
|
|
window.cancelIdleCallback(idleId)
|
|
|
|
|
}
|
|
|
|
|
if (timeoutId != null) window.clearTimeout(timeoutId)
|
2026-05-09 09:13:38 +02:00
|
|
|
}
|
2026-05-11 17:54:53 +02:00
|
|
|
}, [canAccess, canAccessReports, user?.id])
|
2026-05-09 09:13:38 +02:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-05-11 17:54:53 +02:00
|
|
|
const onChange = () => { refresh() }
|
2026-05-09 09:13:38 +02:00
|
|
|
window.addEventListener('shinkan:inbox-changed', onChange)
|
|
|
|
|
return () => window.removeEventListener('shinkan:inbox-changed', onChange)
|
|
|
|
|
}, [refresh])
|
|
|
|
|
|
|
|
|
|
const value = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
inboxJoinRequests: items,
|
|
|
|
|
inboxCount: items.length,
|
2026-05-11 17:54:53 +02:00
|
|
|
contentReports,
|
|
|
|
|
contentReportCount: contentReports.filter((r) => r.status === 'submitted').length,
|
2026-05-11 21:32:44 +02:00
|
|
|
contentReportsError,
|
2026-05-09 09:13:38 +02:00
|
|
|
refreshOrgInbox: refresh,
|
|
|
|
|
canAccessOrgInbox: canAccess,
|
2026-05-11 17:54:53 +02:00
|
|
|
canAccessContentReports: canAccessReports,
|
|
|
|
|
isSuperadmin: user?.role === 'superadmin',
|
2026-05-11 22:15:29 +02:00
|
|
|
isPlatformAdmin: user?.role === 'admin' || user?.role === 'superadmin',
|
|
|
|
|
isClubAdmin: activeClubMemberships(user?.clubs || []).some((c) => (c.roles || []).includes('club_admin')),
|
2026-05-09 09:13:38 +02:00
|
|
|
}),
|
2026-05-11 22:15:29 +02:00
|
|
|
[items, contentReports, contentReportsError, refresh, canAccess, canAccessReports, user?.role, user?.clubs]
|
2026-05-09 09:13:38 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return <OrgInboxContext.Provider value={value}>{children}</OrgInboxContext.Provider>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useOrgInbox() {
|
|
|
|
|
const ctx = useContext(OrgInboxContext)
|
|
|
|
|
if (!ctx) {
|
|
|
|
|
throw new Error('useOrgInbox must be used within OrgInboxProvider')
|
|
|
|
|
}
|
|
|
|
|
return ctx
|
|
|
|
|
}
|