Merge branch 'main' of https://git.coastit.co.za/caitlin/CRM_ENVR
This commit is contained in:
@@ -1,10 +1,4 @@
|
|||||||
import type { NextConfig } from "next"
|
import type { NextConfig } from "next"
|
||||||
import crypto from "crypto"
|
|
||||||
|
|
||||||
// Generate a random JWT secret on every dev server start to invalidate all prior sessions
|
|
||||||
if (process.env.NODE_ENV !== "production") {
|
|
||||||
process.env.JWT_SECRET = crypto.randomUUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
eslint: {
|
eslint: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
import {
|
import {
|
||||||
Search, Send, Phone, Video, MoreHorizontal, Paperclip,
|
Search, Send, Phone, MoreHorizontal, Paperclip,
|
||||||
Smile, Flag, Ban, Trash2, Image, FileIcon, X, Mic, Square, Play, Pause, Check, CheckCheck,
|
Smile, Flag, Ban, Trash2, Image, FileIcon, X, Mic, Square, Play, Pause, Check, CheckCheck,
|
||||||
CornerDownRight, Forward, Pencil, Download, Undo2, CalendarDays, Loader2, FolderOpen, Mail,
|
CornerDownRight, Forward, Pencil, Download, Undo2, CalendarDays, Loader2, FolderOpen, Mail,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
@@ -792,7 +792,7 @@ const formatPreviewContent = (content: string) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[calc(100vh-8rem)] -m-4 lg:-m-6 rounded-lg border bg-card overflow-hidden">
|
<div className="flex h-[calc(100dvh-4rem)] -m-4 lg:-m-6 rounded-lg border bg-card overflow-hidden">
|
||||||
{/* Conversations list - left panel */}
|
{/* Conversations list - left panel */}
|
||||||
<div
|
<div
|
||||||
className="flex flex-col border-r shrink-0 overflow-hidden"
|
className="flex flex-col border-r shrink-0 overflow-hidden"
|
||||||
@@ -923,9 +923,6 @@ const formatPreviewContent = (content: string) => {
|
|||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setIsCallModalOpen(true)}>
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setIsCallModalOpen(true)}>
|
||||||
<Phone className="h-4 w-4" />
|
<Phone className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => toast.info("Video calling coming soon")}>
|
|
||||||
<Video className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setScheduleDialogOpen(true)}>
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setScheduleDialogOpen(true)}>
|
||||||
<CalendarDays className="h-4 w-4" />
|
<CalendarDays className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -196,11 +196,11 @@ export async function GET(request: NextRequest) {
|
|||||||
trends,
|
trends,
|
||||||
recentLeads: mappedLeads.slice(0, 10),
|
recentLeads: mappedLeads.slice(0, 10),
|
||||||
statusDistribution: [
|
statusDistribution: [
|
||||||
{ name: "Open", value: currentCounts.open, color: "#FFFFFF" },
|
{ name: "Open", value: currentCounts.open, color: "#3b82f6" },
|
||||||
{ name: "Contacted", value: currentCounts.contacted, color: "#dc2626" },
|
{ name: "Contacted", value: currentCounts.contacted, color: "#f59e0b" },
|
||||||
{ name: "Pending", value: currentCounts.pending, color: "#1e3a8a" },
|
{ name: "Pending", value: currentCounts.pending, color: "#8b5cf6" },
|
||||||
{ name: "Closed", value: currentCounts.closed, color: "#60a5fa" },
|
{ name: "Closed", value: currentCounts.closed, color: "#10b981" },
|
||||||
{ name: "Ignored", value: currentCounts.ignored, color: "#000000" },
|
{ name: "Ignored", value: currentCounts.ignored, color: "#6B7280" },
|
||||||
],
|
],
|
||||||
periodLabel: periodLabels[period] ?? "Selected period",
|
periodLabel: periodLabels[period] ?? "Selected period",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const pos = searchParams.get("pos") || ""
|
const pos = searchParams.get("pos") || ""
|
||||||
|
|
||||||
if (!TENOR_API_KEY) {
|
if (!TENOR_API_KEY) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({ results: [], noKey: true })
|
||||||
results: [],
|
|
||||||
error: "TENOR_API_KEY not configured",
|
|
||||||
noKey: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint = q
|
const endpoint = q
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ export default function MediaPicker({ onEmojiSelect, onMediaSelect, onClose, the
|
|||||||
const [gifResults, setGifResults] = useState<any[]>([])
|
const [gifResults, setGifResults] = useState<any[]>([])
|
||||||
const [gifLoading, setGifLoading] = useState(false)
|
const [gifLoading, setGifLoading] = useState(false)
|
||||||
const [gifError, setGifError] = useState("")
|
const [gifError, setGifError] = useState("")
|
||||||
const [gifNoKey, setGifNoKey] = useState(false)
|
const [gifUnavailable, setGifUnavailable] = useState(false)
|
||||||
const [gifNext, setGifNext] = useState("")
|
const [gifNext, setGifNext] = useState("")
|
||||||
|
const gifCache = useRef<Map<string, { results: any[]; next: string }>>(new Map())
|
||||||
const [recentGifs, setRecentGifs] = useState<string[]>([])
|
const [recentGifs, setRecentGifs] = useState<string[]>([])
|
||||||
const [recentStickers, setRecentStickers] = useState<string[]>([])
|
const [recentStickers, setRecentStickers] = useState<string[]>([])
|
||||||
const [stickerPacks] = useState<StickerPack[]>(getStickerPacks)
|
const [stickerPacks] = useState<StickerPack[]>(getStickerPacks)
|
||||||
@@ -60,6 +61,13 @@ export default function MediaPicker({ onEmojiSelect, onMediaSelect, onClose, the
|
|||||||
useEffect(() => { setRecentStickers(getRecent(RECENT_STICKERS_KEY)) }, [])
|
useEffect(() => { setRecentStickers(getRecent(RECENT_STICKERS_KEY)) }, [])
|
||||||
|
|
||||||
const fetchGifs = useCallback(async (query: string, pos = "") => {
|
const fetchGifs = useCallback(async (query: string, pos = "") => {
|
||||||
|
const cacheKey = `${query}::${pos}`
|
||||||
|
const cached = gifCache.current.get(cacheKey)
|
||||||
|
if (cached && !pos) {
|
||||||
|
setGifResults(cached.results)
|
||||||
|
setGifNext(cached.next)
|
||||||
|
return
|
||||||
|
}
|
||||||
setGifLoading(true)
|
setGifLoading(true)
|
||||||
setGifError("")
|
setGifError("")
|
||||||
try {
|
try {
|
||||||
@@ -68,17 +76,18 @@ export default function MediaPicker({ onEmojiSelect, onMediaSelect, onClose, the
|
|||||||
const res = await fetch(`/api/gifs?${params}`)
|
const res = await fetch(`/api/gifs?${params}`)
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (data.noKey) {
|
if (data.noKey) {
|
||||||
setGifNoKey(true)
|
setGifUnavailable(true)
|
||||||
setGifResults([])
|
setGifResults([])
|
||||||
} else if (data.results) {
|
} else if (data.results) {
|
||||||
setGifResults((prev) => (pos ? [...prev, ...data.results] : data.results))
|
setGifResults((prev) => (pos ? [...prev, ...data.results] : data.results))
|
||||||
setGifNext(data.next || "")
|
setGifNext(data.next || "")
|
||||||
setGifNoKey(false)
|
setGifUnavailable(false)
|
||||||
|
if (!pos) gifCache.current.set(cacheKey, { results: data.results, next: data.next || "" })
|
||||||
} else {
|
} else {
|
||||||
setGifError("No results found")
|
setGifError("No results found")
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setGifError("Failed to load GIFs")
|
setGifError("Could not load GIFs. Check your connection.")
|
||||||
}
|
}
|
||||||
setGifLoading(false)
|
setGifLoading(false)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -196,11 +205,10 @@ export default function MediaPicker({ onEmojiSelect, onMediaSelect, onClose, the
|
|||||||
<div className="flex items-center justify-center h-48">
|
<div className="flex items-center justify-center h-48">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
) : gifNoKey ? (
|
) : gifUnavailable ? (
|
||||||
<div className="flex flex-col items-center justify-center h-48 text-muted-foreground text-sm gap-2">
|
<div className="flex flex-col items-center justify-center h-48 text-muted-foreground text-sm gap-2">
|
||||||
<Image className="h-8 w-8 opacity-40" />
|
<Image className="h-8 w-8 opacity-40" />
|
||||||
<span>GIF search requires a Tenor API key</span>
|
<span>GIFs are temporarily unavailable.</span>
|
||||||
<span className="text-xs opacity-60">Set TENOR_API_KEY in .env.local</span>
|
|
||||||
</div>
|
</div>
|
||||||
) : gifError ? (
|
) : gifError ? (
|
||||||
<div className="flex flex-col items-center justify-center h-48 text-muted-foreground text-sm gap-2">
|
<div className="flex flex-col items-center justify-center h-48 text-muted-foreground text-sm gap-2">
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function LeadStatusChart({ data }: LeadStatusChartProps) {
|
|||||||
key={i}
|
key={i}
|
||||||
d={arcPath(160, 160, oR, iR, s.start, s.end)}
|
d={arcPath(160, 160, oR, iR, s.start, s.end)}
|
||||||
fill={`url(#chart-grad-${i})`}
|
fill={`url(#chart-grad-${i})`}
|
||||||
stroke="#d4d4d4"
|
stroke="#000000"
|
||||||
strokeWidth="1"
|
strokeWidth="1"
|
||||||
opacity={hov !== null && !isH ? 0.3 : 1}
|
opacity={hov !== null && !isH ? 0.3 : 1}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import { Lead } from "@/types"
|
|||||||
import { ArrowRight } from "lucide-react"
|
import { ArrowRight } from "lucide-react"
|
||||||
|
|
||||||
const statusStyles: Record<string, string> = {
|
const statusStyles: Record<string, string> = {
|
||||||
open: "bg-white/40 text-gray-700 dark:text-gray-300 border-gray-300/50 dark:border-gray-600/50",
|
open: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20",
|
||||||
contacted: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20",
|
contacted: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||||
pending: "bg-blue-900/10 text-blue-900 dark:text-blue-300 border-blue-900/20",
|
pending: "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20",
|
||||||
closed: "bg-blue-400/10 text-blue-600 dark:text-blue-300 border-blue-400/20",
|
closed: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
|
||||||
ignored: "bg-black/10 text-black dark:text-gray-300 border-black/20 dark:border-black/40",
|
ignored: "bg-zinc-500/10 text-zinc-600 dark:text-zinc-400 border-zinc-500/20",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RecentLeadsTableProps {
|
interface RecentLeadsTableProps {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useWebsiteTheme } from "@/providers/website-theme-provider";
|
import { useWebsiteTheme } from "@/providers/website-theme-provider";
|
||||||
|
import { CaptainAmericaShield } from "@/components/shared/captain-america-shield";
|
||||||
|
|
||||||
interface TopbarProps {
|
interface TopbarProps {
|
||||||
onMenuClick: () => void;
|
onMenuClick: () => void;
|
||||||
@@ -72,40 +73,11 @@ export function Topbar({ onMenuClick }: TopbarProps) {
|
|||||||
.toUpperCase();
|
.toUpperCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-20 flex h-16 items-center gap-4 border-b bg-card dark:bg-[#141414] px-4 lg:px-6 relative overflow-hidden">
|
<header className="sticky top-0 z-20 flex h-16 items-center gap-4 border-b bg-card dark:bg-sidebar px-4 lg:px-6 relative overflow-hidden">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="flex items-center gap-2 mr-2 shrink-0">
|
<div className="flex items-center gap-2 mr-2 shrink-0">
|
||||||
{websiteTheme === "spidey" && (
|
{websiteTheme === "spidey" && <CaptainAmericaShield />}
|
||||||
<>
|
<span className="text-primary font-bold text-sm tracking-wide">
|
||||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" className="dark:hidden shrink-0">
|
|
||||||
<circle cx="14" cy="14" r="14" fill="white"/>
|
|
||||||
<ellipse cx="14" cy="14" rx="4" ry="5" fill="#CC0000"/>
|
|
||||||
<ellipse cx="14" cy="14" rx="2" ry="2.5" fill="#CC0000"/>
|
|
||||||
<line x1="14" y1="9" x2="6" y2="5" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="9" x2="22" y2="5" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="12" x2="5" y2="11" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="12" x2="23" y2="11" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="19" x2="6" y2="23" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="19" x2="22" y2="23" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="16" x2="5" y2="17" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="16" x2="23" y2="17" stroke="#CC0000" strokeWidth="1.2"/>
|
|
||||||
</svg>
|
|
||||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" className="hidden dark:block shrink-0">
|
|
||||||
<circle cx="14" cy="14" r="14" fill="#1A1A1A"/>
|
|
||||||
<ellipse cx="14" cy="14" rx="4" ry="5" fill="#FF1111"/>
|
|
||||||
<ellipse cx="14" cy="14" rx="2" ry="2.5" fill="#FF1111"/>
|
|
||||||
<line x1="14" y1="9" x2="6" y2="5" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="9" x2="22" y2="5" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="12" x2="5" y2="11" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="12" x2="23" y2="11" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="19" x2="6" y2="23" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="19" x2="22" y2="23" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="16" x2="5" y2="17" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
<line x1="14" y1="16" x2="23" y2="17" stroke="#FF1111" strokeWidth="1.2"/>
|
|
||||||
</svg>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span className="text-[#CC0000] dark:text-[#FF1111] font-bold text-sm tracking-wide">
|
|
||||||
CRM
|
CRM
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,23 +7,23 @@ import { cn } from "@/lib/utils"
|
|||||||
const statusConfig: Record<LeadStatus, { label: string; class: string }> = {
|
const statusConfig: Record<LeadStatus, { label: string; class: string }> = {
|
||||||
open: {
|
open: {
|
||||||
label: "Open",
|
label: "Open",
|
||||||
class: "bg-white/40 text-gray-700 dark:text-gray-300 border-gray-300/50 dark:border-gray-600/50",
|
class: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20",
|
||||||
},
|
},
|
||||||
contacted: {
|
contacted: {
|
||||||
label: "Contacted",
|
label: "Contacted",
|
||||||
class: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20",
|
class: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||||
},
|
},
|
||||||
pending: {
|
pending: {
|
||||||
label: "Pending",
|
label: "Pending",
|
||||||
class: "bg-blue-900/10 text-blue-900 dark:text-blue-300 border-blue-900/20",
|
class: "bg-purple-500/10 text-purple-600 dark:text-purple-400 border-purple-500/20",
|
||||||
},
|
},
|
||||||
closed: {
|
closed: {
|
||||||
label: "Closed",
|
label: "Closed",
|
||||||
class: "bg-blue-400/10 text-blue-600 dark:text-blue-300 border-blue-400/20",
|
class: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
|
||||||
},
|
},
|
||||||
ignored: {
|
ignored: {
|
||||||
label: "Ignored",
|
label: "Ignored",
|
||||||
class: "bg-black/10 text-black dark:text-gray-300 border-black/20 dark:border-black/40",
|
class: "bg-zinc-500/10 text-zinc-600 dark:text-zinc-400 border-zinc-500/20",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,11 +37,11 @@ export function LeadStatusBadge({ status, className }: LeadStatusBadgeProps) {
|
|||||||
return (
|
return (
|
||||||
<Badge variant="outline" className={cn(config.class, className)}>
|
<Badge variant="outline" className={cn(config.class, className)}>
|
||||||
<span className={cn("mr-1.5 h-1.5 w-1.5 rounded-full", {
|
<span className={cn("mr-1.5 h-1.5 w-1.5 rounded-full", {
|
||||||
"bg-white border border-gray-300 dark:border-gray-500": status === "open",
|
"bg-blue-500": status === "open",
|
||||||
"bg-red-500": status === "contacted",
|
"bg-amber-500": status === "contacted",
|
||||||
"bg-blue-900": status === "pending",
|
"bg-purple-500": status === "pending",
|
||||||
"bg-blue-400": status === "closed",
|
"bg-emerald-500": status === "closed",
|
||||||
"bg-black": status === "ignored",
|
"bg-zinc-500": status === "ignored",
|
||||||
})} />
|
})} />
|
||||||
{config.label}
|
{config.label}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -8,24 +8,11 @@ export function CaptainAmericaShield() {
|
|||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<defs>
|
<circle cx="50" cy="50" r="48" fill="#c62828" />
|
||||||
<clipPath id="shield-clip">
|
<circle cx="50" cy="50" r="36" fill="#efefef" />
|
||||||
<polygon points="50,95 5,60 5,30 50,5 95,30 95,60" />
|
<circle cx="50" cy="50" r="24" fill="#c62828" />
|
||||||
</clipPath>
|
<circle cx="50" cy="50" r="14" fill="#1565c0" />
|
||||||
</defs>
|
<polygon points="50,34 55,48 70,48 58,57 62,72 50,62 38,72 42,57 30,48 45,48" fill="#efefef" />
|
||||||
<g clipPath="url(#shield-clip)">
|
|
||||||
<circle cx="50" cy="45" r="45" fill="#c62828" />
|
|
||||||
<circle cx="50" cy="45" r="35" fill="#efefef" />
|
|
||||||
<circle cx="50" cy="45" r="25" fill="#c62828" />
|
|
||||||
<circle cx="50" cy="45" r="15" fill="#1565c0" />
|
|
||||||
<polygon points="50,32 56,47 72,48 60,58 64,75 50,64 36,75 40,58 28,48 44,47" fill="#efefef" />
|
|
||||||
</g>
|
|
||||||
<polygon
|
|
||||||
points="50,95 5,60 5,30 50,5 95,30 95,60"
|
|
||||||
fill="none"
|
|
||||||
stroke="#c62828"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2,8 +2,11 @@ import { SignJWT, jwtVerify } from "jose";
|
|||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
import { query } from "@/lib/db";
|
import { query } from "@/lib/db";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
const RAW_SECRET = process.env.JWT_SECRET;
|
const RAW_SECRET = process.env.NODE_ENV === "production"
|
||||||
|
? process.env.JWT_SECRET
|
||||||
|
: crypto.randomUUID();
|
||||||
if (!RAW_SECRET) {
|
if (!RAW_SECRET) {
|
||||||
throw new Error("JWT_SECRET environment variable is required");
|
throw new Error("JWT_SECRET environment variable is required");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export const LEAD_STATUSES = {
|
export const LEAD_STATUSES = {
|
||||||
open: { label: "Open", color: "bg-white" },
|
open: { label: "Open", color: "bg-blue-500" },
|
||||||
contacted: { label: "Contacted", color: "bg-red-500" },
|
contacted: { label: "Contacted", color: "bg-amber-500" },
|
||||||
pending: { label: "Pending", color: "bg-blue-900" },
|
pending: { label: "Pending", color: "bg-purple-500" },
|
||||||
closed: { label: "Closed", color: "bg-blue-400" },
|
closed: { label: "Closed", color: "bg-emerald-500" },
|
||||||
ignored: { label: "Ignored", color: "bg-black" },
|
ignored: { label: "Ignored", color: "bg-zinc-500" },
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const LEAD_SOURCES = [
|
export const LEAD_SOURCES = [
|
||||||
|
|||||||
Reference in New Issue
Block a user