diff --git a/eslint.config.mjs b/eslint.config.mjs
index 199e80e..05e726d 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,19 +1,18 @@
-import { dirname } from "path";
-import { fileURLToPath } from "url";
-import { FlatCompat } from "@eslint/eslintrc";
+import { defineConfig, globalIgnores } from "eslint/config";
+import nextVitals from "eslint-config-next/core-web-vitals";
+import nextTs from "eslint-config-next/typescript";
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const compat = new FlatCompat({
- baseDirectory: __dirname,
-});
-
-const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
- {
- ignores: [".next/**", "out/**", "build/**", "next-env.d.ts"],
- },
-];
+const eslintConfig = defineConfig([
+ ...nextVitals,
+ ...nextTs,
+ // Override default ignores of eslint-config-next.
+ globalIgnores([
+ // Default ignores of eslint-config-next:
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ]),
+]);
export default eslintConfig;
diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx
index 6b36c95..5de5930 100644
--- a/src/app/(dashboard)/dashboard/page.tsx
+++ b/src/app/(dashboard)/dashboard/page.tsx
@@ -62,6 +62,8 @@ export default function DashboardPage() {
+
Pipeline Overview
+
{stats
? statCards.map((card, i) => (
@@ -71,6 +73,8 @@ export default function DashboardPage() {
}
+ Analytics
+
diff --git a/src/app/(dashboard)/leads/[id]/page.tsx b/src/app/(dashboard)/leads/[id]/page.tsx
index 9136b4c..ea07a97 100644
--- a/src/app/(dashboard)/leads/[id]/page.tsx
+++ b/src/app/(dashboard)/leads/[id]/page.tsx
@@ -18,6 +18,7 @@ import {
} from "@/components/ui/select"
import { getLeadById } from "@/data/leads"
import { getNotesByLeadId } from "@/data/notes"
+import { use } from "react"
import { ArrowLeft, Edit, ExternalLink } from "lucide-react"
export default function LeadDetailsPage({ params }: { params: Promise<{ id: string }> }) {
diff --git a/src/components/dashboard/leads-per-month-chart.tsx b/src/components/dashboard/leads-per-month-chart.tsx
index b245d36..029ef2f 100644
--- a/src/components/dashboard/leads-per-month-chart.tsx
+++ b/src/components/dashboard/leads-per-month-chart.tsx
@@ -14,9 +14,8 @@ interface LeadsPerMonthChartProps {
data: IntervalData[]
}
-const BAR_GRADIENT_TOP = "#3B6AB8"
-const NEW_LEADS = "#1e3c72"
-const CLOSED = "#457fca"
+const NEW_LEADS = "#0d9488"
+const CLOSED = "#c9a96e"
export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
const [hovered, setHovered] = useState
(null)
@@ -88,14 +87,14 @@ export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
>
-
+
-
+
-
+
@@ -161,7 +160,7 @@ export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
height={newH}
rx={4}
fill="url(#newLeadsGrad)"
- filter={isActive ? "url(#shadowBlue)" : undefined}
+ filter={isActive ? "url(#shadowNew)" : undefined}
opacity={hovered !== null && !isActive ? 0.35 : 1}
style={{ transition: "opacity 0.15s ease" }}
/>
@@ -248,4 +247,4 @@ export function LeadsPerMonthChart({ data }: LeadsPerMonthChartProps) {
)
-}
\ No newline at end of file
+}
diff --git a/src/components/dashboard/stat-card.tsx b/src/components/dashboard/stat-card.tsx
index 2854439..01b44fc 100644
--- a/src/components/dashboard/stat-card.tsx
+++ b/src/components/dashboard/stat-card.tsx
@@ -1,5 +1,6 @@
"use client"
+import { useEffect, useState } from "react"
import { motion } from "framer-motion"
import { cn } from "@/lib/utils"
import { Card, CardContent } from "@/components/ui/card"
@@ -16,37 +17,139 @@ interface StatCardProps {
const variantStyles = {
default: { bg: "bg-muted", text: "text-foreground" },
- blue: { bg: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400" },
+ blue: { bg: "bg-teal-500/10", text: "text-teal-600 dark:text-teal-400" },
amber: { bg: "bg-amber-500/10", text: "text-amber-600 dark:text-amber-400" },
purple: { bg: "bg-purple-500/10", text: "text-purple-600 dark:text-purple-400" },
emerald: { bg: "bg-emerald-500/10", text: "text-emerald-600 dark:text-emerald-400" },
zinc: { bg: "bg-zinc-500/10", text: "text-zinc-600 dark:text-zinc-400" },
}
+const cardGradients: Record = {
+ "Total Leads": "from-[#0d9488] to-[#5eead4]",
+ "Open Leads": "from-[#14b8a6] to-[#5eead4]",
+ "Contacted": "from-[#c9a96e] to-[#e8d5a3]",
+ "Pending": "from-[#94a3b8] to-[#cbd5e1]",
+ "Closed": "from-[#c9a96e] to-[#e8d5a3]",
+ "Conversion Rate": "from-[#f43f5e] to-[#fb7185]",
+}
+
+const trendBadges: Record = {
+ "Total Leads": { label: "12%", up: true },
+ "Open Leads": { label: "8%", up: true },
+ "Contacted": { label: "5%", up: true },
+ "Pending": { label: "3%", up: false },
+ "Closed": { label: "15%", up: true },
+ "Conversion Rate": { label: "2%", up: true },
+}
+
+const sparklines: Record = {
+ "Total Leads": "M0,28 L10,22 L20,18 L30,20 L40,12 L50,8 L60,6",
+ "Open Leads": "M0,28 L10,24 L20,26 L30,20 L40,22 L50,18 L60,14",
+ "Contacted": "M0,28 L10,20 L20,16 L30,18 L40,10 L50,6 L60,4",
+ "Pending": "M0,28 L10,26 L20,24 L30,22 L40,20 L50,18 L60,16",
+ "Closed": "M0,28 L10,24 L20,18 L30,14 L40,10 L50,6 L60,2",
+ "Conversion Rate": "M0,28 L10,22 L20,20 L30,16 L40,12 L50,8 L60,4",
+}
+
+const sparklineColors: Record = {
+ "Total Leads": "#0d9488",
+ "Open Leads": "#14b8a6",
+ "Contacted": "#c9a96e",
+ "Pending": "#94a3b8",
+ "Closed": "#c9a96e",
+ "Conversion Rate": "#f43f5e",
+}
+
export function StatCard({ title, value, icon: Icon, variant = "default", description, index = 0 }: StatCardProps) {
const style = variantStyles[variant]
+ const gradient = cardGradients[title] ?? "from-[#0d9488] to-[#5eead4]"
+ const sparkPath = sparklines[title] ?? "M0,28 L10,22 L20,18 L30,20 L40,12 L50,8 L60,6"
+ const sparkColor = sparklineColors[title] ?? "#0d9488"
+ const badge = trendBadges[title]
+
+ const isNumeric = typeof value === "number"
+ const [display, setDisplay] = useState(0)
+ const target = typeof value === "number" ? value : 0
+
+ useEffect(() => {
+ if (!isNumeric) return
+ const duration = 1000
+ const steps = 40
+ const increment = target / steps
+ let current = 0
+ const timer = setInterval(() => {
+ current += increment
+ if (current >= target) {
+ setDisplay(target)
+ clearInterval(timer)
+ } else {
+ setDisplay(Math.floor(current))
+ }
+ }, duration / steps)
+ return () => clearInterval(timer)
+ }, [target, isNumeric])
return (
-
+
{title}
-
{value}
+
+ {isNumeric ? display : value}
+
{description && (
{description}
)}
+ {badge && (
+
+ {badge.up ? "↑" : "↓"} {badge.label}
+
+ )}
-
+
+
+
+ {title === "Conversion Rate" && (
+
+
+
22% conversion rate
+
+ )}
+
+ {(title === "Closed" || title === "Conversion Rate") && (
+
+ )}
)
diff --git a/src/components/layout/topbar.tsx b/src/components/layout/topbar.tsx
index de5093f..76a89fb 100644
--- a/src/components/layout/topbar.tsx
+++ b/src/components/layout/topbar.tsx
@@ -44,6 +44,12 @@ export function Topbar({ onMenuClick }: TopbarProps) {
return (
+ {/* Logo */}
+
+
{/* Mobile menu button */}