scraping and improvements

This commit is contained in:
Stefan Hardegger
2025-07-28 13:52:09 +02:00
parent f95d7aa8bb
commit fcad028959
31 changed files with 3788 additions and 118 deletions

View File

@@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation';
import { useAuth } from '../../contexts/AuthContext';
import { useTheme } from '../../lib/theme';
import Button from '../ui/Button';
import Dropdown from '../ui/Dropdown';
export default function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -14,6 +15,24 @@ export default function Header() {
const { theme, toggleTheme } = useTheme();
const router = useRouter();
const addStoryItems = [
{
href: '/add-story',
label: 'Manual Entry',
description: 'Add a story by manually entering details'
},
{
href: '/stories/import',
label: 'Import from URL',
description: 'Import a single story from a website'
},
{
href: '/stories/import/bulk',
label: 'Bulk Import',
description: 'Import multiple stories from a list of URLs'
}
];
const handleLogout = () => {
logout();
router.push('/login');
@@ -57,12 +76,10 @@ export default function Header() {
>
Authors
</Link>
<Link
href="/add-story"
className="theme-text hover:theme-accent transition-colors font-medium"
>
Add Story
</Link>
<Dropdown
trigger="Add Story"
items={addStoryItems}
/>
</nav>
{/* Right side actions */}
@@ -131,13 +148,32 @@ export default function Header() {
>
Authors
</Link>
<Link
href="/add-story"
className="theme-text hover:theme-accent transition-colors font-medium px-2 py-1"
onClick={() => setIsMenuOpen(false)}
>
Add Story
</Link>
<div className="px-2 py-1">
<div className="font-medium theme-text mb-1">Add Story</div>
<div className="pl-4 space-y-1">
<Link
href="/add-story"
className="block theme-text hover:theme-accent transition-colors text-sm py-1"
onClick={() => setIsMenuOpen(false)}
>
Manual Entry
</Link>
<Link
href="/stories/import"
className="block theme-text hover:theme-accent transition-colors text-sm py-1"
onClick={() => setIsMenuOpen(false)}
>
Import from URL
</Link>
<Link
href="/stories/import/bulk"
className="block theme-text hover:theme-accent transition-colors text-sm py-1"
onClick={() => setIsMenuOpen(false)}
>
Bulk Import
</Link>
</div>
</div>
<Link
href="/settings"
className="theme-text hover:theme-accent transition-colors font-medium px-2 py-1"

View File

@@ -0,0 +1,98 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import Link from 'next/link';
import { ChevronDownIcon } from '@heroicons/react/24/outline';
interface DropdownItem {
href: string;
label: string;
description?: string;
}
interface DropdownProps {
trigger: string;
items: DropdownItem[];
className?: string;
onItemClick?: () => void;
}
export default function Dropdown({ trigger, items, className = '', onItemClick }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [isOpen]);
const handleMouseEnter = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setIsOpen(true);
};
const handleMouseLeave = () => {
timeoutRef.current = setTimeout(() => {
setIsOpen(false);
}, 150);
};
const handleItemClick = () => {
setIsOpen(false);
onItemClick?.();
};
return (
<div
className={`relative ${className}`}
ref={dropdownRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<button
onClick={() => setIsOpen(!isOpen)}
className="theme-text hover:theme-accent transition-colors font-medium flex items-center gap-1"
>
{trigger}
<ChevronDownIcon
className={`h-4 w-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
/>
</button>
{isOpen && (
<div className="absolute top-full left-0 mt-1 w-64 theme-card theme-shadow border theme-border rounded-lg py-2 z-50">
{items.map((item, index) => (
<Link
key={index}
href={item.href}
onClick={handleItemClick}
className="block px-4 py-2 theme-text hover:theme-accent transition-colors"
>
<div className="font-medium">{item.label}</div>
{item.description && (
<div className="text-sm theme-text-secondary mt-1">{item.description}</div>
)}
</Link>
))}
</div>
)}
</div>
);
}