Format and lint

This commit is contained in:
Andras Schmelczer 2026-02-08 12:37:07 +00:00
parent 42ee2d4c51
commit 04a78e7bfe
75 changed files with 1290 additions and 719 deletions

View file

@ -64,7 +64,7 @@ export default function AuthModal({
<div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose}>
<div className="absolute inset-0 bg-black/50 dark:bg-black/70" />
<div
className="relative w-full max-w-sm mx-4 bg-white dark:bg-warm-800 rounded-lg shadow-xl border border-warm-200 dark:border-warm-700"
className="relative w-full max-w-sm mx-4 bg-white dark:bg-warm-900 rounded-lg shadow-xl border border-warm-200 dark:border-warm-700"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
@ -115,7 +115,7 @@ export default function AuthModal({
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-3 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-900 text-navy-950 dark:text-white placeholder-warm-400 dark:placeholder-warm-500 outline-none focus:ring-2 ring-teal-400 dark:ring-teal-500"
className="w-full px-3 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-800 text-navy-950 dark:text-white placeholder-warm-400 dark:placeholder-warm-500 outline-none focus:ring-2 ring-teal-400 dark:ring-teal-500"
placeholder="you@example.com"
/>
</div>
@ -131,7 +131,7 @@ export default function AuthModal({
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
className="w-full px-3 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-900 text-navy-950 dark:text-white placeholder-warm-400 dark:placeholder-warm-500 outline-none focus:ring-2 ring-teal-400 dark:ring-teal-500"
className="w-full px-3 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-800 text-navy-950 dark:text-white placeholder-warm-400 dark:placeholder-warm-500 outline-none focus:ring-2 ring-teal-400 dark:ring-teal-500"
placeholder={view === 'register' ? 'Min 8 characters' : 'Your password'}
/>
{view === 'login' && (

View file

@ -16,10 +16,7 @@ export function CollapsibleGroupHeader({
children,
}: CollapsibleGroupHeaderProps) {
return (
<button
onClick={onToggle}
className={`w-full flex items-center justify-between ${className}`}
>
<button onClick={onToggle} className={`w-full flex items-center justify-between ${className}`}>
<span>{name}</span>
<div className="flex items-center gap-1">
{children}

View file

@ -17,7 +17,7 @@ export function EmptyState({
}: EmptyStateProps) {
return (
<div
className={`flex flex-col items-center justify-center text-center ${centered ? 'h-full px-4' : 'py-8'} ${className}`}
className={`flex flex-col items-center justify-center text-center ${centered ? 'h-full px-4' : 'py-3 md:py-8'} ${className}`}
>
<div className="mb-2">{icon}</div>
<span className="text-sm font-medium text-warm-400 dark:text-warm-500">{title}</span>

View file

@ -26,11 +26,16 @@ export function FeatureActions({
<InfoIcon />
</IconButton>
)}
<IconButton onClick={() => onTogglePin(feature.name)} title={isPinned ? 'Unpin color view' : 'Color map by this feature'} active={isPinned}>
<IconButton
onClick={() => onTogglePin(feature.name)}
title={isPinned ? 'Unpin color view' : 'Color map by this feature'}
active={isPinned}
size="md"
>
<EyeIcon filled={isPinned} />
</IconButton>
{onAdd && (
<IconButton onClick={() => onAdd(feature.name)} title="Add filter">
<IconButton onClick={() => onAdd(feature.name)} title="Add filter" size="md">
<PlusIcon />
</IconButton>
)}

View file

@ -17,8 +17,12 @@ export function FeatureLabel({
const textClass = size === 'sm' ? 'text-sm' : 'text-xs';
return (
<div className={`flex ${size === 'xs' ? 'items-center' : 'items-start'} gap-1 min-w-0 ${className}`}>
<span className={`${textClass} text-warm-700 dark:text-warm-300 ${size === 'xs' ? 'truncate' : ''}`}>
<div
className={`flex ${size === 'xs' ? 'items-center' : 'items-start'} gap-1 min-w-0 ${className}`}
>
<span
className={`${textClass} text-warm-700 dark:text-warm-300 ${size === 'xs' ? 'truncate' : ''}`}
>
{feature.name}
</span>
{feature.detail && onShowInfo && (

View file

@ -93,7 +93,9 @@ export default function Header({
<button
key={page}
className={`w-full text-left px-4 py-3 text-base font-medium rounded ${
activePage === page ? 'bg-navy-700 text-white' : 'text-warm-300 hover:bg-navy-800 hover:text-white'
activePage === page
? 'bg-navy-700 text-white'
: 'text-warm-300 hover:bg-navy-800 hover:text-white'
}`}
onClick={() => {
onPageChange(page);
@ -123,11 +125,17 @@ export default function Header({
Dashboard
</button>
{user && (
<button className={tabClass('saved-searches')} onClick={() => onPageChange('saved-searches')}>
<button
className={tabClass('saved-searches')}
onClick={() => onPageChange('saved-searches')}
>
Saved
</button>
)}
<button className={tabClass('data-sources')} onClick={() => onPageChange('data-sources')}>
<button
className={tabClass('data-sources')}
onClick={() => onPageChange('data-sources')}
>
Data Sources
</button>
<button className={tabClass('faq')} onClick={() => onPageChange('faq')}>
@ -245,10 +253,7 @@ export default function Header({
{isMobile && menuOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setMenuOpen(false)}
/>
<div className="fixed inset-0 bg-black/50 z-40" onClick={() => setMenuOpen(false)} />
{/* Menu panel */}
<div className="fixed top-0 right-0 bottom-0 w-64 bg-navy-900 z-50 flex flex-col shadow-xl">
<div className="flex items-center justify-between px-4 h-12 border-b border-navy-700">
@ -272,23 +277,40 @@ export default function Header({
<div className="mt-3 pt-3 border-t border-navy-700 flex flex-col gap-1">
{onSaveSearch && (
<button
onClick={() => { onSaveSearch(); setMenuOpen(false); }}
onClick={() => {
onSaveSearch();
setMenuOpen(false);
}}
disabled={savingSearch}
className="w-full flex items-center gap-2 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded disabled:opacity-50"
>
{savingSearch ? <SpinnerIcon className="w-5 h-5 animate-spin" /> : <BookmarkIcon className="w-5 h-5" />}
{savingSearch ? (
<SpinnerIcon className="w-5 h-5 animate-spin" />
) : (
<BookmarkIcon className="w-5 h-5" />
)}
Save
</button>
)}
<button
onClick={() => { handleShare(); setMenuOpen(false); }}
onClick={() => {
handleShare();
setMenuOpen(false);
}}
className="w-full flex items-center gap-2 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded"
>
{copied ? <CheckIcon className="w-5 h-5" /> : <ClipboardIcon className="w-5 h-5" />}
{copied ? (
<CheckIcon className="w-5 h-5" />
) : (
<ClipboardIcon className="w-5 h-5" />
)}
{copied ? 'Copied!' : 'Share'}
</button>
<button
onClick={() => { onExport?.(); setMenuOpen(false); }}
onClick={() => {
onExport?.();
setMenuOpen(false);
}}
disabled={!onExport || exporting}
className="w-full flex items-center gap-2 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded disabled:opacity-50"
>
@ -303,41 +325,56 @@ export default function Header({
<div className="p-3 border-t border-navy-700 flex flex-col gap-3">
{/* Theme toggle */}
<button
onClick={() => { onToggleTheme(); }}
onClick={() => {
onToggleTheme();
}}
className="w-full flex items-center gap-3 px-4 py-3 text-base text-warm-300 hover:bg-navy-800 hover:text-white rounded transition-colors"
>
{theme === 'light' ? <SunIcon className="w-5 h-5" /> : <MoonIcon className="w-5 h-5" />}
{theme === 'light' ? (
<SunIcon className="w-5 h-5" />
) : (
<MoonIcon className="w-5 h-5" />
)}
<span>Theme: {theme === 'light' ? 'Light' : 'Dark'}</span>
</button>
{/* Auth buttons */}
<div>
{user ? (
<div className="flex items-center justify-between px-4 py-2">
<span className="text-sm text-warm-300 truncate">{user.email}</span>
<button
onClick={() => { onLogout(); setMenuOpen(false); }}
className="text-sm text-warm-400 hover:text-white"
>
Log out
</button>
</div>
) : (
<div className="flex gap-2">
<button
onClick={() => { onLoginClick(); setMenuOpen(false); }}
className="flex-1 px-3 py-2.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm text-center"
>
Log in
</button>
<button
onClick={() => { onRegisterClick(); setMenuOpen(false); }}
className="flex-1 px-3 py-2.5 rounded bg-teal-600 hover:bg-teal-700 transition-colors text-sm font-medium text-center"
>
Register
</button>
</div>
)}
{user ? (
<div className="flex items-center justify-between px-4 py-2">
<span className="text-sm text-warm-300 truncate">{user.email}</span>
<button
onClick={() => {
onLogout();
setMenuOpen(false);
}}
className="text-sm text-warm-400 hover:text-white"
>
Log out
</button>
</div>
) : (
<div className="flex gap-2">
<button
onClick={() => {
onLoginClick();
setMenuOpen(false);
}}
className="flex-1 px-3 py-2.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm text-center"
>
Log in
</button>
<button
onClick={() => {
onRegisterClick();
setMenuOpen(false);
}}
className="flex-1 px-3 py-2.5 rounded bg-teal-600 hover:bg-teal-700 transition-colors text-sm font-medium text-center"
>
Register
</button>
</div>
)}
</div>
</div>
</div>

View file

@ -6,16 +6,28 @@ interface IconButtonProps {
children: ReactNode;
active?: boolean;
className?: string;
size?: 'sm' | 'md';
}
export function IconButton({ onClick, title, children, active, className }: IconButtonProps) {
const baseClasses = 'p-0.5 rounded';
export function IconButton({
onClick,
title,
children,
active,
className,
size = 'sm',
}: IconButtonProps) {
const padClasses = size === 'md' ? 'p-1' : 'p-0.5';
const colorClasses = active
? 'text-teal-600 dark:text-teal-400'
: 'text-warm-400 hover:text-warm-700 dark:hover:text-warm-300';
return (
<button onClick={onClick} title={title} className={`${baseClasses} ${colorClasses} ${className || ''}`}>
<button
onClick={onClick}
title={title}
className={`${padClasses} rounded ${colorClasses} ${className || ''}`}
>
{children}
</button>
);

View file

@ -41,7 +41,7 @@ export default function SaveSearchModal({
<div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose}>
<div className="absolute inset-0 bg-black/50 dark:bg-black/70" />
<div
className="relative w-full max-w-sm mx-4 bg-white dark:bg-warm-800 rounded-lg shadow-xl border border-warm-200 dark:border-warm-700"
className="relative w-full max-w-sm mx-4 bg-white dark:bg-warm-900 rounded-lg shadow-xl border border-warm-200 dark:border-warm-700"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between px-5 pt-5 pb-3">
@ -63,7 +63,7 @@ export default function SaveSearchModal({
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-900 text-navy-950 dark:text-white placeholder-warm-400 dark:placeholder-warm-500 outline-none focus:ring-2 ring-teal-400 dark:ring-teal-500"
className="w-full px-3 py-2 text-sm rounded border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-800 text-navy-950 dark:text-white placeholder-warm-400 dark:placeholder-warm-500 outline-none focus:ring-2 ring-teal-400 dark:ring-teal-500"
placeholder="My search"
autoFocus
/>

View file

@ -32,7 +32,9 @@ export default function UserMenu({ user, onLogout }: { user: AuthUser; onLogout:
{open && (
<div className="absolute right-0 top-10 w-56 bg-white dark:bg-warm-800 border border-warm-200 dark:border-warm-700 rounded-lg shadow-lg z-50">
<div className="px-4 py-3 border-b border-warm-200 dark:border-warm-700">
<p className="text-sm font-medium text-navy-950 dark:text-warm-100 truncate">{user.email}</p>
<p className="text-sm font-medium text-navy-950 dark:text-warm-100 truncate">
{user.email}
</p>
</div>
<div className="p-1">
<button

View file

@ -4,8 +4,18 @@ interface IconProps {
export function BookmarkIcon({ className = 'w-3.5 h-3.5' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"
/>
</svg>
);
}

View file

@ -4,7 +4,13 @@ interface IconProps {
export function CheckIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
);

View file

@ -13,7 +13,13 @@ export function ChevronIcon({
down: 'M6 9l6 6 6-6',
};
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d={paths[direction]} />
</svg>
);

View file

@ -4,7 +4,13 @@ interface IconProps {
export function ClipboardIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"

View file

@ -4,7 +4,13 @@ interface IconProps {
export function DownloadIcon({ className = 'w-3.5 h-3.5' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v14m0 0l-6-6m6 6l6-6" />
<path strokeLinecap="round" strokeLinejoin="round" d="M5 21h14" />
</svg>

View file

@ -2,7 +2,7 @@ interface IconProps {
className?: string;
}
export function EyeIcon({ filled, className = 'w-3.5 h-3.5' }: IconProps & { filled: boolean }) {
export function EyeIcon({ filled, className = 'w-7 h-7' }: IconProps & { filled: boolean }) {
return (
<svg
className={className}
@ -11,8 +11,17 @@ export function EyeIcon({ filled, className = 'w-3.5 h-3.5' }: IconProps & { fil
stroke="currentColor"
strokeWidth={2}
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill={filled ? 'currentColor' : 'none'} />
<circle cx="12" cy="12" r="3" fill={filled ? 'currentColor' : 'none'} stroke={filled ? 'white' : 'currentColor'} />
<path
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
fill={filled ? 'currentColor' : 'none'}
/>
<circle
cx="12"
cy="12"
r="3"
fill={filled ? 'currentColor' : 'none'}
stroke={filled ? 'white' : 'currentColor'}
/>
</svg>
);
}

View file

@ -4,7 +4,13 @@ interface IconProps {
export function FilterIcon({ className = 'w-8 h-8' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
>
<path
strokeLinecap="round"
strokeLinejoin="round"

View file

@ -4,7 +4,13 @@ interface IconProps {
export function InfoIcon({ className = 'w-3.5 h-3.5' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<circle cx="12" cy="12" r="10" />
<path strokeLinecap="round" d="M12 16v-4m0-4h.01" />
</svg>

View file

@ -4,7 +4,13 @@ interface IconProps {
export function LightbulbIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"

View file

@ -4,17 +4,19 @@ interface IconProps {
export function MapPinIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
);
}

View file

@ -4,7 +4,13 @@ interface IconProps {
export function MoonIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"

View file

@ -2,9 +2,15 @@ interface IconProps {
className?: string;
}
export function PlusIcon({ className = 'w-3.5 h-3.5' }: IconProps) {
export function PlusIcon({ className = 'w-7 h-7' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v14m-7-7h14" />
</svg>
);

View file

@ -6,7 +6,11 @@ export function SpinnerIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
);
}

View file

@ -4,7 +4,13 @@ interface IconProps {
export function SunIcon({ className = 'w-4 h-4' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"

View file

@ -4,8 +4,18 @@ interface IconProps {
export function TrashIcon({ className = 'w-3.5 h-3.5' }: IconProps) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
);
}