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 || '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) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
-
)
- }
+ 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}
-
-
-
-
-
-
-
-
-
-
-
+ {/* Hero Section */}
+
+
+
+
+
-
-
-
-
© 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.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 */}
+
);
}
-Starter.getLayout = function getLayout(page: ReactElement) {
+LandingPage.getLayout = function getLayout(page: ReactElement) {
return {page};
};
-