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
83 changes: 59 additions & 24 deletions app/admin/blog-posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,21 @@ const BlogPostForm = ({
// ref for the content textarea
const contentRef = useRef<HTMLTextAreaElement>(null);

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

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

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

if (error) {
alert("Image upload failed: " + error.message);
alert("Article image upload failed: " + error.message);
return;
}

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

if (publicUrlData?.publicUrl) {
onFormChange({ image: publicUrlData.publicUrl });
}
}

// image upload handler for inserting into content
const handleContentImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;

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

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

if (error) {
alert("Image upload failed: " + error.message);
return;
}

// Get public url
const { data: publicUrlData } = supabase.storage
.from('blog-images')
.getPublicUrl(filePath);

if (publicUrlData?.publicUrl) {
// insert html <img> tag at cursor in content
if (contentRef.current) {
const textarea = contentRef.current;
Expand All @@ -205,6 +231,26 @@ const BlogPostForm = ({

return (
<div className="grid gap-4 py-4">
{/* Article Image upload section */}
<div className="grid gap-2">
<Label htmlFor="article-image">Article Image</Label>
<Input
id="article-image"
type="file"
accept="image/*"
onChange={handleArticleImageUpload}
className="text-sm"
/>
{formData.image && (
<div>
<img src={formData.image} alt="Article Preview" className="mt-2 max-h-32" />
<div className="mt-2 flex items-center gap-2">
<Input value={formData.image} readOnly className="text-xs" />
</div>
</div>
)}
</div>

<div className="grid gap-2">
<Label htmlFor="title">Title *</Label>
<Input
Expand All @@ -227,6 +273,7 @@ const BlogPostForm = ({
/>
</div>

{/* Content image upload button */}
<div className="grid gap-2">
<Label htmlFor="content">Content *</Label>
<Textarea
Expand All @@ -237,6 +284,14 @@ const BlogPostForm = ({
onChange={handleInputChange('content')}
className="text-sm min-h-[120px]"
/>
<Input
id="content-image"
type="file"
accept="image/*"
onChange={handleContentImageUpload}
className="text-sm mt-2"
/>
<span className="text-xs text-muted-foreground">Upload and insert image at cursor in content</span>
</div>

<div className="grid grid-cols-2 gap-4">
Expand Down Expand Up @@ -316,26 +371,6 @@ const BlogPostForm = ({
</Select>
</div>

{/* Image upload section */}
<div className="grid gap-2">
<Label htmlFor="image">Image</Label>
<Input
id="image"
type="file"
accept="image/*"
onChange={handleImageUpload}
className="text-sm"
/>
{formData.image && (
<div>
<img src={formData.image} alt="Preview" className="mt-2 max-h-32" />
<div className="mt-2 flex items-center gap-2">
<Input value={formData.image} readOnly className="text-xs" />
</div>
</div>
)}
</div>

<div className="flex items-center space-x-2">
<Checkbox
id="featured"
Expand Down
29 changes: 29 additions & 0 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { motion } from "framer-motion"
import { categories, BlogPost } from "@/components/data/blog-posts"
import Header from "@/components/header";
import Footer from "@/components/footer";
import Image from "next/image";

export default function BlogPage() {
const [searchTerm, setSearchTerm] = useState("")
Expand Down Expand Up @@ -333,6 +334,20 @@ export default function BlogPage() {
<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>
<div className="h-48 bg-gradient-to-br from-muted to-muted/50 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-primary/10 via-transparent to-purple-500/10"></div>
{post.image ? (
<Image
src={post.image}
alt={post.title || 'Blog post image'}
fill
className="object-cover w-full h-full"
style={{ zIndex: 1 }}
priority={index < 2}
/>
) : (
<div className="flex items-center justify-center w-full h-full">
<BookOpen className="h-16 w-16 text-muted-foreground opacity-40" />
</div>
)}
<div className="absolute top-4 left-4 z-10">
<Badge className={`${getCategoryColor(post.category)} shadow-lg`} variant="secondary">
{post.category}
Expand Down Expand Up @@ -459,6 +474,20 @@ export default function BlogPage() {
<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>
<div className="h-40 bg-gradient-to-br from-muted to-muted/50 relative overflow-hidden">
<div className={`absolute inset-0 bg-gradient-to-br ${getCategoryGradient(post.category)}`}></div>
{post.image ? (
<Image
src={post.image}
alt={post.title || 'Blog post image'}
fill
className="object-cover w-full h-full"
style={{ zIndex: 1 }}
priority={index < 3}
/>
) : (
<div className="flex items-center justify-center w-full h-full">
<BookOpen className="h-12 w-12 text-muted-foreground opacity-40" />
</div>
)}
<div className="absolute top-3 left-3 z-10">
<Badge className={`${getCategoryColor(post.category)} shadow-lg`} variant="secondary">
{post.category}
Expand Down
8 changes: 8 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
images: {
domains: [
'ocnorlktyfswjqgvzrve.supabase.co',
],
},
// ...other config
}