Untitled
unknown
plain_text
10 months ago
8.3 kB
13
Indexable
import { useState, useEffect } from "react"
import { StarIcon, MessageCircle } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"
import { useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { toast } from "sonner"
// Updated interface to work with both snake_case (from Supabase) and camelCase
interface Comment {
id: string
photoId?: string
photo_id?: string
name: string
rating: number | null
text: string
createdAt?: string
created_at?: string
}
interface PhotoCommentsProps {
photoId: string;
onCommentAdded?: () => void;
}
const funnyNames = [
"LustigerLuchs",
"VerspielterVogel",
"QuirlenderQualle",
"SchmunzelnderSchmetterling",
"TanzenderTiger",
"GrinsenderGorilla",
"KichernderKoala",
"WitzigerWaschbär",
"AlbernerAffe",
"LachenderLöwe",
"PossierlicherPinguin",
"ScherzenderSchwan",
]
const generateGradient = (name: string) => {
const hue1 = Array.from(name).reduce((acc, char) => acc + char.charCodeAt(0), 0) % 360;
const hue2 = (hue1 + 40) % 360;
return `linear-gradient(135deg,
hsl(${hue1}, 70%, 60%) 0%,
hsl(${hue2}, 70%, 60%) 100%
)`;
};
export function PhotoComments({ photoId, onCommentAdded }: PhotoCommentsProps) {
const params = useParams();
const clientId = params.clientId as string;
const [comments, setComments] = useState<Comment[]>([])
const [name, setName] = useState("")
const [rating, setRating] = useState<number | null>(null)
const [comment, setComment] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
fetchComments()
}, [photoId, clientId])
const fetchComments = async () => {
try {
const response = await fetch(
`/api/comments?photoId=${encodeURIComponent(photoId)}&clientId=${encodeURIComponent(clientId)}`
);
if (!response.ok) throw new Error('Failed to fetch comments');
const data = await response.json();
setComments(data);
} catch (error) {
console.error('Error fetching comments:', error);
toast.error('Fehler beim Laden der Kommentare');
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSubmitting(true)
try {
const finalName = name || funnyNames[Math.floor(Math.random() * funnyNames.length)]
const response = await fetch('/api/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
photoId,
clientId,
name: finalName,
rating,
text: comment,
}),
})
if (!response.ok) throw new Error('Failed to submit comment');
const newComment = await response.json();
setComments(prev => [newComment, ...prev]);
setName("")
setRating(null)
setComment("")
toast.success('Kommentar erfolgreich hinzugefügt');
onCommentAdded?.();
} catch (error) {
console.error('Error submitting comment:', error);
toast.error('Fehler beim Speichern des Kommentars');
} finally {
setIsSubmitting(false)
}
}
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date);
};
// Helper function to handle both snake_case and camelCase properties
const getCommentDate = (comment: Comment) => {
return comment.created_at || comment.createdAt || '';
};
const getCommentPhotoId = (comment: Comment) => {
return comment.photo_id || comment.photoId || '';
};
return (
<div className="space-y-6">
<h3 className="text-lg font-semibold">Kommentare</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<Input
placeholder="Ihr Name (optional)"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<div className="flex items-center space-x-1">
{[1, 2, 3, 4, 5].map((star) => (
<Button
key={star}
type="button"
variant="ghost"
size="sm"
className="p-0 hover:bg-transparent"
onClick={() => setRating(star)}
>
<StarIcon
fill={rating && rating >= star ? "currentColor" : "transparent"}
className={`h-6 w-6 ${
rating && rating >= star
? "text-yellow-400 dark:text-yellow-300"
: "text-gray-400 dark:text-gray-500"
}`}
/>
</Button>
))}
{rating !== null && (
<Button
type="button"
variant="ghost"
size="sm"
className="ml-2 text-sm text-muted-foreground hover:text-muted-foreground"
onClick={() => setRating(null)}
>
Zurücksetzen
</Button>
)}
</div>
<Textarea
placeholder="Ihr Kommentar"
value={comment}
onChange={(e) => setComment(e.target.value)}
required
className="min-h-[100px]"
/>
<Button type="submit" disabled={isSubmitting} className="w-full">
{isSubmitting ? 'Wird gespeichert...' : 'Kommentar hinzufügen'}
</Button>
</form>
<div className="space-y-4 pt-6">
<AnimatePresence>
{comments.map((c) => (
<motion.div
key={c.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="rounded-lg border bg-card p-4 text-card-foreground shadow-sm"
>
<div className="flex flex-col space-y-3">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-3">
<Avatar>
<AvatarFallback
style={{ background: generateGradient(c.name) }}
className="text-white font-medium"
>
{c.name[0].toUpperCase()}
</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{c.name}</p>
{c.rating !== null && c.rating > 0 && (
<div className="flex mt-1">
{[1, 2, 3, 4, 5].map((star) => (
<StarIcon
key={star}
fill={c.rating >= star ? "currentColor" : "transparent"}
className={`h-4 w-4 ${
c.rating >= star
? "text-yellow-400 dark:text-yellow-300"
: "text-gray-400 dark:text-gray-500"
}`}
/>
))}
</div>
)}
</div>
</div>
<span className="text-sm text-muted-foreground whitespace-nowrap">
{formatDate(getCommentDate(c))}
</span>
</div>
<p className="text-sm leading-relaxed">{c.text}</p>
</div>
</motion.div>
))}
</AnimatePresence>
{comments.length === 0 && (
<p className="text-center text-muted-foreground text-sm py-4">
Noch keine Kommentare. Seien Sie der Erste!
</p>
)}
</div>
</div>
)
}
export default PhotoComments;Editor is loading...
Leave a Comment