Skip to content

Commit 5e54160

Browse files
authored
Merge pull request #58 from codeunia-dev/feat/articleimgupload
Feature: Add configuration for image domains and enhance blog post image upload functionality
2 parents bb9642c + 63adfa2 commit 5e54160

3 files changed

Lines changed: 96 additions & 24 deletions

File tree

app/admin/blog-posts/page.tsx

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,21 @@ const BlogPostForm = ({
159159
// ref for the content textarea
160160
const contentRef = useRef<HTMLTextAreaElement>(null);
161161

162-
// image upload handler with auto-insert HTML <img> tag
163-
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
162+
// Article image upload handler (for main blog image)
163+
const handleArticleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
164164
const file = e.target.files?.[0];
165165
if (!file) return;
166166

167167
const supabase = createClient();
168-
const filePath = `public/${Date.now()}-${file.name}`;
168+
const filePath = `public/${Date.now()}-article-${file.name}`;
169169

170170
// upload to supabase storage
171171
const { error } = await supabase.storage
172172
.from('blog-images')
173173
.upload(filePath, file);
174174

175175
if (error) {
176-
alert("Image upload failed: " + error.message);
176+
alert("Article image upload failed: " + error.message);
177177
return;
178178
}
179179

@@ -184,7 +184,33 @@ const BlogPostForm = ({
184184

185185
if (publicUrlData?.publicUrl) {
186186
onFormChange({ image: publicUrlData.publicUrl });
187+
}
188+
}
189+
190+
// image upload handler for inserting into content
191+
const handleContentImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
192+
const file = e.target.files?.[0];
193+
if (!file) return;
194+
195+
const supabase = createClient();
196+
const filePath = `public/${Date.now()}-${file.name}`;
197+
198+
// upload to supabase storage
199+
const { error } = await supabase.storage
200+
.from('blog-images')
201+
.upload(filePath, file);
187202

203+
if (error) {
204+
alert("Image upload failed: " + error.message);
205+
return;
206+
}
207+
208+
// Get public url
209+
const { data: publicUrlData } = supabase.storage
210+
.from('blog-images')
211+
.getPublicUrl(filePath);
212+
213+
if (publicUrlData?.publicUrl) {
188214
// insert html <img> tag at cursor in content
189215
if (contentRef.current) {
190216
const textarea = contentRef.current;
@@ -205,6 +231,26 @@ const BlogPostForm = ({
205231

206232
return (
207233
<div className="grid gap-4 py-4">
234+
{/* Article Image upload section */}
235+
<div className="grid gap-2">
236+
<Label htmlFor="article-image">Article Image</Label>
237+
<Input
238+
id="article-image"
239+
type="file"
240+
accept="image/*"
241+
onChange={handleArticleImageUpload}
242+
className="text-sm"
243+
/>
244+
{formData.image && (
245+
<div>
246+
<img src={formData.image} alt="Article Preview" className="mt-2 max-h-32" />
247+
<div className="mt-2 flex items-center gap-2">
248+
<Input value={formData.image} readOnly className="text-xs" />
249+
</div>
250+
</div>
251+
)}
252+
</div>
253+
208254
<div className="grid gap-2">
209255
<Label htmlFor="title">Title *</Label>
210256
<Input
@@ -227,6 +273,7 @@ const BlogPostForm = ({
227273
/>
228274
</div>
229275

276+
{/* Content image upload button */}
230277
<div className="grid gap-2">
231278
<Label htmlFor="content">Content *</Label>
232279
<Textarea
@@ -237,6 +284,14 @@ const BlogPostForm = ({
237284
onChange={handleInputChange('content')}
238285
className="text-sm min-h-[120px]"
239286
/>
287+
<Input
288+
id="content-image"
289+
type="file"
290+
accept="image/*"
291+
onChange={handleContentImageUpload}
292+
className="text-sm mt-2"
293+
/>
294+
<span className="text-xs text-muted-foreground">Upload and insert image at cursor in content</span>
240295
</div>
241296

242297
<div className="grid grid-cols-2 gap-4">
@@ -316,26 +371,6 @@ const BlogPostForm = ({
316371
</Select>
317372
</div>
318373

319-
{/* Image upload section */}
320-
<div className="grid gap-2">
321-
<Label htmlFor="image">Image</Label>
322-
<Input
323-
id="image"
324-
type="file"
325-
accept="image/*"
326-
onChange={handleImageUpload}
327-
className="text-sm"
328-
/>
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-
)}
337-
</div>
338-
339374
<div className="flex items-center space-x-2">
340375
<Checkbox
341376
id="featured"

app/blog/page.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { motion } from "framer-motion"
1313
import { categories, BlogPost } from "@/components/data/blog-posts"
1414
import Header from "@/components/header";
1515
import Footer from "@/components/footer";
16+
import Image from "next/image";
1617

1718
export default function BlogPage() {
1819
const [searchTerm, setSearchTerm] = useState("")
@@ -333,6 +334,20 @@ export default function BlogPage() {
333334
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-purple-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
334335
<div className="h-48 bg-gradient-to-br from-muted to-muted/50 relative overflow-hidden">
335336
<div className="absolute inset-0 bg-gradient-to-br from-primary/10 via-transparent to-purple-500/10"></div>
337+
{post.image ? (
338+
<Image
339+
src={post.image}
340+
alt={post.title || 'Blog post image'}
341+
fill
342+
className="object-cover w-full h-full"
343+
style={{ zIndex: 1 }}
344+
priority={index < 2}
345+
/>
346+
) : (
347+
<div className="flex items-center justify-center w-full h-full">
348+
<BookOpen className="h-16 w-16 text-muted-foreground opacity-40" />
349+
</div>
350+
)}
336351
<div className="absolute top-4 left-4 z-10">
337352
<Badge className={`${getCategoryColor(post.category)} shadow-lg`} variant="secondary">
338353
{post.category}
@@ -459,6 +474,20 @@ export default function BlogPage() {
459474
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-purple-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
460475
<div className="h-40 bg-gradient-to-br from-muted to-muted/50 relative overflow-hidden">
461476
<div className={`absolute inset-0 bg-gradient-to-br ${getCategoryGradient(post.category)}`}></div>
477+
{post.image ? (
478+
<Image
479+
src={post.image}
480+
alt={post.title || 'Blog post image'}
481+
fill
482+
className="object-cover w-full h-full"
483+
style={{ zIndex: 1 }}
484+
priority={index < 3}
485+
/>
486+
) : (
487+
<div className="flex items-center justify-center w-full h-full">
488+
<BookOpen className="h-12 w-12 text-muted-foreground opacity-40" />
489+
</div>
490+
)}
462491
<div className="absolute top-3 left-3 z-10">
463492
<Badge className={`${getCategoryColor(post.category)} shadow-lg`} variant="secondary">
464493
{post.category}

next.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
images: {
3+
domains: [
4+
'ocnorlktyfswjqgvzrve.supabase.co',
5+
],
6+
},
7+
// ...other config
8+
}

0 commit comments

Comments
 (0)