@@ -1244,9 +1240,7 @@ export default function OrganizationSettings() {
{/* Table */}
{isLoadingAudit ? (
-
-
-
+
) : (auditEntries ?? []).length === 0 ? (
No audit events found.
) : (
diff --git a/components/skeletons.tsx b/components/skeletons.tsx
new file mode 100644
index 0000000..b1872c6
--- /dev/null
+++ b/components/skeletons.tsx
@@ -0,0 +1,461 @@
+/**
+ * Reusable skeleton loading primitives and composites for Pulse.
+ * All skeletons follow the design-system pattern:
+ * animate-pulse + bg-neutral-100 dark:bg-neutral-800 + rounded
+ */
+
+const SK = 'animate-pulse bg-neutral-100 dark:bg-neutral-800'
+
+// ─── Primitives ──────────────────────────────────────────────
+
+export function SkeletonLine({ className = '' }: { className?: string }) {
+ return
+}
+
+export function SkeletonCircle({ className = '' }: { className?: string }) {
+ return
+}
+
+export function SkeletonCard({ className = '' }: { className?: string }) {
+ return
+}
+
+// ─── List skeleton (icon + two text lines per row) ───────────
+
+export function ListRowSkeleton() {
+ return (
+
+ )
+}
+
+export function ListSkeleton({ rows = 7 }: { rows?: number }) {
+ return (
+
+ {Array.from({ length: rows }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+// ─── Table skeleton (header row + data rows) ─────────────────
+
+export function TableSkeleton({ rows = 7, cols = 5 }: { rows?: number; cols?: number }) {
+ return (
+
+
+ {Array.from({ length: cols }).map((_, i) => (
+
+ ))}
+
+ {Array.from({ length: rows }).map((_, i) => (
+
+ {Array.from({ length: cols }).map((_, j) => (
+
+ ))}
+
+ ))}
+
+ )
+}
+
+// ─── Widget panel skeleton (used inside dashboard grid) ──────
+
+export function WidgetSkeleton() {
+ return (
+
+ )
+}
+
+// ─── Stat card skeleton ──────────────────────────────────────
+
+export function StatCardSkeleton() {
+ return (
+
+
+
+
+ )
+}
+
+// ─── Chart area skeleton ─────────────────────────────────────
+
+export function ChartSkeleton() {
+ return (
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+
+
+
+ ))}
+
+
+
+
+
+ )
+}
+
+// ─── Full dashboard skeleton ─────────────────────────────────
+
+export function DashboardSkeleton() {
+ return (
+
+ {/* Header */}
+
+
+ {/* Chart */}
+
+
+
+
+ {/* Widget grid (2 cols) */}
+
+
+
+
+
+
+
+
+
+ {/* Campaigns table */}
+
+
+ )
+}
+
+// ─── Realtime page skeleton ──────────────────────────────────
+
+export function RealtimeSkeleton() {
+ return (
+
+
+
+
+
+
+ {/* Visitors list */}
+
+
+
+
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+
+ {/* Session details */}
+
+
+
+
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+
+
+
+ )
+}
+
+// ─── Session events skeleton (for loading events panel) ──────
+
+export function SessionEventsSkeleton() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+// ─── Uptime page skeleton ────────────────────────────────────
+
+export function UptimeSkeleton() {
+ return (
+
+
+
+
+
+
+ {/* Overall status */}
+
+ {/* Monitor cards */}
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+
+ )
+}
+
+// ─── Checks / Response time skeleton ─────────────────────────
+
+export function ChecksSkeleton() {
+ return (
+
+
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+
+ )
+}
+
+// ─── Funnels list skeleton ───────────────────────────────────
+
+export function FunnelsListSkeleton() {
+ return (
+
+
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+
+
+
+ {Array.from({ length: 3 }).map((_, j) => (
+
+
+ {j < 2 && }
+
+ ))}
+
+
+ ))}
+
+
+
+ )
+}
+
+// ─── Funnel detail skeleton ──────────────────────────────────
+
+export function FunnelDetailSkeleton() {
+ return (
+
+
+
+
+
+
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+
+ )
+}
+
+// ─── Notifications list skeleton ─────────────────────────────
+
+export function NotificationsListSkeleton() {
+ return (
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+// ─── Settings form skeleton ──────────────────────────────────
+
+export function SettingsFormSkeleton() {
+ return (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+
+
+
+ ))}
+
+
+ )
+}
+
+// ─── Goals list skeleton ─────────────────────────────────────
+
+export function GoalsListSkeleton() {
+ return (
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+// ─── Pricing cards skeleton ──────────────────────────────────
+
+export function PricingCardsSkeleton() {
+ return (
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+// ─── Organization settings skeleton (members, billing, etc) ─
+
+export function MembersListSkeleton() {
+ return (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+export function InvoicesListSkeleton() {
+ return (
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+ )
+}
+
+export function AuditLogSkeleton() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+
+
+
+
+ ))}
+
+ )
+}