From e27d20419df32f1a9ef5c3fba917d9d269ff6864 Mon Sep 17 00:00:00 2001 From: Richard R Date: Wed, 13 May 2026 18:42:31 -0600 Subject: [PATCH] refactor(ui): unify form primitives and simplify document selection modal Move shared button, input, and listbox styles to a new formPrimitives module used across admin panels, settings, and document settings. Update admin and settings components to import these styles from the new location. Refactor DocumentSelectionModal to use a direct files prop with loading and error states, removing fetcher logic and internal fetch state. Add a custom hook for library document queries. Clean up legacy admin/ui primitives and standardize segmented controls and section layouts. --- src/components/SettingsModal.tsx | 159 ++++++++------- src/components/admin/AdminFeaturesPanel.tsx | 62 +++--- src/components/admin/AdminProvidersPanel.tsx | 81 ++++---- src/components/admin/ui.tsx | 159 ++------------- .../documents/DocumentSelectionModal.tsx | 56 ++---- src/components/documents/DocumentSettings.tsx | 132 +++++-------- src/components/formPrimitives.tsx | 182 ++++++++++++++++++ src/hooks/useLibraryDocumentsQuery.ts | 50 +++++ 8 files changed, 477 insertions(+), 404 deletions(-) create mode 100644 src/components/formPrimitives.tsx create mode 100644 src/hooks/useLibraryDocumentsQuery.ts diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index 85b2936..c41093e 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -14,7 +14,6 @@ import { Button, Input, } from '@headlessui/react'; -import { useQueryClient } from '@tanstack/react-query'; import Link from 'next/link'; import { useTheme } from '@/contexts/ThemeContext'; import { useConfig } from '@/contexts/ConfigContext'; @@ -52,6 +51,19 @@ import { useRuntimeConfig } from '@/contexts/RuntimeConfigContext'; import { AdminProvidersPanel } from '@/components/admin/AdminProvidersPanel'; import { AdminFeaturesPanel } from '@/components/admin/AdminFeaturesPanel'; import { useSharedProviders } from '@/hooks/useSharedProviders'; +import { useLibraryDocumentsQuery } from '@/hooks/useLibraryDocumentsQuery'; +import { + btnDanger, + btnOutline, + btnPrimary, + btnSecondary, + inputClass, + listboxButtonClass, + listboxOptionClass, + listboxOptionsClass, + segmentedButtonClass, + segmentedGroupClass, +} from '@/components/formPrimitives'; // Hard-coded theme color palettes for the visual theme selector type ThemeColorSet = { background: string; base: string; offbase: string; accent: string; secondaryAccent: string; foreground: string; muted: string }; @@ -111,17 +123,8 @@ const SIDEBAR_SECTIONS: SidebarSection[] = [ ]; type AdminSubTab = 'providers' | 'features'; -const LIBRARY_DOCUMENTS_QUERY_KEY = ['documents-library', 10000] as const; - -async function fetchLibraryDocuments(): Promise { - const res = await fetch('/api/documents/library?limit=10000'); - if (!res.ok) throw new Error('Failed to list library documents'); - const data = (await res.json()) as { documents?: BaseDocument[] }; - return data.documents || []; -} export function SettingsModal({ className = '' }: { className?: string }) { - const queryClient = useQueryClient(); const runtimeConfig = useRuntimeConfig(); const enableDestructiveDelete = runtimeConfig.enableDestructiveDeleteActions; const showAllDeepInfra = runtimeConfig.showAllDeepInfraModels; @@ -149,8 +152,6 @@ export function SettingsModal({ className = '' }: { className?: string }) { title: string; confirmLabel: string; defaultSelected: boolean; - initialFiles?: BaseDocument[]; - fetcher?: () => Promise; }>({ title: '', confirmLabel: '', @@ -167,13 +168,12 @@ export function SettingsModal({ className = '' }: { className?: string }) { const { data: session } = useAuthSession(); const router = useRouter(); const isBusy = isImportingLibrary; - const fetchLibraryDocumentsCached = useCallback(async () => { - return queryClient.ensureQueryData({ - queryKey: LIBRARY_DOCUMENTS_QUERY_KEY, - queryFn: fetchLibraryDocuments, - staleTime: 60 * 1000, - }); - }, [queryClient]); + const { + documents: libraryDocuments, + isLoading: isLibraryDocumentsLoading, + errorMessage: libraryDocumentsErrorMessage, + prefetch: prefetchLibraryDocuments, + } = useLibraryDocumentsQuery(isSelectionModalOpen); const { providers: sharedProviders } = useSharedProviders(); const { @@ -299,17 +299,11 @@ export function SettingsModal({ className = '' }: { className?: string }) { const handleImportLibrary = async () => { // Start fetching as soon as the user opens the picker so cached data is // often ready before the modal asks for it. - void queryClient.prefetchQuery({ - queryKey: LIBRARY_DOCUMENTS_QUERY_KEY, - queryFn: fetchLibraryDocuments, - staleTime: 60 * 1000, - }); + void prefetchLibraryDocuments(); setSelectionModalProps({ title: 'Import from Library', confirmLabel: 'Import', defaultSelected: false, - initialFiles: queryClient.getQueryData(LIBRARY_DOCUMENTS_QUERY_KEY), - fetcher: fetchLibraryDocumentsCached, }); setIsSelectionModalOpen(true); }; @@ -481,10 +475,13 @@ export function SettingsModal({ className = '' }: { className?: string }) { setActiveSection(visibleSections[0]?.id ?? 'theme'); }, [activeSection, visibleSections]); - const btnBase = "inline-flex items-center justify-center rounded-lg text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 transform transition-transform duration-200 ease-in-out"; - const btnPrimary = `${btnBase} bg-accent text-background hover:bg-secondary-accent hover:scale-[1.04]`; - const btnSecondary = `${btnBase} bg-background text-foreground hover:bg-offbase hover:text-accent hover:scale-[1.04]`; - const btnOutline = `${btnBase} bg-background border border-offbase text-foreground hover:bg-offbase hover:text-accent hover:scale-[1.02]`; + const fieldLabelClass = 'block text-[11px] font-semibold uppercase tracking-wide text-muted'; + const sectionShellClass = 'space-y-2 pb-3 border-b border-offbase px-0.5'; + const sectionHeadingClass = 'text-sm font-semibold text-foreground'; + const accountBtnBase = 'inline-flex items-center justify-center rounded-lg text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 transform transition-transform duration-200 ease-in-out'; + const accountBtnPrimary = `${accountBtnBase} bg-accent text-background hover:bg-secondary-accent hover:scale-[1.04]`; + const accountBtnOutline = `${accountBtnBase} bg-background border border-offbase text-foreground hover:bg-offbase hover:text-accent hover:scale-[1.04]`; + const accountBtnDanger = `${accountBtnBase} bg-red-600 text-white border border-red-700 hover:bg-red-700 hover:scale-[1.02]`; const effectiveProviderType = resolveEffectiveProviderType({ providerRef: selectedProviderRef, providerType: localProviderType, @@ -511,11 +508,11 @@ export function SettingsModal({ className = '' }: { className?: string }) { <> @@ -543,7 +540,7 @@ export function SettingsModal({ className = '' }: { className?: string }) { leaveFrom="opacity-100 scale-100" leaveTo="opacity-0 scale-95" > - + {/* Header */}
@@ -552,7 +549,7 @@ export function SettingsModal({ className = '' }: { className?: string }) { {authEnabled && ( @@ -567,11 +564,11 @@ export function SettingsModal({ className = '' }: { className?: string }) {
-
+
{/* Desktop: vertical sidebar */} -