Merge remote changes from origin/main

This commit is contained in:
2026-06-23 19:54:00 +02:00
25 changed files with 799 additions and 219 deletions
+39
View File
@@ -18,10 +18,12 @@ import {
PanelLeftClose,
MessageSquare,
Bot,
Facebook,
} from "lucide-react"
import { COMPANY_NAME } from "@/lib/constants"
import { useUser } from "@/providers/user-provider"
import { useNotifications } from "@/providers/notification-provider"
import { FacebookAccountsDialog } from "@/components/settings/facebook-accounts-dialog"
const navItems = [
{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
@@ -43,7 +45,9 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
const pathname = usePathname()
const { user } = useUser()
const { unreadChatCount } = useNotifications()
const [fbDialogOpen, setFbDialogOpen] = useState(false)
if (!user) return null
const isAdmin = user.role === "admin" || user.role === "super_admin"
const initials = user.name.split(" ").map((n) => n[0]).join("")
const sidebarContent = (
@@ -143,6 +147,39 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
</Link>
)
})}
{isAdmin && collapsed && (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setFbDialogOpen(true)}
className="relative flex h-10 w-10 items-center justify-center rounded-lg transition-colors text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
>
<Facebook className="h-5 w-5" />
</button>
</TooltipTrigger>
<TooltipContent side="right" className="ml-2">
Facebook Accounts
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{isAdmin && !collapsed && (
<button
onClick={() => setFbDialogOpen(true)}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
>
<Facebook className="h-5 w-5 shrink-0" />
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="truncate"
>
Facebook Accounts
</motion.span>
</button>
)}
</nav>
{/* Collapse toggle at bottom (only in collapsed mode) */}
@@ -189,6 +226,8 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
{sidebarContent}
</aside>
<FacebookAccountsDialog open={fbDialogOpen} onOpenChange={setFbDialogOpen} />
{/* Mobile sidebar overlay */}
<AnimatePresence>
{mobileOpen && (
@@ -0,0 +1,102 @@
"use client"
import { useEffect, useState } from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { ShieldAlert, RefreshCw } from "lucide-react"
import { Button } from "@/components/ui/button"
interface Account {
id: string
label: string
is_active: boolean
last_scrape_at: string | null
last_success_at: string | null
last_error_at: string | null
consecutive_failures: number
flagged: boolean
flagged_reason: string | null
last_leads_found: number
last_success: boolean | null
}
export function FacebookAccountsDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (v: boolean) => void }) {
const [accounts, setAccounts] = useState<Account[]>([])
const [loading, setLoading] = useState(false)
useEffect(() => {
if (open) load()
}, [open])
async function load() {
setLoading(true)
try {
const res = await fetch("/api/settings/facebook/accounts")
if (res.ok) {
setAccounts(await res.json())
}
} catch {}
setLoading(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl">
<DialogHeader className="flex flex-row items-center justify-between">
<div>
<DialogTitle>Facebook Accounts</DialogTitle>
<DialogDescription>
Status of Facebook profiles used for lead scraping
</DialogDescription>
</div>
<Button variant="ghost" size="icon" onClick={load} disabled={loading}>
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
</Button>
</DialogHeader>
{loading ? (
<div className="text-center py-8 text-muted-foreground">Loading...</div>
) : accounts.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">No accounts configured</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Label</TableHead>
<TableHead>Status</TableHead>
<TableHead>Last Scrape</TableHead>
<TableHead>Leads</TableHead>
<TableHead>Failures</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{accounts.map((a) => (
<TableRow key={a.id}>
<TableCell className="font-medium">{a.label}</TableCell>
<TableCell>
{a.flagged ? (
<Badge variant="destructive" className="gap-1">
<ShieldAlert className="h-3 w-3" />
{a.flagged_reason || "Flagged"}
</Badge>
) : a.is_active ? (
<Badge variant="default" className="bg-green-600">Active</Badge>
) : (
<Badge variant="secondary">Disabled</Badge>
)}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{a.last_scrape_at ? new Date(a.last_scrape_at).toLocaleString() : "Never"}
</TableCell>
<TableCell>{a.last_leads_found}</TableCell>
<TableCell>{a.consecutive_failures}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</DialogContent>
</Dialog>
)
}