From 5c26c587655c152de17c486886d1076b1cb52b16 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 28 Jan 2026 14:11:33 +0000 Subject: [PATCH] version1 --- backend/src/index.js | 2 + backend/src/routes/public.js | 28 +++ frontend/src/components/NavBarItem.tsx | 3 +- frontend/src/components/VideoCard.tsx | 35 +++ frontend/src/layouts/Authenticated.tsx | 3 +- frontend/src/pages/index.tsx | 300 +++++++++++++------------ 6 files changed, 224 insertions(+), 147 deletions(-) create mode 100644 backend/src/routes/public.js create mode 100644 frontend/src/components/VideoCard.tsx diff --git a/backend/src/index.js b/backend/src/index.js index eb9420a..1a7cded 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -12,6 +12,7 @@ const swaggerUI = require('swagger-ui-express'); const swaggerJsDoc = require('swagger-jsdoc'); const authRoutes = require('./routes/auth'); +const publicRoutes = require('./routes/public'); const fileRoutes = require('./routes/file'); const searchRoutes = require('./routes/search'); const sqlRoutes = require('./routes/sql'); @@ -90,6 +91,7 @@ require('./auth/auth'); app.use(bodyParser.json()); app.use('/api/auth', authRoutes); +app.use('/api/public', publicRoutes); app.use('/api/file', fileRoutes); app.use('/api/pexels', pexelsRoutes); app.enable('trust proxy'); diff --git a/backend/src/routes/public.js b/backend/src/routes/public.js new file mode 100644 index 0000000..b204464 --- /dev/null +++ b/backend/src/routes/public.js @@ -0,0 +1,28 @@ + +const express = require('express'); +const router = express.Router(); +const db = require('../db/models'); +const { wrapAsync } = require('../helpers'); + +router.get('/videos', wrapAsync(async (req, res) => { + const rows = await db.videos.findAll({ + limit: 10, + order: [['createdAt', 'DESC']], + include: [ + { model: db.file, as: 'thumbnail' }, + { model: db.tags, as: 'tags' } + ] + }); + res.status(200).send({ rows }); +})); + +router.get('/collections', wrapAsync(async (req, res) => { + const rows = await db.collections.findAll({ + include: [ + { model: db.file, as: 'cover' } + ] + }); + res.status(200).send({ rows }); +})); + +module.exports = router; diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 72935e6..995a452 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' diff --git a/frontend/src/components/VideoCard.tsx b/frontend/src/components/VideoCard.tsx new file mode 100644 index 0000000..d523393 --- /dev/null +++ b/frontend/src/components/VideoCard.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { mdiPlayCircle } from '@mdi/js'; +import BaseIcon from './BaseIcon'; + +const VideoCard = ({ video }) => { + const thumbnailUrl = video.thumbnail?.[0]?.url || 'https://via.placeholder.com/400x225?text=No+Thumbnail'; + + return ( +
+
+ {video.title} +
+ +
+
+
+

{video.title || 'Untitled Video'}

+

{video.description || 'No description available.'}

+
+ {video.tags?.slice(0, 3).map((tag) => ( + + {tag.name} + + ))} +
+
+
+ ); +}; + +export default VideoCard; \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..73d8391 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 2fdd258..cde3b27 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -3,164 +3,178 @@ import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; +import axios from 'axios'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -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 VideoCard from '../components/VideoCard'; +import BaseButton from '../components/BaseButton'; +import { mdiPlay, mdiChevronRight, mdiInformationOutline } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; +export default function LandingPage() { + const [videos, setVideos] = useState([]); + const [collections, setCollections] = useState([]); + const [loading, setLoading] = useState(true); -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('image'); - 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 - -
-
) - } + useEffect(() => { + const fetchData = async () => { + try { + const [videosRes, collectionsRes] = await Promise.all([ + axios.get('/public/videos'), + axios.get('/public/collections') + ]); + setVideos(videosRes.data.rows || []); + setCollections(collectionsRes.data.rows || []); + } catch (error) { + console.error('Failed to fetch public data:', error); + } finally { + setLoading(false); + } }; + fetchData(); + }, []); return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('The Ultimate Dommelia Archive')} - -
- {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

-
- - - - - -
+ {/* Hero Section */} +
+
+
+
+ Dommelia Archive Cinematic Background
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+
+

+ + DOMMELIA + +
+ ARCHIVE +

+

+ Gathering every video from across the web. Organized, curated, and categorized for the ultimate fans. +

+
+ + + + + + +
+
+
+ + {/* Categories / Collections */} +
+
+
+

Curated Collections

+

Hand-picked categories for easy discovery.

+
+ + View all + +
+ +
+ {collections.length > 0 ? ( + collections.map((collection) => ( +
+ {collection.title +
+
+ + {collection.status || 'COLLECTION'} + +

{collection.title}

+

+ {collection.description} +

+
+
+ )) + ) : ( + [1, 2, 3].map((i) => ( +
+ Placeholder Collection {i} +
+ )) + )} +
+
+ + {/* Latest Videos Grid */} +
+
+
+

Recently Gathered

+

The latest additions to our ever-growing archive.

+
+ +
+ {videos.length > 0 ? ( + videos.map((video) => ( + + )) + ) : ( + [1, 2, 3, 4].map((i) => ( +
+ )) + )} +
+ +
+ + + +
+
+
+ + {/* Footer */} +
+
+
+ + DOMMELIA + +
+

+ Gathering history, one video at a time. +

+
+ Privacy + Terms + Admin Access +
+
+
); } -Starter.getLayout = function getLayout(page: ReactElement) { +LandingPage.getLayout = function getLayout(page: ReactElement) { return {page}; }; -