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 Card
+
+ )}
+
+ {/* Dark Overlay */}
+
+
+ {/* Logo */}
+
+ {logoUrl ? (
+
+ ) : (
+
+ 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) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
- Your browser does not support the video tag.
-
-
-
)
+ 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}
-
-
-
-
-
-
-
-
+ {/* Navigation */}
+
+
+
+
+ Login
+
+
+
+
-
-
+ {/* 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()}
+ />
+
+ {loading ? 'Fetching...' : 'Generate'}
+
+
+ {error &&
{error}
}
+
Try pasting a link from BBC, CNN, or any news outlet.
+
+
+
+
+ {[1, 2, 3, 4].map((i) => (
+
+
+
+ ))}
+
+
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 */}
+
-
-
-
-
© 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