feat: add disclaimer modal, update lake data, and improve component interactions
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { type Language, t } from '../translations';
|
||||
import { TbSwimming, TbSailboat } from 'react-icons/tb';
|
||||
|
||||
interface Props {
|
||||
language: Language;
|
||||
setLanguage: (lang: Language) => void;
|
||||
}
|
||||
|
||||
export const DisclaimerModal = ({ language, setLanguage }: Props) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const isAccepted = localStorage.getItem('hladinator_disclaimer_accepted');
|
||||
if (!isAccepted) {
|
||||
setShow(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAccept = () => {
|
||||
localStorage.setItem('hladinator_disclaimer_accepted', 'true');
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
zIndex: 999999, padding: '1.5rem', backdropFilter: 'blur(4px)'
|
||||
}}>
|
||||
<div style={{
|
||||
backgroundColor: 'var(--bg-card)',
|
||||
borderRadius: '12px',
|
||||
padding: '2rem',
|
||||
maxWidth: '550px',
|
||||
width: '100%',
|
||||
boxShadow: '0 10px 30px rgba(0,0,0,0.5)',
|
||||
display: 'flex', flexDirection: 'column', gap: '1.5rem',
|
||||
border: '1px solid var(--border-color)',
|
||||
color: 'var(--text-main)',
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', margin: 0, color: 'var(--color-cyan)' }}>
|
||||
{t[language].disclaimer.title}
|
||||
</h2>
|
||||
<div style={{ display: 'flex', gap: '0.25rem', backgroundColor: 'rgba(0,0,0,0.2)', padding: '4px', borderRadius: '8px' }}>
|
||||
<button
|
||||
onClick={() => setLanguage('cs')}
|
||||
style={{ background: language === 'cs' ? 'var(--color-cyan)' : 'transparent', color: language === 'cs' ? '#fff' : 'var(--text-muted)', border: 'none', padding: '4px 8px', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', transition: '0.2s' }}
|
||||
>
|
||||
CS
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLanguage('en')}
|
||||
style={{ background: language === 'en' ? 'var(--color-cyan)' : 'transparent', color: language === 'en' ? '#fff' : 'var(--text-muted)', border: 'none', padding: '4px 8px', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', transition: '0.2s' }}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style={{ margin: 0, lineHeight: 1.5 }}>
|
||||
{t[language].disclaimer.text1}
|
||||
</p>
|
||||
<p style={{ margin: 0, lineHeight: 1.5, color: 'var(--text-muted)' }}>
|
||||
{t[language].disclaimer.text2}
|
||||
</p>
|
||||
|
||||
<div style={{
|
||||
marginTop: '0.5rem', padding: '1rem',
|
||||
backgroundColor: 'rgba(255,255,255,0.03)',
|
||||
borderRadius: '8px', display: 'flex', flexDirection: 'column', gap: '1rem'
|
||||
}}>
|
||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-start' }}>
|
||||
<div style={{ display: 'flex', gap: '0.25rem', marginTop: '0.25rem' }}>
|
||||
<TbSwimming size={24} color="var(--color-green)" />
|
||||
<TbSailboat size={24} color="var(--color-green)" />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.9rem', color: 'var(--text-muted)', lineHeight: 1.4 }}>
|
||||
<div style={{ color: 'var(--text-main)', fontWeight: 'bold', marginBottom: '0.2rem' }}>{language === 'cs' ? 'Zelené ikony (Povoleno)' : 'Green icons (Allowed)'}</div>
|
||||
<ul style={{ margin: 0, paddingLeft: '1.2rem' }}>
|
||||
<li>{t[language].disclaimer.swimDesc}</li>
|
||||
<li>{t[language].disclaimer.sailDesc}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-start' }}>
|
||||
<div style={{ display: 'flex', gap: '0.25rem', marginTop: '0.25rem' }}>
|
||||
<TbSwimming size={24} color="var(--color-red)" />
|
||||
<TbSailboat size={24} color="var(--color-red)" />
|
||||
</div>
|
||||
<div style={{ fontSize: '0.9rem', color: 'var(--text-muted)', lineHeight: 1.4 }}>
|
||||
<div style={{ color: 'var(--text-main)', fontWeight: 'bold', marginBottom: '0.2rem' }}>{language === 'cs' ? 'Červené ikony (Zakázáno)' : 'Red icons (Forbidden)'}</div>
|
||||
{t[language].disclaimer.forbiddenDesc}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
style={{
|
||||
marginTop: '1rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-cyan)',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
fontSize: '1.1rem',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'opacity 0.2s'
|
||||
}}
|
||||
onMouseOver={(e) => e.currentTarget.style.opacity = '0.9'}
|
||||
onMouseOut={(e) => e.currentTarget.style.opacity = '1'}
|
||||
>
|
||||
{t[language].disclaimer.button}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,8 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { slugify } from '../utils/slugify';
|
||||
import { AreaChart, Area, ResponsiveContainer, YAxis } from 'recharts';
|
||||
import { FiTrendingUp, FiTrendingDown } from 'react-icons/fi';
|
||||
import { TbSwimming, TbSailboat } from 'react-icons/tb';
|
||||
import { Tooltip } from './Tooltip';
|
||||
|
||||
interface Lake {
|
||||
id: string;
|
||||
@@ -21,6 +23,7 @@ interface Lake {
|
||||
outflow: number;
|
||||
volume: number;
|
||||
maxVolume: number;
|
||||
navigationForbidden: boolean;
|
||||
sparkline: number[];
|
||||
}
|
||||
|
||||
@@ -119,9 +122,19 @@ const FavoritesOverview = ({ language }: Props) => {
|
||||
<FiStar size={18} fill="#f59e0b" />
|
||||
</button>
|
||||
|
||||
<h3 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0, paddingRight: '2rem' }}>
|
||||
{lake.name} {lake.river ? `- ${lake.river}` : ''}
|
||||
</h3>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', paddingRight: '2rem' }}>
|
||||
<h3 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
||||
{lake.name} {lake.river ? `- ${lake.river}` : ''}
|
||||
</h3>
|
||||
<div style={{ display: 'flex', gap: '0.4rem', flexShrink: 0 }}>
|
||||
<Tooltip content={lake.navigationForbidden ? (language === 'cs' ? 'Koupání zakázáno' : 'Swimming forbidden') : (language === 'cs' ? 'Koupání (bez omezení)' : 'Swimming allowed')}>
|
||||
<TbSwimming size={20} color={lake.navigationForbidden ? 'var(--color-red)' : 'var(--color-green)'} style={{ opacity: lake.navigationForbidden ? 0.5 : 0.8 }} />
|
||||
</Tooltip>
|
||||
<Tooltip content={lake.navigationForbidden ? (language === 'cs' ? 'Plavba zakázána' : 'Navigation forbidden') : (language === 'cs' ? 'Plavba (i bezmotorová) povolena' : 'Navigation allowed')}>
|
||||
<TbSailboat size={20} color={lake.navigationForbidden ? 'var(--color-red)' : 'var(--color-green)'} style={{ opacity: lake.navigationForbidden ? 0.5 : 0.8 }} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1rem' }}>
|
||||
<CircularProgress value={lake.capacity} size={70} strokeWidth={6} />
|
||||
|
||||
@@ -8,7 +8,9 @@ import { WindChart } from './WindChart';
|
||||
import { NAVIGATION_LIMITS } from '../utils/navigationLimits';
|
||||
import { lakesConfig } from '../../scripts/lakesConfig';
|
||||
import { FiAlertCircle, FiStar } from 'react-icons/fi';
|
||||
import { TbSwimming, TbSailboat } from 'react-icons/tb';
|
||||
import { useFavorites } from '../hooks/useFavorites';
|
||||
import { Tooltip as IconTooltip } from './Tooltip';
|
||||
|
||||
interface LipnoData {
|
||||
timestamp: string;
|
||||
@@ -283,6 +285,15 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
|
||||
>
|
||||
<FiStar size={24} fill={isFav ? '#f59e0b' : 'none'} color={isFav ? '#f59e0b' : 'var(--text-muted)'} />
|
||||
</button>
|
||||
|
||||
<div style={{ display: 'flex', gap: '0.5rem', marginLeft: 'auto' }}>
|
||||
<IconTooltip content={(lakeInfo as any).navigationForbidden ? (language === 'cs' ? 'Koupání zakázáno' : 'Swimming forbidden') : (language === 'cs' ? 'Koupání (bez omezení)' : 'Swimming allowed')}>
|
||||
<TbSwimming size={24} color={(lakeInfo as any).navigationForbidden ? 'var(--color-red)' : 'var(--color-green)'} style={{ opacity: (lakeInfo as any).navigationForbidden ? 0.5 : 0.8 }} />
|
||||
</IconTooltip>
|
||||
<IconTooltip content={(lakeInfo as any).navigationForbidden ? (language === 'cs' ? 'Plavba zakázána' : 'Navigation forbidden') : (language === 'cs' ? 'Plavba (i bezmotorová) povolena' : 'Navigation allowed')}>
|
||||
<TbSailboat size={24} color={(lakeInfo as any).navigationForbidden ? 'var(--color-red)' : 'var(--color-green)'} style={{ opacity: (lakeInfo as any).navigationForbidden ? 0.5 : 0.8 }} />
|
||||
</IconTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.95rem', lineHeight: '1.5' }}>
|
||||
{t[language].seo.lakeDesc.replace('{name}', lakeInfo.name)}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { slugify } from '../utils/slugify';
|
||||
import { useFavorites } from '../hooks/useFavorites';
|
||||
import { CircularProgress } from './CircularProgress';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import { TbSwimming, TbSailboat } from 'react-icons/tb';
|
||||
|
||||
interface Lake {
|
||||
id: string;
|
||||
@@ -20,6 +22,7 @@ interface Lake {
|
||||
outflow: number;
|
||||
volume: number;
|
||||
maxVolume: number;
|
||||
navigationForbidden: boolean;
|
||||
sparkline: number[];
|
||||
}
|
||||
|
||||
@@ -72,7 +75,19 @@ const LakeCard = ({ lake, language, isFav, onToggleFav }: { lake: Lake, language
|
||||
<FiStar size={18} fill={isFav ? '#f59e0b' : 'none'} />
|
||||
</button>
|
||||
|
||||
<h3 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0, paddingRight: '2rem' }}>{lake.name} {lake.river ? `- ${lake.river}` : ''}</h3>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', paddingRight: '2rem' }}>
|
||||
<h3 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
||||
{lake.name} {lake.river ? `- ${lake.river}` : ''}
|
||||
</h3>
|
||||
<div style={{ display: 'flex', gap: '0.4rem', flexShrink: 0 }}>
|
||||
<Tooltip content={lake.navigationForbidden ? (language === 'cs' ? 'Koupání zakázáno' : 'Swimming forbidden') : (language === 'cs' ? 'Koupání (bez omezení)' : 'Swimming allowed')}>
|
||||
<TbSwimming size={20} color={lake.navigationForbidden ? 'var(--color-red)' : 'var(--color-green)'} style={{ opacity: lake.navigationForbidden ? 0.5 : 0.8 }} />
|
||||
</Tooltip>
|
||||
<Tooltip content={lake.navigationForbidden ? (language === 'cs' ? 'Plavba zakázána' : 'Navigation forbidden') : (language === 'cs' ? 'Plavba (i bezmotorová) povolena' : 'Navigation allowed')}>
|
||||
<TbSailboat size={20} color={lake.navigationForbidden ? 'var(--color-red)' : 'var(--color-green)'} style={{ opacity: lake.navigationForbidden ? 0.5 : 0.8 }} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '1rem' }}>
|
||||
<CircularProgress value={lake.capacity} size={70} strokeWidth={6} />
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useState, useRef, useEffect, type ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const Tooltip = ({ content, children }: Props) => {
|
||||
const [show, setShow] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
setShow(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{ position: 'relative', display: 'inline-flex' }}
|
||||
onMouseEnter={() => setShow(true)}
|
||||
onMouseLeave={() => setShow(false)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{show && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '100%',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
marginBottom: '8px',
|
||||
backgroundColor: 'var(--bg-card)',
|
||||
border: '1px solid var(--border-color)',
|
||||
padding: '0.5rem 0.75rem',
|
||||
borderRadius: '8px',
|
||||
width: 'max-content',
|
||||
maxWidth: '220px',
|
||||
zIndex: 9999,
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: 1.4,
|
||||
textAlign: 'center',
|
||||
pointerEvents: 'none'
|
||||
}}>
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user