155 lines
6.6 KiB
TypeScript
155 lines
6.6 KiB
TypeScript
"use client";
|
|
|
|
import Link from 'next/link';
|
|
import React, { useEffect, useState } from 'react';
|
|
import axios from 'axios';
|
|
import Cookies from 'universal-cookie';
|
|
import Swal from 'sweetalert2';
|
|
import { buildApiUrl } from '@/utils/BaseUrl.utils';
|
|
import { showMessage } from '@/utils/CommonFunction.utils';
|
|
|
|
interface BlogItem {
|
|
id: number;
|
|
title: string;
|
|
slug: string;
|
|
image: string;
|
|
description: string;
|
|
author: string;
|
|
profile: string;
|
|
date: string;
|
|
}
|
|
|
|
const Blog = () => {
|
|
const [blogs, setBlogs] = useState<BlogItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const fetchBlogs = async () => {
|
|
try {
|
|
const res = await axios.get(buildApiUrl('blogs'));
|
|
if (res.data && res.data.success) {
|
|
setBlogs(res.data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching blogs:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchBlogs();
|
|
}, []);
|
|
|
|
const handleDelete = async (id: number) => {
|
|
try {
|
|
const result = await Swal.fire({
|
|
title: 'Are you sure?',
|
|
text: "You won't be able to revert this!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#3085d6',
|
|
cancelButtonColor: '#d33',
|
|
confirmButtonText: 'Yes, delete it!'
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
const cookies = new Cookies();
|
|
const token = cookies.get('token');
|
|
if (!token) {
|
|
showMessage('Access denied. Please sign in first.', 'error');
|
|
return;
|
|
}
|
|
|
|
await axios.delete(buildApiUrl(`blogs/${id}`), {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
setBlogs(prev => prev.filter(b => b.id !== id));
|
|
showMessage("Blog Deleted Successfully", "success");
|
|
}
|
|
} catch (error: any) {
|
|
console.error("Error deleting blog:", error);
|
|
showMessage(error?.response?.data?.message || "Error deleting blog", "error");
|
|
}
|
|
};
|
|
|
|
const stripHtml = (htmlStr: string) => {
|
|
if (!htmlStr) return '';
|
|
return htmlStr.replace(/<[^>]*>/g, '');
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="mt-10">
|
|
<h3 className="mb-6 text-xl font-bold md:text-3xl">Blogs</h3>
|
|
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-4">
|
|
{/* ✅ First box: Create New Blog */}
|
|
<Link href="/create-blog" className="flex items-center justify-center space-y-5 rounded-md border border-dashed border-blue-500 bg-blue-50 p-5 text-center shadow hover:bg-blue-100 transition dark:border-blue-800 dark:bg-blue-900 dark:hover:bg-blue-800 min-h-[300px]">
|
|
<div>
|
|
<div className="mb-3 text-5xl text-blue-600 dark:text-white">+</div>
|
|
<h5 className="text-lg font-semibold text-blue-800 dark:text-white">Create New Blog</h5>
|
|
</div>
|
|
</Link>
|
|
{blogs.map((blog) => {
|
|
const cleanText = stripHtml(blog.description);
|
|
const excerpt = cleanText.length > 120 ? cleanText.substring(0, 120) + '...' : cleanText;
|
|
|
|
return (
|
|
<div
|
|
key={blog.id}
|
|
className="flex flex-col justify-between space-y-4 rounded-md border border-white-light bg-white p-5 shadow-[0px_0px_2px_0px_rgba(145,158,171,0.20),0px_12px_24px_-4px_rgba(145,158,171,0.12)] dark:border-[#1B2E4B] dark:bg-black"
|
|
>
|
|
<div>
|
|
<div className="max-h-56 overflow-hidden rounded-md mb-4 bg-gray-100 flex items-center justify-center aspect-video">
|
|
<img src={blog.image || '/assets/images/blog/image-1.jpg'} alt={blog.title} className="w-full h-full object-cover" />
|
|
</div>
|
|
|
|
<h5 className="text-lg font-semibold dark:text-white line-clamp-2" title={blog.title}>{blog.title}</h5>
|
|
<p className="text-sm text-gray-600 dark:text-gray-400 mt-2 line-clamp-3">{excerpt || 'No description provided.'}</p>
|
|
<p className="text-xs text-gray-400 mt-3">By {blog.author} on {blog.date}</p>
|
|
</div>
|
|
|
|
{/* ✅ Actions (View, Edit and Delete) */}
|
|
<div className="flex justify-between items-center pt-3 border-t border-gray-100 dark:border-gray-800 space-x-2">
|
|
<a
|
|
href={`http://localhost:3000/blog-single?slug=${blog.slug}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-block rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700 transition"
|
|
>
|
|
View Blog
|
|
</a>
|
|
<div className="flex space-x-2">
|
|
<Link
|
|
href={`/edit-blog/${blog.id}`}
|
|
className="inline-block rounded-lg bg-green-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-green-700 transition"
|
|
>
|
|
Edit
|
|
</Link>
|
|
<button
|
|
onClick={() => handleDelete(blog.id)}
|
|
className="rounded-lg bg-red-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-red-700 transition"
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Blog;
|