spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/pages/admin.tsx6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { useState, useEffect } from "react";18import { useLocation } from "wouter";19import { Shield, Database, FileText, MessageSquare, Trash2, Sparkles, DollarSign, TrendingUp, Users as UsersIcon, Activity, Zap, AlertCircle, RefreshCw } from "lucide-react";20import { Button } from "@/components/ui/button";21import { Input } from "@/components/ui/input";22import { Label } from "@/components/ui/label";23import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";24import { Alert, AlertDescription } from "@/components/ui/alert";25import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";26import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";27import { Badge } from "@/components/ui/badge";2829interface User {30 id: string;31 displayName: string;32 createdAt: string;33}3435interface Session {36 id: string;37 sessionId: string;38 userId: string | null;39 title: string;40 createdAt: string;41 updatedAt: string;42 inputTokens: number;43 outputTokens: number;44 totalCost: number;45}4647interface SharedReport {48 id: string;49 shareId: string;50 question: string;51 createdAt: string;52}5354interface UserMetric {55 userId: string;56 displayName: string;57 inputTokens: number;58 outputTokens: number;59 totalTokens: number;60 cost: number;61 sessionCount: number;62}6364interface Metrics {65 totalInputTokens: number;66 totalOutputTokens: number;67 totalTokens: number;68 totalCost: number;69 totalSessions: number;70 averageCostPerSession: number;71 userMetrics: UserMetric[];72}7374interface DatabaseData {75 users: User[];76 sessions: Session[];77 sharedReports: SharedReport[];78 metrics: Metrics;79}8081interface ActiveUsersData {82 total: number;83 generating: number;84 idle: number;85 error: number;86 users: Array<{87 sessionId: string;88 userId: string | null;89 status: string;90 currentQuery: string | null;91 lastHeartbeat: string;92 userAgent: string | null;93 }>;94}9596interface RealTimeStats {97 activeUsers: {98 total: number;99 generating: number;100 };101 sessions: {102 total: number;103 today: number;104 };105 tokens: {106 total: number;107 input: number;108 output: number;109 };110 cost: {111 total: number;112 today: number;113 };114 errors: {115 lastHour: number;116 rate: number;117 };118}119120export default function Admin() {121 const [, setLocation] = useLocation();122 const [isAuthenticated, setIsAuthenticated] = useState(false);123 const [password, setPassword] = useState("");124 const [error, setError] = useState("");125 const [isLoading, setIsLoading] = useState(true);126 const [databaseData, setDatabaseData] = useState<DatabaseData | null>(null);127 const [activeUsersData, setActiveUsersData] = useState<ActiveUsersData | null>(null);128 const [realTimeStats, setRealTimeStats] = useState<RealTimeStats | null>(null);129 const [autoRefresh, setAutoRefresh] = useState(true);130131 // Auto-login on mount (no password required)132 useEffect(() => {133 const autoLogin = async () => {134 try {135 const response = await fetch("/api/admin/login", {136 method: "POST",137 headers: {138 "Content-Type": "application/json",139 },140 body: JSON.stringify({}),141 });142143 if (response.ok) {144 setIsAuthenticated(true);145 await refreshAllData();146 }147 } catch (err) {148 console.error("Auto-login failed:", err);149 } finally {150 setIsLoading(false);151 }152 };153154 autoLogin();155 }, []);156157 // Auto-refresh data every 10 seconds if enabled158 useEffect(() => {159 if (!isAuthenticated || !autoRefresh) return;160161 const interval = setInterval(() => {162 refreshAllData();163 }, 10000); // Refresh every 10 seconds164165 return () => clearInterval(interval);166 }, [isAuthenticated, autoRefresh]);167168 const handleLogin = async (e: React.FormEvent) => {169 e.preventDefault();170 setError("");171 setIsLoading(true);172173 try {174 const response = await fetch("/api/admin/login", {175 method: "POST",176 headers: {177 "Content-Type": "application/json",178 },179 body: JSON.stringify({ password }),180 });181182 const data = await response.json();183184 if (!response.ok) {185 throw new Error(data.error || "Failed to login");186 }187188 setIsAuthenticated(true);189 await loadDatabaseData();190 } catch (err: any) {191 setError(err.message || "Failed to login");192 } finally {193 setIsLoading(false);194 }195 };196197 const loadDatabaseData = async () => {198 try {199 const response = await fetch("/api/admin/database");200201 if (!response.ok) {202 if (response.status === 403) {203 setIsAuthenticated(false);204 return;205 }206 throw new Error("Failed to load database data");207 }208209 const data = await response.json();210 setDatabaseData(data);211 } catch (err: any) {212 setError(err.message || "Failed to load database data");213 }214 };215216 const loadActiveUsers = async () => {217 try {218 const response = await fetch("/api/analytics/active-users");219220 if (response.ok) {221 const data = await response.json();222 setActiveUsersData(data);223 }224 } catch (err: any) {225 console.error("Failed to load active users:", err);226 }227 };228229 const loadRealTimeStats = async () => {230 try {231 const response = await fetch("/api/analytics/real-time-stats");232233 if (response.ok) {234 const data = await response.json();235 setRealTimeStats(data);236 }237 } catch (err: any) {238 console.error("Failed to load real-time stats:", err);239 }240 };241242 const refreshAllData = async () => {243 await Promise.all([244 loadDatabaseData(),245 loadActiveUsers(),246 loadRealTimeStats(),247 ]);248 };249250 const deleteSession = async (id: string) => {251 if (!confirm("Are you sure you want to delete this session?")) {252 return;253 }254255 try {256 const response = await fetch(`/api/admin/sessions/${id}`, {257 method: "DELETE",258 });259260 if (!response.ok) {261 throw new Error("Failed to delete session");262 }263264 await loadDatabaseData();265 } catch (err: any) {266 setError(err.message || "Failed to delete session");267 }268 };269270 const deleteReport = async (id: string) => {271 if (!confirm("Are you sure you want to delete this shared report?")) {272 return;273 }274275 try {276 const response = await fetch(`/api/admin/reports/${id}`, {277 method: "DELETE",278 });279280 if (!response.ok) {281 throw new Error("Failed to delete report");282 }283284 await loadDatabaseData();285 } catch (err: any) {286 setError(err.message || "Failed to delete report");287 }288 };289290 const formatDate = (dateString: string) => {291 return new Date(dateString).toLocaleString();292 };293294 // Show loading screen during auto-login295 if (!isAuthenticated && isLoading) {296 return (297 <div className="min-h-screen bg-gradient-to-br from-background via-background to-primary/5 flex items-center justify-center">298 <div className="text-center">299 <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-4">300 <Database className="w-8 h-8 text-primary animate-pulse" />301 </div>302 <p className="text-muted-foreground">Loading admin panel...</p>303 </div>304 </div>305 );306 }307308 if (!isAuthenticated) {309 return (310 <div className="min-h-screen bg-gradient-to-br from-background via-background to-primary/5 flex flex-col relative overflow-hidden">311 {/* Decorative gradient blobs */}312 <div className="absolute inset-0 overflow-hidden pointer-events-none">313 <div className="absolute top-0 right-0 w-96 h-96 bg-primary/10 rounded-full blur-3xl transform translate-x-1/2 -translate-y-1/2" />314 <div className="absolute bottom-0 left-0 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl transform -translate-x-1/2 translate-y-1/2" />315 </div>316317 {/* Header */}318 <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/60 backdrop-blur-xl">319 <div className="container flex h-16 items-center justify-between px-4">320 <button321 onClick={() => setLocation('/')}322 className="flex items-center gap-2 hover-elevate active-elevate-2 px-3 py-1.5 rounded-md cursor-pointer transition-all duration-200"323 >324 <Sparkles className="w-6 h-6 text-primary" />325 <div className="hidden md:flex items-baseline gap-2">326 <span className="text-xl font-semibold bg-gradient-to-r from-primary to-purple-500 bg-clip-text text-transparent">327 VQuant328 </span>329 <span className="text-sm text-muted-foreground">330 by Agentilab.ai331 </span>332 </div>333 </button>334 </div>335 </header>336337 {/* Main Content */}338 <main className="flex-1 flex items-center justify-center px-4 py-12 relative z-10">339 <div className="w-full max-w-md">340 <div className="text-center mb-8 animate-in fade-in slide-in-from-top-4 duration-700">341 <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-4">342 <Shield className="w-8 h-8 text-primary" />343 </div>344 <h1 className="text-4xl font-bold mb-3 bg-gradient-to-r from-primary via-purple-500 to-primary bg-clip-text text-transparent">345 Admin Access346 </h1>347 <p className="text-muted-foreground">348 Enter the admin password to access the database panel349 </p>350 </div>351352 <Card className="border-border/50 shadow-2xl backdrop-blur-sm bg-card/95">353 <CardHeader className="space-y-3 pb-6">354 <CardTitle className="flex items-center gap-2 text-2xl">355 <Database className="w-5 h-5 text-primary" />356 Database Administration357 </CardTitle>358 <CardDescription className="text-base">359 View and manage all database records360 </CardDescription>361 </CardHeader>362 <CardContent className="pt-0">363 <form onSubmit={handleLogin} className="space-y-5">364 <div className="space-y-2">365 <Label htmlFor="admin-password" className="text-sm font-medium">Admin Password</Label>366 <Input367 id="admin-password"368 type="password"369 placeholder="Enter admin password"370 value={password}371 onChange={(e) => setPassword(e.target.value)}372 required373 disabled={isLoading}374 className="h-11 bg-background/50 border-border/50 focus:border-primary transition-all"375 />376 </div>377378 {error && (379 <Alert variant="destructive">380 <AlertDescription>{error}</AlertDescription>381 </Alert>382 )}383384 <Button385 type="submit"386 className="w-full h-11 bg-gradient-to-r from-primary to-purple-500 hover:from-primary/90 hover:to-purple-500/90 text-white shadow-lg hover:shadow-xl transition-all duration-200"387 disabled={isLoading}388 >389 {isLoading ? "Authenticating..." : "Access Admin Panel"}390 </Button>391 </form>392 </CardContent>393 </Card>394 </div>395 </main>396 </div>397 );398 }399400 return (401 <div className="min-h-screen bg-gradient-to-br from-background via-background to-primary/5">402 {/* Header */}403 <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/60 backdrop-blur-xl">404 <div className="container flex h-16 items-center justify-between px-4">405 <button406 onClick={() => setLocation('/')}407 className="flex items-center gap-2 hover-elevate active-elevate-2 px-3 py-1.5 rounded-md cursor-pointer transition-all duration-200"408 >409 <Sparkles className="w-6 h-6 text-primary" />410 <div className="hidden md:flex items-baseline gap-2">411 <span className="text-xl font-semibold bg-gradient-to-r from-primary to-purple-500 bg-clip-text text-transparent">412 VQuant Admin413 </span>414 </div>415 </button>416 <Button417 variant="outline"418 onClick={() => {419 setIsAuthenticated(false);420 setPassword("");421 }}422 >423 Logout424 </Button>425 </div>426 </header>427428 {/* Main Content */}429 <main className="container px-4 py-8">430 <div className="mb-8">431 <h1 className="text-3xl font-bold mb-2 flex items-center gap-2">432 <Database className="w-8 h-8 text-primary" />433 Database Administration434 </h1>435 <p className="text-muted-foreground">436 View and manage all database records437 </p>438 </div>439440 {error && (441 <Alert variant="destructive" className="mb-6">442 <AlertDescription>{error}</AlertDescription>443 </Alert>444 )}445446 {databaseData && (447 <Tabs defaultValue="metrics" className="w-full">448 <div className="flex items-center justify-between mb-4">449 <TabsList className="grid w-full max-w-2xl grid-cols-3 h-12 bg-muted/50 backdrop-blur-sm">450 <TabsTrigger value="metrics" className="data-[state=active]:bg-background data-[state=active]:shadow-md transition-all">451 <Activity className="w-4 h-4 mr-2" />452 Live Metrics453 </TabsTrigger>454 <TabsTrigger value="sessions" className="data-[state=active]:bg-background data-[state=active]:shadow-md transition-all">455 <MessageSquare className="w-4 h-4 mr-2" />456 Sessions ({databaseData.sessions.length})457 </TabsTrigger>458 <TabsTrigger value="reports" className="data-[state=active]:bg-background data-[state=active]:shadow-md transition-all">459 <FileText className="w-4 h-4 mr-2" />460 Shared Reports ({databaseData.sharedReports.length})461 </TabsTrigger>462 </TabsList>463464 <div className="flex items-center gap-3">465 <Badge variant={autoRefresh ? "default" : "outline"} className="cursor-pointer" onClick={() => setAutoRefresh(!autoRefresh)}>466 <RefreshCw className={`w-3 h-3 mr-1 ${autoRefresh ? 'animate-spin' : ''}`} />467 Auto-refresh {autoRefresh ? 'ON' : 'OFF'}468 </Badge>469 <Button variant="outline" size="sm" onClick={refreshAllData}>470 <RefreshCw className="w-4 h-4 mr-2" />471 Refresh Now472 </Button>473 </div>474 </div>475476 {/* Metrics Tab */}477 <TabsContent value="metrics" className="mt-6 space-y-6">478 {/* Real-Time Active Users Section */}479 {activeUsersData && (480 <div className="space-y-4">481 <div className="flex items-center justify-between">482 <h3 className="text-lg font-semibold flex items-center gap-2">483 <Zap className="w-5 h-5 text-yellow-500 animate-pulse" />484 Live Activity Monitor485 </h3>486 <div className="flex items-center gap-2">487 <Badge variant="default" className="bg-green-500 hover:bg-green-600">488 {activeUsersData.generating} Generating489 </Badge>490 <Badge variant="secondary">491 {activeUsersData.total} Active492 </Badge>493 </div>494 </div>495496 {activeUsersData.users.length > 0 ? (497 <Card>498 <CardContent className="pt-6">499 <Table>500 <TableHeader>501 <TableRow>502 <TableHead>Session</TableHead>503 <TableHead>Status</TableHead>504 <TableHead>Current Query</TableHead>505 <TableHead>Last Activity</TableHead>506 </TableRow>507 </TableHeader>508 <TableBody>509 {activeUsersData.users.map((user) => (510 <TableRow key={user.sessionId}>511 <TableCell className="font-mono text-xs">512 {user.sessionId.substring(0, 12)}...513 </TableCell>514 <TableCell>515 <Badge516 variant={517 user.status === 'generating' ? 'default' :518 user.status === 'error' ? 'destructive' :519 'secondary'520 }521 >522 {user.status === 'generating' && <Zap className="w-3 h-3 mr-1 animate-pulse" />}523 {user.status === 'error' && <AlertCircle className="w-3 h-3 mr-1" />}524 {user.status}525 </Badge>526 </TableCell>527 <TableCell className="max-w-md truncate">528 {user.currentQuery || '-'}529 </TableCell>530 <TableCell className="text-xs text-muted-foreground">531 {new Date(user.lastHeartbeat).toLocaleTimeString()}532 </TableCell>533 </TableRow>534 ))}535 </TableBody>536 </Table>537 </CardContent>538 </Card>539 ) : (540 <Card>541 <CardContent className="pt-6">542 <div className="text-center py-8 text-muted-foreground">543 <UsersIcon className="w-12 h-12 mx-auto mb-3 opacity-50" />544 <p>No active users at the moment</p>545 </div>546 </CardContent>547 </Card>548 )}549 </div>550 )}551552 {/* Real-Time Stats Overview */}553 {realTimeStats && (554 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">555 <Card className="border-green-500/50 bg-green-500/5">556 <CardHeader className="pb-3">557 <CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">558 <Activity className="w-4 h-4 text-green-500" />559 Active Users Now560 </CardTitle>561 </CardHeader>562 <CardContent>563 <div className="text-3xl font-bold text-green-500">564 {realTimeStats.activeUsers.total}565 </div>566 <p className="text-xs text-muted-foreground mt-1">567 {realTimeStats.activeUsers.generating} generating568 </p>569 </CardContent>570 </Card>571572 <Card className="border-blue-500/50 bg-blue-500/5">573 <CardHeader className="pb-3">574 <CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">575 <MessageSquare className="w-4 h-4 text-blue-500" />576 Sessions Today577 </CardTitle>578 </CardHeader>579 <CardContent>580 <div className="text-3xl font-bold text-blue-500">581 {realTimeStats.sessions.today}582 </div>583 <p className="text-xs text-muted-foreground mt-1">584 of {realTimeStats.sessions.total} total585 </p>586 </CardContent>587 </Card>588589 <Card className="border-purple-500/50 bg-purple-500/5">590 <CardHeader className="pb-3">591 <CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">592 <DollarSign className="w-4 h-4 text-purple-500" />593 Cost Today594 </CardTitle>595 </CardHeader>596 <CardContent>597 <div className="text-3xl font-bold text-purple-500">598 ${realTimeStats.cost.today.toFixed(3)}599 </div>600 <p className="text-xs text-muted-foreground mt-1">601 ${realTimeStats.cost.total.toFixed(3)} total602 </p>603 </CardContent>604 </Card>605606 <Card className={`border-${realTimeStats.errors.rate > 10 ? 'red' : 'yellow'}-500/50 bg-${realTimeStats.errors.rate > 10 ? 'red' : 'yellow'}-500/5`}>607 <CardHeader className="pb-3">608 <CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">609 <AlertCircle className={`w-4 h-4 text-${realTimeStats.errors.rate > 10 ? 'red' : 'yellow'}-500`} />610 Error Rate611 </CardTitle>612 </CardHeader>613 <CardContent>614 <div className={`text-3xl font-bold text-${realTimeStats.errors.rate > 10 ? 'red' : 'yellow'}-500`}>615 {realTimeStats.errors.rate.toFixed(1)}%616 </div>617 <p className="text-xs text-muted-foreground mt-1">618 {realTimeStats.errors.lastHour} errors last hour619 </p>620 </CardContent>621 </Card>622 </div>623 )}624625 {/* Historical Overview Cards */}626 <div className="space-y-4">627 <h3 className="text-lg font-semibold flex items-center gap-2">628 <TrendingUp className="w-5 h-5 text-primary" />629 Cumulative Metrics630 </h3>631 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">632 <Card>633 <CardHeader className="pb-3">634 <CardTitle className="text-sm font-medium text-muted-foreground">Total Cost</CardTitle>635 </CardHeader>636 <CardContent>637 <div className="text-2xl font-bold text-primary">638 ${databaseData.metrics.totalCost.toFixed(4)}639 </div>640 <p className="text-xs text-muted-foreground mt-1">641 Across {databaseData.metrics.totalSessions} sessions642 </p>643 </CardContent>644 </Card>645646 <Card>647 <CardHeader className="pb-3">648 <CardTitle className="text-sm font-medium text-muted-foreground">Total Tokens</CardTitle>649 </CardHeader>650 <CardContent>651 <div className="text-2xl font-bold">652 {databaseData.metrics.totalTokens.toLocaleString()}653 </div>654 <p className="text-xs text-muted-foreground mt-1">655 {databaseData.metrics.totalInputTokens.toLocaleString()} in / {databaseData.metrics.totalOutputTokens.toLocaleString()} out656 </p>657 </CardContent>658 </Card>659660 <Card>661 <CardHeader className="pb-3">662 <CardTitle className="text-sm font-medium text-muted-foreground">Avg Cost/Session</CardTitle>663 </CardHeader>664 <CardContent>665 <div className="text-2xl font-bold text-green-600">666 ${databaseData.metrics.averageCostPerSession.toFixed(4)}667 </div>668 <p className="text-xs text-muted-foreground mt-1">669 Per conversation670 </p>671 </CardContent>672 </Card>673674 <Card>675 <CardHeader className="pb-3">676 <CardTitle className="text-sm font-medium text-muted-foreground">Model</CardTitle>677 </CardHeader>678 <CardContent>679 <div className="text-lg font-bold">680 Opus 4.8681 </div>682 <p className="text-xs text-muted-foreground mt-1">683 $5/$25 per M tokens684 </p>685 </CardContent>686 </Card>687 </div>688 </div>689690 </TabsContent>691692 {/* Sessions Tab */}693 <TabsContent value="sessions" className="mt-6">694 <Card>695 <CardHeader>696 <CardTitle>All Conversation Sessions</CardTitle>697 <CardDescription>Manage conversation history</CardDescription>698 </CardHeader>699 <CardContent>700 <Table>701 <TableHeader>702 <TableRow>703 <TableHead>Session ID</TableHead>704 <TableHead>User ID</TableHead>705 <TableHead>Title</TableHead>706 <TableHead className="text-right">Tokens</TableHead>707 <TableHead className="text-right">Cost</TableHead>708 <TableHead>Updated At</TableHead>709 <TableHead className="text-right">Actions</TableHead>710 </TableRow>711 </TableHeader>712 <TableBody>713 {databaseData.sessions.map((session) => (714 <TableRow key={session.id}>715 <TableCell className="font-mono text-xs">{session.sessionId.substring(0, 8)}...</TableCell>716 <TableCell className="font-mono text-xs">717 {session.userId ? `${session.userId.substring(0, 8)}...` : "Anonymous"}718 </TableCell>719 <TableCell className="max-w-md truncate">{session.title}</TableCell>720 <TableCell className="text-right font-mono text-xs">721 {(session.inputTokens + session.outputTokens).toLocaleString()}722 <span className="text-muted-foreground ml-1">723 ({session.inputTokens}↑/{session.outputTokens}↓)724 </span>725 </TableCell>726 <TableCell className="text-right font-medium text-primary">727 ${session.totalCost.toFixed(4)}728 </TableCell>729 <TableCell>{formatDate(session.updatedAt)}</TableCell>730 <TableCell className="text-right">731 <Button732 variant="destructive"733 size="sm"734 onClick={() => deleteSession(session.id)}735 >736 <Trash2 className="w-4 h-4" />737 </Button>738 </TableCell>739 </TableRow>740 ))}741 </TableBody>742 </Table>743 </CardContent>744 </Card>745 </TabsContent>746747 {/* Shared Reports Tab */}748 <TabsContent value="reports" className="mt-6">749 <Card>750 <CardHeader>751 <CardTitle>All Shared Reports</CardTitle>752 <CardDescription>Manage publicly shared reports</CardDescription>753 </CardHeader>754 <CardContent>755 <Table>756 <TableHeader>757 <TableRow>758 <TableHead>Share ID</TableHead>759 <TableHead>Question Preview</TableHead>760 <TableHead>Created At</TableHead>761 <TableHead className="text-right">Actions</TableHead>762 </TableRow>763 </TableHeader>764 <TableBody>765 {databaseData.sharedReports.map((report) => (766 <TableRow key={report.id}>767 <TableCell className="font-mono text-xs">{report.shareId}</TableCell>768 <TableCell className="max-w-md truncate">{report.question}</TableCell>769 <TableCell>{formatDate(report.createdAt)}</TableCell>770 <TableCell className="text-right">771 <Button772 variant="destructive"773 size="sm"774 onClick={() => deleteReport(report.id)}775 >776 <Trash2 className="w-4 h-4" />777 </Button>778 </TableCell>779 </TableRow>780 ))}781 </TableBody>782 </Table>783 </CardContent>784 </Card>785 </TabsContent>786 </Tabs>787 )}788 </main>789 </div>790 );791}792