Untitled
unknown
plain_text
17 days ago
8.3 kB
4
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