TypeScript · 3462 bytes Raw Blame History
1 // components/ContributionDisplay.tsx
2
3 // alias Image to shut the linter up
4 import NextImage from 'next/image';
5 import { Contribution } from '@/lib/api';
6
7
8 interface ContributionDisplayProps {
9 contributions: Contribution[];
10 }
11
12 export default function ContributionDisplay({ contributions }: ContributionDisplayProps) {
13 if (contributions.length === 0) {
14 return null;
15 }
16
17 const formatDate = (dateString: string) => {
18 const date = new Date(dateString);
19 return date.toLocaleDateString('en-US', {
20 year: 'numeric',
21 month: 'long',
22 day: 'numeric'
23 });
24 };
25
26 return (
27 <div className="space-y-6">
28 <h3 className="text-xl font-bold text-vmi-red mb-4">
29 Community Contributions ({contributions.length})
30 </h3>
31
32 <div className="space-y-4">
33 {contributions.map((contribution) => (
34 <div
35 key={contribution.id}
36 className="bg-gray-50 border border-gray-200 rounded-lg p-6"
37 >
38 {/* Header */}
39 <div className="flex justify-between items-start mb-4">
40 <div>
41 <p className="text-sm text-gray-600">
42 Contributed by <span className="font-semibold">{contribution.contributor_display}</span>
43 </p>
44 <p className="text-xs text-gray-500">
45 {formatDate(contribution.submitted_at)}
46 </p>
47 </div>
48 <span className={`
49 px-3 py-1 rounded-full text-xs font-semibold
50 ${contribution.content_type === 'text' ? 'bg-blue-100 text-blue-700' : ''}
51 ${contribution.content_type === 'image' ? 'bg-green-100 text-green-700' : ''}
52 ${contribution.content_type === 'both' ? 'bg-purple-100 text-purple-700' : ''}
53 `}>
54 {contribution.content_type === 'text' && 'Text'}
55 {contribution.content_type === 'image' && 'Image'}
56 {contribution.content_type === 'both' && 'Text & Image'}
57 </span>
58 </div>
59
60 {/* Content */}
61 <div className="space-y-4">
62 {/* Text Content */}
63 {contribution.content_text && (
64 <div className="prose prose-gray max-w-none">
65 <p className="text-gray-800 whitespace-pre-wrap">
66 {contribution.content_text}
67 </p>
68 </div>
69 )}
70 {/* Image Content */}
71 {contribution.content_image && (
72 <div className="mt-4">
73 <div
74 className="cursor-pointer hover:opacity-95 transition-opacity"
75 onClick={() => window.open(contribution.content_image, '_blank')}
76 >
77 <NextImage
78 src={contribution.content_image}
79 alt="Community contribution"
80 width={800}
81 height={600}
82 className="max-w-full h-auto rounded-lg border-2 border-gray-300"
83 style={{ objectFit: 'contain' }}
84 />
85 </div>
86 <p className="text-xs text-gray-500 mt-2">
87 Click image to view full size
88 </p>
89 </div>
90 )}
91 </div>
92 </div>
93 ))}
94 </div>
95 </div>
96 );
97 }