From 1b4ef8e9fe1bf0872e8f6049a1c6f16ba773fe18 Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 13 Jul 2026 16:20:40 +0200 Subject: [PATCH] fix(plugins): fold resolvePluginIcon into PluginIcon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pluginIcon.ts and PluginIcon.tsx resolve to the same file on the case-insensitive filesystems dev checkouts commonly sit on (Windows, macOS) — tsc sees both casings of one module and fails with TS1149 in every importer. Keep the resolver and the component in one module. --- client/src/components/Layout/BottomNav.tsx | 2 +- client/src/components/Layout/Navbar.tsx | 2 +- .../src/components/shared/PluginIcon.test.tsx | 3 +-- client/src/components/shared/PluginIcon.tsx | 23 ++++++++++++++++++- client/src/components/shared/pluginIcon.ts | 22 ------------------ .../src/pages/tripPlanner/useTripPlanner.ts | 2 +- 6 files changed, 26 insertions(+), 28 deletions(-) delete mode 100644 client/src/components/shared/pluginIcon.ts diff --git a/client/src/components/Layout/BottomNav.tsx b/client/src/components/Layout/BottomNav.tsx index 09a8c6a1b..3d318571f 100644 --- a/client/src/components/Layout/BottomNav.tsx +++ b/client/src/components/Layout/BottomNav.tsx @@ -5,7 +5,7 @@ import { useSettingsStore } from '../../store/settingsStore' import { useTranslation } from '../../i18n' import { LayoutGrid, CalendarDays, Globe, Compass, Bookmark, Plus } from 'lucide-react' import type { LucideIcon } from 'lucide-react' -import { resolvePluginIcon } from '../shared/pluginIcon' +import { resolvePluginIcon } from '../shared/PluginIcon' const ADDON_NAV: Record = { vacay: { icon: CalendarDays, labelKey: 'admin.addons.catalog.vacay.name' }, diff --git a/client/src/components/Layout/Navbar.tsx b/client/src/components/Layout/Navbar.tsx index 2fb333616..cb7cd684c 100644 --- a/client/src/components/Layout/Navbar.tsx +++ b/client/src/components/Layout/Navbar.tsx @@ -9,7 +9,7 @@ import { useTranslation } from '../../i18n' import { Plane, LogOut, Settings, ChevronDown, Shield, ArrowLeft, Users, Moon, Sun, Monitor, CalendarDays, Briefcase, Globe, Compass, BookOpen, Bookmark } from 'lucide-react' import type { LucideIcon } from 'lucide-react' import InAppNotificationBell from './InAppNotificationBell.tsx' -import { resolvePluginIcon } from '../shared/pluginIcon' +import { resolvePluginIcon } from '../shared/PluginIcon' const ADDON_ICONS: Record = { CalendarDays, Briefcase, Globe, Compass, Bookmark } diff --git a/client/src/components/shared/PluginIcon.test.tsx b/client/src/components/shared/PluginIcon.test.tsx index f3a905f5a..cf813c48f 100644 --- a/client/src/components/shared/PluginIcon.test.tsx +++ b/client/src/components/shared/PluginIcon.test.tsx @@ -1,8 +1,7 @@ import { describe, it, expect } from 'vitest' import { render } from '@testing-library/react' import { Blocks, Stethoscope } from 'lucide-react' -import { resolvePluginIcon } from './pluginIcon' -import PluginIcon from './PluginIcon' +import PluginIcon, { resolvePluginIcon } from './PluginIcon' describe('resolvePluginIcon', () => { it('resolves a lucide name outside the old 14-icon allowlist', () => { diff --git a/client/src/components/shared/PluginIcon.tsx b/client/src/components/shared/PluginIcon.tsx index 59f2afadf..a463dd523 100644 --- a/client/src/components/shared/PluginIcon.tsx +++ b/client/src/components/shared/PluginIcon.tsx @@ -1,5 +1,26 @@ import type { CSSProperties } from 'react' -import { resolvePluginIcon } from './pluginIcon' +import * as LucideIcons from 'lucide-react' +import { Blocks, type LucideIcon } from 'lucide-react' + +// Lucide names are PascalCase. Restricting the lookup to that shape keeps a manifest +// from reaching lucide's non-icon exports (`createLucideIcon`, `icons`, `default`, +// `module.exports`), which are all lowercase-initial. +const LUCIDE_ICON_NAME = /^[A-Z][A-Za-z0-9]*$/ + +/** + * Resolves a plugin manifest's `icon` (a lucide icon name, e.g. "Stethoscope") to the + * matching lucide component, falling back to Blocks for a missing or unknown name. + * + * Note lucide icons are forwardRef objects, not functions — never narrow this with + * `typeof x === 'function'`. The lookup returns a module-level reference, so the + * component identity is stable across renders and is safe to call inline while + * building nav/tab arrays. + */ +export function resolvePluginIcon(name: string | null | undefined): LucideIcon { + if (!name || !LUCIDE_ICON_NAME.test(name)) return Blocks + const icon = (LucideIcons as unknown as Record)[name] + return icon ?? Blocks +} /** Renders the lucide icon a plugin declares in its manifest (Blocks if unknown). */ export default function PluginIcon({ name, size = 20, className, style }: { diff --git a/client/src/components/shared/pluginIcon.ts b/client/src/components/shared/pluginIcon.ts deleted file mode 100644 index b7850be8b..000000000 --- a/client/src/components/shared/pluginIcon.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as LucideIcons from 'lucide-react' -import { Blocks, type LucideIcon } from 'lucide-react' - -// Lucide names are PascalCase. Restricting the lookup to that shape keeps a manifest -// from reaching lucide's non-icon exports (`createLucideIcon`, `icons`, `default`, -// `module.exports`), which are all lowercase-initial. -const LUCIDE_ICON_NAME = /^[A-Z][A-Za-z0-9]*$/ - -/** - * Resolves a plugin manifest's `icon` (a lucide icon name, e.g. "Stethoscope") to the - * matching lucide component, falling back to Blocks for a missing or unknown name. - * - * Note lucide icons are forwardRef objects, not functions — never narrow this with - * `typeof x === 'function'`. The lookup returns a module-level reference, so the - * component identity is stable across renders and is safe to call inline while - * building nav/tab arrays. - */ -export function resolvePluginIcon(name: string | null | undefined): LucideIcon { - if (!name || !LUCIDE_ICON_NAME.test(name)) return Blocks - const icon = (LucideIcons as unknown as Record)[name] - return icon ?? Blocks -} diff --git a/client/src/pages/tripPlanner/useTripPlanner.ts b/client/src/pages/tripPlanner/useTripPlanner.ts index 93e34c612..44d01957d 100644 --- a/client/src/pages/tripPlanner/useTripPlanner.ts +++ b/client/src/pages/tripPlanner/useTripPlanner.ts @@ -6,7 +6,7 @@ import { useSettingsStore } from '../../store/settingsStore' import { getCached, fetchPhoto } from '../../services/photoService' import { useToast } from '../../components/shared/Toast' import { Map, Ticket, PackageCheck, Wallet, FolderOpen, Users, Train } from 'lucide-react' -import { resolvePluginIcon } from '../../components/shared/pluginIcon' +import { resolvePluginIcon } from '../../components/shared/PluginIcon' import { useTranslation, translateApiError } from '../../i18n' import { addonsApi, accommodationsApi, authApi, tripsApi, assignmentsApi, healthApi, airtrailApi, mapsApi, placesApi } from '../../api/client' import { parsedItemToDraft, isTransportItem, type BookingReviewDraft } from '../../components/Planner/parsedItemToDraft'