From c396ec9d335bae3c64c53cf06ecfcd641c7f0110 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 31 Jan 2026 17:26:19 +0000 Subject: [PATCH] Auto commit: 2026-01-31T17:26:19.573Z --- backend/src/index.js | 8 +- backend/src/routes/news_cards.js | 27 +- backend/src/services/news_cards.js | 35 +- .../components/News_cards/NewsCardPreview.tsx | 56 ++++ frontend/src/pages/index.tsx | 305 ++++++++++-------- 5 files changed, 280 insertions(+), 151 deletions(-) create mode 100644 frontend/src/components/News_cards/NewsCardPreview.tsx diff --git a/backend/src/index.js b/backend/src/index.js index fcfa43c..22c461c 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -111,7 +111,13 @@ app.use('/api/templates', passport.authenticate('jwt', {session: false}), templa app.use('/api/assets', passport.authenticate('jwt', {session: false}), assetsRoutes); -app.use('/api/news_cards', passport.authenticate('jwt', {session: false}), news_cardsRoutes); +// Custom logic for news_cards to allow public /scrape +app.use('/api/news_cards', (req, res, next) => { + if (req.path === '/scrape' && req.method === 'POST') { + return next(); + } + return passport.authenticate('jwt', {session: false})(req, res, next); +}, news_cardsRoutes); app.use('/api/jobs', passport.authenticate('jwt', {session: false}), jobsRoutes); diff --git a/backend/src/routes/news_cards.js b/backend/src/routes/news_cards.js index d3e2fd4..1a79ab0 100644 --- a/backend/src/routes/news_cards.js +++ b/backend/src/routes/news_cards.js @@ -1,4 +1,3 @@ - const express = require('express'); const News_cardsService = require('../services/news_cards'); @@ -15,6 +14,30 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); +/** + * @swagger + * /api/news_cards/scrape: + * post: + * tags: [News_cards] + * summary: Scrape news metadata from URL + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * url: + * type: string + * responses: + * 200: + * description: Metadata successfully scraped + */ +router.post('/scrape', wrapAsync(async (req, res) => { + const { url } = req.body; + const metadata = await News_cardsService.scrapeUrl(url); + res.status(200).send(metadata); +})); + router.use(checkCrudPermissions('news_cards')); @@ -433,4 +456,4 @@ router.get('/:id', wrapAsync(async (req, res) => { router.use('/', require('../helpers').commonErrorHandler); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/news_cards.js b/backend/src/services/news_cards.js index 17d6032..de82276 100644 --- a/backend/src/services/news_cards.js +++ b/backend/src/services/news_cards.js @@ -7,10 +7,6 @@ const axios = require('axios'); const config = require('../config'); const stream = require('stream'); - - - - module.exports = class News_cardsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); @@ -30,6 +26,33 @@ module.exports = class News_cardsService { } }; + static async scrapeUrl(url) { + try { + const response = await axios.get(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + }, + timeout: 5000 + }); + const html = response.data; + + // Basic extraction using regex for speed and simplicity without new dependencies + const titleMatch = html.match(/([^<]+)<\/title>/); + const imageMatch = html.match(/ = ({ title, date, imageUrl, logoUrl }) => { + return ( +
+ {/* Background Image */} + {imageUrl ? ( + News background + ) : ( +
+ News Card +
+ )} + + {/* Dark Overlay */} +
+ + {/* Logo */} +
+ {logoUrl ? ( + Logo + ) : ( +
+ N +
+ )} +
+ + {/* Content */} +
+
+ {date} +
+

+ {title} +

+
+ + {/* Bottom Border Accent */} +
+
+ ); +}; + +export default NewsCardPreview; diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 4362b9e..98fc3e6 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,4 +1,3 @@ - import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; @@ -12,155 +11,179 @@ import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import NewsCardPreview from '../components/News_cards/NewsCardPreview'; +import axios from 'axios'; +export default function LandingPage() { + const [url, setUrl] = useState(''); + const [loading, setLoading] = useState(false); + const [cardData, setCardData] = useState({ + title: 'Your News Title Will Appear Here', + date: new Date().toLocaleDateString('en-GB', { day: '2-digit', month: 'long', year: 'numeric' }), + imageUrl: '', + }); + const [error, setError] = useState(''); -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('right'); const textColor = useAppSelector((state) => state.style.linkColor); - const title = 'App Draft' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) + const handleScrape = async () => { + if (!url) return; + setLoading(true); + setError(''); + try { + const response = await axios.post('/news_cards/scrape', { url }); + setCardData(response.data); + } catch (err) { + console.error(err); + setError('Failed to fetch news. Please check the URL and try again.'); + } finally { + setLoading(false); } }; - return ( -
- - {getPageTitle('Starter Page')} - + return ( +
+ + {getPageTitle('News Card Generator')} + - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - + {/* Navigation */} + - -
+ {/* Hero Section */} +
+
+
+
+

+ Transform News URLs into Beautiful Cards +

+

+ Instantly generate professional, branded news cards for social media. Just paste a link and let our AI do the magic. +

+
+ +
+
+ setUrl(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleScrape()} + /> + +
+ {error &&

{error}

} +

Try pasting a link from BBC, CNN, or any news outlet.

+
+ +
+
+ {[1, 2, 3, 4].map((i) => ( +
+ user +
+ ))} +
+

Joined by 500+ editors this week

+
+
+ + {/* Preview Component */} +
+
+
+ +
+ + + {/* Floating Action Badge */} +
+
+ + + +
+
+

Status

+

Ready to Share

+
+
+
+
+
+
+ + {/* Features Section */} +
+
+
+

Features designed for modern newsrooms

+

Everything you need to automate your social media visuals.

+
+ +
+ {[ + { title: 'AI Powered', desc: 'Our AI extracts the most impactful headline and image from any article automatically.', icon: '⚡' }, + { title: 'Custom Branding', desc: 'Add your logo and choose colors that match your news organization brand.', icon: '🎨' }, + { title: 'High Res Export', desc: 'Export cards in high resolution ready for Instagram, Facebook, and Twitter.', icon: '📸' } + ].map((f, i) => ( +
+
{f.icon}
+

{f.title}

+

{f.desc}

+
+ ))} +
+
+
+ + {/* Footer */} +
+
+
+
N
+ NewsCard AI +
+

© 2026 NewsCard AI. Powered by Flatlogic.

+
+ Privacy + Terms +
+
+
-
-
-
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
- -
- ); + ); } -Starter.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - +LandingPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file