feat: Add notifications table and integrate Electric SQL for real-time updates

- Introduced a new notifications table in the database schema to manage user notifications.
- Implemented Electric SQL replication setup for the notifications table, ensuring real-time synchronization.
- Updated existing database functions to support real-time updates for connectors and documents using Electric SQL.
- Refactored UI components to utilize new hooks for fetching connectors and documents, enhancing performance and user experience.
This commit is contained in:
Anish Sarkar 2026-01-13 16:54:06 +05:30
parent 7a92ecc1ab
commit e38e6d90e0
9 changed files with 489 additions and 178 deletions

View file

@ -1,7 +1,6 @@
"use client";
import { IconBrandYoutube } from "@tabler/icons-react";
import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns";
import { FileText, Loader2 } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
@ -20,7 +19,6 @@ interface ConnectorCardProps {
isConnecting?: boolean;
documentCount?: number;
accountCount?: number;
lastIndexedAt?: string | null;
isIndexing?: boolean;
activeTask?: LogActiveTask;
onConnect?: () => void;
@ -52,45 +50,6 @@ function formatDocumentCount(count: number | undefined): string {
return `${m.replace(/\.0$/, "")}M docs`;
}
/**
* Format last indexed date with contextual messages
* Examples: "Just now", "10 minutes ago", "Today at 2:30 PM", "Yesterday at 3:45 PM", "3 days ago", "Jan 15, 2026"
*/
function formatLastIndexedDate(dateString: string): string {
const date = new Date(dateString);
const now = new Date();
const minutesAgo = differenceInMinutes(now, date);
const daysAgo = differenceInDays(now, date);
// Just now (within last minute)
if (minutesAgo < 1) {
return "Just now";
}
// X minutes ago (less than 1 hour)
if (minutesAgo < 60) {
return `${minutesAgo} ${minutesAgo === 1 ? "minute" : "minutes"} ago`;
}
// Today at [time]
if (isToday(date)) {
return `Today at ${format(date, "h:mm a")}`;
}
// Yesterday at [time]
if (isYesterday(date)) {
return `Yesterday at ${format(date, "h:mm a")}`;
}
// X days ago (less than 7 days)
if (daysAgo < 7) {
return `${daysAgo} ${daysAgo === 1 ? "day" : "days"} ago`;
}
// Full date for older entries
return format(date, "MMM d, yyyy");
}
export const ConnectorCard: FC<ConnectorCardProps> = ({
id,
title,
@ -100,7 +59,6 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
isConnecting = false,
documentCount,
accountCount,
lastIndexedAt,
isIndexing = false,
activeTask,
onConnect,
@ -118,37 +76,29 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
// Extract count from active task message during indexing
const indexingCount = extractIndexedCount(activeTask?.message);
// Determine the status content to display
const getStatusContent = () => {
if (isIndexing) {
return (
<div className="flex items-center gap-2 w-full max-w-[200px]">
<span className="text-[11px] text-primary font-medium whitespace-nowrap">
{indexingCount !== null ? <>{indexingCount.toLocaleString()} indexed</> : "Syncing..."}
</span>
{/* Indeterminate progress bar with animation */}
<div className="relative flex-1 h-1 overflow-hidden rounded-full bg-primary/20">
<div className="absolute h-full bg-primary rounded-full animate-progress-indeterminate" />
</div>
</div>
);
}
if (isConnected) {
// Show last indexed date for connected connectors
if (lastIndexedAt) {
// Determine the status content to display
const getStatusContent = () => {
if (isIndexing) {
return (
<span className="whitespace-nowrap text-[10px]">
Last indexed: {formatLastIndexedDate(lastIndexedAt)}
</span>
<div className="flex items-center gap-2 w-full max-w-[200px]">
<span className="text-[11px] text-primary font-medium whitespace-nowrap">
{indexingCount !== null ? <>{indexingCount.toLocaleString()} indexed</> : "Syncing..."}
</span>
{/* Indeterminate progress bar with animation */}
<div className="relative flex-1 h-1 overflow-hidden rounded-full bg-primary/20">
<div className="absolute h-full bg-primary rounded-full animate-progress-indeterminate" />
</div>
</div>
);
}
// Fallback for connected but never indexed
return <span className="whitespace-nowrap text-[10px]">Never indexed</span>;
}
return description;
};
if (isConnected) {
// Don't show last indexed in overview tabs - only show in accounts list view
return null;
}
return description;
};
const cardContent = (
<div
@ -186,9 +136,10 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
/>
)}
</div>
<div className="text-[10px] text-muted-foreground mt-1">{getStatusContent()}</div>
{isConnected && documentCount !== undefined && (
<p className="text-[10px] text-muted-foreground mt-0.5 flex items-center gap-1.5">
{isIndexing ? (
<div className="text-[10px] text-muted-foreground mt-1">{getStatusContent()}</div>
) : isConnected ? (
<p className="text-[10px] text-muted-foreground mt-1 flex items-center gap-1.5">
<span>{formatDocumentCount(documentCount)}</span>
{accountCount !== undefined && accountCount > 0 && (
<>
@ -199,6 +150,8 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
</>
)}
</p>
) : (
<div className="text-[10px] text-muted-foreground mt-1">{getStatusContent()}</div>
)}
</div>
<Button