adding chat features, admin features and bug fixes

This commit is contained in:
Matthias Hochmeister
2026-03-12 08:16:34 +01:00
parent 7b14e3d5ba
commit 31f1414e06
43 changed files with 2610 additions and 16 deletions

View File

@@ -0,0 +1,57 @@
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
import { useQuery } from '@tanstack/react-query';
import { nextcloudApi } from '../services/nextcloud';
import { useLayout } from './LayoutContext';
import type { NextcloudConversation } from '../types/nextcloud.types';
interface ChatContextType {
rooms: NextcloudConversation[];
selectedRoomToken: string | null;
selectRoom: (token: string | null) => void;
connected: boolean;
loginName: string | null;
}
const ChatContext = createContext<ChatContextType | undefined>(undefined);
interface ChatProviderProps {
children: ReactNode;
}
export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
const [selectedRoomToken, setSelectedRoomToken] = useState<string | null>(null);
const { chatPanelOpen } = useLayout();
const { data } = useQuery({
queryKey: ['nextcloud', 'rooms'],
queryFn: () => nextcloudApi.getRooms(),
refetchInterval: chatPanelOpen ? 30000 : false,
enabled: chatPanelOpen,
});
const rooms = data?.rooms ?? [];
const connected = data?.connected ?? false;
const loginName = data?.loginName ?? null;
const selectRoom = useCallback((token: string | null) => {
setSelectedRoomToken(token);
}, []);
const value: ChatContextType = {
rooms,
selectedRoomToken,
selectRoom,
connected,
loginName,
};
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
};
export const useChat = (): ChatContextType => {
const context = useContext(ChatContext);
if (context === undefined) {
throw new Error('useChat must be used within a ChatProvider');
}
return context;
};

View File

@@ -0,0 +1,70 @@
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
export const DRAWER_WIDTH = 240;
export const DRAWER_WIDTH_COLLAPSED = 64;
interface LayoutContextType {
sidebarCollapsed: boolean;
chatPanelOpen: boolean;
toggleSidebar: () => void;
toggleChatPanel: () => void;
setChatPanelOpen: (open: boolean) => void;
}
const LayoutContext = createContext<LayoutContextType | undefined>(undefined);
function getInitialCollapsed(): boolean {
try {
const stored = localStorage.getItem('sidebar-collapsed');
return stored === 'true';
} catch {
return false;
}
}
interface LayoutProviderProps {
children: ReactNode;
}
export const LayoutProvider: React.FC<LayoutProviderProps> = ({ children }) => {
const [sidebarCollapsed, setSidebarCollapsed] = useState(getInitialCollapsed);
const [chatPanelOpen, setChatPanelOpenState] = useState(false);
const toggleSidebar = useCallback(() => {
setSidebarCollapsed((prev) => {
const next = !prev;
try {
localStorage.setItem('sidebar-collapsed', String(next));
} catch {
// ignore storage errors
}
return next;
});
}, []);
const toggleChatPanel = useCallback(() => {
setChatPanelOpenState((prev) => !prev);
}, []);
const setChatPanelOpen = useCallback((open: boolean) => {
setChatPanelOpenState(open);
}, []);
const value: LayoutContextType = {
sidebarCollapsed,
chatPanelOpen,
toggleSidebar,
toggleChatPanel,
setChatPanelOpen,
};
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>;
};
export const useLayout = (): LayoutContextType => {
const context = useContext(LayoutContext);
if (context === undefined) {
throw new Error('useLayout must be used within a LayoutProvider');
}
return context;
};