import { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import type { Conversation, PublicUser, User } from '@altricade/core';
import { getPresence, heartbeat } from '@altricade/core/api';
import { SessionProvider, useSession } from '../entities/session';
import { AuthForm } from '../features/auth';
import { RealtimeProvider, useRealtime } from '../features/realtime';
import { useConversations, ConversationSidebar } from '../features/conversations';
import { ContactsPanel } from '../features/contacts';
import { ChatView } from '../features/messaging';
import { apiConfig } from '../shared/api';
import { useTheme } from '../shared/theme';
import type { ThemePreference } from '../shared/theme';
const PREFERENCES: readonly ThemePreference[] = ['light', 'dark', 'system'];
const ThemeSwitch = (): ReactElement => {
const { preference, setPreference } = useTheme();
return (
{PREFERENCES.map((option) => (
))}
);
};
const Dashboard = ({ user, onLogout }: { user: User; onLogout: () => void }): ReactElement => {
const me: PublicUser = {
id: user.id,
username: user.username,
displayName: user.displayName,
avatarUrl: user.avatarUrl,
};
const { state } = useRealtime();
const { conversations, startDirect, createGroupChat } = useConversations(user.id);
const [current, setCurrent] = useState(null);
const [onlineMap, setOnlineMap] = useState>({});
const peerKey = conversations
.flatMap((conversation) => (conversation.peer === null ? [] : [conversation.peer.id]))
.join(',');
// Poll presence for the people we have DMs with.
useEffect(() => {
const ids = peerKey.split(',').filter((id) => id.length > 0);
if (ids.length === 0) {
return undefined;
}
let cancelled = false;
const poll = async (): Promise => {
try {
const presences = await getPresence(apiConfig, ids);
if (!cancelled) {
const map: Record = {};
for (const presence of presences) {
map[presence.userId] = presence.online;
}
setOnlineMap(map);
}
} catch {
// presence is best-effort
}
};
void poll();
const interval = setInterval(() => {
void poll();
}, 20000);
return () => {
cancelled = true;
clearInterval(interval);
};
}, [peerKey]);
// Keep-alive heartbeat so our own last-seen stays fresh while connected.
useEffect(() => {
const interval = setInterval(() => {
void heartbeat(apiConfig);
}, 45000);
return () => {
clearInterval(interval);
};
}, []);
return (
@{user.username} · socket: {state}
{
setCurrent(await startDirect(username));
}}
onCreateGroup={async (title, members) => {
setCurrent(await createGroupChat(title, members));
}}
/>
{
setCurrent(await startDirect(username));
}}
/>
{current !== null ? (
) : (
Search for a user or pick a contact to start chatting.
)}
);
};
const Shell = (): ReactElement => {
const { status, user, logout } = useSession();
if (status === 'loading') {
return (
Loading…
);
}
if (user === null) {
return ;
}
return (
{
void logout();
}}
/>
);
};
export const App = (): ReactElement => (
);