import React, { useEffect, useState, useCallback } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' import { NextSeo } from 'next-seo' import { motion } from 'framer-motion' import { Search } from 'lucide-react' import debounce from 'lodash/debounce' import { Button, Checkbox, cn, Input } from 'ui' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' import Panel from '~/components/Panel' import { features } from '~/data/features' function FeaturesPage() { const router = useRouter() const { basePath, query } = router const [searchTerm, setSearchTerm] = useState((query.q as string) || '') const [selectedProducts, setSelectedProducts] = useState( (query.products as string)?.split(',') || [] ) const HAS_ACTIVE_FILTERS = selectedProducts.length || searchTerm.length const products = Array.from(new Set(features.flatMap((feature) => feature.products))) // Debounced function to update URL params const updateQueryParamsDebounced = useCallback( debounce(() => updateQueryParams(), 300), [searchTerm, selectedProducts] ) const updateQueryParams = () => { const params = new URLSearchParams() if (searchTerm) params.set('q', searchTerm) if (selectedProducts.length > 0) params.set('products', selectedProducts.join(',')) router.replace({ pathname: '/features', query: params.toString() }, undefined, { shallow: true, }) } // Apply filters based on initial URL params on mount useEffect(() => { updateQueryParamsDebounced() return updateQueryParamsDebounced.cancel // Cleanup on unmount }, [searchTerm, selectedProducts, updateQueryParamsDebounced]) // Sync state with query parameters when they change useEffect(() => { if (query.q !== searchTerm) setSearchTerm((query.q as string) || '') if (query.products !== selectedProducts.join(',')) { setSelectedProducts((query.products as string)?.split(',') || []) } }, [query.q, query.products]) // Handle search input change const handleSearchChange = (e: React.ChangeEvent) => { setSearchTerm(e.target.value) } // Handle product checkbox change const handleProductChange = (product: string) => { setSelectedProducts((prev) => prev.includes(product) ? prev.filter((p) => p !== product) : [...prev, product] ) } // Filter features based on search term and selected products const filteredFeatures = features.filter((feature) => { const matchesSearch = feature.title.toLowerCase().includes(searchTerm.toLowerCase()) || feature.subtitle.toLowerCase().includes(searchTerm.toLowerCase()) || feature.description.toLowerCase().includes(searchTerm.toLowerCase()) const matchesProduct = selectedProducts.length === 0 || feature.products.some((product) => selectedProducts.includes(product)) return matchesSearch && matchesProduct }) const meta = { title: 'Supabase Features', description: 'From authentication to storage, everything you need to build and ship your next project.', } return ( <>

Supabase Features

Everything you need
to build and ship your next project.

} size="small" autoComplete="off" type="search" placeholder="Search features" value={searchTerm} onChange={handleSearchChange} className="w-full [&_input]:text-base [&_input]:md:text-sm [&_input]:!leading-4" />

Filter by tags:

{products .sort((a, b) => (a > b ? 1 : -1)) .map((product) => (
handleProductChange(product)} className="[&_input]:m-0" />
))}
Features selected: {filteredFeatures.length}
{!filteredFeatures?.length ? (

No features found with these filters

) : (
{filteredFeatures .sort((a, b) => (a.title > b.title ? 1 : -1)) .map((feature) => (

{feature.title}

{feature.subtitle}

))}
)}
) } export default FeaturesPage