Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/api/testimonials/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function POST(request: Request) {
const data = await request.json();

// Basic validation
if (!data.name || !data.role || !data.email || !data.content || !data.imageUrl) {
if (!data.name || !data.role || !data.email || !data.content) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
Expand All @@ -33,6 +33,7 @@ export async function POST(request: Request) {

const testimonial = await Testimonial.create({
...data,
imageUrl: data.imageUrl || '/avatar.svg',
isApproved: false, // Explicitly false for public submissions
type: data.videoUrl ? 'video' : 'text'
});
Expand Down
23 changes: 17 additions & 6 deletions components/submit-testimonial-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function SubmitTestimonialModal() {
role: '',
email: '',
content: '',
videoUrl: ''
videoUrl: '',
cta: ''
})

const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -57,8 +58,6 @@ export function SubmitTestimonialModal() {
if (!uploadRes.ok) throw new Error('Image upload failed')
const uploadData = await uploadRes.json()
imageUrl = uploadData.url
} else {
throw new Error('Image is required')
}

// Submit testimonial data
Expand All @@ -84,7 +83,8 @@ export function SubmitTestimonialModal() {
role: '',
email: '',
content: '',
videoUrl: ''
videoUrl: '',
cta: ''
})
setImageFile(null)
setImagePreview(null)
Expand Down Expand Up @@ -166,15 +166,14 @@ export function SubmitTestimonialModal() {
</div>

<div className="space-y-2">
<Label>Your Photo *</Label>
<Label>Your Photo (Optional)</Label>
<div className="flex items-center gap-4">
<Input
type="file"
accept="image/*"
onChange={handleImageChange}
className="hidden"
id="testimonial-image-upload"
required
/>
<Label
htmlFor="testimonial-image-upload"
Expand Down Expand Up @@ -215,6 +214,18 @@ export function SubmitTestimonialModal() {
<p className="text-xs text-muted-foreground">Link to a YouTube video or similar.</p>
</div>

<div className="space-y-2">
<Label htmlFor="cta">CTA / Message to Readers (Optional)</Label>
<Input
id="cta"
type="text"
placeholder="Join Dev Weekends to level up your skills"
value={formData.cta}
onChange={(e) => setFormData({...formData, cta: e.target.value})}
/>
<p className="text-xs text-muted-foreground">Short call-to-action or closing thought.</p>
</div>

<DialogFooter>
<Button type="submit" disabled={loading} className="w-full">
{loading && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}
Expand Down
20 changes: 16 additions & 4 deletions components/testimonial-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ interface Testimonial {
name: string;
role: string;
content: string;
cta?: string;
videoUrl?: string;
imageUrl: string;
imageUrl?: string;
type: 'text' | 'video';
}

Expand All @@ -28,9 +29,10 @@ export function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
};

const videoId = testimonial.videoUrl ? getYoutubeId(testimonial.videoUrl) : null;
const avatarUrl = testimonial.imageUrl || '/avatar.svg';
const thumbnailUrl = videoId
? `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`
: testimonial.imageUrl;
: avatarUrl;

if (testimonial.type === 'video' && testimonial.videoUrl) {
return (
Expand All @@ -54,7 +56,7 @@ export function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
<CardHeader className="p-4 pb-2 space-y-1">
<div className="flex items-center gap-3">
<div className="relative w-10 h-10 rounded-full overflow-hidden shrink-0 border">
<Image src={testimonial.imageUrl} alt={testimonial.name} fill className="object-cover" />
<Image src={avatarUrl} alt={testimonial.name} fill className="object-cover" />
</div>
<div>
<h3 className="font-semibold leading-tight">{testimonial.name}</h3>
Expand Down Expand Up @@ -99,6 +101,11 @@ export function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
</div>
</div>
<p className="text-sm text-muted-foreground italic">"{testimonial.content}"</p>
{testimonial.cta && (
<p className="mt-3 text-sm font-semibold text-foreground">
{testimonial.cta}
</p>
)}
</div>
</DialogContent>
</Dialog>
Expand All @@ -115,7 +122,7 @@ export function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
<div className="flex items-center gap-3 mt-auto pt-4 border-t">
<div className="relative w-10 h-10 rounded-full overflow-hidden shrink-0 border">
<Image
src={testimonial.imageUrl}
src={avatarUrl}
alt={testimonial.name}
fill
className="object-cover"
Expand All @@ -126,6 +133,11 @@ export function TestimonialCard({ testimonial }: { testimonial: Testimonial }) {
<p className="text-xs text-muted-foreground">{testimonial.role}</p>
</div>
</div>
{testimonial.cta && (
<p className="text-sm font-semibold text-foreground">
{testimonial.cta}
</p>
)}
</CardContent>
</Card>
);
Expand Down
9 changes: 7 additions & 2 deletions models/Testimonial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ export interface ITestimonial extends Document {
role: string;
email: string;
content: string;
cta?: string;
videoUrl?: string;
imageUrl: string;
imageUrl?: string;
type: 'text' | 'video';
isApproved: boolean;
createdAt: Date;
Expand Down Expand Up @@ -35,13 +36,17 @@ const TestimonialSchema = new Schema<ITestimonial>(
required: [true, 'Testimonial content is required'],
trim: true,
},
cta: {
type: String,
trim: true,
},
videoUrl: {
type: String,
trim: true,
},
imageUrl: {
type: String,
required: [true, 'Image/Avatar is required'],
default: '/avatar.svg',
},
type: {
type: String,
Expand Down
Loading