- Update CSS foundation: HSL→oklch color tokens, Poppins font, new utilities - Replace Three.js sakura particles with CSS glow orbs - Rebrand Header with IMPHNEN logo and gradient text - Update all UI components (Button, Card, Badge, Input, Select, Tabs, Toast) - Apply IMPHNEN design patterns (glass, gradient, grid) to layout - Remove all hardcoded #7EC8E3/#FFB7C5/#FCA5A5 references - Standardize rounded-xl across all components - Replace emoji toasts with Lucide icons Co-Authored-By: Claude <noreply@anthropic.com>
108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
import { Headphones, Radio } from "lucide-react";
|
|
import type { Channel, Guild, VoiceStatus } from "../../../shared/api/client";
|
|
import { Button, Select } from "../../../shared/ui";
|
|
|
|
interface VoiceConnectionCardProps {
|
|
guilds: Guild[];
|
|
voiceChannels: Channel[];
|
|
selectedGuild: string;
|
|
selectedChannel: string;
|
|
status: VoiceStatus;
|
|
voiceLoading: boolean;
|
|
isListening: boolean;
|
|
isStreaming: boolean;
|
|
onGuildChange: (id: string) => void;
|
|
onChannelChange: (id: string) => void;
|
|
onJoin: () => void;
|
|
onDisconnect: () => void;
|
|
onListenToggle: () => void;
|
|
onStreamingToggle: () => void;
|
|
}
|
|
|
|
export function VoiceConnectionCard({
|
|
guilds,
|
|
voiceChannels,
|
|
selectedGuild,
|
|
selectedChannel,
|
|
status,
|
|
voiceLoading,
|
|
isListening,
|
|
isStreaming,
|
|
onGuildChange,
|
|
onChannelChange,
|
|
onJoin,
|
|
onDisconnect,
|
|
onListenToggle,
|
|
onStreamingToggle,
|
|
}: VoiceConnectionCardProps) {
|
|
return (
|
|
<div className="rounded-xl border border-border bg-card shadow-sm">
|
|
<div className="p-6">
|
|
<h3 className="flex items-center gap-2 text-lg font-semibold tracking-tight">
|
|
<Radio className="h-5 w-5 text-primary" /> Voice Bridge
|
|
</h3>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
Join a Discord voice channel, listen, and transmit audio.
|
|
</p>
|
|
|
|
<div className="mt-4 grid gap-4 md:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground">Guild</label>
|
|
<Select
|
|
value={selectedGuild}
|
|
onChange={(e) => onGuildChange(e.target.value)}
|
|
placeholder="Select guild"
|
|
options={guilds.map((g) => ({ value: g.id, label: g.name }))}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-foreground">
|
|
Voice Channel
|
|
</label>
|
|
<Select
|
|
value={selectedChannel}
|
|
onChange={(e) => onChannelChange(e.target.value)}
|
|
placeholder="Select voice channel"
|
|
options={voiceChannels.map((c) => ({
|
|
value: c.id,
|
|
label: c.name,
|
|
}))}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 flex flex-wrap gap-2">
|
|
<Button
|
|
disabled={!selectedGuild || !selectedChannel || voiceLoading}
|
|
onClick={onJoin}
|
|
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
|
>
|
|
{status.connected ? "Reconnect" : "Join Voice"}
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
disabled={!status.connected || voiceLoading}
|
|
onClick={onDisconnect}
|
|
>
|
|
Disconnect
|
|
</Button>
|
|
<Button
|
|
variant={isListening ? "secondary" : "outline"}
|
|
onClick={onListenToggle}
|
|
>
|
|
<Headphones className="mr-1.5 h-4 w-4" />{" "}
|
|
{isListening ? "Stop Listening" : "Listen"}
|
|
</Button>
|
|
<Button
|
|
variant={isStreaming ? "secondary" : "outline"}
|
|
onClick={onStreamingToggle}
|
|
>
|
|
<Radio className="mr-1.5 h-4 w-4" />{" "}
|
|
{isStreaming ? "Stop Transmit" : "Transmit"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|