itpdp/web/src/hooks/use-party.ts
2026-05-12 23:36:42 +02:00

111 lines
2.5 KiB
TypeScript

import {
createContext,
createElement,
type ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import type {
PartySocketEvent,
PartyState,
} from "../../../api/src/party-types";
import { usePartySocket } from "./use-party-socket";
import { useUser } from "./user";
const emptyPartyState: PartyState = {
party: null,
members: [],
};
type PartyContextValue = PartyState & {
connectionState: "disconnected" | "connecting" | "connected" | "reconnecting";
isConnected: boolean;
isConnecting: boolean;
isReconnecting: boolean;
setPartyState: (state: PartyState) => void;
resetParty: () => void;
};
const PartyContext = createContext<PartyContextValue | null>(null);
function reducePartyState(
state: PartyState,
event: PartySocketEvent,
userId: string,
): PartyState {
switch (event.type) {
case "party_status": {
if (!event.party) return emptyPartyState;
const isMember = event.members.some((member) => member.userId === userId);
if (!isMember) return emptyPartyState;
return { party: event.party, members: event.members };
}
case "member_payload":
case "pong":
case "error":
return state;
}
}
function getApiUrl(): string | null {
if (typeof window === "undefined") return null;
const envUrl = import.meta.env.VITE_BETTER_AUTH_URL;
if (envUrl) return envUrl;
return `${window.location.protocol}//${window.location.host}`;
}
export function PartyProvider({ children }: { children: ReactNode }) {
const { user } = useUser();
const userId = user?.id ?? null;
const [state, setState] = useState<PartyState>(emptyPartyState);
const handleMessage = useCallback(
(event: PartySocketEvent) => {
if (!userId) return;
setState((prev: PartyState) => reducePartyState(prev, event, userId));
},
[userId],
);
const apiUrl = useMemo(() => {
const url = getApiUrl();
if (!url) return null;
return url;
}, []);
const resetParty = useCallback(() => {
setState(emptyPartyState);
}, []);
useEffect(() => {
if (!userId) resetParty();
}, [resetParty, userId]);
const wsState = usePartySocket({
apiUrl: userId ? apiUrl : null,
onMessage: userId ? handleMessage : null,
});
const value = useMemo(
() => ({
...state,
...wsState,
setPartyState: setState,
resetParty,
}),
[state, wsState, resetParty],
);
return createElement(PartyContext.Provider, { value }, children);
}
export function useParty() {
const context = useContext(PartyContext);
if (!context) {
throw new Error("useParty must be used within PartyProvider");
}
return context;
}