From bac215299692625f1319510de45460b7b66b68d3 Mon Sep 17 00:00:00 2001 From: m shehzen Date: Sat, 5 Jul 2025 12:21:17 +0530 Subject: [PATCH] feat: Add Twitter engagement metrics component and integrate with PostPage --- next.config.js | 8 + src/app/components/TwitterMetrics.js | 418 +++++++++++++++++++++++++++ src/app/post/[id]/page.js | 13 + 3 files changed, 439 insertions(+) create mode 100644 src/app/components/TwitterMetrics.js diff --git a/next.config.js b/next.config.js index 5aa9db2..3d65cf1 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,14 @@ const nextConfig = { async rewrites() { return [ + { + source: '/api/tweet-metrics', + destination: 'https://twitter-api.opensourceprojects.dev/tweet-metrics', + }, + { + source: '/api/multiple-tweet-metrics', + destination: 'https://twitter-api.opensourceprojects.dev/multiple-tweet-metrics', + }, { source: '/api/:path*', destination: 'https://twitter-api.opensourceprojects.dev/:path*', diff --git a/src/app/components/TwitterMetrics.js b/src/app/components/TwitterMetrics.js new file mode 100644 index 0000000..2bd9c63 --- /dev/null +++ b/src/app/components/TwitterMetrics.js @@ -0,0 +1,418 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +const TwitterMetrics = ({ postId, tweetUrl }) => { + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchMetrics = async () => { + if (!tweetUrl) { + setLoading(false); + return; + } + + try { + setLoading(true); + setError(null); + + // Use the backend API to fetch Twitter metrics + const response = await fetch('/api/tweet-metrics', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: tweetUrl }), + }); + + if (!response.ok) { + throw new Error(`Failed to fetch metrics: ${response.status}`); + } + + const data = await response.json(); + setMetrics(data); + } catch (err) { + console.warn('Failed to fetch Twitter metrics:', err.message); + setError(err.message); + } finally { + setLoading(false); + } + }; + + fetchMetrics(); + }, [tweetUrl]); + + // Don't render anything if there's no tweet URL + if (!tweetUrl) { + return null; + } + + if (loading) { + return ( +
+
+ + X Engagement +
+
+
+ Loading metrics... +
+
+ ); + } + + if (error || !metrics) { + return ( +
+
+ + X Engagement +
+
+ + Metrics unavailable +
+
+ ); + } + + const formatNumber = (num) => { + if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M'; + } + if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K'; + } + return num.toString(); + }; + + return ( + <> +
+
+ + X Engagement + + + +
+ +
+
+
+ +
+
+ {formatNumber(metrics.likes)} + Likes +
+
+ +
+
+ +
+
+ {formatNumber(metrics.retweets)} + Retweets +
+
+ +
+
+ +
+
+ {formatNumber(metrics.shares)} + Quotes +
+
+ +
+
+ +
+
+ {formatNumber(metrics.views)} + Views +
+
+
+
+ + + + ); +}; + +export default TwitterMetrics; \ No newline at end of file diff --git a/src/app/post/[id]/page.js b/src/app/post/[id]/page.js index f7e4e5c..96e0dda 100644 --- a/src/app/post/[id]/page.js +++ b/src/app/post/[id]/page.js @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; +import TwitterMetrics from '../../components/TwitterMetrics'; const fallbackImage = '/images/open-source-logo-830x460.jpg'; @@ -26,6 +27,12 @@ const extractTags = (content) => { return hashtags.map(tag => tag.substring(1)); // Remove the # symbol }; +const getTwitterUrl = (postId, username = 'GithubProjects') => { + // Construct Twitter URL from post ID + // Since the post data comes from @githubprojects tweets, we can construct the URL + return `https://x.com/${username}/status/${postId}`; +}; + const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', @@ -732,6 +739,12 @@ export default function PostPage() {
Created
+ + {/* Twitter Engagement Metrics */} +