small improvements

This commit is contained in:
Daniel Bulant 2026-05-12 23:36:42 +02:00
parent 3dfc773590
commit a7a7eeac0c
No known key found for this signature in database
4 changed files with 90 additions and 23 deletions

View file

@ -15,7 +15,8 @@ import {
export function UserInfo() { export function UserInfo() {
const { user } = useUser(); const { user } = useUser();
const { party, members, isConnecting, isReconnecting } = useParty(); const { party, members, isConnecting, isReconnecting, resetParty } =
useParty();
return ( return (
<Item> <Item>
<ItemMedia> <ItemMedia>
@ -37,8 +38,15 @@ export function UserInfo() {
</ItemDescription> </ItemDescription>
</ItemContent> </ItemContent>
<ItemActions> <ItemActions>
{party && members.length > 1 && ( {party && (
<Button onClick={() => client.api.party.leave.post()}>Leave</Button> <Button
onClick={async () => {
await client.api.party.leave.post();
resetParty();
}}
>
Leave
</Button>
)} )}
</ItemActions> </ItemActions>
</Item> </Item>

View file

@ -1,4 +1,13 @@
import { useCallback, useMemo, useState } from "react"; import {
createContext,
createElement,
type ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import type { import type {
PartySocketEvent, PartySocketEvent,
PartyState, PartyState,
@ -6,14 +15,34 @@ import type {
import { usePartySocket } from "./use-party-socket"; import { usePartySocket } from "./use-party-socket";
import { useUser } from "./user"; 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( function reducePartyState(
state: PartyState, state: PartyState,
event: PartySocketEvent, event: PartySocketEvent,
userId: string,
): PartyState { ): PartyState {
switch (event.type) { switch (event.type) {
case "snapshot": case "party_status": {
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 }; return { party: event.party, members: event.members };
}
case "member_payload": case "member_payload":
case "pong": case "pong":
case "error": case "error":
@ -28,16 +57,18 @@ function getApiUrl(): string | null {
return `${window.location.protocol}//${window.location.host}`; return `${window.location.protocol}//${window.location.host}`;
} }
export function useParty() { export function PartyProvider({ children }: { children: ReactNode }) {
const { session } = useUser(); const { user } = useUser();
const [state, setState] = useState<PartyState>({ const userId = user?.id ?? null;
party: null, const [state, setState] = useState<PartyState>(emptyPartyState);
members: [],
});
const handleMessage = useCallback((event: PartySocketEvent) => { const handleMessage = useCallback(
setState((prev: PartyState) => reducePartyState(prev, event)); (event: PartySocketEvent) => {
}, []); if (!userId) return;
setState((prev: PartyState) => reducePartyState(prev, event, userId));
},
[userId],
);
const apiUrl = useMemo(() => { const apiUrl = useMemo(() => {
const url = getApiUrl(); const url = getApiUrl();
@ -45,13 +76,36 @@ export function useParty() {
return url; return url;
}, []); }, []);
const resetParty = useCallback(() => {
setState(emptyPartyState);
}, []);
useEffect(() => {
if (!userId) resetParty();
}, [resetParty, userId]);
const wsState = usePartySocket({ const wsState = usePartySocket({
apiUrl, apiUrl: userId ? apiUrl : null,
onMessage: session ? handleMessage : null, onMessage: userId ? handleMessage : null,
}); });
return { const value = useMemo(
...state, () => ({
...wsState, ...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;
} }

View file

@ -1,4 +1,8 @@
import { treaty } from "@elysiajs/eden"; import { treaty } from "@elysiajs/eden";
import type { App } from "../../../api/src/index"; import type { App } from "../../../api/src/index";
export const client = treaty<App>("aura.rpi1.danbulant.cloud", {}); // export const client = treaty<App>("aura.rpi1.danbulant.cloud", {});
export const client = treaty<App>(
process.env.VITE_BETTER_AUTH_URL || "127.0.0.1:3000",
{},
);

View file

@ -10,6 +10,7 @@ import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
import type * as React from "react"; import type * as React from "react";
import { useEffect } from "react"; import { useEffect } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { PartyProvider } from "#/hooks/use-party";
import type { AuthSession } from "#/lib/auth.serverfn"; import type { AuthSession } from "#/lib/auth.serverfn";
import { fetchSession, sessionQueryKey } from "#/lib/auth-client"; import { fetchSession, sessionQueryKey } from "#/lib/auth-client";
import { client } from "#/lib/eden"; import { client } from "#/lib/eden";
@ -131,7 +132,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent /> <HeadContent />
</head> </head>
<body className="font-sans antialiased wrap-anywhere dark"> <body className="font-sans antialiased wrap-anywhere dark">
{children} <PartyProvider>{children}</PartyProvider>
<Toaster /> <Toaster />
<TanStackDevtools <TanStackDevtools
config={{ config={{