Good changes

This commit is contained in:
Andras Schmelczer 2026-02-10 22:09:46 +00:00
parent d39d1b15fd
commit 1f68ca0512
23 changed files with 670 additions and 289 deletions

View file

@ -1,5 +1,6 @@
import { useState, useCallback, useEffect } from 'react';
import type { AuthUser } from '../../hooks/useAuth';
import { shortenUrl } from '../../lib/api';
import { DownloadIcon } from './icons/DownloadIcon';
import { BookmarkIcon } from './icons/BookmarkIcon';
import { LogoIcon } from './icons/LogoIcon';
@ -12,7 +13,7 @@ import { SpinnerIcon } from './icons/SpinnerIcon';
import UserMenu from './UserMenu';
import MobileMenu from './MobileMenu';
export type Page = 'home' | 'dashboard' | 'data-sources' | 'faq' | 'saved-searches' | 'pricing';
export type Page = 'home' | 'dashboard' | 'saved-searches' | 'pricing';
export default function Header({
activePage,
@ -44,6 +45,7 @@ export default function Header({
isMobile: boolean;
}) {
const [copied, setCopied] = useState(false);
const [sharing, setSharing] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
// Close menu on Escape
@ -61,17 +63,16 @@ export default function Header({
if (!isMobile) setMenuOpen(false);
}, [isMobile]);
const handleShare = useCallback(() => {
const url = window.location.href;
const copyToClipboard = useCallback((text: string) => {
const onSuccess = () => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(url).then(onSuccess);
navigator.clipboard.writeText(text).then(onSuccess);
} else {
const ta = document.createElement('textarea');
ta.value = url;
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
@ -82,6 +83,23 @@ export default function Header({
}
}, []);
const handleShare = useCallback(async () => {
const params = window.location.search.replace(/^\?/, '');
if (!params) {
copyToClipboard(window.location.href);
return;
}
setSharing(true);
try {
const shortUrl = await shortenUrl(params);
copyToClipboard(shortUrl);
} catch {
copyToClipboard(window.location.href);
} finally {
setSharing(false);
}
}, [copyToClipboard]);
const tabClass = (page: Page) =>
`px-3 py-1.5 rounded text-sm font-medium transition-colors ${
activePage === page
@ -98,7 +116,7 @@ export default function Header({
onClick={() => onPageChange('home')}
>
<LogoIcon className="w-5 h-5 text-teal-400" />
<span className="font-semibold text-lg">Perfect Postcodes</span>
<span className="font-semibold text-lg">Perfect Postcode</span>
</button>
{/* Desktop nav */}
@ -115,15 +133,6 @@ export default function Header({
Saved
</button>
)}
<button
className={tabClass('data-sources')}
onClick={() => onPageChange('data-sources')}
>
Data Sources
</button>
<button className={tabClass('faq')} onClick={() => onPageChange('faq')}>
FAQ
</button>
<button className={tabClass('pricing')} onClick={() => onPageChange('pricing')}>
Pricing
</button>
@ -152,9 +161,15 @@ export default function Header({
)}
<button
onClick={handleShare}
className="flex items-center gap-1.5 px-3 py-1.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm"
disabled={sharing}
className="flex items-center gap-1.5 px-3 py-1.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm disabled:opacity-50 disabled:cursor-wait"
>
{copied ? (
{sharing ? (
<>
<SpinnerIcon className="w-4 h-4 animate-spin" />
Sharing...
</>
) : copied ? (
<>
<CheckIcon className="w-4 h-4" />
Copied!
@ -255,6 +270,13 @@ export default function Header({
copied={copied}
/>
)}
{/* Mobile "Copied" toast */}
{isMobile && copied && (
<div className="fixed top-14 left-1/2 -translate-x-1/2 z-[60] flex items-center gap-2 px-4 py-2 rounded-lg bg-navy-900 text-white text-sm shadow-lg animate-fade-in">
<CheckIcon className="w-4 h-4 text-teal-400" />
Copied to clipboard
</div>
)}
</header>
);
}