refactor: large codebase cleanup - consolidate schemas, migrate to Drizzle ORM, extract frontend components, modernize Docker builds
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
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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
63f21513bd
commit
5802d02e29
@@ -9,6 +9,12 @@ interface DetailStatProps {
|
||||
suffix?: string;
|
||||
}
|
||||
|
||||
const valueColor = {
|
||||
default: "",
|
||||
danger: "text-red-400",
|
||||
success: "text-emerald-400",
|
||||
};
|
||||
|
||||
/**
|
||||
* Small stat label used inside detail views.
|
||||
*/
|
||||
@@ -19,15 +25,13 @@ export function DetailStat({
|
||||
suffix,
|
||||
}: DetailStatProps) {
|
||||
return (
|
||||
<Card>
|
||||
<Card className="bg-gradient-to-br from-cyan-500/5 to-transparent border-cyan-500/10">
|
||||
<CardContent className="p-3">
|
||||
<p className="text-xs text-muted-foreground">{label}</p>
|
||||
<p className="text-xs text-muted-foreground/70 tracking-wide">
|
||||
{label}
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
"text-lg font-bold tabular-nums",
|
||||
variant === "danger" && "text-destructive",
|
||||
variant === "success" && "text-green-500",
|
||||
)}
|
||||
className={cn("text-lg font-bold tabular-nums", valueColor[variant])}
|
||||
>
|
||||
{formatNumber(value)}
|
||||
{suffix}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { configApi, voiceApi } from "@/lib/api";
|
||||
import type { Guild } from "@/lib/types";
|
||||
import { useConfig, useGuilds } from "@/hooks";
|
||||
|
||||
export interface GuildSelectorProps {
|
||||
/** Currently selected guild ID */
|
||||
@@ -34,51 +33,22 @@ export function GuildSelector({
|
||||
onChange,
|
||||
autoHide = true,
|
||||
}: GuildSelectorProps) {
|
||||
const [guilds, setGuilds] = useState<Guild[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { data: guilds = [], isLoading, error, refetch } = useGuilds();
|
||||
const { data: config } = useConfig();
|
||||
|
||||
const initDone = useRef(false);
|
||||
|
||||
const fetchGuilds = useCallback(() => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const tryAutoSelect = (list: Guild[]) => {
|
||||
if (value || list.length === 0 || initDone.current) return;
|
||||
initDone.current = true;
|
||||
// Try config's monitorGuildId first, then fall back to first guild
|
||||
configApi
|
||||
.get()
|
||||
.then((cfg) => {
|
||||
const preferred = cfg.monitorGuildId ?? list[0].id;
|
||||
if (preferred) onChange(preferred);
|
||||
})
|
||||
.catch(() => {
|
||||
onChange(list[0].id);
|
||||
});
|
||||
};
|
||||
|
||||
voiceApi
|
||||
.getGuilds()
|
||||
.then((list) => {
|
||||
setGuilds(list);
|
||||
tryAutoSelect(list);
|
||||
})
|
||||
.catch((err) =>
|
||||
setError(err instanceof Error ? err.message : "Failed to load guilds"),
|
||||
)
|
||||
.finally(() => setLoading(false));
|
||||
}, [value, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGuilds();
|
||||
}, [fetchGuilds]);
|
||||
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 && !loading && !error) return null;
|
||||
if (autoHide && guilds.length <= 1 && !isLoading && !error) return null;
|
||||
|
||||
if (loading) {
|
||||
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" />
|
||||
@@ -93,10 +63,10 @@ export function GuildSelector({
|
||||
<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}
|
||||
Could not load guilds: {error?.message ?? "Failed to load"}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={fetchGuilds}>
|
||||
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||||
<RefreshCw className="size-3 mr-1" />
|
||||
Retry
|
||||
</Button>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function LoadingSkeleton({
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-3",
|
||||
columns > 1 ? `grid-cols-1 md:grid-cols-${columns}` : "grid-cols-1",
|
||||
columns > 1 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -8,9 +8,30 @@ interface StatCardProps {
|
||||
label: string;
|
||||
value: number;
|
||||
icon: LucideIcon;
|
||||
variant?: "default" | "danger" | "success";
|
||||
variant?: "default" | "danger" | "success" | "warning";
|
||||
}
|
||||
|
||||
const variantStyles = {
|
||||
default: "from-cyan-500/10 to-teal-500/5 border-cyan-500/20",
|
||||
danger: "from-red-500/10 to-rose-500/5 border-red-500/20",
|
||||
success: "from-emerald-500/10 to-green-500/5 border-emerald-500/20",
|
||||
warning: "from-amber-500/10 to-yellow-500/5 border-amber-500/20",
|
||||
};
|
||||
|
||||
const iconBg = {
|
||||
default: "bg-cyan-500/15 text-cyan-400",
|
||||
danger: "bg-red-500/15 text-red-400",
|
||||
success: "bg-emerald-500/15 text-emerald-400",
|
||||
warning: "bg-amber-500/15 text-amber-400",
|
||||
};
|
||||
|
||||
const valueColor = {
|
||||
default: "",
|
||||
danger: "text-red-400",
|
||||
success: "text-emerald-400",
|
||||
warning: "text-amber-400",
|
||||
};
|
||||
|
||||
/**
|
||||
* Metric card used across dashboard and landing pages.
|
||||
*/
|
||||
@@ -21,16 +42,22 @@ export function StatCard({
|
||||
variant = "default",
|
||||
}: StatCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<Card
|
||||
className={cn(
|
||||
"border bg-gradient-to-br backdrop-blur-sm",
|
||||
variantStyles[variant],
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs text-muted-foreground">{label}</p>
|
||||
<p className="text-xs text-muted-foreground/80 tracking-wide">
|
||||
{label}
|
||||
</p>
|
||||
<p
|
||||
className={cn(
|
||||
"text-2xl font-bold tabular-nums tracking-tight",
|
||||
variant === "danger" && "text-destructive",
|
||||
variant === "success" && "text-green-500",
|
||||
valueColor[variant],
|
||||
)}
|
||||
>
|
||||
{formatNumber(value)}
|
||||
@@ -39,11 +66,7 @@ export function StatCard({
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-9 shrink-0 items-center justify-center rounded-lg",
|
||||
variant === "danger"
|
||||
? "bg-destructive/10 text-destructive"
|
||||
: variant === "success"
|
||||
? "bg-green-500/10 text-green-500"
|
||||
: "bg-primary/10 text-primary",
|
||||
iconBg[variant],
|
||||
)}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
|
||||
Reference in New Issue
Block a user