feat: add app header, sidebar, and mobile navigation components
Deploy to VPS / deploy (push) Failing after 1m45s
Deploy to VPS / deploy (push) Failing after 1m45s
- Implemented AppHeader component with theme toggle and connection status. - Created AppSidebar component for navigation with connection status indicator. - Added MobileNav component for mobile navigation with responsive design. - Introduced shared components: DetailStat, EmptyState, ErrorState, LoadingSkeleton, and StatCard for consistent UI. - Developed hooks for async data fetching: useAsync, useConfig, useDashboard, useGuilds, useMedia, useMessages, useRecordings, and useVoice. - Added chatbot API functions for sending messages and managing chat history.
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { dashboardApi } from "@/lib/api";
|
||||
import type {
|
||||
DashboardChannel,
|
||||
DashboardChannelDetail,
|
||||
DashboardStats,
|
||||
DashboardUser,
|
||||
DashboardUserDetail,
|
||||
} from "@/lib/types";
|
||||
|
||||
// ── Stats ───────────────────────────────────────
|
||||
|
||||
interface UseStatsReturn {
|
||||
stats: DashboardStats | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
export function useStats(): UseStatsReturn {
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await dashboardApi.getStats();
|
||||
setStats(data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load stats");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { stats, loading, error, refetch: fetch };
|
||||
}
|
||||
|
||||
// ── Users ───────────────────────────────────────
|
||||
|
||||
interface UseUsersReturn {
|
||||
users: DashboardUser[];
|
||||
loading: boolean;
|
||||
search: string;
|
||||
setSearch: (q: string) => void;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
export function useUsers(): UseUsersReturn {
|
||||
const [users, setUsers] = useState<DashboardUser[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const fetch = useCallback(async (q?: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await dashboardApi.listUsers(20, undefined, q);
|
||||
setUsers(result.data);
|
||||
} catch {
|
||||
// silently fail
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchWithSearch = useCallback(() => {
|
||||
fetch(search || undefined);
|
||||
}, [fetch, search]);
|
||||
|
||||
return {
|
||||
users,
|
||||
loading,
|
||||
search,
|
||||
setSearch,
|
||||
refetch: fetchWithSearch,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Channels ────────────────────────────────────
|
||||
|
||||
interface UseChannelsReturn {
|
||||
channels: DashboardChannel[];
|
||||
loading: boolean;
|
||||
search: string;
|
||||
setSearch: (q: string) => void;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
export function useChannels(guildId: string): UseChannelsReturn {
|
||||
const [channels, setChannels] = useState<DashboardChannel[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const fetch = useCallback(
|
||||
async (q?: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await dashboardApi.listChannels(
|
||||
20,
|
||||
q,
|
||||
guildId || undefined,
|
||||
);
|
||||
setChannels(result.data);
|
||||
} catch {
|
||||
// silently fail
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[guildId],
|
||||
);
|
||||
|
||||
const fetchWithSearch = useCallback(() => {
|
||||
fetch(search || undefined);
|
||||
}, [fetch, search]);
|
||||
|
||||
return {
|
||||
channels,
|
||||
loading,
|
||||
search,
|
||||
setSearch,
|
||||
refetch: fetchWithSearch,
|
||||
};
|
||||
}
|
||||
|
||||
// ── User Detail ─────────────────────────────────
|
||||
|
||||
export function useUserDetail() {
|
||||
const [user, setUser] = useState<DashboardUserDetail | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetch = useCallback(async (userId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const detail = await dashboardApi.getUserDetail(userId);
|
||||
setUser(detail);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { user, loading, fetch };
|
||||
}
|
||||
|
||||
// ── Channel Detail ──────────────────────────────
|
||||
|
||||
export function useChannelDetail() {
|
||||
const [channel, setChannel] = useState<DashboardChannelDetail | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetch = useCallback(async (channelId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const detail = await dashboardApi.getChannelDetail(channelId);
|
||||
setChannel(detail);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { channel, loading, fetch };
|
||||
}
|
||||
Reference in New Issue
Block a user