Skip to content

Commit 352ca4a

Browse files
authored
Merge pull request #49 from codeunia-dev/feat/imageuploadtoblog
Feature: Add image upload functionality to Blog Post form
2 parents 874a529 + c6fcb57 commit 352ca4a

File tree

1 file changed

+62
-5
lines changed

1 file changed

+62
-5
lines changed

app/admin/blog-posts/page.tsx

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { useState, useEffect, useCallback, useMemo } from "react"
3+
import { useState, useEffect, useCallback, useMemo, useRef } from "react"
44
import { createClient } from "@/lib/supabase/client"
55
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
66
import { Badge } from "@/components/ui/badge"
@@ -156,6 +156,53 @@ const BlogPostForm = ({
156156
onFormChange({ featured: checked })
157157
}
158158

159+
// ref for the content textarea
160+
const contentRef = useRef<HTMLTextAreaElement>(null);
161+
162+
// image upload handler with auto-insert HTML <img> tag
163+
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
164+
const file = e.target.files?.[0];
165+
if (!file) return;
166+
167+
const supabase = createClient();
168+
const filePath = `public/${Date.now()}-${file.name}`;
169+
170+
// upload to supabase storage
171+
const { error } = await supabase.storage
172+
.from('blog-images')
173+
.upload(filePath, file);
174+
175+
if (error) {
176+
alert("Image upload failed: " + error.message);
177+
return;
178+
}
179+
180+
// Get public url
181+
const { data: publicUrlData } = supabase.storage
182+
.from('blog-images')
183+
.getPublicUrl(filePath);
184+
185+
if (publicUrlData?.publicUrl) {
186+
onFormChange({ image: publicUrlData.publicUrl });
187+
188+
// insert html <img> tag at cursor in content
189+
if (contentRef.current) {
190+
const textarea = contentRef.current;
191+
const start = textarea.selectionStart;
192+
const end = textarea.selectionEnd;
193+
const before = formData.content.slice(0, start);
194+
const after = formData.content.slice(end);
195+
const htmlImg = `<img src="${publicUrlData.publicUrl}" alt="Alt text" style="max-width:100%;height:auto;" />\n`;
196+
const newContent = before + htmlImg + after;
197+
onFormChange({ content: newContent });
198+
setTimeout(() => {
199+
textarea.focus();
200+
textarea.selectionStart = textarea.selectionEnd = start + htmlImg.length;
201+
}, 0);
202+
}
203+
}
204+
}
205+
159206
return (
160207
<div className="grid gap-4 py-4">
161208
<div className="grid gap-2">
@@ -184,6 +231,7 @@ const BlogPostForm = ({
184231
<Label htmlFor="content">Content *</Label>
185232
<Textarea
186233
id="content"
234+
ref={contentRef}
187235
placeholder="Write your blog post content here..."
188236
value={formData.content}
189237
onChange={handleInputChange('content')}
@@ -268,15 +316,24 @@ const BlogPostForm = ({
268316
</Select>
269317
</div>
270318

319+
{/* Image upload section */}
271320
<div className="grid gap-2">
272-
<Label htmlFor="image">Image URL</Label>
321+
<Label htmlFor="image">Image</Label>
273322
<Input
274323
id="image"
275-
placeholder="https://example.com/image.jpg"
276-
value={formData.image}
277-
onChange={handleInputChange('image')}
324+
type="file"
325+
accept="image/*"
326+
onChange={handleImageUpload}
278327
className="text-sm"
279328
/>
329+
{formData.image && (
330+
<div>
331+
<img src={formData.image} alt="Preview" className="mt-2 max-h-32" />
332+
<div className="mt-2 flex items-center gap-2">
333+
<Input value={formData.image} readOnly className="text-xs" />
334+
</div>
335+
</div>
336+
)}
280337
</div>
281338

282339
<div className="flex items-center space-x-2">

0 commit comments

Comments
 (0)