diff --git a/package.json b/package.json index cea4473..76cf062 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-feature-flags", - "version": "0.0.10", + "version": "0.0.11", "description": "Feature flags plugin for Payload CMS - manage feature toggles, A/B tests, and gradual rollouts", "license": "MIT", "type": "module", diff --git a/src/views/FeatureFlagsClient.tsx b/src/views/FeatureFlagsClient.tsx index df5a5db..37d53a5 100644 --- a/src/views/FeatureFlagsClient.tsx +++ b/src/views/FeatureFlagsClient.tsx @@ -5,6 +5,23 @@ import { useTheme } from '@payloadcms/ui' +// Simple debounce hook +function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value) + }, delay) + + return () => { + clearTimeout(handler) + } + }, [value, delay]) + + return debouncedValue +} + interface FeatureFlag { id: string name: string @@ -40,12 +57,17 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe const [saving, setSaving] = useState(null) const [successMessage, setSuccessMessage] = useState('') + // Debounce search to reduce re-renders + const debouncedSearch = useDebounce(search, 300) + const fetchFlags = async (signal?: AbortSignal) => { try { setLoading(true) setError('') - const response = await fetch(`${config.serverURL}${config.routes.api}/feature-flags?limit=1000`, { + // Use a reasonable limit to prevent performance issues + const limit = Math.min(1000, 100) + const response = await fetch(`${config.serverURL}${config.routes.api}/feature-flags?limit=${limit}`, { credentials: 'include', signal, }) @@ -142,12 +164,13 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe // Filter out null/undefined entries first let filtered = flags.filter(flag => flag && flag.name) - // Filter by search - if (search) { + // Filter by debounced search + if (debouncedSearch) { + const searchLower = debouncedSearch.toLowerCase() filtered = filtered.filter(flag => - flag.name?.toLowerCase().includes(search.toLowerCase()) || - flag.description?.toLowerCase().includes(search.toLowerCase()) || - flag.tags?.some(t => t.tag?.toLowerCase().includes(search.toLowerCase())) + flag.name?.toLowerCase().includes(searchLower) || + flag.description?.toLowerCase().includes(searchLower) || + flag.tags?.some(t => t.tag?.toLowerCase().includes(searchLower)) ) } @@ -167,7 +190,7 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe }) return filtered - }, [flags, search, sortField, sortDirection]) + }, [flags, debouncedSearch, sortField, sortDirection]) const SortIcon = ({ field }: { field: typeof sortField }) => { if (sortField !== field) { @@ -219,15 +242,18 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe Manage all feature flags in a spreadsheet view with inline editing capabilities

{!canUpdate && ( -
+
Read-Only Access: You can view feature flags but cannot edit them. Contact your administrator to request update permissions.
)} @@ -264,14 +290,17 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe )} {error && ( -
+
Error: {error}
)} @@ -450,7 +479,7 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe textAlign: 'center', color: styles.textMuted }}> - {search ? 'No flags match your search' : 'No feature flags yet'} + {debouncedSearch ? 'No flags match your search' : 'No feature flags yet'} ) : ( @@ -483,7 +512,7 @@ const FeatureFlagsClientComponent = ({ initialFlags = [], canUpdate = true }: Fe color: styles.text }}> { - const value = Math.min(100, Math.max(0, parseInt(e.target.value) || 0)) - updateFlag(flag.id, { rolloutPercentage: value }) + const value = Math.min(100, Math.max(0, parseFloat(e.target.value) || 0)) + updateFlag(flag.id, { rolloutPercentage: Math.round(value) }) }} disabled={!canUpdate || saving === flag.id} min="0" diff --git a/src/views/FeatureFlagsView.tsx b/src/views/FeatureFlagsView.tsx index b844a46..cbf182e 100644 --- a/src/views/FeatureFlagsView.tsx +++ b/src/views/FeatureFlagsView.tsx @@ -21,11 +21,15 @@ interface FeatureFlag { updatedAt: string } -async function fetchInitialFlags(payload: any): Promise { +async function fetchInitialFlags(payload: any, searchParams?: Record): Promise { try { + const limit = Math.min(1000, parseInt(searchParams?.limit as string) || 100) + const page = Math.max(1, parseInt(searchParams?.page as string) || 1) + const result = await payload.find({ collection: 'feature-flags', - limit: 1000, + limit, + page, sort: 'name', }) @@ -135,7 +139,7 @@ export default async function FeatureFlagsView({ } // Fetch initial data server-side (only if user has access) - const initialFlags = await fetchInitialFlags(initPageResult.req.payload) + const initialFlags = await fetchInitialFlags(initPageResult.req.payload, searchParams) // Check if user can update feature flags const canUpdateFeatureFlags = permissions?.collections?.['feature-flags']?.update || false