fix(voice): live connection state instead of static SSR snapshot
- VoiceView now reads connected/activeChannelName from useVoiceStatus (SWR live, invalidated by connect/disconnect) instead of initialStatus - Seed useSpeakers from live status.activeSpeakers - Add 4s refreshInterval to useVoiceStatus so state converges (tsc clean, next build green)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Headphones, Loader2, Radio, RadioOff } from "lucide-react";
|
import { Headphones, Loader2, Radio, RadioOff } from "lucide-react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { SignalField } from "@/components/three";
|
import { SignalField } from "@/components/three";
|
||||||
import { WebGLGuard } from "@/components/three/webgl-guard";
|
import { WebGLGuard } from "@/components/three/webgl-guard";
|
||||||
import { StaticFallback } from "@/components/three/static-fallback";
|
import { StaticFallback } from "@/components/three/static-fallback";
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
useVoiceConnect,
|
useVoiceConnect,
|
||||||
useVoiceDisconnect,
|
useVoiceDisconnect,
|
||||||
useVoiceListen,
|
useVoiceListen,
|
||||||
|
useVoiceStatus,
|
||||||
} from "@/hooks";
|
} from "@/hooks";
|
||||||
import type { VoiceStatus } from "@/lib/types";
|
import type { VoiceStatus } from "@/lib/types";
|
||||||
import { useWebSocket } from "@/lib/ws/context";
|
import { useWebSocket } from "@/lib/ws/context";
|
||||||
@@ -30,11 +31,11 @@ export default function VoiceView({ initialStatus }: { initialStatus?: VoiceStat
|
|||||||
const [selectedGuild, setSelectedGuild] = useState("");
|
const [selectedGuild, setSelectedGuild] = useState("");
|
||||||
const [selectedChannel, setSelectedChannel] = useState("");
|
const [selectedChannel, setSelectedChannel] = useState("");
|
||||||
|
|
||||||
const initialSpeakers = useMemo(
|
// Live connection status — SWR revalidates on connect/disconnect (the
|
||||||
() => initialStatus?.activeSpeakers ?? [],
|
// useVoiceConnect/Disconnect actions invalidate the "voice-status" key),
|
||||||
[initialStatus],
|
// so this reflects real-time state instead of the static SSR snapshot.
|
||||||
);
|
const { data: status } = useVoiceStatus(initialStatus);
|
||||||
const { speakers, subscribe } = useSpeakers(initialSpeakers);
|
const { speakers, subscribe } = useSpeakers(status?.activeSpeakers ?? []);
|
||||||
const { data: guilds = [] } = useGuilds();
|
const { data: guilds = [] } = useGuilds();
|
||||||
const { data: voiceChannels = [] } = useVoiceChannels(selectedGuild);
|
const { data: voiceChannels = [] } = useVoiceChannels(selectedGuild);
|
||||||
const connect = useVoiceConnect();
|
const connect = useVoiceConnect();
|
||||||
@@ -47,7 +48,7 @@ export default function VoiceView({ initialStatus }: { initialStatus?: VoiceStat
|
|||||||
}, [subscribe, ws]);
|
}, [subscribe, ws]);
|
||||||
|
|
||||||
const active = speakers.filter((s) => s.speaking);
|
const active = speakers.filter((s) => s.speaking);
|
||||||
const connected = initialStatus?.connected ?? false;
|
const connected = status?.connected ?? false;
|
||||||
|
|
||||||
const handleGuildChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleGuildChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const g = e.target.value;
|
const g = e.target.value;
|
||||||
@@ -63,10 +64,10 @@ export default function VoiceView({ initialStatus }: { initialStatus?: VoiceStat
|
|||||||
<Badge tone={connected ? "signal" : "neutral"} dot>
|
<Badge tone={connected ? "signal" : "neutral"} dot>
|
||||||
{connected ? "Connected" : "Disconnected"}
|
{connected ? "Connected" : "Disconnected"}
|
||||||
</Badge>
|
</Badge>
|
||||||
{connected && initialStatus?.activeChannelName && (
|
{connected && status?.activeChannelName && (
|
||||||
<span className="hidden items-center gap-1.5 text-xs text-[var(--color-ink-soft)] sm:flex">
|
<span className="hidden items-center gap-1.5 text-xs text-[var(--color-ink-soft)] sm:flex">
|
||||||
<Headphones className="size-3.5" />
|
<Headphones className="size-3.5" />
|
||||||
{initialStatus.activeChannelName}
|
{status.activeChannelName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function useVoiceStatus(initialData?: VoiceStatus) {
|
|||||||
return useSWR<VoiceStatus>(STATUS_KEY, () => voiceApi.getStatus(), {
|
return useSWR<VoiceStatus>(STATUS_KEY, () => voiceApi.getStatus(), {
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
fallbackData: initialData,
|
fallbackData: initialData,
|
||||||
|
refreshInterval: 4000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user