Clean up
This commit is contained in:
parent
b94cf17d75
commit
0c6d207967
41 changed files with 1809 additions and 1204 deletions
|
|
@ -192,7 +192,11 @@ export default function AuthModal({
|
|||
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-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' ? t('auth.passwordPlaceholderRegister') : t('auth.passwordPlaceholderLogin')}
|
||||
placeholder={
|
||||
view === 'register'
|
||||
? t('auth.passwordPlaceholderRegister')
|
||||
: t('auth.passwordPlaceholderLogin')
|
||||
}
|
||||
/>
|
||||
{view === 'login' && (
|
||||
<button
|
||||
|
|
@ -207,9 +211,7 @@ export default function AuthModal({
|
|||
)}
|
||||
|
||||
{view === 'forgot' && resetSent && (
|
||||
<p className="text-sm text-teal-700 dark:text-teal-400">
|
||||
{t('auth.resetSent')}
|
||||
</p>
|
||||
<p className="text-sm text-teal-700 dark:text-teal-400">{t('auth.resetSent')}</p>
|
||||
)}
|
||||
|
||||
{error && <p className="text-sm text-red-600 dark:text-red-300">{error}</p>}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import type { FeatureMeta } from '../../types';
|
||||
import { ts, tsDesc } from '../../i18n/server';
|
||||
import { ts, tsDesc, tsDetail } from '../../i18n/server';
|
||||
import InfoPopup from './InfoPopup';
|
||||
|
||||
interface FeatureInfoPopupProps {
|
||||
|
|
@ -34,7 +34,7 @@ export function FeatureInfoPopup({ feature, onClose, onNavigateToSource }: Featu
|
|||
)}
|
||||
{feature.detail && (
|
||||
<p className="text-sm text-warm-700 dark:text-warm-300 mb-4 leading-relaxed">
|
||||
{feature.detail}
|
||||
{tsDetail(feature.name, feature.detail)}
|
||||
</p>
|
||||
)}
|
||||
</InfoPopup>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ export default function LanguageDropdown() {
|
|||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const current = SUPPORTED_LANGUAGES.find((l) => l.code === i18n.language) ?? SUPPORTED_LANGUAGES[0];
|
||||
const current =
|
||||
SUPPORTED_LANGUAGES.find((l) => l.code === i18n.language) ?? SUPPORTED_LANGUAGES[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
|
@ -32,7 +33,13 @@ export default function LanguageDropdown() {
|
|||
aria-label="Language"
|
||||
>
|
||||
<span className="text-base leading-none">{current.flag}</span>
|
||||
<svg className="w-3 h-3 text-warm-400" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<svg
|
||||
className="w-3 h-3 text-warm-400"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M3 5l3 3 3-3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import type { Page } from './Header';
|
||||
import { PAGE_PATHS } from './Header';
|
||||
import type { AuthUser } from '../../hooks/useAuth';
|
||||
import { SUPPORTED_LANGUAGES } from '../../i18n';
|
||||
import { DownloadIcon } from './icons/DownloadIcon';
|
||||
import { BookmarkIcon } from './icons/BookmarkIcon';
|
||||
import { CheckIcon } from './icons/CheckIcon';
|
||||
|
|
@ -45,6 +47,8 @@ export default function MobileMenu({
|
|||
onShare,
|
||||
copied,
|
||||
}: MobileMenuProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const mobileNavItem = (page: Page, label: string) => (
|
||||
<a
|
||||
key={page}
|
||||
|
|
@ -72,24 +76,24 @@ export default function MobileMenu({
|
|||
{/* 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">
|
||||
<span className="font-semibold">Menu</span>
|
||||
<span className="font-semibold">{t('mobileMenu.menu')}</span>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex items-center justify-center w-10 h-10 -mr-2 rounded hover:bg-navy-800 transition-colors"
|
||||
aria-label="Close menu"
|
||||
aria-label={t('header.closeMenu')}
|
||||
>
|
||||
<CloseIcon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<nav className="flex-1 flex flex-col gap-1 p-3 overflow-y-auto">
|
||||
{mobileNavItem('home', 'Home')}
|
||||
{mobileNavItem('dashboard', 'Dashboard')}
|
||||
{mobileNavItem('learn', 'Learn')}
|
||||
{mobileNavItem('home', t('mobileMenu.home'))}
|
||||
{mobileNavItem('dashboard', t('header.dashboard'))}
|
||||
{mobileNavItem('learn', t('header.learn'))}
|
||||
{user?.subscription !== 'licensed' &&
|
||||
!user?.isAdmin &&
|
||||
mobileNavItem('pricing', 'Pricing')}
|
||||
{user && mobileNavItem('invites', 'Invite Friends')}
|
||||
{user && mobileNavItem('account', 'Account')}
|
||||
mobileNavItem('pricing', t('header.pricing'))}
|
||||
{user && mobileNavItem('invites', t('header.inviteFriends'))}
|
||||
{user && mobileNavItem('account', t('userMenu.account'))}
|
||||
|
||||
{/* Dashboard actions */}
|
||||
{activePage === 'dashboard' && (
|
||||
|
|
@ -102,7 +106,7 @@ export default function MobileMenu({
|
|||
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 ? 'Copied!' : 'Share'}
|
||||
{copied ? t('common.copied') : t('common.share')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -113,7 +117,7 @@ export default function MobileMenu({
|
|||
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"
|
||||
>
|
||||
<DownloadIcon className="w-5 h-5" />
|
||||
{exporting ? 'Exporting...' : 'Export'}
|
||||
{exporting ? t('header.exporting') : t('header.exportLabel')}
|
||||
</button>
|
||||
{onSaveSearch && (
|
||||
<button
|
||||
|
|
@ -129,13 +133,13 @@ export default function MobileMenu({
|
|||
) : (
|
||||
<BookmarkIcon className="w-5 h-5" />
|
||||
)}
|
||||
Save
|
||||
{t('common.save')}
|
||||
</button>
|
||||
)}
|
||||
{user && mobileNavItem('saved', 'Saved')}
|
||||
{user && mobileNavItem('saved', t('header.saved'))}
|
||||
</div>
|
||||
)}
|
||||
{activePage !== 'dashboard' && user && mobileNavItem('saved', 'Saved')}
|
||||
{activePage !== 'dashboard' && user && mobileNavItem('saved', t('header.saved'))}
|
||||
</nav>
|
||||
|
||||
{/* Theme toggle + Auth section at bottom */}
|
||||
|
|
@ -148,9 +152,30 @@ export default function MobileMenu({
|
|||
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" />}
|
||||
<span>Theme: {theme === 'light' ? 'Light' : 'Dark'}</span>
|
||||
<span>{theme === 'light' ? t('userMenu.themeLight') : t('userMenu.themeDark')}</span>
|
||||
</button>
|
||||
|
||||
{/* Language selector */}
|
||||
<div className="flex gap-1 px-4">
|
||||
{SUPPORTED_LANGUAGES.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => {
|
||||
i18n.changeLanguage(lang.code);
|
||||
localStorage.setItem('language', lang.code);
|
||||
}}
|
||||
className={`flex-1 flex items-center justify-center gap-1.5 px-2 py-2 rounded text-sm ${
|
||||
i18n.language === lang.code
|
||||
? 'bg-navy-700 text-white font-medium'
|
||||
: 'text-warm-400 hover:bg-navy-800 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<span className="text-base leading-none">{lang.flag}</span>
|
||||
<span className="hidden sm:inline">{lang.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Auth buttons */}
|
||||
<div>
|
||||
{user ? (
|
||||
|
|
@ -163,7 +188,7 @@ export default function MobileMenu({
|
|||
}}
|
||||
className="shrink-0 text-sm text-warm-400 hover:text-white"
|
||||
>
|
||||
Log out
|
||||
{t('userMenu.logOut')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -175,7 +200,7 @@ export default function MobileMenu({
|
|||
}}
|
||||
className="flex-1 px-3 py-2.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm text-center"
|
||||
>
|
||||
Log in
|
||||
{t('header.logIn')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -184,7 +209,7 @@ export default function MobileMenu({
|
|||
}}
|
||||
className="flex-1 px-3 py-2.5 rounded bg-teal-600 hover:bg-teal-700 transition-colors text-sm font-medium text-center"
|
||||
>
|
||||
Create account
|
||||
{t('header.createAccount')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ export default function UpgradeModal({
|
|||
}, []);
|
||||
|
||||
const priceLabel =
|
||||
pricePence === null ? '...' : pricePence === 0 ? t('upgrade.free') : `\u00A3${pricePence / 100}`;
|
||||
pricePence === null
|
||||
? '...'
|
||||
: pricePence === 0
|
||||
? t('upgrade.free')
|
||||
: `\u00A3${pricePence / 100}`;
|
||||
const isFree = pricePence === 0;
|
||||
|
||||
const handleUpgrade = async () => {
|
||||
|
|
@ -63,9 +67,7 @@ export default function UpgradeModal({
|
|||
{/* Header */}
|
||||
<div className="bg-gradient-to-br from-navy-950 to-teal-900 px-6 py-8 text-center">
|
||||
<h2 className="text-2xl font-bold text-white mb-2">{t('upgrade.title')}</h2>
|
||||
<p className="text-warm-300 text-sm">
|
||||
{t('upgrade.description')}
|
||||
</p>
|
||||
<p className="text-warm-300 text-sm">{t('upgrade.description')}</p>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
|
|
@ -74,12 +76,12 @@ export default function UpgradeModal({
|
|||
<span className="text-4xl font-extrabold text-navy-950 dark:text-warm-100">
|
||||
{priceLabel}
|
||||
</span>
|
||||
{!isFree && <span className="text-warm-500 dark:text-warm-400 text-lg">{t('upgrade.once')}</span>}
|
||||
{!isFree && (
|
||||
<span className="text-warm-500 dark:text-warm-400 text-lg">{t('upgrade.once')}</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-center text-sm text-warm-500 dark:text-warm-400 mb-6">
|
||||
{isFree
|
||||
? t('upgrade.freeForEarly')
|
||||
: t('upgrade.oneTimePayment')}
|
||||
{isFree ? t('upgrade.freeForEarly') : t('upgrade.oneTimePayment')}
|
||||
</p>
|
||||
|
||||
{isLoggedIn ? (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue