BlogHome.jsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import React, { useState, useEffect } from "react";
  2. import { Link } from "react-router-dom";
  3. import { useAuth } from "../contexts/AuthContext";
  4. import { API_BASE } from "../config";
  5. import Layout, { SkeletonCard } from "./Layout";
  6. function BlogHome() {
  7. const [posts, setPosts] = useState([]);
  8. const [loading, setLoading] = useState(true);
  9. const [error, setError] = useState(null);
  10. useEffect(() => {
  11. async function getTingyun() {
  12. setLoading(true);
  13. try {
  14. const response = await fetch(`${API_BASE}/posts`);
  15. if (!response.ok)
  16. throw new Error(
  17. `Failed to fetch posts: ${response.statusText}`,
  18. );
  19. const postsData = await response.json();
  20. setPosts(postsData);
  21. } catch (e) {
  22. console.error("Error fetching posts:", e);
  23. setError(
  24. "Failed to load posts. Please check if the backend server is running.",
  25. );
  26. } finally {
  27. setLoading(false);
  28. }
  29. }
  30. getTingyun();
  31. }, []);
  32. if (loading) {
  33. return (
  34. <Layout>
  35. <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  36. {[1, 2, 3, 4, 5, 6].map((i) => (
  37. <SkeletonCard key={i} />
  38. ))}
  39. </div>
  40. </Layout>
  41. );
  42. }
  43. if (error) {
  44. return (
  45. <Layout>
  46. <div className="text-center">
  47. <div className="bg-red-50 border border-red-200 rounded-lg p-6 inline-block">
  48. <h2 className="text-red-800 font-semibold mb-2">
  49. Error
  50. </h2>
  51. <p className="text-red-600">{error}</p>
  52. </div>
  53. </div>
  54. </Layout>
  55. );
  56. }
  57. return (
  58. <Layout>
  59. <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  60. {posts.map((post) => (
  61. <div
  62. key={post.slug}
  63. className="group cursor-pointer theme-surface border theme-border rounded-xl hover:border-blue-400 transition-colors duration-200 p-6 flex flex-col justify-between h-full"
  64. >
  65. <div>
  66. <h2 className="text-xl font-semibold theme-text group-hover:theme-primary transition-colors duration-200 mb-2">
  67. <Link to={`/posts/${post.slug}`}>
  68. {post.title}
  69. </Link>
  70. </h2>
  71. </div>
  72. <div className="flex-grow mt-4">
  73. <div className="theme-text-secondary leading-relaxed">
  74. {post.description}
  75. </div>
  76. </div>
  77. <div className="mt-4">
  78. <Link
  79. to={`/posts/${post.slug}`}
  80. className="theme-primary font-medium hover:underline focus:outline-none"
  81. >
  82. Read more →
  83. </Link>
  84. </div>
  85. </div>
  86. ))}
  87. </div>
  88. </Layout>
  89. );
  90. }
  91. export default BlogHome;