Build & Deploy / build-and-push (discord-gateway) (push) Failing after 2m22s
Build & Deploy / build-and-push (backend) (push) Failing after 3m22s
Build & Deploy / build-and-push (proxy) (push) Successful in 1m36s
Build & Deploy / deploy (push) Skipped
- Consolidate all DB schema definitions into packages/shared as single source of truth - Migrate backend from raw SQL to Drizzle ORM across all modules - Extract frontend inline UI into separate component files - Refactor discord-gateway circuitBreaker into conversationState + moderationState - Convert messageStore to Proxy singleton pattern - Add validateBody/validateQuery middleware + Zod schemas for API endpoints - Modernize Docker builds with multi-stage + pnpm deploy - Migrate CI/CD from deployment to image-based pipeline - Remove 60+ unused/dead files (~15K lines) - Update color scheme from sky-blue to teal-cyan - Move DB connection management to @bete/shared/database Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { AlertCircle, RefreshCw } from "lucide-react";
|
|
import { useEffect, useRef } from "react";
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { useConfig, useGuilds } from "@/hooks";
|
|
|
|
export interface GuildSelectorProps {
|
|
/** Currently selected guild ID */
|
|
value: string;
|
|
/** Called when user selects a different guild */
|
|
onChange: (guildId: string) => void;
|
|
/** If true, the bar is hidden when there's only one guild */
|
|
autoHide?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Guild selector bar — fetches the guild list and renders a <Select>.
|
|
* Optionally auto-hides when there's exactly one guild.
|
|
*/
|
|
export function GuildSelector({
|
|
value,
|
|
onChange,
|
|
autoHide = true,
|
|
}: GuildSelectorProps) {
|
|
const { data: guilds = [], isLoading, error, refetch } = useGuilds();
|
|
const { data: config } = useConfig();
|
|
|
|
const initDone = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (value || guilds.length === 0 || initDone.current) return;
|
|
initDone.current = true;
|
|
const preferred = config?.monitorGuildId ?? guilds[0].id;
|
|
if (preferred) onChange(preferred);
|
|
}, [value, guilds, config, onChange]);
|
|
|
|
// Auto-hide when there's exactly one guild and autoHide is on
|
|
if (autoHide && guilds.length <= 1 && !isLoading && !error) return null;
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center gap-3 rounded-xl border border-border/50 bg-card p-3">
|
|
<Skeleton className="h-8 w-36" />
|
|
<Skeleton className="h-8 w-8 rounded-full" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex items-center justify-between rounded-xl border border-destructive/20 bg-destructive/5 p-3">
|
|
<div className="flex items-center gap-2">
|
|
<AlertCircle className="size-4 text-destructive shrink-0" />
|
|
<p className="text-sm text-muted-foreground">
|
|
Could not load guilds: {error?.message ?? "Failed to load"}
|
|
</p>
|
|
</div>
|
|
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
|
<RefreshCw className="size-3 mr-1" />
|
|
Retry
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (guilds.length === 0) {
|
|
return (
|
|
<div className="rounded-xl border border-yellow-500/20 bg-yellow-500/5 p-3">
|
|
<div className="flex items-center gap-2">
|
|
<AlertCircle className="size-4 text-yellow-500 shrink-0" />
|
|
<p className="text-sm text-muted-foreground">
|
|
No guilds available. Make sure the Discord gateway is connected.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-3 rounded-xl border border-border/50 bg-card p-3">
|
|
<Badge variant="outline" className="shrink-0 text-xs font-normal">
|
|
Guild
|
|
</Badge>
|
|
<Select value={value} onValueChange={(v) => v && onChange(v)}>
|
|
<SelectTrigger className="h-8 w-full max-w-xs">
|
|
<SelectValue placeholder="Select a guild…">
|
|
{guilds.find((g) => g.id === value)?.name}
|
|
</SelectValue>
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{guilds.map((g) => (
|
|
<SelectItem key={g.id} value={g.id}>
|
|
{g.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
);
|
|
}
|