Current state

This commit is contained in:
Chariah
2026-06-26 14:31:38 +02:00
commit 7a76841309
982 changed files with 451988 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
"use client"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Send } from "lucide-react"
import { useUser } from "@/providers/user-provider"
interface NoteFormProps {
leadId: string
onNoteAdded?: () => void
}
export function NoteForm({ leadId, onNoteAdded }: NoteFormProps) {
const { user } = useUser()
const [note, setNote] = useState("")
const [submitting, setSubmitting] = useState(false)
const handleSubmit = async () => {
if (!note.trim()) return
setSubmitting(true)
try {
await fetch(`/api/leads/${leadId}/notes`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: note }),
})
setNote("")
onNoteAdded?.()
} catch {
console.warn("Failed to add note")
}
setSubmitting(false)
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleSubmit()
}
}
return (
<Card>
<CardHeader>
<CardTitle>Add Note</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
<div className="flex gap-4">
<Avatar className="h-10 w-10 shrink-0">
<AvatarImage src={user?.avatar || undefined} />
<AvatarFallback>{user?.name?.charAt(0) || "U"}</AvatarFallback>
</Avatar>
<div className="flex-1 space-y-3">
<Textarea
placeholder="Write a note about this lead..."
value={note}
onChange={(e) => setNote(e.target.value)}
onKeyDown={handleKeyDown}
className="min-h-[100px] resize-none"
/>
<div className="flex justify-end">
<Button
type="submit"
disabled={!note.trim() || submitting}
className="gap-2"
>
<Send className="h-4 w-4" />
{submitting ? "Adding..." : "Add Note"}
</Button>
</div>
</div>
</div>
</form>
</CardContent>
</Card>
)
}
+94
View File
@@ -0,0 +1,94 @@
"use client"
import { motion } from "framer-motion"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Note } from "@/types"
import { EmptyState } from "@/components/shared/empty-state"
import { MessageSquare, Edit2, Trash2 } from "lucide-react"
interface NoteTimelineProps {
notes: Note[]
}
export function NoteTimeline({ notes }: NoteTimelineProps) {
if (notes.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>Notes</CardTitle>
</CardHeader>
<CardContent>
<EmptyState
icon={MessageSquare}
title="No notes yet"
description="Add the first note to this lead."
/>
</CardContent>
</Card>
)
}
return (
<Card>
<CardHeader>
<CardTitle>Notes ({notes.length})</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-0">
{notes.map((note, i) => (
<motion.div
key={note.id}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05 }}
className="relative flex gap-4 pb-8 last:pb-0"
>
{/* Timeline line */}
{i < notes.length - 1 && (
<div className="absolute left-5 top-12 bottom-0 w-px bg-border" />
)}
{/* Avatar */}
<div className="relative z-10 shrink-0">
<Avatar className="h-10 w-10">
<AvatarImage src={note.authorAvatar} />
<AvatarFallback>{note.authorName[0]}</AvatarFallback>
</Avatar>
</div>
{/* Content */}
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{note.authorName}</span>
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
{note.authorRole}
</Badge>
<span className="text-xs text-muted-foreground">
{new Date(note.createdAt).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</span>
</div>
<p className="text-sm leading-relaxed text-muted-foreground">{note.note}</p>
<div className="flex items-center gap-2 pt-1">
<Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground/50 hover:text-muted-foreground">
<Edit2 className="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground/50 hover:text-destructive">
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
</motion.div>
))}
</div>
</CardContent>
</Card>
)
}