diff --git a/actions/providers.ts b/actions/providers.ts index 8c50440f46..2ee10763e8 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -10,7 +10,7 @@ export const getProvider = async ({ page = 1, query = "", sort = "", - filter = "", + filters = {}, }) => { const session = await auth(); const tenantId = session?.user.tenantId; @@ -19,13 +19,20 @@ export const getProvider = async ({ const keyServer = process.env.LOCAL_SERVER_URL; const url = new URL(`${keyServer}/providers`); + if (page) url.searchParams.append("page[number]", page.toString()); - if (query) url.searchParams.append("filter[query]", query); + if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); - if (filter) url.searchParams.append("filter[provider]", filter); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]") { + url.searchParams.append(key, String(value)); + } + }); try { - const providers = await fetch(`${url.toString()}`, { + const providers = await fetch(url.toString(), { headers: { "X-Tenant-ID": `${tenantId}`, }, diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index e610187e06..6cc1c5c11e 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -9,24 +9,34 @@ import { } from "@/components/compliance"; import { FilterControls } from "@/components/filters"; import { Header } from "@/components/ui"; -import { searchParamsProps } from "@/types"; +import { SearchParamsProps } from "@/types"; + +export default async function Compliance({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { + const searchParamsKey = JSON.stringify(searchParams || {}); -export default async function Compliance({ searchParams }: searchParamsProps) { return ( <>
- }> + }> ); } -const SSRComplianceGrid = async ({ searchParams }: searchParamsProps) => { - const page = searchParams.page ? parseInt(searchParams.page) : 1; +const SSRComplianceGrid = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); const compliancesData = await getCompliance({ page }); const [compliances] = await Promise.all([compliancesData]); diff --git a/app/(prowler)/page.tsx b/app/(prowler)/page.tsx index 9577693ca8..1fdd78d351 100644 --- a/app/(prowler)/page.tsx +++ b/app/(prowler)/page.tsx @@ -11,7 +11,7 @@ export default function Home() { <>
- +
+ +
@@ -43,28 +41,26 @@ export default async function Providers({ const SSRDataTable = async ({ searchParams, }: { - searchParams?: { - query?: string; - page?: string; - sort?: string; - filter?: string; - }; + searchParams: SearchParamsProps; }) => { - const query = searchParams?.query || ""; - const page = searchParams?.page ? parseInt(searchParams.page) : 1; - const sort = searchParams?.sort || ""; - const filter = searchParams?.filter || ""; + const page = parseInt(searchParams.page?.toString() || "1", 10); + const sort = searchParams.sort?.toString() || ""; - const providersData = await getProvider({ query, page, sort, filter }); - const [providers] = await Promise.all([providersData]); + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); - if (providers?.errors) redirect("/providers"); + // Extract query from filters + const query = (filters["filter[search]"] as string) || ""; + + const providersData = await getProvider({ query, page, sort, filters }); return ( ); }; diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 872d7f3042..24fef60b25 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -6,9 +6,14 @@ import { getService } from "@/actions/services"; import { FilterControls } from "@/components/filters"; import { ServiceCard, ServiceSkeletonGrid } from "@/components/services"; import { Header } from "@/components/ui"; -import { searchParamsProps } from "@/types"; +import { SearchParamsProps } from "@/types"; -export default async function Services({ searchParams }: searchParamsProps) { +export default async function Services({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { + const searchParamsKey = JSON.stringify(searchParams || {}); return ( <>
- }> + }> ); } -const SSRServiceGrid = async ({ searchParams }: searchParamsProps) => { - const page = searchParams.page ? parseInt(searchParams.page) : 1; +const SSRServiceGrid = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); const servicesData = await getService({ page }); const [services] = await Promise.all([servicesData]); diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx index cff19b91e0..a7f643d73b 100644 --- a/app/(prowler)/users/page.tsx +++ b/app/(prowler)/users/page.tsx @@ -11,14 +11,20 @@ import { DataTableUser, SkeletonTableUser, } from "@/components/users"; -import { searchParamsProps } from "@/types"; +import { SearchParamsProps } from "@/types"; -export default async function Users({ searchParams }: searchParamsProps) { +export default async function Users({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { const session = await auth(); if (session?.user?.role !== "admin") { redirect("/"); } + const searchParamsKey = JSON.stringify(searchParams || {}); + return ( <>
@@ -28,7 +34,7 @@ export default async function Users({ searchParams }: searchParamsProps) {
- }> + }>
@@ -36,8 +42,12 @@ export default async function Users({ searchParams }: searchParamsProps) { ); } -const SSRDataTable = async ({ searchParams }: searchParamsProps) => { - const page = searchParams.page ? parseInt(searchParams.page) : 1; +const SSRDataTable = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); const usersData = await getUsers({ page }); const [users] = await Promise.all([usersData]); diff --git a/components/filters/CustomCheckboxMutedFindings.tsx b/components/filters/CustomCheckboxMutedFindings.tsx index 62715403fe..0ebfcb7755 100644 --- a/components/filters/CustomCheckboxMutedFindings.tsx +++ b/components/filters/CustomCheckboxMutedFindings.tsx @@ -1,20 +1,10 @@ import { Checkbox } from "@nextui-org/react"; import React from "react"; -interface CustomCheckboxMutedFindingsProps { - mutedFindings?: boolean; -} - -export const CustomCheckboxMutedFindings: React.FC< - CustomCheckboxMutedFindingsProps -> = ({ mutedFindings }) => { +export const CustomCheckboxMutedFindings = () => { return ( - <> - {mutedFindings && ( - - Include Muted Findings - - )} - + + Include Muted Findings + ); }; diff --git a/components/filters/CustomDatePicker.tsx b/components/filters/CustomDatePicker.tsx index 667078bc8f..820407d6c2 100644 --- a/components/filters/CustomDatePicker.tsx +++ b/components/filters/CustomDatePicker.tsx @@ -7,21 +7,55 @@ import { today, } from "@internationalized/date"; import { Button, ButtonGroup, DatePicker } from "@nextui-org/react"; -import { useDateFormatter, useLocale } from "@react-aria/i18n"; -import React from "react"; +import { useLocale } from "@react-aria/i18n"; +import { useRouter, useSearchParams } from "next/navigation"; +import React, { useCallback, useEffect, useRef } from "react"; export const CustomDatePicker = () => { + const router = useRouter(); + const searchParams = useSearchParams(); const defaultDate = today(getLocalTimeZone()); const [value, setValue] = React.useState(defaultDate); const { locale } = useLocale(); - const formatter = useDateFormatter({ dateStyle: "full" }); const now = today(getLocalTimeZone()); const nextWeek = startOfWeek(now.add({ weeks: 1 }), locale); const nextMonth = startOfMonth(now.add({ months: 1 })); + const applyDateFilter = useCallback( + (date: any) => { + const params = new URLSearchParams(searchParams.toString()); + if (date) { + params.set("filter[updated_at__lte]", date.toString()); + } else { + params.delete("filter[updated_at__lte]"); + } + router.push(`?${params.toString()}`, { scroll: false }); + }, + [router, searchParams], + ); + + const initialRender = useRef(true); + + useEffect(() => { + if (initialRender.current) { + initialRender.current = false; + return; + } + const params = new URLSearchParams(searchParams.toString()); + if (params.size === 0) { + // If all params are cleared, reset to default date + setValue(defaultDate); + } + }, [searchParams]); + + const handleDateChange = (newValue: any) => { + setValue(newValue); + applyDateFilter(newValue); + }; + return (
{ size="sm" variant="bordered" > - - - + + + } calendarProps={{ @@ -50,15 +88,11 @@ export const CustomDatePicker = () => { }, }} value={value} - onChange={setValue} + onChange={handleDateChange} label="Scan date" size="sm" variant="flat" /> -

- Selected date:{" "} - {value ? formatter.format(value.toDate(getLocalTimeZone())) : "--"} -

); }; diff --git a/components/filters/CustomProviderInputs.tsx b/components/filters/CustomProviderInputs.tsx index d97b03b842..373b6255b1 100644 --- a/components/filters/CustomProviderInputs.tsx +++ b/components/filters/CustomProviderInputs.tsx @@ -4,12 +4,13 @@ import { AWSProviderBadge, AzureProviderBadge, GCPProviderBadge, + KS8ProviderBadge, } from "../icons/providers-badge"; export const CustomProviderInputAWS = () => { return (
- +

Amazon Web Services

); @@ -18,7 +19,7 @@ export const CustomProviderInputAWS = () => { export const CustomProviderInputAzure = () => { return (
- +

Azure

); @@ -27,8 +28,17 @@ export const CustomProviderInputAzure = () => { export const CustomProviderInputGCP = () => { return (
- +

Google Cloud Platform

); }; + +export const CustomProviderInputKubernetes = () => { + return ( +
+ +

Kubernetes

+
+ ); +}; diff --git a/components/filters/CustomRegionSelection.tsx b/components/filters/CustomRegionSelection.tsx new file mode 100644 index 0000000000..9204da3d33 --- /dev/null +++ b/components/filters/CustomRegionSelection.tsx @@ -0,0 +1,50 @@ +"use client"; +import { Select, SelectItem } from "@nextui-org/react"; + +const regions = [ + { key: "af-south-1", label: "AF South 1" }, + { key: "ap-east-1", label: "AP East 1" }, + { key: "ap-northeast-1", label: "AP Northeast 1" }, + { key: "ap-northeast-2", label: "AP Northeast 2" }, + { key: "ap-northeast-3", label: "AP Northeast 3" }, + { key: "ap-south-1", label: "AP South 1" }, + { key: "ap-south-2", label: "AP South 2" }, + { key: "ap-southeast-1", label: "AP Southeast 1" }, + { key: "ap-southeast-2", label: "AP Southeast 2" }, + { key: "ap-southeast-3", label: "AP Southeast 3" }, + { key: "ap-southeast-4", label: "AP Southeast 4" }, + { key: "ca-central-1", label: "CA Central 1" }, + { key: "ca-west-1", label: "CA West 1" }, + { key: "eu-central-1", label: "EU Central 1" }, + { key: "eu-central-2", label: "EU Central 2" }, + { key: "eu-north-1", label: "EU North 1" }, + { key: "eu-south-1", label: "EU South 1" }, + { key: "eu-south-2", label: "EU South 2" }, + { key: "eu-west-1", label: "EU West 1" }, + { key: "eu-west-2", label: "EU West 2" }, + { key: "eu-west-3", label: "EU West 3" }, + { key: "il-central-1", label: "IL Central 1" }, + { key: "me-central-1", label: "ME Central 1" }, + { key: "me-south-1", label: "ME South 1" }, + { key: "sa-east-1", label: "SA East 1" }, + { key: "us-east-1", label: "US East 1" }, + { key: "us-east-2", label: "US East 2" }, + { key: "us-west-1", label: "US West 1" }, + { key: "us-west-2", label: "US West 2" }, +]; + +export const CustomRegionSelection = () => { + return ( + + ); +}; diff --git a/components/filters/CustomSearchInput.tsx b/components/filters/CustomSearchInput.tsx new file mode 100644 index 0000000000..b3a827abb7 --- /dev/null +++ b/components/filters/CustomSearchInput.tsx @@ -0,0 +1,61 @@ +import { Input } from "@nextui-org/react"; +import { XCircle } from "lucide-react"; +import { useRouter, useSearchParams } from "next/navigation"; +import React, { useCallback, useEffect, useState } from "react"; + +import { useDebounce } from "../../hooks/useDebounce"; + +export const CustomSearchInput: React.FC = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [searchQuery, setSearchQuery] = useState(() => { + return searchParams.get("filter[search]") || ""; + }); + const debouncedSearchQuery = useDebounce(searchQuery, 300); + + const applySearch = useCallback( + (query: string) => { + const params = new URLSearchParams(searchParams.toString()); + if (query) { + params.set("filter[search]", query); + } else { + params.delete("filter[search]"); + } + router.replace(`?${params.toString()}`, { scroll: false }); + }, + [router, searchParams], + ); + + const clearIconSearch = () => { + setSearchQuery(""); + applySearch(""); + }; + + useEffect(() => { + applySearch(debouncedSearchQuery); + }, [debouncedSearchQuery, applySearch]); + + return ( + setSearchQuery(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + applySearch(searchQuery); + } + }} + endContent={ + searchQuery && ( + + ) + } + size="sm" + /> + ); +}; diff --git a/components/filters/CustomSelectProvider.tsx b/components/filters/CustomSelectProvider.tsx index 222793fdf8..dd4254fdd3 100644 --- a/components/filters/CustomSelectProvider.tsx +++ b/components/filters/CustomSelectProvider.tsx @@ -1,11 +1,14 @@ "use client"; import { Select, SelectItem } from "@nextui-org/react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useState } from "react"; import { CustomProviderInputAWS, CustomProviderInputAzure, CustomProviderInputGCP, + CustomProviderInputKubernetes, } from "./CustomProviderInputs"; const dataInputsProvider = [ @@ -27,11 +30,33 @@ const dataInputsProvider = [ { key: "kubernetes", label: "Kubernetes", - value: , + value: , }, ]; export const CustomSelectProvider = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [selectedProvider, setSelectedProvider] = useState(""); + + const applyProviderFilter = useCallback( + (value: string) => { + const params = new URLSearchParams(searchParams.toString()); + if (value) { + params.set("filter[provider]", value); + } else { + params.delete("filter[provider]"); + } + router.push(`?${params.toString()}`, { scroll: false }); + }, + [router, searchParams], + ); + + useEffect(() => { + const providerFromUrl = searchParams.get("filter[provider]") || ""; + setSelectedProvider(providerFromUrl); + }, [searchParams]); + return ( - + ); }; diff --git a/components/providers/DeleteProvider.tsx b/components/providers/DeleteProvider.tsx index dba2cf22e8..c5090ef274 100644 --- a/components/providers/DeleteProvider.tsx +++ b/components/providers/DeleteProvider.tsx @@ -4,8 +4,8 @@ import { useRef } from "react"; import { deleteProvider } from "@/actions"; +import { CustomButtonClientAction } from "../ui/custom"; import { useToast } from "../ui/toast"; -import { ButtonDeleteProvider } from "./ButtonDeleteProvider"; export const DeleteProvider = ({ id }: { id: string }) => { const ref = useRef(null); @@ -35,7 +35,7 @@ export const DeleteProvider = ({ id }: { id: string }) => { return (
- + ); }; diff --git a/components/providers/index.ts b/components/providers/index.ts index 0b4074236c..58c8dae90f 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -1,8 +1,6 @@ export * from "./AddProvider"; export * from "./AddProviderModal"; export * from "./ButtonAddProvider"; -export * from "./ButtonCheckConnectionProvider"; -export * from "./ButtonDeleteProvider"; export * from "./CheckConnectionProvider"; export * from "./CustomRadioProvider"; export * from "./DateWithTime"; diff --git a/components/providers/table/ColumnsProvider.tsx b/components/providers/table/ColumnsProvider.tsx index 7a6b6d6270..e08050e620 100644 --- a/components/providers/table/ColumnsProvider.tsx +++ b/components/providers/table/ColumnsProvider.tsx @@ -5,9 +5,11 @@ import { Dropdown, DropdownItem, DropdownMenu, + DropdownSection, DropdownTrigger, } from "@nextui-org/react"; import { ColumnDef } from "@tanstack/react-table"; +import clsx from "clsx"; import { add } from "date-fns"; import { VerticalDotsIcon } from "@/components/icons"; @@ -19,11 +21,19 @@ import { DateWithTime } from "../DateWithTime"; import { DeleteProvider } from "../DeleteProvider"; import { ProviderInfo } from "../ProviderInfo"; import { DataTableColumnHeader } from "./DataTableColumnHeader"; - const getProviderData = (row: { original: ProviderProps }) => { return row.original; }; +import { + AddNoteBulkIcon, + DeleteDocumentBulkIcon, + EditDocumentBulkIcon, +} from "@nextui-org/shared-icons"; + +const iconClasses = + "text-2xl text-default-500 pointer-events-none flex-shrink-0"; + export const ColumnsProvider: ColumnDef[] = [ { header: "ID", @@ -117,19 +127,55 @@ export const ColumnsProvider: ColumnDef[] = [ const { id } = getProviderData(row); return (
- + - - - - - - - + + + } + > + + + + } + > + Edit provider + + + + + } + > + + +
diff --git a/components/providers/table/DataTableColumnHeader.tsx b/components/providers/table/DataTableColumnHeader.tsx index 6051a3ef87..3b460ae937 100644 --- a/components/providers/table/DataTableColumnHeader.tsx +++ b/components/providers/table/DataTableColumnHeader.tsx @@ -27,7 +27,8 @@ export const DataTableColumnHeader = ({ const searchParams = useSearchParams(); const getToggleSortingHandler = () => { - const currentSortParam = searchParams.get("sort"); + const currentParams = new URLSearchParams(searchParams.toString()); + const currentSortParam = currentParams.get("sort"); let newSortParam = ""; if (currentSortParam === `${param}`) { @@ -41,13 +42,21 @@ export const DataTableColumnHeader = ({ newSortParam = `${param}`; } - // Construct the new URL with the sorting parameter - const newUrl = newSortParam ? `${pathname}?sort=${newSortParam}` : pathname; + // Update or remove the sort parameter + if (newSortParam) { + currentParams.set("sort", newSortParam); + } else { + currentParams.delete("sort"); + } + + // Construct the new URL with all parameters + const newUrl = `${pathname}?${currentParams.toString()}`; router.push(newUrl, { scroll: false, }); }; + const renderSortIcon = () => { const currentSortParam = searchParams.get("sort"); if ( diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index 2a037faf4f..b89fa2a6c6 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -2,8 +2,10 @@ import { ColumnDef, + ColumnFiltersState, flexRender, getCoreRowModel, + getFilteredRowModel, getPaginationRowModel, getSortedRowModel, SortingState, @@ -22,6 +24,7 @@ import { import { MetaDataProps } from "@/types"; import { DataTablePagination } from "./DataTablePagination"; +import { FilterColumnTable } from "./FilterColumnTable"; interface DataTableProviderProps { columns: ColumnDef[]; @@ -35,6 +38,7 @@ export function DataTableProvider({ metadata, }: DataTableProviderProps) { const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); const table = useReactTable({ data, columns, @@ -43,12 +47,27 @@ export function DataTableProvider({ getPaginationRowModel: getPaginationRowModel(), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), state: { sorting, + columnFilters, }, }); + + // This will be used to have "custom" filters in the table and will be + // passed to the FilterColumnTable component. This needs to be dynamic + // based on the columns that are available in the table. + + const filters = [ + { key: "provider", values: ["aws", "gcp", "azure"] }, + { key: "connected", values: ["false", "true"] }, + // Add more filter categories as needed + ]; + return ( <> +
diff --git a/components/providers/table/FilterColumnTable.tsx b/components/providers/table/FilterColumnTable.tsx new file mode 100644 index 0000000000..42c187a204 --- /dev/null +++ b/components/providers/table/FilterColumnTable.tsx @@ -0,0 +1,58 @@ +import { Button } from "@nextui-org/react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useMemo } from "react"; + +interface FilterColumnTableProps { + filters: { key: string; values: string[] }[]; +} + +export function FilterColumnTable({ filters }: FilterColumnTableProps) { + const router = useRouter(); + const searchParams = useSearchParams(); + + const activeFilters = useMemo(() => { + const currentFilters: Record = {}; + Array.from(searchParams.entries()).forEach(([key, value]) => { + if (key.startsWith("filter[") && key.endsWith("]")) { + const filterKey = key.slice(7, -1); + if (filters.some((filter) => filter.key === filterKey)) { + currentFilters[filterKey] = value; + } + } + }); + return currentFilters; + }, [searchParams, filters]); + + const applyFilter = useCallback( + (key: string, value: string) => { + const params = new URLSearchParams(searchParams); + const filterKey = `filter[${key}]`; + + if (params.get(filterKey) === value) { + params.delete(filterKey); + } else { + params.set(filterKey, value); + } + + router.push(`?${params.toString()}`); + }, + [router, searchParams], + ); + + return ( +
+ {filters.flatMap(({ key, values }) => + values.map((value) => ( + + )), + )} +
+ ); +} diff --git a/components/ui/custom/CustomButtonClientAction.tsx b/components/ui/custom/CustomButtonClientAction.tsx new file mode 100644 index 0000000000..ef31fbb5ec --- /dev/null +++ b/components/ui/custom/CustomButtonClientAction.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { Button, CircularProgress } from "@nextui-org/react"; +import clsx from "clsx"; +import { useFormStatus } from "react-dom"; + +interface CustomButtonClientActionProps { + buttonLabel: string; + danger?: boolean; +} + +export const CustomButtonClientAction = ({ + buttonLabel, + danger = false, +}: CustomButtonClientActionProps) => { + const { pending } = useFormStatus(); + + return ( + + ); +}; diff --git a/components/ui/custom/index.ts b/components/ui/custom/index.ts index 126110ade6..dbc111a3cc 100644 --- a/components/ui/custom/index.ts +++ b/components/ui/custom/index.ts @@ -1,2 +1,3 @@ export * from "./CustomBox"; +export * from "./CustomButtonClientAction"; export * from "./CustomInput"; diff --git a/hooks/index.ts b/hooks/index.ts index 709aadd864..b32251b922 100644 --- a/hooks/index.ts +++ b/hooks/index.ts @@ -1 +1,2 @@ +export * from "./useDebounce"; export * from "./useLocalStorage"; diff --git a/hooks/useDebounce.ts b/hooks/useDebounce.ts new file mode 100644 index 0000000000..f0d8d7c3bf --- /dev/null +++ b/hooks/useDebounce.ts @@ -0,0 +1,15 @@ +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay ?? 900); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/store/ui/ui-store.ts b/store/ui/ui-store.ts index b47d474c94..db9e8e6674 100644 --- a/store/ui/ui-store.ts +++ b/store/ui/ui-store.ts @@ -11,7 +11,7 @@ interface SidebarStoreState { export const useUIStore = create()( persist( (set) => ({ - isSideMenuOpen: true, + isSideMenuOpen: false, openSideMenu: () => set({ isSideMenuOpen: true }), closeSideMenu: () => set({ isSideMenuOpen: false }), }), diff --git a/types/components.ts b/types/components.ts index 0fc88a2190..bd2502a44b 100644 --- a/types/components.ts +++ b/types/components.ts @@ -9,12 +9,9 @@ export type IconProps = { style?: React.CSSProperties; }; -export interface searchParamsProps { - searchParams: { - page?: string; - }; +export interface SearchParamsProps { + [key: string]: string | string[] | undefined; } - export interface ProviderProps { id: string; type: "providers";