From 7e3836ad34a7d81de875b627a51e2f2cd51599cd Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Sat, 11 May 2024 00:58:30 +0200 Subject: [PATCH 1/8] Add blog section initial commit --- packages/backend/index.js | 2 + packages/backend/routes/blog.js | 36 +++++++++++++ packages/react-app/components/BlogSection.jsx | 51 +++++++++++++++++++ packages/react-app/data/api/blog.js | 12 +++++ packages/react-app/pages/index.jsx | 7 ++- 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 packages/backend/routes/blog.js create mode 100644 packages/react-app/components/BlogSection.jsx create mode 100644 packages/react-app/data/api/blog.js diff --git a/packages/backend/index.js b/packages/backend/index.js index 3ac69c90..bd627ebb 100644 --- a/packages/backend/index.js +++ b/packages/backend/index.js @@ -14,6 +14,7 @@ const ensRoutes = require("./routes/ens"); const apiRoutes = require("./routes/api"); const cohortRoutes = require("./routes/cohorts"); const notificationsRoutes = require("./routes/notifications"); +const blogRoutes = require("./routes/blog"); const app = express(); @@ -31,6 +32,7 @@ app.use("/ens", ensRoutes); app.use("/api", apiRoutes); app.use("/cohorts", cohortRoutes); app.use("/notifications", notificationsRoutes); +app.use("/blog", blogRoutes); app.get("/sign-message", async (req, res) => { const messageId = req.query.messageId; diff --git a/packages/backend/routes/blog.js b/packages/backend/routes/blog.js new file mode 100644 index 00000000..ed15aa61 --- /dev/null +++ b/packages/backend/routes/blog.js @@ -0,0 +1,36 @@ +const express = require("express"); +const axios = require("axios"); +const xml2js = require("xml2js"); +const cors = require('cors'); + +const router = express.Router(); + +router.get("/posts", cors(), async (req, res) => { + try { + const response = await axios.get("https://buidlguidl.substack.com/feed"); + const xml = response.data; + + const parser = new xml2js.Parser(); + parser.parseString(xml, (err, result) => { + if (err) { + console.error("Failed to parse blog feed:", err); + res.status(500).json({ error: "Failed to parse blog feed" }); + } else { + const posts = result.rss.channel[0].item.map(item => ({ + title: item.title[0], + description: item.description[0], + link: item.link[0], + pubDate: item.pubDate[0], + imageUrl: item.enclosure ? item.enclosure[0].$.url : null + })); + + res.status(200).json(posts.slice(0, 3)); // Return the last 3 posts + } + }); + } catch (error) { + console.error("Error fetching blog posts:", error); + res.status(500).json({ error: "Internal Server Error" }); + } +}); + +module.exports = router; diff --git a/packages/react-app/components/BlogSection.jsx b/packages/react-app/components/BlogSection.jsx new file mode 100644 index 00000000..2200b69e --- /dev/null +++ b/packages/react-app/components/BlogSection.jsx @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from 'react'; +import { Container, VStack, Box, Heading, Text, Image, Link, Flex } from '@chakra-ui/react'; +import { fetchRecentPosts } from "../data/api/blog"; + +const BlogSection = ( posts ) => { + const [posts, setPosts] = useState([]); + + useEffect(() => { + const fetchPosts = async () => { + try { + const recentPosts = await fetchRecentPosts(); + setPosts(recentPosts); + } catch (error) { + console.error('Error fetching posts:', error); + } + }; + + fetchPosts(); + }, []); + + return ( + + + Shipping Log + + {posts.map((post, index) => { + const date = new Date(post.pubDate); + const formattedDate = `${date.toLocaleString('default', { month: 'short' }).toUpperCase()} ${date.getDate()}`; + + return ( + + + + + + {post.title} + {post.description} + {formattedDate} + + {post.title} + + + + + ); + })} + + ); +}; + +export default BlogSection; diff --git a/packages/react-app/data/api/blog.js b/packages/react-app/data/api/blog.js new file mode 100644 index 00000000..d72f32a9 --- /dev/null +++ b/packages/react-app/data/api/blog.js @@ -0,0 +1,12 @@ +import axios from "axios"; +import { SERVER_URL as serverUrl } from "../../constants"; + +export const fetchRecentPosts = async () => { + try { + const response = await axios.get(`${serverUrl}/blog/posts`); + return response.data; + } catch (error) { + console.error("Error fetching recent posts:", error); + throw new Error("Couldn't fetch recent posts"); + } +}; diff --git a/packages/react-app/pages/index.jsx b/packages/react-app/pages/index.jsx index c3c2f12e..c185a64f 100644 --- a/packages/react-app/pages/index.jsx +++ b/packages/react-app/pages/index.jsx @@ -7,10 +7,11 @@ import { getStats } from "../data/api/builder"; import HeroSection from "../components/home/HeroSection"; import ActivitySection from "../components/home/ActivitySection"; import { getAllEvents } from "../data/api"; +import BlogSection from "../components/BlogSection"; const buildersToShow = ["fullstack", "frontend", "damageDealer", "advisor", "artist", "support"]; /* eslint-disable jsx-a11y/accessible-emoji */ -export default function Index({ bgStats, events }) { +export default function Index({ bgStats, events, posts }) { const [builders, setBuilders] = useState([]); const [isLoadingBuilders, setIsLoadingBuilders] = useState(false); @@ -48,6 +49,8 @@ export default function Index({ bgStats, events }) { + + {/* Footer */} @@ -64,6 +67,7 @@ export default function Index({ bgStats, events }) { export async function getStaticProps() { const stats = await getStats(); const events = await getAllEvents(null, 10); + const posts = await fetchRecentPosts(); return { props: { @@ -76,6 +80,7 @@ export async function getStaticProps() { streamedEthIncrementMonth: stats?.streamedEthIncrementMonth, }, events, + posts, }, // ToDo. Maybe a 15 min refresh? or load events in the frontend? // 6 hours refresh. From ee5861a894dc9027e492862922caea853c38c1b8 Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Sun, 12 May 2024 00:22:42 +0200 Subject: [PATCH 2/8] Remove fetch logic from component to send posts with prop --- packages/react-app/components/BlogSection.jsx | 39 ++++++------------- packages/react-app/pages/index.jsx | 1 + 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/packages/react-app/components/BlogSection.jsx b/packages/react-app/components/BlogSection.jsx index 2200b69e..c30fa90d 100644 --- a/packages/react-app/components/BlogSection.jsx +++ b/packages/react-app/components/BlogSection.jsx @@ -1,33 +1,16 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { Container, VStack, Box, Heading, Text, Image, Link, Flex } from '@chakra-ui/react'; -import { fetchRecentPosts } from "../data/api/blog"; - -const BlogSection = ( posts ) => { - const [posts, setPosts] = useState([]); - - useEffect(() => { - const fetchPosts = async () => { - try { - const recentPosts = await fetchRecentPosts(); - setPosts(recentPosts); - } catch (error) { - console.error('Error fetching posts:', error); - } - }; - - fetchPosts(); - }, []); +const BlogSection = ({ posts }) => { return ( - - - Shipping Log - - {posts.map((post, index) => { - const date = new Date(post.pubDate); - const formattedDate = `${date.toLocaleString('default', { month: 'short' }).toUpperCase()} ${date.getDate()}`; - - return ( + + + Shipping Log + + {posts.map((post, index) => { + const date = new Date(post.pubDate); + const formattedDate = `${date.toLocaleString('default', { month: 'short' }).toUpperCase()} ${date.getDate()}`; + return ( @@ -37,7 +20,7 @@ const BlogSection = ( posts ) => { {post.description} {formattedDate} - {post.title} + {post.title} diff --git a/packages/react-app/pages/index.jsx b/packages/react-app/pages/index.jsx index c185a64f..57ed5a8d 100644 --- a/packages/react-app/pages/index.jsx +++ b/packages/react-app/pages/index.jsx @@ -8,6 +8,7 @@ import HeroSection from "../components/home/HeroSection"; import ActivitySection from "../components/home/ActivitySection"; import { getAllEvents } from "../data/api"; import BlogSection from "../components/BlogSection"; +import { fetchRecentPosts } from "../data/api/blog"; const buildersToShow = ["fullstack", "frontend", "damageDealer", "advisor", "artist", "support"]; /* eslint-disable jsx-a11y/accessible-emoji */ From 0d0336d7d3ce693673df59370552d1c6bea395b6 Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Sun, 12 May 2024 00:22:49 +0200 Subject: [PATCH 3/8] Remove cors --- packages/backend/routes/blog.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend/routes/blog.js b/packages/backend/routes/blog.js index ed15aa61..b2a44670 100644 --- a/packages/backend/routes/blog.js +++ b/packages/backend/routes/blog.js @@ -1,16 +1,15 @@ const express = require("express"); const axios = require("axios"); const xml2js = require("xml2js"); -const cors = require('cors'); const router = express.Router(); -router.get("/posts", cors(), async (req, res) => { +router.get("/posts", async (req, res) => { try { const response = await axios.get("https://buidlguidl.substack.com/feed"); const xml = response.data; - const parser = new xml2js.Parser(); + parser.parseString(xml, (err, result) => { if (err) { console.error("Failed to parse blog feed:", err); From 0b56183a7aceb9a4efa1afc443ec690f0b93a46b Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Sun, 12 May 2024 00:32:44 +0200 Subject: [PATCH 4/8] Tweak color handling and move BlogSection component to the right folder --- packages/react-app/components/{ => home}/BlogSection.jsx | 8 +++++--- packages/react-app/pages/index.jsx | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) rename packages/react-app/components/{ => home}/BlogSection.jsx (80%) diff --git a/packages/react-app/components/BlogSection.jsx b/packages/react-app/components/home/BlogSection.jsx similarity index 80% rename from packages/react-app/components/BlogSection.jsx rename to packages/react-app/components/home/BlogSection.jsx index c30fa90d..76014696 100644 --- a/packages/react-app/components/BlogSection.jsx +++ b/packages/react-app/components/home/BlogSection.jsx @@ -1,8 +1,10 @@ import React from 'react'; import { Container, VStack, Box, Heading, Text, Image, Link, Flex } from '@chakra-ui/react'; +import useCustomColorModes from "../../hooks/useCustomColorModes"; const BlogSection = ({ posts }) => { - return ( + const { baseColor, secondaryFontColor } = useCustomColorModes(); + return ( Shipping Log @@ -13,12 +15,12 @@ const BlogSection = ({ posts }) => { return ( - + {post.title} {post.description} - {formattedDate} + {formattedDate} {post.title} diff --git a/packages/react-app/pages/index.jsx b/packages/react-app/pages/index.jsx index 57ed5a8d..8cd6ba0c 100644 --- a/packages/react-app/pages/index.jsx +++ b/packages/react-app/pages/index.jsx @@ -7,7 +7,7 @@ import { getStats } from "../data/api/builder"; import HeroSection from "../components/home/HeroSection"; import ActivitySection from "../components/home/ActivitySection"; import { getAllEvents } from "../data/api"; -import BlogSection from "../components/BlogSection"; +import BlogSection from "../components/home/BlogSection"; import { fetchRecentPosts } from "../data/api/blog"; const buildersToShow = ["fullstack", "frontend", "damageDealer", "advisor", "artist", "support"]; From c25e71536c9b642215ac9ee08d07ab0c5a34bf69 Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Sun, 12 May 2024 00:56:10 +0200 Subject: [PATCH 5/8] Move date formatting logic out from frontend component --- packages/backend/routes/blog.js | 19 +++++---- .../react-app/components/home/BlogSection.jsx | 41 +++++++++---------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/backend/routes/blog.js b/packages/backend/routes/blog.js index b2a44670..4fec59e1 100644 --- a/packages/backend/routes/blog.js +++ b/packages/backend/routes/blog.js @@ -15,13 +15,18 @@ router.get("/posts", async (req, res) => { console.error("Failed to parse blog feed:", err); res.status(500).json({ error: "Failed to parse blog feed" }); } else { - const posts = result.rss.channel[0].item.map(item => ({ - title: item.title[0], - description: item.description[0], - link: item.link[0], - pubDate: item.pubDate[0], - imageUrl: item.enclosure ? item.enclosure[0].$.url : null - })); + const posts = result.rss.channel[0].item.map(item => { + const date = new Date(item.pubDate[0]); + const formattedDate = `${date.toLocaleString('default', { month: 'short' }).toUpperCase()} ${date.getDate()}`; + + return { + title: item.title[0], + description: item.description[0], + link: item.link[0], + pubDate: formattedDate, + imageUrl: item.enclosure ? item.enclosure[0].$.url : null + }; + }); res.status(200).json(posts.slice(0, 3)); // Return the last 3 posts } diff --git a/packages/react-app/components/home/BlogSection.jsx b/packages/react-app/components/home/BlogSection.jsx index 76014696..49b69cdf 100644 --- a/packages/react-app/components/home/BlogSection.jsx +++ b/packages/react-app/components/home/BlogSection.jsx @@ -9,28 +9,25 @@ const BlogSection = ({ posts }) => { Shipping Log - {posts.map((post, index) => { - const date = new Date(post.pubDate); - const formattedDate = `${date.toLocaleString('default', { month: 'short' }).toUpperCase()} ${date.getDate()}`; - return ( - - - - - - {post.title} - {post.description} - {formattedDate} - - {post.title} - - - - - ); - })} - - ); + + {posts.map((post, index) => ( + + + + + + {post.title} + {post.description} + {post.pubDate} + + {post.title} + + + + + ))} + + ); }; export default BlogSection; From 15604f0bca7c4d3ccfaeb0f8bfacd7c4cbde14b8 Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Mon, 13 May 2024 09:48:38 +0200 Subject: [PATCH 6/8] Manage error on fetching posts --- packages/react-app/components/home/BlogSection.jsx | 5 +++++ packages/react-app/data/api/blog.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-app/components/home/BlogSection.jsx b/packages/react-app/components/home/BlogSection.jsx index 49b69cdf..d714e7fa 100644 --- a/packages/react-app/components/home/BlogSection.jsx +++ b/packages/react-app/components/home/BlogSection.jsx @@ -4,6 +4,11 @@ import useCustomColorModes from "../../hooks/useCustomColorModes"; const BlogSection = ({ posts }) => { const { baseColor, secondaryFontColor } = useCustomColorModes(); + + if (!posts || posts.length === 0) { + return null; + } + return ( diff --git a/packages/react-app/data/api/blog.js b/packages/react-app/data/api/blog.js index d72f32a9..fd443157 100644 --- a/packages/react-app/data/api/blog.js +++ b/packages/react-app/data/api/blog.js @@ -7,6 +7,6 @@ export const fetchRecentPosts = async () => { return response.data; } catch (error) { console.error("Error fetching recent posts:", error); - throw new Error("Couldn't fetch recent posts"); + return []; } }; From 4755bdda83afeb53d390ea2aced551adbb5c1fb6 Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Tue, 21 May 2024 12:57:27 +0200 Subject: [PATCH 7/8] Rename fetch to get for consistency --- packages/react-app/data/api/blog.js | 2 +- packages/react-app/pages/index.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-app/data/api/blog.js b/packages/react-app/data/api/blog.js index fd443157..a4f2d0b9 100644 --- a/packages/react-app/data/api/blog.js +++ b/packages/react-app/data/api/blog.js @@ -1,7 +1,7 @@ import axios from "axios"; import { SERVER_URL as serverUrl } from "../../constants"; -export const fetchRecentPosts = async () => { +export const getRecentPosts = async () => { try { const response = await axios.get(`${serverUrl}/blog/posts`); return response.data; diff --git a/packages/react-app/pages/index.jsx b/packages/react-app/pages/index.jsx index 645001d7..260ac882 100644 --- a/packages/react-app/pages/index.jsx +++ b/packages/react-app/pages/index.jsx @@ -6,7 +6,7 @@ import ActivitySection from "../components/home/ActivitySection"; import { getAllBuilds, getAllEvents } from "../data/api"; import RecentBuildsSection from "../components/home/RecentBuildsSection"; import BlogSection from "../components/home/BlogSection"; -import { fetchRecentPosts } from "../data/api/blog"; +import { getRecentPosts } from "../data/api/blog"; const buildersToShow = ["fullstack", "frontend", "damageDealer", "advisor", "artist", "support"]; /* eslint-disable jsx-a11y/accessible-emoji */ @@ -56,7 +56,7 @@ export async function getStaticProps() { const stats = await getStats(); const events = await getAllEvents(null, 10); const builds = (await getAllBuilds()).sort((a, b) => b.submittedTimestamp - a.submittedTimestamp).slice(0, 4); - const posts = await fetchRecentPosts(); + const posts = await getRecentPosts(); return { props: { From 69525d86ca8ee26316ec60205168220fada7c068 Mon Sep 17 00:00:00 2001 From: Pablo Alayeto <55535804+Pabl0cks@users.noreply.github.com> Date: Tue, 21 May 2024 13:10:38 +0200 Subject: [PATCH 8/8] Fix merge problems --- packages/react-app/pages/index.jsx | 32 ++---------------------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/packages/react-app/pages/index.jsx b/packages/react-app/pages/index.jsx index 260ac882..eb657a76 100644 --- a/packages/react-app/pages/index.jsx +++ b/packages/react-app/pages/index.jsx @@ -5,38 +5,10 @@ import HeroSection from "../components/home/HeroSection"; import ActivitySection from "../components/home/ActivitySection"; import { getAllBuilds, getAllEvents } from "../data/api"; import RecentBuildsSection from "../components/home/RecentBuildsSection"; -import BlogSection from "../components/home/BlogSection"; import { getRecentPosts } from "../data/api/blog"; -const buildersToShow = ["fullstack", "frontend", "damageDealer", "advisor", "artist", "support"]; - -/* eslint-disable jsx-a11y/accessible-emoji */ -export default function Index({ bgStats, events, posts }) { - const [builders, setBuilders] = useState([]); - const [isLoadingBuilders, setIsLoadingBuilders] = useState(false); - - const streamSection = useRef(null); - - const { colorMode } = useColorMode(); - const isDarkMode = colorMode === "dark"; - const scaffoldEthBg = useColorModeValue("#fbf7f6", "whiteAlpha.300"); - - useEffect(() => { - async function fetchBuilders() { - setIsLoadingBuilders(true); - const fetchedBuilders = await axios.get(`${SERVER_URL}/builders`); - - setBuilders(fetchedBuilders.data); - setIsLoadingBuilders(false); - } - - fetchBuilders(); - }, []); - - const smoothScroll = ref => { - ref.current.scrollIntoView({ behavior: "smooth" }); - }; +import BlogSection from "../components/home/BlogSection"; -export default function Index({ bgStats, events, builds }) { +export default function Index({ bgStats, events, builds, posts }) { return ( <>